From 6e646b38523c84f0285863865e2c929870105e2a Mon Sep 17 00:00:00 2001 From: "A.J. Shulman" Date: Thu, 3 Apr 2025 12:30:58 -0400 Subject: better editing of texts --- .../views/nodes/chatbot/agentsystem/Agent.ts | 32 +- .../nodes/chatbot/tools/DocumentMetadataTool.ts | 421 ++++++++++++++++++--- 2 files changed, 390 insertions(+), 63 deletions(-) (limited to 'src') 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 = [ 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 = { name: 'documentMetadata', @@ -686,6 +728,112 @@ export class DocumentMetadataTool extends BaseTool = { + 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 = { + 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 @@ -708,10 +856,10 @@ export class DocumentMetadataTool extends BaseTool { + 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