diff options
Diffstat (limited to 'src/client/views/nodes/chatbot/tools')
| -rw-r--r-- | src/client/views/nodes/chatbot/tools/SortDocsTool.ts | 121 | ||||
| -rw-r--r-- | src/client/views/nodes/chatbot/tools/TagDocsTool.ts | 54 | ||||
| -rw-r--r-- | src/client/views/nodes/chatbot/tools/TakeQuizTool.ts | 88 |
3 files changed, 208 insertions, 55 deletions
diff --git a/src/client/views/nodes/chatbot/tools/SortDocsTool.ts b/src/client/views/nodes/chatbot/tools/SortDocsTool.ts index 741a8f3ce..45d7b4f15 100644 --- a/src/client/views/nodes/chatbot/tools/SortDocsTool.ts +++ b/src/client/views/nodes/chatbot/tools/SortDocsTool.ts @@ -2,66 +2,97 @@ 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 { gptAPICall, GPTCallType, DescriptionSeperator } 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 { collect } from '@turf/turf'; 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. Provide clear sorting criteria, such as by date, title, relevance, or custom metadata fields.', - 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 _docManager: AgentDocumentManager; + private _collectionView: DocumentView; - constructor(docManager: AgentDocumentManager) { - super(toolInfo); - this._docManager = docManager; - this._docManager.initializeFindDocsFreeform(); - } + 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.initializeFindDocsFreeform(); + } + + async execute(args: ParametersType<typeof parameterRules>): Promise<Observation[]> { + const chunkId = uuidv4(); + + // 1) gather metadata & build map from text→id + const textToId = new Map<string, string>(); + + 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); - async execute(args: ParametersType<typeof parameterRules>): Promise<Observation[]> { - const chunkId = uuidv4(); - try { - const docs = this._docManager.docIds.map((id) => this._docManager.extractDocumentMetadata(id)); + // 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); - const descriptions = docs - .filter((doc): doc is NonNullable<typeof doc> => doc !== null) - .map( - (doc) => `${doc.id}: ${doc.title} - ${doc.fields.layout.summary || ''}` - ) - .join('\n'); + // 4) write back the ordering + sortedIds.forEach((docId, idx) => { + this._docManager.editDocumentField(docId, ChatSortField, idx); + }); - const sortedIdsResponse = await gptAPICall(args.sortCriteria, GPTCallType.SORTDOCS, descriptions); - const sortedIds = sortedIdsResponse.trim().split('\n'); - console.log(sortedIdsResponse); - console.log(sortedIds); + const fieldKey = this._collectionView.ComponentView!.fieldKey; + this._collectionView.Document[ `${fieldKey}_sort` ] = docSortings.Chat; - 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>`, - }]; - } + 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"> +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 6b4693279..75f476348 100644 --- a/src/client/views/nodes/chatbot/tools/TagDocsTool.ts +++ b/src/client/views/nodes/chatbot/tools/TagDocsTool.ts @@ -34,25 +34,30 @@ 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(''); - + + // 1) gather metadata & build map from text→id + const textToId = new Map<string, string>(); + //make this a general UTIL + const descriptions = (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(''); // Call GPT const raw = await gptAPICall( args.taggingCriteria, GPTCallType.TAGDOCS, descriptions ); - + console.log('TAG RESP:', raw); // Prepare to collect what we actually applied const appliedTags: Record<string, string[]> = {}; // Parse and apply tags exactly like GPTPopup - raw + /*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 @@ -76,7 +81,36 @@ async execute(args: ParametersType<typeof parameterRules>): Promise<Observation[ // Record for our summary appliedTags[id] = normalized; - }); + });*/ + + raw + .split(DescriptionSeperator) // 1) Split into “blocks” + .filter(item => item.trim() !== '') // 2) Drop empty blocks + .map(block => block.replace(/\n/g, ' ').trim()) // 3) Flatten & trim + .map(block => { + // 4) block looks like: "docId>>>>>>tag1, tag2" + const [idPart, tagsPart] = block.split(DataSeperator); + return { id: idPart.trim(), data: (tagsPart || '').trim() }; + }) + .filter(({ id, data }) => !!id && !!data) // 5) Keep only valid pairs + .forEach(({ id, data }) => { + // 6) Lookup doc by ID + const doc = this._docManager.getDocument(id); + if (!doc) return; + + // 7) Only in the AssignTags branch do we add tags + // And GPTPopup normalizes by lowercasing first letter if no '#' + const tagToApply = data.startsWith('#') + ? data + : '#' + data[0].toLowerCase() + data.slice(1); + + TagItem.addTagToDoc(doc, tagToApply); + + // 8) Record for summary + if (!appliedTags[id]) appliedTags[id] = []; + appliedTags[id].push(tagToApply); + }); + // Build single observation with summary const summary = Object.entries(appliedTags) diff --git a/src/client/views/nodes/chatbot/tools/TakeQuizTool.ts b/src/client/views/nodes/chatbot/tools/TakeQuizTool.ts new file mode 100644 index 000000000..f025e95cd --- /dev/null +++ b/src/client/views/nodes/chatbot/tools/TakeQuizTool.ts @@ -0,0 +1,88 @@ +import { BaseTool } from './BaseTool'; +import { Observation } from '../types/types'; +import { ParametersType, ToolInfo } from '../types/tool_types'; +import { AgentDocumentManager } from '../utils/AgentDocumentManager'; +import { GPTCallType, gptAPICall } from '../../../../apis/gpt/GPT'; +import { v4 as uuidv4 } from 'uuid'; + +const parameterRules = [ + { + name: 'userAnswer', + type: 'string', + description: 'User-provided answer to the quiz question.', + required: true, + }, +] as const; + +const toolInfo: ToolInfo<typeof parameterRules> = { + name: 'takeQuiz', + description: + 'Evaluates a user\'s answer for a randomly selected document using GPT, mirroring GPTPopup\'s quiz functionality.', + parameterRules, + citationRules: 'No citation needed for quiz operations.', +}; + +export class TakeQuizTool extends BaseTool<typeof parameterRules> { + private _docManager: AgentDocumentManager; + + constructor(docManager: AgentDocumentManager) { + super(toolInfo); + this._docManager = docManager; + this._docManager.initializeFindDocsFreeform(); + } + + private async generateRubric(docId: string, description: string): Promise<string> { + const docMeta = this._docManager.extractDocumentMetadata(docId); + if (docMeta && docMeta.fields.layout.gptRubric) { + return docMeta.fields.layout.gptRubric; + } else { + const rubric = await gptAPICall(description, GPTCallType.MAKERUBRIC); + if (rubric) { + await this._docManager.editDocumentField(docId, 'layout.gptRubric', rubric); + } + return rubric || ''; + } + } + + async execute(args: ParametersType<typeof parameterRules>): Promise<Observation[]> { + const chunkId = uuidv4(); + + try { + const allDocIds = this._docManager.docIds; + const randomDocId = allDocIds[Math.floor(Math.random() * allDocIds.length)]; + const docMeta = this._docManager.extractDocumentMetadata(randomDocId); + + if (!docMeta) throw new Error('Randomly selected document metadata is undefined'); + + const description = docMeta.fields.layout.description.replace(/\n/g, ' ').trim(); + const rubric = await this.generateRubric(randomDocId, description); + + const prompt = ` + Question: ${description}; + UserAnswer: ${args.userAnswer}; + Rubric: ${rubric} + `; + + const evaluation = await gptAPICall(prompt, GPTCallType.QUIZDOC); + + return [ + { + type: 'text', + text: `<chunk chunk_id="${chunkId}" chunk_type="quiz_result"> +Evaluation result: ${evaluation || 'GPT provided no answer'}. +Document evaluated: "${docMeta.title}" +</chunk>`, + }, + ]; + } catch (err) { + return [ + { + type: 'text', + text: `<chunk chunk_id="${chunkId}" chunk_type="error"> +Quiz evaluation failed: ${err instanceof Error ? err.message : err} +</chunk>`, + }, + ]; + } + } +} |
