mirror of
https://github.com/pacnpal/Roo-Code.git
synced 2025-12-20 04:11:10 -05:00
Add support for OpenRouter and AWS Bedrock
This commit is contained in:
@@ -6,6 +6,7 @@ import { ClaudeMessage, ExtensionMessage } from "@shared/ExtensionMessage"
|
||||
import WelcomeView from "./components/WelcomeView"
|
||||
import { vscode } from "./utilities/vscode"
|
||||
import { useEvent } from "react-use"
|
||||
import { ApiConfiguration } from "@shared/api"
|
||||
|
||||
/*
|
||||
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.
|
||||
@@ -19,7 +20,7 @@ const App: React.FC = () => {
|
||||
const [didHydrateState, setDidHydrateState] = useState(false)
|
||||
const [showSettings, setShowSettings] = useState(false)
|
||||
const [showWelcome, setShowWelcome] = useState<boolean>(false)
|
||||
const [apiKey, setApiKey] = useState<string>("")
|
||||
const [apiConfiguration, setApiConfiguration] = useState<ApiConfiguration | undefined>(undefined)
|
||||
const [maxRequestsPerTask, setMaxRequestsPerTask] = useState<string>("")
|
||||
const [vscodeThemeName, setVscodeThemeName] = useState<string | undefined>(undefined)
|
||||
const [claudeMessages, setClaudeMessages] = useState<ClaudeMessage[]>([])
|
||||
@@ -33,8 +34,12 @@ const App: React.FC = () => {
|
||||
const message: ExtensionMessage = e.data
|
||||
switch (message.type) {
|
||||
case "state":
|
||||
setShowWelcome(!message.state!.apiKey)
|
||||
setApiKey(message.state!.apiKey || "")
|
||||
const hasKey =
|
||||
message.state!.apiConfiguration?.apiKey !== undefined ||
|
||||
message.state!.apiConfiguration?.openRouterApiKey !== undefined ||
|
||||
message.state!.apiConfiguration?.awsAccessKey !== undefined
|
||||
setShowWelcome(!hasKey)
|
||||
setApiConfiguration(message.state!.apiConfiguration)
|
||||
setMaxRequestsPerTask(
|
||||
message.state!.maxRequestsPerTask !== undefined ? message.state!.maxRequestsPerTask.toString() : ""
|
||||
)
|
||||
@@ -70,13 +75,13 @@ const App: React.FC = () => {
|
||||
return (
|
||||
<>
|
||||
{showWelcome ? (
|
||||
<WelcomeView apiKey={apiKey} setApiKey={setApiKey} />
|
||||
<WelcomeView apiConfiguration={apiConfiguration} setApiConfiguration={setApiConfiguration} />
|
||||
) : (
|
||||
<>
|
||||
{showSettings && (
|
||||
<SettingsView
|
||||
apiKey={apiKey}
|
||||
setApiKey={setApiKey}
|
||||
apiConfiguration={apiConfiguration}
|
||||
setApiConfiguration={setApiConfiguration}
|
||||
maxRequestsPerTask={maxRequestsPerTask}
|
||||
setMaxRequestsPerTask={setMaxRequestsPerTask}
|
||||
onDone={() => setShowSettings(false)}
|
||||
|
||||
144
webview-ui/src/components/ApiOptions.tsx
Normal file
144
webview-ui/src/components/ApiOptions.tsx
Normal file
@@ -0,0 +1,144 @@
|
||||
import { ApiConfiguration } from "@shared/api"
|
||||
import { VSCodeDropdown, VSCodeLink, VSCodeOption, VSCodeTextField } from "@vscode/webview-ui-toolkit/react"
|
||||
import React from "react"
|
||||
|
||||
interface ApiOptionsProps {
|
||||
apiConfiguration?: ApiConfiguration
|
||||
setApiConfiguration: React.Dispatch<React.SetStateAction<ApiConfiguration | undefined>>
|
||||
}
|
||||
|
||||
const ApiOptions: React.FC<ApiOptionsProps> = ({ apiConfiguration, setApiConfiguration }) => {
|
||||
const handleInputChange = (field: keyof ApiConfiguration) => (event: any) => {
|
||||
setApiConfiguration((prev) => ({ ...prev, [field]: event.target.value }))
|
||||
}
|
||||
|
||||
return (
|
||||
<div style={{ display: "flex", flexDirection: "column", gap: 5 }}>
|
||||
<div className="dropdown-container">
|
||||
<label htmlFor="api-provider">
|
||||
<span style={{ fontWeight: 500 }}>API Provider</span>
|
||||
</label>
|
||||
<VSCodeDropdown
|
||||
id="api-provider"
|
||||
value={apiConfiguration?.apiProvider || "anthropic"}
|
||||
onChange={handleInputChange("apiProvider")}>
|
||||
<VSCodeOption value="anthropic">Anthropic</VSCodeOption>
|
||||
<VSCodeOption value="openrouter">OpenRouter</VSCodeOption>
|
||||
<VSCodeOption value="bedrock">AWS Bedrock</VSCodeOption>
|
||||
</VSCodeDropdown>
|
||||
</div>
|
||||
|
||||
{apiConfiguration?.apiProvider === "anthropic" && (
|
||||
<div>
|
||||
<VSCodeTextField
|
||||
value={apiConfiguration?.apiKey || ""}
|
||||
style={{ width: "100%" }}
|
||||
onInput={handleInputChange("apiKey")}
|
||||
placeholder="Enter API Key...">
|
||||
<span style={{ fontWeight: 500 }}>Anthropic API Key</span>
|
||||
</VSCodeTextField>
|
||||
<p
|
||||
style={{
|
||||
fontSize: "12px",
|
||||
marginTop: "5px",
|
||||
color: "var(--vscode-descriptionForeground)",
|
||||
}}>
|
||||
This key is stored locally and only used to make API requests from the extension.
|
||||
<VSCodeLink href="https://console.anthropic.com/" style={{ display: "inline" }}>
|
||||
You can get an Anthropic API key by signing up here.
|
||||
</VSCodeLink>
|
||||
</p>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{apiConfiguration?.apiProvider === "openrouter" && (
|
||||
<div>
|
||||
<VSCodeTextField
|
||||
value={apiConfiguration?.openRouterApiKey || ""}
|
||||
style={{ width: "100%" }}
|
||||
onInput={handleInputChange("openRouterApiKey")}
|
||||
placeholder="Enter API Key...">
|
||||
<span style={{ fontWeight: 500 }}>OpenRouter API Key</span>
|
||||
</VSCodeTextField>
|
||||
<p
|
||||
style={{
|
||||
fontSize: "12px",
|
||||
marginTop: "5px",
|
||||
color: "var(--vscode-descriptionForeground)",
|
||||
}}>
|
||||
This key is stored locally and only used to make API requests from the extension.
|
||||
<VSCodeLink href="https://openrouter.ai/" style={{ display: "inline" }}>
|
||||
You can get an OpenRouter API key by signing up here.
|
||||
</VSCodeLink>
|
||||
</p>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{apiConfiguration?.apiProvider === "bedrock" && (
|
||||
<div style={{ display: "flex", flexDirection: "column", gap: 5 }}>
|
||||
<VSCodeTextField
|
||||
value={apiConfiguration?.awsAccessKey || ""}
|
||||
style={{ width: "100%" }}
|
||||
onInput={handleInputChange("awsAccessKey")}
|
||||
placeholder="Enter Access Key...">
|
||||
<span style={{ fontWeight: 500 }}>AWS Access Key</span>
|
||||
</VSCodeTextField>
|
||||
<VSCodeTextField
|
||||
value={apiConfiguration?.awsSecretKey || ""}
|
||||
style={{ width: "100%" }}
|
||||
onInput={handleInputChange("awsSecretKey")}
|
||||
placeholder="Enter Secret Key...">
|
||||
<span style={{ fontWeight: 500 }}>AWS Secret Key</span>
|
||||
</VSCodeTextField>
|
||||
<div className="dropdown-container">
|
||||
<label htmlFor="aws-region-dropdown">
|
||||
<span style={{ fontWeight: 500 }}>AWS Region</span>
|
||||
</label>
|
||||
<VSCodeDropdown
|
||||
id="aws-region-dropdown"
|
||||
value={apiConfiguration?.awsRegion || ""}
|
||||
style={{ width: "100%" }}
|
||||
onChange={handleInputChange("awsRegion")}>
|
||||
<VSCodeOption value="">Select a region...</VSCodeOption>
|
||||
<VSCodeOption value="us-east-1">US East (N. Virginia)</VSCodeOption>
|
||||
<VSCodeOption value="us-east-2">US East (Ohio)</VSCodeOption>
|
||||
<VSCodeOption value="us-west-1">US West (N. California)</VSCodeOption>
|
||||
<VSCodeOption value="us-west-2">US West (Oregon)</VSCodeOption>
|
||||
<VSCodeOption value="af-south-1">Africa (Cape Town)</VSCodeOption>
|
||||
<VSCodeOption value="ap-east-1">Asia Pacific (Hong Kong)</VSCodeOption>
|
||||
<VSCodeOption value="ap-south-1">Asia Pacific (Mumbai)</VSCodeOption>
|
||||
<VSCodeOption value="ap-northeast-1">Asia Pacific (Tokyo)</VSCodeOption>
|
||||
<VSCodeOption value="ap-northeast-2">Asia Pacific (Seoul)</VSCodeOption>
|
||||
<VSCodeOption value="ap-northeast-3">Asia Pacific (Osaka)</VSCodeOption>
|
||||
<VSCodeOption value="ap-southeast-1">Asia Pacific (Singapore)</VSCodeOption>
|
||||
<VSCodeOption value="ap-southeast-2">Asia Pacific (Sydney)</VSCodeOption>
|
||||
<VSCodeOption value="ca-central-1">Canada (Central)</VSCodeOption>
|
||||
<VSCodeOption value="eu-central-1">Europe (Frankfurt)</VSCodeOption>
|
||||
<VSCodeOption value="eu-west-1">Europe (Ireland)</VSCodeOption>
|
||||
<VSCodeOption value="eu-west-2">Europe (London)</VSCodeOption>
|
||||
<VSCodeOption value="eu-west-3">Europe (Paris)</VSCodeOption>
|
||||
<VSCodeOption value="eu-north-1">Europe (Stockholm)</VSCodeOption>
|
||||
<VSCodeOption value="me-south-1">Middle East (Bahrain)</VSCodeOption>
|
||||
<VSCodeOption value="sa-east-1">South America (São Paulo)</VSCodeOption>
|
||||
</VSCodeDropdown>
|
||||
</div>
|
||||
<p
|
||||
style={{
|
||||
fontSize: "12px",
|
||||
marginTop: "5px",
|
||||
color: "var(--vscode-descriptionForeground)",
|
||||
}}>
|
||||
These credentials are stored locally and only used to make API requests from the extension.
|
||||
<VSCodeLink
|
||||
href="https://docs.aws.amazon.com/IAM/latest/UserGuide/id_credentials_access-keys.html"
|
||||
style={{ display: "inline" }}>
|
||||
You can find your AWS access key and secret key here.
|
||||
</VSCodeLink>
|
||||
</p>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
export default ApiOptions
|
||||
@@ -1,64 +1,50 @@
|
||||
import { ApiConfiguration } from "@shared/api"
|
||||
import { VSCodeButton, VSCodeDivider, VSCodeLink, VSCodeTextField } from "@vscode/webview-ui-toolkit/react"
|
||||
import React, { useState } from "react"
|
||||
import { useEffectOnce } from "react-use"
|
||||
import React, { useEffect, useState } from "react"
|
||||
import { validateApiConfiguration, validateMaxRequestsPerTask } from "../utilities/validate"
|
||||
import { vscode } from "../utilities/vscode"
|
||||
import ApiOptions from "./ApiOptions"
|
||||
|
||||
type SettingsViewProps = {
|
||||
apiKey: string
|
||||
setApiKey: React.Dispatch<React.SetStateAction<string>>
|
||||
apiConfiguration?: ApiConfiguration
|
||||
setApiConfiguration: React.Dispatch<React.SetStateAction<ApiConfiguration | undefined>>
|
||||
maxRequestsPerTask: string
|
||||
setMaxRequestsPerTask: React.Dispatch<React.SetStateAction<string>>
|
||||
onDone: () => void // Define the type of the onDone prop
|
||||
onDone: () => void
|
||||
}
|
||||
|
||||
const SettingsView = ({ apiKey, setApiKey, maxRequestsPerTask, setMaxRequestsPerTask, onDone }: SettingsViewProps) => {
|
||||
const [apiKeyErrorMessage, setApiKeyErrorMessage] = useState<string | undefined>(undefined)
|
||||
const SettingsView = ({
|
||||
apiConfiguration,
|
||||
setApiConfiguration,
|
||||
maxRequestsPerTask,
|
||||
setMaxRequestsPerTask,
|
||||
onDone,
|
||||
}: SettingsViewProps) => {
|
||||
const [apiErrorMessage, setApiErrorMessage] = 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 })
|
||||
const apiValidationResult = validateApiConfiguration(apiConfiguration)
|
||||
const maxRequestsValidationResult = validateMaxRequestsPerTask(maxRequestsPerTask)
|
||||
|
||||
onDone()
|
||||
setApiErrorMessage(apiValidationResult)
|
||||
setMaxRequestsErrorMessage(maxRequestsValidationResult)
|
||||
|
||||
if (!apiValidationResult && !maxRequestsValidationResult) {
|
||||
vscode.postMessage({ type: "apiConfiguration", apiConfiguration })
|
||||
vscode.postMessage({ type: "maxRequestsPerTask", text: maxRequestsPerTask })
|
||||
onDone()
|
||||
}
|
||||
}
|
||||
|
||||
useEffect(() => {
|
||||
setApiErrorMessage(undefined)
|
||||
}, [apiConfiguration])
|
||||
|
||||
useEffect(() => {
|
||||
setMaxRequestsErrorMessage(undefined)
|
||||
}, [maxRequestsPerTask])
|
||||
|
||||
// validate as soon as the component is mounted
|
||||
/*
|
||||
useEffect will use stale values of variables if they are not included in the dependency array. so trying to use useEffect with a dependency array of only one value for example will use any other variables' old values. In most cases you don't want this, and should opt to use react-use hooks.
|
||||
@@ -70,14 +56,6 @@ const SettingsView = ({ apiKey, setApiKey, maxRequestsPerTask, setMaxRequestsPer
|
||||
|
||||
If we only want to run code once on mount we can use react-use's useEffectOnce or useMount
|
||||
*/
|
||||
useEffectOnce(() => {
|
||||
const timeoutId = setTimeout(() => {
|
||||
validateApiKey(apiKey)
|
||||
validateMaxRequests(maxRequestsPerTask)
|
||||
}, 1000)
|
||||
|
||||
return () => clearTimeout(timeoutId)
|
||||
})
|
||||
|
||||
return (
|
||||
<div style={{ margin: "0 auto", paddingTop: "10px" }}>
|
||||
@@ -89,40 +67,21 @@ const SettingsView = ({ apiKey, setApiKey, maxRequestsPerTask, setMaxRequestsPer
|
||||
marginBottom: "17px",
|
||||
}}>
|
||||
<h3 style={{ color: "var(--vscode-foreground)", margin: 0 }}>Settings</h3>
|
||||
<VSCodeButton onClick={handleSubmit} disabled={disableDoneButton}>
|
||||
Done
|
||||
</VSCodeButton>
|
||||
<VSCodeButton onClick={handleSubmit}>Done</VSCodeButton>
|
||||
</div>
|
||||
|
||||
<div style={{ marginBottom: "20px" }}>
|
||||
<VSCodeTextField
|
||||
value={apiKey}
|
||||
style={{ width: "100%" }}
|
||||
placeholder="Enter your Anthropic API Key"
|
||||
onInput={handleApiKeyChange}>
|
||||
<span style={{ fontWeight: "500" }}>Anthropic API Key</span>
|
||||
</VSCodeTextField>
|
||||
{apiKeyErrorMessage && (
|
||||
<div style={{ marginBottom: 5 }}>
|
||||
<ApiOptions apiConfiguration={apiConfiguration} setApiConfiguration={setApiConfiguration} />
|
||||
{apiErrorMessage && (
|
||||
<p
|
||||
style={{
|
||||
margin: "-5px 0 12px 0",
|
||||
fontSize: "12px",
|
||||
marginTop: "5px",
|
||||
color: "var(--vscode-errorForeground)",
|
||||
}}>
|
||||
{apiKeyErrorMessage}
|
||||
{apiErrorMessage}
|
||||
</p>
|
||||
)}
|
||||
<p
|
||||
style={{
|
||||
fontSize: "12px",
|
||||
marginTop: "5px",
|
||||
color: "var(--vscode-descriptionForeground)",
|
||||
}}>
|
||||
This key is not shared with anyone and only used to make API requests from the extension.
|
||||
<VSCodeLink href="https://console.anthropic.com/" style={{ display: "inline" }}>
|
||||
You can get an API key by signing up here.
|
||||
</VSCodeLink>
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div style={{ marginBottom: "20px" }}>
|
||||
@@ -130,9 +89,18 @@ const SettingsView = ({ apiKey, setApiKey, maxRequestsPerTask, setMaxRequestsPer
|
||||
value={maxRequestsPerTask}
|
||||
style={{ width: "100%" }}
|
||||
placeholder="20"
|
||||
onInput={handleMaxRequestsChange}>
|
||||
onInput={(e: any) => setMaxRequestsPerTask(e.target?.value)}>
|
||||
<span style={{ fontWeight: "500" }}>Maximum # Requests Per Task</span>
|
||||
</VSCodeTextField>
|
||||
<p
|
||||
style={{
|
||||
fontSize: "12px",
|
||||
marginTop: "5px",
|
||||
color: "var(--vscode-descriptionForeground)",
|
||||
}}>
|
||||
If Claude Dev reaches this limit, it will pause and ask for your permission before making additional
|
||||
requests.
|
||||
</p>
|
||||
{maxRequestsErrorMessage && (
|
||||
<p
|
||||
style={{
|
||||
@@ -143,15 +111,6 @@ const SettingsView = ({ apiKey, setApiKey, maxRequestsPerTask, setMaxRequestsPer
|
||||
{maxRequestsErrorMessage}
|
||||
</p>
|
||||
)}
|
||||
<p
|
||||
style={{
|
||||
fontSize: "12px",
|
||||
marginTop: "5px",
|
||||
color: "var(--vscode-descriptionForeground)",
|
||||
}}>
|
||||
If Claude Dev reaches this limit, it will pause and ask for your permission before making additional
|
||||
requests.
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<VSCodeDivider />
|
||||
@@ -163,13 +122,14 @@ const SettingsView = ({ apiKey, setApiKey, maxRequestsPerTask, setMaxRequestsPer
|
||||
color: "var(--vscode-descriptionForeground)",
|
||||
fontSize: "12px",
|
||||
lineHeight: "1.2",
|
||||
fontStyle: "italic",
|
||||
}}>
|
||||
<p>
|
||||
<VSCodeLink href="https://github.com/saoudrizwan/claude-dev">
|
||||
<p style={{ wordWrap: "break-word" }}>
|
||||
If you have any questions or feedback, feel free to open an issue at{" "}
|
||||
<VSCodeLink href="https://github.com/saoudrizwan/claude-dev" style={{ display: "inline" }}>
|
||||
https://github.com/saoudrizwan/claude-dev
|
||||
</VSCodeLink>
|
||||
</p>
|
||||
<p style={{ fontStyle: "italic" }}>v1.0.86</p>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
|
||||
@@ -1,32 +1,27 @@
|
||||
import React, { useState, useEffect } from "react"
|
||||
import { VSCodeButton, VSCodeTextField, VSCodeLink, VSCodeDivider } from "@vscode/webview-ui-toolkit/react"
|
||||
import { ApiConfiguration } from "@shared/api"
|
||||
import { VSCodeButton, VSCodeLink } from "@vscode/webview-ui-toolkit/react"
|
||||
import React, { useEffect, useState } from "react"
|
||||
import { validateApiConfiguration } from "../utilities/validate"
|
||||
import { vscode } from "../utilities/vscode"
|
||||
import ApiOptions from "./ApiOptions"
|
||||
|
||||
interface WelcomeViewProps {
|
||||
apiKey: string
|
||||
setApiKey: React.Dispatch<React.SetStateAction<string>>
|
||||
apiConfiguration?: ApiConfiguration
|
||||
setApiConfiguration: React.Dispatch<React.SetStateAction<ApiConfiguration | undefined>>
|
||||
}
|
||||
|
||||
const WelcomeView: React.FC<WelcomeViewProps> = ({ apiKey, setApiKey }) => {
|
||||
const [apiKeyErrorMessage, setApiKeyErrorMessage] = useState<string | undefined>(undefined)
|
||||
const WelcomeView: React.FC<WelcomeViewProps> = ({ apiConfiguration, setApiConfiguration }) => {
|
||||
const [apiErrorMessage, setApiErrorMessage] = useState<string | undefined>(undefined)
|
||||
|
||||
const disableLetsGoButton = apiKeyErrorMessage != null
|
||||
|
||||
const validateApiKey = (value: string) => {
|
||||
if (value.trim() === "") {
|
||||
setApiKeyErrorMessage("API Key cannot be empty")
|
||||
} else {
|
||||
setApiKeyErrorMessage(undefined)
|
||||
}
|
||||
}
|
||||
const disableLetsGoButton = apiErrorMessage != null
|
||||
|
||||
const handleSubmit = () => {
|
||||
vscode.postMessage({ type: "apiKey", text: apiKey })
|
||||
vscode.postMessage({ type: "apiConfiguration", apiConfiguration })
|
||||
}
|
||||
|
||||
useEffect(() => {
|
||||
validateApiKey(apiKey)
|
||||
}, [apiKey])
|
||||
setApiErrorMessage(validateApiConfiguration(apiConfiguration))
|
||||
}, [apiConfiguration])
|
||||
|
||||
return (
|
||||
<div style={{ position: "fixed", top: 0, left: 0, right: 0, bottom: 0, padding: "0 20px" }}>
|
||||
@@ -42,35 +37,14 @@ const WelcomeView: React.FC<WelcomeViewProps> = ({ apiKey, setApiKey }) => {
|
||||
files, analyze project source code, and execute terminal commands (with your permission, of course).
|
||||
</p>
|
||||
|
||||
<b>To get started, this extension needs an Anthropic API key:</b>
|
||||
<ol style={{ paddingLeft: "15px" }}>
|
||||
<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 (you can delete it any time)</li>
|
||||
</ol>
|
||||
<b>To get started, this extension needs an API key for Claude 3.5 Sonnet:</b>
|
||||
|
||||
<VSCodeDivider />
|
||||
|
||||
<div style={{ marginTop: "20px", display: "flex", alignItems: "center" }}>
|
||||
<VSCodeTextField
|
||||
style={{ flexGrow: 1, marginRight: "10px" }}
|
||||
placeholder="Enter API Key..."
|
||||
value={apiKey}
|
||||
onInput={(e: any) => setApiKey(e.target.value)}
|
||||
/>
|
||||
<VSCodeButton onClick={handleSubmit} disabled={disableLetsGoButton}>
|
||||
Submit
|
||||
<div style={{ marginTop: "15px" }}>
|
||||
<ApiOptions apiConfiguration={apiConfiguration} setApiConfiguration={setApiConfiguration} />
|
||||
<VSCodeButton onClick={handleSubmit} disabled={disableLetsGoButton} style={{ marginTop: "3px" }}>
|
||||
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>
|
||||
)
|
||||
}
|
||||
|
||||
@@ -109,3 +109,23 @@ The above scrollbar styling uses some transparent background color magic to acco
|
||||
.code-block-scrollable::-webkit-scrollbar-corner {
|
||||
background-color: transparent;
|
||||
}
|
||||
|
||||
/*
|
||||
Dropdown label
|
||||
https://github.com/microsoft/vscode-webview-ui-toolkit/tree/main/src/dropdown#with-label
|
||||
*/
|
||||
.dropdown-container {
|
||||
box-sizing: border-box;
|
||||
display: flex;
|
||||
flex-flow: column nowrap;
|
||||
align-items: flex-start;
|
||||
justify-content: flex-start;
|
||||
}
|
||||
.dropdown-container label {
|
||||
display: block;
|
||||
color: var(--vscode-foreground);
|
||||
cursor: pointer;
|
||||
font-size: var(--vscode-font-size);
|
||||
line-height: normal;
|
||||
margin-bottom: 2px;
|
||||
}
|
||||
|
||||
34
webview-ui/src/utilities/validate.ts
Normal file
34
webview-ui/src/utilities/validate.ts
Normal file
@@ -0,0 +1,34 @@
|
||||
import { ApiConfiguration } from "@shared/api"
|
||||
|
||||
export function validateApiConfiguration(apiConfiguration?: ApiConfiguration): string | undefined {
|
||||
if (apiConfiguration) {
|
||||
switch (apiConfiguration.apiProvider) {
|
||||
case "anthropic":
|
||||
if (!apiConfiguration.apiKey) {
|
||||
return "API Key cannot be empty. You must provide an API key to use Claude Dev."
|
||||
}
|
||||
break
|
||||
case "bedrock":
|
||||
if (!apiConfiguration.awsAccessKey || !apiConfiguration.awsSecretKey || !apiConfiguration.awsRegion) {
|
||||
return "AWS credentials are incomplete. You must provide an AWS access key, secret key, and region."
|
||||
}
|
||||
break
|
||||
case "openrouter":
|
||||
if (!apiConfiguration.openRouterApiKey) {
|
||||
return "API Key cannot be empty. You must provide an API key to use Claude Dev."
|
||||
}
|
||||
break
|
||||
}
|
||||
}
|
||||
return undefined
|
||||
}
|
||||
|
||||
export function validateMaxRequestsPerTask(maxRequestsPerTask?: string): string | undefined {
|
||||
if (maxRequestsPerTask && maxRequestsPerTask.trim()) {
|
||||
const num = Number(maxRequestsPerTask)
|
||||
if (isNaN(num) || num < 3 || num > 100) {
|
||||
return "Maximum requests must be between 3 and 100"
|
||||
}
|
||||
}
|
||||
return undefined
|
||||
}
|
||||
Reference in New Issue
Block a user