diff --git a/clients/vscode/package.json b/clients/vscode/package.json
index 8d70d9bbfec9..8b70a0a07b3d 100644
--- a/clients/vscode/package.json
+++ b/clients/vscode/package.json
@@ -28,7 +28,8 @@
"Other"
],
"activationEvents": [
- "onStartupFinished"
+ "onStartupFinished",
+ "onView:chatView"
],
"main": "./dist/node/extension.js",
"browser": "./dist/web/extension.js",
@@ -241,7 +242,26 @@
"key": "escape",
"when": "inlineSuggestionVisible"
}
- ]
+ ],
+ "viewsContainers": {
+ "activitybar": [
+ {
+ "id": "chatView",
+ "title": "Tabby",
+ "icon": "assets/logo.png"
+ }
+ ]
+ },
+ "views": {
+ "chatView": [
+ {
+ "type": "webview",
+ "id": "tabby.chatView",
+ "name": "",
+ "visibility": "visible"
+ }
+ ]
+ }
},
"scripts": {
"prebuild": "cd ../tabby-agent && yarn build && cd ../tabby-chat-panel && yarn build:vscode",
diff --git a/clients/vscode/src/ChatViewProvider.ts b/clients/vscode/src/ChatViewProvider.ts
new file mode 100644
index 000000000000..c92e3edb490b
--- /dev/null
+++ b/clients/vscode/src/ChatViewProvider.ts
@@ -0,0 +1,206 @@
+import { ExtensionContext, Disposable, Webview, WebviewPanel, window, Uri, ViewColumn, WebviewViewProvider, WebviewView, TextDocument } from "vscode";
+import { getUri, getNonce } from "./utils";
+
+import { createAgentInstance, disposeAgentInstance } from "./agent";
+
+/**
+ * This class manages the state and behavior of HelloWorld webview panels.
+ *
+ * It contains all the data and methods for:
+ *
+ * - Creating and rendering HelloWorld webview panels
+ * - Properly cleaning up and disposing of webview resources when the panel is closed
+ * - Setting the HTML (and by proxy CSS/JavaScript) content of the webview panel
+ * - Setting message listeners so data can be passed between the webview and extension
+ */
+export class ChatViewProvider implements WebviewViewProvider {
+ _view?: WebviewView;
+ _doc?: TextDocument;
+ private readonly _extensionUri: Uri
+ private readonly _context: ExtensionContext
+
+ // public static currentPanel: ChatViewPanel | undefined;
+ // private readonly _panel: WebviewPanel;
+ // private _disposables: Disposable[] = [];
+
+ /**
+ * The ChatViewPanel class private constructor (called only from the render method).
+ *
+ * @param panel A reference to the webview panel
+ * @param extensionUri The URI of the directory containing the extension
+ */
+ // private constructor(panel: WebviewPanel, extensionUri: Uri) {
+ // this._panel = panel;
+
+ // // Set an event listener to listen for when the panel is disposed (i.e. when the user closes
+ // // the panel or when the panel is closed programmatically)
+ // this._panel.onDidDispose(() => this.dispose(), null, this._disposables);
+
+ // // Set the HTML content for the webview panel
+ // this._panel.webview.html = this._getWebviewContent(this._panel.webview, extensionUri);
+
+ // // Set an event listener to listen for messages passed from the webview context
+ // this._setWebviewMessageListener(this._panel.webview);
+ // }
+
+ constructor(context: ExtensionContext) {
+ this._extensionUri = context.extensionUri
+ this._context = context;
+ }
+
+ /**
+ * Renders the current webview panel if it exists otherwise a new webview panel
+ * will be created and displayed.
+ *
+ * @param extensionUri The URI of the directory containing the extension.
+ */
+ // public static render(extensionUri: Uri) {
+ // if (ChatViewPanel.currentPanel) {
+ // // If the webview panel already exists reveal it
+ // ChatViewPanel.currentPanel._panel.reveal(ViewColumn.One);
+ // } else {
+ // // If a webview panel does not already exist create and show a new one
+ // const panel = window.createWebviewPanel(
+ // // Panel view type
+ // "showHelloWorld",
+ // // Panel title
+ // "Hello World",
+ // // The editor column the panel should be displayed in
+ // ViewColumn.One,
+ // // Extra panel configurations
+ // {
+ // // Enable JavaScript in the webview
+ // enableScripts: true,
+ // // Restrict the webview to only load resources from the `out` and `webview-ui/build` directories
+ // localResourceRoots: [Uri.joinPath(extensionUri, "out"), Uri.joinPath(extensionUri, "webview/build")],
+ // }
+ // );
+
+ // ChatViewPanel.currentPanel = new ChatViewPanel(panel, extensionUri);
+ // }
+ // }
+
+ public async resolveWebviewView(webviewView: WebviewView) {
+ this._view = webviewView;
+
+ webviewView.webview.options = {
+ enableScripts: true,
+ localResourceRoots: [this._extensionUri],
+ };
+ webviewView.webview.html = await this._getWebviewContent(webviewView.webview, this._extensionUri);
+ webviewView.webview.onDidReceiveMessage((data) => {
+ console.log('onDidReceiveMessage data', data)
+ switch (data.command) {
+ case 'explainThis': {
+ const { text, language } = data;
+ console.log('text', text)
+ console.log('language', language)
+ return;
+ }
+ }
+ });
+ console.log('resolveWebviewView this._view', this._view)
+
+}
+
+ /**
+ * Cleans up and disposes of webview resources when the webview panel is closed.
+ */
+ // public dispose() {
+ // ChatViewPanel.currentPanel = undefined;
+
+ // // Dispose of the current webview panel
+ // this._panel.dispose();
+
+ // // Dispose of all disposables (i.e. commands) for the current webview panel
+ // while (this._disposables.length) {
+ // const disposable = this._disposables.pop();
+ // if (disposable) {
+ // disposable.dispose();
+ // }
+ // }
+ // }
+
+ /**
+ * Defines and returns the HTML that should be rendered within the webview panel.
+ *
+ * @remarks This is also the place where references to the React webview build files
+ * are created and inserted into the webview HTML.
+ *
+ * @param webview A reference to the extension webview
+ * @param extensionUri The URI of the directory containing the extension
+ * @returns A template string literal containing the HTML that should be
+ * rendered within the webview panel
+ */
+ private async _getWebviewContent(webview: Webview, extensionUri: Uri) {
+ const agent = await createAgentInstance(this._context);
+ const { server } = agent.getConfig()
+ const scriptUri = getUri(webview, extensionUri, ['webview', "index.js"]);
+ // Tip: Install the es6-string-html VS Code extension to enable code highlighting below
+
+ console.log(server.endpoint)
+ return /*html*/ `
+
+
+
+
+
+ Tabby
+
+
+
+
+
+
+
+
+
+ `;
+ }
+
+ /**
+ * Sets up an event listener to listen for messages passed from the webview context and
+ * executes code based on the message that is recieved.
+ *
+ * @param webview A reference to the extension webview
+ * @param context A reference to the extension context
+ */
+ // private _setWebviewMessageListener(webview: Webview) {
+ // webview.onDidReceiveMessage(
+ // (message: any) => {
+ // const command = message.command;
+ // const text = message.text;
+
+ // switch (command) {
+ // case "hello":
+ // // Code that should run in response to the hello message command
+ // window.showInformationMessage(text);
+ // return;
+ // // Add more switch case statements here as more webview message commands
+ // // are created within the webview context (i.e. inside media/main.js)
+ // }
+ // },
+ // undefined,
+ // this._disposables
+ // );
+ // }
+
+ public revive(panel: WebviewView) {
+ this._view = panel;
+ }
+
+ public postMessage (message: any) {
+ if (this._view) {
+ console.log('this._view exist')
+ console.log('this._view?.webview.', this._view?.webview)
+ this._view?.webview.postMessage(message)
+ }
+ }
+}
\ No newline at end of file
diff --git a/clients/vscode/src/extension.ts b/clients/vscode/src/extension.ts
index 36ccc72266f6..5e1762002080 100644
--- a/clients/vscode/src/extension.ts
+++ b/clients/vscode/src/extension.ts
@@ -1,12 +1,13 @@
// The module 'vscode' contains the VS Code extensibility API
// Import the module and reference it with the alias vscode in your code below
-import { ExtensionContext, commands, languages, workspace } from "vscode";
+import { ExtensionContext, commands, languages, workspace, window } from "vscode";
import { getLogger } from "./logger";
import { createAgentInstance, disposeAgentInstance } from "./agent";
import { tabbyCommands } from "./commands";
import { TabbyCompletionProvider } from "./TabbyCompletionProvider";
import { TabbyStatusBarItem } from "./TabbyStatusBarItem";
import { RecentlyChangedCodeSearch } from "./RecentlyChangedCodeSearch";
+import { ChatViewProvider } from "./ChatViewProvider";
const logger = getLogger();
@@ -39,6 +40,18 @@ export async function activate(context: ExtensionContext) {
const statusBarItem = new TabbyStatusBarItem(context, completionProvider);
context.subscriptions.push(statusBarItem.register());
+ // Register the Sidebar Panel
+ const chatViewProvider = new ChatViewProvider(context);
+ context.subscriptions.push(
+ window.registerWebviewViewProvider(
+ "tabby.chatView",
+ chatViewProvider,
+ {
+ webviewOptions: { retainContextWhenHidden: true },
+ },
+ )
+ );
+
context.subscriptions.push(...tabbyCommands(context, completionProvider, statusBarItem));
const updateIsChatEnabledContextVariable = () => {
diff --git a/clients/vscode/src/utils.ts b/clients/vscode/src/utils.ts
index 0cd9eb308e70..049a9ae459f6 100644
--- a/clients/vscode/src/utils.ts
+++ b/clients/vscode/src/utils.ts
@@ -1,4 +1,4 @@
-import { commands, Position, Range, SemanticTokens, SemanticTokensLegend, TextDocument } from "vscode";
+import { commands, Position, Range, SemanticTokens, SemanticTokensLegend, TextDocument, Uri, Webview } from "vscode";
export type SemanticSymbolInfo = {
position: Position;
@@ -133,3 +133,23 @@ export function extractNonReservedWordList(text: string): string {
...new Set(text.match(re)?.filter((symbol) => symbol.length > 2 && !reservedKeywords.includes(symbol))).values(),
].join(" ");
}
+
+/**
+ * This function is primarily used to help enforce content security
+ * policies for resources/scripts being executed in a webview context.
+ */
+export function getNonce() {
+ let text = "";
+ const possible = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789";
+ for (let i = 0; i < 32; i++) {
+ text += possible.charAt(Math.floor(Math.random() * possible.length));
+ }
+ return text;
+}
+
+/**
+ * A helper function which will get the webview URI of a given file or resource.
+ */
+export function getUri(webview: Webview, extensionUri: Uri, pathList: string[]) {
+ return webview.asWebviewUri(Uri.joinPath(extensionUri, ...pathList));
+}
diff --git a/yarn.lock b/yarn.lock
index 4101f288a88b..e36547362c79 100644
--- a/yarn.lock
+++ b/yarn.lock
@@ -3888,7 +3888,16 @@ stream-shift@^1.0.0:
resolved "https://registry.yarnpkg.com/stream-shift/-/stream-shift-1.0.1.tgz#d7088281559ab2778424279b0877da3c392d5a3d"
integrity sha512-AiisoFqQ0vbGcZgQPY1cdP2I76glaVA/RauYR4G4thNFgkTqr90yXTo4LYX60Jl+sIlPNHHdGSwo01AvbKUSVQ==
-"string-width-cjs@npm:string-width@^4.2.0", string-width@^4.1.0, string-width@^4.2.0:
+"string-width-cjs@npm:string-width@^4.2.0":
+ version "4.2.3"
+ resolved "https://registry.yarnpkg.com/string-width/-/string-width-4.2.3.tgz#269c7117d27b05ad2e536830a8ec895ef9c6d010"
+ integrity sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==
+ dependencies:
+ emoji-regex "^8.0.0"
+ is-fullwidth-code-point "^3.0.0"
+ strip-ansi "^6.0.1"
+
+string-width@^4.1.0, string-width@^4.2.0:
version "4.2.3"
resolved "https://registry.yarnpkg.com/string-width/-/string-width-4.2.3.tgz#269c7117d27b05ad2e536830a8ec895ef9c6d010"
integrity sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==
@@ -3920,7 +3929,14 @@ string_decoder@~1.1.1:
dependencies:
safe-buffer "~5.1.0"
-"strip-ansi-cjs@npm:strip-ansi@^6.0.1", strip-ansi@^6.0.0, strip-ansi@^6.0.1:
+"strip-ansi-cjs@npm:strip-ansi@^6.0.1":
+ version "6.0.1"
+ resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-6.0.1.tgz#9e26c63d30f53443e9489495b2105d37b67a85d9"
+ integrity sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==
+ dependencies:
+ ansi-regex "^5.0.1"
+
+strip-ansi@^6.0.0, strip-ansi@^6.0.1:
version "6.0.1"
resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-6.0.1.tgz#9e26c63d30f53443e9489495b2105d37b67a85d9"
integrity sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==
@@ -4433,7 +4449,16 @@ workerpool@6.2.1:
resolved "https://registry.yarnpkg.com/workerpool/-/workerpool-6.2.1.tgz#46fc150c17d826b86a008e5a4508656777e9c343"
integrity sha512-ILEIE97kDZvF9Wb9f6h5aXK4swSlKGUcOEGiIYb2OOu/IrDU9iwj0fD//SsA6E5ibwJxpEvhullJY4Sl4GcpAw==
-"wrap-ansi-cjs@npm:wrap-ansi@^7.0.0", wrap-ansi@^7.0.0:
+"wrap-ansi-cjs@npm:wrap-ansi@^7.0.0":
+ version "7.0.0"
+ resolved "https://registry.yarnpkg.com/wrap-ansi/-/wrap-ansi-7.0.0.tgz#67e145cff510a6a6984bdf1152911d69d2eb9e43"
+ integrity sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==
+ dependencies:
+ ansi-styles "^4.0.0"
+ string-width "^4.1.0"
+ strip-ansi "^6.0.0"
+
+wrap-ansi@^7.0.0:
version "7.0.0"
resolved "https://registry.yarnpkg.com/wrap-ansi/-/wrap-ansi-7.0.0.tgz#67e145cff510a6a6984bdf1152911d69d2eb9e43"
integrity sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==