diff --git a/scripts/find-missing-translations.js b/scripts/find-missing-translations.js index 72faef776e..9277d935ba 100755 --- a/scripts/find-missing-translations.js +++ b/scripts/find-missing-translations.js @@ -141,10 +141,17 @@ function checkAreaTranslations(area) { } // Load file contents - const englishFileContents = englishFiles.map((file) => ({ - name: file, - content: JSON.parse(fs.readFileSync(path.join(englishDir, file), "utf8")), - })) + let englishFileContents + + try { + englishFileContents = englishFiles.map((file) => ({ + name: file, + content: JSON.parse(fs.readFileSync(path.join(englishDir, file), "utf8")), + })) + } catch (e) { + console.error(`Error: File '${englishDir}' is not a valid JSON file`) + process.exit(1) + } console.log( `Checking ${englishFileContents.length} translation file(s): ${englishFileContents.map((f) => f.name).join(", ")}`, @@ -167,7 +174,14 @@ function checkAreaTranslations(area) { } // Load the locale file - const localeContent = JSON.parse(fs.readFileSync(localeFilePath, "utf8")) + let localeContent + + try { + localeContent = JSON.parse(fs.readFileSync(localeFilePath, "utf8")) + } catch (e) { + console.error(`Error: File '${localeFilePath}' is not a valid JSON file`) + process.exit(1) + } // Find all keys in the English file const englishKeys = findKeys(englishContent) diff --git a/webview-ui/src/__mocks__/@vscode/webview-ui-toolkit/react.ts b/webview-ui/src/__mocks__/@vscode/webview-ui-toolkit/react.ts index 97dae9d821..2062efb141 100644 --- a/webview-ui/src/__mocks__/@vscode/webview-ui-toolkit/react.ts +++ b/webview-ui/src/__mocks__/@vscode/webview-ui-toolkit/react.ts @@ -103,15 +103,3 @@ export const VSCodeRadio: React.FC = ({ children, value, checked, o export const VSCodeRadioGroup: React.FC = ({ children, onChange, ...props }) => React.createElement("div", { role: "radiogroup", onChange, ...props }, children) - -export const VSCodeSlider: React.FC = ({ value, onChange, ...props }) => - React.createElement("input", { - type: "range", - value, - onChange: (e: any) => onChange?.({ target: { value: Number(e.target.value) } }), - min: 0, - max: 1, - step: 0.01, - style: { flexGrow: 1, height: "2px" }, - ...props, - }) diff --git a/webview-ui/src/__mocks__/vscrui.ts b/webview-ui/src/__mocks__/vscrui.ts index 9b4a20f4d6..08f6d6982a 100644 --- a/webview-ui/src/__mocks__/vscrui.ts +++ b/webview-ui/src/__mocks__/vscrui.ts @@ -1,9 +1,9 @@ import React from "react" -export const Checkbox = ({ children, checked, onChange }: any) => +export const Checkbox = ({ children, onChange }: any) => React.createElement("div", { "data-testid": "mock-checkbox", onClick: onChange }, children) -export const Dropdown = ({ children, value, onChange }: any) => +export const Dropdown = ({ children, onChange }: any) => React.createElement("div", { "data-testid": "mock-dropdown", onClick: onChange }, children) export const Pane = ({ children }: any) => React.createElement("div", { "data-testid": "mock-pane" }, children) diff --git a/webview-ui/src/components/settings/AdvancedSettings.tsx b/webview-ui/src/components/settings/AdvancedSettings.tsx index fa61dce256..e0a909a373 100644 --- a/webview-ui/src/components/settings/AdvancedSettings.tsx +++ b/webview-ui/src/components/settings/AdvancedSettings.tsx @@ -6,26 +6,22 @@ import { Cog } from "lucide-react" import { EXPERIMENT_IDS, ExperimentId } from "../../../../src/shared/experiments" import { cn } from "@/lib/utils" +import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue, Slider } from "@/components/ui" import { SetCachedStateField, SetExperimentEnabled } from "./types" -import { sliderLabelStyle } from "./styles" import { SectionHeader } from "./SectionHeader" import { Section } from "./Section" type AdvancedSettingsProps = HTMLAttributes & { rateLimitSeconds: number - terminalShellIntegrationTimeout: number | undefined diffEnabled?: boolean fuzzyMatchThreshold?: number - setCachedStateField: SetCachedStateField< - "rateLimitSeconds" | "diffEnabled" | "fuzzyMatchThreshold" | "terminalShellIntegrationTimeout" - > + setCachedStateField: SetCachedStateField<"rateLimitSeconds" | "diffEnabled" | "fuzzyMatchThreshold"> experiments: Record setExperimentEnabled: SetExperimentEnabled } export const AdvancedSettings = ({ rateLimitSeconds, - terminalShellIntegrationTimeout, diffEnabled, fuzzyMatchThreshold, setCachedStateField, @@ -35,6 +31,7 @@ export const AdvancedSettings = ({ ...props }: AdvancedSettingsProps) => { const { t } = useAppTranslation() + return (
@@ -49,50 +46,18 @@ export const AdvancedSettings = ({
{t("settings:advanced.rateLimit.label")}
- setCachedStateField("rateLimitSeconds", parseInt(e.target.value))} - className="h-2 focus:outline-0 w-4/5 accent-vscode-button-background" + setCachedStateField("rateLimitSeconds", value)} /> - {rateLimitSeconds}s + {rateLimitSeconds}s
-

+

{t("settings:advanced.rateLimit.description")} -

-
- -
-
- Terminal shell integration timeout -
- - setCachedStateField( - "terminalShellIntegrationTimeout", - Math.min(60000, Math.max(1000, parseInt(e.target.value))), - ) - } - className="h-2 focus:outline-0 w-4/5 accent-vscode-button-background" - /> - - {(terminalShellIntegrationTimeout ?? 4000) / 1000}s - -
-

- Maximum time to wait for shell integration to initialize before executing commands. For - users with long shell startup times, this value may need to be increased if you see "Shell - Integration Unavailable" errors in the terminal. -

@@ -109,48 +74,53 @@ export const AdvancedSettings = ({ }}> {t("settings:advanced.diff.label")} -

+

{t("settings:advanced.diff.description")} -

- {diffEnabled && ( -
-
- {t("settings:advanced.diff.strategy.label")} - -
+
+
-

+ {diffEnabled && ( +

+
+ + +
{!experiments[EXPERIMENT_IDS.DIFF_STRATEGY] && !experiments[EXPERIMENT_IDS.MULTI_SEARCH_AND_REPLACE] && t("settings:advanced.diff.strategy.descriptions.standard")} @@ -158,31 +128,29 @@ export const AdvancedSettings = ({ t("settings:advanced.diff.strategy.descriptions.unified")} {experiments[EXPERIMENT_IDS.MULTI_SEARCH_AND_REPLACE] && t("settings:advanced.diff.strategy.descriptions.multiBlock")} -

- - {t("settings:advanced.diff.matchPrecision.label")} -
- { - setCachedStateField("fuzzyMatchThreshold", parseFloat(e.target.value)) - }} - className="h-2 focus:outline-0 w-4/5 accent-vscode-button-background" - /> - - {Math.round((fuzzyMatchThreshold || 1) * 100)}% -
-

- {t("settings:advanced.diff.matchPrecision.description")} -

- )} -
+ +
+ +
+ setCachedStateField("fuzzyMatchThreshold", value)} + /> + {Math.round((fuzzyMatchThreshold || 1) * 100)}% +
+
+ {t("settings:advanced.diff.matchPrecision.description")} +
+
+
+ )}
) diff --git a/webview-ui/src/components/settings/ApiConfigManager.tsx b/webview-ui/src/components/settings/ApiConfigManager.tsx index b7c7668930..e57bfd8175 100644 --- a/webview-ui/src/components/settings/ApiConfigManager.tsx +++ b/webview-ui/src/components/settings/ApiConfigManager.tsx @@ -1,11 +1,21 @@ -import { VSCodeButton, VSCodeTextField } from "@vscode/webview-ui-toolkit/react" import { memo, useEffect, useRef, useState } from "react" -import { useAppTranslation } from "@/i18n/TranslationContext" +import { VSCodeTextField } from "@vscode/webview-ui-toolkit/react" + import { ApiConfigMeta } from "../../../../src/shared/ExtensionMessage" -import { Dropdown } from "vscrui" -import type { DropdownOption } from "vscrui" -import { Dialog, DialogContent, DialogTitle } from "../ui/dialog" -import { Button, Input } from "../ui" + +import { useAppTranslation } from "@/i18n/TranslationContext" +import { + Button, + Input, + Dialog, + DialogContent, + DialogTitle, + Select, + SelectTrigger, + SelectValue, + SelectContent, + SelectItem, +} from "@/components/ui" interface ApiConfigManagerProps { currentApiConfigName?: string @@ -25,6 +35,7 @@ const ApiConfigManager = ({ onUpsertConfig, }: ApiConfigManagerProps) => { const { t } = useAppTranslation() + const [isRenaming, setIsRenaming] = useState(false) const [isCreating, setIsCreating] = useState(false) const [inputValue, setInputValue] = useState("") @@ -39,12 +50,12 @@ const ApiConfigManager = ({ const nameExists = listApiConfigMeta?.some((config) => config.name.toLowerCase() === trimmed.toLowerCase()) - // For new profiles, any existing name is invalid + // For new profiles, any existing name is invalid. if (isNewProfile && nameExists) { return t("settings:providers.nameExists") } - // For rename, only block if trying to rename to a different existing profile + // For rename, only block if trying to rename to a different existing profile. if (!isNewProfile && nameExists && trimmed.toLowerCase() !== currentApiConfigName?.toLowerCase()) { return t("settings:providers.nameExists") } @@ -64,7 +75,7 @@ const ApiConfigManager = ({ setError(null) } - // Focus input when entering rename mode + // Focus input when entering rename mode. useEffect(() => { if (isRenaming) { const timeoutId = setTimeout(() => inputRef.current?.focus(), 0) @@ -72,7 +83,7 @@ const ApiConfigManager = ({ } }, [isRenaming]) - // Focus input when opening new dialog + // Focus input when opening new dialog. useEffect(() => { if (isCreating) { const timeoutId = setTimeout(() => newProfileInputRef.current?.focus(), 0) @@ -80,7 +91,7 @@ const ApiConfigManager = ({ } }, [isCreating]) - // Reset state when current profile changes + // Reset state when current profile changes. useEffect(() => { resetCreateState() resetRenameState() @@ -137,7 +148,7 @@ const ApiConfigManager = ({ const handleDelete = () => { if (!currentApiConfigName || !listApiConfigMeta || listApiConfigMeta.length <= 1) return - // Let the extension handle both deletion and selection + // Let the extension handle both deletion and selection. onDeleteConfig(currentApiConfigName) } @@ -145,15 +156,11 @@ const ApiConfigManager = ({ return (
- + {isRenaming ? ( -
-
+
+
{ - const event = e as { key: string } - if (event.key === "Enter" && inputValue.trim()) { + onKeyDown={({ key }) => { + if (key === "Enter" && inputValue.trim()) { handleSave() - } else if (event.key === "Escape") { + } else if (key === "Escape") { handleCancel() } }} + className="grow" /> - + data-testid="save-rename-button"> - - +
{error && ( -

+

{error} -

+
)}
) : ( <> -
- { - onSelectConfig((value as DropdownOption).value) - }} - role="combobox" - options={listApiConfigMeta.map((config) => ({ - value: config.name, - label: config.name, - }))} - className="w-full" - /> - + + {currentApiConfigName && ( <> - + data-testid="rename-profile-button"> - - + )}
-

+

{t("settings:providers.description")} -

+
)} diff --git a/webview-ui/src/components/settings/ApiOptions.tsx b/webview-ui/src/components/settings/ApiOptions.tsx index fa307a6de4..5455d92359 100644 --- a/webview-ui/src/components/settings/ApiOptions.tsx +++ b/webview-ui/src/components/settings/ApiOptions.tsx @@ -249,7 +249,7 @@ const ApiOptions = ({ value={selectedProvider} onValueChange={(value) => setApiConfigurationField("apiProvider", value as ApiProvider)}> - + OpenRouter @@ -521,7 +521,7 @@ const ApiOptions = ({ value={apiConfiguration?.awsRegion || ""} onValueChange={(value) => setApiConfigurationField("awsRegion", value)}> - + {AWS_REGIONS.map(({ value, label }) => ( @@ -595,7 +595,7 @@ const ApiOptions = ({ value={apiConfiguration?.vertexRegion || ""} onValueChange={(value) => setApiConfigurationField("vertexRegion", value)}> - + {VERTEX_REGIONS.map(({ value, label }) => ( @@ -1222,7 +1222,7 @@ const ApiOptions = ({ return { vendor, family } })}> - + {vsCodeLmModels.map((model) => ( @@ -1361,7 +1361,7 @@ const ApiOptions = ({ setApiConfigurationField("openRouterSpecificProvider", value) }}> - + @@ -1374,7 +1374,7 @@ const ApiOptions = ({ ))} -
+
{t("settings:providers.openRouter.providerRouting.description")}{" "} {t("settings:providers.openRouter.providerRouting.learnMore")}. @@ -1441,7 +1441,7 @@ const ApiOptions = ({ } }}> - + {selectedProviderModelOptions.map((option) => ( diff --git a/webview-ui/src/components/settings/AutoApproveSettings.tsx b/webview-ui/src/components/settings/AutoApproveSettings.tsx index 98cd4ade3b..a5f9e9c1c7 100644 --- a/webview-ui/src/components/settings/AutoApproveSettings.tsx +++ b/webview-ui/src/components/settings/AutoApproveSettings.tsx @@ -4,7 +4,7 @@ import { VSCodeButton, VSCodeCheckbox, VSCodeTextField } from "@vscode/webview-u import { CheckCheck } from "lucide-react" import { vscode } from "@/utils/vscode" -import { ExtensionStateContextType } from "@/context/ExtensionStateContext" +import { Slider } from "@/components/ui" import { SetCachedStateField } from "./types" import { SectionHeader } from "./SectionHeader" @@ -22,7 +22,19 @@ type AutoApproveSettingsProps = HTMLAttributes & { alwaysAllowSubtasks?: boolean alwaysAllowExecute?: boolean allowedCommands?: string[] - setCachedStateField: SetCachedStateField + setCachedStateField: SetCachedStateField< + | "alwaysAllowReadOnly" + | "alwaysAllowWrite" + | "writeDelayMs" + | "alwaysAllowBrowser" + | "alwaysApproveResubmit" + | "requestDelaySeconds" + | "alwaysAllowMcp" + | "alwaysAllowModeSwitch" + | "alwaysAllowSubtasks" + | "alwaysAllowExecute" + | "allowedCommands" + > } export const AutoApproveSettings = ({ @@ -71,9 +83,9 @@ export const AutoApproveSettings = ({ data-testid="always-allow-readonly-checkbox"> {t("settings:autoApprove.readOnly.label")} -

+

{t("settings:autoApprove.readOnly.description")} -

+
@@ -83,36 +95,32 @@ export const AutoApproveSettings = ({ data-testid="always-allow-write-checkbox"> {t("settings:autoApprove.write.label")} -

+

{t("settings:autoApprove.write.description")} -

- {alwaysAllowWrite && ( -
-
- setCachedStateField("writeDelayMs", parseInt(e.target.value))} - data-testid="write-delay-slider" - className="h-2 focus:outline-0 w-4/5 accent-vscode-button-background" - /> - {writeDelayMs}ms -
-

- {t("settings:autoApprove.write.delayLabel")} -

-
- )} +
+ {alwaysAllowWrite && ( +
+
+
+ setCachedStateField("writeDelayMs", value)} + data-testid="write-delay-slider" + /> + {writeDelayMs}ms +
+
+ {t("settings:autoApprove.write.delayLabel")} +
+
+
+ )} +
{t("settings:autoApprove.browser.label")} -

- {t("settings:autoApprove.browser.description")} -
- {t("settings:autoApprove.browser.note")} -

+
+
{t("settings:autoApprove.browser.description")}
+
{t("settings:autoApprove.browser.note")}
+
@@ -134,38 +141,32 @@ export const AutoApproveSettings = ({ data-testid="always-approve-resubmit-checkbox"> {t("settings:autoApprove.retry.label")} -

+

{t("settings:autoApprove.retry.description")} -

- {alwaysApproveResubmit && ( -
-
- - setCachedStateField("requestDelaySeconds", parseInt(e.target.value)) - } - data-testid="request-delay-slider" - className="h-2 focus:outline-0 w-4/5 accent-vscode-button-background" - /> - {requestDelaySeconds}s -
-

- {t("settings:autoApprove.retry.delayLabel")} -

-
- )} +
+ {alwaysApproveResubmit && ( +
+
+
+ setCachedStateField("requestDelaySeconds", value)} + data-testid="request-delay-slider" + /> + {requestDelaySeconds}s +
+
+ {t("settings:autoApprove.retry.delayLabel")} +
+
+
+ )} +
{t("settings:autoApprove.mcp.label")} -

+

{t("settings:autoApprove.mcp.description")} -

+
@@ -185,9 +186,9 @@ export const AutoApproveSettings = ({ data-testid="always-allow-mode-switch-checkbox"> {t("settings:autoApprove.modeSwitch.label")} -

+

{t("settings:autoApprove.modeSwitch.description")} -

+
@@ -197,9 +198,9 @@ export const AutoApproveSettings = ({ data-testid="always-allow-subtasks-checkbox"> {t("settings:autoApprove.subtasks.label")} -

+

{t("settings:autoApprove.subtasks.description")} -

+
@@ -209,71 +210,63 @@ export const AutoApproveSettings = ({ data-testid="always-allow-execute-checkbox"> {t("settings:autoApprove.execute.label")} -

+

{t("settings:autoApprove.execute.description")} -

- {alwaysAllowExecute && ( -
- +
+
+ + {alwaysAllowExecute && ( +
+
+
+ +
+ setCommandInput(e.target.value)} + onKeyDown={(e: any) => { + if (e.key === "Enter") { + e.preventDefault() + handleAddCommand() + } + }} + placeholder={t("settings:autoApprove.execute.commandPlaceholder")} + className="grow" + data-testid="command-input" + /> + + {t("settings:autoApprove.execute.addButton")} + +
+ +
+ {(allowedCommands ?? []).map((cmd, index) => ( +
+ {cmd} + { + const newCommands = (allowedCommands ?? []).filter((_, i) => i !== index) + setCachedStateField("allowedCommands", newCommands) + vscode.postMessage({ type: "allowedCommands", commands: newCommands }) + }}> + + +
+ ))} +
+
+ )}
) diff --git a/webview-ui/src/components/settings/BrowserSettings.tsx b/webview-ui/src/components/settings/BrowserSettings.tsx index 626f84ac1f..ee998ad2b4 100644 --- a/webview-ui/src/components/settings/BrowserSettings.tsx +++ b/webview-ui/src/components/settings/BrowserSettings.tsx @@ -4,10 +4,9 @@ import { SquareMousePointer } from "lucide-react" import { vscode } from "@/utils/vscode" import { useAppTranslation } from "@/i18n/TranslationContext" -import { Select, SelectContent, SelectGroup, SelectItem, SelectTrigger, SelectValue } from "@/components/ui" +import { Select, SelectContent, SelectGroup, SelectItem, SelectTrigger, SelectValue, Slider } from "@/components/ui" import { SetCachedStateField } from "./types" -import { sliderLabelStyle } from "./styles" import { SectionHeader } from "./SectionHeader" import { Section } from "./Section" @@ -128,119 +127,110 @@ export const BrowserSettings = ({ onChange={(e: any) => setCachedStateField("browserToolEnabled", e.target.checked)}> {t("settings:browser.enable.label")} -

+

{t("settings:browser.enable.description")} -

- {browserToolEnabled && ( -
-
- - -

- {t("settings:browser.viewport.description")} -

-
-
-
- {t("settings:browser.screenshotQuality.label")} -
- - setCachedStateField("screenshotQuality", parseInt(e.target.value)) - } - /> - {screenshotQuality ?? 75}% -
-
-

- {t("settings:browser.screenshotQuality.description")} -

-
-
-
- { - // Update the global state - remoteBrowserEnabled now means "enable remote browser connection" - setCachedStateField("remoteBrowserEnabled", e.target.checked) - if (!e.target.checked) { - // If disabling remote browser, clear the custom URL - setCachedStateField("remoteBrowserHost", undefined) - } - }}> - {t("settings:browser.remote.label")} - -

- {t("settings:browser.remote.description")} -

-
- {remoteBrowserEnabled && ( - <> -
- - setCachedStateField( - "remoteBrowserHost", - e.target.value || undefined, - ) - } - placeholder={t("settings:browser.remote.urlPlaceholder")} - style={{ flexGrow: 1 }} - /> - - {testingConnection || discovering - ? t("settings:browser.remote.testingButton") - : t("settings:browser.remote.testButton")} - -
- {testResult && ( -
- {testResult.message} -
- )} -

- {t("settings:browser.remote.instructions")} -

- - )} +
+
+ + {browserToolEnabled && ( +
+
+ + +
+ {t("settings:browser.viewport.description")}
- )} -
+ +
+ +
+ setCachedStateField("screenshotQuality", value)} + /> + {screenshotQuality ?? 75}% +
+
+ {t("settings:browser.screenshotQuality.description")} +
+
+ +
+ { + // Update the global state - remoteBrowserEnabled now means "enable remote browser connection". + setCachedStateField("remoteBrowserEnabled", e.target.checked) + + if (!e.target.checked) { + // If disabling remote browser, clear the custom URL. + setCachedStateField("remoteBrowserHost", undefined) + } + }}> + + +
+ {t("settings:browser.remote.description")} +
+
+ + {remoteBrowserEnabled && ( + <> +
+ + setCachedStateField("remoteBrowserHost", e.target.value || undefined) + } + placeholder={t("settings:browser.remote.urlPlaceholder")} + style={{ flexGrow: 1 }} + /> + + {testingConnection || discovering + ? t("settings:browser.remote.testingButton") + : t("settings:browser.remote.testButton")} + +
+ {testResult && ( +
+ {testResult.message} +
+ )} +
+ {t("settings:browser.remote.instructions")} +
+ + )} +
+ )}
) diff --git a/webview-ui/src/components/settings/ContextManagementSettings.tsx b/webview-ui/src/components/settings/ContextManagementSettings.tsx index 8d90f93af6..8493ea2627 100644 --- a/webview-ui/src/components/settings/ContextManagementSettings.tsx +++ b/webview-ui/src/components/settings/ContextManagementSettings.tsx @@ -4,9 +4,9 @@ import { VSCodeCheckbox } from "@vscode/webview-ui-toolkit/react" import { Database } from "lucide-react" import { cn } from "@/lib/utils" +import { Slider } from "@/components/ui" import { SetCachedStateField } from "./types" -import { sliderLabelStyle } from "./styles" import { SectionHeader } from "./SectionHeader" import { Section } from "./Section" @@ -37,61 +37,55 @@ export const ContextManagementSettings = ({
-
- {t("settings:contextManagement.openTabs.label")} -
- setCachedStateField("maxOpenTabsContext", parseInt(e.target.value))} - className="h-2 focus:outline-0 w-4/5 accent-vscode-button-background" - data-testid="open-tabs-limit-slider" - /> - {maxOpenTabsContext ?? 20} -
+ {t("settings:contextManagement.openTabs.label")} +
+ setCachedStateField("maxOpenTabsContext", value)} + data-testid="open-tabs-limit-slider" + /> + {maxOpenTabsContext ?? 20}
-

+

{t("settings:contextManagement.openTabs.description")} -

+
-
- {t("settings:contextManagement.workspaceFiles.label")} -
- setCachedStateField("maxWorkspaceFiles", parseInt(e.target.value))} - className="h-2 focus:outline-0 w-4/5 accent-vscode-button-background" - data-testid="workspace-files-limit-slider" - /> - {maxWorkspaceFiles ?? 200} -
+ + {t("settings:contextManagement.workspaceFiles.label")} + +
+ setCachedStateField("maxWorkspaceFiles", value)} + data-testid="workspace-files-limit-slider" + /> + {maxWorkspaceFiles ?? 200}
-

+

{t("settings:contextManagement.workspaceFiles.description")} -

+
{ - setCachedStateField("showRooIgnoredFiles", e.target.checked) - }} + onChange={(e: any) => setCachedStateField("showRooIgnoredFiles", e.target.checked)} data-testid="show-rooignored-files-checkbox"> - {t("settings:contextManagement.rooignore.label")} + -

+

{t("settings:contextManagement.rooignore.description")} -

+
diff --git a/webview-ui/src/components/settings/LanguageSettings.tsx b/webview-ui/src/components/settings/LanguageSettings.tsx index 8bfa39c5aa..74f35b891a 100644 --- a/webview-ui/src/components/settings/LanguageSettings.tsx +++ b/webview-ui/src/components/settings/LanguageSettings.tsx @@ -47,7 +47,7 @@ export const LanguageSettings = ({ language, setCachedStateField, className, ...
setCachedStateField("ttsSpeed", parseFloat(e.target.value))} - className="h-2 focus:outline-0 w-4/5 accent-vscode-button-background" - aria-label="Speed" +
+
+ + {ttsEnabled && ( +
+
+ +
+ setCachedStateField("ttsSpeed", value)} data-testid="tts-speed-slider" /> - {((ttsSpeed ?? 1.0) * 100).toFixed(0)}% + {((ttsSpeed ?? 1.0) * 100).toFixed(0)}%
-

- {t("settings:notifications.tts.speedLabel")} -

- )} -
+ + )} +
{t("settings:notifications.sound.label")} -

+

{t("settings:notifications.sound.description")} -

- {soundEnabled && ( -
-
- setCachedStateField("soundVolume", parseFloat(e.target.value))} - className="h-2 focus:outline-0 w-4/5 accent-vscode-button-background" - aria-label="Volume" +
+
+ + {soundEnabled && ( +
+
+ +
+ setCachedStateField("soundVolume", value)} data-testid="sound-volume-slider" /> - - {((soundVolume ?? 0.5) * 100).toFixed(0)}% - + {((soundVolume ?? 0.5) * 100).toFixed(0)}%
-

- {t("settings:notifications.sound.volumeLabel")} -

- )} -
+
+ )}
) diff --git a/webview-ui/src/components/settings/Section.tsx b/webview-ui/src/components/settings/Section.tsx index 9c05daf298..99f21e8530 100644 --- a/webview-ui/src/components/settings/Section.tsx +++ b/webview-ui/src/components/settings/Section.tsx @@ -5,5 +5,5 @@ import { cn } from "@/lib/utils" type SectionProps = HTMLAttributes export const Section = ({ className, ...props }: SectionProps) => ( -
+
) diff --git a/webview-ui/src/components/settings/SettingsView.tsx b/webview-ui/src/components/settings/SettingsView.tsx index abb69f58c3..10112d8924 100644 --- a/webview-ui/src/components/settings/SettingsView.tsx +++ b/webview-ui/src/components/settings/SettingsView.tsx @@ -466,7 +466,6 @@ const SettingsView = forwardRef(({ onDone },
void - maxValue?: number // Some providers like OpenAI use 0-2 range + maxValue?: number // Some providers like OpenAI use 0-2 range. } export const TemperatureControl = ({ value, onChange, maxValue = 1 }: TemperatureControlProps) => { const { t } = useAppTranslation() const [isCustomTemperature, setIsCustomTemperature] = useState(value !== undefined) const [inputValue, setInputValue] = useState(value) + useDebounce(() => onChange(inputValue), 50, [onChange, inputValue]) - // Sync internal state with prop changes when switching profiles + + // Sync internal state with prop changes when switching profiles. useEffect(() => { const hasCustomTemperature = value !== undefined && value !== null setIsCustomTemperature(hasCustomTemperature) @@ -29,39 +33,37 @@ export const TemperatureControl = ({ value, onChange, maxValue = 1 }: Temperatur onChange={(e: any) => { const isChecked = e.target.checked setIsCustomTemperature(isChecked) + if (!isChecked) { - setInputValue(null) // Unset the temperature, note that undefined is unserializable + setInputValue(null) // Unset the temperature, note that undefined is unserializable. } else { - setInputValue(value ?? 0) // Use the value from apiConfiguration, if set + setInputValue(value ?? 0) // Use the value from apiConfiguration, if set. } }}> - {t("settings:temperature.useCustom")} + -
{t("settings:temperature.description")}
+
+ {t("settings:temperature.description")} +
{isCustomTemperature && ( -
-
- setInputValue(parseFloat(e.target.value))} - /> - {inputValue} +
+
+
+ setInputValue(value)} + /> + {inputValue} +
+
+ {t("settings:temperature.rangeDescription")} +
-

- {t("settings:temperature.rangeDescription")} -

)} diff --git a/webview-ui/src/components/settings/TerminalSettings.tsx b/webview-ui/src/components/settings/TerminalSettings.tsx index 2bd0c529d2..27e4a5b587 100644 --- a/webview-ui/src/components/settings/TerminalSettings.tsx +++ b/webview-ui/src/components/settings/TerminalSettings.tsx @@ -3,9 +3,9 @@ import { useAppTranslation } from "@/i18n/TranslationContext" import { SquareTerminal } from "lucide-react" import { cn } from "@/lib/utils" +import { Slider } from "@/components/ui" import { SetCachedStateField } from "./types" -import { sliderLabelStyle } from "./styles" import { SectionHeader } from "./SectionHeader" import { Section } from "./Section" @@ -35,54 +35,44 @@ export const TerminalSettings = ({
-
- {t("settings:terminal.outputLineLimit.label")} -
- - setCachedStateField("terminalOutputLineLimit", parseInt(e.target.value)) - } - className="h-2 focus:outline-0 w-4/5 accent-vscode-button-background" - data-testid="terminal-output-limit-slider" - /> - {terminalOutputLineLimit ?? 500} -
+ +
+ setCachedStateField("terminalOutputLineLimit", value)} + data-testid="terminal-output-limit-slider" + /> + {terminalOutputLineLimit ?? 500}
-

+

{t("settings:terminal.outputLineLimit.description")} -

+
-
- {t("settings:terminal.shellIntegrationTimeout.label")} -
- - setCachedStateField( - "terminalShellIntegrationTimeout", - Math.min(60000, Math.max(1000, parseInt(e.target.value))), - ) - } - className="h-2 focus:outline-0 w-4/5 accent-vscode-button-background" - /> - - {(terminalShellIntegrationTimeout ?? 5000) / 1000}s - -
-

- {t("settings:terminal.shellIntegrationTimeout.description")} -

+ +
+ + setCachedStateField( + "terminalShellIntegrationTimeout", + Math.min(60000, Math.max(1000, value)), + ) + } + /> + {(terminalShellIntegrationTimeout ?? 5000) / 1000}s +
+
+ {t("settings:terminal.shellIntegrationTimeout.description")}
diff --git a/webview-ui/src/components/settings/__tests__/ApiConfigManager.test.tsx b/webview-ui/src/components/settings/__tests__/ApiConfigManager.test.tsx index 81431db2f7..6dba740d8e 100644 --- a/webview-ui/src/components/settings/__tests__/ApiConfigManager.test.tsx +++ b/webview-ui/src/components/settings/__tests__/ApiConfigManager.test.tsx @@ -1,13 +1,12 @@ +// npx jest src/components/settings/__tests__/ApiConfigManager.test.tsx + +import React from "react" import { render, screen, fireEvent, within } from "@testing-library/react" + import ApiConfigManager from "../ApiConfigManager" // Mock VSCode components jest.mock("@vscode/webview-ui-toolkit/react", () => ({ - VSCodeButton: ({ children, onClick, title, disabled, "data-testid": dataTestId }: any) => ( - - ), VSCodeTextField: ({ value, onInput, placeholder, onKeyDown, "data-testid": dataTestId }: any) => ( ({ ), })) -jest.mock("vscrui", () => ({ - Dropdown: ({ id, value, onChange, options, role }: any) => ( -
- -
- ), -})) - -// Mock Dialog component -jest.mock("@/components/ui/dialog", () => ({ +jest.mock("@/components/ui", () => ({ + ...jest.requireActual("@/components/ui"), Dialog: ({ children, open, onOpenChange }: any) => (
{children} @@ -43,10 +28,6 @@ jest.mock("@/components/ui/dialog", () => ({ ), DialogContent: ({ children }: any) =>
{children}
, DialogTitle: ({ children }: any) =>
{children}
, -})) - -// Mock UI components -jest.mock("@/components/ui", () => ({ Button: ({ children, onClick, disabled, variant, "data-testid": dataTestId }: any) => (