aboutsummaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
Diffstat (limited to 'src')
-rw-r--r--src/client/views/nodes/chatbot/agentsystem/Agent.ts19
-rw-r--r--src/client/views/nodes/chatbot/chatboxcomponents/ChatBox.tsx29
-rw-r--r--src/client/views/nodes/chatbot/tools/SortDocsTool.ts121
-rw-r--r--src/client/views/nodes/chatbot/tools/TagDocsTool.ts54
-rw-r--r--src/client/views/nodes/chatbot/tools/TakeQuizTool.ts88
-rw-r--r--src/client/views/nodes/chatbot/utils/AgentDocumentManager.ts22
6 files changed, 274 insertions, 59 deletions
diff --git a/src/client/views/nodes/chatbot/agentsystem/Agent.ts b/src/client/views/nodes/chatbot/agentsystem/Agent.ts
index 3acdc6aa8..d7aee51e6 100644
--- a/src/client/views/nodes/chatbot/agentsystem/Agent.ts
+++ b/src/client/views/nodes/chatbot/agentsystem/Agent.ts
@@ -30,6 +30,7 @@ import { CreateNewTool } from '../tools/CreateNewTool';
import { SortDocsTool} from '../tools/SortDocsTool';
import { TagDocsTool } from '../tools/TagDocsTool';
import { GPTTutorialTool } from '../tools/TutorialTool';
+import { DocumentView } from '../../DocumentView';
dotenv.config();
@@ -53,6 +54,7 @@ export class Agent {
private tools: Record<string, BaseTool<ReadonlyArray<Parameter>>>;
private _docManager: AgentDocumentManager;
private is_dash_doc_assistant: boolean;
+ private parentView: DocumentView;
// Dynamic tool registry for tools created at runtime
private dynamicToolRegistry: Map<string, BaseTool<ReadonlyArray<Parameter>>> = new Map();
// Callback for notifying when tools are created and need reload
@@ -83,6 +85,7 @@ export class Agent {
// Initialize OpenAI client with API key from environment
this.client = new OpenAI({ apiKey: process.env.OPENAI_KEY, dangerouslyAllowBrowser: true });
this.vectorstore = _vectorstore;
+ this.parentView = docManager.parentViewDocument; // Get the parent DocumentView
this._history = history;
this._csvData = csvData;
this._docManager = docManager;
@@ -106,7 +109,7 @@ export class Agent {
fileContent: new FileContentTool(this.vectorstore),
fileNames: new FileNamesTool(this.vectorstore),
generateTutorialNode: new GPTTutorialTool(this._docManager),
- sortDocs: new SortDocsTool(this._docManager),
+ sortDocs: new SortDocsTool(this._docManager, this.parentView),
tagDocs: new TagDocsTool(this._docManager),
};
@@ -346,13 +349,25 @@ export class Agent {
console.log(`Thought: ${stage[key]}`);
this.processingNumber++;
} else if (key === 'action') {
+
// Handle action stage
currentAction = stage[key] as string;
console.log(`Action: ${currentAction}`);
// Check both static tools and dynamic registry
const tool = this.tools[currentAction] || this.dynamicToolRegistry.get(currentAction);
-
+ if (currentAction === 'noTool') {
+ // Immediately ask for clarification in plain text, not as a tool prompt
+ this.interMessages.push({
+ role: 'user',
+ content: `<stage number="${i+1}" role="assistant">
+ <answer>
+ I’m not sure what you’d like me to do. Could you clarify your request?
+ </answer>
+ </stage>`
+ });
+ break;
+ }
if (tool) {
// Prepare the next action based on the current tool
const nextPrompt = [
diff --git a/src/client/views/nodes/chatbot/chatboxcomponents/ChatBox.tsx b/src/client/views/nodes/chatbot/chatboxcomponents/ChatBox.tsx
index 636b77b38..f84a4cd2a 100644
--- a/src/client/views/nodes/chatbot/chatboxcomponents/ChatBox.tsx
+++ b/src/client/views/nodes/chatbot/chatboxcomponents/ChatBox.tsx
@@ -111,8 +111,16 @@ export class ChatBox extends ViewBoxAnnotatableComponent<FieldViewProps>() {
super(props);
makeObservable(this);
+ // At mount time, find the DocumentView whose .Document is the collection container.
+ const parentView = DocumentView.Selected().lastElement();
+ if (!parentView) {
+ console.warn("GPT ChatBox not inside a DocumentView – cannot sort.");
+ }
+
+
+
this.messagesRef = React.createRef();
- this.docManager = new AgentDocumentManager(this);
+ this.docManager = new AgentDocumentManager(this, parentView);
// Initialize OpenAI client
this.initializeOpenAI();
@@ -154,6 +162,25 @@ export class ChatBox extends ViewBoxAnnotatableComponent<FieldViewProps>() {
}
);
+ /*
+ reaction(
+ () => ({ selDoc: DocumentView.Selected().lastElement(), visible: SnappingManager.ChatVisible }),
+ ({ selDoc, visible }) => {
+ const hasChildDocs = visible && selDoc?.ComponentView?.hasChildDocs;
+ if (hasChildDocs) {
+ this._textToDocMap.clear();
+ this.setCollectionContext(selDoc.Document);
+ this.onGptResponse = (sortResult: string, questionType: GPTDocCommand) => this.processGptResponse(selDoc, this._textToDocMap, sortResult, questionType);
+ this.onQuizRandom = () => this.randomlyChooseDoc(selDoc.Document, hasChildDocs());
+ this._documentDescriptions = Promise.all(hasChildDocs().map(doc =>
+ Doc.getDescription(doc).then(text => this._textToDocMap.set(text.replace(/\n/g, ' ').trim(), doc) && `${DescriptionSeperator}${text}${DescriptionSeperator}`)
+ )).then(docDescriptions => docDescriptions.join()); // prettier-ignore
+ }
+ },
+ { fireImmediately: true }
+ );
+ }*/
+
// Initialize font size from saved preference
this.initFontSize();
}
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>`,
+ },
+ ];
+ }
+ }
+}
diff --git a/src/client/views/nodes/chatbot/utils/AgentDocumentManager.ts b/src/client/views/nodes/chatbot/utils/AgentDocumentManager.ts
index dcb708450..857cc859d 100644
--- a/src/client/views/nodes/chatbot/utils/AgentDocumentManager.ts
+++ b/src/client/views/nodes/chatbot/utils/AgentDocumentManager.ts
@@ -27,6 +27,7 @@ interface AgentDocument {
export class AgentDocumentManager {
@observable private documentsById: ObservableMap<string, AgentDocument>;
private chatBox: ChatBox;
+ private parentView : DocumentView
private chatBoxDocument: Doc | null = null;
private fieldMetadata: Record<string, any> = {}; // bcz: CHANGE any to a proper type!
@observable private simplifiedChunks: ObservableMap<string, SimplifiedChunk>;
@@ -35,8 +36,9 @@ export class AgentDocumentManager {
* Creates a new DocumentManager
* @param templateDocument The document that serves as a template for new documents
*/
- constructor(chatBox: ChatBox) {
+ constructor(chatBox: ChatBox, parentView : DocumentView) {
makeObservable(this);
+ this.parentView = parentView;
const agentDoc = DocCast(chatBox.Document.agentDocument) ?? new Doc();
const chunk_simpl = DocCast(agentDoc.chunk_simpl) ?? new Doc();
@@ -164,6 +166,10 @@ export class AgentDocumentManager {
}
}
+ public get parentViewDocument(): DocumentView {
+ return this.parentView;
+ }
+
/**
* Process a document by ensuring it has an ID and adding it to the appropriate collections
* @param doc The document to process
@@ -1011,6 +1017,20 @@ export class AgentDocumentManager {
const docInfo = this.documentsById.get(docId);
return docInfo?.dataDoc;
}
+
+ // In AgentDocumentManager
+ private descriptionCache = new Map<string,string>();
+
+ public async getDocDescription(id: string): Promise<string> {
+ if (!this.descriptionCache.has(id)) {
+ const doc = this.getDocument(id)!;
+ const desc = await Doc.getDescription(doc);
+ this.descriptionCache.set(id, desc.replace(/\n/g,' ').trim());
+ }
+ return this.descriptionCache.get(id)!;
+ }
+
+
/**
* Adds simplified chunks to a document for citation handling
* @param doc The document to add simplified chunks to