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 = `