aboutsummaryrefslogtreecommitdiff
path: root/src/client/views/nodes/chatbot/tools/DocumentMetadataTool.ts
diff options
context:
space:
mode:
Diffstat (limited to 'src/client/views/nodes/chatbot/tools/DocumentMetadataTool.ts')
-rw-r--r--src/client/views/nodes/chatbot/tools/DocumentMetadataTool.ts573
1 files changed, 573 insertions, 0 deletions
diff --git a/src/client/views/nodes/chatbot/tools/DocumentMetadataTool.ts b/src/client/views/nodes/chatbot/tools/DocumentMetadataTool.ts
new file mode 100644
index 000000000..e6c2421e5
--- /dev/null
+++ b/src/client/views/nodes/chatbot/tools/DocumentMetadataTool.ts
@@ -0,0 +1,573 @@
+import { Doc, FieldType } from '../../../../../fields/Doc';
+import { DocData } from '../../../../../fields/DocSymbols';
+import { Observation } from '../types/types';
+import { ParametersType, ToolInfo, Parameter } from '../types/tool_types';
+import { BaseTool } from './BaseTool';
+import { DocumentOptions } from '../../../../documents/Documents';
+import { CollectionFreeFormDocumentView } from '../../../nodes/CollectionFreeFormDocumentView';
+import { v4 as uuidv4 } from 'uuid';
+import { LinkManager } from '../../../../util/LinkManager';
+import { DocCast, StrCast } from '../../../../../fields/Types';
+import { supportedDocTypes } from '../types/tool_types';
+import { parsedDoc } from '../chatboxcomponents/ChatBox';
+import { AgentDocumentManager } from '../utils/AgentDocumentManager';
+
+// Define the parameters for the DocumentMetadataTool
+const parameterDefinitions: ReadonlyArray<Parameter> = [
+ {
+ name: 'action',
+ type: 'string',
+ required: true,
+ description: 'The action to perform: "get" to retrieve metadata, "edit" to modify metadata, "list" to enumerate documents, "getFieldOptions" to retrieve all available field options, or "create" to create a new document',
+ },
+ {
+ 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", "getFieldOptions", and "create"',
+ },
+ {
+ name: 'fieldEdits',
+ type: 'string',
+ required: false,
+ description:
+ 'JSON array of field edits for editing fields. Each item should have fieldName and fieldValue. For single field edits, use an array with one item. Example: [{"fieldName":"layout_autoHeight","fieldValue":false},{"fieldName":"height","fieldValue":300}]',
+ },
+ {
+ name: 'title',
+ type: 'string',
+ required: false,
+ description: 'The title of the document to create. Required for "create" action',
+ },
+ {
+ name: 'data',
+ type: 'string',
+ required: false,
+ description: 'The data content for the document to create. Required for "create" action',
+ },
+ {
+ name: 'doc_type',
+ 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;
+
+// Detailed description with usage guidelines for the DocumentMetadataTool
+const toolDescription = `Extracts and modifies metadata from documents in the same Freeform view as the ChatBox, and can create new documents.
+This tool helps you work with document properties, understand available fields, edit document metadata, and create new documents.
+
+The Dash document system organizes fields in two locations:
+1. Layout documents: contain visual properties like position, dimensions, and appearance
+2. Data documents: contain the actual content and document-specific data
+
+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
+- Create new documents with basic properties
+
+DOCUMENT CREATION:
+- Use action="create" to create new documents with a simplified approach
+- Required parameters: title, data, and doc_type
+- The tool will create the document with sensible defaults and link it to the current view
+- After creation, you can use the edit action to update its properties
+
+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
+- All edits are done using the fieldEdits parameter which accepts an array of fields to modify
+
+Example: To change document height, disable auto-height and set height in a single operation:
+{... 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:
+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 CREATE a new document:
+- Use action="create" with the following required parameters:
+ - 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: {...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:
+- Use action="edit" with required parameters:
+ - documentId: The ID of the document to edit
+ - fieldEdits: JSON array of fields to edit, each with fieldName and fieldValue
+- 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
+
+SPECIAL FIELD HANDLING:
+- Text fields: When editing the 'text' field, provide simple plain text
+ Example: {...inputs: { action: "edit", documentId: "doc123", fieldEdits: [{ 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
+
+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
+OR
+1. Create a new document with action="create"
+2. Get its ID from the response
+3. Edit the document's properties with action="edit"
+
+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:
+ {...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)
+ - 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. 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 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 single field: { action: "edit", documentId: "doc123", fieldEdits: [{ fieldName: "backgroundColor", fieldValue: "#ff0000" }] }
+- To edit a width property: { action: "edit", documentId: "doc123", fieldEdits: [{ fieldName: "width", fieldValue: 300 }] }
+- To edit text content: { action: "edit", documentId: "doc123", fieldEdits: [{ fieldName: "text", fieldValue: "Simple plain text goes here" }] }
+- To disable auto-height: { action: "edit", documentId: "doc123", fieldEdits: [{ fieldName: "layout_autoHeight", fieldValue: false }] }
+- To create a text document: { action: "create", title: "My Notes", data: "This is my note content", doc_type: "text" }
+- To create a web document: { action: "create", title: "Google", data: "https://www.google.com", doc_type: "web" }
+- To edit height with its dependent field together:
+ { 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<DocumentMetadataToolParamsType> = {
+ name: 'documentMetadata',
+ description: toolDescription,
+ parameterRules: parameterDefinitions,
+ citationRules: citationRules,
+};
+
+/**
+ * A tool for extracting and modifying metadata from documents in a Freeform view.
+ * This tool collects metadata from both layout and data documents in a Freeform view
+ * and allows for editing document fields in the correct location.
+ */
+export class DocumentMetadataTool extends BaseTool<DocumentMetadataToolParamsType> {
+ private _docManager: AgentDocumentManager;
+
+ constructor(docManager: AgentDocumentManager) {
+ super(documentMetadataToolInfo);
+ this._docManager = docManager;
+ this._docManager.initializeFindDocsFreeform();
+ }
+
+ /**
+ * 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);
+
+ // Find all documents in the Freeform view
+ this._docManager.initializeFindDocsFreeform();
+
+ 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))}`,
+ },
+ ];
+ }
+
+ // 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".',
+ },
+ ];
+ }
+
+ // 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._docManager.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)}`,
+ },
+ ];
+ }
+
+ case 'edit': {
+ // Edit a specific field on a document
+ if (!documentId) {
+ return [
+ {
+ type: 'text',
+ text: 'Error: Document ID is required for edit actions.',
+ },
+ ];
+ }
+
+ // Ensure document exists
+ if (!this._docManager.has(documentId)) {
+ return [
+ {
+ type: 'text',
+ text: `Error: Document with ID ${documentId} not found.`,
+ },
+ ];
+ }
+
+ // Check for fieldEdits parameter
+ if (!args.fieldEdits) {
+ return [
+ {
+ type: 'text',
+ text: 'Error: fieldEdits is required for edit actions. Please provide a JSON array of field edits.',
+ },
+ ];
+ }
+
+ 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._docManager.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._docManager.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)}`,
+ },
+ ];
+ }
+ }
+
+ case 'list': {
+ this._docManager.listDocs();
+ }
+
+ case 'getFieldOptions': {
+ // Get all available field options with metadata
+ const fieldOptions = this._docManager.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)}`,
+ },
+ ];
+ }
+
+ 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.',
+ },
+ ];
+ }
+
+ const docType = String(args.doc_type);
+ const title = String(args.title);
+ const data = String(args.data);
+
+ const id = this._docManager.createDocInDash(docType, data, { title: title });
+
+ if (!id) {
+ return [
+ {
+ type: 'text',
+ text: 'Error: Failed to create document.',
+ },
+ ];
+ }
+ // Get the created document's metadata
+ const createdMetadata = this._docManager.extractDocumentMetadata(id);
+
+ return [
+ {
+ type: 'text',
+ text: `Document created successfully.
+Document ID: ${id}
+Type: ${docType}
+Title: "${title}"
+
+The document has been created with default dimensions and positioning.
+You can now use the "edit" action to modify additional properties of this document.
+
+Next steps:
+1. Use the "getFieldOptions" action to understand available editable/addable fields/properties and their dependencies.
+2. To modify this document, use: { action: "edit", documentId: "${id}", fieldEdits: [{"fieldName":"property","fieldValue":"value"}] }
+3. To add styling, consider setting backgroundColor, fontColor, or other properties
+4. For text documents, you can edit the content with: { action: "edit", documentId: "${id}", fieldEdits: [{"fieldName":"text","fieldValue":"New content"}] }
+
+Full metadata for the created document:
+${JSON.stringify(createdMetadata, null, 2)}`,
+ },
+ ];
+ }
+
+ default:
+ return [
+ {
+ type: 'text',
+ text: 'Error: Unknown action. Valid actions are "get", "edit", "list", "getFieldOptions", or "create".',
+ },
+ ];
+ }
+ } catch (error) {
+ console.error('DocumentMetadataTool execution error:', error);
+ return [
+ {
+ type: 'text',
+ text: `Error executing DocumentMetadataTool: ${error instanceof Error ? error.message : String(error)}`,
+ },
+ ];
+ }
+ }
+
+ /**
+ * Validates the input parameters for the DocumentMetadataTool
+ * 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
+ */
+ inputValidator(params: ParametersType<DocumentMetadataToolParamsType>): boolean {
+ // Default validation for required fields
+ if (params.action === undefined) {
+ return false;
+ }
+
+ // For create action, validate required parameters
+ if (params.action === 'create') {
+ return !!(params.title && params.data && params.doc_type);
+ }
+
+ // For edit action, validate fieldEdits is provided
+ if (params.action === 'edit') {
+ if (!params.documentId || !params.fieldEdits) {
+ return false;
+ }
+
+ 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 true;
+ } catch (error) {
+ console.log('Error parsing fieldEdits:', error);
+ return false;
+ }
+ }
+
+ // For get action with documentId, documentId is required
+ if (params.action === 'get' && params.documentId === '') {
+ return false;
+ }
+
+ // getFieldOptions action doesn't require any additional parameters
+ if (params.action === 'getFieldOptions') {
+ return true;
+ }
+
+ // list action doesn't require any additional parameters
+ if (params.action === 'list') {
+ 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", "list", "getFieldOptions", or "create".';
+ }
+
+ switch (action.toLowerCase()) {
+ case 'get':
+ return 'The "get" action accepts an optional documentId parameter.';
+ case 'edit':
+ return 'The "edit" action requires documentId and fieldEdits parameters. fieldEdits must be a JSON array of 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.';
+ case 'create':
+ return 'The "create" action requires title, data, and doc_type parameters.';
+ default:
+ return `Unknown action "${action}". Valid actions are "get", "edit", "list", "getFieldOptions", or "create".`;
+ }
+ }
+}