diff options
-rw-r--r-- | src/client/views/nodes/chatbot/agentsystem/Agent.ts | 4 | ||||
-rw-r--r-- | src/client/views/nodes/chatbot/tools/SortDocsTool.ts | 67 | ||||
-rw-r--r-- | src/client/views/nodes/chatbot/tools/TagDocsTool.ts | 105 | ||||
-rw-r--r-- | src/client/views/pdf/Annotation.tsx | 4 |
4 files changed, 178 insertions, 2 deletions
diff --git a/src/client/views/nodes/chatbot/agentsystem/Agent.ts b/src/client/views/nodes/chatbot/agentsystem/Agent.ts index a16794e10..3acdc6aa8 100644 --- a/src/client/views/nodes/chatbot/agentsystem/Agent.ts +++ b/src/client/views/nodes/chatbot/agentsystem/Agent.ts @@ -27,6 +27,8 @@ import { CodebaseSummarySearchTool } from '../tools/CodebaseSummarySearchTool'; import { FileContentTool } from '../tools/FileContentTool'; import { FileNamesTool } from '../tools/FileNamesTool'; import { CreateNewTool } from '../tools/CreateNewTool'; +import { SortDocsTool} from '../tools/SortDocsTool'; +import { TagDocsTool } from '../tools/TagDocsTool'; import { GPTTutorialTool } from '../tools/TutorialTool'; dotenv.config(); @@ -104,6 +106,8 @@ export class Agent { fileContent: new FileContentTool(this.vectorstore), fileNames: new FileNamesTool(this.vectorstore), generateTutorialNode: new GPTTutorialTool(this._docManager), + sortDocs: new SortDocsTool(this._docManager), + tagDocs: new TagDocsTool(this._docManager), }; // Add the createNewTool after other tools are defined diff --git a/src/client/views/nodes/chatbot/tools/SortDocsTool.ts b/src/client/views/nodes/chatbot/tools/SortDocsTool.ts new file mode 100644 index 000000000..741a8f3ce --- /dev/null +++ b/src/client/views/nodes/chatbot/tools/SortDocsTool.ts @@ -0,0 +1,67 @@ +import { BaseTool } from './BaseTool'; +import { Observation } from '../types/types'; +import { ParametersType, ToolInfo } from '../types/tool_types'; +import { AgentDocumentManager } from '../utils/AgentDocumentManager'; +import { gptAPICall, GPTCallType } from '../../../../apis/gpt/GPT'; +import { ChatSortField } from '../../../collections/CollectionSubView'; +import { v4 as uuidv4 } from 'uuid'; + +const parameterRules = [ + { + name: 'sortCriteria', + type: 'string', + description: 'Criteria provided by the user to sort the documents.', + required: true, + }, +] as const; + +const toolInfo: ToolInfo<typeof parameterRules> = { + name: 'sortDocs', + description: + 'Sorts documents within the current Dash environment based on user-specified criteria. Provide clear sorting criteria, such as by date, title, relevance, or custom metadata fields.', + parameterRules, + citationRules: 'No citation needed for sorting operations.', +}; + +export class SortDocsTool extends BaseTool<typeof parameterRules> { + private _docManager: AgentDocumentManager; + + constructor(docManager: AgentDocumentManager) { + super(toolInfo); + this._docManager = docManager; + this._docManager.initializeFindDocsFreeform(); + } + + async execute(args: ParametersType<typeof parameterRules>): Promise<Observation[]> { + const chunkId = uuidv4(); + try { + const docs = this._docManager.docIds.map((id) => this._docManager.extractDocumentMetadata(id)); + + const descriptions = docs + .filter((doc): doc is NonNullable<typeof doc> => doc !== null) + .map( + (doc) => `${doc.id}: ${doc.title} - ${doc.fields.layout.summary || ''}` + ) + .join('\n'); + + const sortedIdsResponse = await gptAPICall(args.sortCriteria, GPTCallType.SORTDOCS, descriptions); + const sortedIds = sortedIdsResponse.trim().split('\n'); + console.log(sortedIdsResponse); + console.log(sortedIds); + + sortedIds.forEach((id, index) => { + this._docManager.editDocumentField(id, ChatSortField, index); + }); + + return [{ + type: 'text', + text: `<chunk chunk_id="${chunkId}" chunk_type="sort_status">Successfully sorted ${sortedIds.length} documents based on "${args.sortCriteria}".</chunk>`, + }]; + } catch (error) { + return [{ + type: 'text', + text: `<chunk chunk_id="${chunkId}" chunk_type="error">Sorting failed: ${error instanceof Error ? error.message : String(error)}</chunk>`, + }]; + } + } +} diff --git a/src/client/views/nodes/chatbot/tools/TagDocsTool.ts b/src/client/views/nodes/chatbot/tools/TagDocsTool.ts new file mode 100644 index 000000000..6b4693279 --- /dev/null +++ b/src/client/views/nodes/chatbot/tools/TagDocsTool.ts @@ -0,0 +1,105 @@ +import { BaseTool } from './BaseTool'; +import { Observation } from '../types/types'; +import { ParametersType, ToolInfo } from '../types/tool_types'; +import { AgentDocumentManager } from '../utils/AgentDocumentManager'; +import { gptAPICall, GPTCallType, DescriptionSeperator, DataSeperator } from '../../../../apis/gpt/GPT'; +import { v4 as uuidv4 } from 'uuid'; +import { TagItem } from '../../../TagsView'; +const parameterRules = [ + { + name: 'taggingCriteria', + type: 'string', + description: 'Natural-language criteria for tagging documents.', + required: true, + }, +] as const; + +const toolInfo: ToolInfo<typeof parameterRules> = { + name: 'tagDocs', + description: 'Automatically generate and apply tags to docs based on criteria.', + parameterRules, + citationRules: 'No citation needed for tagging operations.', +}; + +export class TagDocsTool extends BaseTool<typeof parameterRules> { + private _docManager: AgentDocumentManager; + + constructor(docManager: AgentDocumentManager) { + super(toolInfo); + this._docManager = docManager; + // make sure manager has scanned all docs + this._docManager.initializeFindDocsFreeform(); + } +async execute(args: ParametersType<typeof parameterRules>): Promise<Observation[]> { + const chunkId = uuidv4(); + try { + // Build a single string of all docs in the EXACT same format as GPTPopup does: + const descriptions = this._docManager.docIds + .map(id => this._docManager.extractDocumentMetadata(id)) + .filter(m => m !== null) + .map(m => `${m!.id}${DataSeperator}${m!.title}`) + .map(str => `${DescriptionSeperator}${str}${DescriptionSeperator}`) + .join(''); + + // Call GPT + const raw = await gptAPICall( + args.taggingCriteria, + GPTCallType.TAGDOCS, + descriptions + ); + + // Prepare to collect what we actually applied + const appliedTags: Record<string, string[]> = {}; + + // Parse and apply tags exactly like GPTPopup + raw + .split(DescriptionSeperator) // 1) break into blocks at "======" + .filter(block => block.trim() !== '') // 2) drop empty + .map(block => block.replace(/\n/g, ' ').trim()) // 3) flatten & trim + .map(block => { + const [idPart = '', tagsPart = ''] = block.split(DataSeperator); + return { id: idPart.trim(), tags: tagsPart.trim() }; + }) + .filter(({ id, tags }) => id && tags) // 4) valid pairs only + .forEach(({ id, tags }) => { + const doc = this._docManager.getDocument(id); + if (!doc) return; + + // Split tags, normalize, then apply + const normalized = tags + .split(',') + .map(t => t.trim()) + .filter(t => t.length > 0) + .map(t => (t.startsWith('#') ? t : `#${t}`)); + + normalized.forEach(tag => TagItem.addTagToDoc(doc, tag)); + + // Record for our summary + appliedTags[id] = normalized; + }); + + // Build single observation with summary + const summary = Object.entries(appliedTags) + .map(([id, tags]) => `${id}: ${tags.join(', ')}`) + .join('; '); + + return [ + { + type: 'text', + text: `<chunk chunk_id="${chunkId}" chunk_type="tagging_status"> +Successfully tagged documents based on "${args.taggingCriteria}". Tags applied: ${summary} +</chunk>`, + }, + ]; + } catch (e) { + return [ + { + type: 'text', + text: `<chunk chunk_id="${chunkId}" chunk_type="error"> +Tagging failed: ${e instanceof Error ? e.message : String(e)} +</chunk>`, + }, + ]; + } + } +}
\ No newline at end of file diff --git a/src/client/views/pdf/Annotation.tsx b/src/client/views/pdf/Annotation.tsx index e8a5235c9..69dda89cb 100644 --- a/src/client/views/pdf/Annotation.tsx +++ b/src/client/views/pdf/Annotation.tsx @@ -119,9 +119,9 @@ export class Annotation extends ObservableReactComponent<IAnnotationProps> { {StrListCast(this._props.annoDoc.text_inlineAnnotations) .map(a => a.split?.(':')) .filter(fields => fields) - .map(([x, y, width, height]) => ( + .map(([x, y, width, height], i) => ( <div - key={'' + x + y + width + height} + key={'' + x + y + width + height + i} style={{ pointerEvents: this._props.pointerEvents?.() as Property.PointerEvents }} onPointerDown={this.onPointerDown} onContextMenu={this.onContextMenu} |