Get communication working between extension and webview; add shared data types

This commit is contained in:
Saoud Rizwan
2024-07-07 06:22:00 -04:00
parent 08effc4799
commit 991ea6bd4e
10 changed files with 105 additions and 80 deletions

View File

@@ -37,6 +37,7 @@ export function activate(context: vscode.ExtensionContext) {
vscode.commands.registerCommand("claude-dev.plusButtonTapped", () => { vscode.commands.registerCommand("claude-dev.plusButtonTapped", () => {
const message = "claude-dev.plusButtonTapped!" const message = "claude-dev.plusButtonTapped!"
vscode.window.showInformationMessage(message) 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", () => { vscode.commands.registerCommand("claude-dev.settingsButtonTapped", () => {
const message = "claude-dev.settingsButtonTapped!" const message = "claude-dev.settingsButtonTapped!"
vscode.window.showInformationMessage(message) vscode.window.showInformationMessage(message)
provider.postMessageToWebview({ type: "action", action: "settingsButtonTapped"})
}) })
) )

View File

@@ -2,6 +2,8 @@ import { getUri } from "../utilities/getUri"
import { getNonce } from "../utilities/getNonce" import { getNonce } from "../utilities/getNonce"
//import * as weather from "weather-js" //import * as weather from "weather-js"
import * as vscode from "vscode" 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 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) 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. * 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 * @param context A reference to the extension context
*/ */
private _setWebviewMessageListener(webview: vscode.Webview) { private _setWebviewMessageListener(webview: vscode.Webview) {
webview.onDidReceiveMessage((message: any) => { webview.onDidReceiveMessage((message: WebviewMessage) => {
const command = message.command switch (message.type) {
const text = message.text case "text":
switch (command) {
case "hello":
// Code that should run in response to the hello message command // 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 return
// Add more switch case statements here as more webview message commands // Add more switch case statements here as more webview message commands
// are created within the webview context (i.e. inside media/main.js) // are created within the webview context (i.e. inside media/main.js)

View File

@@ -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"
}

View File

@@ -0,0 +1,5 @@
export interface WebviewMessage {
type: "text" | "action"
text?: string
action?: "newTaskButtonTapped" | "yesButtonTapped" | "noButtonTapped" | "executeButtonTapped"
}

View File

@@ -1,42 +1,28 @@
import React, { useState } from "react" import React, { useEffect, useState } from "react"
import logo from "./logo.svg"
import "./App.css" 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 ChatSidebar from "./components/ChatSidebar"
import Demo from "./components/Demo"
import SettingsView from "./components/SettingsView" import SettingsView from "./components/SettingsView"
import { ExtensionMessage } from "@shared/ExtensionMessage"
const App: React.FC = () => { const App: React.FC = () => {
const [showSettings, setShowSettings] = useState(true) const [showSettings, setShowSettings] = useState(false)
const handleHowdyClick = () => { useEffect(() => {
vscode.postMessage({ window.addEventListener("message", (e: MessageEvent) => {
command: "hello", const message: ExtensionMessage = e.data
text: "Hey there partner! 🤠", if (message.type === "action") {
}) switch (message.action!) {
case "settingsButtonTapped":
setShowSettings(true)
break
case "plusButtonTapped":
setShowSettings(false)
break
} }
}
})
}, [])
return <>{showSettings ? <SettingsView /> : <ChatSidebar />}</> return <>{showSettings ? <SettingsView /> : <ChatSidebar />}</>
} }

View File

@@ -2,9 +2,10 @@ import React, { useState, useRef, useEffect, useCallback, KeyboardEvent } from "
import { VSCodeButton, VSCodeTextArea, VSCodeDivider, VSCodeTextField } from "@vscode/webview-ui-toolkit/react" import { VSCodeButton, VSCodeTextArea, VSCodeDivider, VSCodeTextField } from "@vscode/webview-ui-toolkit/react"
import { vscode } from "../utilities/vscode" import { vscode } from "../utilities/vscode"
import DynamicTextArea from "react-textarea-autosize" import DynamicTextArea from "react-textarea-autosize"
import { ExtensionMessage } from "@shared/ExtensionMessage"
interface Message { interface Message {
id: number id: string
text: string text: string
sender: "user" | "assistant" sender: "user" | "assistant"
} }
@@ -26,17 +27,14 @@ const ChatSidebar = () => {
const handleSendMessage = () => { const handleSendMessage = () => {
if (inputValue.trim()) { if (inputValue.trim()) {
const newMessage: Message = { const newMessage: Message = {
id: Date.now(), id: `${Date.now()}-user`,
text: inputValue.trim(), text: inputValue.trim(),
sender: "user", sender: "user",
} }
setMessages([...messages, newMessage]) setMessages(currentMessages => [...currentMessages, newMessage])
setInputValue("") setInputValue("")
// Here you would typically send the message to your extension's backend // Here you would typically send the message to your extension's backend
vscode.postMessage({ vscode.postMessage({ type: "text", text: newMessage.text})
command: "sendMessage",
text: newMessage.text,
})
} }
} }
const handleKeyDown = (event: KeyboardEvent<HTMLTextAreaElement>) => { const handleKeyDown = (event: KeyboardEvent<HTMLTextAreaElement>) => {
@@ -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 ( return (
<div style={{ display: "flex", flexDirection: "column", height: "100vh", backgroundColor: "gray", overflow: "hidden" }}> <div style={{ display: "flex", flexDirection: "column", height: "100vh", overflow: "hidden" }}>
<div style={{ flexGrow: 1, overflowY: "scroll", scrollbarWidth: "none" }}> <div style={{ flexGrow: 1, overflowY: "scroll", scrollbarWidth: "none" }}>
{messages.map((message) => ( {messages.map((message, index) => (
<div <div
key={message.id} key={message.id}
style={{ style={{

View File

@@ -1,5 +1,4 @@
import { vscode } from "../utilities/vscode"
import { import {
VSCodeBadge, VSCodeBadge,
VSCodeButton, VSCodeButton,
@@ -23,12 +22,12 @@ import {
} from "@vscode/webview-ui-toolkit/react" } from "@vscode/webview-ui-toolkit/react"
function Demo() { function Demo() {
function handleHowdyClick() { // function handleHowdyClick() {
vscode.postMessage({ // vscode.postMessage({
command: "hello", // command: "hello",
text: "Hey there partner! 🤠", // text: "Hey there partner! 🤠",
}) // })
} // }
const rowData = [ const rowData = [
{ {
@@ -54,7 +53,7 @@ function Demo() {
return ( return (
<main> <main>
<h1>Hello World!</h1> <h1>Hello World!</h1>
<VSCodeButton onClick={handleHowdyClick}>Howdy!</VSCodeButton> <VSCodeButton>Howdy!</VSCodeButton>
<div className="grid gap-3 p-2 place-items-start"> <div className="grid gap-3 p-2 place-items-start">
<VSCodeDataGrid> <VSCodeDataGrid>

View File

@@ -61,13 +61,13 @@ const SettingsView = () => {
color: "var(--vscode-descriptionForeground)", color: "var(--vscode-descriptionForeground)",
fontSize: "12px", fontSize: "12px",
lineHeight: "1.5", lineHeight: "1.5",
fontStyle: "italic" fontStyle: "italic",
}}> }}>
<p>Made possible by the latest breakthroughs in Claude 3.5 Sonnet's agentic coding capabilities.</p> <p>Made possible by the latest breakthroughs in Claude 3.5 Sonnet's agentic coding capabilities.</p>
<p> <p>
This project was submitted to Anthropic's "Build with Claude June 2024 contest". This project was submitted to Anthropic's<br/>"Build with Claude June 2024 contest"
<VSCodeLink href="https://github.com/saoudrizwan/claude-dev"> <VSCodeLink href="https://github.com/saoudrizwan/claude-dev">
github.com/saoudrizwan/claude-dev https://github.com/saoudrizwan/claude-dev
</VSCodeLink> </VSCodeLink>
</p> </p>
</div> </div>

View File

@@ -1,3 +1,4 @@
import { WebviewMessage } from "@shared/WebviewMessage"
import type { WebviewApi } from "vscode-webview" 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. * @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) { if (this.vsCodeApi) {
this.vsCodeApi.postMessage(message) this.vsCodeApi.postMessage(message)
} else { } else {

View File

@@ -14,7 +14,10 @@
"resolveJsonModule": true, "resolveJsonModule": true,
"isolatedModules": true, "isolatedModules": true,
"noEmit": true, "noEmit": true,
"jsx": "react-jsx" "jsx": "react-jsx",
}, "paths": {
"include": ["src"] "@shared/*": ["../src/shared/*"]
}
},
"include": ["src", "../src/shared"]
} }