From 923d4a56f98b6de35e04677f12befc47762f1327 Mon Sep 17 00:00:00 2001 From: Chris Estreich Date: Tue, 18 Mar 2025 10:15:36 -0700 Subject: [PATCH] Port all `Dropdown` components to `Select` in `ApiOptions` (#1780) * Port all `Dropdown` components to `Select` in `ApiOptions` * Fix tests --- .../src/components/settings/ApiOptions.tsx | 291 ++++++++---------- .../settings/__tests__/ApiOptions.test.tsx | 8 +- .../src/components/settings/constants.ts | 65 ++++ webview-ui/src/components/ui/select.tsx | 2 +- 4 files changed, 189 insertions(+), 177 deletions(-) create mode 100644 webview-ui/src/components/settings/constants.ts diff --git a/webview-ui/src/components/settings/ApiOptions.tsx b/webview-ui/src/components/settings/ApiOptions.tsx index 8ea5f8ab45..b9deb0399f 100644 --- a/webview-ui/src/components/settings/ApiOptions.tsx +++ b/webview-ui/src/components/settings/ApiOptions.tsx @@ -2,12 +2,12 @@ import React, { memo, useCallback, useEffect, useMemo, useState } from "react" import { useAppTranslation } from "@/i18n/TranslationContext" import { Trans } from "react-i18next" import { useDebounce, useEvent } from "react-use" -import { Checkbox, Dropdown, type DropdownOption } from "vscrui" +import { LanguageModelChatSelector } from "vscode" +import { Checkbox } from "vscrui" import { VSCodeLink, VSCodeRadio, VSCodeRadioGroup, VSCodeTextField } from "@vscode/webview-ui-toolkit/react" -import * as vscodemodels from "vscode" import { ExternalLinkIcon } from "@radix-ui/react-icons" -import { Select, SelectContent, SelectGroup, SelectItem, SelectTrigger, SelectValue, Button } from "@/components/ui" +import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue, SelectSeparator, Button } from "@/components/ui" import { ApiConfiguration, @@ -36,6 +36,7 @@ import { unboundDefaultModelInfo, requestyDefaultModelId, requestyDefaultModelInfo, + ApiProvider, } from "../../../../src/shared/api" import { ExtensionMessage } from "../../../../src/shared/ExtensionMessage" @@ -45,6 +46,7 @@ import { OPENROUTER_DEFAULT_PROVIDER_NAME, } from "@/components/ui/hooks/useOpenRouterModelProviders" +import { MODELS_BY_PROVIDER, PROVIDERS, AWS_REGIONS, VERTEX_REGIONS } from "./constants" import { VSCodeButtonLink } from "../common/VSCodeButtonLink" import { ModelInfoView } from "./ModelInfoView" import { ModelPicker } from "./ModelPicker" @@ -53,35 +55,6 @@ import { validateApiConfiguration, validateModelId, validateBedrockArn } from "@ import { ApiErrorMessage } from "./ApiErrorMessage" import { ThinkingBudget } from "./ThinkingBudget" -const modelsByProvider: Record> = { - anthropic: anthropicModels, - bedrock: bedrockModels, - vertex: vertexModels, - gemini: geminiModels, - "openai-native": openAiNativeModels, - deepseek: deepSeekModels, - mistral: mistralModels, -} - -const providers = [ - { value: "openrouter", label: "OpenRouter" }, - { value: "anthropic", label: "Anthropic" }, - { value: "gemini", label: "Google Gemini" }, - { value: "deepseek", label: "DeepSeek" }, - { value: "openai-native", label: "OpenAI" }, - { value: "openai", label: "OpenAI Compatible" }, - { value: "vertex", label: "GCP Vertex AI" }, - { value: "bedrock", label: "AWS Bedrock" }, - { value: "glama", label: "Glama" }, - { value: "vscode-lm", label: "VS Code LM API" }, - { value: "mistral", label: "Mistral" }, - { value: "lmstudio", label: "LM Studio" }, - { value: "ollama", label: "Ollama" }, - { value: "unbound", label: "Unbound" }, - { value: "requesty", label: "Requesty" }, - { value: "human-relay", label: "Human Relay" }, -] - interface ApiOptionsProps { uriScheme: string | undefined apiConfiguration: ApiConfiguration @@ -103,7 +76,7 @@ const ApiOptions = ({ const [ollamaModels, setOllamaModels] = useState([]) const [lmStudioModels, setLmStudioModels] = useState([]) - const [vsCodeLmModels, setVsCodeLmModels] = useState([]) + const [vsCodeLmModels, setVsCodeLmModels] = useState([]) const [openRouterModels, setOpenRouterModels] = useState>({ [openRouterDefaultModelId]: openRouterDefaultModelInfo, @@ -132,9 +105,8 @@ const ApiOptions = ({ const [isDescriptionExpanded, setIsDescriptionExpanded] = useState(false) const noTransform = (value: T) => value + const inputEventTransform = (event: E) => (event as { target: HTMLInputElement })?.target?.value as any - const dropdownEventTransform = (event: DropdownOption | string | undefined) => - (typeof event == "string" ? event : event?.value) as T const handleInputChange = useCallback( ( @@ -258,40 +230,35 @@ const ApiOptions = ({ useEvent("message", onMessage) - const selectedProviderModelOptions: DropdownOption[] = useMemo( + const selectedProviderModelOptions = useMemo( () => - modelsByProvider[selectedProvider] - ? [ - { value: "", label: "Select a model..." }, - ...Object.keys(modelsByProvider[selectedProvider]).map((modelId) => ({ - value: modelId, - label: modelId, - })), - ] + MODELS_BY_PROVIDER[selectedProvider] + ? Object.keys(MODELS_BY_PROVIDER[selectedProvider]).map((modelId) => ({ + value: modelId, + label: modelId, + })) : [], [selectedProvider], ) return (
-
- +
+
@@ -544,34 +511,22 @@ const ApiOptions = ({ )} -
- - + +
{t("settings:providers.googleCloudProjectId")} -
- - + +
)} @@ -1247,10 +1200,8 @@ const ApiOptions = ({ {selectedProvider === "vscode-lm" && ( <> -
- +
+ {vsCodeLmModels.length > 0 ? ( ) : ( @@ -1379,51 +1328,52 @@ const ApiOptions = ({ /> )} - {openRouterModelProviders && ( - <> -
+ {selectedProvider === "openrouter" && + openRouterModelProviders && + Object.keys(openRouterModelProviders).length > 0 && ( +
-
- { - const provider = typeof event == "string" ? event : event?.value - const providerModelInfo = provider ? openRouterModelProviders[provider] : undefined - - if (providerModelInfo) { + +
+ {t("settings:providers.openRouter.providerRouting.description")}{" "} + + {t("settings:providers.openRouter.providerRouting.learnMore")}. + +
-
- {t("settings:providers.openRouter.providerRouting.description")}{" "} - - {t("settings:providers.openRouter.providerRouting.learnMore")}. - -
- - )} + )} {selectedProvider === "glama" && ( 0 && ( <> -
- - { - const modelValue = typeof value == "string" ? value : value?.value - setApiConfigurationField("apiModelId", modelValue) +
+ - // Clear custom ARN if not using custom ARN option - if (modelValue !== "custom-arn" && selectedProvider === "bedrock") { +
{selectedProvider === "bedrock" && selectedModelId === "custom-arn" && ( diff --git a/webview-ui/src/components/settings/__tests__/ApiOptions.test.tsx b/webview-ui/src/components/settings/__tests__/ApiOptions.test.tsx index 65bff4ce79..cde8ba8537 100644 --- a/webview-ui/src/components/settings/__tests__/ApiOptions.test.tsx +++ b/webview-ui/src/components/settings/__tests__/ApiOptions.test.tsx @@ -23,18 +23,12 @@ jest.mock("@vscode/webview-ui-toolkit/react", () => ({ // Mock other components jest.mock("vscrui", () => ({ - Dropdown: ({ children, value, onChange }: any) => ( - - ), Checkbox: ({ children, checked, onChange }: any) => ( ), - Pane: ({ children }: any) =>
{children}
, })) // Mock @shadcn/ui components @@ -49,12 +43,12 @@ jest.mock("@/components/ui", () => ({ SelectTrigger: ({ children }: any) =>
{children}
, SelectValue: ({ children }: any) =>
{children}
, SelectContent: ({ children }: any) =>
{children}
, - SelectGroup: ({ children }: any) =>
{children}
, SelectItem: ({ children, value }: any) => ( ), + SelectSeparator: ({ children }: any) =>
{children}
, Button: ({ children, onClick }: any) => (