aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--src/client/views/nodes/chatbot/agentsystem/Agent.ts32
-rw-r--r--src/client/views/nodes/chatbot/tools/DocumentMetadataTool.ts421
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".`;
}
}