diff --git a/examples/server/public/index.html.gz b/examples/server/public/index.html.gz index 582ccc0d3..711ccab9d 100644 Binary files a/examples/server/public/index.html.gz and b/examples/server/public/index.html.gz differ diff --git a/examples/server/webui/index.html b/examples/server/webui/index.html index d3893ea4e..844abf54d 100644 --- a/examples/server/webui/index.html +++ b/examples/server/webui/index.html @@ -149,19 +149,42 @@
- +
+ + + + +
+ +
+
+ + + + {{ file.name }} + +
+
diff --git a/examples/server/webui/package-lock.json b/examples/server/webui/package-lock.json index bbebccbf2..04770679e 100644 --- a/examples/server/webui/package-lock.json +++ b/examples/server/webui/package-lock.json @@ -15,6 +15,7 @@ "highlight.js": "^11.10.0", "katex": "^0.16.15", "markdown-it": "^14.1.0", + "pdfjs-dist": "^4.10.38", "postcss": "^8.4.49", "tailwindcss": "^3.4.15", "textlinestream": "^1.1.1", @@ -397,6 +398,177 @@ "node": ">=12" } }, + "node_modules/@napi-rs/canvas": { + "version": "0.1.66", + "resolved": "https://registry.npmjs.org/@napi-rs/canvas/-/canvas-0.1.66.tgz", + "integrity": "sha512-NE/eQKLbUS+LCbMHRa5HnR7cc1Q4ibg/qfLUN4Ukl3CC0lq6LfHE0YbvFm/l4i5RyyS+aUjL+8IuZDD9EH3amg==", + "optional": true, + "engines": { + "node": ">= 10" + }, + "optionalDependencies": { + "@napi-rs/canvas-android-arm64": "0.1.66", + "@napi-rs/canvas-darwin-arm64": "0.1.66", + "@napi-rs/canvas-darwin-x64": "0.1.66", + "@napi-rs/canvas-linux-arm-gnueabihf": "0.1.66", + "@napi-rs/canvas-linux-arm64-gnu": "0.1.66", + "@napi-rs/canvas-linux-arm64-musl": "0.1.66", + "@napi-rs/canvas-linux-riscv64-gnu": "0.1.66", + "@napi-rs/canvas-linux-x64-gnu": "0.1.66", + "@napi-rs/canvas-linux-x64-musl": "0.1.66", + "@napi-rs/canvas-win32-x64-msvc": "0.1.66" + } + }, + "node_modules/@napi-rs/canvas-android-arm64": { + "version": "0.1.66", + "resolved": "https://registry.npmjs.org/@napi-rs/canvas-android-arm64/-/canvas-android-arm64-0.1.66.tgz", + "integrity": "sha512-77Yq9yaUYN90zCovYOpw7LhidJiswU9wLIWWBGF6iiEJyQdt6tkiXpGRZpOMJVO70afkcdc4T7532cxMIBhk0Q==", + "cpu": [ + "arm64" + ], + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@napi-rs/canvas-darwin-arm64": { + "version": "0.1.66", + "resolved": "https://registry.npmjs.org/@napi-rs/canvas-darwin-arm64/-/canvas-darwin-arm64-0.1.66.tgz", + "integrity": "sha512-cz3aJ06b8BZGtwRxKTiE0OVUlB17MH8j+BnE4A5+wD9aD1guCCqECsz+k7tpXdAdTAYKRIz2pq6ZuiJ76NyUbQ==", + "cpu": [ + "arm64" + ], + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@napi-rs/canvas-darwin-x64": { + "version": "0.1.66", + "resolved": "https://registry.npmjs.org/@napi-rs/canvas-darwin-x64/-/canvas-darwin-x64-0.1.66.tgz", + "integrity": "sha512-szIWqJgFm2OTyGzM+hSiJOaOtjI73VYRC2KN30zZTt7i1+0sgpm5exK5ltDBPOmCdnLt7SbUfpInLj8VvxYlKA==", + "cpu": [ + "x64" + ], + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@napi-rs/canvas-linux-arm-gnueabihf": { + "version": "0.1.66", + "resolved": "https://registry.npmjs.org/@napi-rs/canvas-linux-arm-gnueabihf/-/canvas-linux-arm-gnueabihf-0.1.66.tgz", + "integrity": "sha512-h/TZJFc6JLvp8FwbA5mu+yXiblN0iKqshU7xzd6L+ks5uNYgjS7XWLkNiyPQkMaXQgVczOJfZy7r4NSPK3V8Hg==", + "cpu": [ + "arm" + ], + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@napi-rs/canvas-linux-arm64-gnu": { + "version": "0.1.66", + "resolved": "https://registry.npmjs.org/@napi-rs/canvas-linux-arm64-gnu/-/canvas-linux-arm64-gnu-0.1.66.tgz", + "integrity": "sha512-RGFUdBdi0Xmf+TfwZcB89Ap6hDYh4nzyJhXhNJIgve6ELrIPFhf7sDHvUHxjgW0YzczGoo+ophyCm03cJggu+w==", + "cpu": [ + "arm64" + ], + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@napi-rs/canvas-linux-arm64-musl": { + "version": "0.1.66", + "resolved": "https://registry.npmjs.org/@napi-rs/canvas-linux-arm64-musl/-/canvas-linux-arm64-musl-0.1.66.tgz", + "integrity": "sha512-2cFViDIZ0xQlAHyJmyym+rj3p04V16vgAiz64sCAfwOOiW6e19agv1HQWHUsro3G2lF3PaHGAnp0WRPXGqLOfg==", + "cpu": [ + "arm64" + ], + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@napi-rs/canvas-linux-riscv64-gnu": { + "version": "0.1.66", + "resolved": "https://registry.npmjs.org/@napi-rs/canvas-linux-riscv64-gnu/-/canvas-linux-riscv64-gnu-0.1.66.tgz", + "integrity": "sha512-Vm5ZWS2RDPeBpnfx83eJpZfJT07xl0jqp8d83PklKqiDNa3BmDZZ/uuI40/ICgejGLymXXYo5N21b7oAxhRTSA==", + "cpu": [ + "riscv64" + ], + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@napi-rs/canvas-linux-x64-gnu": { + "version": "0.1.66", + "resolved": "https://registry.npmjs.org/@napi-rs/canvas-linux-x64-gnu/-/canvas-linux-x64-gnu-0.1.66.tgz", + "integrity": "sha512-/ptGBhErNBCgWff3khtuEjhiiYWf70oWvBPRj8y5EMB0nLYpve7RxxFnavVvxN49kJ0MQHRIwgfyd47RSOOKPw==", + "cpu": [ + "x64" + ], + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@napi-rs/canvas-linux-x64-musl": { + "version": "0.1.66", + "resolved": "https://registry.npmjs.org/@napi-rs/canvas-linux-x64-musl/-/canvas-linux-x64-musl-0.1.66.tgz", + "integrity": "sha512-XunvXisTkIG+bpq6BcXmsUstoLX3RLS6N9Uz9Pg9RpWIMeM6ObR5shr3NgpGRJq93769I1hS4mJW0DX2Au3WBw==", + "cpu": [ + "x64" + ], + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@napi-rs/canvas-win32-x64-msvc": { + "version": "0.1.66", + "resolved": "https://registry.npmjs.org/@napi-rs/canvas-win32-x64-msvc/-/canvas-win32-x64-msvc-0.1.66.tgz", + "integrity": "sha512-3n34watNFqpwACDA+pt4jfQD6zR8PzfK86FBajdsgDVVZhSp6ohgbbJv+eUrXM08VUtjxTq7+U4sWspTu9+4Ug==", + "cpu": [ + "x64" + ], + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">= 10" + } + }, "node_modules/@rollup/rollup-android-arm-eabi": { "version": "4.28.0", "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.28.0.tgz", @@ -1732,6 +1904,17 @@ "node": ">= 6" } }, + "node_modules/pdfjs-dist": { + "version": "4.10.38", + "resolved": "https://registry.npmjs.org/pdfjs-dist/-/pdfjs-dist-4.10.38.tgz", + "integrity": "sha512-/Y3fcFrXEAsMjJXeL9J8+ZG9U01LbuWaYypvDW2ycW1jL269L3js3DVBjDJ0Up9Np1uqDXsDrRihHANhZOlwdQ==", + "engines": { + "node": ">=20" + }, + "optionalDependencies": { + "@napi-rs/canvas": "^0.1.65" + } + }, "node_modules/picocolors": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz", diff --git a/examples/server/webui/package.json b/examples/server/webui/package.json index 2836cce00..6a8076e5e 100644 --- a/examples/server/webui/package.json +++ b/examples/server/webui/package.json @@ -21,6 +21,7 @@ "highlight.js": "^11.10.0", "katex": "^0.16.15", "markdown-it": "^14.1.0", + "pdfjs-dist": "^4.10.38", "postcss": "^8.4.49", "tailwindcss": "^3.4.15", "textlinestream": "^1.1.1", diff --git a/examples/server/webui/src/main.js b/examples/server/webui/src/main.js index 90f4ca368..52a1d3c24 100644 --- a/examples/server/webui/src/main.js +++ b/examples/server/webui/src/main.js @@ -15,6 +15,11 @@ import daisyuiThemes from 'daisyui/src/theming/themes'; // ponyfill for missing ReadableStream asyncIterator on Safari import { asyncIterator } from '@sec-ant/readable-stream/ponyfill/asyncIterator'; +// pdf parsing +import * as pdfjsLib from "pdfjs-dist"; +import pdfWorker from "pdfjs-dist/build/pdf.worker.mjs?url"; +pdfjsLib.GlobalWorkerOptions.workerSrc = pdfWorker; + const isDev = import.meta.env.MODE === 'development'; // types @@ -387,6 +392,8 @@ const mainApp = createApp({ viewingConvId: StorageUtils.getNewConvId(), inputMsg: '', isGenerating: false, + uploadedFiles: [], + fileText: '', /** @type {Array | null} */ pendingMsg: null, // the on-going message from assistant stopGeneration: () => {}, @@ -467,6 +474,36 @@ const mainApp = createApp({ document.body.removeChild(a); URL.revokeObjectURL(url); }, + removeFile(index){ + this.uploadedFiles.splice(index, 1); + }, + async handlePdfUpload(event) { + const file = event.target.files[0]; + if (file && file.type === "application/pdf") { + try { + const arrayBuffer = await file.arrayBuffer(); + const loadingTask = pdfjsLib.getDocument({ data: new Uint8Array(arrayBuffer) }); + + loadingTask.promise.then(pdfDocument => { + console.log("PDF loaded:", pdfDocument); + const pdfPromise = extractPdfText(file); + pdfPromise.then((data) => { + this.uploadedFiles.push(file); + this.fileText += data; + }) + .catch((error) => { + console.log(error) + }); + }).catch(error => { + console.error("Error loading PDF:", error); + }); + } catch (error) { + console.error("Error extracting PDF text:", error); + } + } else { + alert("Please upload a valid PDF file."); + } + }, async sendMessage() { if (!this.inputMsg) return; const currConvId = this.viewingConvId; @@ -474,11 +511,13 @@ const mainApp = createApp({ StorageUtils.appendMsg(currConvId, { id: Date.now(), role: 'user', - content: this.inputMsg, + content: this.fileText + '\n' + this.inputMsg, }); this.fetchConversation(); this.fetchMessages(); this.inputMsg = ''; + this.uploadedFiles = []; + this.fileText = ''; this.generateMessage(currConvId); chatScrollToBottom(); }, @@ -669,6 +708,32 @@ try { `; } +/** + * extracts text content from a given PDF file using pdf.js + * @param {File} file + * @returns {Promise} + */ +async function extractPdfText(file) { + const fileReader = new FileReader(); + return new Promise((resolve, reject) => { + fileReader.onload = async (e) => { + const pdfData = new Uint8Array(e.target.result); + try { + const pdf = await pdfjsLib.getDocument(pdfData).promise; + let textContent = ""; + for (let i = 1; i <= pdf.numPages; i++) { + const page = await pdf.getPage(i); + const textContentPage = await page.getTextContent(); + textContent += textContentPage.items.map(item => item.str).join(" ") + "\n"; + } + resolve(textContent.trim()); + } catch (error) { + reject(error); + } + }; + fileReader.readAsArrayBuffer(file); + }); +} /** * filter out redundant fields upon sending to API diff --git a/examples/server/webui/vite.config.js b/examples/server/webui/vite.config.js index 6619a630d..93d6de3ce 100644 --- a/examples/server/webui/vite.config.js +++ b/examples/server/webui/vite.config.js @@ -4,7 +4,7 @@ import path from 'path'; import fs from 'fs'; import zlib from 'zlib'; -const MAX_BUNDLE_SIZE = 1.5 * 1024 * 1024; // only increase when absolutely necessary +const MAX_BUNDLE_SIZE = 2 * 1024 * 1024; // only increase when absolutely necessary const GUIDE_FOR_FRONTEND = `