diff options
author | bobzel <zzzman@gmail.com> | 2025-07-24 10:53:45 -0400 |
---|---|---|
committer | bobzel <zzzman@gmail.com> | 2025-07-24 10:53:45 -0400 |
commit | ee129da1ed03a9897cc282e52e0d31dee006983d (patch) | |
tree | 928182fcaee777609576c4eba91ab57ca3002035 | |
parent | 9c7ddca0782a1d8dd1a8ff35759321b867a2b175 (diff) |
making filter sort and tag doc tools work the same way with docs within the collection and with improved formatting for openai.
-rw-r--r-- | src/client/apis/gpt/GPT.ts | 30 | ||||
-rw-r--r-- | src/client/views/nodes/chatbot/agentsystem/Agent.ts | 3 | ||||
-rw-r--r-- | src/client/views/nodes/chatbot/tools/FilterDocsTool.ts | 11 | ||||
-rw-r--r-- | src/client/views/nodes/chatbot/tools/SortDocsTool.ts | 175 | ||||
-rw-r--r-- | src/client/views/nodes/chatbot/tools/TagDocsTool.ts | 231 | ||||
-rw-r--r-- | src/client/views/pdf/GPTPopup/GPTPopup.tsx | 10 |
6 files changed, 268 insertions, 192 deletions
diff --git a/src/client/apis/gpt/GPT.ts b/src/client/apis/gpt/GPT.ts index c2b518178..83ead150b 100644 --- a/src/client/apis/gpt/GPT.ts +++ b/src/client/apis/gpt/GPT.ts @@ -10,6 +10,8 @@ export enum GPTDocCommand { } export const DescriptionSeperator = '======'; +export const DescStart = '<description>'; +export const DescEnd = '</description>'; export const DocSeperator = '------'; export const DataSeperator = '>>>>>>'; export enum TextClassifications { @@ -62,14 +64,14 @@ const callTypeMap: { [type in GPTCallType]: GPTCallOpts } = { sort_docs: { model: 'gpt-4o', maxTokens: 2048, - temp: 0.25, + temp: 0, prompt: `The user is going to give you a list of descriptions. - Each one is separated by '${DescriptionSeperator}' on either side. - Descriptions will vary in length, so make sure to only separate when you see '${DescriptionSeperator}'. - Sort them by the user's specifications. - Make sure each description is only in the list once. Each item should be separated by '${DescriptionSeperator}'. + Every description is enclosed within a ${DescStart} and ${DescEnd} tag. + Based on the question the user asks, sort the given descriptions making sure each description is treated as a single entity no matter what its content. + Do NOT omit any descriptions from the final sorted list. + Return the sorted list of descriptions using the same tag format you received them. Immediately afterward, surrounded by '${DocSeperator}' on BOTH SIDES, provide some insight into your reasoning for the way you sorted (and mention nothing about the formatting details given in this description). - It is VERY important that you format it exactly as described, ensuring the proper number of '${DescriptionSeperator[0]}' and '${DocSeperator[0]}' (${DescriptionSeperator.length} of each) and NO commas`, + It is VERY important that you format it exactly as described, ensuring the proper number (${DocSeperator.length} of each) of '${DocSeperator[0]}' and NO commas`, }, edit: { model: 'gpt-4-turbo', maxTokens: 256, temp: 0.5, prompt: 'Reword the text.' }, @@ -210,10 +212,10 @@ const callTypeMap: { [type in GPTCallType]: GPTCallOpts } = { maxTokens: 1024, temp: 0, prompt: `I'm going to give you a list of descriptions. - Each one is separated by '${DescriptionSeperator}' on either side. - Descriptions will vary in length, so make sure to only separate when you see '${DescriptionSeperator}'. + Every description is enclosed within a ${DescStart} and ${DescEnd} tag. Based on the question/command the user asks, provide a tag label of the given descriptions that best matches the user's specifications. - Format your response precisely as a single string that prints each description without including any '${DescriptionSeperator}', followed by '${DataSeperator}', followed by the tag label, followed by '${DescriptionSeperator}'. + Format you response by returnign each description with its tag label in the following format: + ${DescStart}description${DataSeperator}tag label${DescEnd} Do not use any additional formatting marks or punctuation'. Immediately afterward, surrounded by '${DocSeperator}' on BOTH SIDES, provide some insight into your reasoning for the way you sorted (and mention nothing about the formatting details given in this description). `, @@ -223,12 +225,12 @@ const callTypeMap: { [type in GPTCallType]: GPTCallOpts } = { maxTokens: 1024, temp: 0, prompt: `I'm going to give you a list of descriptions. - Each one is separated by '${DescriptionSeperator}' on either side. - Descriptions will vary in length, so make sure to only separate when you see '${DescriptionSeperator}'. + Every description is enclosed within a ${DescStart} and ${DescEnd} tag. Based on the question the user asks, provide a subset of the given descriptions that best matches the user's specifications. - Make sure each description is only in the list once. Each item should be separated by '${DescriptionSeperator}'. + Make sure each description is only in the list once. + Return the filtered list of descriptions using the same tag format you received them. Immediately afterward, surrounded by '${DocSeperator}' on BOTH SIDES, provide some insight into your reasoning in the 2nd person (and mention nothing about the formatting details given in this description). - It is VERY important that you format it exactly as described, ensuring the proper number of '${DescriptionSeperator[0]}' and '${DocSeperator[0]}' (${DescriptionSeperator.length} of each) and NO commas`, + It is VERY important that you format it exactly as described, ensuring the proper number (${DocSeperator.length}) of '${DocSeperator[0]}' and NO commas`, }, //A description of a Chat Assistant, if present, should always be included in the subset. doc_info: { @@ -263,7 +265,7 @@ const gptAPICall = async (inputTextIn: string, callType: GPTCallType, prompt?: s } if (lastCall === inputText && dontCache !== true && lastResp) return lastResp; try { - const usePrompt = prompt ? prompt + '.' + opts.prompt : opts.prompt; + const usePrompt = prompt ? opts.prompt + '.\n' + prompt : opts.prompt; const messages: ChatCompletionMessageParam[] = [ { role: 'system', content: usePrompt }, { role: 'user', content: inputText }, diff --git a/src/client/views/nodes/chatbot/agentsystem/Agent.ts b/src/client/views/nodes/chatbot/agentsystem/Agent.ts index 0f7738703..cf9b47fca 100644 --- a/src/client/views/nodes/chatbot/agentsystem/Agent.ts +++ b/src/client/views/nodes/chatbot/agentsystem/Agent.ts @@ -114,12 +114,11 @@ export class Agent { fileNames: new FileNamesTool(this.vectorstore), generateTutorialNode: new GPTTutorialTool(this._docManager), sortDocs: new SortDocsTool(this._docManager, this.parentView), - tagDocs: new TagDocsTool(this._docManager), + tagDocs: new TagDocsTool(this._docManager, this.parentView), filterDocs: new FilterDocsTool(this._docManager, this.parentView), takeQuiz: new TakeQuizTool(this._docManager), canvasDocs: new CanvasDocsTool(), uiControl: new UIControlTool(), - }; // Add the createNewTool after other tools are defined diff --git a/src/client/views/nodes/chatbot/tools/FilterDocsTool.ts b/src/client/views/nodes/chatbot/tools/FilterDocsTool.ts index 18e7481f5..730f91005 100644 --- a/src/client/views/nodes/chatbot/tools/FilterDocsTool.ts +++ b/src/client/views/nodes/chatbot/tools/FilterDocsTool.ts @@ -3,12 +3,11 @@ import { BaseTool } from './BaseTool'; import { Observation } from '../types/types'; import { ParametersType, ToolInfo } from '../types/tool_types'; import { AgentDocumentManager } from '../utils/AgentDocumentManager'; -import { gptAPICall, GPTCallType, GPTDocCommand, DescriptionSeperator, DataSeperator, DocSeperator } from '../../../../apis/gpt/GPT'; +import { gptAPICall, GPTCallType, GPTDocCommand, DataSeperator, DocSeperator, DescStart, DescEnd } from '../../../../apis/gpt/GPT'; import { v4 as uuidv4 } from 'uuid'; import { TagItem } from '../../../TagsView'; import { DocumentView } from '../../DocumentView'; import { Doc } from '../../../../../fields/Doc'; -import { computed } from 'mobx'; const parameterRules = [ { @@ -51,7 +50,7 @@ export class FilterDocsTool extends BaseTool<typeof parameterRules> { Doc.getDescription(doc).then(text => { const cleanText = text.replace(/\n/g, ' ').trim(); textToDocMap.set(cleanText, doc); - return `${DescriptionSeperator}${cleanText}${DescriptionSeperator}`; + return `${DescStart}${cleanText}${DescEnd}`; }) ) ).then(docDescriptions => docDescriptions.join('')); @@ -165,7 +164,7 @@ FilterDocsTool: No parent collection document found. Please ensure you're workin const doc = this._docManager.getDocument(id); if (doc) { textToDocMap.set(desc, doc); - blocks.push(`${DescriptionSeperator}${desc}${DescriptionSeperator}`); + blocks.push(`${DescStart}${desc}${DescEnd}`); } } @@ -226,9 +225,9 @@ Please try rephrasing your filter criteria or check if the collection contains v // Parse document descriptions from GPT response const docBlocks = gptResponse - .split(DescriptionSeperator) + .split(DescEnd) .filter(block => block.trim() !== '') - .map(block => block.replace(/\n/g, ' ').trim()); + .map(block => block.replace(DescStart, '').replace(/\n/g, ' ').trim()); console.log(`[FilterDocsTool] Found ${docBlocks.length} document blocks in GPT response`); diff --git a/src/client/views/nodes/chatbot/tools/SortDocsTool.ts b/src/client/views/nodes/chatbot/tools/SortDocsTool.ts index 9eb0cf4d1..a1f43a8f2 100644 --- a/src/client/views/nodes/chatbot/tools/SortDocsTool.ts +++ b/src/client/views/nodes/chatbot/tools/SortDocsTool.ts @@ -2,97 +2,138 @@ 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 } from '../../../../apis/gpt/GPT'; +import { gptAPICall, GPTCallType, DescStart, DescEnd } from '../../../../apis/gpt/GPT'; import { ChatSortField } from '../../../collections/CollectionSubView'; import { v4 as uuidv4 } from 'uuid'; import { DocumentView } from '../../DocumentView'; import { docSortings } from '../../../collections/CollectionSubView'; - +import { Doc } from '../../../../../fields/Doc'; const parameterRules = [ - { - name: 'sortCriteria', - type: 'string', - description: 'Criteria provided by the user to sort the documents.', - required: true, - }, + { + 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.', - parameterRules, - citationRules: 'No citation needed for sorting operations.', + name: 'sortDocs', + description: 'Sorts documents within the current Dash environment based on user-specified criteria.', + parameterRules, + citationRules: 'No citation needed for sorting operations.', }; export class SortDocsTool extends BaseTool<typeof parameterRules> { - private _docManager: AgentDocumentManager; - private _collectionView: DocumentView; + private _docManager: AgentDocumentManager; + private _collectionView: DocumentView; + private _documentDescriptions: Promise<string> | undefined; + + constructor(docManager: AgentDocumentManager, collectionView: DocumentView) { + super(toolInfo); + // Grab the parent collection’s DocumentView (the ChatBox container) + // We assume the ChatBox itself is currently selected in its parent view. + this._collectionView = collectionView; + this._docManager = docManager; + this._docManager.initializeDocuments(); + } + + get TextToDocMap() { + // Use any type to avoid complex type checking while maintaining runtime safety + const childDocs = this._collectionView?.ComponentView?.hasChildDocs?.(); + if (childDocs) { + const textToDocMap = new Map<string, Doc>(); + try { + this._documentDescriptions = Promise.all( + childDocs.map((doc: Doc) => + Doc.getDescription(doc).then(text => { + const cleanText = text.replace(/\n/g, ' ').trim(); + textToDocMap.set(cleanText, doc); + return `${DescStart}${cleanText}${DescEnd}`; + }) + ) + ).then(docDescriptions => docDescriptions.join('')); + return textToDocMap; + } catch (error) { + console.warn('[SortDocsTool] Error initializing document context:', error); + } + } + return undefined; + } + + async execute(args: ParametersType<typeof parameterRules>): Promise<Observation[]> { + const chunkId = uuidv4(); + let textToDocMap = await this.TextToDocMap; + await this._documentDescriptions; + let chunks: string; - constructor(docManager: AgentDocumentManager, collectionView: DocumentView) - { - super(toolInfo); - // Grab the parent collection’s DocumentView (the ChatBox container) - // We assume the ChatBox itself is currently selected in its parent view. - this._collectionView = collectionView; - this._docManager = docManager; - this._docManager.initializeDocuments(); - } + if (textToDocMap && textToDocMap.size > 0 && this._documentDescriptions) { + console.log('[SortDocsTool] Using pre-computed document descriptions'); + chunks = await this._documentDescriptions; + } else { + // Method 2: Build descriptions from scratch using docManager + console.log('[SortDocsTool] Building document descriptions from docManager'); + textToDocMap = new Map<string, Doc>(); + const blocks: string[] = []; - async execute(args: ParametersType<typeof parameterRules>): Promise<Observation[]> { - const chunkId = uuidv4(); + for (const id of this._docManager.docIds) { + const descRaw = await this._docManager.getDocDescription(id); + const desc = descRaw.replace(/\n/g, ' ').trim(); - // 1) gather metadata & build map from text→id - const textToId = new Map<string, string>(); + if (!desc) { + console.warn(`[SortDocsTool] Skipping document ${id} with empty description`); + continue; + } - const chunks = (await Promise.all( - this._docManager.docIds.map(async id => { - const text = await this._docManager.getDocDescription(id); - textToId.set(text,id); - return DescriptionSeperator + text + DescriptionSeperator; - }) - )) - .join(''); - try { - // 2) call GPT to sort those chunks - const gptResponse = await gptAPICall(args.sortCriteria, GPTCallType.SORTDOCS, chunks); - console.log('GPT RESP:', gptResponse); + const doc = this._docManager.getDocument(id); + if (doc) { + textToDocMap.set(desc, doc); + blocks.push(`${DescStart}${desc}${DescEnd}`); + } + } - // 3) parse & map back to IDs - const sortedIds = gptResponse - .split(DescriptionSeperator) - .filter(s => s.trim() !== '') - .map(s => s.replace(/\n/g, ' ').trim()) - .map(s => textToId.get(s)) // lookup in our map - .filter((id): id is string => !!id); + chunks = blocks.join(''); + } + try { + // 2) call GPT to sort those chunks + const gptResponse = await gptAPICall(args.sortCriteria, GPTCallType.SORTDOCS, chunks); + console.log('GPT RESP:', gptResponse); - // 4) write back the ordering - sortedIds.forEach((docId, idx) => { - this._docManager.editDocumentField(docId, ChatSortField, idx); - }); + // 3) parse & map back to IDs + const sortedIds = gptResponse + .split(DescEnd) + .filter(s => s.trim() !== '') + .map(s => s.replace(DescStart, '').replace(/\n/g, ' ').trim()) + .map(s => textToDocMap.get(s)) // lookup in our map + .filter(doc => doc) + .map(doc => doc!); - const fieldKey = this._collectionView.ComponentView!.fieldKey; - this._collectionView.Document[ `${fieldKey}_sort` ] = docSortings.Chat; + // 4) write back the ordering + sortedIds.forEach((doc, idx) => { + doc[ChatSortField] = idx; + }); + const fieldKey = this._collectionView.ComponentView!.fieldKey; + this._collectionView.Document[`${fieldKey}_sort`] = docSortings.Chat; - return [ - { - type: 'text', - text: `<chunk chunk_id="${chunkId}" chunk_type="sort_status"> + return [ + { + type: 'text', + text: `<chunk chunk_id="${chunkId}" chunk_type="sort_status"> Successfully sorted ${sortedIds.length} documents by "${args.sortCriteria}". </chunk>`, - }, - ]; - } catch (err) { - return [ - { - type: 'text', - text: `<chunk chunk_id="${chunkId}" chunk_type="error"> + }, + ]; + } catch (err) { + return [ + { + type: 'text', + text: `<chunk chunk_id="${chunkId}" chunk_type="error"> Sorting failed: ${err instanceof Error ? err.message : err} </chunk>`, - }, - ]; + }, + ]; + } } - } } diff --git a/src/client/views/nodes/chatbot/tools/TagDocsTool.ts b/src/client/views/nodes/chatbot/tools/TagDocsTool.ts index de824ec0d..69b303724 100644 --- a/src/client/views/nodes/chatbot/tools/TagDocsTool.ts +++ b/src/client/views/nodes/chatbot/tools/TagDocsTool.ts @@ -2,120 +2,155 @@ import { v4 as uuidv4 } from 'uuid'; import { Doc } from '../../../../../fields/Doc'; import { Id } from '../../../../../fields/FieldSymbols'; import { TagItem } from '../../../TagsView'; -import { gptAPICall, GPTCallType, DescriptionSeperator, DataSeperator } from '../../../../apis/gpt/GPT'; +import { gptAPICall, GPTCallType, DataSeperator, DescStart, DescEnd } from '../../../../apis/gpt/GPT'; import { ParametersType, ToolInfo } from '../types/tool_types'; import { Observation } from '../types/types'; import { BaseTool } from './BaseTool'; import { AgentDocumentManager } from '../utils/AgentDocumentManager'; +import { DocumentView } from '../../DocumentView'; const parameterRules = [ - { - name: 'taggingCriteria', - type: 'string', - description: 'Natural language description of how to tag the documents (e.g., "tag by subject", "categorize by theme")', - required: true, - }, + { + name: 'taggingCriteria', + type: 'string', + description: 'Natural language description of how to tag the documents (e.g., "tag by subject", "categorize by theme")', + required: true, + }, ] as const; const toolInfo: ToolInfo<typeof parameterRules> = { - name: 'tagDocs', - description: 'Tag documents in the collection based on natural language criteria. Uses GPT to analyze document content and apply appropriate tags.', - parameterRules, - citationRules: 'Citation not required for tagging operations.', + name: 'tagDocs', + description: 'Tag documents in the collection based on natural language criteria. Uses GPT to analyze document content and apply appropriate tags.', + parameterRules, + citationRules: 'Citation not required for tagging operations.', }; export class TagDocsTool extends BaseTool<typeof parameterRules> { - private _docManager: AgentDocumentManager; - - constructor(docManager: AgentDocumentManager) { - super(toolInfo); - this._docManager = docManager; - this._docManager.initializeDocuments(); - } - - async execute(args: ParametersType<typeof parameterRules>): Promise<Observation[]> { - const chunkId = uuidv4(); - - try { - // Build the textToDocMap exactly like GPTPopup does - this._docManager.initializeDocuments(); - const textToDocMap = new Map<string, Doc>(); - const descriptionsArray: string[] = []; - - // Build descriptions exactly like GPTPopup - for (const id of this._docManager.docIds) { - const doc = this._docManager.getDocument(id); - if (!doc) continue; - - const desc = (await this._docManager.getDocDescription(id)).replace(/\n/g, ' ').trim(); - if (!desc) continue; - - textToDocMap.set(desc, doc); - descriptionsArray.push(`${DescriptionSeperator}${desc}${DescriptionSeperator}`); - } - - const promptDescriptions = descriptionsArray.join(''); - console.log('[TagDocsTool] Sending descriptions to GPT:', promptDescriptions.substring(0, 200) + '...'); - - // Call GPT with the same method as GPTPopup - const gptOutput = await gptAPICall( - args.taggingCriteria, - GPTCallType.TAGDOCS, - promptDescriptions - ); - console.log('[TagDocsTool] GPT raw:', gptOutput); - - // Use the same parsing logic as GPTPopup's processGptResponse for AssignTags - const appliedTags: Record<string, string[]> = {}; - - gptOutput - .split(DescriptionSeperator) - .filter(item => item.trim() !== '') - .map(docContentRaw => docContentRaw.replace(/\n/g, ' ').trim()) - .map(docContentRaw => ({ - doc: textToDocMap.get(docContentRaw.split(DataSeperator)[0]), - data: docContentRaw.split(DataSeperator)[1] - })) - .filter(({doc}) => doc) - .map(({doc, data}) => ({doc: doc!, data})) - .forEach(({doc, data}) => { - if (data) { - // Apply tag exactly like GPTPopup does - const tag = data.startsWith('#') ? data : '#' + data[0].toLowerCase() + data.slice(1); - TagItem.addTagToDoc(doc, tag); - - const docId = doc[Id] || 'unknown'; - appliedTags[docId] = appliedTags[docId] || []; - appliedTags[docId].push(tag); - - console.log(`[TagDocsTool] Applied tag "${tag}" to document ${docId}`); - } - }); - - // Build 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"> + private _docManager: AgentDocumentManager; + private _collectionView: DocumentView; + private _documentDescriptions: Promise<string> | undefined; + + constructor(docManager: AgentDocumentManager, collectionView: DocumentView) { + super(toolInfo); + this._docManager = docManager; + this._collectionView = collectionView; + this._docManager.initializeDocuments(); + } + + get TextToDocMap() { + // Use any type to avoid complex type checking while maintaining runtime safety + const childDocs = this._collectionView?.ComponentView?.hasChildDocs?.(); + if (childDocs) { + const textToDocMap = new Map<string, Doc>(); + try { + this._documentDescriptions = Promise.all( + childDocs.map((doc: Doc) => + Doc.getDescription(doc).then(text => { + const cleanText = text.replace(/\n/g, ' ').trim(); + textToDocMap.set(cleanText, doc); + return `${DescStart}${cleanText}${DescEnd}`; + }) + ) + ).then(docDescriptions => docDescriptions.join('')); + return textToDocMap; + } catch (error) { + console.warn('[TagDocsTool] Error initializing document context:', error); + } + } + return undefined; + } + + async execute(args: ParametersType<typeof parameterRules>): Promise<Observation[]> { + const chunkId = uuidv4(); + + try { + // Build the textToDocMap exactly like GPTPopup does + let textToDocMap = await this.TextToDocMap; + await this._documentDescriptions; + let promptDescriptions: string; + + if (textToDocMap && textToDocMap.size > 0 && this._documentDescriptions) { + console.log('[TagDocsTool] Using pre-computed document descriptions'); + promptDescriptions = await this._documentDescriptions; + } else { + // Method 2: Build descriptions from scratch using docManager + console.log('[TagDocsTool] Building document descriptions from docManager'); + textToDocMap = new Map<string, Doc>(); + const blocks: string[] = []; + + for (const id of this._docManager.docIds) { + const descRaw = await this._docManager.getDocDescription(id); + const desc = descRaw.replace(/\n/g, ' ').trim(); + + if (!desc) { + console.warn(`[TagDocsTool] Skipping document ${id} with empty description`); + continue; + } + + const doc = this._docManager.getDocument(id); + if (doc) { + textToDocMap.set(desc, doc); + blocks.push(`${DescStart}${desc}${DescEnd}`); + } + } + + promptDescriptions = blocks.join(''); + } + // Call GPT with the same method as GPTPopup + const gptOutput = await gptAPICall(args.taggingCriteria, GPTCallType.TAGDOCS, promptDescriptions); + console.log('[TagDocsTool] GPT raw:', gptOutput); + + // Use the same parsing logic as GPTPopup's processGptResponse for AssignTags + const appliedTags: Record<string, string[]> = {}; + + gptOutput + .split(DescEnd) + .filter(item => item.trim() !== '') + .map(docContentRaw => docContentRaw.replace(DescStart, '').replace(/\n/g, ' ').trim()) + .map(docContentRaw => ({ + doc: textToDocMap.get(docContentRaw.split(DataSeperator)[0]), + data: docContentRaw.split(DataSeperator)[1], + })) + .filter(({ doc }) => doc) + .map(({ doc, data }) => ({ doc: doc!, data })) + .forEach(({ doc, data }) => { + if (data) { + // Apply tag exactly like GPTPopup does + const tag = data.startsWith('#') ? data : '#' + data[0].toLowerCase() + data.slice(1); + TagItem.addTagToDoc(doc, tag); + + const docId = doc[Id] || 'unknown'; + appliedTags[docId] = appliedTags[docId] || []; + appliedTags[docId].push(tag); + + console.log(`[TagDocsTool] Applied tag "${tag}" to document ${docId}`); + } + }); + + // Build 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 ${Object.keys(appliedTags).length} documents based on "${args.taggingCriteria}". Tags applied: ${summary || '(none)'} </chunk>`, - }, - ]; - } catch (err) { - console.error('[TagDocsTool] error:', err); - return [ - { - type: 'text', - text: `<chunk chunk_id="${chunkId}" chunk_type="error"> + }, + ]; + } catch (err) { + console.error('[TagDocsTool] error:', err); + return [ + { + type: 'text', + text: `<chunk chunk_id="${chunkId}" chunk_type="error"> Tagging failed: ${err instanceof Error ? err.message : String(err)} </chunk>`, - }, - ]; + }, + ]; + } } - } } diff --git a/src/client/views/pdf/GPTPopup/GPTPopup.tsx b/src/client/views/pdf/GPTPopup/GPTPopup.tsx index 65be98c83..23bbe3577 100644 --- a/src/client/views/pdf/GPTPopup/GPTPopup.tsx +++ b/src/client/views/pdf/GPTPopup/GPTPopup.tsx @@ -14,7 +14,7 @@ import { NumCast, StrCast } from '../../../../fields/Types'; import { ImageField } from '../../../../fields/URLField'; import { Upload } from '../../../../server/SharedMediaTypes'; import { Networking } from '../../../Network'; -import { DataSeperator, DescriptionSeperator, DocSeperator, GPTCallType, GPTDocCommand, gptAPICall, gptImageCall } from '../../../apis/gpt/GPT'; +import { DataSeperator, DescEnd, DescStart, DocSeperator, GPTCallType, GPTDocCommand, gptAPICall, gptImageCall } from '../../../apis/gpt/GPT'; import { DocUtils } from '../../../documents/DocUtils'; import { Docs } from '../../../documents/Documents'; import { SettingsManager } from '../../../util/SettingsManager'; @@ -93,8 +93,8 @@ export class GPTPopup extends ObservableReactComponent<object> { this.onQuizRandom = () => this.randomlyChooseDoc(selDoc.Document, hasChildDocs()); this._documentDescriptions = Promise.all(hasChildDocs().map(doc => Doc.getDescription(doc).then(text => text.replace(/\n/g, ' ').trim()) - .then(text => this._textToDocMap.set(text, doc) && `${DescriptionSeperator}${text}${DescriptionSeperator}`) - )).then(docDescriptions => docDescriptions.join()); // prettier-ignore + .then(text => this._textToDocMap.set(text, doc) && `${DescStart}${text}${DescEnd}`) + )).then(docDescriptions => docDescriptions.join('')); // prettier-ignore this._documentDescriptions.then(descs => { console.log(descs); }); @@ -148,8 +148,8 @@ export class GPTPopup extends ObservableReactComponent<object> { break; } // prettier-ignore - gptOutput.split(DescriptionSeperator).filter(item => item.trim() !== '') // Split output into individual document contents - .map(docContentRaw => docContentRaw.replace(/\n/g, ' ').trim()) + gptOutput.split(DescEnd).filter(item => item.trim() !== '') // Split output into individual document contents + .map(docContentRaw => docContentRaw.replace(DescStart,"").replace(/\n/g, ' ').trim()) .map(docContentRaw => ({doc: textToDocMap.get(docContentRaw.split(DataSeperator)[0]), data: docContentRaw.split(DataSeperator)[1] })) // the find the corresponding Doc using textToDoc map .filter(({doc}) => doc).map(({doc, data}) => ({doc:doc!, data})) // filter out undefined values .forEach(({doc, data}, index) => { |