diff options
Diffstat (limited to 'src/client/views/nodes/chatbot/tools/TutorialTool.ts')
-rw-r--r-- | src/client/views/nodes/chatbot/tools/TutorialTool.ts | 339 |
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 +} |