aboutsummaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
Diffstat (limited to 'src')
-rw-r--r--src/client/views/nodes/chatbot/agentsystem/Agent.ts4
-rw-r--r--src/client/views/nodes/chatbot/tools/SortDocsTool.ts67
-rw-r--r--src/client/views/nodes/chatbot/tools/TagDocsTool.ts105
-rw-r--r--src/client/views/pdf/Annotation.tsx4
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}