diff --git a/README.md b/README.md
index e534814..4a59eed 100644
--- a/README.md
+++ b/README.md
@@ -52,6 +52,7 @@ Samples showing how to get started with WebViewer in different environments.
### 3rd Party Integrations
Samples showing how to integrate and use WebViewer in 3rd party platforms.
+- [webviewer-ask-ai](./webviewer-ask-ai) - Integrate WebViewer with Artificial Intelligence
- [webviewer-salesforce](./webviewer-salesforce) - Integrate WebViewer in Salesforce
- [webviewer-salesforce-attachments](./webviewer-salesforce-attachments) - View Salesforce record attachments in WebViewer
- [webviewer-mendix](./webviewer-mendix) - Integrate WebViewer into a Mendix low-code app
diff --git a/lerna.json b/lerna.json
index 4c640e7..d313e36 100644
--- a/lerna.json
+++ b/lerna.json
@@ -8,6 +8,7 @@
"webviewer-annotations-nodejs",
"webviewer-annotations-php",
"webviewer-annotations-sqlite3",
+ "webviewer-ask-ai",
"webviewer-barcode",
"webviewer-blazor",
"webviewer-blazor-wasm",
diff --git a/webviewer-ask-ai/.env.example b/webviewer-ask-ai/.env.example
new file mode 100644
index 0000000..92fe747
--- /dev/null
+++ b/webviewer-ask-ai/.env.example
@@ -0,0 +1,10 @@
+DOTENV_CONFIG_QUIET=true
+
+# OpenAI Configuration
+OPENAI_API_KEY=your-openai-api-key-here
+OPENAI_MODEL=your-openai-model-here
+OPENAI_TEMPERATURE=your-openai-temperature-here
+OPENAI_MAX_TOKENS=your-openai-max-tokens-here
+
+# Server Configuration
+PORT=4040
\ No newline at end of file
diff --git a/webviewer-ask-ai/.gitignore b/webviewer-ask-ai/.gitignore
new file mode 100644
index 0000000..8bc4ae2
--- /dev/null
+++ b/webviewer-ask-ai/.gitignore
@@ -0,0 +1,7 @@
+# Misc
+.DS_Store
+node_modules
+
+# WebViewer
+client/lib
+client/license-key.js
\ No newline at end of file
diff --git a/webviewer-ask-ai/LICENSE b/webviewer-ask-ai/LICENSE
new file mode 100644
index 0000000..81ffa45
--- /dev/null
+++ b/webviewer-ask-ai/LICENSE
@@ -0,0 +1,2 @@
+Copyright (c) 2025 Apryse Software Inc. All Rights Reserved.
+WebViewer UI project/codebase or any derived works is only permitted in solutions with an active commercial Apryse WebViewer license. For exact licensing terms refer to the commercial WebViewer license. For licensing, pricing, or product questions, Contact [Sales](https://apryse.com/form/contact-sales).
\ No newline at end of file
diff --git a/webviewer-ask-ai/README.md b/webviewer-ask-ai/README.md
new file mode 100644
index 0000000..1fd656a
--- /dev/null
+++ b/webviewer-ask-ai/README.md
@@ -0,0 +1,45 @@
+# WebViewer - Ask AI sample
+
+[WebViewer](https://docs.apryse.com/web/guides/get-started) is a powerful JavaScript-based PDF Library that is part of the [Apryse SDK](https://apryse.com/).
+
+- [WebViewer Documentation](https://docs.apryse.com/web/guides/get-started)
+- [WebViewer Demo](https://showcase.apryse.com/)
+
+This sample demonstrates how to utilize the artificial intelligence capabilities within the WebViewer, using a chat panel interface to ask questions about the loaded document. Also the user can select a text in the document that can be summarized.
+
+## Get your trial key
+
+A license key is required to run WebViewer. You can obtain a trial key in our [get started guides](https://docs.apryse.com/web/guides/get-started), or by signing-up on our [developer portal](https://dev.apryse.com/).
+
+## Initial setup
+
+Before you begin, make sure the development environment includes [Node.js](https://nodejs.org/en/).
+
+## Install
+
+```
+git clone --depth=1 https://github.com/ApryseSDK/webviewer-samples.git
+cd webviewer-samples/webviewer-ask-ai
+npm install
+```
+
+## Configuration
+
+This sample uses OpenAI. You can use any other artificial intelligence of your choice.
+
+However, to get started with this sample rename `.env.example` file into `.env` and fill the followings:
+
+```
+OPENAI_API_KEY=your-openai-api-key-here
+OPENAI_MODEL=your-openai-model-here
+OPENAI_TEMPERATURE=your-openai-temperature-here
+OPENAI_MAX_TOKENS=your-openai-max-tokens-here
+```
+
+## Run
+
+```
+npm start
+```
+
+This will start a server that you can access the WebViewer client at http://localhost:4040/client/index.html, and the connection to the OpenAI will be managed on backend.
\ No newline at end of file
diff --git a/webviewer-ask-ai/client/assets/favicon.svg b/webviewer-ask-ai/client/assets/favicon.svg
new file mode 100644
index 0000000..6586460
--- /dev/null
+++ b/webviewer-ask-ai/client/assets/favicon.svg
@@ -0,0 +1,13 @@
+
+
+
+
+
+
+
diff --git a/webviewer-ask-ai/client/chatbot.js b/webviewer-ask-ai/client/chatbot.js
new file mode 100644
index 0000000..bdd15a0
--- /dev/null
+++ b/webviewer-ask-ai/client/chatbot.js
@@ -0,0 +1,204 @@
+// Browser-compatible chatbot client
+class ChatbotClient {
+ constructor() {
+ this.conversationHistory = [];
+ }
+
+ // Initialize chat interface for the WebViewer panel
+ initialize = () => {
+ // You can expand this to integrate with the WebViewer panel UI
+ window.chatbot = this; // Make chatbot available globally for testing
+ };
+
+ async sendMessage(promptLine, message, options = {}) {
+ try {
+ // For document-level operations, optionally use empty history to prevent token overflow
+ const historyToSend = options.useEmptyHistory ? [] : this.conversationHistory;
+ const response = await fetch('/api/chat', {
+ method: 'POST',
+ headers: {
+ 'Content-Type': 'application/json',
+ },
+ body: JSON.stringify({
+ message: message,
+ promptType: promptLine,
+ history: historyToSend
+ })
+ });
+
+ if (!response.ok) {
+ throw new Error(`HTTP error! status: ${response.status}`);
+ }
+
+ const data = await response.json();
+
+ // Update conversation history only if not explicitly disabled
+ if (!options.skipHistoryUpdate) {
+ this.conversationHistory.push(
+ { role: 'human', content: `${promptLine}: ${message.substring(0, 100)}...` }, // Truncate long messages in history
+ { role: 'assistant', content: data.response }
+ );
+ }
+
+ return data.response;
+ } catch (error) {
+ throw error;
+ }
+ }
+
+ //Combine into single container for all bubble responses
+ getAllText = async (promptType, createBubble) => {
+ const doc = window.WebViewer.getInstance().Core.documentViewer.getDocument();
+ doc.getDocumentCompletePromise().then(async () => {
+
+ const pageCount = doc.getPageCount();
+ const pageTexts = new Array(pageCount);
+ let loadedPages = 0;
+
+ // Load all pages and store them in correct order
+ for (let i = 1; i <= pageCount; i++) {
+ try {
+ const text = await doc.loadPageText(i);
+ // Store with page break BEFORE the content
+ pageTexts[i - 1] = `<> Page ${i}\n${text}`;
+ loadedPages++;
+
+ // When all pages are loaded, combine and send
+ if (loadedPages === pageCount) {
+ const completeText = pageTexts.join('\n\n');
+
+ // Use empty history for document-level operations to prevent token overflow
+ this.sendMessage(promptType, completeText, {
+ useEmptyHistory: true,
+ skipHistoryUpdate: false // Still update history but with truncated content
+ }).then(response => {
+ let responseText = this.responseText(response);
+ responseText = this.formatText(promptType, responseText);
+ createBubble(responseText, 'assistant');
+ }).catch(error => {
+ createBubble(`Error: ${error.message}`, 'assistant');
+ });
+ }
+ } catch (error) {
+ pageTexts[i - 1] = `<> Page ${i}\n[Error loading page content]`;
+ loadedPages++;
+
+ // Still proceed if all pages are processed (even with errors)
+ if (loadedPages === pageCount) {
+ const completeText = pageTexts.join('\n\n');
+
+ this.sendMessage(promptType, completeText, {
+ useEmptyHistory: true,
+ skipHistoryUpdate: false
+ }).then(response => {
+ let responseText = this.responseText(response);
+ responseText = this.formatText(promptType, responseText);
+ createBubble(responseText, 'assistant');
+ }).catch(error => {
+ createBubble(`Error: ${error.message}`, 'assistant');
+ });
+ }
+ }
+ }
+ });
+ };
+
+ // Extract text from OpenAI response via LangChain
+ responseText = (response) => {
+ // Primary: Server should send clean string content
+ if (typeof response === 'string') {
+ return response;
+ }
+
+ // Fallback: if server still sends complex object, extract properly
+ if (typeof response === 'object' && response !== null) {
+
+ // Standard LangChain approach: use .content property directly
+ if (response.content !== undefined) {
+ return response.content;
+ }
+
+ // Fallback for serialized LangChain objects
+ if (response.kwargs && response.kwargs.content) {
+ return response.kwargs.content;
+ }
+
+ return JSON.stringify(response);
+ }
+
+ return 'No response received';
+ };
+
+ // Format text to include cited page links
+ // and page breaks based on prompt type
+ formatText = (promptType, text) => {
+ switch (promptType) {
+ case 'DOCUMENT_SUMMARY':
+ case 'SELECTED_TEXT_SUMMARY':
+ case 'DOCUMENT_QUESTION':
+ // Add page breaks to page citation ends with period
+ text = text.replace(/(\d+\])\./g, '$1. ');
+ break;
+ case 'DOCUMENT_KEYWORDS':
+ // Format bullet points with line breaks
+ let lines = text.split(/•\s*/).filter(Boolean);
+ text = lines.map(line => `• ${line.trim()}`).join(' ');
+ break;
+ default:
+ break;
+ }
+
+ // Separate citations group on form [1, 2, 3] to individual [1][2][3]
+ text = this.separateGroupedCitations(text, /\[\d+(?:\s*,\s*\d+)+\]/g);
+
+ // Separate citations range on form [1-3] to individual [1][2][3]
+ text = this.separateGroupedCitations(text, /\[\d+(?:\s*-\s*\d+)+\]/g);
+
+ let matches = text.match(/\[\d+\]/g);
+ if (matches && matches.length > 0) {
+ // Element duplicate matches
+ matches = [...new Set(matches)];
+
+ let pageNumber = 1;
+ // match to be turned into link
+ matches.forEach(match => {
+ pageNumber = match.match(/\d+/)[0];
+ if (pageNumber > 0 &&
+ pageNumber <= window.WebViewer.getInstance().Core.documentViewer.getDocument().getPageCount()) {
+ const pageLink = `[${pageNumber}] `;
+ text = text.replaceAll(match, `${pageLink}`);
+ }
+ });
+ }
+
+ return text;
+ }
+
+ // Helper to separate grouped citations on form [1, 2, 3] or [1-3] into individual [1][2][3]
+ separateGroupedCitations = (text, pattern) => {
+ let matches = text.match(pattern);
+ if (matches && matches.length > 0) {
+ let formattedMatchNumbers = '';
+ matches.forEach(match => {
+ let matchNumbers = match.match(/\d+/g);
+ matchNumbers.forEach(matchNumber => {
+ formattedMatchNumbers += `[${matchNumber}]`;
+ });
+
+ text = text.replaceAll(match, formattedMatchNumbers);
+ formattedMatchNumbers = '';
+ });
+ }
+
+ return text;
+ }
+
+ clearHistory() {
+ this.conversationHistory = [];
+ }
+}
+
+// Export for use in other modules
+export default function createChatbot() {
+ return new ChatbotClient();
+}
\ No newline at end of file
diff --git a/webviewer-ask-ai/client/config/ui/custom.json b/webviewer-ask-ai/client/config/ui/custom.json
new file mode 100644
index 0000000..0dd251d
--- /dev/null
+++ b/webviewer-ask-ai/client/config/ui/custom.json
@@ -0,0 +1,1165 @@
+{
+ "modularComponents": {
+ "filePickerButton": {
+ "dataElement": "filePickerButton",
+ "title": "action.openFile",
+ "label": "action.openFile",
+ "type": "presetButton",
+ "buttonType": "filePickerButton"
+ },
+ "downloadButton": {
+ "dataElement": "downloadButton",
+ "title": "action.download",
+ "label": "action.download",
+ "type": "presetButton",
+ "buttonType": "downloadButton",
+ "disabled": true
+ },
+ "saveAsButton": {
+ "dataElement": "saveAsButton",
+ "title": "saveModal.saveAs",
+ "isActive": false,
+ "label": "saveModal.saveAs",
+ "type": "presetButton",
+ "buttonType": "saveAsButton",
+ "disabled": true
+ },
+ "printButton": {
+ "dataElement": "printButton",
+ "title": "action.print",
+ "isActive": false,
+ "label": "action.print",
+ "type": "presetButton",
+ "buttonType": "printButton",
+ "disabled": true
+ },
+ "createPortfolioButton": {
+ "dataElement": "createPortfolioButton",
+ "title": "portfolio.createPDFPortfolio",
+ "isActive": false,
+ "label": "portfolio.createPDFPortfolio",
+ "type": "presetButton",
+ "buttonType": "createPortfolioButton",
+ "disabled": true
+ },
+ "settingsButton": {
+ "dataElement": "settingsButton",
+ "title": "option.settings.settings",
+ "isActive": false,
+ "label": "option.settings.settings",
+ "type": "presetButton",
+ "buttonType": "settingsButton"
+ },
+ "divider-0.1": {
+ "dataElement": "divider-0.1",
+ "disabled": false,
+ "type": "divider"
+ },
+ "leftPanelButton": {
+ "dataElement": "leftPanelButton",
+ "title": "component.leftPanel",
+ "disabled": true,
+ "type": "toggleButton",
+ "img": "icon-header-sidebar-line",
+ "toggleElement": "tabPanel"
+ },
+ "view-controls": {
+ "dataElement": "view-controls",
+ "title": "component.viewControls",
+ "type": "viewControls",
+ "icon": "icon-header-page-manipulation-line"
+ },
+ "divider-0.3": {
+ "dataElement": "divider-0.3",
+ "disabled": false,
+ "type": "divider"
+ },
+ "zoom-container": {
+ "dataElement": "zoom-container",
+ "type": "zoom"
+ },
+ "divider-0.2": {
+ "dataElement": "divider-0.2",
+ "disabled": false,
+ "type": "divider"
+ },
+ "panToolButton": {
+ "dataElement": "panToolButton",
+ "disabled": true,
+ "type": "toolButton",
+ "toolName": "Pan"
+ },
+ "annotationEditToolButton": {
+ "dataElement": "annotationEditToolButton",
+ "type": "toolButton",
+ "toolName": "AnnotationEdit"
+ },
+ "menuButton": {
+ "dataElement": "menuButton",
+ "title": "component.menuOverlay",
+ "type": "toggleButton",
+ "img": "ic-hamburger-menu",
+ "toggleElement": "MainMenuFlyout"
+ },
+ "toolbarGroup-View": {
+ "dataElement": "toolbarGroup-View",
+ "title": "View",
+ "type": "ribbonItem",
+ "label": "View",
+ "groupedItems": [],
+ "toolbarGroup": "toolbarGroup-View"
+ },
+ "toolbarGroup-Annotate": {
+ "dataElement": "toolbarGroup-Annotate",
+ "title": "Annotate",
+ "disabled": true,
+ "type": "ribbonItem",
+ "label": "Annotate",
+ "groupedItems": [
+ "annotateGroupedItems"
+ ],
+ "toolbarGroup": "toolbarGroup-Annotate"
+ },
+ "toolbarGroup-Shapes": {
+ "dataElement": "toolbarGroup-Shapes",
+ "title": "Shapes",
+ "disabled": true,
+ "type": "ribbonItem",
+ "label": "Shapes",
+ "groupedItems": [
+ "shapesGroupedItems"
+ ],
+ "toolbarGroup": "toolbarGroup-Shapes"
+ },
+ "toolbarGroup-Insert": {
+ "dataElement": "toolbarGroup-Insert",
+ "title": "Insert",
+ "disabled": true,
+ "type": "ribbonItem",
+ "label": "Insert",
+ "groupedItems": [
+ "insertGroupedItems"
+ ],
+ "toolbarGroup": "toolbarGroup-Insert"
+ },
+ "toolbarGroup-Measure": {
+ "dataElement": "toolbarGroup-Measure",
+ "title": "Measure",
+ "disabled": true,
+ "type": "ribbonItem",
+ "label": "Measure",
+ "groupedItems": [
+ "measureGroupedItems"
+ ],
+ "toolbarGroup": "toolbarGroup-Measure"
+ },
+ "toolbarGroup-Redact": {
+ "dataElement": "toolbarGroup-Redact",
+ "title": "Redact",
+ "disabled": true,
+ "type": "ribbonItem",
+ "label": "Redact",
+ "groupedItems": [
+ "redactionGroupedItems"
+ ],
+ "toolbarGroup": "toolbarGroup-Redact"
+ },
+ "toolbarGroup-Edit": {
+ "dataElement": "toolbarGroup-Edit",
+ "title": "Edit",
+ "disabled": true,
+ "type": "ribbonItem",
+ "label": "Edit",
+ "groupedItems": [
+ "editGroupedItems"
+ ],
+ "toolbarGroup": "toolbarGroup-Edit"
+ },
+ "toolbarGroup-EditText": {
+ "dataElement": "toolbarGroup-EditText",
+ "title": "Content Edit",
+ "disabled": true,
+ "type": "ribbonItem",
+ "label": "Content Edit",
+ "groupedItems": [
+ "contentEditGroupedItems"
+ ],
+ "toolbarGroup": "toolbarGroup-EditText"
+ },
+ "toolbarGroup-FillAndSign": {
+ "dataElement": "toolbarGroup-FillAndSign",
+ "title": "Fill and Sign",
+ "disabled": true,
+ "type": "ribbonItem",
+ "label": "Fill and Sign",
+ "groupedItems": [
+ "fillAndSignGroupedItems"
+ ],
+ "toolbarGroup": "toolbarGroup-FillAndSign"
+ },
+ "toolbarGroup-Forms": {
+ "dataElement": "toolbarGroup-Forms",
+ "title": "Forms",
+ "disabled": true,
+ "type": "ribbonItem",
+ "label": "Forms",
+ "groupedItems": [
+ "formsGroupedItems"
+ ],
+ "toolbarGroup": "toolbarGroup-Forms"
+ },
+ "highlightToolButton": {
+ "dataElement": "highlightToolButton",
+ "type": "toolButton",
+ "toolName": "AnnotationCreateTextHighlight"
+ },
+ "underlineToolButton": {
+ "dataElement": "underlineToolButton",
+ "type": "toolButton",
+ "toolName": "AnnotationCreateTextUnderline"
+ },
+ "strikeoutToolButton": {
+ "dataElement": "strikeoutToolButton",
+ "type": "toolButton",
+ "toolName": "AnnotationCreateTextStrikeout"
+ },
+ "squigglyToolButton": {
+ "dataElement": "squigglyToolButton",
+ "type": "toolButton",
+ "toolName": "AnnotationCreateTextSquiggly"
+ },
+ "freeTextToolButton": {
+ "dataElement": "freeTextToolButton",
+ "type": "toolButton",
+ "toolName": "AnnotationCreateFreeText"
+ },
+ "markInsertTextToolButton": {
+ "dataElement": "markInsertTextToolButton",
+ "type": "toolButton",
+ "toolName": "AnnotationCreateMarkInsertText"
+ },
+ "markReplaceTextToolButton": {
+ "dataElement": "markReplaceTextToolButton",
+ "type": "toolButton",
+ "toolName": "AnnotationCreateMarkReplaceText"
+ },
+ "freeHandToolButton": {
+ "dataElement": "freeHandToolButton",
+ "type": "toolButton",
+ "toolName": "AnnotationCreateFreeHand"
+ },
+ "freeHandHighlightToolButton": {
+ "dataElement": "freeHandHighlightToolButton",
+ "type": "toolButton",
+ "toolName": "AnnotationCreateFreeHandHighlight"
+ },
+ "stickyToolButton": {
+ "dataElement": "stickyToolButton",
+ "type": "toolButton",
+ "toolName": "AnnotationCreateSticky"
+ },
+ "calloutToolButton": {
+ "dataElement": "calloutToolButton",
+ "type": "toolButton",
+ "toolName": "AnnotationCreateCallout"
+ },
+ "divider-0.4": {
+ "dataElement": "divider-0.4",
+ "type": "divider"
+ },
+ "stylePanelToggle": {
+ "dataElement": "stylePanelToggle",
+ "title": "action.style",
+ "type": "toggleButton",
+ "img": "icon-style-panel-toggle",
+ "toggleElement": "stylePanel"
+ },
+ "indexPanelListToggle": {
+ "dataElement": "indexPanelListToggle",
+ "title": "component.indexPanel",
+ "type": "toggleButton",
+ "img": "icon-index-panel-list",
+ "toggleElement": "indexPanel"
+ },
+ "divider-0.5": {
+ "dataElement": "divider-0.5",
+ "type": "divider"
+ },
+ "undoButton": {
+ "dataElement": "undoButton",
+ "type": "presetButton",
+ "buttonType": "undoButton"
+ },
+ "redoButton": {
+ "dataElement": "redoButton",
+ "type": "presetButton",
+ "buttonType": "redoButton"
+ },
+ "toggleAccessibilityModeButton": {
+ "dataElement": "toggleAccessibilityModePresetButton",
+ "type": "presetButton",
+ "buttonType": "toggleAccessibilityModeButton"
+ },
+ "eraserToolButton": {
+ "dataElement": "eraserToolButton",
+ "type": "toolButton",
+ "toolName": "AnnotationEraserTool"
+ },
+ "defaultAnnotationUtilities": {
+ "dataElement": "defaultAnnotationUtilities",
+ "items": [
+ "divider-0.5",
+ "undoButton",
+ "redoButton",
+ "eraserToolButton"
+ ],
+ "type": "groupedItems",
+ "grow": 0,
+ "gap": 12,
+ "alwaysVisible": false
+ },
+ "annotateToolsGroupedItems": {
+ "dataElement": "annotateToolsGroupedItems",
+ "items": [
+ "highlightToolButton",
+ "underlineToolButton",
+ "strikeoutToolButton",
+ "squigglyToolButton",
+ "freeHandToolButton",
+ "freeHandHighlightToolButton",
+ "freeTextToolButton",
+ "markInsertTextToolButton",
+ "markReplaceTextToolButton",
+ "stickyToolButton",
+ "calloutToolButton"
+ ],
+ "type": "groupedItems",
+ "justifyContent": "center",
+ "grow": 0,
+ "gap": 12,
+ "alwaysVisible": false
+ },
+ "annotateGroupedItems": {
+ "dataElement": "annotateGroupedItems",
+ "items": [
+ "annotateToolsGroupedItems",
+ "divider-0.4",
+ "stylePanelToggle",
+ "defaultAnnotationUtilities"
+ ],
+ "type": "groupedItems",
+ "justifyContent": "center",
+ "grow": 0,
+ "gap": 12,
+ "alwaysVisible": false
+ },
+ "rectangleToolButton": {
+ "dataElement": "rectangleToolButton",
+ "type": "toolButton",
+ "toolName": "AnnotationCreateRectangle"
+ },
+ "ellipseToolButton": {
+ "dataElement": "ellipseToolButton",
+ "type": "toolButton",
+ "toolName": "AnnotationCreateEllipse"
+ },
+ "arcToolButton": {
+ "dataElement": "arcToolButton",
+ "type": "toolButton",
+ "toolName": "AnnotationCreateArc"
+ },
+ "polygonToolButton": {
+ "dataElement": "polygonToolButton",
+ "type": "toolButton",
+ "toolName": "AnnotationCreatePolygon"
+ },
+ "cloudToolButton": {
+ "dataElement": "cloudToolButton",
+ "type": "toolButton",
+ "toolName": "AnnotationCreatePolygonCloud"
+ },
+ "lineToolButton": {
+ "dataElement": "lineToolButton",
+ "type": "toolButton",
+ "toolName": "AnnotationCreateLine"
+ },
+ "polylineToolButton": {
+ "dataElement": "polylineToolButton",
+ "type": "toolButton",
+ "toolName": "AnnotationCreatePolyline"
+ },
+ "arrowToolButton": {
+ "dataElement": "arrowToolButton",
+ "type": "toolButton",
+ "toolName": "AnnotationCreateArrow"
+ },
+ "shapesToolsGroupedItems": {
+ "dataElement": "shapesToolsGroupedItems",
+ "items": [
+ "rectangleToolButton",
+ "ellipseToolButton",
+ "arcToolButton",
+ "polygonToolButton",
+ "cloudToolButton",
+ "lineToolButton",
+ "polylineToolButton",
+ "arrowToolButton"
+ ],
+ "type": "groupedItems",
+ "grow": 0,
+ "gap": 12,
+ "alwaysVisible": false
+ },
+ "shapesGroupedItems": {
+ "dataElement": "shapesGroupedItems",
+ "items": [
+ "shapesToolsGroupedItems",
+ "divider-0.4",
+ "stylePanelToggle",
+ "defaultAnnotationUtilities"
+ ],
+ "type": "groupedItems",
+ "grow": 0,
+ "gap": 12,
+ "alwaysVisible": false
+ },
+ "rubberStampToolButton": {
+ "dataElement": "rubberStampToolButton",
+ "type": "toolButton",
+ "toolName": "AnnotationCreateRubberStamp"
+ },
+ "signatureCreateToolButton": {
+ "dataElement": "signatureCreateToolButton",
+ "type": "toolButton",
+ "toolName": "AnnotationCreateSignature"
+ },
+ "fileAttachmentButton": {
+ "dataElement": "fileAttachmentButton",
+ "type": "toolButton",
+ "toolName": "AnnotationCreateFileAttachment"
+ },
+ "stampToolButton": {
+ "dataElement": "stampToolButton",
+ "type": "toolButton",
+ "toolName": "AnnotationCreateStamp"
+ },
+ "insertToolsGroupedItems": {
+ "dataElement": "insertToolsGroupedItems",
+ "items": [
+ "rubberStampToolButton",
+ "signatureCreateToolButton",
+ "fileAttachmentButton",
+ "stampToolButton"
+ ],
+ "type": "groupedItems",
+ "grow": 0,
+ "gap": 12,
+ "alwaysVisible": false
+ },
+ "insertGroupedItems": {
+ "dataElement": "insertGroupedItems",
+ "items": [
+ "insertToolsGroupedItems",
+ "divider-0.4",
+ "stylePanelToggle",
+ "defaultAnnotationUtilities"
+ ],
+ "type": "groupedItems",
+ "grow": 0,
+ "gap": 12,
+ "alwaysVisible": false
+ },
+ "redactionToolButton": {
+ "dataElement": "redactionToolButton",
+ "type": "toolButton",
+ "toolName": "AnnotationCreateRedaction"
+ },
+ "pageRedactionToggleButton": {
+ "dataElement": "pageRedactionToggleButton",
+ "title": "action.redactPages",
+ "type": "toggleButton",
+ "img": "icon-tool-page-redact",
+ "toggleElement": "pageRedactionModal"
+ },
+ "redactionPanelToggle": {
+ "dataElement": "redactionPanelToggle",
+ "type": "toggleButton",
+ "img": "icon-redact-panel",
+ "toggleElement": "redactionPanel",
+ "title": "component.redactionPanel"
+ },
+ "redactionGroupedItems": {
+ "dataElement": "redactionGroupedItems",
+ "items": [
+ "redactionToolButton",
+ "pageRedactionToggleButton",
+ "redactionPanelToggle",
+ "divider-0.4",
+ "stylePanelToggle",
+ "defaultAnnotationUtilities"
+ ],
+ "type": "groupedItems",
+ "grow": 0,
+ "gap": 12,
+ "alwaysVisible": false
+ },
+ "distanceMeasurementToolButton": {
+ "dataElement": "distanceMeasurementToolButton",
+ "type": "toolButton",
+ "toolName": "AnnotationCreateDistanceMeasurement"
+ },
+ "arcMeasurementToolButton": {
+ "dataElement": "arcMeasurementToolButton",
+ "type": "toolButton",
+ "toolName": "AnnotationCreateArcMeasurement"
+ },
+ "perimeterMeasurementToolButton": {
+ "dataElement": "perimeterMeasurementToolButton",
+ "type": "toolButton",
+ "toolName": "AnnotationCreatePerimeterMeasurement"
+ },
+ "areaMeasurementToolButton": {
+ "dataElement": "areaMeasurementToolButton",
+ "type": "toolButton",
+ "toolName": "AnnotationCreateAreaMeasurement"
+ },
+ "ellipseMeasurementToolButton": {
+ "dataElement": "ellipseMeasurementToolButton",
+ "type": "toolButton",
+ "toolName": "AnnotationCreateEllipseMeasurement"
+ },
+ "rectangularAreaMeasurementToolButton": {
+ "dataElement": "rectangularAreaMeasurementToolButton",
+ "type": "toolButton",
+ "toolName": "AnnotationCreateRectangularAreaMeasurement"
+ },
+ "countMeasurementToolButton": {
+ "dataElement": "countMeasurementToolButton",
+ "type": "toolButton",
+ "toolName": "AnnotationCreateCountMeasurement"
+ },
+ "measureGroupedItems": {
+ "dataElement": "measureGroupedItems",
+ "items": [
+ "distanceMeasurementToolButton",
+ "arcMeasurementToolButton",
+ "perimeterMeasurementToolButton",
+ "areaMeasurementToolButton",
+ "ellipseMeasurementToolButton",
+ "rectangularAreaMeasurementToolButton",
+ "countMeasurementToolButton",
+ "divider-0.4",
+ "stylePanelToggle",
+ "defaultAnnotationUtilities"
+ ],
+ "type": "groupedItems",
+ "grow": 0,
+ "gap": 12,
+ "alwaysVisible": false
+ },
+ "cropToolButton": {
+ "dataElement": "cropToolButton",
+ "type": "toolButton",
+ "toolName": "CropPage"
+ },
+ "snippingToolButton": {
+ "dataElement": "snippingToolButton",
+ "type": "toolButton",
+ "toolName": "SnippingTool"
+ },
+ "editGroupedItems": {
+ "dataElement": "editGroupedItems",
+ "items": [
+ "cropToolButton",
+ "snippingToolButton"
+ ],
+ "type": "groupedItems",
+ "grow": 0,
+ "gap": 12,
+ "alwaysVisible": false
+ },
+ "addParagraphToolGroupButton": {
+ "dataElement": "addParagraphToolGroupButton",
+ "type": "toolButton",
+ "toolName": "AddParagraphTool",
+ "disabled": true
+ },
+ "addImageContentToolGroupButton": {
+ "dataElement": "addImageContentToolGroupButton",
+ "type": "toolButton",
+ "toolName": "AddImageContentTool",
+ "disabled": true
+ },
+ "divider-0.6": {
+ "dataElement": "divider-0.6",
+ "type": "divider"
+ },
+ "contentEditButton": {
+ "dataElement": "contentEditButton",
+ "type": "presetButton",
+ "buttonType": "contentEditButton",
+ "disabled": true
+ },
+ "contentEditGroupedItems": {
+ "dataElement": "contentEditGroupedItems",
+ "items": [
+ "addParagraphToolGroupButton",
+ "addImageContentToolGroupButton",
+ "divider-0.6",
+ "contentEditButton"
+ ],
+ "type": "groupedItems",
+ "grow": 0,
+ "gap": 12,
+ "alwaysVisible": false
+ },
+ "crossStampToolButton": {
+ "dataElement": "crossStampToolButton",
+ "type": "toolButton",
+ "toolName": "AnnotationCreateCrossStamp"
+ },
+ "checkStampToolButton": {
+ "dataElement": "checkStampToolButton",
+ "type": "toolButton",
+ "toolName": "AnnotationCreateCheckStamp"
+ },
+ "dotStampToolButton": {
+ "dataElement": "dotStampToolButton",
+ "type": "toolButton",
+ "toolName": "AnnotationCreateDotStamp"
+ },
+ "calendarToolButton": {
+ "dataElement": "calendarToolButton",
+ "type": "toolButton",
+ "toolName": "AnnotationCreateDateFreeText"
+ },
+ "fillAndSignGroupedItems": {
+ "dataElement": "fillAndSignGroupedItems",
+ "items": [
+ "signatureCreateToolButton",
+ "freeTextToolButton",
+ "crossStampToolButton",
+ "checkStampToolButton",
+ "dotStampToolButton",
+ "rubberStampToolButton",
+ "calendarToolButton",
+ "divider-0.4",
+ "stylePanelToggle",
+ "defaultAnnotationUtilities"
+ ],
+ "type": "groupedItems",
+ "grow": 0,
+ "gap": 12,
+ "alwaysVisible": false
+ },
+ "signatureFieldButton": {
+ "dataElement": "signatureFieldButton",
+ "type": "toolButton",
+ "toolName": "SignatureFormFieldCreateTool"
+ },
+ "textFieldButton": {
+ "dataElement": "textFieldButton",
+ "type": "toolButton",
+ "toolName": "TextFormFieldCreateTool"
+ },
+ "checkboxFieldButton": {
+ "dataElement": "checkboxFieldButton",
+ "type": "toolButton",
+ "toolName": "CheckBoxFormFieldCreateTool"
+ },
+ "radioFieldButton": {
+ "dataElement": "radioFieldButton",
+ "type": "toolButton",
+ "toolName": "RadioButtonFormFieldCreateTool"
+ },
+ "listBoxFieldButton": {
+ "dataElement": "listBoxFieldButton",
+ "type": "toolButton",
+ "toolName": "ListBoxFormFieldCreateTool"
+ },
+ "comboBoxFieldButton": {
+ "dataElement": "comboBoxFieldButton",
+ "type": "toolButton",
+ "toolName": "ComboBoxFormFieldCreateTool"
+ },
+ "divider-0.7": {
+ "dataElement": "divider-0.7",
+ "type": "divider"
+ },
+ "formFieldEditButton": {
+ "dataElement": "formFieldEditButton",
+ "type": "presetButton",
+ "buttonType": "formFieldEditButton"
+ },
+ "divider-0.8": {
+ "dataElement": "divider-0.8",
+ "type": "divider"
+ },
+ "formsToolsGroupedItems": {
+ "dataElement": "formsToolsGroupedItems",
+ "items": [
+ "signatureFieldButton",
+ "textFieldButton",
+ "freeTextToolButton",
+ "checkboxFieldButton",
+ "radioFieldButton",
+ "listBoxFieldButton",
+ "comboBoxFieldButton",
+ "divider-0.7",
+ "formFieldEditButton"
+ ],
+ "type": "groupedItems",
+ "grow": 0,
+ "gap": 12,
+ "alwaysVisible": false
+ },
+ "formsGroupedItems": {
+ "dataElement": "formsGroupedItems",
+ "items": [
+ "formsToolsGroupedItems",
+ "divider-0.8",
+ "stylePanelToggle",
+ "indexPanelListToggle"
+ ],
+ "type": "groupedItems",
+ "grow": 0,
+ "gap": 12,
+ "alwaysVisible": false
+ },
+ "page-controls-container": {
+ "dataElement": "page-controls-container",
+ "type": "pageControls",
+ "title": "component.pageControls",
+ "icon": "icon-page-controls"
+ },
+ "groupedLeftHeaderButtons": {
+ "dataElement": "groupedLeftHeaderButtons",
+ "items": [
+ "menuButton",
+ "divider-0.1",
+ "leftPanelButton",
+ "view-controls",
+ "divider-0.3",
+ "zoom-container",
+ "divider-0.2",
+ "panToolButton",
+ "annotationEditToolButton"
+ ],
+ "type": "groupedItems",
+ "grow": 1,
+ "gap": 12,
+ "alwaysVisible": true
+ },
+ "default-ribbon-group": {
+ "dataElement": "default-ribbon-group",
+ "items": [
+ "toolbarGroup-View",
+ "toolbarGroup-Annotate",
+ "toolbarGroup-Shapes",
+ "toolbarGroup-Insert",
+ "toolbarGroup-Measure",
+ "toolbarGroup-Redact",
+ "toolbarGroup-Edit",
+ "toolbarGroup-EditText",
+ "toolbarGroup-FillAndSign",
+ "toolbarGroup-Forms"
+ ],
+ "type": "ribbonGroup",
+ "justifyContent": "start",
+ "grow": 2,
+ "gap": 12,
+ "alwaysVisible": false
+ },
+ "comparePanelToggle": {
+ "dataElement": "comparePanelToggle",
+ "title": "action.comparePages",
+ "disabled": true,
+ "type": "presetButton",
+ "label": "action.comparePages",
+ "buttonType": "compareButton"
+ },
+ "searchPanelToggle": {
+ "dataElement": "searchPanelToggle",
+ "title": "component.searchPanel",
+ "disabled": true,
+ "type": "toggleButton",
+ "img": "icon-header-search",
+ "toggleElement": "searchPanel"
+ },
+ "notesPanelToggle": {
+ "dataElement": "notesPanelToggle",
+ "title": "component.notesPanel",
+ "disabled": true,
+ "type": "toggleButton",
+ "img": "icon-header-chat-line",
+ "toggleElement": "notesPanel"
+ },
+ "askWebSDKPanelToggle": {
+ "dataElement": "askWebSDKPanelToggle",
+ "title": "{{APP_SITE_NAME}}",
+ "type": "toggleButton",
+ "img": "{{ASK_WEB_SDK_ICO}}",
+ "toggleElement": "askWebSDKPanel"
+ },
+ "newDocumentButton": {
+ "dataElement": "newDocumentButton",
+ "presetDataElement": "newDocumentPresetButton",
+ "label": "action.newDocument",
+ "title": "action.newDocument",
+ "isActive": false,
+ "type": "presetButton",
+ "buttonType": "newDocumentButton"
+ },
+ "fullscreenButton": {
+ "dataElement": "fullscreenButton",
+ "presetDataElement": "fullscreenPresetButton",
+ "label": "action.enterFullscreen",
+ "title": "action.enterFullscreen",
+ "type": "presetButton",
+ "buttonType": "fullscreenButton",
+ "disabled": true
+ }
+ },
+ "modularHeaders": {
+ "default-top-header": {
+ "dataElement": "default-top-header",
+ "placement": "top",
+ "grow": 0,
+ "gap": 12,
+ "position": "start",
+ "float": false,
+ "stroke": true,
+ "dimension": {
+ "paddingTop": 8,
+ "paddingBottom": 8,
+ "borderWidth": 1
+ },
+ "style": {},
+ "items": [
+ "groupedLeftHeaderButtons",
+ "default-ribbon-group",
+ "comparePanelToggle",
+ "searchPanelToggle",
+ "notesPanelToggle",
+ "askWebSDKPanelToggle"
+ ]
+ },
+ "tools-header": {
+ "dataElement": "tools-header",
+ "placement": "top",
+ "justifyContent": "center",
+ "grow": 0,
+ "gap": 12,
+ "position": "end",
+ "float": false,
+ "stroke": true,
+ "dimension": {
+ "paddingTop": 8,
+ "paddingBottom": 8,
+ "borderWidth": 1
+ },
+ "style": {},
+ "items": [
+ "annotateGroupedItems",
+ "shapesGroupedItems",
+ "insertGroupedItems",
+ "redactionGroupedItems",
+ "measureGroupedItems",
+ "editGroupedItems",
+ "contentEditGroupedItems",
+ "fillAndSignGroupedItems",
+ "formsGroupedItems"
+ ]
+ },
+ "page-nav-floating-header": {
+ "dataElement": "page-nav-floating-header",
+ "placement": "bottom",
+ "grow": 0,
+ "gap": 12,
+ "position": "center",
+ "opacityMode": "dynamic",
+ "opacity": "full",
+ "float": true,
+ "stroke": true,
+ "dimension": {
+ "paddingTop": 8,
+ "paddingBottom": 8,
+ "borderWidth": 1
+ },
+ "style": {
+ "background": "var(--gray-1)",
+ "padding": "8px",
+ "borderStyle": "solid",
+ "borderWidth": 1,
+ "borderColor": "var(--gray-5)"
+ },
+ "items": [
+ "page-controls-container"
+ ]
+ }
+ },
+ "panels": {
+ "comparePanel": {
+ "dataElement": "comparePanel",
+ "render": "changeListPanel",
+ "location": "end"
+ },
+ "stylePanel": {
+ "dataElement": "stylePanel",
+ "render": "stylePanel",
+ "location": "start"
+ },
+ "thumbnailsPanel": {
+ "dataElement": "thumbnailsPanel",
+ "render": "thumbnailsPanel",
+ "location": "start"
+ },
+ "outlinesPanel": {
+ "dataElement": "outlinesPanel",
+ "render": "outlinesPanel",
+ "location": "start"
+ },
+ "bookmarksPanel": {
+ "dataElement": "bookmarksPanel",
+ "render": "bookmarksPanel",
+ "location": "start"
+ },
+ "formFieldPanel": {
+ "dataElement": "formFieldPanel",
+ "render": "formFieldPanel",
+ "location": "end"
+ },
+ "indexPanel": {
+ "dataElement": "indexPanel",
+ "render": "indexPanel",
+ "location": "end"
+ },
+ "layersPanel": {
+ "dataElement": "layersPanel",
+ "render": "layersPanel",
+ "location": "start"
+ },
+ "signatureListPanel": {
+ "dataElement": "signatureListPanel",
+ "render": "signatureListPanel",
+ "location": "start"
+ },
+ "fileAttachmentPanel": {
+ "dataElement": "fileAttachmentPanel",
+ "render": "fileAttachmentPanel",
+ "location": "start"
+ },
+ "rubberStampPanel": {
+ "dataElement": "rubberStampPanel",
+ "render": "rubberStampPanel",
+ "location": "start"
+ },
+ "textEditingPanel": {
+ "dataElement": "textEditingPanel",
+ "render": "textEditingPanel",
+ "location": "end"
+ },
+ "signaturePanel": {
+ "dataElement": "signaturePanel",
+ "render": "signaturePanel",
+ "location": "start",
+ "disabled": true
+ },
+ "portfolioPanel": {
+ "dataElement": "portfolioPanel",
+ "render": "portfolioPanel",
+ "location": "start",
+ "disabled": true
+ },
+ "tabPanel": {
+ "render": "tabPanel",
+ "dataElement": "tabPanel",
+ "panelsList": [
+ {
+ "render": "thumbnailsPanel"
+ },
+ {
+ "render": "outlinesPanel"
+ },
+ {
+ "render": "bookmarksPanel"
+ },
+ {
+ "render": "layersPanel"
+ },
+ {
+ "render": "signaturePanel"
+ },
+ {
+ "render": "fileAttachmentPanel"
+ },
+ {
+ "render": "portfolioPanel"
+ }
+ ],
+ "location": "start"
+ },
+ "notesPanel": {
+ "dataElement": "notesPanel",
+ "render": "notesPanel",
+ "location": "end"
+ },
+ "searchPanel": {
+ "dataElement": "searchPanel",
+ "render": "searchPanel",
+ "location": "end"
+ },
+ "redactionPanel": {
+ "dataElement": "redactionPanel",
+ "render": "redactionPanel",
+ "location": "end"
+ },
+ "askWebSDKPanel": {
+ "dataElement": "askWebSDKPanel",
+ "render": "askWebSDKPanelRender",
+ "location": "end"
+ }
+ },
+ "flyouts": {
+ "MainMenuFlyout": {
+ "dataElement": "MainMenuFlyout",
+ "items": [
+ "newDocumentButton",
+ "filePickerButton",
+ "downloadButton",
+ "fullscreenButton",
+ "saveAsButton",
+ "printButton",
+ "divider",
+ "createPortfolioButton",
+ "divider",
+ "settingsButton",
+ "divider"
+ ]
+ },
+ "multiSelectStylePanelFlyout": {
+ "dataElement": "multiSelectStylePanelFlyout",
+ "className": "StylePanelFlyout",
+ "items": [
+ {
+ "dataElement": "stylePanelInFlyout",
+ "render": "stylePanel"
+ }
+ ]
+ }
+ },
+ "popups": {
+ "annotationPopup": [
+ {
+ "dataElement": "viewFileButton"
+ },
+ {
+ "dataElement": "annotationCommentButton"
+ },
+ {
+ "dataElement": "annotationStyleEditButton"
+ },
+ {
+ "dataElement": "annotationDateEditButton"
+ },
+ {
+ "dataElement": "annotationRedactButton"
+ },
+ {
+ "dataElement": "annotationCropButton"
+ },
+ {
+ "dataElement": "annotationContentEditButton"
+ },
+ {
+ "dataElement": "annotationClearSignatureButton"
+ },
+ {
+ "dataElement": "annotationGroupButton"
+ },
+ {
+ "dataElement": "annotationUngroupButton"
+ },
+ {
+ "dataElement": "formFieldEditButton"
+ },
+ {
+ "dataElement": "calibratePopupButton"
+ },
+ {
+ "dataElement": "linkButton"
+ },
+ {
+ "dataElement": "fileAttachmentDownload"
+ },
+ {
+ "dataElement": "annotationDeleteButton"
+ },
+ {
+ "dataElement": "shortCutKeysFor3D"
+ },
+ {
+ "dataElement": "playSoundButton"
+ },
+ {
+ "dataElement": "openAlignmentButton"
+ }
+ ],
+ "textPopup": [
+ {
+ "dataElement": "askWebSDKButton",
+ "type": "customButton",
+ "img": "{{ASK_WEB_SDK_ICO}}",
+ "title": "Summarize Selection",
+ "onClick": "askWebSDKPopupClick"
+ },
+ {
+ "dataElement": "copyTextButton"
+ },
+ {
+ "dataElement": "textHighlightToolButton"
+ },
+ {
+ "dataElement": "textUnderlineToolButton"
+ },
+ {
+ "dataElement": "textSquigglyToolButton"
+ },
+ {
+ "dataElement": "textStrikeoutToolButton"
+ },
+ {
+ "dataElement": "textRedactToolButton"
+ },
+ {
+ "dataElement": "linkButton"
+ }
+ ],
+ "contextMenuPopup": [
+ {
+ "dataElement": "panToolButton"
+ },
+ {
+ "dataElement": "stickyToolButton"
+ },
+ {
+ "dataElement": "highlightToolButton"
+ },
+ {
+ "dataElement": "freeHandToolButton"
+ },
+ {
+ "dataElement": "freeHandHighlightToolButton"
+ },
+ {
+ "dataElement": "freeTextToolButton"
+ },
+ {
+ "dataElement": "markInsertTextToolButton"
+ },
+ {
+ "dataElement": "markReplaceTextToolButton"
+ }
+ ]
+ }
+}
\ No newline at end of file
diff --git a/webviewer-ask-ai/client/config/ui/functionMap.js b/webviewer-ask-ai/client/config/ui/functionMap.js
new file mode 100644
index 0000000..8fc0f3a
--- /dev/null
+++ b/webviewer-ask-ai/client/config/ui/functionMap.js
@@ -0,0 +1,252 @@
+import { Spinner } from './spinjs/spin.js';
+const spinOptions = {
+ lines: 13, // The number of lines to draw
+ length: 38, // The length of each line
+ width: 17, // The line thickness
+ radius: 45, // The radius of the inner circle
+ scale: 1, // Scales overall size of the spinner
+ corners: 1, // Corner roundness (0..1)
+ speed: 1, // Rounds per second
+ rotate: 0, // The rotation offset
+ animation: 'spinner-line-fade-quick', // The CSS animation name for the lines
+ direction: 1, // 1: clockwise, -1: counterclockwise
+ color: '#ffffff', // CSS color or array of colors
+ fadeColor: 'transparent', // CSS color or array of colors
+ top: '50%', // Top position relative to parent
+ left: '50%', // Left position relative to parent
+ shadow: '0 0 1px transparent', // Box-shadow for the lines
+ zIndex: 2000000000, // The z-index (defaults to 2e9)
+ className: 'spinner', // The CSS class to assign to the spinner
+ position: 'absolute', // Element positioning
+};
+let askWebSDKMainDiv = null;
+let askWebSDKChattingDiv = null;
+const keywords = {
+ summarization: ['summarize', 'summary', 'summarization'],
+ area: ['text', 'paragraph', 'area'],
+ selection: ['selected', 'selection', 'highlighted']
+};
+const spinner = new Spinner(spinOptions);
+const assistantMessages = [
+ {
+ type: 'info',
+ content: `Hello, I'm ${APP_SITE_NAME}. How can I help you?`,
+ },
+ {
+ type: 'info',
+ content: `Select the options below to get started. You can also select text in the document and click the popup to summarize the selected text.`,
+ },
+ {
+ type: 'info',
+ content: [
+ {
+ type: 'info',
+ content: 'Choose:',
+ promptType: '',
+ },
+ {
+ type: 'question',
+ content: 'Summarize Document',
+ promptType: 'DOCUMENT_SUMMARY'
+ },
+ {
+ type: 'question',
+ content: 'List Keywords',
+ promptType: 'DOCUMENT_KEYWORDS'
+ }
+ ]
+ }
+];
+
+const functionMap = {
+ // Render the WebViewer chat panel
+ 'askWebSDKPanelRender': () => {
+ // The main container div
+ askWebSDKMainDiv = document.createElement('div');
+ askWebSDKMainDiv.id = 'askWebSDKMainDiv';
+ askWebSDKMainDiv.className = 'askWebSDKMainDivClass';
+
+ // Header container div with document title
+ let askWebSDKHeaderDiv = document.createElement('div');
+ askWebSDKHeaderDiv.id = 'askWebSDKHeaderDiv';
+ askWebSDKHeaderDiv.className = 'askWebSDKHeaderDivClass';
+
+ let askWebSDKHeaderTitle = document.createElement('h4');
+ askWebSDKHeaderTitle.id = 'askWebSDKHeaderTitle';
+ askWebSDKHeaderTitle.className = 'askWebSDKHeaderTitleClass';
+ askWebSDKHeaderTitle.innerText = `Document: ${window.WebViewer.getInstance().Core.documentViewer.getDocument().getFilename()}`;
+ askWebSDKHeaderDiv.appendChild(askWebSDKHeaderTitle);
+ askWebSDKMainDiv.appendChild(askWebSDKHeaderDiv);
+
+ // Chatting container div with assistant and human messages
+ askWebSDKChattingDiv = document.createElement('div');
+ askWebSDKChattingDiv.id = 'askWebSDKChattingDiv';
+ askWebSDKChattingDiv.className = 'askWebSDKChattingDivClass';
+ assistantMessages.forEach((message) => {
+ let messageDiv = document.createElement('div');
+ messageDiv.className = 'askWebSDKAssistantMessageClass';
+ if (Array.isArray(message.content)) {
+ message.content.forEach((contentItem) => {
+ let assistantContentDiv = (contentItem.type === 'info') ? document.createElement('div') : document.createElement('li');
+ assistantContentDiv.className = (contentItem.type === 'info') ? 'askWebSDKInfoMessageClass' : 'askWebSDKQuestionMessageClass';
+ if (contentItem.type === 'question') {
+ assistantContentDiv.onmouseover = () => {
+ assistantContentDiv.style.textDecoration = 'underline';
+ assistantContentDiv.style.color = 'blue';
+ };
+ assistantContentDiv.onmouseout = () => {
+ assistantContentDiv.style.textDecoration = 'none';
+ assistantContentDiv.style.color = 'black';
+ };
+ assistantContentDiv.onclick = () => {
+ createBubble(contentItem.content, 'human');
+ askQuestionByPrompt(contentItem.promptType);
+ };
+ }
+ assistantContentDiv.innerText = contentItem.content;
+ messageDiv.appendChild(assistantContentDiv);
+ });
+ } else
+ messageDiv.innerText = message.content;
+
+ askWebSDKChattingDiv.appendChild(messageDiv);
+ });
+
+ // maintain the conversation sequence
+ if (window.conversationLog.length > 0) {
+ window.conversationLog.forEach((chatMessage) => {
+ let messageDiv = document.createElement('div');
+ messageDiv.className = (chatMessage.role === 'assistant') ? 'askWebSDKAssistantMessageClass' : 'askWebSDKHumanMessageClass';
+ messageDiv.innerHTML = chatMessage.content;
+ askWebSDKChattingDiv.appendChild(messageDiv);
+ });
+ }
+
+ askWebSDKMainDiv.appendChild(askWebSDKChattingDiv);
+
+ // Question input container div with input box and send button
+ let askWebSDKQuestionDiv = document.createElement('div');
+ askWebSDKQuestionDiv.id = 'askWebSDKQuestionDiv';
+ askWebSDKQuestionDiv.className = 'askWebSDKQuestionDivClass';
+
+ let askWebSDKQuestionInput = document.createElement('input');
+ askWebSDKQuestionInput.id = 'askWebSDKQuestionInput';
+ askWebSDKQuestionInput.className = 'askWebSDKQuestionInputClass';
+ askWebSDKQuestionInput.type = 'text';
+ askWebSDKQuestionInput.placeholder = 'Ask your question here...';
+ askWebSDKQuestionInput.onkeydown = (event) => {
+ if (event.key === 'Enter')
+ askWebSDKQuestionButton.click();
+ };
+
+ let askWebSDKQuestionButton = document.createElement('button');
+ askWebSDKQuestionButton.id = 'askWebSDKQuestionButton';
+ askWebSDKQuestionButton.className = 'askWebSDKQuestionButtonClass';
+ askWebSDKQuestionButton.innerText = 'Send';
+ askWebSDKQuestionButton.onclick = () => {
+ let question = askWebSDKQuestionInput.value.trim();
+ if (question === '') {
+ createBubble('Please ask a question first.', 'assistant');
+ return;
+ }
+
+ createBubble(question, 'human');
+
+ // Check if the question is a summarization request
+ if (containsAny(question.toLowerCase(), keywords.summarization)) {
+ // summarize entire document
+ if (question.toLowerCase().includes('document') &&
+ !containsAny(question.toLowerCase(), keywords.area))
+ askQuestionByPrompt('DOCUMENT_SUMMARY');
+
+ // summarize selected text in document
+ if (containsAny(question.toLowerCase(), keywords.selection)) {
+ if (window.selectedText && window.selectedText.trim() !== '')
+ summarizeSelectedText();
+ else
+ createBubble('Please select text in the document first.', 'assistant');
+ }
+
+ if (!question.toLowerCase().includes('document')
+ && !containsAny(question.toLowerCase(), keywords.selection)) {
+ createBubble('Please specify if you want to summarize the entire document or selected text.', 'assistant');
+ }
+ }
+ // Any other questions about the document
+ else {
+ // Start spinning on main div
+ spinner.spin(askWebSDKMainDiv);
+
+ // Send question as document query
+ window.chatbot.sendMessage('DOCUMENT_QUESTION', askWebSDKQuestionInput.value.trim()).then(response => {
+ spinner.stop();
+ let responseText = window.chatbot.responseText(response);
+ responseText = window.chatbot.formatText('DOCUMENT_QUESTION', responseText);
+ createBubble(responseText, 'assistant');
+ }).catch(error => {
+ spinner.stop();
+ createBubble(`Error: ${error.message}`, 'assistant');
+ });
+ }
+ };
+
+ askWebSDKQuestionDiv.appendChild(askWebSDKQuestionInput);
+ askWebSDKQuestionDiv.appendChild(askWebSDKQuestionButton);
+ askWebSDKMainDiv.appendChild(askWebSDKQuestionDiv);
+
+ return askWebSDKMainDiv;
+ },
+ // Handle selected text summary popup click
+ 'askWebSDKPopupClick': () => {
+ createBubble('Summarize the selected text.', 'human');
+ summarizeSelectedText();
+ },
+};
+
+// Function to summarize selected text
+function summarizeSelectedText() {
+ // Start spinning on main div
+ spinner.spin(askWebSDKMainDiv);
+
+ // Combine into single container for all bubble responses
+ window.chatbot.sendMessage('SELECTED_TEXT_SUMMARY', window.selectedText).then(response => {
+ spinner.stop();
+ let responseText = window.chatbot.responseText(response);
+ responseText = window.chatbot.formatText('SELECTED_TEXT_SUMMARY', responseText);
+ createBubble(responseText, 'assistant');
+ }).catch(error => {
+ spinner.stop();
+ createBubble(`Error: ${error.message}`, 'assistant');
+ });
+}
+
+// Function to ask question by prompt
+function askQuestionByPrompt(prompt) {
+ // Start spinning on main div
+ spinner.spin(askWebSDKMainDiv);
+
+ // Create a wrapper callback that stops the spinner after createBubble is called
+ const callbackWrapper = (...args) => {
+ createBubble(...args);
+ spinner.stop();
+ };
+
+ window.chatbot.getAllText(prompt, callbackWrapper);
+}
+
+// Function to create a chat bubble
+function createBubble(content, role) {
+ window.conversationLog.push({ role: role, content: content });
+
+ let messageDiv = document.createElement('div');
+ messageDiv.className = (role === 'assistant') ? 'askWebSDKAssistantMessageClass' : 'askWebSDKHumanMessageClass';
+ messageDiv.innerHTML = content;
+ askWebSDKChattingDiv.appendChild(messageDiv);
+ askWebSDKChattingDiv.scrollTop = askWebSDKChattingDiv.scrollHeight;
+}
+
+function containsAny(text, list) {
+ return list.some(keyword => text.toLowerCase().includes(keyword.toLowerCase()));
+}
+
+export default functionMap;
\ No newline at end of file
diff --git a/webviewer-ask-ai/client/config/ui/styles.css b/webviewer-ask-ai/client/config/ui/styles.css
new file mode 100644
index 0000000..ffd20f7
--- /dev/null
+++ b/webviewer-ask-ai/client/config/ui/styles.css
@@ -0,0 +1,79 @@
+.askWebSDKMainDiv {
+ display: flex;
+ flex-direction: column;
+ overflow: hidden;
+ position: relative;
+}
+
+.askWebSDKHeaderDivClass {
+ background-color: #34495e;
+ color: white;
+ display: flex;
+ align-items: center;
+ padding-left: 10px;
+}
+
+.askWebSDKChattingDivClass {
+ display: flex;
+ flex-direction: column;
+ flex: 1 1 auto;
+ overflow: auto;
+ padding: 10px 10px 150px 10px;
+ height: 80vh;
+ position: fixed;
+}
+
+.askWebSDKQuestionDivClass {
+ width: 600px;
+ background-color: #ecf0f1;
+ padding: 10px;
+ box-sizing: border-box;
+ display: flex;
+ position: fixed;
+ bottom: 0;
+ left: 0;
+ right: 0;
+ z-index: 1000;
+ align-items: center;
+ justify-self: end;
+}
+
+.askWebSDKQuestionInputClass {
+ width: 85%;
+ margin-right: 5px;
+}
+
+.askWebSDKQuestionButtonClass {
+ background-color: Blue;
+ color: white;
+ cursor: pointer;
+}
+
+.askWebSDKAssistantMessageClass {
+ background-color: #ecf0f1;
+ color: black;
+ align-self: flex-start;
+ margin: 5px;
+ padding: 10px;
+ border-radius: 5px;
+ max-width: 70%;
+}
+
+.askWebSDKHumanMessageClass {
+ background-color: #3498db;
+ color: white;
+ align-self: flex-end;
+ margin: 5px;
+ padding: 10px;
+ border-radius: 5px;
+ max-width: 70%;
+}
+
+.askWebSDKInfoMessageClass {
+ margin-top: 5px;
+}
+
+.askWebSDKQuestionMessageClass {
+ margin-top: 5px;
+ cursor: pointer;
+}
\ No newline at end of file
diff --git a/webviewer-ask-ai/client/globals.js b/webviewer-ask-ai/client/globals.js
new file mode 100644
index 0000000..0632df8
--- /dev/null
+++ b/webviewer-ask-ai/client/globals.js
@@ -0,0 +1,10 @@
+// Globals for the app
+const APP_SITE_NAME = "WebViewer Ask AI";
+const ASK_WEB_SDK_ICO = " ";
+
+const setSelectedText = (selectedText) => {
+ window.selectedText = selectedText;
+}
+
+let conversationLog = [];
+window.conversationLog = conversationLog;
\ No newline at end of file
diff --git a/webviewer-ask-ai/client/index.html b/webviewer-ask-ai/client/index.html
new file mode 100644
index 0000000..975235a
--- /dev/null
+++ b/webviewer-ask-ai/client/index.html
@@ -0,0 +1,12 @@
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/webviewer-ask-ai/client/index.js b/webviewer-ask-ai/client/index.js
new file mode 100644
index 0000000..6e9b9d4
--- /dev/null
+++ b/webviewer-ask-ai/client/index.js
@@ -0,0 +1,63 @@
+import createChatbot from './chatbot.js';
+import functionMap from './config/ui/functionMap.js';
+const customUIFile = './config/ui/custom.json';
+
+WebViewer({
+ path: 'lib',
+ initialDoc: 'https://apryse.s3.us-west-1.amazonaws.com/public/files/samples/Report_2011.pdf',
+ fullAPI: true,
+ loadAsPDF: true,
+ enableFilePicker: true, // Enable file picker to open files. In WebViewer -> menu icon -> Open File
+ css: 'config/ui/styles.css',
+ licenseKey: 'YOUR_LICENSE_KEY',
+}, document.getElementById('viewer')
+).then(instance => {
+
+ // Import modular components configuration from JSON file
+ importModularComponents(instance);
+
+ // Set up text selection listener
+ const tool = instance.Core.documentViewer.getTool(instance.Core.Tools.ToolNames.TEXT_SELECT);
+
+ // Listen for text selectionComplete event
+ // The user can select text in the document, to be added as context for the chatbot to be processed
+ // The text selection can span multiple pages
+ tool.addEventListener('selectionComplete', (startQuad, allQuads) => {
+ let selectedText = '';
+ Object.keys(allQuads).forEach(pageNum => {
+ const text = instance.Core.documentViewer.getSelectedText(pageNum);
+ selectedText += text + `\n<> Page ${pageNum}\n`;
+ });
+ setSelectedText(selectedText);
+ });
+
+ // Listen for document loaded event to initialize the chatbot panel
+ instance.Core.documentViewer.addEventListener('documentLoaded', () => {
+ // Initialize chatbot
+ const chatbot = createChatbot();
+ window.chatbot = chatbot;
+ // Clear the conversation on new document load
+ window.conversationLog = [];
+
+ instance.UI.closeElements(['askWebSDKPanel']);
+ instance.UI.openElements(['askWebSDKPanel']);
+ instance.UI.setPanelWidth('askWebSDKPanel', 600);
+ });
+});
+
+// Import modular components configuration from JSON file
+const importModularComponents = async (instance) => {
+ try {
+ const response = await fetch(customUIFile);
+ if (!response.ok)
+ throw new Error(`Failed to import modular components configuration: ${response.statusText}`);
+
+ let customUIConfig = JSON.stringify(await response.json());
+ customUIConfig = customUIConfig.replaceAll("{{APP_SITE_NAME}}", APP_SITE_NAME);
+ customUIConfig = customUIConfig.replaceAll("{{ASK_WEB_SDK_ICO}}", ASK_WEB_SDK_ICO);
+
+ await instance.UI.importModularComponents(JSON.parse(customUIConfig), functionMap);
+ } catch (error) {
+ throw new Error(`Failed to import modular components configuration: ${error.message}`);
+ }
+};
\ No newline at end of file
diff --git a/webviewer-ask-ai/mainsamplesource.json b/webviewer-ask-ai/mainsamplesource.json
new file mode 100644
index 0000000..2edc851
--- /dev/null
+++ b/webviewer-ask-ai/mainsamplesource.json
@@ -0,0 +1,45 @@
+{
+ "githubId": "d10ebe64-2a72-4792-bf63-690811a2aa75",
+ "files": [
+ {
+ "path": "ApryseSDK/webviewer-samples/refs/heads/main/webviewer-ask-ai/client/index.js",
+ "description": "",
+ "reason": ""
+ },
+ {
+ "path": "ApryseSDK/webviewer-samples/refs/heads/main/webviewer-ask-ai/client/chatbot.js",
+ "description": "",
+ "reason": ""
+ },
+ {
+ "path": "ApryseSDK/webviewer-samples/refs/heads/main/webviewer-ask-ai/client/globals.js",
+ "description": "",
+ "reason": ""
+ },
+ {
+ "path": "ApryseSDK/webviewer-samples/refs/heads/main/webviewer-ask-ai/server/handler.js",
+ "description": "",
+ "reason": ""
+ },
+ {
+ "path": "ApryseSDK/webviewer-samples/refs/heads/main/webviewer-ask-ai/server/server.js",
+ "description": "",
+ "reason": ""
+ },
+ {
+ "path": "ApryseSDK/webviewer-samples/refs/heads/main/webviewer-ask-ai/client/config/ui/custom.json",
+ "description": "",
+ "reason": ""
+ },
+ {
+ "path": "ApryseSDK/webviewer-samples/refs/heads/main/webviewer-ask-ai/client/config/ui/functionMap.js",
+ "description": "",
+ "reason": ""
+ },
+ {
+ "path": "ApryseSDK/webviewer-samples/refs/heads/main/webviewer-ask-ai/client/config/ui/styles.css",
+ "description": "",
+ "reason": ""
+ }
+ ]
+}
diff --git a/webviewer-ask-ai/package-lock.json b/webviewer-ask-ai/package-lock.json
new file mode 100644
index 0000000..04d81bf
--- /dev/null
+++ b/webviewer-ask-ai/package-lock.json
@@ -0,0 +1,1486 @@
+{
+ "name": "webviewer-ask-ai",
+ "version": "1.0.0",
+ "lockfileVersion": 3,
+ "requires": true,
+ "packages": {
+ "": {
+ "name": "webviewer-ask-ai",
+ "version": "1.0.0",
+ "hasInstallScript": true,
+ "dependencies": {
+ "@langchain/core": "^1.0.6",
+ "@langchain/openai": "^1.1.2",
+ "@pdftron/webviewer": "^11.8.0",
+ "dotenv": "^17.2.3",
+ "spin.js": "^4.1.2"
+ },
+ "devDependencies": {
+ "body-parser": "^2.2.0",
+ "express": "^5.1.0",
+ "fs-extra": "^11.3.2",
+ "open": "^10.2.0"
+ }
+ },
+ "node_modules/@cfworker/json-schema": {
+ "version": "4.1.1",
+ "resolved": "https://registry.npmjs.org/@cfworker/json-schema/-/json-schema-4.1.1.tgz",
+ "integrity": "sha512-gAmrUZSGtKc3AiBL71iNWxDsyUC5uMaKKGdvzYsBoTW/xi42JQHl7eKV2OYzCUqvc+D2RCcf7EXY2iCyFIk6og==",
+ "license": "MIT"
+ },
+ "node_modules/@langchain/core": {
+ "version": "1.1.1",
+ "resolved": "https://registry.npmjs.org/@langchain/core/-/core-1.1.1.tgz",
+ "integrity": "sha512-vdUoj2CVbb+0Qszi8llP34vdUCfP7bfA9VoFr4Se1pFGu7VAPnk8lBnRat9IvqSxMfTvOHJSd7Rn6TUPjzKsnA==",
+ "license": "MIT",
+ "dependencies": {
+ "@cfworker/json-schema": "^4.0.2",
+ "ansi-styles": "^5.0.0",
+ "camelcase": "6",
+ "decamelize": "1.2.0",
+ "js-tiktoken": "^1.0.12",
+ "langsmith": "^0.3.64",
+ "mustache": "^4.2.0",
+ "p-queue": "^6.6.2",
+ "p-retry": "^7.0.0",
+ "uuid": "^10.0.0",
+ "zod": "^3.25.76 || ^4"
+ },
+ "engines": {
+ "node": ">=20"
+ }
+ },
+ "node_modules/@langchain/openai": {
+ "version": "1.1.3",
+ "resolved": "https://registry.npmjs.org/@langchain/openai/-/openai-1.1.3.tgz",
+ "integrity": "sha512-p+xR+4HRms5Ozjf5miC6U2AYRyNVSTdO7AMBkMYs1Tp6DWHBd+mQ72H8Ogd2dKrPuS5UDJ5dbpI1fS+OrTbgQQ==",
+ "license": "MIT",
+ "dependencies": {
+ "js-tiktoken": "^1.0.12",
+ "openai": "^6.9.0",
+ "zod": "^3.25.76 || ^4"
+ },
+ "engines": {
+ "node": ">=20"
+ },
+ "peerDependencies": {
+ "@langchain/core": "^1.0.0"
+ }
+ },
+ "node_modules/@pdftron/webviewer": {
+ "version": "11.9.0",
+ "resolved": "https://registry.npmjs.org/@pdftron/webviewer/-/webviewer-11.9.0.tgz",
+ "integrity": "sha512-cW1XFG2hLQ7hUFtI+8HuyrmHc2+fN6t/9UQuaJQ/6Ct09qXDS7ljpvRYNJ1YyYAeyqgOo8Q1ZzZ/1zdv+4l25g=="
+ },
+ "node_modules/@types/uuid": {
+ "version": "10.0.0",
+ "resolved": "https://registry.npmjs.org/@types/uuid/-/uuid-10.0.0.tgz",
+ "integrity": "sha512-7gqG38EyHgyP1S+7+xomFtL+ZNHcKv6DwNaCZmJmo1vgMugyF3TCnXVg4t1uk89mLNwnLtnY3TpOpCOyp1/xHQ==",
+ "license": "MIT"
+ },
+ "node_modules/accepts": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/accepts/-/accepts-2.0.0.tgz",
+ "integrity": "sha512-5cvg6CtKwfgdmVqY1WIiXKc3Q1bkRqGLi+2W/6ao+6Y7gu/RCwRuAhGEzh5B4KlszSuTLgZYuqFqo5bImjNKng==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "mime-types": "^3.0.0",
+ "negotiator": "^1.0.0"
+ },
+ "engines": {
+ "node": ">= 0.6"
+ }
+ },
+ "node_modules/ansi-styles": {
+ "version": "5.2.0",
+ "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-5.2.0.tgz",
+ "integrity": "sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA==",
+ "license": "MIT",
+ "engines": {
+ "node": ">=10"
+ },
+ "funding": {
+ "url": "https://github.com/chalk/ansi-styles?sponsor=1"
+ }
+ },
+ "node_modules/base64-js": {
+ "version": "1.5.1",
+ "resolved": "https://registry.npmjs.org/base64-js/-/base64-js-1.5.1.tgz",
+ "integrity": "sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==",
+ "funding": [
+ {
+ "type": "github",
+ "url": "https://github.com/sponsors/feross"
+ },
+ {
+ "type": "patreon",
+ "url": "https://www.patreon.com/feross"
+ },
+ {
+ "type": "consulting",
+ "url": "https://feross.org/support"
+ }
+ ],
+ "license": "MIT"
+ },
+ "node_modules/body-parser": {
+ "version": "2.2.1",
+ "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-2.2.1.tgz",
+ "integrity": "sha512-nfDwkulwiZYQIGwxdy0RUmowMhKcFVcYXUU7m4QlKYim1rUtg83xm2yjZ40QjDuc291AJjjeSc9b++AWHSgSHw==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "bytes": "^3.1.2",
+ "content-type": "^1.0.5",
+ "debug": "^4.4.3",
+ "http-errors": "^2.0.0",
+ "iconv-lite": "^0.7.0",
+ "on-finished": "^2.4.1",
+ "qs": "^6.14.0",
+ "raw-body": "^3.0.1",
+ "type-is": "^2.0.1"
+ },
+ "engines": {
+ "node": ">=18"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/express"
+ }
+ },
+ "node_modules/bundle-name": {
+ "version": "4.1.0",
+ "resolved": "https://registry.npmjs.org/bundle-name/-/bundle-name-4.1.0.tgz",
+ "integrity": "sha512-tjwM5exMg6BGRI+kNmTntNsvdZS1X8BFYS6tnJ2hdH0kVxM6/eVZ2xy+FqStSWvYmtfFMDLIxurorHwDKfDz5Q==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "run-applescript": "^7.0.0"
+ },
+ "engines": {
+ "node": ">=18"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "node_modules/bytes": {
+ "version": "3.1.2",
+ "resolved": "https://registry.npmjs.org/bytes/-/bytes-3.1.2.tgz",
+ "integrity": "sha512-/Nf7TyzTx6S3yRJObOAV7956r8cr2+Oj8AC5dt8wSP3BQAoeX58NoHyCU8P8zGkNXStjTSi6fzO6F0pBdcYbEg==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">= 0.8"
+ }
+ },
+ "node_modules/call-bind-apply-helpers": {
+ "version": "1.0.2",
+ "resolved": "https://registry.npmjs.org/call-bind-apply-helpers/-/call-bind-apply-helpers-1.0.2.tgz",
+ "integrity": "sha512-Sp1ablJ0ivDkSzjcaJdxEunN5/XvksFJ2sMBFfq6x0ryhQV/2b/KwFe21cMpmHtPOSij8K99/wSfoEuTObmuMQ==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "es-errors": "^1.3.0",
+ "function-bind": "^1.1.2"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ }
+ },
+ "node_modules/call-bound": {
+ "version": "1.0.4",
+ "resolved": "https://registry.npmjs.org/call-bound/-/call-bound-1.0.4.tgz",
+ "integrity": "sha512-+ys997U96po4Kx/ABpBCqhA9EuxJaQWDQg7295H4hBphv3IZg0boBKuwYpt4YXp6MZ5AmZQnU/tyMTlRpaSejg==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "call-bind-apply-helpers": "^1.0.2",
+ "get-intrinsic": "^1.3.0"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/camelcase": {
+ "version": "6.3.0",
+ "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-6.3.0.tgz",
+ "integrity": "sha512-Gmy6FhYlCY7uOElZUSbxo2UCDH8owEk996gkbrpsgGtrJLM3J7jGxl9Ic7Qwwj4ivOE5AWZWRMecDdF7hqGjFA==",
+ "license": "MIT",
+ "engines": {
+ "node": ">=10"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "node_modules/chalk": {
+ "version": "4.1.2",
+ "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz",
+ "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==",
+ "license": "MIT",
+ "dependencies": {
+ "ansi-styles": "^4.1.0",
+ "supports-color": "^7.1.0"
+ },
+ "engines": {
+ "node": ">=10"
+ },
+ "funding": {
+ "url": "https://github.com/chalk/chalk?sponsor=1"
+ }
+ },
+ "node_modules/chalk/node_modules/ansi-styles": {
+ "version": "4.3.0",
+ "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz",
+ "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==",
+ "license": "MIT",
+ "dependencies": {
+ "color-convert": "^2.0.1"
+ },
+ "engines": {
+ "node": ">=8"
+ },
+ "funding": {
+ "url": "https://github.com/chalk/ansi-styles?sponsor=1"
+ }
+ },
+ "node_modules/color-convert": {
+ "version": "2.0.1",
+ "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz",
+ "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==",
+ "license": "MIT",
+ "dependencies": {
+ "color-name": "~1.1.4"
+ },
+ "engines": {
+ "node": ">=7.0.0"
+ }
+ },
+ "node_modules/color-name": {
+ "version": "1.1.4",
+ "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz",
+ "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==",
+ "license": "MIT"
+ },
+ "node_modules/console-table-printer": {
+ "version": "2.15.0",
+ "resolved": "https://registry.npmjs.org/console-table-printer/-/console-table-printer-2.15.0.tgz",
+ "integrity": "sha512-SrhBq4hYVjLCkBVOWaTzceJalvn5K1Zq5aQA6wXC/cYjI3frKWNPEMK3sZsJfNNQApvCQmgBcc13ZKmFj8qExw==",
+ "license": "MIT",
+ "dependencies": {
+ "simple-wcswidth": "^1.1.2"
+ }
+ },
+ "node_modules/content-disposition": {
+ "version": "1.0.1",
+ "resolved": "https://registry.npmjs.org/content-disposition/-/content-disposition-1.0.1.tgz",
+ "integrity": "sha512-oIXISMynqSqm241k6kcQ5UwttDILMK4BiurCfGEREw6+X9jkkpEe5T9FZaApyLGGOnFuyMWZpdolTXMtvEJ08Q==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=18"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/express"
+ }
+ },
+ "node_modules/content-type": {
+ "version": "1.0.5",
+ "resolved": "https://registry.npmjs.org/content-type/-/content-type-1.0.5.tgz",
+ "integrity": "sha512-nTjqfcBFEipKdXCv4YDQWCfmcLZKm81ldF0pAopTvyrFGVbcR6P/VAAd5G7N+0tTr8QqiU0tFadD6FK4NtJwOA==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">= 0.6"
+ }
+ },
+ "node_modules/cookie": {
+ "version": "0.7.2",
+ "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.7.2.tgz",
+ "integrity": "sha512-yki5XnKuf750l50uGTllt6kKILY4nQ1eNIQatoXEByZ5dWgnKqbnqmTrBE5B4N7lrMJKQ2ytWMiTO2o0v6Ew/w==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">= 0.6"
+ }
+ },
+ "node_modules/cookie-signature": {
+ "version": "1.2.2",
+ "resolved": "https://registry.npmjs.org/cookie-signature/-/cookie-signature-1.2.2.tgz",
+ "integrity": "sha512-D76uU73ulSXrD1UXF4KE2TMxVVwhsnCgfAyTg9k8P6KGZjlXKrOLe4dJQKI3Bxi5wjesZoFXJWElNWBjPZMbhg==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=6.6.0"
+ }
+ },
+ "node_modules/debug": {
+ "version": "4.4.3",
+ "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.3.tgz",
+ "integrity": "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "ms": "^2.1.3"
+ },
+ "engines": {
+ "node": ">=6.0"
+ },
+ "peerDependenciesMeta": {
+ "supports-color": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/decamelize": {
+ "version": "1.2.0",
+ "resolved": "https://registry.npmjs.org/decamelize/-/decamelize-1.2.0.tgz",
+ "integrity": "sha512-z2S+W9X73hAUUki+N+9Za2lBlun89zigOyGrsax+KUQ6wKW4ZoWpEYBkGhQjwAjjDCkWxhY0VKEhk8wzY7F5cA==",
+ "license": "MIT",
+ "engines": {
+ "node": ">=0.10.0"
+ }
+ },
+ "node_modules/default-browser": {
+ "version": "5.4.0",
+ "resolved": "https://registry.npmjs.org/default-browser/-/default-browser-5.4.0.tgz",
+ "integrity": "sha512-XDuvSq38Hr1MdN47EDvYtx3U0MTqpCEn+F6ft8z2vYDzMrvQhVp0ui9oQdqW3MvK3vqUETglt1tVGgjLuJ5izg==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "bundle-name": "^4.1.0",
+ "default-browser-id": "^5.0.0"
+ },
+ "engines": {
+ "node": ">=18"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "node_modules/default-browser-id": {
+ "version": "5.0.1",
+ "resolved": "https://registry.npmjs.org/default-browser-id/-/default-browser-id-5.0.1.tgz",
+ "integrity": "sha512-x1VCxdX4t+8wVfd1so/9w+vQ4vx7lKd2Qp5tDRutErwmR85OgmfX7RlLRMWafRMY7hbEiXIbudNrjOAPa/hL8Q==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=18"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "node_modules/define-lazy-prop": {
+ "version": "3.0.0",
+ "resolved": "https://registry.npmjs.org/define-lazy-prop/-/define-lazy-prop-3.0.0.tgz",
+ "integrity": "sha512-N+MeXYoqr3pOgn8xfyRPREN7gHakLYjhsHhWGT3fWAiL4IkAt0iDw14QiiEm2bE30c5XX5q0FtAA3CK5f9/BUg==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=12"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "node_modules/depd": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/depd/-/depd-2.0.0.tgz",
+ "integrity": "sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">= 0.8"
+ }
+ },
+ "node_modules/dotenv": {
+ "version": "17.2.3",
+ "resolved": "https://registry.npmjs.org/dotenv/-/dotenv-17.2.3.tgz",
+ "integrity": "sha512-JVUnt+DUIzu87TABbhPmNfVdBDt18BLOWjMUFJMSi/Qqg7NTYtabbvSNJGOJ7afbRuv9D/lngizHtP7QyLQ+9w==",
+ "license": "BSD-2-Clause",
+ "engines": {
+ "node": ">=12"
+ },
+ "funding": {
+ "url": "https://dotenvx.com"
+ }
+ },
+ "node_modules/dunder-proto": {
+ "version": "1.0.1",
+ "resolved": "https://registry.npmjs.org/dunder-proto/-/dunder-proto-1.0.1.tgz",
+ "integrity": "sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "call-bind-apply-helpers": "^1.0.1",
+ "es-errors": "^1.3.0",
+ "gopd": "^1.2.0"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ }
+ },
+ "node_modules/ee-first": {
+ "version": "1.1.1",
+ "resolved": "https://registry.npmjs.org/ee-first/-/ee-first-1.1.1.tgz",
+ "integrity": "sha512-WMwm9LhRUo+WUaRN+vRuETqG89IgZphVSNkdFgeb6sS/E4OrDIN7t48CAewSHXc6C8lefD8KKfr5vY61brQlow==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/encodeurl": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-2.0.0.tgz",
+ "integrity": "sha512-Q0n9HRi4m6JuGIV1eFlmvJB7ZEVxu93IrMyiMsGC0lrMJMWzRgx6WGquyfQgZVb31vhGgXnfmPNNXmxnOkRBrg==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">= 0.8"
+ }
+ },
+ "node_modules/es-define-property": {
+ "version": "1.0.1",
+ "resolved": "https://registry.npmjs.org/es-define-property/-/es-define-property-1.0.1.tgz",
+ "integrity": "sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">= 0.4"
+ }
+ },
+ "node_modules/es-errors": {
+ "version": "1.3.0",
+ "resolved": "https://registry.npmjs.org/es-errors/-/es-errors-1.3.0.tgz",
+ "integrity": "sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">= 0.4"
+ }
+ },
+ "node_modules/es-object-atoms": {
+ "version": "1.1.1",
+ "resolved": "https://registry.npmjs.org/es-object-atoms/-/es-object-atoms-1.1.1.tgz",
+ "integrity": "sha512-FGgH2h8zKNim9ljj7dankFPcICIK9Cp5bm+c2gQSYePhpaG5+esrLODihIorn+Pe6FGJzWhXQotPv73jTaldXA==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "es-errors": "^1.3.0"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ }
+ },
+ "node_modules/escape-html": {
+ "version": "1.0.3",
+ "resolved": "https://registry.npmjs.org/escape-html/-/escape-html-1.0.3.tgz",
+ "integrity": "sha512-NiSupZ4OeuGwr68lGIeym/ksIZMJodUGOSCZ/FSnTxcrekbvqrgdUxlJOMpijaKZVjAJrWrGs/6Jy8OMuyj9ow==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/etag": {
+ "version": "1.8.1",
+ "resolved": "https://registry.npmjs.org/etag/-/etag-1.8.1.tgz",
+ "integrity": "sha512-aIL5Fx7mawVa300al2BnEE4iNvo1qETxLrPI/o05L7z6go7fCw1J6EQmbK4FmJ2AS7kgVF/KEZWufBfdClMcPg==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">= 0.6"
+ }
+ },
+ "node_modules/eventemitter3": {
+ "version": "4.0.7",
+ "resolved": "https://registry.npmjs.org/eventemitter3/-/eventemitter3-4.0.7.tgz",
+ "integrity": "sha512-8guHBZCwKnFhYdHr2ysuRWErTwhoN2X8XELRlrRwpmfeY2jjuUN4taQMsULKUVo1K4DvZl+0pgfyoysHxvmvEw==",
+ "license": "MIT"
+ },
+ "node_modules/express": {
+ "version": "5.2.1",
+ "resolved": "https://registry.npmjs.org/express/-/express-5.2.1.tgz",
+ "integrity": "sha512-hIS4idWWai69NezIdRt2xFVofaF4j+6INOpJlVOLDO8zXGpUVEVzIYk12UUi2JzjEzWL3IOAxcTubgz9Po0yXw==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "accepts": "^2.0.0",
+ "body-parser": "^2.2.1",
+ "content-disposition": "^1.0.0",
+ "content-type": "^1.0.5",
+ "cookie": "^0.7.1",
+ "cookie-signature": "^1.2.1",
+ "debug": "^4.4.0",
+ "depd": "^2.0.0",
+ "encodeurl": "^2.0.0",
+ "escape-html": "^1.0.3",
+ "etag": "^1.8.1",
+ "finalhandler": "^2.1.0",
+ "fresh": "^2.0.0",
+ "http-errors": "^2.0.0",
+ "merge-descriptors": "^2.0.0",
+ "mime-types": "^3.0.0",
+ "on-finished": "^2.4.1",
+ "once": "^1.4.0",
+ "parseurl": "^1.3.3",
+ "proxy-addr": "^2.0.7",
+ "qs": "^6.14.0",
+ "range-parser": "^1.2.1",
+ "router": "^2.2.0",
+ "send": "^1.1.0",
+ "serve-static": "^2.2.0",
+ "statuses": "^2.0.1",
+ "type-is": "^2.0.1",
+ "vary": "^1.1.2"
+ },
+ "engines": {
+ "node": ">= 18"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/express"
+ }
+ },
+ "node_modules/finalhandler": {
+ "version": "2.1.1",
+ "resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-2.1.1.tgz",
+ "integrity": "sha512-S8KoZgRZN+a5rNwqTxlZZePjT/4cnm0ROV70LedRHZ0p8u9fRID0hJUZQpkKLzro8LfmC8sx23bY6tVNxv8pQA==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "debug": "^4.4.0",
+ "encodeurl": "^2.0.0",
+ "escape-html": "^1.0.3",
+ "on-finished": "^2.4.1",
+ "parseurl": "^1.3.3",
+ "statuses": "^2.0.1"
+ },
+ "engines": {
+ "node": ">= 18.0.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/express"
+ }
+ },
+ "node_modules/forwarded": {
+ "version": "0.2.0",
+ "resolved": "https://registry.npmjs.org/forwarded/-/forwarded-0.2.0.tgz",
+ "integrity": "sha512-buRG0fpBtRHSTCOASe6hD258tEubFoRLb4ZNA6NxMVHNw2gOcwHo9wyablzMzOA5z9xA9L1KNjk/Nt6MT9aYow==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">= 0.6"
+ }
+ },
+ "node_modules/fresh": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/fresh/-/fresh-2.0.0.tgz",
+ "integrity": "sha512-Rx/WycZ60HOaqLKAi6cHRKKI7zxWbJ31MhntmtwMoaTeF7XFH9hhBp8vITaMidfljRQ6eYWCKkaTK+ykVJHP2A==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">= 0.8"
+ }
+ },
+ "node_modules/fs-extra": {
+ "version": "11.3.2",
+ "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-11.3.2.tgz",
+ "integrity": "sha512-Xr9F6z6up6Ws+NjzMCZc6WXg2YFRlrLP9NQDO3VQrWrfiojdhS56TzueT88ze0uBdCTwEIhQ3ptnmKeWGFAe0A==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "graceful-fs": "^4.2.0",
+ "jsonfile": "^6.0.1",
+ "universalify": "^2.0.0"
+ },
+ "engines": {
+ "node": ">=14.14"
+ }
+ },
+ "node_modules/function-bind": {
+ "version": "1.1.2",
+ "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz",
+ "integrity": "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==",
+ "dev": true,
+ "license": "MIT",
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/get-intrinsic": {
+ "version": "1.3.0",
+ "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.3.0.tgz",
+ "integrity": "sha512-9fSjSaos/fRIVIp+xSJlE6lfwhES7LNtKaCBIamHsjr2na1BiABJPo0mOjjz8GJDURarmCPGqaiVg5mfjb98CQ==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "call-bind-apply-helpers": "^1.0.2",
+ "es-define-property": "^1.0.1",
+ "es-errors": "^1.3.0",
+ "es-object-atoms": "^1.1.1",
+ "function-bind": "^1.1.2",
+ "get-proto": "^1.0.1",
+ "gopd": "^1.2.0",
+ "has-symbols": "^1.1.0",
+ "hasown": "^2.0.2",
+ "math-intrinsics": "^1.1.0"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/get-proto": {
+ "version": "1.0.1",
+ "resolved": "https://registry.npmjs.org/get-proto/-/get-proto-1.0.1.tgz",
+ "integrity": "sha512-sTSfBjoXBp89JvIKIefqw7U2CCebsc74kiY6awiGogKtoSGbgjYE/G/+l9sF3MWFPNc9IcoOC4ODfKHfxFmp0g==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "dunder-proto": "^1.0.1",
+ "es-object-atoms": "^1.0.0"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ }
+ },
+ "node_modules/gopd": {
+ "version": "1.2.0",
+ "resolved": "https://registry.npmjs.org/gopd/-/gopd-1.2.0.tgz",
+ "integrity": "sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">= 0.4"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/graceful-fs": {
+ "version": "4.2.11",
+ "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.11.tgz",
+ "integrity": "sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==",
+ "dev": true,
+ "license": "ISC"
+ },
+ "node_modules/has-flag": {
+ "version": "4.0.0",
+ "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz",
+ "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==",
+ "license": "MIT",
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/has-symbols": {
+ "version": "1.1.0",
+ "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.1.0.tgz",
+ "integrity": "sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">= 0.4"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/hasown": {
+ "version": "2.0.2",
+ "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.2.tgz",
+ "integrity": "sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "function-bind": "^1.1.2"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ }
+ },
+ "node_modules/http-errors": {
+ "version": "2.0.1",
+ "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-2.0.1.tgz",
+ "integrity": "sha512-4FbRdAX+bSdmo4AUFuS0WNiPz8NgFt+r8ThgNWmlrjQjt1Q7ZR9+zTlce2859x4KSXrwIsaeTqDoKQmtP8pLmQ==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "depd": "~2.0.0",
+ "inherits": "~2.0.4",
+ "setprototypeof": "~1.2.0",
+ "statuses": "~2.0.2",
+ "toidentifier": "~1.0.1"
+ },
+ "engines": {
+ "node": ">= 0.8"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/express"
+ }
+ },
+ "node_modules/iconv-lite": {
+ "version": "0.7.0",
+ "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.7.0.tgz",
+ "integrity": "sha512-cf6L2Ds3h57VVmkZe+Pn+5APsT7FpqJtEhhieDCvrE2MK5Qk9MyffgQyuxQTm6BChfeZNtcOLHp9IcWRVcIcBQ==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "safer-buffer": ">= 2.1.2 < 3.0.0"
+ },
+ "engines": {
+ "node": ">=0.10.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/express"
+ }
+ },
+ "node_modules/inherits": {
+ "version": "2.0.4",
+ "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz",
+ "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==",
+ "dev": true,
+ "license": "ISC"
+ },
+ "node_modules/ipaddr.js": {
+ "version": "1.9.1",
+ "resolved": "https://registry.npmjs.org/ipaddr.js/-/ipaddr.js-1.9.1.tgz",
+ "integrity": "sha512-0KI/607xoxSToH7GjN1FfSbLoU0+btTicjsQSWQlh/hZykN8KpmMf7uYwPW3R+akZ6R/w18ZlXSHBYXiYUPO3g==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">= 0.10"
+ }
+ },
+ "node_modules/is-docker": {
+ "version": "3.0.0",
+ "resolved": "https://registry.npmjs.org/is-docker/-/is-docker-3.0.0.tgz",
+ "integrity": "sha512-eljcgEDlEns/7AXFosB5K/2nCM4P7FQPkGc/DWLy5rmFEWvZayGrik1d9/QIY5nJ4f9YsVvBkA6kJpHn9rISdQ==",
+ "dev": true,
+ "license": "MIT",
+ "bin": {
+ "is-docker": "cli.js"
+ },
+ "engines": {
+ "node": "^12.20.0 || ^14.13.1 || >=16.0.0"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "node_modules/is-inside-container": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/is-inside-container/-/is-inside-container-1.0.0.tgz",
+ "integrity": "sha512-KIYLCCJghfHZxqjYBE7rEy0OBuTd5xCHS7tHVgvCLkx7StIoaxwNW3hCALgEUjFfeRk+MG/Qxmp/vtETEF3tRA==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "is-docker": "^3.0.0"
+ },
+ "bin": {
+ "is-inside-container": "cli.js"
+ },
+ "engines": {
+ "node": ">=14.16"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "node_modules/is-network-error": {
+ "version": "1.3.0",
+ "resolved": "https://registry.npmjs.org/is-network-error/-/is-network-error-1.3.0.tgz",
+ "integrity": "sha512-6oIwpsgRfnDiyEDLMay/GqCl3HoAtH5+RUKW29gYkL0QA+ipzpDLA16yQs7/RHCSu+BwgbJaOUqa4A99qNVQVw==",
+ "license": "MIT",
+ "engines": {
+ "node": ">=16"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "node_modules/is-promise": {
+ "version": "4.0.0",
+ "resolved": "https://registry.npmjs.org/is-promise/-/is-promise-4.0.0.tgz",
+ "integrity": "sha512-hvpoI6korhJMnej285dSg6nu1+e6uxs7zG3BYAm5byqDsgJNWwxzM6z6iZiAgQR4TJ30JmBTOwqZUw3WlyH3AQ==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/is-wsl": {
+ "version": "3.1.0",
+ "resolved": "https://registry.npmjs.org/is-wsl/-/is-wsl-3.1.0.tgz",
+ "integrity": "sha512-UcVfVfaK4Sc4m7X3dUSoHoozQGBEFeDC+zVo06t98xe8CzHSZZBekNXH+tu0NalHolcJ/QAGqS46Hef7QXBIMw==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "is-inside-container": "^1.0.0"
+ },
+ "engines": {
+ "node": ">=16"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "node_modules/js-tiktoken": {
+ "version": "1.0.21",
+ "resolved": "https://registry.npmjs.org/js-tiktoken/-/js-tiktoken-1.0.21.tgz",
+ "integrity": "sha512-biOj/6M5qdgx5TKjDnFT1ymSpM5tbd3ylwDtrQvFQSu0Z7bBYko2dF+W/aUkXUPuk6IVpRxk/3Q2sHOzGlS36g==",
+ "license": "MIT",
+ "dependencies": {
+ "base64-js": "^1.5.1"
+ }
+ },
+ "node_modules/jsonfile": {
+ "version": "6.2.0",
+ "resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-6.2.0.tgz",
+ "integrity": "sha512-FGuPw30AdOIUTRMC2OMRtQV+jkVj2cfPqSeWXv1NEAJ1qZ5zb1X6z1mFhbfOB/iy3ssJCD+3KuZ8r8C3uVFlAg==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "universalify": "^2.0.0"
+ },
+ "optionalDependencies": {
+ "graceful-fs": "^4.1.6"
+ }
+ },
+ "node_modules/langsmith": {
+ "version": "0.3.82",
+ "resolved": "https://registry.npmjs.org/langsmith/-/langsmith-0.3.82.tgz",
+ "integrity": "sha512-RTcxtRm0zp2lV+pMesMW7EZSsIlqN7OmR2F6sZ/sOFQwmcLVl+VErMPV4VkX4Sycs4/EIAFT5hpr36EqiHoikQ==",
+ "license": "MIT",
+ "dependencies": {
+ "@types/uuid": "^10.0.0",
+ "chalk": "^4.1.2",
+ "console-table-printer": "^2.12.1",
+ "p-queue": "^6.6.2",
+ "semver": "^7.6.3",
+ "uuid": "^10.0.0"
+ },
+ "peerDependencies": {
+ "@opentelemetry/api": "*",
+ "@opentelemetry/exporter-trace-otlp-proto": "*",
+ "@opentelemetry/sdk-trace-base": "*",
+ "openai": "*"
+ },
+ "peerDependenciesMeta": {
+ "@opentelemetry/api": {
+ "optional": true
+ },
+ "@opentelemetry/exporter-trace-otlp-proto": {
+ "optional": true
+ },
+ "@opentelemetry/sdk-trace-base": {
+ "optional": true
+ },
+ "openai": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/math-intrinsics": {
+ "version": "1.1.0",
+ "resolved": "https://registry.npmjs.org/math-intrinsics/-/math-intrinsics-1.1.0.tgz",
+ "integrity": "sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">= 0.4"
+ }
+ },
+ "node_modules/media-typer": {
+ "version": "1.1.0",
+ "resolved": "https://registry.npmjs.org/media-typer/-/media-typer-1.1.0.tgz",
+ "integrity": "sha512-aisnrDP4GNe06UcKFnV5bfMNPBUw4jsLGaWwWfnH3v02GnBuXX2MCVn5RbrWo0j3pczUilYblq7fQ7Nw2t5XKw==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">= 0.8"
+ }
+ },
+ "node_modules/merge-descriptors": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/merge-descriptors/-/merge-descriptors-2.0.0.tgz",
+ "integrity": "sha512-Snk314V5ayFLhp3fkUREub6WtjBfPdCPY1Ln8/8munuLuiYhsABgBVWsozAG+MWMbVEvcdcpbi9R7ww22l9Q3g==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=18"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "node_modules/mime-db": {
+ "version": "1.54.0",
+ "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.54.0.tgz",
+ "integrity": "sha512-aU5EJuIN2WDemCcAp2vFBfp/m4EAhWJnUNSSw0ixs7/kXbd6Pg64EmwJkNdFhB8aWt1sH2CTXrLxo/iAGV3oPQ==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">= 0.6"
+ }
+ },
+ "node_modules/mime-types": {
+ "version": "3.0.2",
+ "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-3.0.2.tgz",
+ "integrity": "sha512-Lbgzdk0h4juoQ9fCKXW4by0UJqj+nOOrI9MJ1sSj4nI8aI2eo1qmvQEie4VD1glsS250n15LsWsYtCugiStS5A==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "mime-db": "^1.54.0"
+ },
+ "engines": {
+ "node": ">=18"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/express"
+ }
+ },
+ "node_modules/ms": {
+ "version": "2.1.3",
+ "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz",
+ "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/mustache": {
+ "version": "4.2.0",
+ "resolved": "https://registry.npmjs.org/mustache/-/mustache-4.2.0.tgz",
+ "integrity": "sha512-71ippSywq5Yb7/tVYyGbkBggbU8H3u5Rz56fH60jGFgr8uHwxs+aSKeqmluIVzM0m0kB7xQjKS6qPfd0b2ZoqQ==",
+ "license": "MIT",
+ "bin": {
+ "mustache": "bin/mustache"
+ }
+ },
+ "node_modules/negotiator": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-1.0.0.tgz",
+ "integrity": "sha512-8Ofs/AUQh8MaEcrlq5xOX0CQ9ypTF5dl78mjlMNfOK08fzpgTHQRQPBxcPlEtIw0yRpws+Zo/3r+5WRby7u3Gg==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">= 0.6"
+ }
+ },
+ "node_modules/object-inspect": {
+ "version": "1.13.4",
+ "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.13.4.tgz",
+ "integrity": "sha512-W67iLl4J2EXEGTbfeHCffrjDfitvLANg0UlX3wFUUSTx92KXRFegMHUVgSqE+wvhAbi4WqjGg9czysTV2Epbew==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">= 0.4"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/on-finished": {
+ "version": "2.4.1",
+ "resolved": "https://registry.npmjs.org/on-finished/-/on-finished-2.4.1.tgz",
+ "integrity": "sha512-oVlzkg3ENAhCk2zdv7IJwd/QUD4z2RxRwpkcGY8psCVcCYZNq4wYnVWALHM+brtuJjePWiYF/ClmuDr8Ch5+kg==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "ee-first": "1.1.1"
+ },
+ "engines": {
+ "node": ">= 0.8"
+ }
+ },
+ "node_modules/once": {
+ "version": "1.4.0",
+ "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz",
+ "integrity": "sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==",
+ "dev": true,
+ "license": "ISC",
+ "dependencies": {
+ "wrappy": "1"
+ }
+ },
+ "node_modules/open": {
+ "version": "10.2.0",
+ "resolved": "https://registry.npmjs.org/open/-/open-10.2.0.tgz",
+ "integrity": "sha512-YgBpdJHPyQ2UE5x+hlSXcnejzAvD0b22U2OuAP+8OnlJT+PjWPxtgmGqKKc+RgTM63U9gN0YzrYc71R2WT/hTA==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "default-browser": "^5.2.1",
+ "define-lazy-prop": "^3.0.0",
+ "is-inside-container": "^1.0.0",
+ "wsl-utils": "^0.1.0"
+ },
+ "engines": {
+ "node": ">=18"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "node_modules/openai": {
+ "version": "6.9.1",
+ "resolved": "https://registry.npmjs.org/openai/-/openai-6.9.1.tgz",
+ "integrity": "sha512-vQ5Rlt0ZgB3/BNmTa7bIijYFhz3YBceAA3Z4JuoMSBftBF9YqFHIEhZakSs+O/Ad7EaoEimZvHxD5ylRjN11Lg==",
+ "license": "Apache-2.0",
+ "bin": {
+ "openai": "bin/cli"
+ },
+ "peerDependencies": {
+ "ws": "^8.18.0",
+ "zod": "^3.25 || ^4.0"
+ },
+ "peerDependenciesMeta": {
+ "ws": {
+ "optional": true
+ },
+ "zod": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/p-finally": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/p-finally/-/p-finally-1.0.0.tgz",
+ "integrity": "sha512-LICb2p9CB7FS+0eR1oqWnHhp0FljGLZCWBE9aix0Uye9W8LTQPwMTYVGWQWIw9RdQiDg4+epXQODwIYJtSJaow==",
+ "license": "MIT",
+ "engines": {
+ "node": ">=4"
+ }
+ },
+ "node_modules/p-queue": {
+ "version": "6.6.2",
+ "resolved": "https://registry.npmjs.org/p-queue/-/p-queue-6.6.2.tgz",
+ "integrity": "sha512-RwFpb72c/BhQLEXIZ5K2e+AhgNVmIejGlTgiB9MzZ0e93GRvqZ7uSi0dvRF7/XIXDeNkra2fNHBxTyPDGySpjQ==",
+ "license": "MIT",
+ "dependencies": {
+ "eventemitter3": "^4.0.4",
+ "p-timeout": "^3.2.0"
+ },
+ "engines": {
+ "node": ">=8"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "node_modules/p-retry": {
+ "version": "7.1.0",
+ "resolved": "https://registry.npmjs.org/p-retry/-/p-retry-7.1.0.tgz",
+ "integrity": "sha512-xL4PiFRQa/f9L9ZvR4/gUCRNus4N8YX80ku8kv9Jqz+ZokkiZLM0bcvX0gm1F3PDi9SPRsww1BDsTWgE6Y1GLQ==",
+ "license": "MIT",
+ "dependencies": {
+ "is-network-error": "^1.1.0"
+ },
+ "engines": {
+ "node": ">=20"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "node_modules/p-timeout": {
+ "version": "3.2.0",
+ "resolved": "https://registry.npmjs.org/p-timeout/-/p-timeout-3.2.0.tgz",
+ "integrity": "sha512-rhIwUycgwwKcP9yTOOFK/AKsAopjjCakVqLHePO3CC6Mir1Z99xT+R63jZxAT5lFZLa2inS5h+ZS2GvR99/FBg==",
+ "license": "MIT",
+ "dependencies": {
+ "p-finally": "^1.0.0"
+ },
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/parseurl": {
+ "version": "1.3.3",
+ "resolved": "https://registry.npmjs.org/parseurl/-/parseurl-1.3.3.tgz",
+ "integrity": "sha512-CiyeOxFT/JZyN5m0z9PfXw4SCBJ6Sygz1Dpl0wqjlhDEGGBP1GnsUVEL0p63hoG1fcj3fHynXi9NYO4nWOL+qQ==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">= 0.8"
+ }
+ },
+ "node_modules/path-to-regexp": {
+ "version": "8.3.0",
+ "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-8.3.0.tgz",
+ "integrity": "sha512-7jdwVIRtsP8MYpdXSwOS0YdD0Du+qOoF/AEPIt88PcCFrZCzx41oxku1jD88hZBwbNUIEfpqvuhjFaMAqMTWnA==",
+ "dev": true,
+ "license": "MIT",
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/express"
+ }
+ },
+ "node_modules/proxy-addr": {
+ "version": "2.0.7",
+ "resolved": "https://registry.npmjs.org/proxy-addr/-/proxy-addr-2.0.7.tgz",
+ "integrity": "sha512-llQsMLSUDUPT44jdrU/O37qlnifitDP+ZwrmmZcoSKyLKvtZxpyV0n2/bD/N4tBAAZ/gJEdZU7KMraoK1+XYAg==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "forwarded": "0.2.0",
+ "ipaddr.js": "1.9.1"
+ },
+ "engines": {
+ "node": ">= 0.10"
+ }
+ },
+ "node_modules/qs": {
+ "version": "6.14.0",
+ "resolved": "https://registry.npmjs.org/qs/-/qs-6.14.0.tgz",
+ "integrity": "sha512-YWWTjgABSKcvs/nWBi9PycY/JiPJqOD4JA6o9Sej2AtvSGarXxKC3OQSk4pAarbdQlKAh5D4FCQkJNkW+GAn3w==",
+ "dev": true,
+ "license": "BSD-3-Clause",
+ "dependencies": {
+ "side-channel": "^1.1.0"
+ },
+ "engines": {
+ "node": ">=0.6"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/range-parser": {
+ "version": "1.2.1",
+ "resolved": "https://registry.npmjs.org/range-parser/-/range-parser-1.2.1.tgz",
+ "integrity": "sha512-Hrgsx+orqoygnmhFbKaHE6c296J+HTAQXoxEF6gNupROmmGJRoyzfG3ccAveqCBrwr/2yxQ5BVd/GTl5agOwSg==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">= 0.6"
+ }
+ },
+ "node_modules/raw-body": {
+ "version": "3.0.2",
+ "resolved": "https://registry.npmjs.org/raw-body/-/raw-body-3.0.2.tgz",
+ "integrity": "sha512-K5zQjDllxWkf7Z5xJdV0/B0WTNqx6vxG70zJE4N0kBs4LovmEYWJzQGxC9bS9RAKu3bgM40lrd5zoLJ12MQ5BA==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "bytes": "~3.1.2",
+ "http-errors": "~2.0.1",
+ "iconv-lite": "~0.7.0",
+ "unpipe": "~1.0.0"
+ },
+ "engines": {
+ "node": ">= 0.10"
+ }
+ },
+ "node_modules/router": {
+ "version": "2.2.0",
+ "resolved": "https://registry.npmjs.org/router/-/router-2.2.0.tgz",
+ "integrity": "sha512-nLTrUKm2UyiL7rlhapu/Zl45FwNgkZGaCpZbIHajDYgwlJCOzLSk+cIPAnsEqV955GjILJnKbdQC1nVPz+gAYQ==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "debug": "^4.4.0",
+ "depd": "^2.0.0",
+ "is-promise": "^4.0.0",
+ "parseurl": "^1.3.3",
+ "path-to-regexp": "^8.0.0"
+ },
+ "engines": {
+ "node": ">= 18"
+ }
+ },
+ "node_modules/run-applescript": {
+ "version": "7.1.0",
+ "resolved": "https://registry.npmjs.org/run-applescript/-/run-applescript-7.1.0.tgz",
+ "integrity": "sha512-DPe5pVFaAsinSaV6QjQ6gdiedWDcRCbUuiQfQa2wmWV7+xC9bGulGI8+TdRmoFkAPaBXk8CrAbnlY2ISniJ47Q==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=18"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "node_modules/safer-buffer": {
+ "version": "2.1.2",
+ "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz",
+ "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/semver": {
+ "version": "7.7.3",
+ "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.3.tgz",
+ "integrity": "sha512-SdsKMrI9TdgjdweUSR9MweHA4EJ8YxHn8DFaDisvhVlUOe4BF1tLD7GAj0lIqWVl+dPb/rExr0Btby5loQm20Q==",
+ "license": "ISC",
+ "bin": {
+ "semver": "bin/semver.js"
+ },
+ "engines": {
+ "node": ">=10"
+ }
+ },
+ "node_modules/send": {
+ "version": "1.2.0",
+ "resolved": "https://registry.npmjs.org/send/-/send-1.2.0.tgz",
+ "integrity": "sha512-uaW0WwXKpL9blXE2o0bRhoL2EGXIrZxQ2ZQ4mgcfoBxdFmQold+qWsD2jLrfZ0trjKL6vOw0j//eAwcALFjKSw==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "debug": "^4.3.5",
+ "encodeurl": "^2.0.0",
+ "escape-html": "^1.0.3",
+ "etag": "^1.8.1",
+ "fresh": "^2.0.0",
+ "http-errors": "^2.0.0",
+ "mime-types": "^3.0.1",
+ "ms": "^2.1.3",
+ "on-finished": "^2.4.1",
+ "range-parser": "^1.2.1",
+ "statuses": "^2.0.1"
+ },
+ "engines": {
+ "node": ">= 18"
+ }
+ },
+ "node_modules/serve-static": {
+ "version": "2.2.0",
+ "resolved": "https://registry.npmjs.org/serve-static/-/serve-static-2.2.0.tgz",
+ "integrity": "sha512-61g9pCh0Vnh7IutZjtLGGpTA355+OPn2TyDv/6ivP2h/AdAVX9azsoxmg2/M6nZeQZNYBEwIcsne1mJd9oQItQ==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "encodeurl": "^2.0.0",
+ "escape-html": "^1.0.3",
+ "parseurl": "^1.3.3",
+ "send": "^1.2.0"
+ },
+ "engines": {
+ "node": ">= 18"
+ }
+ },
+ "node_modules/setprototypeof": {
+ "version": "1.2.0",
+ "resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.2.0.tgz",
+ "integrity": "sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw==",
+ "dev": true,
+ "license": "ISC"
+ },
+ "node_modules/side-channel": {
+ "version": "1.1.0",
+ "resolved": "https://registry.npmjs.org/side-channel/-/side-channel-1.1.0.tgz",
+ "integrity": "sha512-ZX99e6tRweoUXqR+VBrslhda51Nh5MTQwou5tnUDgbtyM0dBgmhEDtWGP/xbKn6hqfPRHujUNwz5fy/wbbhnpw==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "es-errors": "^1.3.0",
+ "object-inspect": "^1.13.3",
+ "side-channel-list": "^1.0.0",
+ "side-channel-map": "^1.0.1",
+ "side-channel-weakmap": "^1.0.2"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/side-channel-list": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/side-channel-list/-/side-channel-list-1.0.0.tgz",
+ "integrity": "sha512-FCLHtRD/gnpCiCHEiJLOwdmFP+wzCmDEkc9y7NsYxeF4u7Btsn1ZuwgwJGxImImHicJArLP4R0yX4c2KCrMrTA==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "es-errors": "^1.3.0",
+ "object-inspect": "^1.13.3"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/side-channel-map": {
+ "version": "1.0.1",
+ "resolved": "https://registry.npmjs.org/side-channel-map/-/side-channel-map-1.0.1.tgz",
+ "integrity": "sha512-VCjCNfgMsby3tTdo02nbjtM/ewra6jPHmpThenkTYh8pG9ucZ/1P8So4u4FGBek/BjpOVsDCMoLA/iuBKIFXRA==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "call-bound": "^1.0.2",
+ "es-errors": "^1.3.0",
+ "get-intrinsic": "^1.2.5",
+ "object-inspect": "^1.13.3"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/side-channel-weakmap": {
+ "version": "1.0.2",
+ "resolved": "https://registry.npmjs.org/side-channel-weakmap/-/side-channel-weakmap-1.0.2.tgz",
+ "integrity": "sha512-WPS/HvHQTYnHisLo9McqBHOJk2FkHO/tlpvldyrnem4aeQp4hai3gythswg6p01oSoTl58rcpiFAjF2br2Ak2A==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "call-bound": "^1.0.2",
+ "es-errors": "^1.3.0",
+ "get-intrinsic": "^1.2.5",
+ "object-inspect": "^1.13.3",
+ "side-channel-map": "^1.0.1"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/simple-wcswidth": {
+ "version": "1.1.2",
+ "resolved": "https://registry.npmjs.org/simple-wcswidth/-/simple-wcswidth-1.1.2.tgz",
+ "integrity": "sha512-j7piyCjAeTDSjzTSQ7DokZtMNwNlEAyxqSZeCS+CXH7fJ4jx3FuJ/mTW3mE+6JLs4VJBbcll0Kjn+KXI5t21Iw==",
+ "license": "MIT"
+ },
+ "node_modules/spin.js": {
+ "version": "4.1.2",
+ "resolved": "https://registry.npmjs.org/spin.js/-/spin.js-4.1.2.tgz",
+ "integrity": "sha512-ua/yEpxEwyEUWs57tMQYdik/KJ12sQRyMXjSlK/Ai927aEUDVY3FXUi4ml4VvlLCTQNIjC6tHyjSLBrJzFAqMA==",
+ "license": "MIT"
+ },
+ "node_modules/statuses": {
+ "version": "2.0.2",
+ "resolved": "https://registry.npmjs.org/statuses/-/statuses-2.0.2.tgz",
+ "integrity": "sha512-DvEy55V3DB7uknRo+4iOGT5fP1slR8wQohVdknigZPMpMstaKJQWhwiYBACJE3Ul2pTnATihhBYnRhZQHGBiRw==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">= 0.8"
+ }
+ },
+ "node_modules/supports-color": {
+ "version": "7.2.0",
+ "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz",
+ "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==",
+ "license": "MIT",
+ "dependencies": {
+ "has-flag": "^4.0.0"
+ },
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/toidentifier": {
+ "version": "1.0.1",
+ "resolved": "https://registry.npmjs.org/toidentifier/-/toidentifier-1.0.1.tgz",
+ "integrity": "sha512-o5sSPKEkg/DIQNmH43V0/uerLrpzVedkUh8tGNvaeXpfpuwjKenlSox/2O/BTlZUtEe+JG7s5YhEz608PlAHRA==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=0.6"
+ }
+ },
+ "node_modules/type-is": {
+ "version": "2.0.1",
+ "resolved": "https://registry.npmjs.org/type-is/-/type-is-2.0.1.tgz",
+ "integrity": "sha512-OZs6gsjF4vMp32qrCbiVSkrFmXtG/AZhY3t0iAMrMBiAZyV9oALtXO8hsrHbMXF9x6L3grlFuwW2oAz7cav+Gw==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "content-type": "^1.0.5",
+ "media-typer": "^1.1.0",
+ "mime-types": "^3.0.0"
+ },
+ "engines": {
+ "node": ">= 0.6"
+ }
+ },
+ "node_modules/universalify": {
+ "version": "2.0.1",
+ "resolved": "https://registry.npmjs.org/universalify/-/universalify-2.0.1.tgz",
+ "integrity": "sha512-gptHNQghINnc/vTGIk0SOFGFNXw7JVrlRUtConJRlvaw6DuX0wO5Jeko9sWrMBhh+PsYAZ7oXAiOnf/UKogyiw==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">= 10.0.0"
+ }
+ },
+ "node_modules/unpipe": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/unpipe/-/unpipe-1.0.0.tgz",
+ "integrity": "sha512-pjy2bYhSsufwWlKwPc+l3cN7+wuJlK6uz0YdJEOlQDbl6jo/YlPi4mb8agUkVC8BF7V8NuzeyPNqRksA3hztKQ==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">= 0.8"
+ }
+ },
+ "node_modules/uuid": {
+ "version": "10.0.0",
+ "resolved": "https://registry.npmjs.org/uuid/-/uuid-10.0.0.tgz",
+ "integrity": "sha512-8XkAphELsDnEGrDxUOHB3RGvXz6TeuYSGEZBOjtTtPm2lwhGBjLgOzLHB63IUWfBpNucQjND6d3AOudO+H3RWQ==",
+ "funding": [
+ "https://github.com/sponsors/broofa",
+ "https://github.com/sponsors/ctavan"
+ ],
+ "license": "MIT",
+ "bin": {
+ "uuid": "dist/bin/uuid"
+ }
+ },
+ "node_modules/vary": {
+ "version": "1.1.2",
+ "resolved": "https://registry.npmjs.org/vary/-/vary-1.1.2.tgz",
+ "integrity": "sha512-BNGbWLfd0eUPabhkXUVm0j8uuvREyTh5ovRa/dyow/BqAbZJyC+5fU+IzQOzmAKzYqYRAISoRhdQr3eIZ/PXqg==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">= 0.8"
+ }
+ },
+ "node_modules/wrappy": {
+ "version": "1.0.2",
+ "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz",
+ "integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==",
+ "dev": true,
+ "license": "ISC"
+ },
+ "node_modules/wsl-utils": {
+ "version": "0.1.0",
+ "resolved": "https://registry.npmjs.org/wsl-utils/-/wsl-utils-0.1.0.tgz",
+ "integrity": "sha512-h3Fbisa2nKGPxCpm89Hk33lBLsnaGBvctQopaBSOW/uIs6FTe1ATyAnKFJrzVs9vpGdsTe73WF3V4lIsk4Gacw==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "is-wsl": "^3.1.0"
+ },
+ "engines": {
+ "node": ">=18"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "node_modules/zod": {
+ "version": "4.1.13",
+ "resolved": "https://registry.npmjs.org/zod/-/zod-4.1.13.tgz",
+ "integrity": "sha512-AvvthqfqrAhNH9dnfmrfKzX5upOdjUVJYFqNSlkmGf64gRaTzlPwz99IHYnVs28qYAybvAlBV+H7pn0saFY4Ig==",
+ "license": "MIT",
+ "funding": {
+ "url": "https://github.com/sponsors/colinhacks"
+ }
+ }
+ }
+}
diff --git a/webviewer-ask-ai/package.json b/webviewer-ask-ai/package.json
new file mode 100644
index 0000000..38b4782
--- /dev/null
+++ b/webviewer-ask-ai/package.json
@@ -0,0 +1,25 @@
+{
+ "name": "webviewer-ask-ai",
+ "version": "1.0.0",
+ "description": "",
+ "type": "module",
+ "main": "index.js",
+ "scripts": {
+ "postinstall": "node tools/copy-webviewer-files.js",
+ "start": "node server/serve.js"
+ },
+ "author": "Apryse Systems Inc.",
+ "devDependencies": {
+ "body-parser": "^2.2.0",
+ "express": "^5.1.0",
+ "fs-extra": "^11.3.2",
+ "open": "^10.2.0"
+ },
+ "dependencies": {
+ "@langchain/core": "^1.0.6",
+ "@langchain/openai": "^1.1.2",
+ "@pdftron/webviewer": "^11.8.0",
+ "dotenv": "^17.2.3",
+ "spin.js": "^4.1.2"
+ }
+}
diff --git a/webviewer-ask-ai/server/handler.js b/webviewer-ask-ai/server/handler.js
new file mode 100644
index 0000000..e9bd9f0
--- /dev/null
+++ b/webviewer-ask-ai/server/handler.js
@@ -0,0 +1,214 @@
+import { ChatOpenAI } from '@langchain/openai';
+import { HumanMessage, SystemMessage as AssistantMessage } from '@langchain/core/messages';
+import { StringOutputParser } from '@langchain/core/output_parsers';
+import dotenv from 'dotenv';
+
+dotenv.config();
+
+// Guard rails configuration for different prompt types
+const PROMPT_GUARD_RAILS = {
+ 'DOCUMENT_SUMMARY': {
+ assistantPrompt: 'You are a document summarizer specializing in PDF documents. Summarize the provided text concisely under 300 words. CRITICALLY IMPORTANT: For each sentence in your summary, you MUST add square brackets with the page number where that information came from (e.g., [1] for page 1, [2] for page 2, etc.). The document text is divided by page break markers in the format "<> Page N" where N is the page number. When you see "<> Page 3", all content following that marker until the next page break is from page 3. Always cite the correct page number for each fact or statement. Example: "The company reported strong earnings [1]. The new policy takes effect in January [2]."',
+ maxTokens: 500,
+ temperature: 0.3
+ },
+ 'DOCUMENT_KEYWORDS': {
+ assistantPrompt: 'You are a keyword extraction specialist. Create a bulleted list of the 10 most important keywords from the provided document text. CRITICALLY IMPORTANT: For each keyword, you MUST include the page number where it appears in square brackets. The document text is divided by page break markers in the format "<> Page N" where N is the page number. When you see "<> Page 3", all content following that marker until the next page break is from page 3. Format each keyword as: "• Keyword [page#]" Example: "• Federal Acquisition Regulation [1]" or "• Section 508 compliance [2]". Always cite the correct page number for each keyword."',
+ maxTokens: 500,
+ temperature: 0.1
+ },
+ 'SELECTED_TEXT_SUMMARY': {
+ assistantPrompt: 'You are a text summarizer for selected content. Provide a concise summary of the selected text, highlighting the main points and key information. Be clear and focused.',
+ maxTokens: 500,
+ temperature: 0.3
+ },
+ 'DOCUMENT_QUESTION': {
+ assistantPrompt: 'You are a document Q&A assistant. Answer questions about the provided document content accurately and concisely. Use specific information from the document to support your answers. If you cannot find relevant information in the document, say so clearly. CRITICALLY IMPORTANT: For each statement or fact in your answer, you MUST add square brackets with the page number where that information came from (e.g., [1] for page 1, [2] for page 2, etc.). The document text is divided by page break markers in the format "<> Page N" where N is the page number. When you see "<> Page 3", all content following that marker until the next page break is from page 3. Always cite the correct page number for each fact or statement. Example: "The policy requires annual reviews [3]. Training must be completed within 30 days [5]."',
+ maxTokens: 500,
+ temperature: 0.4
+ },
+ 'default': {
+ assistantPrompt: 'You are a helpful assistant that helps users with PDF documents and general questions. Be concise and helpful.',
+ maxTokens: 1000,
+ temperature: 0.7
+ }
+};
+
+// Function to estimate tokens (rough approximation: 1 token ≈ 4 characters)
+function estimateTokens(text) {
+ return Math.ceil(text.length / 4);
+}
+
+// Function to chunk text to fit within token limits
+function chunkText(text, maxTokens = 12000) { // Leave room for assistant prompt and response
+ const words = text.split(' ');
+ const chunks = [];
+ let currentChunk = '';
+
+ for (const word of words) {
+ const testChunk = currentChunk + (currentChunk ? ' ' : '') + word;
+ if (estimateTokens(testChunk) > maxTokens && currentChunk) {
+ chunks.push(currentChunk);
+ currentChunk = word;
+ } else {
+ currentChunk = testChunk;
+ }
+ }
+
+ if (currentChunk) {
+ chunks.push(currentChunk);
+ }
+
+ return chunks;
+}
+
+// Function to get assistant prompt and settings based on prompt type
+function getPromptSettings(promptType) {
+ return PROMPT_GUARD_RAILS[promptType] || PROMPT_GUARD_RAILS['default'];
+}
+
+// Initialize the OpenAI chat llm and parser
+let llm = null;
+let parser = null;
+
+// Initialize LangChain components
+function initializeLangChain() {
+ if (!process.env.OPENAI_API_KEY) {
+ console.error('❌ Missing OPENAI_API_KEY in .env file');
+ return false;
+ }
+
+ try {
+ llm = new ChatOpenAI({
+ apiKey: process.env.OPENAI_API_KEY,
+ modelName: process.env.OPENAI_MODEL || 'gpt-3.5-turbo',
+ temperature: parseFloat(process.env.OPENAI_TEMPERATURE) || 0.7,
+ maxTokens: parseInt(process.env.OPENAI_MAX_TOKENS) || 1000,
+ });
+
+ parser = new StringOutputParser();
+ console.log('✅ LangChain initialized successfully');
+ return true;
+ } catch (error) {
+ console.error('❌ Error initializing LangChain:', error);
+ return false;
+ }
+}
+
+export default (app) => {
+
+ // Initialize LangChain on startup
+ const langChainReady = initializeLangChain();
+
+ // Chat API endpoint
+ app.post('/api/chat', async (request, response) => {
+ try {
+ if (!langChainReady || !llm) {
+ return response.status(500).json({
+ error: 'Chat service not available. Please check server configuration.'
+ });
+ }
+
+ const { message, promptType, history = [] } = request.body;
+
+ if (!message || typeof message !== 'string') {
+ return response.status(400).json({ error: 'Message is required' });
+ }
+
+ // Get appropriate prompt settings based on prompt type
+ const promptSettings = getPromptSettings(promptType);
+
+ // Check if message is too long and handle accordingly
+ const estimatedTokens = estimateTokens(message) + estimateTokens(promptSettings.assistantPrompt) + 500; // Buffer for history and response
+
+ let finalContent;
+
+ if (estimatedTokens > 16000) { // Leave buffer for llm limit
+
+ // For keyword extraction, we can chunk the document and extract keywords from each chunk
+ if (promptType.toLowerCase()?.includes('keywords')) {
+ const chunks = chunkText(message, 12000);
+
+ const allKeywords = [];
+
+ for (let i = 0; i < chunks.length; i++) {
+ const chunkMessages = [
+ new AssistantMessage(`${promptSettings.assistantPrompt} This is chunk ${i + 1} of ${chunks.length}. Extract keywords from this section only.`),
+ new HumanMessage(chunks[i])
+ ];
+
+ // update llm settings for this prompt
+ llm.temperature = promptSettings.temperature;
+ llm.maxTokens = 150;
+
+ const chunkResponse = await llm.invoke(chunkMessages);
+ const chunkContent = await parser.parse(chunkResponse);
+ allKeywords.push(chunkContent);
+ }
+
+ // Now consolidate all keywords into final list
+ const consolidationMessages = [
+ new AssistantMessage('You are a keyword consolidation specialist. From the following keyword lists extracted from different sections of a document, create a final bulleted list of the 10 most important and representative keywords. Remove duplicates and prioritize the most significant terms.'),
+ new HumanMessage(`Consolidate these keyword lists into the top 10 keywords:\n\n${allKeywords.join('\n\n---\n\n')}`)
+ ];
+
+ // update llm settings for consolidation
+ llm.temperature = 0.1;
+ llm.maxTokens = 200;
+
+ const finalResponse = await llm.invoke(consolidationMessages);
+ finalContent = await parser.parse(finalResponse);
+
+ } else {
+ // For other prompt types, use first chunk with warning
+ const chunks = chunkText(message, 12000);
+ const messages = [
+ new AssistantMessage(`${promptSettings.assistantPrompt}\n\nNOTE: This document was too long, so only the first section is being processed.`),
+ ...history.map(msg =>
+ msg.role === 'human'
+ ? new HumanMessage(msg.content)
+ : new AssistantMessage(`Previous assistant response: ${msg.content}`)
+ ),
+ new HumanMessage(chunks[0])
+ ];
+
+ // update llm settings for this prompt
+ llm.temperature = promptSettings.temperature;
+ llm.maxTokens = promptSettings.maxTokens;
+
+ const response_data = await llm.invoke(messages);
+ finalContent = await parser.parse(response_data);
+ }
+
+ } else {
+ // Normal processing for documents within token limits
+ const messages = [
+ new AssistantMessage(promptSettings.assistantPrompt),
+ ...history.map(msg =>
+ msg.role === 'human'
+ ? new HumanMessage(msg.content)
+ : new AssistantMessage(`Previous assistant response: ${msg.content}`)
+ ),
+ new HumanMessage(message)
+ ];
+
+ // update llm with prompt-specific settings
+ llm.temperature = promptSettings.temperature;
+ llm.maxTokens = promptSettings.maxTokens;
+
+ const response_data = await llm.invoke(messages);
+ finalContent = await parser.parse(response_data);
+ }
+
+ // Ensure sending a clean string response
+ const cleanResponse = typeof finalContent === 'string' ? finalContent :
+ (finalContent?.content || JSON.stringify(finalContent));
+
+ response.status(200).json({ response: cleanResponse });
+ } catch (error) {
+ response.status(500).json({
+ error: 'An error occurred while processing your request'
+ });
+ }
+ });
+}
diff --git a/webviewer-ask-ai/server/serve.js b/webviewer-ask-ai/server/serve.js
new file mode 100644
index 0000000..f10708a
--- /dev/null
+++ b/webviewer-ask-ai/server/serve.js
@@ -0,0 +1,28 @@
+// This file is to run a server in localhost:process.env.PORT
+
+import express from 'express';
+import bodyParser from 'body-parser';
+import open from 'open';
+import handler from './handler.js';
+import dotenv from 'dotenv';
+
+dotenv.config();
+
+const app = express();
+
+// Use JSON body parser for API endpoints
+app.use(bodyParser.json());
+app.use(bodyParser.text());
+app.use('/client', express.static('client')); // For statically serving 'client' folder at '/'
+
+handler(app);
+
+// Run server
+app.listen(process.env.PORT, 'localhost', (err) => {
+ if (err) {
+ console.error(err);
+ } else {
+ console.info(`Server is listening at http://localhost:${process.env.PORT}/client/index.html`);
+ open(`http://localhost:${process.env.PORT}/client/index.html`);
+ }
+});
\ No newline at end of file
diff --git a/webviewer-ask-ai/tools/copy-webviewer-files.js b/webviewer-ask-ai/tools/copy-webviewer-files.js
new file mode 100644
index 0000000..5296e1e
--- /dev/null
+++ b/webviewer-ask-ai/tools/copy-webviewer-files.js
@@ -0,0 +1,15 @@
+import fs from 'fs-extra';
+
+const copyFiles = async () => {
+ try {
+ await fs.copy('./node_modules/@pdftron/webviewer/public', './client/lib');
+ await fs.copy('./node_modules/@pdftron/webviewer/webviewer.min.js', './client/lib/webviewer.min.js');
+ await fs.copy('./node_modules/spin.js/spin.js', './client/config/ui/spinjs/spin.js');
+ await fs.copy('./node_modules/spin.js/spin.css', './client/config/ui/spinjs/spin.css');
+ console.log('WebViewer files copied over successfully');
+ } catch (err) {
+ console.error(err);
+ }
+};
+
+copyFiles();