From 991ea6bd4e9f40209364e6c7238aea4dc55d8ebe Mon Sep 17 00:00:00 2001 From: Saoud Rizwan <7799382+saoudrizwan@users.noreply.github.com> Date: Sun, 7 Jul 2024 06:22:00 -0400 Subject: [PATCH] Get communication working between extension and webview; add shared data types --- src/extension.ts | 2 + src/providers/SidebarProvider.ts | 23 +++++++--- src/shared/ExtensionMessage.ts | 8 ++++ src/shared/WebviewMessage.ts | 5 ++ webview-ui/src/App.tsx | 48 +++++++------------- webview-ui/src/components/ChatSidebar.tsx | 30 ++++++++---- webview-ui/src/components/Demo.tsx | 53 +++++++++++----------- webview-ui/src/components/SettingsView.tsx | 6 +-- webview-ui/src/utilities/vscode.ts | 3 +- webview-ui/tsconfig.json | 7 ++- 10 files changed, 105 insertions(+), 80 deletions(-) create mode 100644 src/shared/ExtensionMessage.ts create mode 100644 src/shared/WebviewMessage.ts diff --git a/src/extension.ts b/src/extension.ts index 5470ec7..e1654da 100644 --- a/src/extension.ts +++ b/src/extension.ts @@ -37,6 +37,7 @@ export function activate(context: vscode.ExtensionContext) { vscode.commands.registerCommand("claude-dev.plusButtonTapped", () => { const message = "claude-dev.plusButtonTapped!" vscode.window.showInformationMessage(message) + provider.postMessageToWebview({ type: "action", action: "plusButtonTapped"}) }) ) @@ -44,6 +45,7 @@ export function activate(context: vscode.ExtensionContext) { vscode.commands.registerCommand("claude-dev.settingsButtonTapped", () => { const message = "claude-dev.settingsButtonTapped!" vscode.window.showInformationMessage(message) + provider.postMessageToWebview({ type: "action", action: "settingsButtonTapped"}) }) ) diff --git a/src/providers/SidebarProvider.ts b/src/providers/SidebarProvider.ts index d09aee7..2f1949c 100644 --- a/src/providers/SidebarProvider.ts +++ b/src/providers/SidebarProvider.ts @@ -2,6 +2,8 @@ import { getUri } from "../utilities/getUri" import { getNonce } from "../utilities/getNonce" //import * as weather from "weather-js" import * as vscode from "vscode" +import { ExtensionMessage } from "../shared/ExtensionMessage" +import { WebviewMessage } from "../shared/WebviewMessage" /* https://github.com/microsoft/vscode-webview-ui-toolkit-samples/blob/main/default/weather-webview/src/providers/WeatherViewProvider.ts @@ -36,6 +38,11 @@ export class SidebarProvider implements vscode.WebviewViewProvider { this._setWebviewMessageListener(webviewView.webview) } + // Send any JSON serializable data to the react app + postMessageToWebview(message: ExtensionMessage) { + this._view?.webview.postMessage(message) + } + /** * Defines and returns the HTML that should be rendered within the webview panel. * @@ -112,14 +119,16 @@ export class SidebarProvider implements vscode.WebviewViewProvider { * @param context A reference to the extension context */ private _setWebviewMessageListener(webview: vscode.Webview) { - webview.onDidReceiveMessage((message: any) => { - const command = message.command - const text = message.text - - switch (command) { - case "hello": + webview.onDidReceiveMessage((message: WebviewMessage) => { + switch (message.type) { + case "text": // Code that should run in response to the hello message command - vscode.window.showInformationMessage(text) + vscode.window.showInformationMessage(message.text!) + + // Send a message to our webview. + // You can send any JSON serializable data. + // Could also do this in extension .ts + this.postMessageToWebview({ type: "text", text: `Extension: ${Date.now()}` }) return // Add more switch case statements here as more webview message commands // are created within the webview context (i.e. inside media/main.js) diff --git a/src/shared/ExtensionMessage.ts b/src/shared/ExtensionMessage.ts new file mode 100644 index 0000000..9f39a6c --- /dev/null +++ b/src/shared/ExtensionMessage.ts @@ -0,0 +1,8 @@ +// type that represents json data that is sent from extension to webview, called ExtensionMessage and has 'type' enum which can be 'plusButtonTapped' or 'settingsButtonTapped' or 'hello' + +// webview will hold state +export interface ExtensionMessage { + type: "text" | "action" + text?: string + action?: "plusButtonTapped" | "settingsButtonTapped" +} \ No newline at end of file diff --git a/src/shared/WebviewMessage.ts b/src/shared/WebviewMessage.ts new file mode 100644 index 0000000..f4bc3ab --- /dev/null +++ b/src/shared/WebviewMessage.ts @@ -0,0 +1,5 @@ +export interface WebviewMessage { + type: "text" | "action" + text?: string + action?: "newTaskButtonTapped" | "yesButtonTapped" | "noButtonTapped" | "executeButtonTapped" +} \ No newline at end of file diff --git a/webview-ui/src/App.tsx b/webview-ui/src/App.tsx index f81fd30..d6eb288 100644 --- a/webview-ui/src/App.tsx +++ b/webview-ui/src/App.tsx @@ -1,42 +1,28 @@ -import React, { useState } from "react" -import logo from "./logo.svg" +import React, { useEffect, useState } from "react" import "./App.css" -import { vscode } from "./utilities/vscode" -import { - VSCodeBadge, - VSCodeButton, - VSCodeCheckbox, - VSCodeDataGrid, - VSCodeDataGridCell, - VSCodeDataGridRow, - VSCodeDivider, - VSCodeDropdown, - VSCodeLink, - VSCodeOption, - VSCodePanels, - VSCodePanelTab, - VSCodePanelView, - VSCodeProgressRing, - VSCodeRadio, - VSCodeRadioGroup, - VSCodeTag, - VSCodeTextArea, - VSCodeTextField, -} from "@vscode/webview-ui-toolkit/react" import ChatSidebar from "./components/ChatSidebar" -import Demo from "./components/Demo" import SettingsView from "./components/SettingsView" +import { ExtensionMessage } from "@shared/ExtensionMessage" const App: React.FC = () => { - const [showSettings, setShowSettings] = useState(true) + const [showSettings, setShowSettings] = useState(false) - const handleHowdyClick = () => { - vscode.postMessage({ - command: "hello", - text: "Hey there partner! 🤠", + useEffect(() => { + window.addEventListener("message", (e: MessageEvent) => { + const message: ExtensionMessage = e.data + if (message.type === "action") { + switch (message.action!) { + case "settingsButtonTapped": + setShowSettings(true) + break + case "plusButtonTapped": + setShowSettings(false) + break + } + } }) - } + }, []) return <>{showSettings ? : } } diff --git a/webview-ui/src/components/ChatSidebar.tsx b/webview-ui/src/components/ChatSidebar.tsx index d60fb0a..ad0417d 100644 --- a/webview-ui/src/components/ChatSidebar.tsx +++ b/webview-ui/src/components/ChatSidebar.tsx @@ -2,9 +2,10 @@ import React, { useState, useRef, useEffect, useCallback, KeyboardEvent } from " import { VSCodeButton, VSCodeTextArea, VSCodeDivider, VSCodeTextField } from "@vscode/webview-ui-toolkit/react" import { vscode } from "../utilities/vscode" import DynamicTextArea from "react-textarea-autosize" +import { ExtensionMessage } from "@shared/ExtensionMessage" interface Message { - id: number + id: string text: string sender: "user" | "assistant" } @@ -26,17 +27,14 @@ const ChatSidebar = () => { const handleSendMessage = () => { if (inputValue.trim()) { const newMessage: Message = { - id: Date.now(), + id: `${Date.now()}-user`, text: inputValue.trim(), sender: "user", } - setMessages([...messages, newMessage]) + setMessages(currentMessages => [...currentMessages, newMessage]) setInputValue("") // Here you would typically send the message to your extension's backend - vscode.postMessage({ - command: "sendMessage", - text: newMessage.text, - }) + vscode.postMessage({ type: "text", text: newMessage.text}) } } const handleKeyDown = (event: KeyboardEvent) => { @@ -52,10 +50,24 @@ const ChatSidebar = () => { } }, []) + useEffect(() => { + window.addEventListener("message", (e: MessageEvent) => { + const message: ExtensionMessage = e.data + if (message.type === "text") { + const newMessage: Message = { + id: `${Date.now()}-assistant`, + text: message.text!.trim(), + sender: "assistant", + } + setMessages(currentMessages => [...currentMessages, newMessage]) + } + }) + }, []) + return ( -
+
- {messages.map((message) => ( + {messages.map((message, index) => (

Hello World!

- Howdy! + Howdy!
diff --git a/webview-ui/src/components/SettingsView.tsx b/webview-ui/src/components/SettingsView.tsx index 16fc79c..f190980 100644 --- a/webview-ui/src/components/SettingsView.tsx +++ b/webview-ui/src/components/SettingsView.tsx @@ -61,13 +61,13 @@ const SettingsView = () => { color: "var(--vscode-descriptionForeground)", fontSize: "12px", lineHeight: "1.5", - fontStyle: "italic" + fontStyle: "italic", }}>

Made possible by the latest breakthroughs in Claude 3.5 Sonnet's agentic coding capabilities.

- This project was submitted to Anthropic's "Build with Claude June 2024 contest". + This project was submitted to Anthropic's
"Build with Claude June 2024 contest" - github.com/saoudrizwan/claude-dev + https://github.com/saoudrizwan/claude-dev

diff --git a/webview-ui/src/utilities/vscode.ts b/webview-ui/src/utilities/vscode.ts index ab89936..7c754c5 100644 --- a/webview-ui/src/utilities/vscode.ts +++ b/webview-ui/src/utilities/vscode.ts @@ -1,3 +1,4 @@ +import { WebviewMessage } from "@shared/WebviewMessage" import type { WebviewApi } from "vscode-webview" /** @@ -28,7 +29,7 @@ class VSCodeAPIWrapper { * * @param message Abitrary data (must be JSON serializable) to send to the extension context. */ - public postMessage(message: unknown) { + public postMessage(message: WebviewMessage) { if (this.vsCodeApi) { this.vsCodeApi.postMessage(message) } else { diff --git a/webview-ui/tsconfig.json b/webview-ui/tsconfig.json index 2eae7b2..cde1010 100644 --- a/webview-ui/tsconfig.json +++ b/webview-ui/tsconfig.json @@ -14,7 +14,10 @@ "resolveJsonModule": true, "isolatedModules": true, "noEmit": true, - "jsx": "react-jsx" + "jsx": "react-jsx", + "paths": { + "@shared/*": ["../src/shared/*"] + } }, - "include": ["src"] + "include": ["src", "../src/shared"] }