소스 검색

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

Saoud Rizwan 1 년 전
부모
커밋
991ea6bd4e

+ 2 - 0
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"})
 		})
 	)
 

+ 16 - 7
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)

+ 8 - 0
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"
+}

+ 5 - 0
src/shared/WebviewMessage.ts

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

+ 17 - 31
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 ? <SettingsView /> : <ChatSidebar />}</>
 }

+ 21 - 9
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<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 (
-		<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" }}>
-				{messages.map((message) => (
+				{messages.map((message, index) => (
 					<div
 						key={message.id}
 						style={{

+ 26 - 27
webview-ui/src/components/Demo.tsx

@@ -1,34 +1,33 @@
 
-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,
+    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"
 
 function Demo() {
-	function handleHowdyClick() {
-		vscode.postMessage({
-			command: "hello",
-			text: "Hey there partner! 🤠",
-		})
-	}
+	// function handleHowdyClick() {
+	// 	vscode.postMessage({
+	// 		command: "hello",
+	// 		text: "Hey there partner! 🤠",
+	// 	})
+	// }
 
 	const rowData = [
 		{
@@ -54,7 +53,7 @@ function Demo() {
 	return (
 		<main>
 			<h1>Hello World!</h1>
-			<VSCodeButton onClick={handleHowdyClick}>Howdy!</VSCodeButton>
+			<VSCodeButton>Howdy!</VSCodeButton>
 
 			<div className="grid gap-3 p-2 place-items-start">
 				<VSCodeDataGrid>

+ 3 - 3
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",
 				}}>
 				<p>Made possible by the latest breakthroughs in Claude 3.5 Sonnet's agentic coding capabilities.</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">
-						github.com/saoudrizwan/claude-dev
+						https://github.com/saoudrizwan/claude-dev
 					</VSCodeLink>
 				</p>
 			</div>

+ 2 - 1
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 {

+ 5 - 2
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"]
 }