mirror of
https://github.com/pacnpal/Roo-Code.git
synced 2025-12-20 04:11:10 -05:00
Add secret and global state management + messaging between web and extension; persist API key and max num requests; add Welcome screen
This commit is contained in:
@@ -4,27 +4,70 @@ import "./App.css"
|
||||
import ChatSidebar from "./components/ChatSidebar"
|
||||
import SettingsView from "./components/SettingsView"
|
||||
import { ExtensionMessage } from "@shared/ExtensionMessage"
|
||||
import WelcomeView from "./components/WelcomeView"
|
||||
import { vscode } from "./utilities/vscode"
|
||||
|
||||
/*
|
||||
The contents of webviews however are created when the webview becomes visible and destroyed when the webview is moved into the background. Any state inside the webview will be lost when the webview is moved to a background tab.
|
||||
|
||||
The best way to solve this is to make your webview stateless. Use message passing to save off the webview's state and then restore the state when the webview becomes visible again.
|
||||
|
||||
|
||||
*/
|
||||
|
||||
const App: React.FC = () => {
|
||||
const [showSettings, setShowSettings] = useState(false)
|
||||
const [showWelcome, setShowWelcome] = useState<boolean>(false)
|
||||
const [apiKey, setApiKey] = useState<string>("")
|
||||
const [maxRequestsPerTask, setMaxRequestsPerTask] = useState<string>("")
|
||||
|
||||
useEffect(() => {
|
||||
vscode.postMessage({ type: "webviewDidLaunch" })
|
||||
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
|
||||
}
|
||||
// switch message.type
|
||||
switch (message.type) {
|
||||
case "webviewState":
|
||||
const shouldShowWelcome = !message.webviewState!.didOpenOnce || !message.webviewState!.apiKey
|
||||
setShowWelcome(shouldShowWelcome)
|
||||
setApiKey(message.webviewState!.apiKey || "")
|
||||
setMaxRequestsPerTask(
|
||||
message.webviewState!.maxRequestsPerTask !== undefined
|
||||
? message.webviewState!.maxRequestsPerTask.toString()
|
||||
: ""
|
||||
)
|
||||
break
|
||||
case "action":
|
||||
switch (message.action!) {
|
||||
case "settingsButtonTapped":
|
||||
setShowSettings(true)
|
||||
break
|
||||
case "plusButtonTapped":
|
||||
setShowSettings(false)
|
||||
break
|
||||
}
|
||||
break
|
||||
}
|
||||
})
|
||||
}, [])
|
||||
|
||||
return <>{showSettings ? <SettingsView /> : <ChatSidebar />}</>
|
||||
return (
|
||||
<>
|
||||
{showWelcome ? (
|
||||
<WelcomeView apiKey={apiKey} setApiKey={setApiKey} />
|
||||
) : showSettings ? (
|
||||
<SettingsView
|
||||
apiKey={apiKey}
|
||||
setApiKey={setApiKey}
|
||||
maxRequestsPerTask={maxRequestsPerTask}
|
||||
setMaxRequestsPerTask={setMaxRequestsPerTask}
|
||||
onDone={() => setShowSettings(false)}
|
||||
/>
|
||||
) : (
|
||||
<ChatSidebar />
|
||||
)}
|
||||
</>
|
||||
)
|
||||
}
|
||||
|
||||
export default App
|
||||
|
||||
@@ -1,12 +1,69 @@
|
||||
import React from "react"
|
||||
import React, { useEffect, useState } from "react"
|
||||
import { VSCodeTextField, VSCodeDivider, VSCodeLink, VSCodeButton } from "@vscode/webview-ui-toolkit/react"
|
||||
import { vscode } from "../utilities/vscode"
|
||||
|
||||
const SettingsView = () => {
|
||||
const handleDoneClick = () => {
|
||||
// Add your logic here for what should happen when the Done button is clicked
|
||||
console.log("Done button clicked")
|
||||
type SettingsViewProps = {
|
||||
apiKey: string
|
||||
setApiKey: React.Dispatch<React.SetStateAction<string>>
|
||||
maxRequestsPerTask: string
|
||||
setMaxRequestsPerTask: React.Dispatch<React.SetStateAction<string>>
|
||||
onDone: () => void // Define the type of the onDone prop
|
||||
}
|
||||
|
||||
const SettingsView = ({ apiKey, setApiKey, maxRequestsPerTask, setMaxRequestsPerTask, onDone }: SettingsViewProps) => {
|
||||
const [apiKeyErrorMessage, setApiKeyErrorMessage] = useState<string | undefined>(undefined)
|
||||
const [maxRequestsErrorMessage, setMaxRequestsErrorMessage] = useState<string | undefined>(undefined)
|
||||
|
||||
const disableDoneButton = apiKeyErrorMessage != null || maxRequestsErrorMessage != null
|
||||
|
||||
const handleApiKeyChange = (event: any) => {
|
||||
const input = event.target.value
|
||||
setApiKey(input)
|
||||
validateApiKey(input)
|
||||
}
|
||||
|
||||
const validateApiKey = (value: string) => {
|
||||
if (value.trim() === "") {
|
||||
setApiKeyErrorMessage("API Key cannot be empty")
|
||||
} else {
|
||||
setApiKeyErrorMessage(undefined)
|
||||
}
|
||||
}
|
||||
|
||||
const handleMaxRequestsChange = (event: any) => {
|
||||
const input = event.target.value
|
||||
setMaxRequestsPerTask(input)
|
||||
validateMaxRequests(input)
|
||||
}
|
||||
|
||||
const validateMaxRequests = (value: string | undefined) => {
|
||||
if (value?.trim()) {
|
||||
const num = Number(value)
|
||||
if (isNaN(num)) {
|
||||
setMaxRequestsErrorMessage("Maximum requests must be a number")
|
||||
} else if (num < 3 || num > 100) {
|
||||
setMaxRequestsErrorMessage("Maximum requests must be between 3 and 100")
|
||||
} else {
|
||||
setMaxRequestsErrorMessage(undefined)
|
||||
}
|
||||
} else {
|
||||
setMaxRequestsErrorMessage(undefined)
|
||||
}
|
||||
}
|
||||
|
||||
const handleSubmit = () => {
|
||||
vscode.postMessage({ type: "apiKey", text: apiKey })
|
||||
vscode.postMessage({ type: "maxRequestsPerTask", text: maxRequestsPerTask })
|
||||
|
||||
onDone()
|
||||
}
|
||||
|
||||
// validate as soon as the component is mounted
|
||||
useEffect(() => {
|
||||
validateApiKey(apiKey)
|
||||
validateMaxRequests(maxRequestsPerTask)
|
||||
}, [])
|
||||
|
||||
return (
|
||||
<div style={{ margin: "0 auto", paddingTop: "10px" }}>
|
||||
<div
|
||||
@@ -14,16 +71,32 @@ const SettingsView = () => {
|
||||
display: "flex",
|
||||
justifyContent: "space-between",
|
||||
alignItems: "center",
|
||||
marginBottom: "20px",
|
||||
marginBottom: "17px",
|
||||
}}>
|
||||
<h2 style={{ color: "var(--vscode-foreground)", margin: 0 }}>Settings</h2>
|
||||
<VSCodeButton onClick={handleDoneClick}>Done</VSCodeButton>
|
||||
<h3 style={{ color: "var(--vscode-foreground)", margin: 0 }}>Settings</h3>
|
||||
<VSCodeButton onClick={handleSubmit} disabled={disableDoneButton}>
|
||||
Done
|
||||
</VSCodeButton>
|
||||
</div>
|
||||
|
||||
<div style={{ marginBottom: "20px" }}>
|
||||
<VSCodeTextField style={{ width: "100%" }} placeholder="Enter your Anthropic API Key">
|
||||
<VSCodeTextField
|
||||
value={apiKey}
|
||||
style={{ width: "100%" }}
|
||||
placeholder="Enter your Anthropic API Key"
|
||||
onInput={handleApiKeyChange}>
|
||||
Anthropic API Key
|
||||
</VSCodeTextField>
|
||||
{apiKeyErrorMessage && (
|
||||
<p
|
||||
style={{
|
||||
fontSize: "12px",
|
||||
marginTop: "5px",
|
||||
color: "var(--vscode-errorForeground)",
|
||||
}}>
|
||||
{apiKeyErrorMessage}
|
||||
</p>
|
||||
)}
|
||||
<p
|
||||
style={{
|
||||
fontSize: "12px",
|
||||
@@ -38,9 +111,23 @@ const SettingsView = () => {
|
||||
</div>
|
||||
|
||||
<div style={{ marginBottom: "20px" }}>
|
||||
<VSCodeTextField style={{ width: "100%" }} placeholder="Enter maximum number of requests">
|
||||
<VSCodeTextField
|
||||
value={maxRequestsPerTask}
|
||||
style={{ width: "100%" }}
|
||||
placeholder="20"
|
||||
onInput={handleMaxRequestsChange}>
|
||||
Maximum # Requests Per Task
|
||||
</VSCodeTextField>
|
||||
{maxRequestsErrorMessage && (
|
||||
<p
|
||||
style={{
|
||||
fontSize: "12px",
|
||||
marginTop: "5px",
|
||||
color: "var(--vscode-errorForeground)",
|
||||
}}>
|
||||
{maxRequestsErrorMessage}
|
||||
</p>
|
||||
)}
|
||||
<p
|
||||
style={{
|
||||
fontSize: "12px",
|
||||
@@ -65,7 +152,7 @@ const SettingsView = () => {
|
||||
}}>
|
||||
<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<br/>"Build with Claude June 2024 contest"
|
||||
This project was made for Anthropic's "Build with Claude June 2024 contest"
|
||||
<VSCodeLink href="https://github.com/saoudrizwan/claude-dev">
|
||||
https://github.com/saoudrizwan/claude-dev
|
||||
</VSCodeLink>
|
||||
|
||||
91
webview-ui/src/components/WelcomeView.tsx
Normal file
91
webview-ui/src/components/WelcomeView.tsx
Normal file
@@ -0,0 +1,91 @@
|
||||
import React, { useState, useEffect } from "react"
|
||||
import { VSCodeButton, VSCodeTextField, VSCodeLink, VSCodeDivider } from "@vscode/webview-ui-toolkit/react"
|
||||
import { vscode } from "../utilities/vscode"
|
||||
|
||||
interface WelcomeViewProps {
|
||||
apiKey: string
|
||||
setApiKey: React.Dispatch<React.SetStateAction<string>>
|
||||
}
|
||||
|
||||
const WelcomeView: React.FC<WelcomeViewProps> = ({ apiKey, setApiKey }) => {
|
||||
const [apiKeyErrorMessage, setApiKeyErrorMessage] = useState<string | undefined>(undefined)
|
||||
|
||||
const disableLetsGoButton = apiKeyErrorMessage != null
|
||||
|
||||
const handleApiKeyChange = (event: any) => {
|
||||
const input = event.target.value
|
||||
setApiKey(input)
|
||||
validateApiKey(input)
|
||||
}
|
||||
|
||||
const validateApiKey = (value: string) => {
|
||||
if (value.trim() === "") {
|
||||
setApiKeyErrorMessage("API Key cannot be empty")
|
||||
} else {
|
||||
setApiKeyErrorMessage(undefined)
|
||||
}
|
||||
}
|
||||
|
||||
const handleSubmit = () => {
|
||||
vscode.postMessage({ type: "apiKey", text: apiKey })
|
||||
}
|
||||
|
||||
useEffect(() => {
|
||||
validateApiKey(apiKey)
|
||||
}, [])
|
||||
|
||||
return (
|
||||
<div style={{ maxWidth: "600px", margin: "0 auto", padding: "20px" }}>
|
||||
<h1 style={{ color: "var(--vscode-foreground)" }}>Hi, I'm Claude Dev</h1>
|
||||
<p>
|
||||
I can do all kinds of tasks thanks to the latest breakthroughs in Claude Sonnet 3.5's agentic coding
|
||||
capabilities. I am prompted to think through tasks step-by-step and have access to tools that let me get
|
||||
information about your project, read & write code, and execute terminal commands (with your permission,
|
||||
of course).
|
||||
</p>
|
||||
|
||||
<h3>Here are some cool things I can do:</h3>
|
||||
<ul>
|
||||
<li>Create new projects from scratch based on your requirements</li>
|
||||
<li>Debug and fix code issues in your existing projects</li>
|
||||
<li>Refactor and optimize your codebase</li>
|
||||
<li>Analyze your system's performance and suggest improvements</li>
|
||||
<li>Generate documentation for your code</li>
|
||||
<li>Set up and configure development environments</li>
|
||||
<li>Perform code reviews and suggest best practices</li>
|
||||
</ul>
|
||||
|
||||
<h3>To get started, this extension needs an Anthropic API key:</h3>
|
||||
<ol>
|
||||
<li>
|
||||
Go to{" "}
|
||||
<VSCodeLink href="https://console.anthropic.com/" style={{ display: "inline" }}>
|
||||
https://console.anthropic.com/
|
||||
</VSCodeLink>
|
||||
</li>
|
||||
<li>You may need to buy some credits (although Anthropic is offering $5 free credit for new users)</li>
|
||||
<li>Click 'Get API Keys' and create a new key for me (you can delete it any time)</li>
|
||||
</ol>
|
||||
|
||||
<VSCodeDivider />
|
||||
|
||||
<div style={{ marginTop: "20px", display: "flex", alignItems: "center" }}>
|
||||
<VSCodeTextField
|
||||
style={{ flexGrow: 1, marginRight: "10px" }}
|
||||
placeholder="Enter your Anthropic API Key"
|
||||
value={apiKey}
|
||||
onInput={handleApiKeyChange}
|
||||
/>
|
||||
<VSCodeButton onClick={handleSubmit} disabled={disableLetsGoButton}>
|
||||
Let's go!
|
||||
</VSCodeButton>
|
||||
</div>
|
||||
|
||||
<p style={{ fontSize: "12px", marginTop: "10px", color: "var(--vscode-descriptionForeground)" }}>
|
||||
Your API key is stored securely on your computer and used only for interacting with the Anthropic API.
|
||||
</p>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
export default WelcomeView
|
||||
@@ -14,4 +14,8 @@ body {
|
||||
}
|
||||
textarea:focus {
|
||||
outline: 1.5px solid var(--vscode-focusBorder, #007fd4);
|
||||
}
|
||||
}
|
||||
|
||||
vscode-button::part(control):focus {
|
||||
outline: none;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user