aboutsummaryrefslogtreecommitdiff
path: root/src/client/views/nodes/chatbot/tools/TutorialTool.ts
diff options
context:
space:
mode:
Diffstat (limited to 'src/client/views/nodes/chatbot/tools/TutorialTool.ts')
-rw-r--r--src/client/views/nodes/chatbot/tools/TutorialTool.ts339
1 files changed, 189 insertions, 150 deletions
diff --git a/src/client/views/nodes/chatbot/tools/TutorialTool.ts b/src/client/views/nodes/chatbot/tools/TutorialTool.ts
index 69ae9c618..08e4e1409 100644
--- a/src/client/views/nodes/chatbot/tools/TutorialTool.ts
+++ b/src/client/views/nodes/chatbot/tools/TutorialTool.ts
@@ -1,166 +1,205 @@
import { BaseTool } from './BaseTool';
import { Observation } from '../types/types';
-import { Parameter, ParametersType, ToolInfo } from '../types/tool_types';
-// import { gptAPICall } from '../../../../apis/gpt/GPT';
+import { ParametersType, ToolInfo } from '../types/tool_types';
import { schema } from '../../../../views/nodes/formattedText/schema_rts';
-import { RichTextField } from '../../../../../fields/RichTextField';
-import { Docs } from '../../../../documents/Documents';
-import { DocumentViewInternal } from '../../../nodes/DocumentView';
import { v4 as uuidv4 } from 'uuid';
-import { OpenWhere } from '../../../../views/nodes/OpenWhere';
-import { gptAPICall } from '../../../../apis/gpt/GPT';
+import { gptTutorialAPICall } from '../../../../apis/gpt/TutorialGPT';
import { parsedDoc } from '../chatboxcomponents/ChatBox';
import { Id } from '../../../../../fields/FieldSymbols';
import { Doc } from '../../../../../fields/Doc';
-
+import { RichTextField } from '../../../../../fields/RichTextField';
+import { DocumentViewInternal } from '../../DocumentView';
+import { Docs } from '../../../../documents/Documents';
+import { OpenWhere } from '../../OpenWhere';
+import { CollectionFreeFormView } from '../../../collections/collectionFreeForm';
const generateTutorialNodeToolParams = [
- {
- name: 'query',
- type: 'string',
- description: 'The user\'s query about Dash functionality.',
- required: true,
- },
+ {
+ name: 'query',
+ type: 'string',
+ description: 'The user query that asks how to use the environment',
+ required: true,
+ },
] as const;
const generateTutorialNodeToolInfo: ToolInfo<typeof generateTutorialNodeToolParams> = {
- name: 'generateTutorialNode',
- description: 'Generates a tutorial text node based on the user\'s query about Dash functionality. Use this when the user asks for help or tutorials on how to use Dash features.',
- parameterRules: generateTutorialNodeToolParams,
- citationRules: 'No citation needed for this tool\'s output.',
+ name: 'generateTutorialNode',
+ description: "Generates a tutorial text node based on the user's query about Dash functionality. Use this when the user asks for help or tutorials on how to use Dash features.",
+ parameterRules: generateTutorialNodeToolParams,
+ citationRules: "No citation needed for this tool's output.",
+};
+const applyFormatting = (markdownText: string): { doc: any; plainText: string } => {
+ const lines = markdownText.split('\n');
+ const nodes: any[] = [];
+ let plainText = '';
+ let i = 0;
+ let currentListItems: any[] = [];
+ let currentParagraph: any[] = [];
+ let currentOrderedListItems: any[] = [];
+ let inOrderedList = false;
+ let inBulletList = false;
+
+ const processBoldText = (text: string) => {
+ const boldRegex = /\*\*(.*?)\*\*/g;
+ const parts = [];
+ let lastIndex = 0;
+ let match;
+
+ while ((match = boldRegex.exec(text)) !== null) {
+ if (match.index > lastIndex) {
+ parts.push(schema.text(text.substring(lastIndex, match.index)));
+ }
+ parts.push(schema.text(match[1], [schema.marks.strong.create()]));
+ lastIndex = match.index + match[0].length;
+ }
+ if (lastIndex < text.length) {
+ parts.push(schema.text(text.substring(lastIndex)));
+ }
+ return parts.length > 0 ? parts : [schema.text(text)];
+ };
+
+ const flushListItems = () => {
+ if (currentListItems.length > 0) {
+ nodes.push(schema.nodes.ordered_list.create({ mapStyle: 'bullet' }, currentListItems));
+ nodes.push(schema.nodes.paragraph.create());
+ currentListItems = [];
+ inBulletList = false;
+ }
+ if (currentOrderedListItems.length > 0) {
+ nodes.push(schema.nodes.ordered_list.create({ mapStyle: 'number' }, currentOrderedListItems));
+ nodes.push(schema.nodes.paragraph.create());
+ currentOrderedListItems = [];
+ inOrderedList = false;
+ }
+ };
+
+ const flushParagraph = () => {
+ if (currentParagraph.length > 0) {
+ nodes.push(schema.nodes.paragraph.create({}, currentParagraph));
+ currentParagraph = [];
+ }
+ };
+
+ const processHeader = (line: string) => {
+ const headerMatch = line.match(/^(#{1,6})\s+(.+)$/);
+ if (headerMatch) {
+ const level = Math.min(headerMatch[1].length, 6); // Cap at h6
+ const textContent = headerMatch[2];
+ flushParagraph();
+ nodes.push(schema.nodes.heading.create({ level }, processBoldText(textContent)));
+ plainText += textContent + '\n';
+ return true;
+ }
+ return false;
+ };
+
+ while (i < lines.length) {
+ const line = lines[i].trim();
+ if (line) {
+ if (processHeader(line)) {
+ flushListItems();
+ flushParagraph();
+ } else if (line.startsWith('- ')) {
+ flushParagraph();
+ if (!inBulletList) {
+ flushListItems();
+ inBulletList = true;
+ }
+ const textContent = line.replace('- ', '');
+ currentListItems.push(schema.nodes.list_item.create({}, schema.nodes.paragraph.create({}, processBoldText(textContent))));
+ plainText += textContent + '\n';
+ } else if (/^\d+\.\s+/.test(line)) {
+ flushParagraph();
+ if (!inOrderedList) {
+ flushListItems();
+ inOrderedList = true;
+ }
+ const textContent = line.replace(/^\d+\.\s+/, '');
+ currentOrderedListItems.push(schema.nodes.list_item.create({}, schema.nodes.paragraph.create({}, processBoldText(textContent))));
+ plainText += textContent + '\n';
+ } else {
+ flushListItems();
+ currentParagraph = currentParagraph.concat(processBoldText(line));
+ plainText += line + '\n';
+ }
+ } else {
+ flushListItems();
+ flushParagraph();
+ nodes.push(schema.nodes.paragraph.create());
+ plainText += '\n';
+ }
+ i++;
+ }
+ flushListItems();
+ flushParagraph();
+
+ const doc = schema.nodes.doc.create({}, nodes);
+ return { doc, plainText: plainText.trim() };
};
export class GPTTutorialTool extends BaseTool<typeof generateTutorialNodeToolParams> {
- private _createDocInDash: (doc: parsedDoc) => Doc | undefined;
-
- constructor(createDocInDash: (doc: parsedDoc) => Doc | undefined) {
- super(generateTutorialNodeToolInfo);
-
- this._createDocInDash = createDocInDash;
- }
-
-// private applyFormatting(markdownText: string): { doc: any; plainText: string } {
-// const lines = markdownText.split('\n');
-// const nodes: any[] = [];
-// let plainText = '';
-// let i = 0;
-// let currentListItems: any[] = [];
-
-// const processBoldText = (text: string) => {
-// const boldRegex = /\*\*(.*?)\*\*/g;
-// const parts = [];
-// let lastIndex = 0;
-// let match;
-
-// while ((match = boldRegex.exec(text)) !== null) {
-// if (match.index > lastIndex) {
-// parts.push(schema.text(text.substring(lastIndex, match.index)));
-// }
-// parts.push(schema.text(match[1], [schema.marks.strong.create()]));
-// lastIndex = match.index + match[0].length;
-// }
-// if (lastIndex < text.length) {
-// parts.push(schema.text(text.substring(lastIndex)));
-// }
-// return parts.length > 0 ? parts : [schema.text(text)];
-// };
-
-// const flushListItems = () => {
-// if (currentListItems.length > 0) {
-// nodes.push(schema.nodes.ordered_list.create({ mapStyle: 'bullet' }, currentListItems));
-// currentListItems = [];
-// }
-// };
-
-// while (i < lines.length) {
-// const line = lines[i].trim();
-// if (line) {
-// if (line.startsWith('## ')) {
-// flushListItems();
-// const textContent = line.replace('## ', '');
-// nodes.push(schema.nodes.heading.create({ level: 1 }, processBoldText(textContent)));
-// plainText += textContent + '\n';
-// } else if (line.startsWith('- ')) {
-// const textContent = line.replace('- ', '');
-// currentListItems.push(
-// schema.nodes.list_item.create(
-// {},
-// schema.nodes.paragraph.create({}, processBoldText(textContent))
-// )
-// );
-// plainText += textContent + '\n';
-// } else {
-// flushListItems();
-// nodes.push(schema.nodes.paragraph.create({}, processBoldText(line)));
-// plainText += line + '\n';
-// }
-// } else {
-// flushListItems();
-// nodes.push(schema.nodes.paragraph.create());
-// plainText += '\n';
-// }
-// i++;
-// }
-// flushListItems();
-
-// const doc = schema.nodes.doc.create({}, nodes);
-// return { doc, plainText: plainText.trim() };
-// }
-
- async execute(args: ParametersType<typeof generateTutorialNodeToolParams>): Promise<Observation[]> {
- const chunkId = uuidv4();
- try {
- console.log('Executing with args:', args);
- const query = args.query;
- if (typeof query !== 'string' || !query.trim()) {
- return [{
- type: 'text',
- text: `<chunk chunk_id="${chunkId}" chunk_type="error">Invalid input: Query must be a non-empty string.</chunk>`
- }];
- }
-
- const markdownResponse = await gptAPICall(query);
- if (!markdownResponse || typeof markdownResponse !== 'string') {
- throw new Error('Invalid GPT API response');
- }
- console.log('Markdown response:', markdownResponse);
-
- // const { doc } = this.applyFormatting(markdownResponse);
- // const rtfData = {
- // doc: doc.toJSON(),
- // selection: { type: 'text', anchor: 1, head: 1 },
- // storedMarks: [],
- // };
- // const serializedData = JSON.stringify(rtfData);
- // console.log('Serialized data:', serializedData);
-
- const tutorialDoc: parsedDoc = {
- doc_type: 'text',
- data: markdownResponse,
- title: 'Tutorial Node',
- _width: 600,
- _layout_fitWidth: true,
- _layout_autoHeight: true,
- text_fontSize: '16px',
- };
-
- const createdDoc = this._createDocInDash(tutorialDoc);
- console.log('Created doc:', createdDoc);
- if (!createdDoc || !createdDoc[Id]) {
- throw new Error('Failed to create tutorial node');
- }
-
- return [{
- type: 'text',
- text: `<chunk chunk_id="${chunkId}" chunk_type="tutorial_node_creation">Created tutorial node with ID ${createdDoc[Id]}.</chunk>`
- }];
- } catch (error) {
- console.error('Error in GPTTutorialTool:', error);
- const errorMessage = error instanceof Error ? error.message : 'Unknown error';
- return [{
- type: 'text',
- text: `<chunk chunk_id="${chunkId}" chunk_type="error">Error generating tutorial node: ${errorMessage}</chunk>`
- }];
+ private _createDocInDash: (doc: parsedDoc) => Doc | undefined;
+
+ constructor(createDocInDash: (doc: parsedDoc) => Doc | undefined) {
+ super(generateTutorialNodeToolInfo);
+
+ this._createDocInDash = createDocInDash;
+ }
+
+ async execute(args: ParametersType<typeof generateTutorialNodeToolParams>): Promise<Observation[]> {
+ const chunkId = uuidv4();
+ try {
+ const query = (args.query || '').trim();
+ if (!query) {
+ return [{ type: 'text', text: `<chunk chunk_id="${chunkId}" chunk_type="error">Please provide a query.</chunk>` }];
+ }
+ const markdown = await gptTutorialAPICall(query);
+ const { doc, plainText } = applyFormatting(markdown);
+
+ // Build the ProseMirror‐in‐JSON + plain-text for RichTextField
+ const rtfData = {
+ doc: (doc as any).toJSON ? (doc as any).toJSON() : doc,
+ selection: { type: 'text', anchor: 0, head: 0 },
+ storedMarks: [],
+ };
+ const rtf = new RichTextField(JSON.stringify(rtfData), plainText);
+
+ // Create and show the TextDocument directly:
+ const formattedDoc = Docs.Create.TextDocument(rtf, {
+ title: 'Tutorial Node',
+ _width: 600,
+ _layout_fitWidth: true,
+ _layout_autoHeight: true,
+ text_fontSize: '16px',
+ });
+ DocumentViewInternal.addDocTabFunc(formattedDoc, OpenWhere.addRight);
+
+ // If user asked about linking/pinning/presentation, also fire the in-app tutorial:
+ const q = query.toLowerCase();
+ if (q.includes('link')) {
+ Doc.IsInfoUIDisabled = false;
+ CollectionFreeFormView.showTutorial('links');
+ } else if (q.includes('presentation')) {
+ Doc.IsInfoUIDisabled = false;
+ CollectionFreeFormView.showTutorial('presentation');
+ } else if (q.includes('pin')) {
+ Doc.IsInfoUIDisabled = false;
+ CollectionFreeFormView.showTutorial('pins');
+ }
+
+ return [
+ {
+ type: 'text',
+ text: `<chunk chunk_id="${chunkId}" chunk_type="tutorial_node_creation">Created tutorial node with ID ${formattedDoc[Id]}.</chunk>`,
+ },
+ ];
+ } catch (error) {
+ return [
+ {
+ type: 'text',
+ text: `<chunk chunk_id="${chunkId}" chunk_type="error">Error generating tutorial node: ${error}</chunk>`,
+ },
+ ];
+ }
}
- }
-} \ No newline at end of file
+}