From 301814bcddb160861a68d5e778e16caf60988d38 Mon Sep 17 00:00:00 2001 From: Elio Struyf Date: Thu, 6 Jun 2024 17:13:03 +0200 Subject: [PATCH] #816 - Sample implementation --- assets/icons/frontmatter-short-teal.svg | 17 +++ package-lock.json | 10 +- package.json | 22 +++- src/commands/Chatbot.ts | 135 +++++++++++++++++++++++- src/extension.ts | 3 + 5 files changed, 178 insertions(+), 9 deletions(-) create mode 100644 assets/icons/frontmatter-short-teal.svg diff --git a/assets/icons/frontmatter-short-teal.svg b/assets/icons/frontmatter-short-teal.svg new file mode 100644 index 00000000..bd32c12b --- /dev/null +++ b/assets/icons/frontmatter-short-teal.svg @@ -0,0 +1,17 @@ + + + + + + + + + + + + diff --git a/package-lock.json b/package-lock.json index ccdf1577..5f6bb328 100644 --- a/package-lock.json +++ b/package-lock.json @@ -33,7 +33,7 @@ "@types/react": "17.0.0", "@types/react-datepicker": "^4.8.0", "@types/react-dom": "17.0.0", - "@types/vscode": "^1.73.0", + "@types/vscode": "^1.90.0", "@typescript-eslint/eslint-plugin": "^5.50.0", "@typescript-eslint/parser": "^5.50.0", "@vscode-elements/elements": "^1.2.0", @@ -110,7 +110,7 @@ "yawn-yaml": "^1.5.0" }, "engines": { - "vscode": "^1.73.0" + "vscode": "^1.90.0" } }, "node_modules/@aashutoshrathi/word-wrap": { @@ -2084,9 +2084,9 @@ "dev": true }, "node_modules/@types/vscode": { - "version": "1.86.0", - "resolved": "https://registry.npmjs.org/@types/vscode/-/vscode-1.86.0.tgz", - "integrity": "sha512-DnIXf2ftWv+9LWOB5OJeIeaLigLHF7fdXF6atfc7X5g2w/wVZBgk0amP7b+ub5xAuW1q7qP5YcFvOcit/DtyCQ==", + "version": "1.90.0", + "resolved": "https://registry.npmjs.org/@types/vscode/-/vscode-1.90.0.tgz", + "integrity": "sha512-oT+ZJL7qHS9Z8bs0+WKf/kQ27qWYR3trsXpq46YDjFqBsMLG4ygGGjPaJ2tyrH0wJzjOEmDyg9PDJBBhWg9pkQ==", "dev": true }, "node_modules/@types/vscode-webview": { diff --git a/package.json b/package.json index b6529f63..0e06ac23 100644 --- a/package.json +++ b/package.json @@ -26,10 +26,13 @@ }, "qna": "https://github.com/estruyf/vscode-front-matter/discussions", "engines": { - "vscode": "^1.73.0" + "vscode": "^1.90.0" }, "l10n": "./l10n", "categories": [ + "AI", + "Chat", + "Visualization", "Other" ], "keywords": [ @@ -69,6 +72,21 @@ "**/.frontmatter/config/*.json": "jsonc" } }, + "chatParticipants": [{ + "id": "frontMatter.chat", + "name": "fm", + "fullName": "Front Matter", + "description": "Front Matter CMS chat", + "isSticky": true, + "commands": [{ + "name": "docs", + "description": "Ask a question about Front Matter CMS and we will try to help you out." + }, { + "name": "create", + "description": "Create a new Front Matter CMS project.", + "isSticky": true + }] + }], "keybindings": [{ "command": "frontMatter.dashboard", "key": "alt+d" @@ -2768,7 +2786,7 @@ "@types/react": "17.0.0", "@types/react-datepicker": "^4.8.0", "@types/react-dom": "17.0.0", - "@types/vscode": "^1.73.0", + "@types/vscode": "^1.90.0", "@typescript-eslint/eslint-plugin": "^5.50.0", "@typescript-eslint/parser": "^5.50.0", "@vscode-elements/elements": "^1.2.0", diff --git a/src/commands/Chatbot.ts b/src/commands/Chatbot.ts index 856e3424..0fc8923e 100644 --- a/src/commands/Chatbot.ts +++ b/src/commands/Chatbot.ts @@ -1,14 +1,99 @@ import { Telemetry } from './../helpers/Telemetry'; -import { TelemetryEvent, PreviewCommands, GeneralCommands } from './../constants'; +import { TelemetryEvent, PreviewCommands, GeneralCommands, WEBSITE_LINKS, COMMAND_NAME } from './../constants'; import { join } from 'path'; -import { commands, Uri, ViewColumn, window } from 'vscode'; +import { + CancellationToken, + chat, + ChatContext, + ChatRequest, + ChatResponseStream, + commands, + LanguageModelChatMessage, + lm, + Uri, + ViewColumn, + window +} from 'vscode'; import { Extension } from '../helpers'; import { WebviewHelper } from '@estruyf/vscode'; import { getLocalizationFile } from '../utils/getLocalizationFile'; import * as l10n from '@vscode/l10n'; import { LocalizationKey } from '../localization'; +import { Folders } from '.'; export class Chatbot { + private static company: string; + private static chatId: number; + + public static async register() { + const fmChat = chat.createChatParticipant('frontMatter.chat', this.handler); + fmChat.iconPath = Uri.joinPath( + Extension.getInstance().extensionPath, + '/assets/icons/frontmatter-short-teal.svg' + ); + } + + private static async handler( + request: ChatRequest, + context: ChatContext, + stream: ChatResponseStream, + token: CancellationToken + ) { + const { command, prompt } = request; + + if (command === 'docs' && prompt) { + if (!this.chatId || !this.company) { + stream.progress('Initializing the Front Matter AI...'); + + await this.initChat(); + } + + if (!this.company || !this.chatId) { + this.showAiError(stream); + return; + } + + stream.progress('Fetching information from the docs...'); + + const data: { + answer: string; + answerId: number; + sources: string[]; + } = await this.retrieveDocs(prompt); + + if (!data.answer) { + this.showAiError(stream); + return; + } + + stream.markdown(data.answer); + } else if (command === 'create') { + stream.progress(`Checking your configuration...`); + + const messages = [LanguageModelChatMessage.User(`You are a kind and helpful assistant named Front Matter AI. You aim to provide support in managing Front Matter CMS and its content. If you don't know the answer to a question, you will guide the user to the documentation (https://frontmatter.codes/docs). You can use the "https://frontmatter.codes/docs" link as a reference. When returning code you will surround it in a MD code block, not HTML.`)]; + + messages.push(LanguageModelChatMessage.User(request.prompt)); + + const [model] = await lm.selectChatModels({ vendor: 'copilot', family: 'gpt-4' }); + + try { + const chatResponse = await model.sendRequest(messages, {}, token); + for await (const fragment of chatResponse.text) { + stream.markdown(fragment); + } + stream.button({ + command: COMMAND_NAME.createByContentType, + title: 'Create new content', + }); + } catch (err) { + this.showAiError(stream); + return; + } + } else { + stream.markdown('Sorry, I do not understand that command. Check out the [Front Matter CMS](https://frontmatter.codes/docs) documentation for more information.') + } + } + /** * Open the Chatbot in the editor */ @@ -119,4 +204,50 @@ export class Chatbot { Telemetry.send(TelemetryEvent.openChatbot); } + + private static async initChat() { + const response = await fetch(`${WEBSITE_LINKS.root}/api/ai-init`); + + if (!response.ok) { + return; + } + + const data = await response.json(); + + if (!data.company || !data.chatId) { + return; + } + + this.company = data.company; + this.chatId = data.chatId; + } + + private static async retrieveDocs(prompt: string) { + if (!this.company || !this.chatId) { + return; + } + + const response = await fetch(`${WEBSITE_LINKS.root}/api/ai-chat`, { + method: 'POST', + headers: { + 'Content-Type': 'application/json', + 'accept': '*', + }, + body: JSON.stringify({ + company: this.company, + chatId: this.chatId, + question: prompt, + }), + }); + + if (!response.ok) { + return; + } + + return await response.json(); + } + + private static showAiError(stream: ChatResponseStream) { + stream.markdown(`Sorry, I am not able to connect to the Front Matter AI service. Please try again later or check out the [Front Matter CMS](https://frontmatter.codes/docs) documentation.`); + } } diff --git a/src/extension.ts b/src/extension.ts index f4a872f8..4c33cff6 100644 --- a/src/extension.ts +++ b/src/extension.ts @@ -241,6 +241,9 @@ export async function activate(context: vscode.ExtensionContext) { // Cache commands Cache.registerCommands(); + // GitHub Copilot integration + Chatbot.register(); + // Subscribe all commands subscriptions.push(insertTags, PanelView, insertCategories, collapseAll, fmStatusBarItem);