diff options
-rw-r--r-- | src/client/views/nodes/chatbot/agentsystem/Agent.ts | 32 | ||||
-rw-r--r-- | src/client/views/nodes/chatbot/tools/DocumentMetadataTool.ts | 421 |
2 files changed, 390 insertions, 63 deletions
diff --git a/src/client/views/nodes/chatbot/agentsystem/Agent.ts b/src/client/views/nodes/chatbot/agentsystem/Agent.ts index 0610dc198..3c76cf348 100644 --- a/src/client/views/nodes/chatbot/agentsystem/Agent.ts +++ b/src/client/views/nodes/chatbot/agentsystem/Agent.ts @@ -462,10 +462,34 @@ export class Agent { console.log(actionInput); // 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); + if (action === 'documentMetadata') { + // Handle single field edit + if ('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); + } + } + + // Handle fieldEdits parameter (for multiple field edits) + if ('fieldEdits' in actionInput && actionInput.fieldEdits) { + try { + // If it's already an array, stringify it to ensure it passes validation + if (Array.isArray(actionInput.fieldEdits)) { + actionInput.fieldEdits = JSON.stringify(actionInput.fieldEdits); + } + // If it's an object but not an array, it might be a single edit - convert to array and stringify + else if (typeof actionInput.fieldEdits === 'object') { + actionInput.fieldEdits = JSON.stringify([actionInput.fieldEdits]); + } + // Otherwise, ensure it's a string for the validator + else if (typeof actionInput.fieldEdits !== 'string') { + actionInput.fieldEdits = String(actionInput.fieldEdits); + } + } catch (error) { + console.error('Error processing fieldEdits:', error); + // Don't fail validation here, let the tool handle it + } } } diff --git a/src/client/views/nodes/chatbot/tools/DocumentMetadataTool.ts b/src/client/views/nodes/chatbot/tools/DocumentMetadataTool.ts index 13b8315da..6354e8df3 100644 --- a/src/client/views/nodes/chatbot/tools/DocumentMetadataTool.ts +++ b/src/client/views/nodes/chatbot/tools/DocumentMetadataTool.ts @@ -15,25 +15,31 @@ const parameterDefinitions: ReadonlyArray<Parameter> = [ name: 'action', type: 'string', required: true, - description: 'The action to perform: "get" to retrieve metadata, "edit" to modify metadata, or "list" to enumerate documents', + description: 'The action to perform: "get" to retrieve metadata, "edit" to modify metadata, "list" to enumerate documents, or "getFieldOptions" to retrieve all available field options', }, { 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"', + description: 'The ID of the document to get or edit metadata for. Required for "edit", optional for "get", ignored for "list" and "getFieldOptions"', }, { name: 'fieldName', type: 'string', required: false, - description: 'The name of the field to edit. Required for "edit" action. Field names can be provided with or without leading underscores', + description: 'The name of the field to edit. Required for single field edits. Ignored if fieldEdits is provided', }, { name: 'fieldValue', type: 'string', 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', + description: 'The new value for the field. Required for single field edits. Can be a string, number, or boolean value depending on the field type', + }, + { + name: 'fieldEdits', + type: 'string', + required: false, + description: 'JSON array of field edits for editing multiple fields at once. Each item should have fieldName and fieldValue. Example: [{"fieldName":"layout_autoHeight","fieldValue":false},{"fieldName":"height","fieldValue":300}]', } ] as const; @@ -51,19 +57,34 @@ 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 +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 +- 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 }`; +2. { action: "edit", documentId: "doc123", fieldName: "height", fieldValue: 300 } + +OR using multi-field edit (recommended for dependent fields): +{ action: "edit", documentId: "doc123", fieldEdits: [ + { fieldName: "layout_autoHeight", fieldValue: false }, + { fieldName: "height", fieldValue: 300 } +]}`; // Extensive usage guidelines for the tool const citationRules = `USAGE GUIDELINES: @@ -71,19 +92,37 @@ 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 EDIT document metadata: -- Use action="edit" with required documentId, fieldName, and fieldValue parameters +- Use action="edit" with required documentId, and either: + 1. fieldName + fieldValue for single field edits, OR + 2. fieldEdits for updating multiple fields at once - 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 +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 + 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 } +- 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: [ + { 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) @@ -101,15 +140,18 @@ Editing fields follows these rules: 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 field: { action: "edit", documentId: "doc123", fieldName: "backgroundColor", fieldValue: "#ff0000" } +- To edit a single field: { action: "edit", documentId: "doc123", fieldName: "backgroundColor", fieldValue: "#ff0000" } - To edit a width property: { action: "edit", documentId: "doc123", fieldName: "width", fieldValue: 300 } - 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 }`; +- To edit height with its dependent field together (recommended): + { action: "edit", documentId: "doc123", fieldEdits: [ + { fieldName: "layout_autoHeight", fieldValue: false }, + { fieldName: "height", fieldValue: 200 } + ]}`; const documentMetadataToolInfo: ToolInfo<DocumentMetadataToolParamsType> = { name: 'documentMetadata', @@ -687,6 +729,112 @@ export class DocumentMetadataTool extends BaseTool<DocumentMetadataToolParamsTyp } /** + * Extracts all field metadata from DocumentOptions + * @returns A structured object containing metadata about all available document fields + */ + private getAllFieldMetadata() { + // Start with our already populated fieldMetadata from the DocumentOptions class + const result: Record<string, any> = { + fieldCount: Object.keys(this.fieldMetadata).length, + fields: {}, + fieldsByType: { + string: [], + number: [], + boolean: [], + doc: [], + list: [], + date: [], + enumeration: [], + other: [] + }, + fieldNameMappings: {}, + commonFields: { + appearance: [], + position: [], + size: [], + content: [], + behavior: [], + layout: [] + } + }; + + // Process each field in the metadata + Object.entries(this.fieldMetadata).forEach(([fieldName, fieldInfo]) => { + 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<string, any> = { + name: fieldName, + displayName: strippedName, + description: fieldInfo.description || '', + type: fieldInfo.fieldType || 'unknown', + possibleValues: fieldInfo.values || [], + }; + + // Add field to fields collection + result.fields[fieldName] = fieldData; + + // Categorize by field type + const type = fieldInfo.fieldType?.toLowerCase() || 'unknown'; + if (type === 'string') { + result.fieldsByType.string.push(fieldName); + } else if (type === 'number') { + result.fieldsByType.number.push(fieldName); + } else if (type === 'boolean') { + result.fieldsByType.boolean.push(fieldName); + } else if (type === 'doc') { + result.fieldsByType.doc.push(fieldName); + } else if (type === 'list') { + result.fieldsByType.list.push(fieldName); + } else if (type === 'date') { + result.fieldsByType.date.push(fieldName); + } else if (type === 'enumeration') { + result.fieldsByType.enumeration.push(fieldName); + } else { + result.fieldsByType.other.push(fieldName); + } + + // Categorize by field purpose + if (fieldName.includes('width') || fieldName.includes('height') || fieldName.includes('size')) { + result.commonFields.size.push(fieldName); + } else if (fieldName.includes('color') || fieldName.includes('background') || fieldName.includes('border')) { + result.commonFields.appearance.push(fieldName); + } else if (fieldName.includes('x') || fieldName.includes('y') || fieldName.includes('position') || fieldName.includes('pan')) { + result.commonFields.position.push(fieldName); + } else if (fieldName.includes('text') || fieldName.includes('title') || fieldName.includes('data')) { + result.commonFields.content.push(fieldName); + } else if (fieldName.includes('action') || fieldName.includes('click') || fieldName.includes('event')) { + result.commonFields.behavior.push(fieldName); + } else if (fieldName.includes('layout')) { + result.commonFields.layout.push(fieldName); + } + }); + + // Add special section for auto-sizing related fields + result.autoSizingFields = { + height: { + autoHeightField: '_layout_autoHeight', + heightField: '_height', + displayName: 'height', + usage: 'To manually set height, first set layout_autoHeight to false' + }, + width: { + autoWidthField: '_layout_autoWidth', + widthField: '_width', + displayName: 'width', + usage: 'To manually set width, first set layout_autoWidth to false' + } + }; + + return result; + } + + /** * Executes the document metadata tool * @param args The arguments for the tool * @returns An observation with the results of the tool execution @@ -708,10 +856,10 @@ export class DocumentMetadataTool extends BaseTool<DocumentMetadataToolParamsTyp // Ensure the action is valid and convert to string const action = String(args.action); - if (!['get', 'edit', 'list'].includes(action)) { + if (!['get', 'edit', 'list', 'getFieldOptions'].includes(action)) { return [{ type: 'text', - text: 'Error: Invalid action. Valid actions are "get", "edit", or "list".' + text: 'Error: Invalid action. Valid actions are "get", "edit", "list", or "getFieldOptions".' }]; } @@ -732,10 +880,10 @@ export class DocumentMetadataTool extends BaseTool<DocumentMetadataToolParamsTyp case 'edit': { // Edit a specific field on a document - if (!documentId || !args.fieldName) { + if (!documentId) { return [{ type: 'text', - text: 'Error: Document ID, field name, and field value are required for edit actions.' + text: 'Error: Document ID is required for edit actions.' }]; } @@ -747,42 +895,147 @@ export class DocumentMetadataTool extends BaseTool<DocumentMetadataToolParamsTyp }]; } - // 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}`; + // 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.' + }]; + } + + // 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.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.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)}` + }]; + } + } else { + // Single field edit (original behavior) + if (!args.fieldName) { + return [{ + type: 'text', + text: 'Error: Field name and field value are required for edit actions.' + }]; + } + + // 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)}` + }]; } - - // Get the updated metadata to return - const updatedMetadata = this.getDocumentMetadata(documentId); - - return [{ - type: 'text', - text: `${responseText}\nUpdated metadata:\n${JSON.stringify(updatedMetadata, null, 2)}` - }]; } case 'list': { @@ -806,10 +1059,20 @@ export class DocumentMetadataTool extends BaseTool<DocumentMetadataToolParamsTyp }]; } + 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)}` + }]; + } + default: return [{ type: 'text', - text: 'Error: Unknown action. Valid actions are "get", "edit", or "list".' + text: 'Error: Unknown action. Valid actions are "get", "edit", "list", or "getFieldOptions".' }]; } } catch (error) { @@ -835,10 +1098,43 @@ export class DocumentMetadataTool extends BaseTool<DocumentMetadataToolParamsTyp return false; } - // For edit action, documentId, fieldName, and fieldValue are required + // For edit action, validate either single field edit or multiple field edits if (params.action === 'edit') { - if (!params.documentId || !params.fieldName || params.fieldValue === undefined) { - return false; + // If fieldEdits is provided, it must be valid and we'll ignore fieldName/fieldValue + if (params.fieldEdits) { + 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 !!params.documentId; // Just ensure documentId is provided + } catch (error) { + console.log('Error parsing fieldEdits:', error); + return false; + } + } else { + // Traditional single field edit + if (!params.documentId || !params.fieldName || params.fieldValue === undefined) { + return false; + } } } @@ -847,6 +1143,11 @@ export class DocumentMetadataTool extends BaseTool<DocumentMetadataToolParamsTyp return false; } + // getFieldOptions action doesn't require any additional parameters + if (params.action === 'getFieldOptions') { + 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') { @@ -872,18 +1173,20 @@ export class DocumentMetadataTool extends BaseTool<DocumentMetadataToolParamsTyp */ private getParameterRequirementsByAction(action?: string): string { if (!action) { - return 'Please specify an action: "get", "edit", or "list".'; + return 'Please specify an action: "get", "edit", "list", or "getFieldOptions".'; } 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.'; + return 'The "edit" action requires documentId, fieldName, and fieldValue parameters, or documentId and fieldEdits parameters for multi-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.'; default: - return `Unknown action "${action}". Valid actions are "get", "edit", or "list".`; + return `Unknown action "${action}". Valid actions are "get", "edit", "list", or "getFieldOptions".`; } } |