diff options
author | sharkiecodes <lanyi_stroud@brown.edu> | 2025-07-10 10:38:23 -0400 |
---|---|---|
committer | sharkiecodes <lanyi_stroud@brown.edu> | 2025-07-10 10:38:23 -0400 |
commit | 6ab42243141c2b6474cb4e85421d60f1827e403b (patch) | |
tree | 02cd9c32d4701e04293dc359934269ba4a6952f5 | |
parent | b2b49aa38d6998c7fb8a44818fb6c6eb486d6d2e (diff) |
fixed filterdoctool
-rw-r--r-- | src/client/views/nodes/chatbot/tools/FilterDocTool.ts | 161 |
1 files changed, 77 insertions, 84 deletions
diff --git a/src/client/views/nodes/chatbot/tools/FilterDocTool.ts b/src/client/views/nodes/chatbot/tools/FilterDocTool.ts index 6be42d83b..d6d01ee82 100644 --- a/src/client/views/nodes/chatbot/tools/FilterDocTool.ts +++ b/src/client/views/nodes/chatbot/tools/FilterDocTool.ts @@ -7,13 +7,12 @@ import { gptAPICall, GPTCallType, DescriptionSeperator, - DataSeperator + DataSeperator, } from '../../../../apis/gpt/GPT'; import { v4 as uuidv4 } from 'uuid'; import { TagItem } from '../../../TagsView'; import { DocumentView } from '../../DocumentView'; import { Doc } from '../../../../../fields/Doc'; -import { Filter } from '../../../../util/reportManager/ReportManagerComponents'; const parameterRules = [ { @@ -28,108 +27,136 @@ const toolInfo: ToolInfo<typeof parameterRules> = { name: 'filterDocs', description: 'Filters documents based on user-specified natural-language criteria.', parameterRules, - citationRules: - 'No citation needed for filtering operations.', + citationRules: 'No citation needed for filtering operations.', }; export class FilterDocsTool extends BaseTool<typeof parameterRules> { private _docManager: AgentDocumentManager; static ChatTag = '#chat'; // tag used by GPT popup to filter docs - // the parent Document (collection) that will be filtered private _collectionView: DocumentView; - constructor( - docManager: AgentDocumentManager, - collectionView: DocumentView - ) { + constructor(docManager: AgentDocumentManager, collectionView: DocumentView) { super(toolInfo); this._docManager = docManager; this._docManager.initializeFindDocsFreeform(); this._collectionView = collectionView; } - async execute( - args: ParametersType<typeof parameterRules> - ): Promise<Observation[]> { + async execute(args: ParametersType<typeof parameterRules>): Promise<Observation[]> { const chunkId = uuidv4(); + console.log('[FilterDocsTool] execute() called with criteria:', args.filterCriteria); + + // 0) Check parent view & doc + const parentView = this._collectionView; + console.log('[FilterDocsTool] parentView:', parentView); + const parentDoc = parentView?.Document; + if (!parentDoc) { + console.error('[FilterDocsTool] Missing parentView.Document!'); + return [ + { + type: 'text', + text: `<chunk chunk_id="${chunkId}" chunk_type="error"> +FilterDocsTool: no parent DocumentView / parentDoc provided. +</chunk>`, + }, + ]; + } + console.log('[FilterDocsTool] parentDoc:', parentDoc.id || parentDoc); try { // 1) Build description→ID map & prompt blocks + console.log('[FilterDocsTool] docIds:', this._docManager.docIds); const textToId = new Map<string, string>(); const blocks: string[] = []; for (const id of this._docManager.docIds) { - // get a reliable human-readable description - const desc = ( - await this._docManager.getDocDescription(id) - ) - .replace(/\n/g, ' ') - .trim(); - - if (!desc) continue; + const descRaw = await this._docManager.getDocDescription(id); + const desc = descRaw.replace(/\n/g, ' ').trim(); + console.log(`[FilterDocsTool] got desc for ${id}:`, desc); + + if (!desc) { + console.warn(`[FilterDocsTool] skipping empty desc for ${id}`); + continue; + } textToId.set(desc, id); - blocks.push(`${DescriptionSeperator}${desc}${DescriptionSeperator}`); + + const block = `${DescriptionSeperator}${desc}${DescriptionSeperator}`; + console.log('[FilterDocsTool] adding block:', block); + blocks.push(block); } const prompt = blocks.join(''); + console.log('[FilterDocsTool] final prompt to GPT:', prompt); // 2) Ask GPT for subset - const raw = await gptAPICall( - args.filterCriteria, - GPTCallType.SUBSETDOCS, - prompt - ); - console.log('[FilterDocsTool] GPT response:', raw); + const raw = await gptAPICall(args.filterCriteria, GPTCallType.SUBSETDOCS, prompt); + console.log('[FilterDocsTool] GPT raw response:', raw); // 3) Clear existing chat-filter tags/filters const allDocs = this._docManager.docIds .map((id) => this._docManager.getDocument(id)) .filter((d): d is Doc => !!d); - allDocs.forEach((d) => { - // remove any prior ChatTag - TagItem.removeTagFromDoc(d, FilterDocsTool.ChatTag); - }); - // also remove the docFilter setting on the parent - Doc.setDocFilter( - this._collectionView.Document, - 'tags', - FilterDocsTool.ChatTag, - 'remove' + console.log('[FilterDocsTool] clearing existing tags from docs:', allDocs.map((d) => d.id || d)); + allDocs.forEach((d) => TagItem.removeTagFromDoc(d, FilterDocsTool.ChatTag)); + + console.log( + `[FilterDocsTool] removing docFilter('${FilterDocsTool.ChatTag}') from parentDoc ${parentDoc.id}` ); + Doc.setDocFilter(parentDoc, 'tags', FilterDocsTool.ChatTag, 'remove'); // 4) Parse GPT’s output, re-apply tag + docFilter + const newlyTagged: string[] = []; + raw .split(DescriptionSeperator) .filter((blk) => blk.trim() !== '') .map((blk) => blk.replace(/\n/g, ' ').trim()) - .forEach((blk) => { - // split on '>>>>>>' aka DataSeperator - const [descText, _extra] = blk.split(DataSeperator).map((s) => s.trim()); + .forEach((blk, idx) => { + console.log(`[FilterDocsTool] parsing block[${idx}]:`, blk); + const [descText = '', _extra] = blk.split(DataSeperator).map((s) => s.trim()); + console.log(` → descText = "${descText}", ignoring extra="${_extra}"`); + + if (!descText) { + console.warn('[FilterDocsTool] skipping block with empty descText:', blk); + return; + } + const docId = textToId.get(descText); if (!docId) { - console.warn('[FilterDocsTool] no match for', descText); + console.warn('[FilterDocsTool] no match in textToId for descText:', descText); return; } + const doc = this._docManager.getDocument(docId); - if (!doc) return; + if (!doc) { + console.warn('[FilterDocsTool] no Doc instance for ID:', docId); + return; + } - // add the special '#chat' tag: + // apply the special '#chat' tag + console.log(`[FilterDocsTool] adding tag "${FilterDocsTool.ChatTag}" to doc ${docId}`); TagItem.addTagToDoc(doc, FilterDocsTool.ChatTag); + newlyTagged.push(docId); }); - // Finally, set the parent’s filter to **check** on that tag - Doc.setDocFilter( - this._collectionView.Document, - 'tags', - FilterDocsTool.ChatTag, - 'check' + console.log('[FilterDocsTool] newly tagged IDs:', newlyTagged); + + // 5) Finally, set the parent’s filter to **check** on that tag + console.log( + `[FilterDocsTool] setting docFilter('${FilterDocsTool.ChatTag}', 'check') on parentDoc ${parentDoc.id}` ); + Doc.setDocFilter(parentDoc, 'tags', FilterDocsTool.ChatTag, 'check'); + + // build summary + const summary = newlyTagged.length + ? newlyTagged.join(', ') + : '(none)'; return [ { type: 'text', text: `<chunk chunk_id="${chunkId}" chunk_type="filter_status"> -Filtered documents based on "${args.filterCriteria}". Only docs tagged "${FilterDocsTool.ChatTag}" will be shown. +Filtered documents based on "${args.filterCriteria}". Docs tagged ${FilterDocsTool.ChatTag}: ${summary} </chunk>`, }, ]; @@ -146,37 +173,3 @@ Filtering failed: ${err instanceof Error ? err.message : String(err)} } } } - -/* processGptResponse = (docView: DocumentView, textToDocMap: Map<string, Doc>, gptOutput: string, questionType: GPTDocCommand) => - undoable(() => { - switch (questionType) { // reset collection based on question typefc - case GPTDocCommand.Sort: - docView.Document[docView.ComponentView?.fieldKey + '_sort'] = docSortings.Chat; - break; - case GPTDocCommand.Filter: - docView.ComponentView?.hasChildDocs?.().forEach(d => TagItem.removeTagFromDoc(d, GPTPopup.ChatTag)); - break; - } // prettier-ignore - - gptOutput.split(DescriptionSeperator).filter(item => item.trim() !== '') // Split output into individual document contents - .map(docContentRaw => docContentRaw.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) => { - switch (questionType) { - case GPTDocCommand.Sort: - doc[ChatSortField] = index; - break; - case GPTDocCommand.AssignTags: - data && TagItem.addTagToDoc(doc, data.startsWith('#') ? data : '#'+data[0].toLowerCase()+data.slice(1) ); - break; - case GPTDocCommand.Filter: - TagItem.addTagToDoc(doc, GPTPopup.ChatTag); - Doc.setDocFilter(docView.Document, 'tags', GPTPopup.ChatTag, 'check'); - break; - } - }); // prettier-ignore - }, '')(); - - /** - * When in quiz mode, rando*/
\ No newline at end of file |