diff options
-rw-r--r-- | src/client/views/nodes/chatbot/agentsystem/Agent.ts | 12 | ||||
-rw-r--r-- | src/client/views/nodes/chatbot/tools/DocumentMetadataTool.ts | 497 |
2 files changed, 261 insertions, 248 deletions
diff --git a/src/client/views/nodes/chatbot/agentsystem/Agent.ts b/src/client/views/nodes/chatbot/agentsystem/Agent.ts index b5cdb8cf1..0610dc198 100644 --- a/src/client/views/nodes/chatbot/agentsystem/Agent.ts +++ b/src/client/views/nodes/chatbot/agentsystem/Agent.ts @@ -461,12 +461,12 @@ export class Agent { } console.log(actionInput); - // Special handling for documentMetadata tool with numeric fieldValue - if (action === 'documentMetadata' && - 'fieldValue' in actionInput && - typeof actionInput.fieldValue === 'number') { - // Convert number to string to pass validation - actionInput.fieldValue = String(actionInput.fieldValue); + // Special handling for documentMetadata tool with numeric or boolean fieldValue + if (action === 'documentMetadata' && 'fieldValue' in actionInput) { + if (typeof actionInput.fieldValue === 'number' || typeof actionInput.fieldValue === 'boolean') { + // Convert number or boolean to string to pass validation + actionInput.fieldValue = String(actionInput.fieldValue); + } } for (const param of this.tools[action].parameterRules) { diff --git a/src/client/views/nodes/chatbot/tools/DocumentMetadataTool.ts b/src/client/views/nodes/chatbot/tools/DocumentMetadataTool.ts index 9c3a1fbb5..13b8315da 100644 --- a/src/client/views/nodes/chatbot/tools/DocumentMetadataTool.ts +++ b/src/client/views/nodes/chatbot/tools/DocumentMetadataTool.ts @@ -1,7 +1,7 @@ import { Doc, FieldType } from '../../../../../fields/Doc'; import { DocData } from '../../../../../fields/DocSymbols'; import { Observation } from '../types/types'; -import { ParametersType, ToolInfo } from '../types/tool_types'; +import { ParametersType, ToolInfo, Parameter } from '../types/tool_types'; import { BaseTool } from './BaseTool'; import { DocumentOptions } from '../../../../documents/Documents'; import { CollectionFreeFormDocumentView } from '../../../nodes/CollectionFreeFormDocumentView'; @@ -9,41 +9,35 @@ import { v4 as uuidv4 } from 'uuid'; import { LinkManager } from '../../../../util/LinkManager'; import { DocCast, StrCast } from '../../../../../fields/Types'; -// Parameter definitions for DocumentMetadataTool -const documentMetadataToolParams = [ +// Define the parameters for the DocumentMetadataTool +const parameterDefinitions: ReadonlyArray<Parameter> = [ { name: 'action', type: 'string', - description: 'The action to perform: "get" for retrieving metadata, "edit" for modifying document metadata, or "list" for a simple document list', required: true, + description: 'The action to perform: "get" to retrieve metadata, "edit" to modify metadata, or "list" to enumerate documents', }, { name: 'documentId', type: 'string', - description: 'The ID of the document to get metadata from or edit. If not provided when getting metadata, information about all documents will be returned.', required: false, + description: 'The ID of the document to get or edit metadata for. Required for "edit", optional for "get", ignored for "list"', }, { name: 'fieldName', type: 'string', - description: 'When editing, the name of the field to modify (with or without leading underscore).', required: false, + description: 'The name of the field to edit. Required for "edit" action. Field names can be provided with or without leading underscores', }, { name: 'fieldValue', type: 'string', - description: 'When editing, the new value to set for the specified field. Numeric values will be converted to strings automatically.', - required: false, - }, - { - name: 'documentLocation', - type: 'string', - description: 'When editing, specify where to modify the field: "layout" (default), "data", or "auto" (determines automatically based on existing fields).', required: false, + description: 'The new value for the field. Required for "edit" action. Can be a string, number, or boolean value depending on the field type', } ] as const; -type DocumentMetadataToolParamsType = typeof documentMetadataToolParams; +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. @@ -59,7 +53,17 @@ This tool provides the following capabilities: - Edit metadata fields on documents (in either layout or data documents) - List all available documents in the current view - Understand which fields are stored where (layout vs data document) -- Get detailed information about all available document fields`; +- Get detailed information about all available document fields +- Support for all value types: strings, numbers, and booleans + +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 + +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 }`; // Extensive usage guidelines for the tool const citationRules = `USAGE GUIDELINES: @@ -69,22 +73,32 @@ To GET document metadata: To EDIT document metadata: - Use action="edit" with required documentId, fieldName, and fieldValue parameters -- Optionally specify documentLocation="layout"|"data"|"auto" (default is "auto") -- The tool will determine the correct document location automatically unless specified +- 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" -- Numeric values are accepted for appropriate fields (width, height, etc.) -- The tool will apply the edit to the correct document (layout or data) +- 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 + +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: + 1. First: { action: "edit", documentId: "doc123", fieldName: "layout_autoHeight", fieldValue: false } + 2. Then: { action: "edit", documentId: "doc123", 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. If documentLocation is specified, the field is modified in that location -2. If documentLocation="auto", the tool checks if the field exists on layout or data document first -3. If the field doesn't exist in either document, it's added to the location specified or layout document by default -4. Fields with leading underscores are automatically handled correctly +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 list all documents: { action: "list" } @@ -92,12 +106,15 @@ Examples: - To get metadata for a specific document: { action: "get", documentId: "doc123" } - To edit a field: { action: "edit", documentId: "doc123", fieldName: "backgroundColor", fieldValue: "#ff0000" } - To edit a width property: { action: "edit", documentId: "doc123", fieldName: "width", fieldValue: 300 } -- To edit a field in the data document: { action: "edit", documentId: "doc123", fieldName: "text", fieldValue: "New content", documentLocation: "data" }`; +- To disable auto-height: { action: "edit", documentId: "doc123", fieldName: "layout_autoHeight", fieldValue: false } +- To edit height with dependent field: + { action: "edit", documentId: "doc123", fieldName: "layout_autoHeight", fieldValue: false } + { action: "edit", documentId: "doc123", fieldName: "height", fieldValue: 200 }`; const documentMetadataToolInfo: ToolInfo<DocumentMetadataToolParamsType> = { name: 'documentMetadata', description: toolDescription, - parameterRules: documentMetadataToolParams, + parameterRules: parameterDefinitions, citationRules: citationRules, }; @@ -449,16 +466,16 @@ export class DocumentMetadataTool extends BaseTool<DocumentMetadataToolParamsTyp * Edits a specific field on a document * @param docId The ID of the document to edit * @param fieldName The name of the field to edit - * @param fieldValue The new value for the field - * @param documentLocation Where to edit the field: 'layout', 'data', or 'auto' + * @param fieldValue The new value for the field (string, number, or boolean) * @returns Object with success status, message, and additional information */ - private editDocumentField(docId: string, fieldName: string, fieldValue: string, documentLocation: string = 'auto'): { + private editDocumentField(docId: string, fieldName: string, fieldValue: string | number | boolean): { success: boolean; message: string; fieldName?: string; originalFieldName?: string; newValue?: any; + warning?: string; } { // Normalize field name (handle with/without underscore) let normalizedFieldName = fieldName.startsWith('_') ? fieldName : fieldName; @@ -489,46 +506,44 @@ export class DocumentMetadataTool extends BaseTool<DocumentMetadataToolParamsTyp // Convert the field value to the appropriate type based on field metadata const convertedValue = this.convertFieldValue(normalizedFieldName, fieldValue); - // Determine where to set the field value let targetDoc: Doc | undefined; let targetLocation: string; - if (documentLocation === 'layout') { - // Explicitly set on layout document - targetDoc = layoutDoc; - targetLocation = 'layout'; - } else if (documentLocation === 'data') { - // Explicitly set on data document - targetDoc = dataDoc; - targetLocation = 'data'; - } else { - // Auto-detect where to set the field - - // Check if field exists on layout document (with or without underscore) - const existsOnLayout = layoutDoc && - (layoutDoc[normalizedFieldName] !== undefined || - layoutDoc[strippedFieldName] !== undefined); - - // Check if field exists on data document (with or without underscore) - const existsOnData = dataDoc && - (dataDoc[normalizedFieldName] !== undefined || - dataDoc[strippedFieldName] !== undefined); + // First, check if field exists on layout document using Doc.Get + if (layoutDoc) { + const fieldExistsOnLayout = Doc.Get(layoutDoc, normalizedFieldName, true) !== undefined; - if (existsOnLayout) { + // If it exists on layout document, update it there + if (fieldExistsOnLayout) { + targetDoc = layoutDoc; + targetLocation = 'layout'; + } + // If it has an underscore prefix, it's likely a layout property even if not yet set + else if (normalizedFieldName.startsWith('_')) { targetDoc = layoutDoc; targetLocation = 'layout'; - } else if (existsOnData) { + } + // Otherwise, look for or create on data document + else if (dataDoc) { targetDoc = dataDoc; targetLocation = 'data'; - } else { - // Field doesn't exist on either document, default to layout document - targetDoc = layoutDoc || dataDoc; - targetLocation = layoutDoc ? 'layout' : 'data'; + } + // If no data document available, default to layout + else { + targetDoc = layoutDoc; + targetLocation = 'layout'; } + } + // If no layout document, use data document + else if (dataDoc) { + targetDoc = dataDoc; + targetLocation = 'data'; + } else { + return { success: false, message: `No valid document found for editing` }; } if (!targetDoc) { - return { success: false, message: `Target document (${documentLocation}) not available` }; + return { success: false, message: `Target document not available` }; } // Set the field value on the target document @@ -557,12 +572,22 @@ export class DocumentMetadataTool extends BaseTool<DocumentMetadataToolParamsTyp * @returns The converted value with the appropriate type */ private convertFieldValue(fieldName: string, fieldValue: any): any { - // If fieldValue is already a number, we don't need to convert it from string - if (typeof fieldValue === 'number') { + // If fieldValue is already a number or boolean, we don't need to convert it from string + if (typeof fieldValue === 'number' || typeof fieldValue === 'boolean') { return fieldValue; } - // If fieldValue is not a string (and not a number), convert it to string + // If fieldValue is a string "true" or "false", convert to boolean + if (typeof fieldValue === 'string') { + if (fieldValue.toLowerCase() === 'true') { + return true; + } + if (fieldValue.toLowerCase() === 'false') { + return false; + } + } + + // If fieldValue is not a string (and not a number or boolean), convert it to string if (typeof fieldValue !== 'string') { fieldValue = String(fieldValue); } @@ -662,210 +687,144 @@ export class DocumentMetadataTool extends BaseTool<DocumentMetadataToolParamsTyp } /** - * Executes the DocumentMetadataTool to extract or edit metadata from documents in the Freeform view - * @param args The parameters for the tool execution - * @returns A promise that resolves to an array of Observation objects + * 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); - - // Add diagnostic information about the ChatBox instance - if (this.chatBox) { - console.log('ChatBox instance available:', { - hasDocument: !!this.chatBoxDocument, - chatBoxProps: this.chatBox.props ? Object.keys(this.chatBox.props) : 'No props', - linkedDocsMethod: typeof this.chatBox.linkedDocs === 'function' ? 'Exists (function)' : - typeof this.chatBox.linkedDocs === 'object' ? 'Exists (object)' : 'Not available' - }); - } else { - console.warn('No ChatBox instance available'); - } + console.log('DocumentMetadataTool: Executing with args:', args); // Find all documents in the Freeform view this.findDocumentsInFreeformView(); - // Debug diagnostic information about all documents found - if (this.documentsById.size > 0) { - console.log('Documents found:', Array.from(this.documentsById.entries()).map(([id, doc]) => ({ - id, - title: doc.title || 'Untitled', - type: doc.type || 'Unknown', - hasLayout: !!this.layoutDocsById.get(id), - hasData: !!this.dataDocsById.get(id) - }))); - } - - // Check if we found any documents - if (this.documentsById.size === 0) { - console.error('No documents found in Freeform view'); - return [{ - type: 'text', - text: JSON.stringify({ - success: false, - message: 'No documents found in the current view. Unable to extract or edit metadata.', - chatBoxDocumentId: this.chatBoxDocument ? this.chatBoxDocument.id : 'No ChatBox document', - diagnostics: { - chatBoxAvailable: !!this.chatBox, - documentAvailable: !!this.chatBoxDocument, - toolInitializedWith: this.chatBox ? - (this.chatBox.constructor && this.chatBox.constructor.name) || 'Unknown constructor' - : 'No ChatBox' - } - }, null, 2) - }]; - } - - let result: Record<string, any>; - - // Determine which action to perform - const action = args.action?.toLowerCase(); - - if (action === 'list') { - // Just return a simple list of available documents for quick reference - const documentList = Array.from(this.documentsById.entries()).map(([id, doc]) => ({ - id, - title: doc.title || 'Untitled', - type: doc.type || 'Unknown' - })); - - result = { - success: true, - message: `Found ${documentList.length} documents in the current view`, - documents: documentList - }; - } - else if (action === 'edit') { - // Edit document metadata - if (!args.documentId) { - return [{ - type: 'text', - text: JSON.stringify({ - success: false, - message: 'Document ID is required for edit operations', - availableDocuments: Array.from(this.documentsById.keys()) - }, null, 2) - }]; - } - - if (!args.fieldName) { - return [{ - type: 'text', - text: JSON.stringify({ - success: false, - message: 'Field name is required for edit operations', - }, null, 2) - }]; - } - - if (args.fieldValue === undefined) { + try { + // Validate required input parameters based on action + if (!this.inputValidator(args)) { return [{ - type: 'text', - text: JSON.stringify({ - success: false, - message: 'Field value is required for edit operations', - }, null, 2) + type: 'text', + text: `Error: Invalid or missing parameters for action "${args.action}". ${this.getParameterRequirementsByAction(String(args.action))}` }]; } - // Check if the document exists - if (!this.documentsById.has(args.documentId)) { + // Ensure the action is valid and convert to string + const action = String(args.action); + if (!['get', 'edit', 'list'].includes(action)) { return [{ - type: 'text', - text: JSON.stringify({ - success: false, - message: `Document with ID ${args.documentId} not found`, - availableDocuments: Array.from(this.documentsById.keys()) - }, null, 2) + type: 'text', + text: 'Error: Invalid action. Valid actions are "get", "edit", or "list".' }]; } + + // Safely convert documentId to string or undefined + const documentId = args.documentId ? String(args.documentId) : undefined; - // Convert fieldValue to string if it's not already - // This is to support the Agent passing numeric values directly - const fieldValue = typeof args.fieldValue === 'string' ? - args.fieldValue : - String(args.fieldValue); - - // Perform the edit - const editResult = this.editDocumentField( - args.documentId, - args.fieldName, - fieldValue, - args.documentLocation || 'auto' - ); - - // If successful, also get the updated metadata - if (editResult.success) { - const documentMetadata = this.extractDocumentMetadata(args.documentId); - result = { - ...editResult, - document: documentMetadata, - }; - } else { - result = editResult; - } - } else if (action === 'get') { - // Get document metadata - if (args.documentId) { - // Check if the document exists - if (!this.documentsById.has(args.documentId)) { + // 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: JSON.stringify({ - success: false, - message: `Document with ID ${args.documentId} not found`, - availableDocuments: Array.from(this.documentsById.keys()) - }, null, 2) + type: 'text', + text: `Document metadata ${documentId ? 'for document ' + documentId : ''} retrieved successfully:\n${JSON.stringify(result, null, 2)}` }]; } - // Get metadata for a specific document - const documentMetadata = this.extractDocumentMetadata(args.documentId); - result = { - success: !!documentMetadata, - message: documentMetadata ? 'Document metadata retrieved successfully' : `Document with ID ${args.documentId} not found`, - document: documentMetadata, - fieldDefinitions: this.fieldMetadata, - }; - } else { - // Get metadata for all documents - const documentsMetadata: Record<string, any> = {}; - for (const docId of this.documentsById.keys()) { - documentsMetadata[docId] = this.extractDocumentMetadata(docId); + case 'edit': { + // Edit a specific field on a document + if (!documentId || !args.fieldName) { + return [{ + type: 'text', + text: 'Error: Document ID, field name, and field value are required for edit actions.' + }]; + } + + // Ensure document exists + if (!this.documentsById.has(documentId)) { + return [{ + type: 'text', + text: `Error: Document with ID ${documentId} not found.` + }]; + } + + // Get fieldValue in its original form - we'll handle conversion in editDocumentField + let fieldValue = args.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(args.fieldName); + + // Edit the field + const result = this.editDocumentField( + documentId, + fieldName, + fieldValue + ); + + console.log('DocumentMetadataTool: Edit field result:', result); + + if (!result.success) { + return [{ type: 'text', text: result.message }]; + } + + // Include warning if present + let responseText = result.message; + if (result.warning) { + responseText += `\n\n${result.warning}`; + } + + // Get the updated metadata to return + const updatedMetadata = this.getDocumentMetadata(documentId); + + return [{ + type: 'text', + text: `${responseText}\nUpdated metadata:\n${JSON.stringify(updatedMetadata, null, 2)}` + }]; } - result = { - success: true, - message: `Retrieved metadata for ${this.documentsById.size} documents`, - documents: documentsMetadata, - fieldDefinitions: this.fieldMetadata, - documentCount: this.documentsById.size, - fieldMetadataCount: Object.keys(this.fieldMetadata).length, - }; + case 'list': { + // List all available documents in simple format + const docs = Array.from(this.documentsById.entries()).map(([id, doc]) => ({ + id, + title: doc.title || 'Untitled Document', + type: doc.type || 'Unknown Type' + })); + + if (docs.length === 0) { + 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)}` + }]; + } + + default: + return [{ + type: 'text', + text: 'Error: Unknown action. Valid actions are "get", "edit", or "list".' + }]; } - } else { - // Invalid action - result = { - success: false, - message: `Invalid action: ${action}. Valid actions are "list", "get", and "edit".`, - availableActions: ["list", "get", "edit"] - }; + } catch (error) { + console.error('DocumentMetadataTool execution error:', error); + return [{ + type: 'text', + text: `Error executing DocumentMetadataTool: ${error instanceof Error ? error.message : String(error)}` + }]; } - - // Log the result to the console - console.log('Document Metadata Tool Result:', result); - - // Return the result as an observation - // Convert to string format as the Observation type only supports 'text' or 'image_url' types - return [{ - type: 'text', - text: JSON.stringify(result, null, 2) - }]; } /** * Validates the input parameters for the DocumentMetadataTool - * This custom validator allows numbers to be passed for fieldValue while maintaining - * compatibility with the standard validation + * 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 @@ -888,13 +847,67 @@ export class DocumentMetadataTool extends BaseTool<DocumentMetadataToolParamsTyp return false; } - // Allow for numeric fieldValue even though the type is defined as string - if (params.fieldValue !== undefined && typeof params.fieldValue === 'number') { - console.log('Numeric fieldValue detected, will be converted to string'); - // We'll convert it later, so don't fail validation - return true; + // Allow for numeric or boolean fieldValue even though the type is defined as string + if (params.fieldValue !== undefined) { + if (typeof params.fieldValue === 'number') { + console.log('Numeric fieldValue detected, will be converted to string'); + // We'll convert it later, so don't fail validation + return true; + } + + if (typeof params.fieldValue === 'boolean') { + console.log('Boolean fieldValue detected, will be converted appropriately'); + // We'll handle boolean conversion in the execute method + 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", or "list".'; + } + + switch (action.toLowerCase()) { + case 'get': + return 'The "get" action accepts an optional documentId parameter.'; + case 'edit': + return 'The "edit" action requires documentId, fieldName, and fieldValue parameters.'; + case 'list': + return 'The "list" action does not require any additional parameters.'; + default: + return `Unknown action "${action}". Valid actions are "get", "edit", or "list".`; + } + } + + /** + * Gets metadata for a specific document or all documents + * @param documentId Optional ID of a specific document to get metadata for + * @returns Document metadata or metadata for all documents + */ + private getDocumentMetadata(documentId?: string): any { + if (documentId) { + // Get metadata for a specific document + return this.extractDocumentMetadata(documentId); + } else { + // Get metadata for all documents + const documentsMetadata: Record<string, any> = {}; + for (const docId of this.documentsById.keys()) { + documentsMetadata[docId] = this.extractDocumentMetadata(docId); + } + + return { + documentCount: this.documentsById.size, + documents: documentsMetadata, + fieldDefinitions: this.fieldMetadata + }; + } + } }
\ No newline at end of file |