feat: support uploading text and document files

This commit is contained in:
vgcman16 2025-06-05 17:56:30 -05:00
parent 95e62853a8
commit 9968b9c51b
8 changed files with 335 additions and 25 deletions

View File

@ -26,6 +26,7 @@ import type { ModelInfo } from '~/lib/modules/llm/types';
import ProgressCompilation from './ProgressCompilation';
import type { ProgressAnnotation } from '~/types/context';
import type { ActionRunner } from '~/lib/runtime/action-runner';
import { extractTextFromFile } from '~/utils/fileExtract';
import { SupabaseChatAlert } from '~/components/chat/SupabaseAlert';
import { expoUrlAtom } from '~/lib/stores/qrCodeStore';
import { useStore } from '@nanostores/react';
@ -64,6 +65,8 @@ interface BaseChatProps {
setUploadedFiles?: (files: File[]) => void;
imageDataList?: string[];
setImageDataList?: (dataList: string[]) => void;
textDataList?: string[];
setTextDataList?: (dataList: string[]) => void;
actionAlert?: ActionAlert;
clearAlert?: () => void;
supabaseAlert?: SupabaseAlert;
@ -108,6 +111,8 @@ export const BaseChat = React.forwardRef<HTMLDivElement, BaseChatProps>(
setUploadedFiles,
imageDataList = [],
setImageDataList,
textDataList = [],
setTextDataList,
messages,
actionAlert,
clearAlert,
@ -284,20 +289,28 @@ export const BaseChat = React.forwardRef<HTMLDivElement, BaseChatProps>(
const handleFileUpload = () => {
const input = document.createElement('input');
input.type = 'file';
input.accept = 'image/*';
input.accept =
'image/*,.pdf,.docx,.txt,.md,.js,.ts,.tsx,.html,.css,.json';
input.onchange = async (e) => {
const file = (e.target as HTMLInputElement).files?.[0];
if (file) {
const reader = new FileReader();
reader.onload = (e) => {
const base64Image = e.target?.result as string;
if (file.type.startsWith('image/')) {
const reader = new FileReader();
reader.onload = (ev) => {
const base64Image = ev.target?.result as string;
setUploadedFiles?.([...uploadedFiles, file]);
setImageDataList?.([...imageDataList, base64Image]);
setTextDataList?.([...textDataList, '']);
};
reader.readAsDataURL(file);
} else {
const text = await extractTextFromFile(file);
setUploadedFiles?.([...uploadedFiles, file]);
setImageDataList?.([...imageDataList, base64Image]);
};
reader.readAsDataURL(file);
setImageDataList?.([...imageDataList, '']);
setTextDataList?.([...textDataList, text]);
}
}
};
@ -312,20 +325,25 @@ export const BaseChat = React.forwardRef<HTMLDivElement, BaseChatProps>(
}
for (const item of items) {
if (item.type.startsWith('image/')) {
if (item.kind === 'file') {
const file = item.getAsFile();
if (!file) continue;
e.preventDefault();
const file = item.getAsFile();
if (file) {
if (file.type.startsWith('image/')) {
const reader = new FileReader();
reader.onload = (e) => {
const base64Image = e.target?.result as string;
reader.onload = (ev) => {
const base64Image = ev.target?.result as string;
setUploadedFiles?.([...uploadedFiles, file]);
setImageDataList?.([...imageDataList, base64Image]);
setTextDataList?.([...textDataList, '']);
};
reader.readAsDataURL(file);
} else {
const text = await extractTextFromFile(file);
setUploadedFiles?.([...uploadedFiles, file]);
setImageDataList?.([...imageDataList, '']);
setTextDataList?.([...textDataList, text]);
}
break;
@ -432,6 +450,8 @@ export const BaseChat = React.forwardRef<HTMLDivElement, BaseChatProps>(
setUploadedFiles={setUploadedFiles}
imageDataList={imageDataList}
setImageDataList={setImageDataList}
textDataList={textDataList}
setTextDataList={setTextDataList}
textareaRef={textareaRef}
input={input}
handleInputChange={handleInputChange}

View File

@ -25,7 +25,8 @@ import { createSampler } from '~/utils/sampler';
import { getTemplates, selectStarterTemplate } from '~/utils/selectStarterTemplate';
import { logStore } from '~/lib/stores/logs';
import { streamingState } from '~/lib/stores/streaming';
import { filesToArtifacts } from '~/utils/fileUtils';
import { filesToArtifacts, uploadedFilesToArtifacts } from '~/utils/fileUtils';
import { escapeBoltTags } from '~/utils/projectCommands';
import { supabaseConnection } from '~/lib/stores/supabase';
import { defaultDesignScheme, type DesignScheme } from '~/types/design-scheme';
import type { ElementInfo } from '~/components/workbench/Inspector';
@ -123,6 +124,7 @@ export const ChatImpl = memo(
const [chatStarted, setChatStarted] = useState(initialMessages.length > 0);
const [uploadedFiles, setUploadedFiles] = useState<File[]>([]);
const [imageDataList, setImageDataList] = useState<string[]>([]);
const [textDataList, setTextDataList] = useState<string[]>([]);
const [searchParams, setSearchParams] = useSearchParams();
const [fakeLoading, setFakeLoading] = useState(false);
const files = useStore(workbenchStore.files);
@ -353,7 +355,7 @@ export const ChatImpl = memo(
content: [
{
type: 'text',
text: `[Model: ${model}]\n\n[Provider: ${provider.name}]\n\n${finalMessageContent}`,
text: `[Model: ${model}]\n\n[Provider: ${provider.name}]\n\n${uploadArtifact}${finalMessageContent}`,
},
...imageDataList.map((imageData) => ({
type: 'image',
@ -379,6 +381,7 @@ export const ChatImpl = memo(
setUploadedFiles([]);
setImageDataList([]);
setTextDataList([]);
resetEnhancer();
@ -398,7 +401,7 @@ export const ChatImpl = memo(
content: [
{
type: 'text',
text: `[Model: ${model}]\n\n[Provider: ${provider.name}]\n\n${finalMessageContent}`,
text: `[Model: ${model}]\n\n[Provider: ${provider.name}]\n\n${uploadArtifact}${finalMessageContent}`,
},
...imageDataList.map((imageData) => ({
type: 'image',
@ -414,6 +417,7 @@ export const ChatImpl = memo(
setUploadedFiles([]);
setImageDataList([]);
setTextDataList([]);
resetEnhancer();
@ -430,6 +434,18 @@ export const ChatImpl = memo(
chatStore.setKey('aborted', false);
const uploadedMap: { [path: string]: string } = {};
uploadedFiles.forEach((file, idx) => {
const text = textDataList[idx];
if (text) {
uploadedMap[file.name] = escapeBoltTags(text);
}
});
const uploadArtifact =
Object.keys(uploadedMap).length > 0
? uploadedFilesToArtifacts(uploadedMap, `uploaded-${Date.now()}`)
: '';
if (modifiedFiles !== undefined) {
const userUpdateArtifact = filesToArtifacts(modifiedFiles, `${Date.now()}`);
append({
@ -437,7 +453,7 @@ export const ChatImpl = memo(
content: [
{
type: 'text',
text: `[Model: ${model}]\n\n[Provider: ${provider.name}]\n\n${userUpdateArtifact}${finalMessageContent}`,
text: `[Model: ${model}]\n\n[Provider: ${provider.name}]\n\n${uploadArtifact}${userUpdateArtifact}${finalMessageContent}`,
},
...imageDataList.map((imageData) => ({
type: 'image',
@ -453,7 +469,7 @@ export const ChatImpl = memo(
content: [
{
type: 'text',
text: `[Model: ${model}]\n\n[Provider: ${provider.name}]\n\n${finalMessageContent}`,
text: `[Model: ${model}]\n\n[Provider: ${provider.name}]\n\n${uploadArtifact}${finalMessageContent}`,
},
...imageDataList.map((imageData) => ({
type: 'image',
@ -468,6 +484,7 @@ export const ChatImpl = memo(
setUploadedFiles([]);
setImageDataList([]);
setTextDataList([]);
resetEnhancer();
@ -565,6 +582,8 @@ export const ChatImpl = memo(
setUploadedFiles={setUploadedFiles}
imageDataList={imageDataList}
setImageDataList={setImageDataList}
textDataList={textDataList}
setTextDataList={setTextDataList}
actionAlert={actionAlert}
clearAlert={() => workbenchStore.clearAlert()}
supabaseAlert={supabaseAlert}

View File

@ -10,6 +10,7 @@ import { ScreenshotStateManager } from './ScreenshotStateManager';
import { SendButton } from './SendButton.client';
import { IconButton } from '~/components/ui/IconButton';
import { toast } from 'react-toastify';
import { extractTextFromFile } from '~/utils/fileExtract';
import { SpeechRecognitionButton } from '~/components/chat/SpeechRecognition';
import { SupabaseConnection } from './SupabaseConnection';
import { ExpoQrModal } from '~/components/workbench/ExpoQrModal';
@ -50,6 +51,8 @@ interface ChatBoxProps {
setModel?: ((model: string) => void) | undefined;
setUploadedFiles?: ((files: File[]) => void) | undefined;
setImageDataList?: ((dataList: string[]) => void) | undefined;
textDataList: string[];
setTextDataList?: ((dataList: string[]) => void) | undefined;
handleInputChange?: ((event: React.ChangeEvent<HTMLTextAreaElement>) => void) | undefined;
handleStop?: (() => void) | undefined;
enhancingPrompt?: boolean | undefined;
@ -134,9 +137,11 @@ export const ChatBox: React.FC<ChatBoxProps> = (props) => {
<FilePreview
files={props.uploadedFiles}
imageDataList={props.imageDataList}
textDataList={props.textDataList}
onRemove={(index) => {
props.setUploadedFiles?.(props.uploadedFiles.filter((_, i) => i !== index));
props.setImageDataList?.(props.imageDataList.filter((_, i) => i !== index));
props.setTextDataList?.(props.textDataList.filter((_, i) => i !== index));
}}
/>
<ClientOnly>
@ -192,16 +197,22 @@ export const ChatBox: React.FC<ChatBoxProps> = (props) => {
e.currentTarget.style.border = '1px solid var(--bolt-elements-borderColor)';
const files = Array.from(e.dataTransfer.files);
files.forEach((file) => {
files.forEach(async (file) => {
if (file.type.startsWith('image/')) {
const reader = new FileReader();
reader.onload = (e) => {
const base64Image = e.target?.result as string;
reader.onload = (ev) => {
const base64Image = ev.target?.result as string;
props.setUploadedFiles?.([...props.uploadedFiles, file]);
props.setImageDataList?.([...props.imageDataList, base64Image]);
props.setTextDataList?.([...props.textDataList, '']);
};
reader.readAsDataURL(file);
} else {
const text = await extractTextFromFile(file);
props.setUploadedFiles?.([...props.uploadedFiles, file]);
props.setImageDataList?.([...props.imageDataList, '']);
props.setTextDataList?.([...props.textDataList, text]);
}
});
}}

View File

@ -3,10 +3,11 @@ import React from 'react';
interface FilePreviewProps {
files: File[];
imageDataList: string[];
textDataList: string[];
onRemove: (index: number) => void;
}
const FilePreview: React.FC<FilePreviewProps> = ({ files, imageDataList, onRemove }) => {
const FilePreview: React.FC<FilePreviewProps> = ({ files, imageDataList, textDataList, onRemove }) => {
if (!files || files.length === 0) {
return null;
}
@ -15,7 +16,7 @@ const FilePreview: React.FC<FilePreviewProps> = ({ files, imageDataList, onRemov
<div className="flex flex-row overflow-x-auto mx-2 -mt-1 p-2 bg-bolt-elements-background-depth-3 border border-b-none border-bolt-elements-borderColor rounded-lg rounded-b-none">
{files.map((file, index) => (
<div key={file.name + file.size} className="mr-2 relative">
{imageDataList[index] && (
{imageDataList[index] ? (
<div className="relative">
<img src={imageDataList[index]} alt={file.name} className="max-h-20 rounded-lg" />
<button
@ -28,6 +29,19 @@ const FilePreview: React.FC<FilePreviewProps> = ({ files, imageDataList, onRemov
<span className="truncate">{file.name}</span>
</div>
</div>
) : (
<div className="relative flex items-center justify-center w-20 h-20 bg-bolt-elements-background-depth-2 rounded-lg">
<div className="i-ph:file-text text-xl" />
<button
onClick={() => onRemove(index)}
className="absolute -top-1 -right-1 z-10 bg-black rounded-full w-5 h-5 shadow-md hover:bg-gray-900 transition-colors flex items-center justify-center"
>
<div className="i-ph:x w-3 h-3 text-gray-200" />
</button>
<div className="absolute bottom-0 w-full h-5 flex items-center px-2 rounded-b-lg text-bolt-elements-textTertiary font-thin text-xs">
<span className="truncate">{file.name}</span>
</div>
</div>
)}
</div>
))}

31
app/utils/fileExtract.ts Normal file
View File

@ -0,0 +1,31 @@
import * as pdfjsLib from 'pdfjs-dist/build/pdf.mjs';
import pdfWorker from 'pdfjs-dist/build/pdf.worker.mjs';
import mammoth from 'mammoth/mammoth.browser';
pdfjsLib.GlobalWorkerOptions.workerSrc = pdfWorker;
export async function extractTextFromFile(file: File): Promise<string> {
if (file.type === 'application/pdf' || file.name.toLowerCase().endsWith('.pdf')) {
const arrayBuffer = await file.arrayBuffer();
const pdf = await pdfjsLib.getDocument({ data: arrayBuffer }).promise;
const texts: string[] = [];
for (let i = 1; i <= pdf.numPages; i++) {
const page = await pdf.getPage(i);
const content = await page.getTextContent();
texts.push(content.items.map((item: any) => item.str).join(' '));
}
return texts.join('\n');
}
if (
file.type ===
'application/vnd.openxmlformats-officedocument.wordprocessingml.document' ||
file.name.toLowerCase().endsWith('.docx')
) {
const arrayBuffer = await file.arrayBuffer();
const result = await mammoth.extractRawText({ arrayBuffer });
return result.value;
}
return file.text();
}

View File

@ -119,3 +119,22 @@ ${files[filePath].content}
</boltArtifact>
`;
};
export const uploadedFilesToArtifacts = (
files: { [path: string]: string },
id: string,
): string => {
return `
<boltArtifact id="${id}" title="Uploaded Files">
${Object.keys(files)
.map(
(filePath) => `
<boltAction type="file" filePath="${filePath}">
${files[filePath]}
</boltAction>
`,
)
.join('\n')}
</boltArtifact>
`;
};

View File

@ -123,10 +123,12 @@
"jspdf": "^2.5.2",
"jszip": "^3.10.1",
"lucide-react": "^0.485.0",
"mammoth": "^1.9.1",
"mime": "^4.0.4",
"nanostores": "^0.10.3",
"ollama-ai-provider": "^0.15.2",
"path-browserify": "^1.0.1",
"pdfjs-dist": "^5.3.31",
"react": "^18.3.1",
"react-beautiful-dnd": "^13.1.1",
"react-chartjs-2": "^5.3.0",

View File

@ -248,6 +248,9 @@ importers:
lucide-react:
specifier: ^0.485.0
version: 0.485.0(react@18.3.1)
mammoth:
specifier: ^1.9.1
version: 1.9.1
mime:
specifier: ^4.0.4
version: 4.0.6
@ -260,6 +263,9 @@ importers:
path-browserify:
specifier: ^1.0.1
version: 1.0.1
pdfjs-dist:
specifier: ^5.3.31
version: 5.3.31
react:
specifier: ^18.3.1
version: 18.3.1
@ -2095,6 +2101,70 @@ packages:
nanostores: ^0.9.0 || ^0.10.0 || ^0.11.0
react: '>=18.0.0'
'@napi-rs/canvas-android-arm64@0.1.70':
resolution: {integrity: sha512-I/YOuQ0wbkVYxVaYtCgN42WKTYxNqFA0gTcTrHIGG1jfpDSyZWII/uHcjOo4nzd19io6Y4+/BqP8E5hJgf9OmQ==}
engines: {node: '>= 10'}
cpu: [arm64]
os: [android]
'@napi-rs/canvas-darwin-arm64@0.1.70':
resolution: {integrity: sha512-4pPGyXetHIHkw2TOJHujt3mkCP8LdDu8+CT15ld9Id39c752RcI0amDHSuMLMQfAjvusA9B5kKxazwjMGjEJpQ==}
engines: {node: '>= 10'}
cpu: [arm64]
os: [darwin]
'@napi-rs/canvas-darwin-x64@0.1.70':
resolution: {integrity: sha512-+2N6Os9LbkmDMHL+raknrUcLQhsXzc5CSXRbXws9C3pv/mjHRVszQ9dhFUUe9FjfPhCJznO6USVdwOtu7pOrzQ==}
engines: {node: '>= 10'}
cpu: [x64]
os: [darwin]
'@napi-rs/canvas-linux-arm-gnueabihf@0.1.70':
resolution: {integrity: sha512-QjscX9OaKq/990sVhSMj581xuqLgiaPVMjjYvWaCmAJRkNQ004QfoSMEm3FoTqM4DRoquP8jvuEXScVJsc1rqQ==}
engines: {node: '>= 10'}
cpu: [arm]
os: [linux]
'@napi-rs/canvas-linux-arm64-gnu@0.1.70':
resolution: {integrity: sha512-LNakMOwwqwiHIwMpnMAbFRczQMQ7TkkMyATqFCOtUJNlE6LPP/QiUj/mlFrNbUn/hctqShJ60gWEb52ZTALbVw==}
engines: {node: '>= 10'}
cpu: [arm64]
os: [linux]
'@napi-rs/canvas-linux-arm64-musl@0.1.70':
resolution: {integrity: sha512-wBTOllEYNfJCHOdZj9v8gLzZ4oY3oyPX8MSRvaxPm/s7RfEXxCyZ8OhJ5xAyicsDdbE5YBZqdmaaeP5+xKxvtg==}
engines: {node: '>= 10'}
cpu: [arm64]
os: [linux]
'@napi-rs/canvas-linux-riscv64-gnu@0.1.70':
resolution: {integrity: sha512-GVUUPC8TuuFqHip0rxHkUqArQnlzmlXmTEBuXAWdgCv85zTCFH8nOHk/YCF5yo0Z2eOm8nOi90aWs0leJ4OE5Q==}
engines: {node: '>= 10'}
cpu: [riscv64]
os: [linux]
'@napi-rs/canvas-linux-x64-gnu@0.1.70':
resolution: {integrity: sha512-/kvUa2lZRwGNyfznSn5t1ShWJnr/m5acSlhTV3eXECafObjl0VBuA1HJw0QrilLpb4Fe0VLywkpD1NsMoVDROQ==}
engines: {node: '>= 10'}
cpu: [x64]
os: [linux]
'@napi-rs/canvas-linux-x64-musl@0.1.70':
resolution: {integrity: sha512-aqlv8MLpycoMKRmds7JWCfVwNf1fiZxaU7JwJs9/ExjTD8lX2KjsO7CTeAj5Cl4aEuzxUWbJPUUE2Qu9cZ1vfg==}
engines: {node: '>= 10'}
cpu: [x64]
os: [linux]
'@napi-rs/canvas-win32-x64-msvc@0.1.70':
resolution: {integrity: sha512-Q9QU3WIpwBTVHk4cPfBjGHGU4U0llQYRXgJtFtYqqGNEOKVN4OT6PQ+ve63xwIPODMpZ0HHyj/KLGc9CWc3EtQ==}
engines: {node: '>= 10'}
cpu: [x64]
os: [win32]
'@napi-rs/canvas@0.1.70':
resolution: {integrity: sha512-nD6NGa4JbNYSZYsTnLGrqe9Kn/lCkA4ybXt8sx5ojDqZjr2i0TWAHxx/vhgfjX+i3hCdKWufxYwi7CfXqtITSA==}
engines: {node: '>= 10'}
'@nodelib/fs.scandir@2.1.5':
resolution: {integrity: sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g==}
engines: {node: '>= 8'}
@ -3683,6 +3753,9 @@ packages:
arg@5.0.2:
resolution: {integrity: sha512-PYjyFOLKQ9y57JvQ6QLo8dAgNqswh8M1RMJYdQduT6xbWSgK36P/Z/v+p888pM69jMMfS8Xd8F6I1kQ/I9HUGg==}
argparse@1.0.10:
resolution: {integrity: sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg==}
argparse@2.0.1:
resolution: {integrity: sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==}
@ -3791,6 +3864,9 @@ packages:
bluebird-lst@1.0.9:
resolution: {integrity: sha512-7B1Rtx82hjnSD4PGLAjVWeYH3tHAcVUmChh85a3lltKQm6FresXh9ErQo6oAv6CqxttczC3/kEg8SY5NluPuUw==}
bluebird@3.4.7:
resolution: {integrity: sha512-iD3898SR7sWVRHbiQv+sHUtHnMvC1o3nW5rAcqnq3uOn07DSAppZYUkIGslDz6gXC7HfunPe7YVBgoEJASPcHA==}
bluebird@3.7.2:
resolution: {integrity: sha512-XpNj6GDQzdfW+r2Wnn7xiSAd7TM3jzkxGXBGTtWKuSXv1xUV+azxAm8jdWZN06QTQk+2N2XB9jRDkvbmQmcRtg==}
@ -4407,6 +4483,9 @@ packages:
diffie-hellman@5.0.3:
resolution: {integrity: sha512-kqag/Nl+f3GwyK25fhUMYj81BUOrZ9IuJsjIcDE5icNM9FJHAVm3VcUDxdLPoQtTuUylWm6ZIknYJwwaPxsUzg==}
dingbat-to-unicode@1.0.1:
resolution: {integrity: sha512-98l0sW87ZT58pU4i61wa2OHwxbiYSbuxsCBozaVnYX2iCnr3bLM3fIes1/ej7h1YdOKuKt/MLs706TVnALA65w==}
dir-compare@4.2.0:
resolution: {integrity: sha512-2xMCmOoMrdQIPHdsTawECdNPwlVFB9zGcz3kuhmBO6U3oU+UQjsue0i8ayLKpgBcm+hcXPMVSGUN9d+pvJ6+VQ==}
@ -4447,6 +4526,9 @@ packages:
resolution: {integrity: sha512-47qPchRCykZC03FhkYAhrvwU4xDBFIj1QPqaarj6mdM/hgUzfPHcpkHJOn3mJAufFeeAxAzeGsr5X0M4k6fLZQ==}
engines: {node: '>=12'}
duck@0.1.12:
resolution: {integrity: sha512-wkctla1O6VfP89gQ+J/yDesM0S7B7XLXjKGzXxMDVFg7uEn706niAtyYovKbyq1oT9YwDcly721/iUWoc8MVRg==}
dunder-proto@1.0.1:
resolution: {integrity: sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A==}
engines: {node: '>= 0.4'}
@ -5644,6 +5726,9 @@ packages:
resolution: {integrity: sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q==}
hasBin: true
lop@0.4.2:
resolution: {integrity: sha512-RefILVDQ4DKoRZsJ4Pj22TxE3omDO47yFpkIBoDKzkqPRISs5U1cnAdg/5583YPkWPaLIYHOKRMQSvjFsO26cw==}
loupe@3.1.3:
resolution: {integrity: sha512-kkIp7XSkP78ZxJEsSxW3712C6teJVoeHHwgo9zJ380de7IYyJ2ISlxojcH2pC5OFLewESmnRi/+XCDIEEVyoug==}
@ -5684,6 +5769,11 @@ packages:
resolution: {integrity: sha512-NgOPbRiaQM10DYXvN3/hhGVI2M5MtITFryzBGxHM5p4wnFxsVCbxkrBrDsk+EZ5OB4jEOT7AjDxtdF+KVEFT7w==}
engines: {node: ^12.13.0 || ^14.15.0 || >=16.0.0}
mammoth@1.9.1:
resolution: {integrity: sha512-4S2v1eP4Yo4so0zGNicJKcP93su3wDPcUk+xvkjSG75nlNjSkDJu8BhWQ+e54BROM0HfA6nPzJn12S6bq2Ko6w==}
engines: {node: '>=12.0.0'}
hasBin: true
markdown-extensions@1.1.1:
resolution: {integrity: sha512-WWC0ZuMzCyDHYCasEGs4IPvLyTGftYwh6wIEOULOF0HXcqZlhwRzrK0w2VUlxWA98xnvb/jszw4ZSkJ6ADpM6Q==}
engines: {node: '>=0.10.0'}
@ -6336,6 +6426,9 @@ packages:
oniguruma-to-es@2.3.0:
resolution: {integrity: sha512-bwALDxriqfKGfUufKGGepCzu9x7nJQuoRoAFp4AnwehhC2crqrDIAP/uN2qdlsAvSMpeRC3+Yzhqc7hLmle5+g==}
option@0.2.4:
resolution: {integrity: sha512-pkEqbDyl8ou5cpq+VsnQbe/WlEy5qS7xPzMS1U55OCG9KPvwFD46zDbxQIj3egJSFc3D+XhYOPUzz49zQAVy7A==}
optionator@0.9.4:
resolution: {integrity: sha512-6IpQ7mKUxRcZNLIObR0hz7lxsapSSIYNZJwXPGeF0mTVqGKFIXj1DQcMoT22S3ROcLyY/rz0PWaWZ9ayWmad9g==}
engines: {node: '>= 0.8.0'}
@ -6445,6 +6538,10 @@ packages:
resolution: {integrity: sha512-iuh7L6jA7JEGu2WxDwtQP1ddOpaJNC4KlDEFfdQajSGgGPNi4OyDc2R7QnbY2bR9QjBVGwgvTdNJZoE7RaxUMA==}
engines: {node: '>=0.12'}
pdfjs-dist@5.3.31:
resolution: {integrity: sha512-EhPdIjNX0fcdwYQO+e3BAAJPXt+XI29TZWC7COhIXs/K0JHcUt1Gdz1ITpebTwVMFiLsukdUZ3u0oTO7jij+VA==}
engines: {node: '>=20.16.0 || >=22.3.0'}
pe-library@0.4.1:
resolution: {integrity: sha512-eRWB5LBz7PpDu4PUlwT0PhnQfTQJlDDdPa35urV4Osrm0t0AqQFGn+UIkU3klZvwJ8KPO3VbBFsXquA6p6kqZw==}
engines: {node: '>=12', npm: '>=6'}
@ -7375,6 +7472,9 @@ packages:
spdx-license-ids@3.0.21:
resolution: {integrity: sha512-Bvg/8F5XephndSK3JffaRqdT+gyhfqIPwDHpX80tJrF8QQRYMo8sNMeaZ2Dp5+jhwKnUmIOyFFQfHRkjJm5nXg==}
sprintf-js@1.0.3:
resolution: {integrity: sha512-D9cPgkvLlV3t3IzL0D0YLvGA9Ahk4PcvVwUbN0dSGr1aP0Nrt4AEnTUbuGvquEC0mA64Gqt1fzirlRs5ibXx8g==}
sprintf-js@1.1.3:
resolution: {integrity: sha512-Oo+0REFV59/rz3gfJNKQiBlwfHaSESl1pcGyABQsnnIfWOFt6JNj5gCog2U6MLZ//IGYD+nA8nI+mTShREReaA==}
@ -7717,6 +7817,9 @@ packages:
unconfig@0.5.5:
resolution: {integrity: sha512-VQZ5PT9HDX+qag0XdgQi8tJepPhXiR/yVOkn707gJDKo31lGjRilPREiQJ9Z6zd/Ugpv6ZvO5VxVIcatldYcNQ==}
underscore@1.13.7:
resolution: {integrity: sha512-GMXzWtsc57XAtguZgaQViUOzs0KTkk8ojr3/xAxXLITqf/3EMwxC0inyETfDFjH/Krbhuep0HNbbjI9i/q3F3g==}
undici-types@6.19.8:
resolution: {integrity: sha512-ve2KP6f/JnbPBFyobGHuerC9g1FYGn/F8n1LWTwNxCEzd6IfqTwUQcNXgEtmmQ6DlRrC1hrSrBnCZPokRrDHjw==}
@ -8179,6 +8282,10 @@ packages:
resolution: {integrity: sha512-EvGK8EJ3DhaHfbRlETOWAS5pO9MZITeauHKJyb8wyajUfQUenkIg2MvLDTZ4T/TgIcm3HU0TFBgWWboAZ30UHg==}
engines: {node: '>=18'}
xmlbuilder@10.1.1:
resolution: {integrity: sha512-OyzrcFLL/nb6fMGHbiRDuPup9ljBycsdCypwuyg5AAHvyWzGfChJpCXMG88AGTIMFhGZ9RccFN1e6lhg3hkwKg==}
engines: {node: '>=4.0'}
xmlbuilder@15.1.1:
resolution: {integrity: sha512-yMqGBqtXyeN1e3TGYvgNgDVZ3j84W4cwkOXQswghol6APgZWaff9lnbvN7MHYJOiXsvGPXtjTYJEiC9J2wv9Eg==}
engines: {node: '>=8.0'}
@ -9967,6 +10074,50 @@ snapshots:
nanostores: 0.10.3
react: 18.3.1
'@napi-rs/canvas-android-arm64@0.1.70':
optional: true
'@napi-rs/canvas-darwin-arm64@0.1.70':
optional: true
'@napi-rs/canvas-darwin-x64@0.1.70':
optional: true
'@napi-rs/canvas-linux-arm-gnueabihf@0.1.70':
optional: true
'@napi-rs/canvas-linux-arm64-gnu@0.1.70':
optional: true
'@napi-rs/canvas-linux-arm64-musl@0.1.70':
optional: true
'@napi-rs/canvas-linux-riscv64-gnu@0.1.70':
optional: true
'@napi-rs/canvas-linux-x64-gnu@0.1.70':
optional: true
'@napi-rs/canvas-linux-x64-musl@0.1.70':
optional: true
'@napi-rs/canvas-win32-x64-msvc@0.1.70':
optional: true
'@napi-rs/canvas@0.1.70':
optionalDependencies:
'@napi-rs/canvas-android-arm64': 0.1.70
'@napi-rs/canvas-darwin-arm64': 0.1.70
'@napi-rs/canvas-darwin-x64': 0.1.70
'@napi-rs/canvas-linux-arm-gnueabihf': 0.1.70
'@napi-rs/canvas-linux-arm64-gnu': 0.1.70
'@napi-rs/canvas-linux-arm64-musl': 0.1.70
'@napi-rs/canvas-linux-riscv64-gnu': 0.1.70
'@napi-rs/canvas-linux-x64-gnu': 0.1.70
'@napi-rs/canvas-linux-x64-musl': 0.1.70
'@napi-rs/canvas-win32-x64-msvc': 0.1.70
optional: true
'@nodelib/fs.scandir@2.1.5':
dependencies:
'@nodelib/fs.stat': 2.0.5
@ -11967,6 +12118,10 @@ snapshots:
arg@5.0.2: {}
argparse@1.0.10:
dependencies:
sprintf-js: 1.0.3
argparse@2.0.1: {}
aria-hidden@1.2.4:
@ -12063,6 +12218,8 @@ snapshots:
dependencies:
bluebird: 3.7.2
bluebird@3.4.7: {}
bluebird@3.7.2: {}
bn.js@4.12.1: {}
@ -12748,6 +12905,8 @@ snapshots:
miller-rabin: 4.0.1
randombytes: 2.1.0
dingbat-to-unicode@1.0.1: {}
dir-compare@4.2.0:
dependencies:
minimatch: 3.1.2
@ -12805,6 +12964,10 @@ snapshots:
dotenv@16.4.7: {}
duck@0.1.12:
dependencies:
underscore: 1.13.7
dunder-proto@1.0.1:
dependencies:
call-bind-apply-helpers: 1.0.2
@ -14338,6 +14501,12 @@ snapshots:
dependencies:
js-tokens: 4.0.0
lop@0.4.2:
dependencies:
duck: 0.1.12
option: 0.2.4
underscore: 1.13.7
loupe@3.1.3: {}
lowercase-keys@2.0.0: {}
@ -14390,6 +14559,19 @@ snapshots:
- bluebird
- supports-color
mammoth@1.9.1:
dependencies:
'@xmldom/xmldom': 0.8.10
argparse: 1.0.10
base64-js: 1.5.1
bluebird: 3.4.7
dingbat-to-unicode: 1.0.1
jszip: 3.10.1
lop: 0.4.2
path-is-absolute: 1.0.1
underscore: 1.13.7
xmlbuilder: 10.1.1
markdown-extensions@1.1.1: {}
markdown-table@3.0.4: {}
@ -15466,6 +15648,8 @@ snapshots:
regex: 5.1.1
regex-recursion: 5.1.1
option@0.2.4: {}
optionator@0.9.4:
dependencies:
deep-is: 0.1.4
@ -15581,6 +15765,10 @@ snapshots:
safe-buffer: 5.2.1
sha.js: 2.4.11
pdfjs-dist@5.3.31:
optionalDependencies:
'@napi-rs/canvas': 0.1.70
pe-library@0.4.1: {}
peek-stream@1.1.3:
@ -16584,6 +16772,8 @@ snapshots:
spdx-license-ids@3.0.21: {}
sprintf-js@1.0.3: {}
sprintf-js@1.1.3: {}
ssri@10.0.6:
@ -16910,6 +17100,8 @@ snapshots:
transitivePeerDependencies:
- supports-color
underscore@1.13.7: {}
undici-types@6.19.8: {}
undici-types@6.20.0: {}
@ -17439,6 +17631,8 @@ snapshots:
xml-name-validator@5.0.0: {}
xmlbuilder@10.1.1: {}
xmlbuilder@15.1.1: {}
xmlchars@2.2.0: {}