diff --git a/clients/vscode/.gitattributes b/clients/vscode/.gitattributes index 24a8e87939aa..dd67860bc1c8 100644 --- a/clients/vscode/.gitattributes +++ b/clients/vscode/.gitattributes @@ -1 +1,2 @@ *.png filter=lfs diff=lfs merge=lfs -text +*.svg filter=lfs diff=lfs merge=lfs -text \ No newline at end of file diff --git a/clients/vscode/assets/chat-panel.css b/clients/vscode/assets/chat-panel.css index 51bf78947437..c4968bbfd7e8 100644 --- a/clients/vscode/assets/chat-panel.css +++ b/clients/vscode/assets/chat-panel.css @@ -15,3 +15,34 @@ iframe { width: 100%; height: 100vh; } + +/* Static content page */ +.static-content { + padding: 0.65rem 1.2rem; +} + +.static-content .avatar { + display: flex; + align-items: center; +} + +.static-content .avatar img { + width: 1rem; + height: 1rem; + object-fit: contain; + border-radius: 100%; + margin-right: 0.4rem; + padding: 0.2rem; + border: 1px solid var(--vscode-editorWidget-border); + background-color: rgb(232, 226, 210); +} + +.static-content .title { + margin: 0.45rem 0 0; + font-size: 0.85rem; +} + +.static-content p { + line-height: 1.45; + margin: 0.45rem 0; +} diff --git a/clients/vscode/assets/side-logo.svg b/clients/vscode/assets/side-logo.svg index a9b3c53addf9..d15576636974 100644 --- a/clients/vscode/assets/side-logo.svg +++ b/clients/vscode/assets/side-logo.svg @@ -1 +1,3 @@ - \ No newline at end of file +version https://git-lfs.github.com/spec/v1 +oid sha256:4826ce8f21a55f8b020b23f491fc241f6bc275c709596898dddb44fd4c9bb8f3 +size 297 diff --git a/clients/vscode/assets/tabby.png b/clients/vscode/assets/tabby.png new file mode 100644 index 000000000000..54e71249c6e9 --- /dev/null +++ b/clients/vscode/assets/tabby.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:163ea1f583c171e1bdfed2c3a3c94347bd7956c2ad4a9f07a0c4edcdba53ddcd +size 46728 diff --git a/clients/vscode/src/chat/ChatViewProvider.ts b/clients/vscode/src/chat/ChatViewProvider.ts index 2dda4c48e405..e1510b0f4466 100644 --- a/clients/vscode/src/chat/ChatViewProvider.ts +++ b/clients/vscode/src/chat/ChatViewProvider.ts @@ -11,6 +11,7 @@ export class ChatViewProvider implements WebviewViewProvider { webview?: WebviewView; client?: ServerApi; private pendingMessages: ChatMessage[] = []; + private isReady = false; constructor( private readonly context: ExtensionContext, @@ -19,27 +20,19 @@ export class ChatViewProvider implements WebviewViewProvider { public async resolveWebviewView(webviewView: WebviewView) { this.webview = webviewView; + this.isReady = this.agent.status === "ready"; const extensionUri = this.context.extensionUri; webviewView.webview.options = { enableScripts: true, localResourceRoots: [extensionUri], }; - // FIXME: we need to wait for the server to be ready, consider rendering a loading indicator - if (this.agent.status !== "ready") { - await new Promise((resolve) => { - this.agent.on("didChangeStatus", (status) => { - if (status === "ready") { - resolve(); - } - }); - }); + + if (this.isReady) { + await this.renderChatPage(); + } else { + webviewView.webview.html = this.getWelcomeContent(); } - const serverInfo = await this.agent.fetchServerInfo(); - webviewView.webview.html = this.getWebviewContent(serverInfo); - this.agent.on("didUpdateServerInfo", (serverInfo: ServerInfo) => { - webviewView.webview.html = this.getWebviewContent(serverInfo); - }); this.client = createClient(webviewView, { navigate: async (context: Context) => { @@ -50,18 +43,27 @@ export class ChatViewProvider implements WebviewViewProvider { }, }); + this.agent.on("didChangeStatus", async (status) => { + if (status === "ready" && !this.isReady) { + this.isReady = true; + await this.renderChatPage(); + } + }); + + this.agent.on("didUpdateServerInfo", async (serverInfo: ServerInfo) => { + await this.renderChatPage(serverInfo); + }); + + // The event will not be triggered during the initial rendering. + webviewView.onDidChangeVisibility(async () => { + if (webviewView.visible) { + await this.initChatPage(); + } + }); + webviewView.webview.onDidReceiveMessage(async (message) => { if (message.action === "rendered") { - this.webview?.webview.postMessage({ action: "sync-theme" }); - this.pendingMessages.forEach((message) => this.client?.sendMessage(message)); - const serverInfo = await this.agent.fetchServerInfo(); - if (serverInfo.config.token) { - this.client?.init({ - fetcherOptions: { - authorization: serverInfo.config.token, - }, - }); - } + await this.initChatPage(); } }); @@ -72,6 +74,15 @@ export class ChatViewProvider implements WebviewViewProvider { }); } + private async renderChatPage(serverInfo?: ServerInfo) { + if (!serverInfo) { + serverInfo = await this.agent.fetchServerInfo(); + } + if (this.webview) { + this.webview.webview.html = this.getWebviewContent(serverInfo); + } + } + private isChatPanelAvailable(serverInfo: ServerInfo): boolean { if (!serverInfo.health || !serverInfo.health["webserver"] || !serverInfo.health["chat_model"]) { return false; @@ -94,24 +105,28 @@ export class ChatViewProvider implements WebviewViewProvider { return true; } + private async initChatPage() { + this.webview?.webview.postMessage({ action: "sync-theme" }); + this.pendingMessages.forEach((message) => this.client?.sendMessage(message)); + const serverInfo = await this.agent.fetchServerInfo(); + if (serverInfo.config.token) { + this.client?.init({ + fetcherOptions: { + authorization: serverInfo.config.token, + }, + }); + } + } + private getWebviewContent(serverInfo: ServerInfo) { if (!this.isChatPanelAvailable(serverInfo)) { - return ` - - - - - - -

Tabby is not available

- - - - `; + return this.getStaticContent(` +

Tabby is not available

+

Please update to the latest version of the Tabby server.

+

You also need to launch the server with the chat model enabled; for example, use --chat-model Mistral-7B.

+ `); } + const endpoint = serverInfo.config.endpoint; const styleUri = this.webview?.webview.asWebviewUri( Uri.joinPath(this.context.extensionUri, "assets", "chat-panel.css"), @@ -123,7 +138,6 @@ export class ChatViewProvider implements WebviewViewProvider { - Tabby