mirror of
https://github.com/pacnpal/Roo-Code.git
synced 2025-12-20 12:21:13 -05:00
Add option to choose different models
This commit is contained in:
@@ -1,34 +1,78 @@
|
||||
import { ApiConfiguration } from "@shared/api"
|
||||
import { VSCodeDropdown, VSCodeLink, VSCodeOption, VSCodeTextField } from "@vscode/webview-ui-toolkit/react"
|
||||
import React from "react"
|
||||
import React, { useMemo } from "react"
|
||||
import {
|
||||
ApiConfiguration,
|
||||
ApiModelId,
|
||||
ModelInfo,
|
||||
anthropicDefaultModelId,
|
||||
anthropicModels,
|
||||
bedrockDefaultModelId,
|
||||
bedrockModels,
|
||||
openRouterDefaultModelId,
|
||||
openRouterModels,
|
||||
} from "../../../src/shared/api"
|
||||
|
||||
interface ApiOptionsProps {
|
||||
showModelOptions: boolean
|
||||
apiConfiguration?: ApiConfiguration
|
||||
setApiConfiguration: React.Dispatch<React.SetStateAction<ApiConfiguration | undefined>>
|
||||
}
|
||||
|
||||
const ApiOptions: React.FC<ApiOptionsProps> = ({ apiConfiguration, setApiConfiguration }) => {
|
||||
const ApiOptions: React.FC<ApiOptionsProps> = ({ showModelOptions, apiConfiguration, setApiConfiguration }) => {
|
||||
const handleInputChange = (field: keyof ApiConfiguration) => (event: any) => {
|
||||
setApiConfiguration((prev) => ({ ...prev, [field]: event.target.value }))
|
||||
}
|
||||
|
||||
const { selectedProvider, selectedModelId, selectedModelInfo } = useMemo(() => {
|
||||
return normalizeApiConfiguration(apiConfiguration)
|
||||
}, [apiConfiguration])
|
||||
|
||||
/*
|
||||
VSCodeDropdown has an open bug where dynamically rendered options don't auto select the provided value prop. You can see this for yourself by comparing it with normal select/option elements, which work as expected.
|
||||
https://github.com/microsoft/vscode-webview-ui-toolkit/issues/433
|
||||
|
||||
In our case, when the user switches between providers, we recalculate the selectedModelId depending on the provider, the default model for that provider, and a modelId that the user may have selected. Unfortunately, the VSCodeDropdown component wouldn't select this calculated value, and would default to the first "Select a model..." option instead, which makes it seem like the model was cleared out when it wasn't.
|
||||
|
||||
As a workaround, we create separate instances of the dropdown for each provider, and then conditionally render the one that matches the current provider.
|
||||
*/
|
||||
const createDropdown = (models: Record<string, ModelInfo>) => {
|
||||
return (
|
||||
<VSCodeDropdown
|
||||
id="model-id"
|
||||
value={selectedModelId}
|
||||
onChange={handleInputChange("apiModelId")}
|
||||
style={{ width: "100%" }}>
|
||||
<VSCodeOption value="">Select a model...</VSCodeOption>
|
||||
{Object.keys(models).map((modelId) => (
|
||||
<VSCodeOption
|
||||
key={modelId}
|
||||
value={modelId}
|
||||
style={{
|
||||
whiteSpace: "normal",
|
||||
wordWrap: "break-word",
|
||||
maxWidth: "100%",
|
||||
}}>
|
||||
{modelId}
|
||||
</VSCodeOption>
|
||||
))}
|
||||
</VSCodeDropdown>
|
||||
)
|
||||
}
|
||||
|
||||
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")}>
|
||||
<VSCodeDropdown id="api-provider" value={selectedProvider} onChange={handleInputChange("apiProvider")}>
|
||||
<VSCodeOption value="anthropic">Anthropic</VSCodeOption>
|
||||
<VSCodeOption value="openrouter">OpenRouter</VSCodeOption>
|
||||
<VSCodeOption value="bedrock">AWS Bedrock</VSCodeOption>
|
||||
</VSCodeDropdown>
|
||||
</div>
|
||||
|
||||
{apiConfiguration?.apiProvider === "anthropic" && (
|
||||
{selectedProvider === "anthropic" && (
|
||||
<div>
|
||||
<VSCodeTextField
|
||||
value={apiConfiguration?.apiKey || ""}
|
||||
@@ -51,7 +95,7 @@ const ApiOptions: React.FC<ApiOptionsProps> = ({ apiConfiguration, setApiConfigu
|
||||
</div>
|
||||
)}
|
||||
|
||||
{apiConfiguration?.apiProvider === "openrouter" && (
|
||||
{selectedProvider === "openrouter" && (
|
||||
<div>
|
||||
<VSCodeTextField
|
||||
value={apiConfiguration?.openRouterApiKey || ""}
|
||||
@@ -74,7 +118,7 @@ const ApiOptions: React.FC<ApiOptionsProps> = ({ apiConfiguration, setApiConfigu
|
||||
</div>
|
||||
)}
|
||||
|
||||
{apiConfiguration?.apiProvider === "bedrock" && (
|
||||
{selectedProvider === "bedrock" && (
|
||||
<div style={{ display: "flex", flexDirection: "column", gap: 5 }}>
|
||||
<VSCodeTextField
|
||||
value={apiConfiguration?.awsAccessKey || ""}
|
||||
@@ -100,9 +144,9 @@ const ApiOptions: React.FC<ApiOptionsProps> = ({ apiConfiguration, setApiConfigu
|
||||
style={{ width: "100%" }}
|
||||
onChange={handleInputChange("awsRegion")}>
|
||||
<VSCodeOption value="">Select a region...</VSCodeOption>
|
||||
{/* Currently Claude 3.5 Sonnet is only available in us-east-1 */}
|
||||
{/* The user will have to choose a region that supports the model they use, but this shouldn't be a problem since they'd have to request access for it in that region in the first place. */}
|
||||
<VSCodeOption value="us-east-1">US East (N. Virginia)</VSCodeOption>
|
||||
{/* <VSCodeOption value="us-east-2">US East (Ohio)</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>
|
||||
@@ -120,7 +164,7 @@ const ApiOptions: React.FC<ApiOptionsProps> = ({ apiConfiguration, setApiConfigu
|
||||
<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> */}
|
||||
<VSCodeOption value="sa-east-1">South America (São Paulo)</VSCodeOption>
|
||||
</VSCodeDropdown>
|
||||
</div>
|
||||
<p
|
||||
@@ -138,8 +182,91 @@ const ApiOptions: React.FC<ApiOptionsProps> = ({ apiConfiguration, setApiConfigu
|
||||
</p>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{showModelOptions && (
|
||||
<>
|
||||
<div className="dropdown-container">
|
||||
<label htmlFor="model-id">
|
||||
<span style={{ fontWeight: 500 }}>Model</span>
|
||||
</label>
|
||||
{selectedProvider === "anthropic" && createDropdown(anthropicModels)}
|
||||
{selectedProvider === "openrouter" && createDropdown(openRouterModels)}
|
||||
{selectedProvider === "bedrock" && createDropdown(bedrockModels)}
|
||||
</div>
|
||||
|
||||
<ModelInfoView modelInfo={selectedModelInfo} />
|
||||
</>
|
||||
)}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
const ModelInfoView = ({ modelInfo }: { modelInfo: ModelInfo }) => {
|
||||
const formatPrice = (price: number) => {
|
||||
return new Intl.NumberFormat("en-US", {
|
||||
style: "currency",
|
||||
currency: "USD",
|
||||
minimumFractionDigits: 2,
|
||||
maximumFractionDigits: 2,
|
||||
}).format(price)
|
||||
}
|
||||
|
||||
return (
|
||||
<p style={{ fontSize: "12px", marginTop: "2px", color: "var(--vscode-descriptionForeground)" }}>
|
||||
<span
|
||||
style={{
|
||||
fontWeight: 500,
|
||||
color: modelInfo.supportsImages
|
||||
? "var(--vscode-testing-iconPassed)"
|
||||
: "var(--vscode-errorForeground)",
|
||||
}}>
|
||||
<i
|
||||
className={`codicon codicon-${modelInfo.supportsImages ? "check" : "x"}`}
|
||||
style={{
|
||||
marginRight: 4,
|
||||
marginBottom: modelInfo.supportsImages ? 1 : -1,
|
||||
fontSize: modelInfo.supportsImages ? 11 : 13,
|
||||
fontWeight: 700,
|
||||
display: "inline-block",
|
||||
verticalAlign: "bottom",
|
||||
}}></i>
|
||||
{modelInfo.supportsImages ? "Supports images" : "Does not support images"}
|
||||
</span>
|
||||
<br />
|
||||
<span style={{ fontWeight: 500 }}>Max output:</span> {modelInfo.maxTokens.toLocaleString()} tokens
|
||||
<br />
|
||||
<span style={{ fontWeight: 500 }}>Input price:</span> {formatPrice(modelInfo.inputPrice)} per million tokens
|
||||
<br />
|
||||
<span style={{ fontWeight: 500 }}>Output price:</span> {formatPrice(modelInfo.outputPrice)} per million
|
||||
tokens
|
||||
</p>
|
||||
)
|
||||
}
|
||||
|
||||
export function normalizeApiConfiguration(apiConfiguration?: ApiConfiguration) {
|
||||
const provider = apiConfiguration?.apiProvider || "anthropic"
|
||||
const modelId = apiConfiguration?.apiModelId
|
||||
|
||||
const getProviderData = (models: Record<string, ModelInfo>, defaultId: ApiModelId) => {
|
||||
let selectedModelId: ApiModelId
|
||||
let selectedModelInfo: ModelInfo
|
||||
if (modelId && modelId in models) {
|
||||
selectedModelId = modelId
|
||||
selectedModelInfo = models[modelId]
|
||||
} else {
|
||||
selectedModelId = defaultId
|
||||
selectedModelInfo = models[defaultId]
|
||||
}
|
||||
return { selectedProvider: provider, selectedModelId, selectedModelInfo }
|
||||
}
|
||||
switch (provider) {
|
||||
case "anthropic":
|
||||
return getProviderData(anthropicModels, anthropicDefaultModelId)
|
||||
case "openrouter":
|
||||
return getProviderData(openRouterModels, openRouterDefaultModelId)
|
||||
case "bedrock":
|
||||
return getProviderData(bedrockModels, bedrockDefaultModelId)
|
||||
}
|
||||
}
|
||||
|
||||
export default ApiOptions
|
||||
|
||||
Reference in New Issue
Block a user