diff options
| author | Joanne <zehan_ding@brown.edu> | 2025-06-17 13:02:50 -0400 |
|---|---|---|
| committer | Joanne <zehan_ding@brown.edu> | 2025-06-17 13:02:50 -0400 |
| commit | 2aa2c26b95a539d220e46b20cdfbef6ae39d6c43 (patch) | |
| tree | 344a6f798f692fdd4921ab5a6762e907f5ad7b06 /src/client/views/nodes/chatbot/tools/DocumentMetadataTool.ts | |
| parent | 430db63077868fa54829721d6530a810aa4d4588 (diff) | |
| parent | ccfdf905400cd4b81d8cde0f16bb0e15cd65621b (diff) | |
Merge branch 'agent-paper-main' of https://github.com/brown-dash/Dash-Web into joanne-tutorialagent
Diffstat (limited to 'src/client/views/nodes/chatbot/tools/DocumentMetadataTool.ts')
| -rw-r--r-- | src/client/views/nodes/chatbot/tools/DocumentMetadataTool.ts | 853 |
1 files changed, 853 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..a55f901e1 --- /dev/null +++ b/src/client/views/nodes/chatbot/tools/DocumentMetadataTool.ts @@ -0,0 +1,853 @@ +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, "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 "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) +- 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 } +]}} + + +Here is a detailed example: + +<interaction description="Using DocumentMetadataTool to create and align a diagram with its notes"> + <stage number="1" role="user"> + <query>Create a flow-chart of neurotransmitter release at a synapse, and place a notes box directly underneath that explains each step.</query> + </stage> + + <!-- CREATE THE DIAGRAM --> + <stage number="2" role="assistant"> + <thought> + I will create a DiagramBox containing a Mermaid flow-chart. After that, I'll make a notes document, read the diagram’s metadata, and then move the notes so it lines up beneath the chart. + </thought> + <action>documentMetadata</action> + </stage> + + <stage number="3" role="user"> + <action_rules>***Action rules omitted***</action_rules> + </stage> + + <stage number="4" role="assistant"> + <action_input> + <action_input_description>Creating a DiagramBox with a Mermaid chart of neurotransmitter release.</action_input_description> + <inputs> + <action>create</action> + <title>Synaptic Release Flowchart</title> + <data>graph TD; A(Vesicle docks)-->B(Ca²⁺ influx); B-->C(Vesicle fuses); C-->D(Neurotransmitter released)</data> + <doc_type>diagram</doc_type> + </inputs> + </action_input> + </stage> + + <stage number="5" role="user"> + <observation> + <chunk chunk_id="9f2c43c8-0a16-4c12-987b-b813d2f7b1ee" chunk_type="text"> + Document created successfully. + Document ID: 9f2c43c8-0a16-4c12-987b-b813d2f7b1ee + Type: diagram + Title: "Synaptic Release Flowchart" + + The document has been created with default dimensions and positioning. + … + Full metadata for the created document: + { /* huge JSON omitted for brevity */ } + </chunk> + </observation> + </stage> + + <!-- CREATE THE NOTES DOC --> + <stage number="6" role="assistant"> + <thought> + I will create a plain-text notes document that summarises the diagram’s steps. + </thought> + <action>documentMetadata</action> + </stage> + + <stage number="7" role="user"> + <action_rules>***Action rules omitted***</action_rules> + </stage> + + <stage number="8" role="assistant"> + <action_input> + <action_input_description>Creating a text document for explanatory notes.</action_input_description> + <inputs> + <action>create</action> + <title>Synaptic Release – Notes</title> + <data>This diagram shows vesicle docking, calcium-triggered fusion, and neurotransmitter release into the synaptic cleft.</data> + <doc_type>note</doc_type> + </inputs> + </action_input> + </stage> + + <stage number="9" role="user"> + <observation> + <chunk chunk_id="c8e3f4b9-7d2e-462a-a444-61e8e9ff0c7f" chunk_type="text"> + Document created successfully. + Document ID: c8e3f4b9-7d2e-462a-a444-61e8e9ff0c7f + Type: note + Title: "Synaptic Release – Notes" + … + Full metadata for the created document: + { /* huge JSON omitted for brevity */ } + </chunk> + </observation> + </stage> + + <!-- READ DIAGRAM METADATA --> + <stage number="10" role="assistant"> + <thought> + I'll fetch the diagram’s width, height, and position so I can align the notes box underneath it. + </thought> + <action>documentMetadata</action> + </stage> + + <stage number="11" role="user"> + <action_rules>***Action rules omitted***</action_rules> + </stage> + + <stage number="12" role="assistant"> + <action_input> + <action_input_description>Getting metadata for the diagram document.</action_input_description> + <inputs> + <action>get</action> + <documentId>9f2c43c8-0a16-4c12-987b-b813d2f7b1ee</documentId> + </inputs> + </action_input> + </stage> + + <stage number="13" role="user"> + <observation> + <chunk chunk_id="9f2c43c8-0a16-4c12-987b-b813d2f7b1ee" chunk_type="text"> + { + "x": 100, + "y": 100, + "width": 600, + "height": 300, + "layout_autoWidth": false, + "layout_autoHeight": false + } + </chunk> + </observation> + </stage> + + <!-- MOVE & SIZE THE NOTES DOC --> + <stage number="14" role="assistant"> + <thought> + The diagram ends at y = 100 + 300 = 400. + I’ll move the notes to x = 100, y = 420 (a 20-px gap) and expand its width to 600 px. + </thought> + <action>documentMetadata</action> + </stage> + + <stage number="15" role="user"> + <action_rules>***Action rules omitted***</action_rules> + </stage> + + <stage number="16" role="assistant"> + <action_input> + <action_input_description>Editing the notes document’s position and size.</action_input_description> + <inputs> + <action>edit</action> + <documentId>c8e3f4b9-7d2e-462a-a444-61e8e9ff0c7f</documentId> + <fieldEdits>[ + {"fieldName":"layout_autoWidth","fieldValue":false}, + {"fieldName":"width","fieldValue":600}, + {"fieldName":"x","fieldValue":100}, + {"fieldName":"y","fieldValue":420} + ]</fieldEdits> + </inputs> + </action_input> + </stage> + + <stage number="17" role="user"> + <observation> + <chunk chunk_id="c8e3f4b9-7d2e-462a-a444-61e8e9ff0c7f" chunk_type="text"> + Successfully edited 4 fields on document c8e3f4b9-7d2e-462a-a444-61e8e9ff0c7f. + New position: (x:100, y:420) Width:600 + </chunk> + </observation> + </stage> + + <!-- FINAL ANSWER --> + <stage number="18" role="assistant"> + <thought> + Both documents are now created and perfectly aligned; I can summarise the result. + </thought> + <answer> + <grounded_text citation_index="1"> + A **DiagramBox** depicting neurotransmitter release was created at **(100 px, 100 px)**, sized **600 × 300 px**. + </grounded_text> + <grounded_text citation_index="2"> + A **notes document** was then resized to **600 px** wide and moved to **(100 px, 420 px)**—20 px below the diagram—to keep the explanation directly beneath the visualization. + </grounded_text> + <normal_text> + This layout ensures viewers can read the synopsis while referring to the flow-chart above. + </normal_text> + <citations> + <citation index="1" chunk_id="9f2c43c8-0a16-4c12-987b-b813d2f7b1ee" type="text"></citation> + <citation index="2" chunk_id="c8e3f4b9-7d2e-462a-a444-61e8e9ff0c7f" type="text"></citation> + </citations> + <follow_up_questions> + <question>Would you like to tweak the diagram’s styling (e.g., colours or fonts)?</question> + <question>Should I link external references or papers in the notes?</question> + <question>Do you want similar diagrams for other neural processes?</question> + </follow_up_questions> + <loop_summary> + The assistant used **DocumentMetadataTool** four times: + 1) **create** DiagramBox → 2) **create** notes document → 3) **get** diagram metadata → 4) **edit** notes position/size. + This demonstrates creating, inspecting, and aligning documents within a Freeform view. + </loop_summary> + </answer> + </stage> +</interaction> + +<MermaidMindmapGuide> + <Overview> + <Description> + Mermaid mindmaps are hierarchical diagrams used to visually organize ideas. Nodes are created using indentation to show parent-child relationships. + </Description> + <Note>This is an experimental feature in Mermaid and may change in future versions.</Note> + </Overview> + + <BasicSyntax> + <CodeExample language="mermaid"> + <![CDATA[ + mindmap + Root + Branch A + Leaf A1 + Leaf A2 + Branch B + Leaf B1 + ]]> + </CodeExample> + <Explanation> + <Point><code>mindmap</code> declares the diagram.</Point> + <Point>Indentation determines the hierarchy.</Point> + <Point>Each level must be indented more than its parent.</Point> + </Explanation> + </BasicSyntax> + + <NodeShapes> + <Description>Nodes can be styled with various shapes similar to flowchart syntax.</Description> + <Shapes> + <Shape name="Square"><Code>id[Square Text]</Code></Shape> + <Shape name="Rounded Square"><Code>id(Rounded Square)</Code></Shape> + <Shape name="Circle"><Code>id((Circle))</Code></Shape> + <Shape name="Bang"><Code>id))Bang((</Code></Shape> + <Shape name="Cloud"><Code>id)Cloud(</Code></Shape> + <Shape name="Hexagon"><Code>id{{Hexagon}}</Code></Shape> + <Shape name="Default"><Code>Default shape without any brackets</Code></Shape> + </Shapes> + </NodeShapes> + + <Icons> + <Description>Nodes can include icons using the <code>::icon(class)</code> syntax.</Description> + <CodeExample> + <![CDATA[ + mindmap + Root + Node A + ::icon(fa fa-book) + Node B + ::icon(mdi mdi-lightbulb) + ]]> + </CodeExample> + <Note>Icon fonts must be included by the site administrator for proper rendering.</Note> + </Icons> + + <CSSClasses> + <Description>Add custom styling classes using <code>:::</code>.</Description> + <CodeExample> + <![CDATA[ + mindmap + Root + Important Node + :::urgent large + Regular Node + ]]> + </CodeExample> + <Note>Classes must be defined in your website or application CSS.</Note> + </CSSClasses> + + <MarkdownSupport> + <Description>Supports markdown-style strings for rich text, line breaks, and auto-wrapping.</Description> + <CodeExample> + <![CDATA[ + mindmap + id1["**Bold Root** with new line"] + id2["*Italicized* and long text that wraps"] + id3[Plain label] + ]]> + </CodeExample> + </MarkdownSupport> + + <RenderingNote> + <Note>Indentation is relative, not absolute — Mermaid will infer hierarchy based on surrounding context even with inconsistent spacing.</Note> + </RenderingNote> + + <Integration> + <Description> + From Mermaid v11, mindmaps are included natively. For older versions, use external imports with lazy loading. + </Description> + <CodeExample> + <![CDATA[ + <script type="module"> + import mermaid from 'https://cdn.jsdelivr.net/npm/mermaid@11/dist/mermaid.esm.min.mjs'; + </script> + ]]> + </CodeExample> + </Integration> +</MermaidMindmapGuide> + +`; + +// 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: +0. Understand the currently available documents that were provided as <available_documents> in the prompt +1. 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 +0. Understand the state of the currently available documents and their metadata using action="get" (this includes spacial positioning). +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 + +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 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. +- IMPORTANT: WHEN CITING A DOCUMENT, MAKE THE CHUNK ID THE DOCUMENT ID. WHENEVER YOU CITE A DOCUMENT, ALWAYS MAKE THE CITATION TYPE "text", THE "direct_text" FIELD BLANK, AND THE "chunk_id" FIELD THE DOCUMENT ID.`; +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', 'getFieldOptions', 'create'].includes(action)) { + return [ + { + type: 'text', + text: 'Error: Invalid action. Valid actions are "get", "edit", "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 '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 = await 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", "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; + } + + 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", "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 '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", "getFieldOptions", or "create".`; + } + } +} |
