From 6e8f05495dfcf7d64bdc424503f874fece85e291 Mon Sep 17 00:00:00 2001 From: "A.J. Shulman" Date: Thu, 10 Apr 2025 12:36:07 -0400 Subject: trying again --- .../views/nodes/chatbot/tools/CreateAnyDocTool.ts | 158 ------ .../nodes/chatbot/tools/DocumentMetadataTool.ts | 561 +++++++++++---------- 2 files changed, 299 insertions(+), 420 deletions(-) delete mode 100644 src/client/views/nodes/chatbot/tools/CreateAnyDocTool.ts (limited to 'src/client/views/nodes/chatbot/tools') 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 = { - 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: - ` + - Object.entries(documentTypesInfo) - .map( - ([doc_type, info]) => - ` - ${info.dataDescription} - ` + - info.options.map(option => ``).join('\n') + - ` - ` - ) - .join('\n') + - ``, - parameterRules: createAnyDocumentToolParams, - citationRules: 'No citation needed.', -}; - -export class CreateAnyDocumentTool extends BaseTool { - private _addLinkedDoc: (doc: parsedDoc) => Doc | undefined; - - constructor(addLinkedDoc: (doc: parsedDoc) => Doc | undefined) { - super(createAnyDocToolInfo); - this._addLinkedDoc = addLinkedDoc; - } - - async execute(args: ParametersType): Promise { - 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/DocumentMetadataTool.ts b/src/client/views/nodes/chatbot/tools/DocumentMetadataTool.ts index a9fb45b5a..a3d86287d 100644 --- a/src/client/views/nodes/chatbot/tools/DocumentMetadataTool.ts +++ b/src/client/views/nodes/chatbot/tools/DocumentMetadataTool.ts @@ -11,7 +11,6 @@ import { DocCast, StrCast } from '../../../../../fields/Types'; import { supportedDocTypes } from '../types/tool_types'; import { parsedDoc } from '../chatboxcomponents/ChatBox'; - // Define the parameters for the DocumentMetadataTool const parameterDefinitions: ReadonlyArray = [ { @@ -61,7 +60,7 @@ const parameterDefinitions: ReadonlyArray = [ 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; @@ -105,14 +104,14 @@ IMPORTANT: Some fields have dependencies that must be handled for edits to work - You can edit dependent fields in a single operation using the fieldEdits parameter Example: To change document height, first disable auto-height: -1. { action: "edit", documentId: "doc123", fieldName: "layout_autoHeight", fieldValue: false } -2. { action: "edit", documentId: "doc123", fieldName: "height", fieldValue: 300 } +1. {... inputs: { action: "edit", documentId: "doc123", fieldName: "layout_autoHeight", fieldValue: false }} +2. {... inputs: { action: "edit", documentId: "doc123", fieldName: "height", fieldValue: 300 }} OR using multi-field edit (recommended for dependent fields): -{ action: "edit", documentId: "doc123", fieldEdits: [ +{... 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: @@ -132,7 +131,7 @@ To CREATE a new document: - 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: { action: "create", title: "My Notes", data: "This is the content", doc_type: "text" } +- 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: @@ -147,7 +146,7 @@ To EDIT document metadata: SPECIAL FIELD HANDLING: - Text fields: When editing the 'text' field, provide simple plain text - Example: { action: "edit", documentId: "doc123", fieldName: "text", fieldValue: "Hello world" } + Example: {...inputs: { action: "edit", documentId: "doc123", 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 @@ -165,10 +164,10 @@ 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 (recommended): - { action: "edit", documentId: "doc123", fieldEdits: [ + {...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) @@ -200,8 +199,8 @@ Examples: { 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 = { name: 'documentMetadata', description: toolDescription, @@ -227,7 +226,7 @@ export class DocumentMetadataTool extends BaseTool DocCast(LinkManager.getOppositeAnchor(d, this.chatBoxDocument!))) .map(d => DocCast(d?.annotationOn, d)) .filter(d => d); - + console.log(`Found ${linkedDocs.length} linked documents via LinkManager`); - + // Process the linked documents linkedDocs.forEach((doc: Doc) => { if (doc) { this.processDocument(doc); } }); - + // Include the ChatBox document itself this.processDocument(this.chatBoxDocument); - + // If we have access to the Document's parent, try to find sibling documents if (this.chatBoxDocument.parent) { const parent = this.chatBoxDocument.parent; console.log('Found parent document, checking for siblings'); - + // Check if parent is a Doc type and has a childDocs function if (parent && typeof parent === 'object' && 'childDocs' in parent && typeof parent.childDocs === 'function') { try { @@ -356,7 +355,7 @@ export class DocumentMetadataTool extends BaseTool = { id: docId, title: doc.title || '', @@ -488,7 +487,7 @@ export class DocumentMetadataTool extends BaseTool { const fieldDef = this.fieldMetadata[fieldName]; const strippedName = fieldName.startsWith('_') ? fieldName.substring(1) : fieldName; - + // Check if field exists on layout document let layoutValue = undefined; if (layoutDoc) { @@ -499,7 +498,7 @@ export class DocumentMetadataTool extends BaseTool extractText(child)); } }; - + extractText(rtfObj.doc); - + // If we successfully extracted text, show it, but also preserve the original value if (plainText) { return { @@ -819,14 +822,14 @@ export class DocumentMetadataTool extends BaseTool { const strippedName = fieldName.startsWith('_') ? fieldName.substring(1) : fieldName; - + // Add to fieldNameMappings if (fieldName.startsWith('_')) { result.fieldNameMappings[strippedName] = fieldName; } - + // Create structured field metadata const fieldData: Record = { name: fieldName, @@ -886,10 +889,10 @@ export class DocumentMetadataTool extends BaseTool): Promise { console.log('DocumentMetadataTool: Executing with args:', args); - + // Find all documents in the Freeform view this.findDocumentsInFreeformView(); - + 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))}` - }]; + 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".' - }]; + 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.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)}` - }]; + 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.' - }]; + return [ + { + type: 'text', + text: 'Error: Document ID is required for edit actions.', + }, + ]; } - + // Ensure document exists if (!this.documentsById.has(documentId)) { - return [{ - type: 'text', - text: `Error: Document with ID ${documentId} not found.` - }]; + return [ + { + type: 'text', + text: `Error: Document with ID ${documentId} not found.`, + }, + ]; } - + // Check if we're doing a multi-field edit or a single field edit if (args.fieldEdits) { 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.' - }]; + 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; + 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.editDocumentField( - documentId, - fieldName, - fieldValue - ); - + const result = this.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`; @@ -1088,7 +1099,7 @@ export class DocumentMetadataTool extends BaseTool { 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`; @@ -1098,120 +1109,134 @@ export class DocumentMetadataTool extends BaseTool ({ id, title: doc.title || 'Untitled Document', - type: doc.type || 'Unknown Type' + type: doc.type || 'Unknown Type', })); - + if (docs.length === 0) { - return [{ - type: 'text', - text: 'No documents found in the current view.' - }]; + return [ + { + type: 'text', + text: 'No documents found in the current view.', + }, + ]; } - - return [{ - type: 'text', - text: `Found ${docs.length} document(s) in the current view:\n${JSON.stringify(docs, null, 2)}` - }]; + + return [ + { + type: 'text', + text: `Found ${docs.length} document(s) in the current view:\n${JSON.stringify(docs, null, 2)}`, + }, + ]; } - + case 'getFieldOptions': { // Get all available field options with metadata const fieldOptions = this.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)}` - }]; + + 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.' - }]; + 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); - + // Validate doc_type if (!this.isValidDocType(docType)) { - return [{ - type: 'text', - text: `Error: Invalid doc_type. Valid options are: ${Object.keys(supportedDocTypes).join(',')}` - }]; + return [ + { + type: 'text', + text: `Error: Invalid doc_type. Valid options are: ${Object.keys(supportedDocTypes).join(',')}`, + }, + ]; } - + try { // Create simple document with just title and data const simpleDoc: parsedDoc = { @@ -1223,35 +1248,40 @@ export class DocumentMetadataTool extends BaseTool