From 66e036eaa23ee553a0078fcf474d0c3e4bff5a82 Mon Sep 17 00:00:00 2001 From: cte Date: Wed, 5 Mar 2025 12:18:00 -0800 Subject: [PATCH 1/2] Fix settings colors for themes that use color transparency --- .../src/components/settings/ApiOptions.tsx | 1388 +++++++---------- .../settings/ModelDescriptionMarkdown.tsx | 76 +- .../src/components/settings/ModelInfoView.tsx | 124 +- .../src/components/settings/ModelPicker.tsx | 50 +- .../src/components/settings/SectionHeader.tsx | 7 +- .../src/components/settings/SettingsView.tsx | 10 +- .../settings/TemperatureControl.tsx | 42 +- .../components/settings/ThinkingBudget.tsx | 8 +- webview-ui/src/index.css | 11 + 9 files changed, 713 insertions(+), 1003 deletions(-) diff --git a/webview-ui/src/components/settings/ApiOptions.tsx b/webview-ui/src/components/settings/ApiOptions.tsx index d38ef752a6..81acc9dddf 100644 --- a/webview-ui/src/components/settings/ApiOptions.tsx +++ b/webview-ui/src/components/settings/ApiOptions.tsx @@ -1,6 +1,6 @@ import React, { memo, useCallback, useEffect, useMemo, useState } from "react" import { useDebounce, useEvent } from "react-use" -import { Checkbox, Dropdown, Pane, type DropdownOption } from "vscrui" +import { Checkbox, Dropdown, type DropdownOption } from "vscrui" import { VSCodeLink, VSCodeRadio, VSCodeRadioGroup, VSCodeTextField } from "@vscode/webview-ui-toolkit/react" import * as vscodemodels from "vscode" @@ -42,6 +42,7 @@ import { TemperatureControl } from "./TemperatureControl" import { validateApiConfiguration, validateModelId } from "@/utils/validate" import { ApiErrorMessage } from "./ApiErrorMessage" import { ThinkingBudget } from "./ThinkingBudget" +import { Button } from "../ui" const modelsByProvider: Record> = { anthropic: anthropicModels, @@ -232,7 +233,7 @@ const ApiOptions = ({ ) return ( -
+
{errorMessage && } - {selectedProvider === "anthropic" && ( -
- - Anthropic API Key - - - { - setAnthropicBaseUrlSelected(checked) - - if (!checked) { - setApiConfigurationField("anthropicBaseUrl", "") - } - }}> - Use custom base URL - - - {anthropicBaseUrlSelected && ( - - )} - -

- This key is stored locally and only used to make API requests from this extension. - {!apiConfiguration?.apiKey && ( - - You can get an Anthropic API key by signing up here. - - )} -

-
- )} - - {selectedProvider === "glama" && ( -
- - Glama API Key - - {!apiConfiguration?.glamaApiKey && ( - - Get Glama API Key - - )} -

- This key is stored locally and only used to make API requests from this extension. -

-
- )} - - {selectedProvider === "requesty" && ( -
- - Requesty API Key - -

- This key is stored locally and only used to make API requests from this extension. -

-
- )} - - {selectedProvider === "openai-native" && ( -
- - OpenAI API Key - -

- This key is stored locally and only used to make API requests from this extension. - {!apiConfiguration?.openAiNativeApiKey && ( - - You can get an OpenAI API key by signing up here. - - )} -

-
- )} - - {selectedProvider === "mistral" && ( -
- - Mistral API Key - -

- This key is stored locally and only used to make API requests from this extension. - - You can get a La Plateforme (api.mistral.ai) or Codestral (codestral.mistral.ai) API key by - signing up here. - -

- - {(apiConfiguration?.apiModelId?.startsWith("codestral-") || - (!apiConfiguration?.apiModelId && mistralDefaultModelId.startsWith("codestral-"))) && ( -
- - Codestral Base URL (Optional) - -

- Set alternative URL for Codestral model: https://api.mistral.ai -

-
- )} -
- )} - {selectedProvider === "openrouter" && ( -
+ <> + placeholder="Enter API Key..." + className="w-full"> OpenRouter API Key +
+ This key is stored locally and only used to make API requests from this extension. +
{!apiConfiguration?.openRouterApiKey && ( -

- - Get OpenRouter API Key - -

+ + Get OpenRouter API Key + )} -

- This key is stored locally and only used to make API requests from this extension.{" "} -

{!fromWelcomeView && ( <> - { - setOpenRouterBaseUrlSelected(checked) +
+ { + setOpenRouterBaseUrlSelected(checked) - if (!checked) { - setApiConfigurationField("openRouterBaseUrl", "") - } - }}> - Use custom base URL - - - {openRouterBaseUrlSelected && ( - - )} + if (!checked) { + setApiConfigurationField("openRouterBaseUrl", "") + } + }}> + Use custom base URL + + {openRouterBaseUrlSelected && ( + + )} +
@@ -504,11 +316,149 @@ const ApiOptions = ({ )} -
+ + )} + + {selectedProvider === "anthropic" && ( + <> + +
Anthropic API Key
+
+
+ This key is stored locally and only used to make API requests from this extension. +
+ {!apiConfiguration?.apiKey && ( + + Get Anthropic API Key + + )} +
+ { + setAnthropicBaseUrlSelected(checked) + + if (!checked) { + setApiConfigurationField("anthropicBaseUrl", "") + } + }}> + Use custom base URL + + {anthropicBaseUrlSelected && ( + + )} +
+ + )} + + {selectedProvider === "glama" && ( + <> + + Glama API Key + +
+ This key is stored locally and only used to make API requests from this extension. +
+ {!apiConfiguration?.glamaApiKey && ( + + Get Glama API Key + + )} + + )} + + {selectedProvider === "requesty" && ( + <> + + Requesty API Key + +
+ This key is stored locally and only used to make API requests from this extension. +
+ + )} + + {selectedProvider === "openai-native" && ( + <> + + OpenAI API Key + +
+ This key is stored locally and only used to make API requests from this extension. +
+ {!apiConfiguration?.openAiNativeApiKey && ( + + Get OpenAI API Key + + )} + + )} + + {selectedProvider === "mistral" && ( + <> + + Mistral API Key + +
+ This key is stored locally and only used to make API requests from this extension. +
+ {!apiConfiguration?.mistralApiKey && ( + + Get Mistral / Codestral API Key + + )} + {(apiConfiguration?.apiModelId?.startsWith("codestral-") || + (!apiConfiguration?.apiModelId && mistralDefaultModelId.startsWith("codestral-"))) && ( + <> + + Codestral Base URL (Optional) + +
+ Set an alternative URL for the Codestral model. +
+ + )} + )} {selectedProvider === "bedrock" && ( -
+ <> AWS Credentials AWS Profile - {/* AWS Profile Config Block */} +
+ Authenticate by providing an access key and secret or use the default AWS credential providers, + i.e. ~/.aws/credentials or environment variables. These credentials are only used locally to + make API requests from this extension. +
{apiConfiguration?.awsUseProfile ? ( + placeholder="Enter profile name" + className="w-full"> AWS Profile Name ) : ( <> - {/* AWS Credentials Config Block */} + placeholder="Enter Access Key..." + className="w-full"> AWS Access Key + placeholder="Enter Secret Key..." + className="w-full"> AWS Secret Key + placeholder="Enter Session Token..." + className="w-full"> AWS Session Token )}
-
Use cross-region inference -

- Authenticate by either providing the keys above or use the default AWS credential providers, - i.e. ~/.aws/credentials or environment variables. These credentials are only used locally to - make API requests from this extension. -

-
+ )} {selectedProvider === "vertex" && ( -
+ <> +
+
To use Google Cloud Vertex AI, you need to:
+
+ + 1. Create a Google Cloud account, enable the Vertex AI API & enable the desired Claude + models. + +
+
+ + 2. Install the Google Cloud CLI & configure application default credentials. + +
+
+ placeholder="Enter Project ID..." + className="w-full"> Google Cloud Project ID
-
-

- To use Google Cloud Vertex AI, you need to - - { - "1) create a Google Cloud account › enable the Vertex AI API › enable the desired Claude models," - } - {" "} - - {"2) install the Google Cloud CLI › configure Application Default Credentials."} - -

-
+ )} {selectedProvider === "gemini" && ( -
+ <> Gemini API Key -

+

This key is stored locally and only used to make API requests from this extension. - {!apiConfiguration?.geminiApiKey && ( - - You can get a Gemini API key by signing up here. - - )} -

-
+
+ {!apiConfiguration?.geminiApiKey && ( + + Get Gemini API Key + + )} + )} {selectedProvider === "openai" && ( -
+ <> + placeholder={"Enter base URL..."} + className="w-full"> Base URL + placeholder="Enter API Key..." + className="w-full"> API Key -
- - Enable streaming - -
+ + Enable streaming + Use Azure - { - setAzureApiVersionSelected(checked) +
+ { + setAzureApiVersionSelected(checked) - if (!checked) { - setApiConfigurationField("azureApiVersion", "") - } - }}> - Set Azure API version - - {azureApiVersionSelected && ( - - )} -
- - setApiConfigurationField("openAiCustomModelInfo", openAiModelInfoSaneDefaults), - }, - ]}> -
-

+ {azureApiVersionSelected && ( + + )} +

+ +
+
+ Configure the capabilities and pricing for your custom OpenAI-compatible model. Be careful + when specifying the model capabilities, as they can affect how Roo Code performs. +
+ +
+ - Configure the capabilities and pricing for your custom OpenAI-compatible model.
- Be careful for the model capabilities, as they can affect how Roo Code can work. -

- - {/* Capabilities Section */} -
-

- Model Capabilities -

-
-
- { - const value = apiConfiguration?.openAiCustomModelInfo?.maxTokens - if (!value) return "var(--vscode-input-border)" - return value > 0 - ? "var(--vscode-charts-green)" - : "var(--vscode-errorForeground)" - })(), - }} - title="Maximum number of tokens the model can generate in a single response" - onInput={handleInputChange("openAiCustomModelInfo", (e) => { - const value = parseInt((e.target as HTMLInputElement).value) - return { - ...(apiConfiguration?.openAiCustomModelInfo || - openAiModelInfoSaneDefaults), - maxTokens: isNaN(value) ? undefined : value, - } - })} - placeholder="e.g. 4096"> - Max Output Tokens - -
- - - Maximum number of tokens the model can generate in a response.
- (-1 is depend on server) -
-
-
- -
- { - const value = apiConfiguration?.openAiCustomModelInfo?.contextWindow - if (!value) return "var(--vscode-input-border)" - return value > 0 - ? "var(--vscode-charts-green)" - : "var(--vscode-errorForeground)" - })(), - }} - title="Total number of tokens (input + output) the model can process in a single request" - onInput={handleInputChange("openAiCustomModelInfo", (e) => { - const value = (e.target as HTMLInputElement).value - const parsed = parseInt(value) - return { - ...(apiConfiguration?.openAiCustomModelInfo || - openAiModelInfoSaneDefaults), - contextWindow: isNaN(parsed) - ? openAiModelInfoSaneDefaults.contextWindow - : parsed, - } - })} - placeholder="e.g. 128000"> - Context Window Size - -
- - - Total tokens (input + output) the model can process. This will help Roo - Code run correctly. - -
-
-
-
- -
-

Model Features

-
-
-
- { - return { - ...(apiConfiguration?.openAiCustomModelInfo || - openAiModelInfoSaneDefaults), - supportsImages: checked, - } - })}> - Image Support - - -
-

- Allows the model to analyze and understand images, essential for visual code - assistance -

-
- -
-
- { - return { - ...(apiConfiguration?.openAiCustomModelInfo || - openAiModelInfoSaneDefaults), - supportsComputerUse: checked, - } - })}> - Computer Use - - -
-

- This model feature is for computer use like sonnet 3.5 support -

-
-
-
- - {/* Pricing Section */} -
-

- Model Pricing -

-
Configure token-based pricing in USD per million tokens
-
-
- { - const value = apiConfiguration?.openAiCustomModelInfo?.inputPrice - if (!value && value !== 0) return "var(--vscode-input-border)" - return value >= 0 - ? "var(--vscode-charts-green)" - : "var(--vscode-errorForeground)" - })(), - }} - onInput={handleInputChange("openAiCustomModelInfo", (e) => { - const value = (e.target as HTMLInputElement).value - const parsed = parseFloat(value) - return { - ...(apiConfiguration?.openAiCustomModelInfo ?? - openAiModelInfoSaneDefaults), - inputPrice: isNaN(parsed) - ? openAiModelInfoSaneDefaults.inputPrice - : parsed, - } - })} - placeholder="e.g. 0.0001"> -
- Input Price - -
-
-
- -
- { - const value = apiConfiguration?.openAiCustomModelInfo?.outputPrice - if (!value && value !== 0) return "var(--vscode-input-border)" - return value >= 0 - ? "var(--vscode-charts-green)" - : "var(--vscode-errorForeground)" - })(), - }} - onInput={handleInputChange("openAiCustomModelInfo", (e) => { - const value = (e.target as HTMLInputElement).value - const parsed = parseFloat(value) - return { - ...(apiConfiguration?.openAiCustomModelInfo || - openAiModelInfoSaneDefaults), - outputPrice: isNaN(parsed) - ? openAiModelInfoSaneDefaults.outputPrice - : parsed, - } - })} - placeholder="e.g. 0.0002"> -
- Output Price - -
-
-
-
+ width: "100%", + borderColor: (() => { + const value = apiConfiguration?.openAiCustomModelInfo?.maxTokens + if (!value) return "var(--vscode-input-border)" + return value > 0 + ? "var(--vscode-charts-green)" + : "var(--vscode-errorForeground)" + })(), + }} + title="Maximum number of tokens the model can generate in a single response" + onInput={handleInputChange("openAiCustomModelInfo", (e) => { + const value = parseInt((e.target as HTMLInputElement).value) + return { + ...(apiConfiguration?.openAiCustomModelInfo || openAiModelInfoSaneDefaults), + maxTokens: isNaN(value) ? undefined : value, + } + })} + placeholder="e.g. 4096"> + Max Output Tokens + +
+ Maximum number of tokens the model can generate in a response. (Specify -1 to allow the + server to set the max tokens.)
- -
- {/* end Model Info Configuration */} -
+
+ { + const value = apiConfiguration?.openAiCustomModelInfo?.contextWindow + if (!value) return "var(--vscode-input-border)" + return value > 0 + ? "var(--vscode-charts-green)" + : "var(--vscode-errorForeground)" + })(), + }} + title="Total number of tokens (input + output) the model can process in a single request" + onInput={handleInputChange("openAiCustomModelInfo", (e) => { + const value = (e.target as HTMLInputElement).value + const parsed = parseInt(value) + return { + ...(apiConfiguration?.openAiCustomModelInfo || openAiModelInfoSaneDefaults), + contextWindow: isNaN(parsed) + ? openAiModelInfoSaneDefaults.contextWindow + : parsed, + } + })} + placeholder="e.g. 128000"> + Context Window Size + +
+ Total tokens (input + output) the model can process. +
+
+ +
+
+ { + return { + ...(apiConfiguration?.openAiCustomModelInfo || openAiModelInfoSaneDefaults), + supportsImages: checked, + } + })}> + Image Support + + +
+
+ Is this model capable of processing and understanding images? +
+
+ +
+
+ { + return { + ...(apiConfiguration?.openAiCustomModelInfo || openAiModelInfoSaneDefaults), + supportsComputerUse: checked, + } + })}> + Computer Use + + +
+
+ Is this model capable of interacting with a browser? (e.g. Claude 3.7 Sonnet). +
+
+ +
+ { + const value = apiConfiguration?.openAiCustomModelInfo?.inputPrice + if (!value && value !== 0) return "var(--vscode-input-border)" + return value >= 0 + ? "var(--vscode-charts-green)" + : "var(--vscode-errorForeground)" + })(), + }} + onInput={handleInputChange("openAiCustomModelInfo", (e) => { + const value = (e.target as HTMLInputElement).value + const parsed = parseFloat(value) + return { + ...(apiConfiguration?.openAiCustomModelInfo ?? openAiModelInfoSaneDefaults), + inputPrice: isNaN(parsed) ? openAiModelInfoSaneDefaults.inputPrice : parsed, + } + })} + placeholder="e.g. 0.0001"> +
+ Input Price + +
+
+
+ +
+ { + const value = apiConfiguration?.openAiCustomModelInfo?.outputPrice + if (!value && value !== 0) return "var(--vscode-input-border)" + return value >= 0 + ? "var(--vscode-charts-green)" + : "var(--vscode-errorForeground)" + })(), + }} + onInput={handleInputChange("openAiCustomModelInfo", (e) => { + const value = (e.target as HTMLInputElement).value + const parsed = parseFloat(value) + return { + ...(apiConfiguration?.openAiCustomModelInfo || openAiModelInfoSaneDefaults), + outputPrice: isNaN(parsed) ? openAiModelInfoSaneDefaults.outputPrice : parsed, + } + })} + placeholder="e.g. 0.0002"> +
+ Output Price + +
+
+
+ + +
+ )} {selectedProvider === "lmstudio" && ( -
+ <> + placeholder={"Default: http://localhost:1234"} + className="w-full"> Base URL (optional) + placeholder={"e.g. meta-llama-3.1-8b-instruct"} + className="w-full"> Model ID {lmStudioModels.length > 0 && ( @@ -1106,45 +926,32 @@ const ApiOptions = ({ ))} )} -
- { - // Explicitly set the boolean value using direct method - setApiConfigurationField("lmStudioSpeculativeDecodingEnabled", checked) - }}> - Enable Speculative Decoding - -
+ { + // Explicitly set the boolean value using direct method. + setApiConfigurationField("lmStudioSpeculativeDecodingEnabled", checked) + }}> + Enable Speculative Decoding + {apiConfiguration?.lmStudioSpeculativeDecodingEnabled && ( <> - - Draft Model ID - -
- - +
+ + Draft Model ID + +
Draft model must be from the same model family for speculative decoding to work correctly. - +
{lmStudioModels.length > 0 && ( <> -
- Select Draft Model -
+
Select Draft Model
{lmStudioModels.length === 0 && (
- No draft models found. Please ensure LM Studio is running with Server Mode enabled.
@@ -1178,66 +981,46 @@ const ApiOptions = ({ )} )} - -

+

LM Studio allows you to run models locally on your computer. For instructions on how to get - started, see their - - quickstart guide. - + started, see their quickstart guide. You will also need to start LM Studio's{" "} - - local server - {" "} - feature to use it with this extension.{" "} - - (Note: Roo Code uses complex prompts and works best - with Claude models. Less capable models may not work as expected.) + local server feature to + use it with this extension. + + Note: Roo Code uses complex prompts and works best with + Claude models. Less capable models may not work as expected. -

-
+
+ )} {selectedProvider === "deepseek" && ( -
+ <> + placeholder="Enter API Key..." + className="w-full"> DeepSeek API Key -

+

This key is stored locally and only used to make API requests from this extension. - {!apiConfiguration?.deepSeekApiKey && ( - - You can get a DeepSeek API key by signing up here. - - )} -

-
+
+ {!apiConfiguration?.deepSeekApiKey && ( + + Get DeepSeek API Key + + )} + )} {selectedProvider === "vscode-lm" && ( -
+ <>
-
-
+
+ Note: This is a very experimental integration and provider support will vary. If you get an + error about a model not being supported, that's an issue on the provider's end. +
+ )} {selectedProvider === "ollama" && ( -
+ <> + placeholder={"Default: http://localhost:11434"} + className="w-full"> Base URL (optional) + placeholder={"e.g. llama3.1"} + className="w-full"> Model ID - {errorMessage && ( -
- - {errorMessage} -
- )} {ollamaModels.length > 0 && ( )} -

+

Ollama allows you to run models locally on your computer. For instructions on how to get started, see their - - quickstart guide. + + quickstart guide - - (Note: Roo Code uses complex prompts and works best - with Claude models. Less capable models may not work as expected.) + . + + Note: Roo Code uses complex prompts and works best with + Claude models. Less capable models may not work as expected. -

-
+
+ )} {selectedProvider === "unbound" && ( -
+ <> + onInput={handleInputChange("unboundApiKey")} + placeholder="Enter API Key..." + className="w-full"> Unbound API Key +
+ This key is stored locally and only used to make API requests from this extension. +
{!apiConfiguration?.unboundApiKey && ( - + Get Unbound API Key )} -

- This key is stored locally and only used to make API requests from this extension. -

-
+ )} {selectedProvider === "human-relay" && ( -
-

- The API key is not required, but the user needs to help copy and paste the information to the - web chat AI. -

-

+ <> +

+ No API key is required, but the user needs to help copy and paste the information to the web + chat AI. +
+
During use, a dialog box will pop up and the current message will be copied to the clipboard automatically. You need to paste these to web versions of AI (such as ChatGPT or Claude), then copy the AI's reply back to the dialog box and click the confirm button. -

-
+
+ )} + {/* Model Pickers */} + {selectedProvider === "openrouter" && (
- + )} {!fromWelcomeView && ( -
- -
+ )}
) @@ -1507,7 +1246,6 @@ const ApiOptions = ({ export function getGlamaAuthUrl(uriScheme?: string) { const callbackUrl = `${uriScheme || "vscode"}://rooveterinaryinc.roo-cline/glama` - return `https://glama.ai/oauth/authorize?callback_url=${encodeURIComponent(callbackUrl)}` } diff --git a/webview-ui/src/components/settings/ModelDescriptionMarkdown.tsx b/webview-ui/src/components/settings/ModelDescriptionMarkdown.tsx index 351464f706..d6aac8ba74 100644 --- a/webview-ui/src/components/settings/ModelDescriptionMarkdown.tsx +++ b/webview-ui/src/components/settings/ModelDescriptionMarkdown.tsx @@ -2,11 +2,14 @@ import { VSCodeLink } from "@vscode/webview-ui-toolkit/react" import { memo, useEffect, useRef, useState } from "react" import { useRemark } from "react-remark" +import { cn } from "@/lib/utils" +import { Collapsible, CollapsibleTrigger } from "@/components/ui" + import { StyledMarkdown } from "./styles" export const ModelDescriptionMarkdown = memo( ({ - markdown, + markdown = "", key, isExpanded, setIsExpanded, @@ -16,75 +19,30 @@ export const ModelDescriptionMarkdown = memo( isExpanded: boolean setIsExpanded: (isExpanded: boolean) => void }) => { - const [reactContent, setMarkdown] = useRemark() - const [showSeeMore, setShowSeeMore] = useState(false) + const [content, setContent] = useRemark() + const [isExpandable, setIsExpandable] = useState(false) const textContainerRef = useRef(null) const textRef = useRef(null) - useEffect(() => { - setMarkdown(markdown || "") - }, [markdown, setMarkdown]) + useEffect(() => setContent(markdown), [markdown, setContent]) useEffect(() => { if (textRef.current && textContainerRef.current) { - const { scrollHeight } = textRef.current - const { clientHeight } = textContainerRef.current - const isOverflowing = scrollHeight > clientHeight - setShowSeeMore(isOverflowing) + setIsExpandable(textRef.current.scrollHeight > textContainerRef.current.clientHeight) } - }, [reactContent, setIsExpanded]) + }, [content]) return ( - -
-
- {reactContent} + +
+
+ {content}
- {!isExpanded && showSeeMore && ( -
-
- setIsExpanded(true)}> - See more - -
- )}
- + + {isExpanded ? "Less" : "More"} + + ) }, ) diff --git a/webview-ui/src/components/settings/ModelInfoView.tsx b/webview-ui/src/components/settings/ModelInfoView.tsx index 397d04e02f..8e9564525f 100644 --- a/webview-ui/src/components/settings/ModelInfoView.tsx +++ b/webview-ui/src/components/settings/ModelInfoView.tsx @@ -1,32 +1,29 @@ +import { useMemo } from "react" import { VSCodeLink } from "@vscode/webview-ui-toolkit/react" -import { Fragment } from "react" + +import { formatPrice } from "@/utils/formatPrice" +import { cn } from "@/lib/utils" import { ModelInfo, geminiModels } from "../../../../src/shared/api" + import { ModelDescriptionMarkdown } from "./ModelDescriptionMarkdown" -import { formatPrice } from "../../utils/formatPrice" + +type ModelInfoViewProps = { + selectedModelId: string + modelInfo: ModelInfo + isDescriptionExpanded: boolean + setIsDescriptionExpanded: (isExpanded: boolean) => void +} export const ModelInfoView = ({ selectedModelId, modelInfo, isDescriptionExpanded, setIsDescriptionExpanded, -}: { - selectedModelId: string - modelInfo: ModelInfo - isDescriptionExpanded: boolean - setIsDescriptionExpanded: (isExpanded: boolean) => void -}) => { - const isGemini = Object.keys(geminiModels).includes(selectedModelId) +}: ModelInfoViewProps) => { + const isGemini = useMemo(() => Object.keys(geminiModels).includes(selectedModelId), [selectedModelId]) const infoItems = [ - modelInfo.description && ( - - ), ), modelInfo.maxTokens !== undefined && modelInfo.maxTokens > 0 && ( - - Max output: {modelInfo.maxTokens?.toLocaleString()} tokens - + <> + Max output: {modelInfo.maxTokens?.toLocaleString()} tokens + ), modelInfo.inputPrice !== undefined && modelInfo.inputPrice > 0 && ( - - Input price: {formatPrice(modelInfo.inputPrice)}/million tokens - - ), - modelInfo.supportsPromptCache && modelInfo.cacheWritesPrice && ( - - Cache writes price:{" "} - {formatPrice(modelInfo.cacheWritesPrice || 0)}/million tokens - - ), - modelInfo.supportsPromptCache && modelInfo.cacheReadsPrice && ( - - Cache reads price:{" "} - {formatPrice(modelInfo.cacheReadsPrice || 0)}/million tokens - + <> + Input price: {formatPrice(modelInfo.inputPrice)} / 1M tokens + ), modelInfo.outputPrice !== undefined && modelInfo.outputPrice > 0 && ( - - Output price: {formatPrice(modelInfo.outputPrice)}/million - tokens - + <> + Output price: {formatPrice(modelInfo.outputPrice)} / 1M tokens + + ), + modelInfo.supportsPromptCache && modelInfo.cacheReadsPrice && ( + <> + Cache reads price: {formatPrice(modelInfo.cacheReadsPrice || 0)} / + 1M tokens + + ), + modelInfo.supportsPromptCache && modelInfo.cacheWritesPrice && ( + <> + Cache writes price: {formatPrice(modelInfo.cacheWritesPrice || 0)}{" "} + / 1M tokens + ), isGemini && ( - + * Free up to {selectedModelId && selectedModelId.includes("flash") ? "15" : "2"} requests per minute. After that, billing depends on prompt size.{" "} - + For more info, see pricing details. @@ -84,14 +80,21 @@ export const ModelInfoView = ({ ].filter(Boolean) return ( -
- {infoItems.map((item, index) => ( - - {item} - {index < infoItems.length - 1 &&
} -
- ))} -
+ <> + {modelInfo.description && ( + + )} +
+ {infoItems.map((item, index) => ( +
{item}
+ ))} +
+ ) } @@ -104,21 +107,12 @@ const ModelInfoSupportsItem = ({ supportsLabel: string doesNotSupportLabel: string }) => ( - - +
+ {isSupported ? supportsLabel : doesNotSupportLabel} - +
) diff --git a/webview-ui/src/components/settings/ModelPicker.tsx b/webview-ui/src/components/settings/ModelPicker.tsx index 5a7737edd5..8a3e7f73dc 100644 --- a/webview-ui/src/components/settings/ModelPicker.tsx +++ b/webview-ui/src/components/settings/ModelPicker.tsx @@ -72,23 +72,20 @@ export const ModelPicker = ({ return ( <> -
Model
- - - - No model found. - {modelIds.map((model) => ( - - {model} - - ))} - - - +
+
Model
+ + + + No model found. + {modelIds.map((model) => ( + + {model} + + ))} + + +
{selectedModelId && selectedModelInfo && selectedModelId === inputValue && ( )} -

+ +

The extension automatically fetches the latest list of models available on{" "} - - {serviceName}. + + {serviceName} + + . If you're unsure which model to choose, Roo Code works best with{" "} + onSelect(defaultModelId)} className="text-sm"> + {defaultModelId}. - If you're unsure which model to choose, Roo Code works best with{" "} - onSelect(defaultModelId)}>{defaultModelId}. You can also try searching "free" for no-cost options currently available. -

+
) } diff --git a/webview-ui/src/components/settings/SectionHeader.tsx b/webview-ui/src/components/settings/SectionHeader.tsx index 1c7bc8553f..709052cea8 100644 --- a/webview-ui/src/components/settings/SectionHeader.tsx +++ b/webview-ui/src/components/settings/SectionHeader.tsx @@ -8,7 +8,12 @@ type SectionHeaderProps = HTMLAttributes & { } export const SectionHeader = ({ description, children, className, ...props }: SectionHeaderProps) => ( -
+

{children}

{description &&

{description}

}
diff --git a/webview-ui/src/components/settings/SettingsView.tsx b/webview-ui/src/components/settings/SettingsView.tsx index f03464f0fc..7b38455b80 100644 --- a/webview-ui/src/components/settings/SettingsView.tsx +++ b/webview-ui/src/components/settings/SettingsView.tsx @@ -7,6 +7,7 @@ import { ApiConfiguration } from "../../../../src/shared/api" import { vscode } from "@/utils/vscode" import { ExtensionStateContextType, useExtensionState } from "@/context/ExtensionStateContext" +import { cn } from "@/lib/utils" import { AlertDialog, AlertDialogContent, @@ -247,14 +248,13 @@ const SettingsView = forwardRef(({ onDone },

Settings

-
+
{sections.map(({ id, icon: Icon, ref }) => ( ))} @@ -287,7 +287,7 @@ const SettingsView = forwardRef(({ onDone },
diff --git a/webview-ui/src/components/settings/TemperatureControl.tsx b/webview-ui/src/components/settings/TemperatureControl.tsx index dd8ccc8e58..2f7dc29f64 100644 --- a/webview-ui/src/components/settings/TemperatureControl.tsx +++ b/webview-ui/src/components/settings/TemperatureControl.tsx @@ -20,30 +20,30 @@ export const TemperatureControl = ({ value, onChange, maxValue = 1 }: Temperatur }, [value]) return ( -
- { - const isChecked = e.target.checked - setIsCustomTemperature(isChecked) - if (!isChecked) { - setInputValue(undefined) // Unset the temperature - } else { - setInputValue(value ?? 0) // Use the value from apiConfiguration, if set - } - }}> - Use custom temperature - - -

- Controls randomness in the model's responses. -

+ <> +
+ { + const isChecked = e.target.checked + setIsCustomTemperature(isChecked) + if (!isChecked) { + setInputValue(undefined) // Unset the temperature + } else { + setInputValue(value ?? 0) // Use the value from apiConfiguration, if set + } + }}> + Use custom temperature + +
+ Controls randomness in the model's responses. +
+
{isCustomTemperature && (
@@ -64,6 +64,6 @@ export const TemperatureControl = ({ value, onChange, maxValue = 1 }: Temperatur

)} -
+ ) } diff --git a/webview-ui/src/components/settings/ThinkingBudget.tsx b/webview-ui/src/components/settings/ThinkingBudget.tsx index 12a5bbb1c7..927dbbadfa 100644 --- a/webview-ui/src/components/settings/ThinkingBudget.tsx +++ b/webview-ui/src/components/settings/ThinkingBudget.tsx @@ -35,8 +35,8 @@ export const ThinkingBudget = ({ apiConfiguration, setApiConfigurationField, mod } return ( -
-
+ <> +
Max Tokens
{tokens}
-
+
Max Thinking Tokens
{thinkingTokens}
-
+ ) } diff --git a/webview-ui/src/index.css b/webview-ui/src/index.css index 5276384b11..6cd405b93f 100644 --- a/webview-ui/src/index.css +++ b/webview-ui/src/index.css @@ -100,6 +100,17 @@ --color-vscode-toolbar-hoverBackground: var(--vscode-toolbar-hoverBackground); --color-vscode-panel-border: var(--vscode-panel-border); + + --color-vscode-sideBar-foreground: var(--vscode-sideBar-foreground); + --color-vscode-sideBar-background: var(--vscode-sideBar-background); + --color-vscode-sideBar-border: var(--vscode-sideBar-border); + + --color-vscode-sideBarSectionHeader-foreground: var(--vscode-sideBarSectionHeader-foreground); + --color-vscode-sideBarSectionHeader-background: var(--vscode-sideBarSectionHeader-background); + --color-vscode-sideBarSectionHeader-border: var(--vscode-sideBarSectionHeader-border); + + --color-vscode-charts-green: var(--vscode-charts-green); + --color-vscode-charts-yellow: var(--vscode-charts-yellow); } @layer base { From dd7d9824baeee71c62f173bfb5622c94147df425 Mon Sep 17 00:00:00 2001 From: cte Date: Wed, 5 Mar 2025 12:28:17 -0800 Subject: [PATCH 2/2] Fix tests --- .../components/common/VSCodeButtonLink.tsx | 24 ++++++++----------- .../src/components/settings/ApiOptions.tsx | 2 +- .../settings/__tests__/ApiOptions.test.tsx | 6 ++++- 3 files changed, 16 insertions(+), 16 deletions(-) diff --git a/webview-ui/src/components/common/VSCodeButtonLink.tsx b/webview-ui/src/components/common/VSCodeButtonLink.tsx index 8d0c87d69d..d0cd6b1c97 100644 --- a/webview-ui/src/components/common/VSCodeButtonLink.tsx +++ b/webview-ui/src/components/common/VSCodeButtonLink.tsx @@ -7,17 +7,13 @@ interface VSCodeButtonLinkProps { [key: string]: any } -const VSCodeButtonLink: React.FC = ({ href, children, ...props }) => { - return ( - - {children} - - ) -} - -export default VSCodeButtonLink +export const VSCodeButtonLink = ({ href, children, ...props }: VSCodeButtonLinkProps) => ( + + {children} + +) diff --git a/webview-ui/src/components/settings/ApiOptions.tsx b/webview-ui/src/components/settings/ApiOptions.tsx index 81acc9dddf..407a4b4fcc 100644 --- a/webview-ui/src/components/settings/ApiOptions.tsx +++ b/webview-ui/src/components/settings/ApiOptions.tsx @@ -35,7 +35,7 @@ import { import { ExtensionMessage } from "../../../../src/shared/ExtensionMessage" import { vscode } from "../../utils/vscode" -import VSCodeButtonLink from "../common/VSCodeButtonLink" +import { VSCodeButtonLink } from "../common/VSCodeButtonLink" import { ModelInfoView } from "./ModelInfoView" import { ModelPicker } from "./ModelPicker" import { TemperatureControl } from "./TemperatureControl" diff --git a/webview-ui/src/components/settings/__tests__/ApiOptions.test.tsx b/webview-ui/src/components/settings/__tests__/ApiOptions.test.tsx index 0b1fb28498..364e57a715 100644 --- a/webview-ui/src/components/settings/__tests__/ApiOptions.test.tsx +++ b/webview-ui/src/components/settings/__tests__/ApiOptions.test.tsx @@ -1,6 +1,9 @@ +// npx jest src/components/settings/__tests__/ApiOptions.test.ts + import { render, screen } from "@testing-library/react" -import ApiOptions from "../ApiOptions" + import { ExtensionStateContextProvider } from "../../../context/ExtensionStateContext" +import ApiOptions from "../ApiOptions" // Mock VSCode components jest.mock("@vscode/webview-ui-toolkit/react", () => ({ @@ -13,6 +16,7 @@ jest.mock("@vscode/webview-ui-toolkit/react", () => ({ VSCodeLink: ({ children, href }: any) => {children}, VSCodeRadio: ({ children, value, checked }: any) => , VSCodeRadioGroup: ({ children }: any) =>
{children}
, + VSCodeButton: ({ children }: any) =>
{children}
, })) // Mock other components