aboutsummaryrefslogtreecommitdiff
path: root/src/client/views/nodes/chatbot/tools
diff options
context:
space:
mode:
Diffstat (limited to 'src/client/views/nodes/chatbot/tools')
-rw-r--r--src/client/views/nodes/chatbot/tools/CreateAnyDocTool.ts158
-rw-r--r--src/client/views/nodes/chatbot/tools/CreateDocumentTool.ts497
-rw-r--r--src/client/views/nodes/chatbot/tools/CreateLinksTool.ts68
-rw-r--r--src/client/views/nodes/chatbot/tools/CreateTextDocumentTool.ts57
-rw-r--r--src/client/views/nodes/chatbot/tools/DocumentMetadataTool.ts573
-rw-r--r--src/client/views/nodes/chatbot/tools/SearchTool.ts18
-rw-r--r--src/client/views/nodes/chatbot/tools/WebsiteInfoScraperTool.ts114
7 files changed, 751 insertions, 734 deletions
diff --git a/src/client/views/nodes/chatbot/tools/CreateAnyDocTool.ts b/src/client/views/nodes/chatbot/tools/CreateAnyDocTool.ts
deleted file mode 100644
index 754d230c8..000000000
--- a/src/client/views/nodes/chatbot/tools/CreateAnyDocTool.ts
+++ /dev/null
@@ -1,158 +0,0 @@
-import { toLower } from 'lodash';
-import { Doc } from '../../../../../fields/Doc';
-import { Id } from '../../../../../fields/FieldSymbols';
-import { DocumentOptions } from '../../../../documents/Documents';
-import { parsedDoc } from '../chatboxcomponents/ChatBox';
-import { ParametersType, ToolInfo } from '../types/tool_types';
-import { Observation } from '../types/types';
-import { BaseTool } from './BaseTool';
-import { supportedDocTypes } from './CreateDocumentTool';
-
-const standardOptions = ['title', 'backgroundColor'];
-/**
- * Description of document options and data field for each type.
- */
-const documentTypesInfo: { [key in supportedDocTypes]: { options: string[]; dataDescription: string } } = {
- [supportedDocTypes.flashcard]: {
- options: [...standardOptions, 'fontColor', 'text_align'],
- dataDescription: 'an array of two strings. the first string contains a question, and the second string contains an answer',
- },
- [supportedDocTypes.text]: {
- options: [...standardOptions, 'fontColor', 'text_align'],
- dataDescription: 'The text content of the document.',
- },
- [supportedDocTypes.html]: {
- options: [],
- dataDescription: 'The HTML-formatted text content of the document.',
- },
- [supportedDocTypes.equation]: {
- options: [...standardOptions, 'fontColor'],
- dataDescription: 'The equation content as a string.',
- },
- [supportedDocTypes.functionplot]: {
- options: [...standardOptions, 'function_definition'],
- dataDescription: 'The function definition(s) for plotting. Provide as a string or array of function definitions.',
- },
- [supportedDocTypes.dataviz]: {
- options: [...standardOptions, 'chartType'],
- dataDescription: 'A string of comma-separated values representing the CSV data.',
- },
- [supportedDocTypes.notetaking]: {
- options: standardOptions,
- dataDescription: 'The initial content or structure for note-taking.',
- },
- [supportedDocTypes.rtf]: {
- options: standardOptions,
- dataDescription: 'The rich text content in RTF format.',
- },
- [supportedDocTypes.image]: {
- options: standardOptions,
- dataDescription: 'The image content as an image file URL.',
- },
- [supportedDocTypes.pdf]: {
- options: standardOptions,
- dataDescription: 'the pdf content as a PDF file url.',
- },
- [supportedDocTypes.audio]: {
- options: standardOptions,
- dataDescription: 'The audio content as a file url.',
- },
- [supportedDocTypes.video]: {
- options: standardOptions,
- dataDescription: 'The video content as a file url.',
- },
- [supportedDocTypes.message]: {
- options: standardOptions,
- dataDescription: 'The message content of the document.',
- },
- [supportedDocTypes.diagram]: {
- options: ['title', 'backgroundColor'],
- dataDescription: 'diagram content as a text string in Mermaid format.',
- },
- [supportedDocTypes.script]: {
- options: ['title', 'backgroundColor'],
- dataDescription: 'The compilable JavaScript code. Use this for creating scripts.',
- },
-};
-
-const createAnyDocumentToolParams = [
- {
- name: 'document_type',
- type: 'string',
- description: `The type of the document to create. Supported types are: ${Object.values(supportedDocTypes).join(', ')}`,
- required: true,
- },
- {
- name: 'data',
- type: 'string',
- description: 'The content or data of the document. The exact format depends on the document type.',
- required: true,
- },
- {
- name: 'options',
- type: 'string',
- required: false,
- description: `A JSON string representing the document options. Available options depend on the document type. For example:
- ${Object.entries(documentTypesInfo).map( ([doc_type, info]) => `
-- For '${doc_type}' documents, options include: ${info.options.join(', ')}`)
- .join('\n')}`, // prettier-ignore
- },
-] as const;
-
-type CreateAnyDocumentToolParamsType = typeof createAnyDocumentToolParams;
-
-const createAnyDocToolInfo: ToolInfo<CreateAnyDocumentToolParamsType> = {
- name: 'createAnyDocument',
- description:
- `Creates any type of document with the provided options and data.
- Supported document types are: ${Object.values(supportedDocTypes).join(', ')}.
- dataviz is a csv table tool, so for CSVs, use dataviz. Here are the options for each type:
- <supported_document_types>` +
- Object.entries(documentTypesInfo)
- .map(
- ([doc_type, info]) =>
- `<document_type name="${doc_type}">
- <data_description>${info.dataDescription}</data_description>
- <options>` +
- info.options.map(option => `<option>${option}</option>`).join('\n') +
- `</options>
- </document_type>`
- )
- .join('\n') +
- `</supported_document_types>`,
- parameterRules: createAnyDocumentToolParams,
- citationRules: 'No citation needed.',
-};
-
-export class CreateAnyDocumentTool extends BaseTool<CreateAnyDocumentToolParamsType> {
- private _addLinkedDoc: (doc: parsedDoc) => Doc | undefined;
-
- constructor(addLinkedDoc: (doc: parsedDoc) => Doc | undefined) {
- super(createAnyDocToolInfo);
- this._addLinkedDoc = addLinkedDoc;
- }
-
- async execute(args: ParametersType<CreateAnyDocumentToolParamsType>): Promise<Observation[]> {
- try {
- const documentType = toLower(args.document_type) as unknown as supportedDocTypes;
- const info = documentTypesInfo[documentType];
-
- if (info === undefined) {
- throw new Error(`Unsupported document type: ${documentType}. Supported types are: ${Object.values(supportedDocTypes).join(', ')}.`);
- }
-
- if (!args.data) {
- throw new Error(`Data is required for ${documentType} documents. ${info.dataDescription}`);
- }
-
- const options: DocumentOptions = !args.options ? {} : JSON.parse(args.options);
-
- // Call the function to add the linked document (add default title that can be overriden if set in options)
- const doc = this._addLinkedDoc({ doc_type: documentType, data: args.data, title: `New ${documentType.charAt(0).toUpperCase() + documentType.slice(1)} Document`, ...options });
-
- return [{ type: 'text', text: `Created ${documentType} document with ID ${doc?.[Id]}.` }];
- } catch (error) {
- return [{ type: 'text', text: 'Error creating document: ' + (error as Error).message }];
- }
- }
-}
diff --git a/src/client/views/nodes/chatbot/tools/CreateDocumentTool.ts b/src/client/views/nodes/chatbot/tools/CreateDocumentTool.ts
deleted file mode 100644
index 284879a4a..000000000
--- a/src/client/views/nodes/chatbot/tools/CreateDocumentTool.ts
+++ /dev/null
@@ -1,497 +0,0 @@
-import { BaseTool } from './BaseTool';
-import { Observation } from '../types/types';
-import { Parameter, ParametersType, ToolInfo } from '../types/tool_types';
-import { parsedDoc } from '../chatboxcomponents/ChatBox';
-import { CollectionViewType } from '../../../../documents/DocumentTypes';
-
-/**
- * List of supported document types that can be created via text LLM.
- */
-export enum supportedDocTypes {
- flashcard = 'flashcard',
- text = 'text',
- html = 'html',
- equation = 'equation',
- functionplot = 'functionplot',
- dataviz = 'dataviz',
- notetaking = 'notetaking',
- audio = 'audio',
- video = 'video',
- pdf = 'pdf',
- rtf = 'rtf',
- message = 'message',
- collection = 'collection',
- image = 'image',
- deck = 'deck',
- web = 'web',
- comparison = 'comparison',
- diagram = 'diagram',
- script = 'script',
-}
-/**
- * Tthe CreateDocTool class is responsible for creating
- * documents of various types (e.g., text, flashcards, collections) and organizing them in a
- * structured manner. The tool supports creating dashboards with diverse document types and
- * ensures proper placement of documents without overlap.
- */
-
-// Example document structure for various document types
-const example = [
- {
- doc_type: supportedDocTypes.equation,
- title: 'quadratic',
- data: 'x^2 + y^2 = 3',
- _width: 300,
- _height: 300,
- x: 0,
- y: 0,
- },
- {
- doc_type: supportedDocTypes.collection,
- title: 'Advanced Biology',
- data: [
- {
- doc_type: supportedDocTypes.text,
- title: 'Cell Structure',
- data: 'Cells are the basic building blocks of all living organisms.',
- _width: 300,
- _height: 300,
- x: 500,
- y: 0,
- },
- ],
- backgroundColor: '#00ff00',
- _width: 600,
- _height: 600,
- x: 600,
- y: 0,
- type_collection: 'tree',
- },
- {
- doc_type: supportedDocTypes.image,
- title: 'experiment',
- data: 'https://plus.unsplash.com/premium_photo-1694819488591-a43907d1c5cc?q=80&w=2628&auto=format&fit=crop&ixlib=rb-4.0.3&ixid=M3wxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8fA%3D%3D',
- _width: 300,
- _height: 300,
- x: 600,
- y: 300,
- },
- {
- doc_type: supportedDocTypes.deck,
- title: 'Chemistry',
- data: [
- {
- doc_type: supportedDocTypes.flashcard,
- title: 'Photosynthesis',
- data: [
- {
- doc_type: supportedDocTypes.text,
- title: 'front_Photosynthesis',
- data: 'What is photosynthesis?',
- _width: 300,
- _height: 300,
- x: 100,
- y: 600,
- },
- {
- doc_type: supportedDocTypes.text,
- title: 'back_photosynthesis',
- data: 'The process by which plants make food.',
- _width: 300,
- _height: 300,
- x: 100,
- y: 700,
- },
- ],
- backgroundColor: '#00ff00',
- _width: 300,
- _height: 300,
- x: 300,
- y: 1000,
- },
- {
- doc_type: supportedDocTypes.flashcard,
- title: 'Photosynthesis',
- data: [
- {
- doc_type: supportedDocTypes.text,
- title: 'front_Photosynthesis',
- data: 'What is photosynthesis?',
- _width: 300,
- _height: 300,
- x: 200,
- y: 800,
- },
- {
- doc_type: supportedDocTypes.text,
- title: 'back_photosynthesis',
- data: 'The process by which plants make food.',
- _width: 300,
- _height: 300,
- x: 100,
- y: -100,
- },
- ],
- backgroundColor: '#00ff00',
- _width: 300,
- _height: 300,
- x: 10,
- y: 70,
- },
- ],
- backgroundColor: '#00ff00',
- _width: 600,
- _height: 600,
- x: 200,
- y: 800,
- },
- {
- doc_type: supportedDocTypes.web,
- title: 'Brown University Wikipedia',
- data: 'https://en.wikipedia.org/wiki/Brown_University',
- _width: 300,
- _height: 300,
- x: 1000,
- y: 2000,
- },
- {
- doc_type: supportedDocTypes.comparison,
- title: 'WWI vs. WWII',
- data: [
- {
- doc_type: supportedDocTypes.text,
- title: 'WWI',
- data: 'From 1914 to 1918, fighting took place across several continents, at sea and, for the first time, in the air.',
- _width: 300,
- _height: 300,
- x: 100,
- y: 100,
- },
- {
- doc_type: supportedDocTypes.text,
- title: 'WWII',
- data: 'A devastating global conflict spanning from 1939 to 1945, saw the Allied powers fight against the Axis powers.',
- _width: 300,
- _height: 300,
- x: 100,
- y: 100,
- },
- ],
- _width: 300,
- _height: 300,
- x: 100,
- y: 100,
- },
- {
- doc_type: supportedDocTypes.collection,
- title: 'Science Collection',
- data: [
- {
- doc_type: supportedDocTypes.flashcard,
- title: 'Photosynthesis',
- data: [
- {
- doc_type: supportedDocTypes.text,
- title: 'front_Photosynthesis',
- data: 'What is photosynthesis?',
- _width: 300,
- _height: 300,
- },
- {
- doc_type: supportedDocTypes.text,
- title: 'back_photosynthesis',
- data: 'The process by which plants make food.',
- _width: 300,
- _height: 300,
- },
- ],
- backgroundColor: '#00ff00',
- _width: 300,
- _height: 300,
- },
- {
- doc_type: supportedDocTypes.web,
- title: 'Brown University Wikipedia',
- data: 'https://en.wikipedia.org/wiki/Brown_University',
- _width: 300,
- _height: 300,
- x: 1100,
- y: 1100,
- },
- {
- doc_type: supportedDocTypes.text,
- title: 'Water Cycle',
- data: 'The continuous movement of water on, above, and below the Earth’s surface.',
- _width: 300,
- _height: 300,
- x: 1500,
- y: 500,
- },
- {
- doc_type: supportedDocTypes.collection,
- title: 'Advanced Biology',
- data: [
- {
- doc_type: 'text',
- title: 'Cell Structure',
- data: 'Cells are the basic building blocks of all living organisms.',
- _width: 300,
- _height: 300,
- },
- ],
- backgroundColor: '#00ff00',
- _width: 600,
- _height: 600,
- x: 1100,
- y: 500,
- type_collection: 'stacking',
- },
- ],
- _width: 600,
- _height: 600,
- x: 500,
- y: 500,
- type_collection: 'carousel',
- },
-];
-
-// Stringify the entire structure for transmission if needed
-const finalJsonString = JSON.stringify(example);
-
-const standardOptions = ['title', 'backgroundColor'];
-/**
- * Description of document options and data field for each type.
- */
-const documentTypesInfo: { [key in supportedDocTypes]: { options: string[]; dataDescription: string } } = {
- comparison: {
- options: [...standardOptions, 'fontColor', 'text_align'],
- dataDescription: 'an array of two documents of any kind that can be compared.',
- },
- deck: {
- options: [...standardOptions, 'fontColor', 'text_align'],
- dataDescription: 'an array of flashcard docs',
- },
- flashcard: {
- options: [...standardOptions, 'fontColor', 'text_align'],
- dataDescription: 'an array of two strings. the first string contains a question, and the second string contains an answer',
- },
- text: {
- options: [...standardOptions, 'fontColor', 'text_align'],
- dataDescription: 'The text content of the document.',
- },
- web: {
- options: [],
- dataDescription: 'A URL to a webpage. Example: https://en.wikipedia.org/wiki/Brown_University',
- },
- html: {
- options: [],
- dataDescription: 'The HTML-formatted text content of the document.',
- },
- equation: {
- options: [...standardOptions, 'fontColor'],
- dataDescription: 'The equation content represented as a MathML string.',
- },
- functionplot: {
- options: [...standardOptions, 'function_definition'],
- dataDescription: 'The function definition(s) for plotting. Provide as a string or array of function definitions.',
- },
- dataviz: {
- options: [...standardOptions, 'chartType'],
- dataDescription: 'A string of comma-separated values representing the CSV data.',
- },
- notetaking: {
- options: standardOptions,
- dataDescription: 'An array of related text documents with small amounts of text.',
- },
- rtf: {
- options: standardOptions,
- dataDescription: 'The rich text content in RTF format.',
- },
- image: {
- options: standardOptions,
- dataDescription: `A url string that must end with '.png', '.jpeg', '.gif', or '.jpg'`,
- },
- pdf: {
- options: standardOptions,
- dataDescription: 'the pdf content as a PDF file url.',
- },
- audio: {
- options: standardOptions,
- dataDescription: 'The audio content as a file url.',
- },
- video: {
- options: standardOptions,
- dataDescription: 'The video content as a file url.',
- },
- message: {
- options: standardOptions,
- dataDescription: 'The message content of the document.',
- },
- diagram: {
- options: standardOptions,
- dataDescription: 'diagram content as a text string in Mermaid format.',
- },
- script: {
- options: standardOptions,
- dataDescription: 'The compilable JavaScript code. Use this for creating scripts.',
- },
- collection: {
- options: [...standardOptions, 'type_collection'],
- dataDescription: 'A collection of Docs represented as an array.',
- },
-};
-
-// Parameters for creating individual documents
-const createDocToolParams: { name: string; type: 'string' | 'number' | 'boolean' | 'string[]' | 'number[]'; description: string; required: boolean }[] = [
- {
- name: 'data',
- type: 'string', // Accepts either string or array, supporting individual and nested data
- description:
- 'the data that describes the Document contents. For collections this is an' +
- `Array of documents in stringified JSON format. Each item in the array should be an individual stringified JSON object. ` +
- `Creates any type of document with the provided options and data. Supported document types are: ${Object.keys(documentTypesInfo).join(', ')}.
- dataviz is a csv table tool, so for CSVs, use dataviz. Here are the options for each type:
- <supported_document_types>` +
- Object.entries(documentTypesInfo)
- .map(
- ([doc_type, info]) =>
- `<document_type name="${doc_type}">
- <data_description>${info.dataDescription}</data_description>
- <options>` +
- info.options.map(option => `<option>${option}</option>`).join('\n') +
- `
- </options>
- </document_type>`
- )
- .join('\n') +
- `</supported_document_types> An example of the structure of a collection is:` +
- finalJsonString, // prettier-ignore,
- required: true,
- },
- {
- name: 'doc_type',
- type: 'string',
- description: `The type of the document. Options: ${Object.keys(documentTypesInfo).join(',')}.`,
- required: true,
- },
- {
- name: 'title',
- type: 'string',
- description: 'The title of the document.',
- required: true,
- },
- {
- name: 'x',
- type: 'number',
- description: 'The x location of the document; 0 <= x.',
- required: true,
- },
- {
- name: 'y',
- type: 'number',
- description: 'The y location of the document; 0 <= y.',
- required: true,
- },
- {
- name: 'backgroundColor',
- type: 'string',
- description: 'The background color of the document as a hex string.',
- required: false,
- },
- {
- name: 'fontColor',
- type: 'string',
- description: 'The font color of the document as a hex string.',
- required: false,
- },
- {
- name: '_width',
- type: 'number',
- description: 'The width of the document in pixels.',
- required: true,
- },
- {
- name: '_height',
- type: 'number',
- description: 'The height of the document in pixels.',
- required: true,
- },
- {
- name: 'type_collection',
- type: 'string',
- description: `the visual style for a collection doc. Options include: ${Object.values(CollectionViewType).join(',')}.`,
- required: false,
- },
-] as const;
-
-type CreateDocToolParamsType = typeof createDocToolParams;
-
-const createDocToolInfo: ToolInfo<CreateDocToolParamsType> = {
- name: 'createDoc',
- description: `Creates one or more documents that best fit the user’s request.
- If the user requests a "dashboard," first call the search tool and then generate a variety of document types individually, with absolutely a minimum of 20 documents
- with two stacks of flashcards that are small and it should have a couple nested freeform collections of things, each with different content and color schemes.
- For example, create multiple individual documents, including ${Object.keys(documentTypesInfo)
- .map(t => '"' + t + '"')
- .join(',')}
- If the "doc_type" parameter is missing, set it to an empty string ("").
- Use Decks instead of Flashcards for dashboards. Decks should have at least three flashcards.
- Really think about what documents are useful to the user. If they ask for a dashboard about the skeletal system, include flashcards, as they would be helpful.
- Arrange the documents in a grid layout, ensuring that the x and y coordinates are calculated so no documents overlap but they should be directly next to each other with 20 padding in between.
- Take into account the width and height of each document, spacing them appropriately to prevent collisions.
- Use a systematic approach, such as placing each document in a grid cell based on its order, where cell dimensions match the document dimensions plus a fixed margin for spacing.
- Do not nest all documents within a single collection unless explicitly requested by the user.
- Instead, create a set of independent documents with diverse document types. Each type should appear separately unless specified otherwise.
- Use the "data" parameter for document content and include title, color, and document dimensions.
- Ensure web documents use URLs from the search tool if relevant. Each document in a dashboard should be unique and well-differentiated in type and content,
- without repetition of similar types in any single collection.
- When creating a dashboard, ensure that it consists of a broad range of document types.
- Include a variety of documents, such as text, web, deck, comparison, image, and equation documents,
- each with distinct titles and colors, following the user’s preferences.
- Do not overuse collections or nest all document types within a single collection; instead, represent document types individually. Use this example for reference:
- ${finalJsonString} .
- Which documents are created should be random with different numbers of each document type and different for each dashboard.
- Must use search tool before creating a dashboard.`,
- parameterRules: createDocToolParams,
- citationRules: 'No citation needed.',
-};
-
-// Tool class for creating documents
-export class CreateDocTool extends BaseTool<
- {
- name: string;
- type: 'string' | 'number' | 'boolean' | 'string[]' | 'number[]';
- description: string;
- required: boolean;
- }[]
-> {
- private _addLinkedDoc: (doc: parsedDoc) => void;
-
- constructor(addLinkedDoc: (doc: parsedDoc) => void) {
- super(createDocToolInfo);
- this._addLinkedDoc = addLinkedDoc;
- }
-
- override inputValidator(inputParam: ParametersType<readonly Parameter[]>) {
- return !!inputParam.data;
- }
- // Executes the tool logic for creating documents
- async execute(
- args: ParametersType<
- {
- name: 'string';
- type: 'string' | 'number' | 'boolean' | 'string[]' | 'number[]';
- description: 'string';
- required: boolean;
- }[]
- >
- ): Promise<Observation[]> {
- try {
- const parsedDocs = args instanceof Array ? args : Object.keys(args).length === 1 && 'data' in args ? JSON.parse(args.data as string) : [args];
- parsedDocs.forEach((pdoc: parsedDoc) => this._addLinkedDoc({ ...pdoc, _layout_fitWidth: false, _layout_autoHeight: true }));
- return [{ type: 'text', text: 'Created document.' }];
- } catch (error) {
- return [{ type: 'text', text: 'Error creating text document, ' + error }];
- }
- }
-}
diff --git a/src/client/views/nodes/chatbot/tools/CreateLinksTool.ts b/src/client/views/nodes/chatbot/tools/CreateLinksTool.ts
new file mode 100644
index 000000000..c2850a8ce
--- /dev/null
+++ b/src/client/views/nodes/chatbot/tools/CreateLinksTool.ts
@@ -0,0 +1,68 @@
+import { Observation } from '../types/types';
+import { ParametersType, ToolInfo } from '../types/tool_types';
+import { BaseTool } from './BaseTool';
+import { AgentDocumentManager } from '../utils/AgentDocumentManager';
+
+const createLinksToolParams = [
+ {
+ name: 'document_ids',
+ type: 'string[]',
+ description: 'List of document IDs to create links between. All documents will be linked to each other.',
+ required: true,
+ },
+] as const;
+
+type CreateLinksToolParamsType = typeof createLinksToolParams;
+
+const createLinksToolInfo: ToolInfo<CreateLinksToolParamsType> = {
+ name: 'createLinks',
+ description: 'Creates visual links between multiple documents in the dashboard. This allows related documents to be connected visually with lines that users can see.',
+ citationRules: 'No citation needed.',
+ parameterRules: createLinksToolParams,
+};
+
+export class CreateLinksTool extends BaseTool<CreateLinksToolParamsType> {
+ private _documentManager: AgentDocumentManager;
+
+ constructor(documentManager: AgentDocumentManager) {
+ super(createLinksToolInfo);
+ this._documentManager = documentManager;
+ }
+
+ async execute(args: ParametersType<CreateLinksToolParamsType>): Promise<Observation[]> {
+ try {
+ // Validate that we have at least 2 documents to link
+ if (args.document_ids.length < 2) {
+ return [{ type: 'text', text: 'Error: At least 2 document IDs are required to create links.' }];
+ }
+
+ // Validate that all documents exist
+ const missingDocIds = args.document_ids.filter(id => !this._documentManager.has(id));
+ if (missingDocIds.length > 0) {
+ return [
+ {
+ type: 'text',
+ text: `Error: The following document IDs were not found: ${missingDocIds.join(', ')}`,
+ },
+ ];
+ }
+
+ // Create links between all documents with the specified relationship
+ const createdLinks = this._documentManager.addLinks(args.document_ids);
+
+ return [
+ {
+ type: 'text',
+ text: `Successfully created ${createdLinks.length} visual links between ${args.document_ids.length}.`,
+ },
+ ];
+ } catch (error) {
+ return [
+ {
+ type: 'text',
+ text: `Error creating links: ${error instanceof Error ? error.message : String(error)}`,
+ },
+ ];
+ }
+ }
+}
diff --git a/src/client/views/nodes/chatbot/tools/CreateTextDocumentTool.ts b/src/client/views/nodes/chatbot/tools/CreateTextDocumentTool.ts
deleted file mode 100644
index 16dc938bb..000000000
--- a/src/client/views/nodes/chatbot/tools/CreateTextDocumentTool.ts
+++ /dev/null
@@ -1,57 +0,0 @@
-import { parsedDoc } from '../chatboxcomponents/ChatBox';
-import { ParametersType, ToolInfo } from '../types/tool_types';
-import { Observation } from '../types/types';
-import { BaseTool } from './BaseTool';
-const createTextDocToolParams = [
- {
- name: 'text_content',
- type: 'string',
- description: 'The text content that the document will display',
- required: true,
- },
- {
- name: 'title',
- type: 'string',
- description: 'The title of the document',
- required: true,
- },
- // {
- // name: 'background_color',
- // type: 'string',
- // description: 'The background color of the document as a hex string',
- // required: false,
- // },
- // {
- // name: 'font_color',
- // type: 'string',
- // description: 'The font color of the document as a hex string',
- // required: false,
- // },
-] as const;
-
-type CreateTextDocToolParamsType = typeof createTextDocToolParams;
-
-const createTextDocToolInfo: ToolInfo<CreateTextDocToolParamsType> = {
- name: 'createTextDoc',
- description: 'Creates a text document with the provided content and title. Use if the user wants to create a textbox or text document of some sort. Can use after a search or other tool to save information.',
- citationRules: 'No citation needed.',
- parameterRules: createTextDocToolParams,
-};
-
-export class CreateTextDocTool extends BaseTool<CreateTextDocToolParamsType> {
- private _addLinkedDoc: (doc: parsedDoc) => void;
-
- constructor(addLinkedDoc: (doc: parsedDoc) => void) {
- super(createTextDocToolInfo);
- this._addLinkedDoc = addLinkedDoc;
- }
-
- async execute(args: ParametersType<CreateTextDocToolParamsType>): Promise<Observation[]> {
- try {
- this._addLinkedDoc({ doc_type: 'text', data: args.text_content, title: args.title });
- return [{ type: 'text', text: 'Created text document.' }];
- } catch (error) {
- return [{ type: 'text', text: 'Error creating text document, ' + error }];
- }
- }
-}
diff --git a/src/client/views/nodes/chatbot/tools/DocumentMetadataTool.ts b/src/client/views/nodes/chatbot/tools/DocumentMetadataTool.ts
new file mode 100644
index 000000000..e6c2421e5
--- /dev/null
+++ b/src/client/views/nodes/chatbot/tools/DocumentMetadataTool.ts
@@ -0,0 +1,573 @@
+import { Doc, FieldType } from '../../../../../fields/Doc';
+import { DocData } from '../../../../../fields/DocSymbols';
+import { Observation } from '../types/types';
+import { ParametersType, ToolInfo, Parameter } from '../types/tool_types';
+import { BaseTool } from './BaseTool';
+import { DocumentOptions } from '../../../../documents/Documents';
+import { CollectionFreeFormDocumentView } from '../../../nodes/CollectionFreeFormDocumentView';
+import { v4 as uuidv4 } from 'uuid';
+import { LinkManager } from '../../../../util/LinkManager';
+import { DocCast, StrCast } from '../../../../../fields/Types';
+import { supportedDocTypes } from '../types/tool_types';
+import { parsedDoc } from '../chatboxcomponents/ChatBox';
+import { AgentDocumentManager } from '../utils/AgentDocumentManager';
+
+// Define the parameters for the DocumentMetadataTool
+const parameterDefinitions: ReadonlyArray<Parameter> = [
+ {
+ name: 'action',
+ type: 'string',
+ required: true,
+ description: 'The action to perform: "get" to retrieve metadata, "edit" to modify metadata, "list" to enumerate documents, "getFieldOptions" to retrieve all available field options, or "create" to create a new document',
+ },
+ {
+ name: 'documentId',
+ type: 'string',
+ required: false,
+ description: 'The ID of the document to get or edit metadata for. Required for "edit", optional for "get", ignored for "list", "getFieldOptions", and "create"',
+ },
+ {
+ name: 'fieldEdits',
+ type: 'string',
+ required: false,
+ description:
+ 'JSON array of field edits for editing fields. Each item should have fieldName and fieldValue. For single field edits, use an array with one item. Example: [{"fieldName":"layout_autoHeight","fieldValue":false},{"fieldName":"height","fieldValue":300}]',
+ },
+ {
+ name: 'title',
+ type: 'string',
+ required: false,
+ description: 'The title of the document to create. Required for "create" action',
+ },
+ {
+ name: 'data',
+ type: 'string',
+ required: false,
+ description: 'The data content for the document to create. Required for "create" action',
+ },
+ {
+ name: 'doc_type',
+ type: 'string',
+ required: false,
+ description: `The type of document to create. Required for "create" action. Options: ${Object.keys(supportedDocTypes).join(',')}`,
+ },
+] as const;
+
+type DocumentMetadataToolParamsType = typeof parameterDefinitions;
+
+// Detailed description with usage guidelines for the DocumentMetadataTool
+const toolDescription = `Extracts and modifies metadata from documents in the same Freeform view as the ChatBox, and can create new documents.
+This tool helps you work with document properties, understand available fields, edit document metadata, and create new documents.
+
+The Dash document system organizes fields in two locations:
+1. Layout documents: contain visual properties like position, dimensions, and appearance
+2. Data documents: contain the actual content and document-specific data
+
+This tool provides the following capabilities:
+- Get metadata from all documents in the current Freeform view
+- Get metadata from a specific document
+- Edit metadata fields on documents (in either layout or data documents)
+- Edit multiple fields at once (useful for updating dependent fields together)
+- List all available documents in the current view
+- Retrieve all available field options with metadata (IMPORTANT: always call this before editing)
+- Understand which fields are stored where (layout vs data document)
+- Get detailed information about all available document fields
+- Support for all value types: strings, numbers, and booleans
+- Create new documents with basic properties
+
+DOCUMENT CREATION:
+- Use action="create" to create new documents with a simplified approach
+- Required parameters: title, data, and doc_type
+- The tool will create the document with sensible defaults and link it to the current view
+- After creation, you can use the edit action to update its properties
+
+IMPORTANT: Before editing any document metadata, first call 'getFieldOptions' to understand:
+- Which fields are available
+- The data type of each field
+- Special dependencies between fields (like layout_autoHeight and height)
+- Proper naming conventions (with or without underscores)
+
+IMPORTANT: Some fields have dependencies that must be handled for edits to work correctly:
+- When editing "height", first set "layout_autoHeight" to false (as a boolean value, not a string)
+- When editing "width", first set "layout_autoWidth" to false (as a boolean value, not a string)
+- Check document metadata to identify other similar dependencies
+- All edits are done using the fieldEdits parameter which accepts an array of fields to modify
+
+Example: To change document height, disable auto-height and set height in a single operation:
+{... inputs: { action: "edit", documentId: "doc123", fieldEdits: [
+ { fieldName: "layout_autoHeight", fieldValue: false },
+ { fieldName: "height", fieldValue: 300 }
+]}}`;
+
+// Extensive usage guidelines for the tool
+const citationRules = `USAGE GUIDELINES:
+To GET document metadata:
+- Use action="get" with optional documentId to return metadata for one or all documents
+- Returns field values, field definitions, and location information (layout vs data document)
+
+To GET ALL FIELD OPTIONS (call this first):
+- Use action="getFieldOptions" to retrieve metadata about all available document fields
+- No additional parameters are required
+- Returns structured metadata with field names, types, descriptions, and dependencies
+- ALWAYS call this before attempting to edit document metadata
+- Use this information to understand which fields need special handling
+
+To CREATE a new document:
+- Use action="create" with the following required parameters:
+ - title: The title of the document to create
+ - data: The content data for the document (text content, URL, etc.)
+ - doc_type: The type of document to create (text, web, image, etc.)
+- Example: {...inputs: { action: "create", title: "My Notes", data: "This is the content", doc_type: "text" }}
+- After creation, you can edit the document with more specific properties
+
+To EDIT document metadata:
+- Use action="edit" with required parameters:
+ - documentId: The ID of the document to edit
+ - fieldEdits: JSON array of fields to edit, each with fieldName and fieldValue
+- The tool will determine the correct document location automatically
+- Field names can be provided with or without leading underscores (e.g., both "width" and "_width" work)
+- Common fields like "width" and "height" are automatically mapped to "_width" and "_height"
+- All value types are supported: strings, numbers, and booleans
+- The tool will apply the edit to the correct document (layout or data) based on existing fields
+
+SPECIAL FIELD HANDLING:
+- Text fields: When editing the 'text' field, provide simple plain text
+ Example: {...inputs: { action: "edit", documentId: "doc123", fieldEdits: [{ fieldName: "text", fieldValue: "Hello world" }] }}
+ The tool will automatically convert your text to the proper RichTextField format
+- Width/Height: Set layout_autoHeight/layout_autoWidth to false before editing
+
+RECOMMENDED WORKFLOW:
+1. First call action="list" to identify available documents
+2. Then call action="getFieldOptions" to understand available fields
+3. Get document metadata with action="get" to see current values
+4. Edit fields with action="edit" using proper dependencies
+OR
+1. Create a new document with action="create"
+2. Get its ID from the response
+3. Edit the document's properties with action="edit"
+
+HANDLING DEPENDENT FIELDS:
+- When editing some fields, you may need to update related dependent fields
+- For example, when changing "height", you should also set "layout_autoHeight" to false
+- Use the fieldEdits parameter to update dependent fields in a single operation:
+ {...inputs: { action: "edit", documentId: "doc123", fieldEdits: [
+ { fieldName: "layout_autoHeight", fieldValue: false },
+ { fieldName: "height", fieldValue: 300 }
+]}}
+- Always check for dependent fields that might affect your edits, such as:
+ - height → layout_autoHeight (set to false to allow manual height)
+ - width → layout_autoWidth (set to false to allow manual width)
+ - Other auto-sizing related properties
+
+To LIST available documents:
+- Use action="list" to get a simple list of all documents in the current view
+- This is useful when you need to identify documents before getting details or editing them
+
+Editing fields follows these rules:
+1. First checks if the field exists on the layout document using Doc.Get
+2. If it exists on the layout document, it's updated there
+3. If it has an underscore prefix (_), it's created/updated on the layout document
+4. Otherwise, the field is created/updated on the data document
+5. Fields with leading underscores are automatically handled correctly
+
+Examples:
+- To get field options: { action: "getFieldOptions" }
+- To list all documents: { action: "list" }
+- To get all document metadata: { action: "get" }
+- To get metadata for a specific document: { action: "get", documentId: "doc123" }
+- To edit a single field: { action: "edit", documentId: "doc123", fieldEdits: [{ fieldName: "backgroundColor", fieldValue: "#ff0000" }] }
+- To edit a width property: { action: "edit", documentId: "doc123", fieldEdits: [{ fieldName: "width", fieldValue: 300 }] }
+- To edit text content: { action: "edit", documentId: "doc123", fieldEdits: [{ fieldName: "text", fieldValue: "Simple plain text goes here" }] }
+- To disable auto-height: { action: "edit", documentId: "doc123", fieldEdits: [{ fieldName: "layout_autoHeight", fieldValue: false }] }
+- To create a text document: { action: "create", title: "My Notes", data: "This is my note content", doc_type: "text" }
+- To create a web document: { action: "create", title: "Google", data: "https://www.google.com", doc_type: "web" }
+- To edit height with its dependent field together:
+ { action: "edit", documentId: "doc123", fieldEdits: [
+ { fieldName: "layout_autoHeight", fieldValue: false },
+ { fieldName: "height", fieldValue: 200 }
+ ]}
+- IMPORTANT: MULTI STEP WORKFLOWS ARE NOT ONLY ALLOWED BUT ENCOURAGED. TAKE THINGS 1 STEP AT A TIME.`;
+const documentMetadataToolInfo: ToolInfo<DocumentMetadataToolParamsType> = {
+ name: 'documentMetadata',
+ description: toolDescription,
+ parameterRules: parameterDefinitions,
+ citationRules: citationRules,
+};
+
+/**
+ * A tool for extracting and modifying metadata from documents in a Freeform view.
+ * This tool collects metadata from both layout and data documents in a Freeform view
+ * and allows for editing document fields in the correct location.
+ */
+export class DocumentMetadataTool extends BaseTool<DocumentMetadataToolParamsType> {
+ private _docManager: AgentDocumentManager;
+
+ constructor(docManager: AgentDocumentManager) {
+ super(documentMetadataToolInfo);
+ this._docManager = docManager;
+ this._docManager.initializeFindDocsFreeform();
+ }
+
+ /**
+ * Executes the document metadata tool
+ * @param args The arguments for the tool
+ * @returns An observation with the results of the tool execution
+ */
+ async execute(args: ParametersType<DocumentMetadataToolParamsType>): Promise<Observation[]> {
+ console.log('DocumentMetadataTool: Executing with args:', args);
+
+ // Find all documents in the Freeform view
+ this._docManager.initializeFindDocsFreeform();
+
+ try {
+ // Validate required input parameters based on action
+ if (!this.inputValidator(args)) {
+ return [
+ {
+ type: 'text',
+ text: `Error: Invalid or missing parameters for action "${args.action}". ${this.getParameterRequirementsByAction(String(args.action))}`,
+ },
+ ];
+ }
+
+ // Ensure the action is valid and convert to string
+ const action = String(args.action);
+ if (!['get', 'edit', 'list', 'getFieldOptions', 'create'].includes(action)) {
+ return [
+ {
+ type: 'text',
+ text: 'Error: Invalid action. Valid actions are "get", "edit", "list", "getFieldOptions", or "create".',
+ },
+ ];
+ }
+
+ // Safely convert documentId to string or undefined
+ const documentId = args.documentId ? String(args.documentId) : undefined;
+
+ // Perform the specified action
+ switch (action) {
+ case 'get': {
+ // Get metadata for a specific document or all documents
+ const result = this._docManager.getDocumentMetadata(documentId);
+ console.log('DocumentMetadataTool: Get metadata result:', result);
+ return [
+ {
+ type: 'text',
+ text: `Document metadata ${documentId ? 'for document ' + documentId : ''} retrieved successfully:\n${JSON.stringify(result, null, 2)}`,
+ },
+ ];
+ }
+
+ case 'edit': {
+ // Edit a specific field on a document
+ if (!documentId) {
+ return [
+ {
+ type: 'text',
+ text: 'Error: Document ID is required for edit actions.',
+ },
+ ];
+ }
+
+ // Ensure document exists
+ if (!this._docManager.has(documentId)) {
+ return [
+ {
+ type: 'text',
+ text: `Error: Document with ID ${documentId} not found.`,
+ },
+ ];
+ }
+
+ // Check for fieldEdits parameter
+ if (!args.fieldEdits) {
+ return [
+ {
+ type: 'text',
+ text: 'Error: fieldEdits is required for edit actions. Please provide a JSON array of field edits.',
+ },
+ ];
+ }
+
+ try {
+ // Parse fieldEdits array
+ const edits = JSON.parse(String(args.fieldEdits));
+ if (!Array.isArray(edits) || edits.length === 0) {
+ return [
+ {
+ type: 'text',
+ text: 'Error: fieldEdits must be a non-empty array of field edits.',
+ },
+ ];
+ }
+
+ // Track results for all edits
+ const results: {
+ success: boolean;
+ message: string;
+ fieldName?: string;
+ originalFieldName?: string;
+ newValue?: any;
+ warning?: string;
+ }[] = [];
+
+ let allSuccessful = true;
+
+ // Process each edit
+ for (const edit of edits) {
+ // Get fieldValue in its original form
+ let fieldValue = edit.fieldValue;
+
+ // Only convert to string if it's neither boolean nor number
+ if (typeof fieldValue !== 'boolean' && typeof fieldValue !== 'number') {
+ fieldValue = String(fieldValue);
+ }
+
+ const fieldName = String(edit.fieldName);
+
+ // Edit the field
+ const result = this._docManager.editDocumentField(documentId, fieldName, fieldValue);
+
+ console.log(`DocumentMetadataTool: Edit field result for ${fieldName}:`, result);
+
+ // Add to results
+ results.push(result);
+
+ // Update success status
+ if (!result.success) {
+ allSuccessful = false;
+ }
+ }
+
+ // Format response based on results
+ let responseText = '';
+ if (allSuccessful) {
+ responseText = `Successfully edited ${results.length} fields on document ${documentId}:\n`;
+ results.forEach(result => {
+ responseText += `- Field '${result.originalFieldName}': updated to ${JSON.stringify(result.newValue)}\n`;
+
+ // Add any warnings
+ if (result.warning) {
+ responseText += ` Warning: ${result.warning}\n`;
+ }
+ });
+ } else {
+ responseText = `Errors occurred while editing fields on document ${documentId}:\n`;
+ results.forEach(result => {
+ if (result.success) {
+ responseText += `- Field '${result.originalFieldName}': updated to ${JSON.stringify(result.newValue)}\n`;
+
+ // Add any warnings
+ if (result.warning) {
+ responseText += ` Warning: ${result.warning}\n`;
+ }
+ } else {
+ responseText += `- Error editing '${result.originalFieldName}': ${result.message}\n`;
+ }
+ });
+ }
+
+ // Get the updated metadata to return
+ const updatedMetadata = this._docManager.getDocumentMetadata(documentId);
+
+ return [
+ {
+ type: 'text',
+ text: `${responseText}\nUpdated metadata:\n${JSON.stringify(updatedMetadata, null, 2)}`,
+ },
+ ];
+ } catch (error) {
+ return [
+ {
+ type: 'text',
+ text: `Error processing fieldEdits: ${error instanceof Error ? error.message : String(error)}`,
+ },
+ ];
+ }
+ }
+
+ case 'list': {
+ this._docManager.listDocs();
+ }
+
+ case 'getFieldOptions': {
+ // Get all available field options with metadata
+ const fieldOptions = this._docManager.getAllFieldMetadata();
+
+ return [
+ {
+ type: 'text',
+ text: `Document field options retrieved successfully.\nThis information should be consulted before editing document fields to understand available options and dependencies:\n${JSON.stringify(fieldOptions, null, 2)}`,
+ },
+ ];
+ }
+
+ case 'create': {
+ // Create a new document
+ if (!args.title || !args.data || !args.doc_type) {
+ return [
+ {
+ type: 'text',
+ text: 'Error: Title, data, and doc_type are required for create action.',
+ },
+ ];
+ }
+
+ const docType = String(args.doc_type);
+ const title = String(args.title);
+ const data = String(args.data);
+
+ const id = this._docManager.createDocInDash(docType, data, { title: title });
+
+ if (!id) {
+ return [
+ {
+ type: 'text',
+ text: 'Error: Failed to create document.',
+ },
+ ];
+ }
+ // Get the created document's metadata
+ const createdMetadata = this._docManager.extractDocumentMetadata(id);
+
+ return [
+ {
+ type: 'text',
+ text: `Document created successfully.
+Document ID: ${id}
+Type: ${docType}
+Title: "${title}"
+
+The document has been created with default dimensions and positioning.
+You can now use the "edit" action to modify additional properties of this document.
+
+Next steps:
+1. Use the "getFieldOptions" action to understand available editable/addable fields/properties and their dependencies.
+2. To modify this document, use: { action: "edit", documentId: "${id}", fieldEdits: [{"fieldName":"property","fieldValue":"value"}] }
+3. To add styling, consider setting backgroundColor, fontColor, or other properties
+4. For text documents, you can edit the content with: { action: "edit", documentId: "${id}", fieldEdits: [{"fieldName":"text","fieldValue":"New content"}] }
+
+Full metadata for the created document:
+${JSON.stringify(createdMetadata, null, 2)}`,
+ },
+ ];
+ }
+
+ default:
+ return [
+ {
+ type: 'text',
+ text: 'Error: Unknown action. Valid actions are "get", "edit", "list", "getFieldOptions", or "create".',
+ },
+ ];
+ }
+ } catch (error) {
+ console.error('DocumentMetadataTool execution error:', error);
+ return [
+ {
+ type: 'text',
+ text: `Error executing DocumentMetadataTool: ${error instanceof Error ? error.message : String(error)}`,
+ },
+ ];
+ }
+ }
+
+ /**
+ * Validates the input parameters for the DocumentMetadataTool
+ * This custom validator allows numbers and booleans to be passed for fieldValue
+ * while maintaining compatibility with the standard validation
+ *
+ * @param params The parameters to validate
+ * @returns True if the parameters are valid, false otherwise
+ */
+ inputValidator(params: ParametersType<DocumentMetadataToolParamsType>): boolean {
+ // Default validation for required fields
+ if (params.action === undefined) {
+ return false;
+ }
+
+ // For create action, validate required parameters
+ if (params.action === 'create') {
+ return !!(params.title && params.data && params.doc_type);
+ }
+
+ // For edit action, validate fieldEdits is provided
+ if (params.action === 'edit') {
+ if (!params.documentId || !params.fieldEdits) {
+ return false;
+ }
+
+ try {
+ // Parse fieldEdits and validate its structure
+ const edits = JSON.parse(String(params.fieldEdits));
+
+ // Ensure it's an array
+ if (!Array.isArray(edits)) {
+ console.log('fieldEdits is not an array');
+ return false;
+ }
+
+ // Ensure each item has fieldName and fieldValue
+ for (const edit of edits) {
+ if (!edit.fieldName) {
+ console.log('An edit is missing fieldName');
+ return false;
+ }
+ if (edit.fieldValue === undefined) {
+ console.log('An edit is missing fieldValue');
+ return false;
+ }
+ }
+
+ // Everything looks good with fieldEdits
+ return true;
+ } catch (error) {
+ console.log('Error parsing fieldEdits:', error);
+ return false;
+ }
+ }
+
+ // For get action with documentId, documentId is required
+ if (params.action === 'get' && params.documentId === '') {
+ return false;
+ }
+
+ // getFieldOptions action doesn't require any additional parameters
+ if (params.action === 'getFieldOptions') {
+ return true;
+ }
+
+ // list action doesn't require any additional parameters
+ if (params.action === 'list') {
+ return true;
+ }
+
+ return true;
+ }
+
+ /**
+ * Returns the parameter requirements for a specific action
+ * @param action The action to get requirements for
+ * @returns A string describing the required parameters
+ */
+ private getParameterRequirementsByAction(action?: string): string {
+ if (!action) {
+ return 'Please specify an action: "get", "edit", "list", "getFieldOptions", or "create".';
+ }
+
+ switch (action.toLowerCase()) {
+ case 'get':
+ return 'The "get" action accepts an optional documentId parameter.';
+ case 'edit':
+ return 'The "edit" action requires documentId and fieldEdits parameters. fieldEdits must be a JSON array of field edits.';
+ case 'list':
+ return 'The "list" action does not require any additional parameters.';
+ case 'getFieldOptions':
+ return 'The "getFieldOptions" action does not require any additional parameters. It returns metadata about all available document fields.';
+ case 'create':
+ return 'The "create" action requires title, data, and doc_type parameters.';
+ default:
+ return `Unknown action "${action}". Valid actions are "get", "edit", "list", "getFieldOptions", or "create".`;
+ }
+ }
+}
diff --git a/src/client/views/nodes/chatbot/tools/SearchTool.ts b/src/client/views/nodes/chatbot/tools/SearchTool.ts
index 6a11407a5..53f5fc109 100644
--- a/src/client/views/nodes/chatbot/tools/SearchTool.ts
+++ b/src/client/views/nodes/chatbot/tools/SearchTool.ts
@@ -3,6 +3,9 @@ import { Networking } from '../../../../Network';
import { BaseTool } from './BaseTool';
import { Observation } from '../types/types';
import { ParametersType, ToolInfo } from '../types/tool_types';
+import { Agent } from 'http';
+import { AgentDocumentManager } from '../utils/AgentDocumentManager';
+import { StrCast } from '../../../../../fields/Types';
const searchToolParams = [
{
@@ -25,12 +28,12 @@ const searchToolInfo: ToolInfo<SearchToolParamsType> = {
};
export class SearchTool extends BaseTool<SearchToolParamsType> {
- private _addLinkedUrlDoc: (url: string, id: string) => void;
+ private _docManager: AgentDocumentManager;
private _max_results: number;
- constructor(addLinkedUrlDoc: (url: string, id: string) => void, max_results: number = 4) {
+ constructor(docManager: AgentDocumentManager, max_results: number = 3) {
super(searchToolInfo);
- this._addLinkedUrlDoc = addLinkedUrlDoc;
+ this._docManager = docManager;
this._max_results = max_results;
}
@@ -46,8 +49,13 @@ export class SearchTool extends BaseTool<SearchToolParamsType> {
max_results: this._max_results,
})) as { results: { url: string; snippet: string }[] };
const data = results.map((result: { url: string; snippet: string }) => {
- const id = uuidv4();
- this._addLinkedUrlDoc(result.url, id);
+ // Create a web document with the URL
+ const id = this._docManager.createDocInDash('web', result.url, {
+ title: `Search Result: ${result.url}`,
+ text_html: result.snippet,
+ data_useCors: true,
+ });
+
return {
type: 'text' as const,
text: `<chunk chunk_id="${id}" chunk_type="url"><url>${result.url}</url><overview>${result.snippet}</overview></chunk>`,
diff --git a/src/client/views/nodes/chatbot/tools/WebsiteInfoScraperTool.ts b/src/client/views/nodes/chatbot/tools/WebsiteInfoScraperTool.ts
index 19ccd0b36..3c7b4e3db 100644
--- a/src/client/views/nodes/chatbot/tools/WebsiteInfoScraperTool.ts
+++ b/src/client/views/nodes/chatbot/tools/WebsiteInfoScraperTool.ts
@@ -66,38 +66,118 @@ const websiteInfoScraperToolInfo: ToolInfo<WebsiteInfoScraperToolParamsType> = {
};
export class WebsiteInfoScraperTool extends BaseTool<WebsiteInfoScraperToolParamsType> {
- private _addLinkedUrlDoc: (url: string, id: string) => void;
+ private _getLinkedUrlDocId: (url: string) => string[];
- constructor(addLinkedUrlDoc: (url: string, id: string) => void) {
+ constructor(getLinkedUrlDocIds: (url: string) => string[]) {
super(websiteInfoScraperToolInfo);
- this._addLinkedUrlDoc = addLinkedUrlDoc;
+ this._getLinkedUrlDocId = getLinkedUrlDocIds;
}
- async execute(args: ParametersType<WebsiteInfoScraperToolParamsType>): Promise<Observation[]> {
- const urls = args.urls;
-
- // Create an array of promises, each one handling a website scrape for a URL
- const scrapingPromises = urls.map(async url => {
+ /**
+ * Attempts to scrape a website with retry logic
+ * @param url URL to scrape
+ * @param maxRetries Maximum number of retry attempts
+ * @returns The scraped content or error message
+ */
+ private async scrapeWithRetry(url: string, maxRetries = 2): Promise<Observation> {
+ let lastError = '';
+ let retryCount = 0;
+
+ // Validate URL format
+ try {
+ new URL(url); // This will throw if URL is invalid
+ } catch (e) {
+ return {
+ type: 'text',
+ text: `Invalid URL format: ${url}. Please provide a valid URL including http:// or https://`,
+ } as Observation;
+ }
+
+ while (retryCount <= maxRetries) {
try {
- const { website_plain_text } = await Networking.PostToServer('/scrapeWebsite', { url });
- const id = uuidv4();
- this._addLinkedUrlDoc(url, id);
+ // Add a slight delay between retries
+ if (retryCount > 0) {
+ console.log(`Retry attempt ${retryCount} for ${url}`);
+ await new Promise(resolve => setTimeout(resolve, retryCount * 2000)); // Increasing delay for each retry
+ }
+
+ const response = await Networking.PostToServer('/scrapeWebsite', { url });
+
+ if (!response || typeof response !== 'object') {
+ lastError = 'Empty or invalid response from server';
+ retryCount++;
+ continue;
+ }
+
+ const { website_plain_text } = response as { website_plain_text: string };
+ const id = this._getLinkedUrlDocId(url);
+
+ // Validate content quality
+ if (!website_plain_text) {
+ lastError = 'Retrieved content was empty';
+ retryCount++;
+ continue;
+ }
+
+ if (website_plain_text.length < 100) {
+ console.warn(`Warning: Content from ${url} is very short (${website_plain_text.length} chars)`);
+
+ // Still return it if this is our last try
+ if (retryCount === maxRetries) {
+ return {
+ type: 'text',
+ text: `<chunk chunk_id="${id}" chunk_type="url">\n${website_plain_text}\nNote: Limited content was retrieved from this URL.\n</chunk>`,
+ } as Observation;
+ }
+
+ lastError = 'Retrieved content was too short, trying again';
+ retryCount++;
+ continue;
+ }
+
+ // Process and return content if it looks good
return {
type: 'text',
text: `<chunk chunk_id="${id}" chunk_type="url">\n${website_plain_text}\n</chunk>`,
} as Observation;
} catch (error) {
- console.log(error);
- return {
- type: 'text',
- text: `An error occurred while scraping the website: ${url}`,
- } as Observation;
+ lastError = error instanceof Error ? error.message : 'Unknown error';
+ console.log(`Error scraping ${url} (attempt ${retryCount + 1}):`, error);
}
- });
+
+ retryCount++;
+ }
+
+ // All attempts failed
+ return {
+ type: 'text',
+ text: `Unable to scrape website: ${url}. Error: ${lastError}`,
+ } as Observation;
+ }
+
+ async execute(args: ParametersType<WebsiteInfoScraperToolParamsType>): Promise<Observation[]> {
+ const urls = args.urls;
+
+ // Create an array of promises, each one handling a website scrape for a URL
+ const scrapingPromises = urls.map(url => this.scrapeWithRetry(url));
// Wait for all scraping promises to resolve
const results = await Promise.all(scrapingPromises);
+ // Check if we got any successful results
+ const successfulResults = results.filter(result => {
+ if (result.type !== 'text') return false;
+ return (result as { type: 'text'; text: string }).text.includes('chunk_id') && !(result as { type: 'text'; text: string }).text.includes('Unable to scrape');
+ });
+
+ // If all scrapes failed, provide a more helpful error message
+ if (successfulResults.length === 0 && results.length > 0) {
+ results.push({
+ type: 'text',
+ text: `Note: All website scraping attempts failed. Please try with different URLs or try again later.`,
+ } as Observation);
+ }
+
return results;
}
}