mirror of
https://github.com/RooVetGit/Roo-Code.git
synced 2026-08-28 05:27:24 +00:00
Use the Slider component everywhere (#1786)
* Use the Slider component everywhere * onValueCommit -> onValueChange * More cleanup * Fix tests * Add missing translations * Update webview-ui/src/components/settings/ContextManagementSettings.tsx Co-authored-by: ellipsis-dev[bot] <65095814+ellipsis-dev[bot]@users.noreply.github.com> * SelectDropdown fixes * Fix tests --------- Co-authored-by: ellipsis-dev[bot] <65095814+ellipsis-dev[bot]@users.noreply.github.com>
This commit is contained in:
parent
2267cad93d
commit
f4abdf4b95
40 changed files with 706 additions and 768 deletions
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -103,15 +103,3 @@ export const VSCodeRadio: React.FC<VSCodeProps> = ({ children, value, checked, o
|
|||
|
||||
export const VSCodeRadioGroup: React.FC<VSCodeProps> = ({ children, onChange, ...props }) =>
|
||||
React.createElement("div", { role: "radiogroup", onChange, ...props }, children)
|
||||
|
||||
export const VSCodeSlider: React.FC<VSCodeProps> = ({ 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,
|
||||
})
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -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<HTMLDivElement> & {
|
||||
rateLimitSeconds: number
|
||||
terminalShellIntegrationTimeout: number | undefined
|
||||
diffEnabled?: boolean
|
||||
fuzzyMatchThreshold?: number
|
||||
setCachedStateField: SetCachedStateField<
|
||||
"rateLimitSeconds" | "diffEnabled" | "fuzzyMatchThreshold" | "terminalShellIntegrationTimeout"
|
||||
>
|
||||
setCachedStateField: SetCachedStateField<"rateLimitSeconds" | "diffEnabled" | "fuzzyMatchThreshold">
|
||||
experiments: Record<ExperimentId, boolean>
|
||||
setExperimentEnabled: SetExperimentEnabled
|
||||
}
|
||||
export const AdvancedSettings = ({
|
||||
rateLimitSeconds,
|
||||
terminalShellIntegrationTimeout,
|
||||
diffEnabled,
|
||||
fuzzyMatchThreshold,
|
||||
setCachedStateField,
|
||||
|
|
@ -35,6 +31,7 @@ export const AdvancedSettings = ({
|
|||
...props
|
||||
}: AdvancedSettingsProps) => {
|
||||
const { t } = useAppTranslation()
|
||||
|
||||
return (
|
||||
<div className={cn("flex flex-col gap-2", className)} {...props}>
|
||||
<SectionHeader>
|
||||
|
|
@ -49,50 +46,18 @@ export const AdvancedSettings = ({
|
|||
<div className="flex flex-col gap-2">
|
||||
<span className="font-medium">{t("settings:advanced.rateLimit.label")}</span>
|
||||
<div className="flex items-center gap-2">
|
||||
<input
|
||||
type="range"
|
||||
min="0"
|
||||
max="60"
|
||||
step="1"
|
||||
value={rateLimitSeconds}
|
||||
onChange={(e) => setCachedStateField("rateLimitSeconds", parseInt(e.target.value))}
|
||||
className="h-2 focus:outline-0 w-4/5 accent-vscode-button-background"
|
||||
<Slider
|
||||
min={0}
|
||||
max={60}
|
||||
step={1}
|
||||
value={[rateLimitSeconds]}
|
||||
onValueChange={([value]) => setCachedStateField("rateLimitSeconds", value)}
|
||||
/>
|
||||
<span style={{ ...sliderLabelStyle }}>{rateLimitSeconds}s</span>
|
||||
<span className="w-10">{rateLimitSeconds}s</span>
|
||||
</div>
|
||||
</div>
|
||||
<p className="text-vscode-descriptionForeground text-sm mt-0">
|
||||
<div className="text-vscode-descriptionForeground text-sm mt-1">
|
||||
{t("settings:advanced.rateLimit.description")}
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<div className="flex flex-col gap-2">
|
||||
<span className="font-medium">Terminal shell integration timeout</span>
|
||||
<div className="flex items-center gap-2">
|
||||
<input
|
||||
type="range"
|
||||
min="1000"
|
||||
max="60000"
|
||||
step="1000"
|
||||
value={terminalShellIntegrationTimeout}
|
||||
onChange={(e) =>
|
||||
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"
|
||||
/>
|
||||
<span style={{ ...sliderLabelStyle }}>
|
||||
{(terminalShellIntegrationTimeout ?? 4000) / 1000}s
|
||||
</span>
|
||||
</div>
|
||||
<p className="text-vscode-descriptionForeground text-sm mt-0">
|
||||
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.
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
|
@ -109,48 +74,53 @@ export const AdvancedSettings = ({
|
|||
}}>
|
||||
<span className="font-medium">{t("settings:advanced.diff.label")}</span>
|
||||
</VSCodeCheckbox>
|
||||
<p className="text-vscode-descriptionForeground text-sm mt-0">
|
||||
<div className="text-vscode-descriptionForeground text-sm">
|
||||
{t("settings:advanced.diff.description")}
|
||||
</p>
|
||||
{diffEnabled && (
|
||||
<div className="flex flex-col gap-2 mt-3 mb-2 pl-3 border-l-2 border-vscode-button-background">
|
||||
<div className="flex flex-col gap-2">
|
||||
<span className="font-medium">{t("settings:advanced.diff.strategy.label")}</span>
|
||||
<select
|
||||
value={
|
||||
experiments[EXPERIMENT_IDS.DIFF_STRATEGY]
|
||||
? "unified"
|
||||
: experiments[EXPERIMENT_IDS.MULTI_SEARCH_AND_REPLACE]
|
||||
? "multiBlock"
|
||||
: "standard"
|
||||
}
|
||||
onChange={(e) => {
|
||||
const value = e.target.value
|
||||
if (value === "standard") {
|
||||
setExperimentEnabled(EXPERIMENT_IDS.DIFF_STRATEGY, false)
|
||||
setExperimentEnabled(EXPERIMENT_IDS.MULTI_SEARCH_AND_REPLACE, false)
|
||||
} else if (value === "unified") {
|
||||
setExperimentEnabled(EXPERIMENT_IDS.DIFF_STRATEGY, true)
|
||||
setExperimentEnabled(EXPERIMENT_IDS.MULTI_SEARCH_AND_REPLACE, false)
|
||||
} else if (value === "multiBlock") {
|
||||
setExperimentEnabled(EXPERIMENT_IDS.DIFF_STRATEGY, false)
|
||||
setExperimentEnabled(EXPERIMENT_IDS.MULTI_SEARCH_AND_REPLACE, true)
|
||||
}
|
||||
}}
|
||||
className="p-2 rounded w-full bg-vscode-input-background text-vscode-input-foreground border border-vscode-input-border outline-none focus:border-vscode-focusBorder">
|
||||
<option value="standard">
|
||||
{t("settings:advanced.diff.strategy.options.standard")}
|
||||
</option>
|
||||
<option value="multiBlock">
|
||||
{t("settings:advanced.diff.strategy.options.multiBlock")}
|
||||
</option>
|
||||
<option value="unified">
|
||||
{t("settings:advanced.diff.strategy.options.unified")}
|
||||
</option>
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<p className="text-vscode-descriptionForeground text-sm mt-1">
|
||||
{diffEnabled && (
|
||||
<div className="flex flex-col gap-3 pl-3 border-l-2 border-vscode-button-background">
|
||||
<div>
|
||||
<label className="block font-medium mb-1">
|
||||
{t("settings:advanced.diff.strategy.label")}
|
||||
</label>
|
||||
<Select
|
||||
value={
|
||||
experiments[EXPERIMENT_IDS.DIFF_STRATEGY]
|
||||
? "unified"
|
||||
: experiments[EXPERIMENT_IDS.MULTI_SEARCH_AND_REPLACE]
|
||||
? "multiBlock"
|
||||
: "standard"
|
||||
}
|
||||
onValueChange={(value) => {
|
||||
if (value === "standard") {
|
||||
setExperimentEnabled(EXPERIMENT_IDS.DIFF_STRATEGY, false)
|
||||
setExperimentEnabled(EXPERIMENT_IDS.MULTI_SEARCH_AND_REPLACE, false)
|
||||
} else if (value === "unified") {
|
||||
setExperimentEnabled(EXPERIMENT_IDS.DIFF_STRATEGY, true)
|
||||
setExperimentEnabled(EXPERIMENT_IDS.MULTI_SEARCH_AND_REPLACE, false)
|
||||
} else if (value === "multiBlock") {
|
||||
setExperimentEnabled(EXPERIMENT_IDS.DIFF_STRATEGY, false)
|
||||
setExperimentEnabled(EXPERIMENT_IDS.MULTI_SEARCH_AND_REPLACE, true)
|
||||
}
|
||||
}}>
|
||||
<SelectTrigger className="w-full">
|
||||
<SelectValue />
|
||||
</SelectTrigger>
|
||||
<SelectContent>
|
||||
<SelectItem value="standard">
|
||||
{t("settings:advanced.diff.strategy.options.standard")}
|
||||
</SelectItem>
|
||||
<SelectItem value="multiBlock">
|
||||
{t("settings:advanced.diff.strategy.options.multiBlock")}
|
||||
</SelectItem>
|
||||
<SelectItem value="unified">
|
||||
{t("settings:advanced.diff.strategy.options.unified")}
|
||||
</SelectItem>
|
||||
</SelectContent>
|
||||
</Select>
|
||||
<div className="text-vscode-descriptionForeground text-sm mt-1">
|
||||
{!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")}
|
||||
</p>
|
||||
|
||||
<span className="font-medium mt-3">{t("settings:advanced.diff.matchPrecision.label")}</span>
|
||||
<div className="flex items-center gap-2">
|
||||
<input
|
||||
type="range"
|
||||
min="0.8"
|
||||
max="1"
|
||||
step="0.005"
|
||||
value={fuzzyMatchThreshold ?? 1.0}
|
||||
onChange={(e) => {
|
||||
setCachedStateField("fuzzyMatchThreshold", parseFloat(e.target.value))
|
||||
}}
|
||||
className="h-2 focus:outline-0 w-4/5 accent-vscode-button-background"
|
||||
/>
|
||||
<span style={{ ...sliderLabelStyle }}>
|
||||
{Math.round((fuzzyMatchThreshold || 1) * 100)}%
|
||||
</span>
|
||||
</div>
|
||||
<p className="text-vscode-descriptionForeground text-sm mt-0">
|
||||
{t("settings:advanced.diff.matchPrecision.description")}
|
||||
</p>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<label className="block font-medium mb-1">
|
||||
{t("settings:advanced.diff.matchPrecision.label")}
|
||||
</label>
|
||||
<div className="flex items-center gap-2">
|
||||
<Slider
|
||||
min={0.8}
|
||||
max={1}
|
||||
step={0.005}
|
||||
value={[fuzzyMatchThreshold ?? 1.0]}
|
||||
onValueChange={([value]) => setCachedStateField("fuzzyMatchThreshold", value)}
|
||||
/>
|
||||
<span className="w-10">{Math.round((fuzzyMatchThreshold || 1) * 100)}%</span>
|
||||
</div>
|
||||
<div className="text-vscode-descriptionForeground text-sm mt-1">
|
||||
{t("settings:advanced.diff.matchPrecision.description")}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
</Section>
|
||||
</div>
|
||||
)
|
||||
|
|
|
|||
|
|
@ -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 (
|
||||
<div className="flex flex-col gap-1">
|
||||
<label htmlFor="config-profile">
|
||||
<span className="font-medium">{t("settings:providers.configProfile")}</span>
|
||||
</label>
|
||||
<label className="block font-medium mb-1">{t("settings:providers.configProfile")}</label>
|
||||
|
||||
{isRenaming ? (
|
||||
<div
|
||||
data-testid="rename-form"
|
||||
style={{ display: "flex", gap: "4px", alignItems: "center", flexDirection: "column" }}>
|
||||
<div style={{ display: "flex", gap: "4px", alignItems: "center", width: "100%" }}>
|
||||
<div data-testid="rename-form">
|
||||
<div className="flex items-center gap-1">
|
||||
<VSCodeTextField
|
||||
ref={inputRef}
|
||||
value={inputValue}
|
||||
|
|
@ -163,100 +170,75 @@ const ApiConfigManager = ({
|
|||
setError(null)
|
||||
}}
|
||||
placeholder={t("settings:providers.enterNewName")}
|
||||
style={{ flexGrow: 1 }}
|
||||
onKeyDown={(e: unknown) => {
|
||||
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"
|
||||
/>
|
||||
<VSCodeButton
|
||||
appearance="icon"
|
||||
<Button
|
||||
variant="ghost"
|
||||
size="icon"
|
||||
disabled={!inputValue.trim()}
|
||||
onClick={handleSave}
|
||||
title={t("settings:common.save")}
|
||||
data-testid="save-rename-button"
|
||||
style={{
|
||||
padding: 0,
|
||||
margin: 0,
|
||||
height: "28px",
|
||||
width: "28px",
|
||||
minWidth: "28px",
|
||||
}}>
|
||||
data-testid="save-rename-button">
|
||||
<span className="codicon codicon-check" />
|
||||
</VSCodeButton>
|
||||
<VSCodeButton
|
||||
appearance="icon"
|
||||
</Button>
|
||||
<Button
|
||||
variant="ghost"
|
||||
size="icon"
|
||||
onClick={handleCancel}
|
||||
title={t("settings:common.cancel")}
|
||||
data-testid="cancel-rename-button"
|
||||
style={{
|
||||
padding: 0,
|
||||
margin: 0,
|
||||
height: "28px",
|
||||
width: "28px",
|
||||
minWidth: "28px",
|
||||
}}>
|
||||
data-testid="cancel-rename-button">
|
||||
<span className="codicon codicon-close" />
|
||||
</VSCodeButton>
|
||||
</Button>
|
||||
</div>
|
||||
{error && (
|
||||
<p className="text-red-500 text-sm mt-2" data-testid="error-message">
|
||||
<div className="text-vscode-descriptionForeground text-sm mt-1" data-testid="error-message">
|
||||
{error}
|
||||
</p>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
) : (
|
||||
<>
|
||||
<div style={{ display: "flex", gap: "4px", alignItems: "center" }}>
|
||||
<Dropdown
|
||||
id="config-profile"
|
||||
value={currentApiConfigName}
|
||||
onChange={(value: unknown) => {
|
||||
onSelectConfig((value as DropdownOption).value)
|
||||
}}
|
||||
role="combobox"
|
||||
options={listApiConfigMeta.map((config) => ({
|
||||
value: config.name,
|
||||
label: config.name,
|
||||
}))}
|
||||
className="w-full"
|
||||
/>
|
||||
<VSCodeButton
|
||||
appearance="icon"
|
||||
<div className="flex items-center gap-1">
|
||||
<Select value={currentApiConfigName} onValueChange={onSelectConfig}>
|
||||
<SelectTrigger className="grow">
|
||||
<SelectValue placeholder={t("settings:common.select")} />
|
||||
</SelectTrigger>
|
||||
<SelectContent>
|
||||
{listApiConfigMeta.map((config) => (
|
||||
<SelectItem key={config.name} value={config.name}>
|
||||
{config.name}
|
||||
</SelectItem>
|
||||
))}
|
||||
</SelectContent>
|
||||
</Select>
|
||||
<Button
|
||||
variant="ghost"
|
||||
size="icon"
|
||||
onClick={handleAdd}
|
||||
title={t("settings:providers.addProfile")}
|
||||
data-testid="add-profile-button"
|
||||
style={{
|
||||
padding: 0,
|
||||
margin: 0,
|
||||
height: "28px",
|
||||
width: "28px",
|
||||
minWidth: "28px",
|
||||
}}>
|
||||
data-testid="add-profile-button">
|
||||
<span className="codicon codicon-add" />
|
||||
</VSCodeButton>
|
||||
</Button>
|
||||
{currentApiConfigName && (
|
||||
<>
|
||||
<VSCodeButton
|
||||
appearance="icon"
|
||||
<Button
|
||||
variant="ghost"
|
||||
size="icon"
|
||||
onClick={handleStartRename}
|
||||
title={t("settings:providers.renameProfile")}
|
||||
data-testid="rename-profile-button"
|
||||
style={{
|
||||
padding: 0,
|
||||
margin: 0,
|
||||
height: "28px",
|
||||
width: "28px",
|
||||
minWidth: "28px",
|
||||
}}>
|
||||
data-testid="rename-profile-button">
|
||||
<span className="codicon codicon-edit" />
|
||||
</VSCodeButton>
|
||||
<VSCodeButton
|
||||
appearance="icon"
|
||||
</Button>
|
||||
<Button
|
||||
variant="ghost"
|
||||
size="icon"
|
||||
onClick={handleDelete}
|
||||
title={
|
||||
isOnlyProfile
|
||||
|
|
@ -264,27 +246,15 @@ const ApiConfigManager = ({
|
|||
: t("settings:providers.deleteProfile")
|
||||
}
|
||||
data-testid="delete-profile-button"
|
||||
disabled={isOnlyProfile}
|
||||
style={{
|
||||
padding: 0,
|
||||
margin: 0,
|
||||
height: "28px",
|
||||
width: "28px",
|
||||
minWidth: "28px",
|
||||
}}>
|
||||
disabled={isOnlyProfile}>
|
||||
<span className="codicon codicon-trash" />
|
||||
</VSCodeButton>
|
||||
</Button>
|
||||
</>
|
||||
)}
|
||||
</div>
|
||||
<p
|
||||
style={{
|
||||
fontSize: "12px",
|
||||
margin: "5px 0 12px",
|
||||
color: "var(--vscode-descriptionForeground)",
|
||||
}}>
|
||||
<div className="text-vscode-descriptionForeground text-sm mt-1">
|
||||
{t("settings:providers.description")}
|
||||
</p>
|
||||
</div>
|
||||
</>
|
||||
)}
|
||||
|
||||
|
|
|
|||
|
|
@ -249,7 +249,7 @@ const ApiOptions = ({
|
|||
value={selectedProvider}
|
||||
onValueChange={(value) => setApiConfigurationField("apiProvider", value as ApiProvider)}>
|
||||
<SelectTrigger className="w-full">
|
||||
<SelectValue placeholder="Select" />
|
||||
<SelectValue placeholder={t("settings:common.select")} />
|
||||
</SelectTrigger>
|
||||
<SelectContent>
|
||||
<SelectItem value="openrouter">OpenRouter</SelectItem>
|
||||
|
|
@ -521,7 +521,7 @@ const ApiOptions = ({
|
|||
value={apiConfiguration?.awsRegion || ""}
|
||||
onValueChange={(value) => setApiConfigurationField("awsRegion", value)}>
|
||||
<SelectTrigger className="w-full">
|
||||
<SelectValue placeholder="Select" />
|
||||
<SelectValue placeholder={t("settings:common.select")} />
|
||||
</SelectTrigger>
|
||||
<SelectContent>
|
||||
{AWS_REGIONS.map(({ value, label }) => (
|
||||
|
|
@ -595,7 +595,7 @@ const ApiOptions = ({
|
|||
value={apiConfiguration?.vertexRegion || ""}
|
||||
onValueChange={(value) => setApiConfigurationField("vertexRegion", value)}>
|
||||
<SelectTrigger className="w-full">
|
||||
<SelectValue placeholder="Select" />
|
||||
<SelectValue placeholder={t("settings:common.select")} />
|
||||
</SelectTrigger>
|
||||
<SelectContent>
|
||||
{VERTEX_REGIONS.map(({ value, label }) => (
|
||||
|
|
@ -1222,7 +1222,7 @@ const ApiOptions = ({
|
|||
return { vendor, family }
|
||||
})}>
|
||||
<SelectTrigger className="w-full">
|
||||
<SelectValue placeholder="Select" />
|
||||
<SelectValue placeholder={t("settings:common.select")} />
|
||||
</SelectTrigger>
|
||||
<SelectContent>
|
||||
{vsCodeLmModels.map((model) => (
|
||||
|
|
@ -1361,7 +1361,7 @@ const ApiOptions = ({
|
|||
setApiConfigurationField("openRouterSpecificProvider", value)
|
||||
}}>
|
||||
<SelectTrigger className="w-full">
|
||||
<SelectValue placeholder="Select" />
|
||||
<SelectValue placeholder={t("settings:common.select")} />
|
||||
</SelectTrigger>
|
||||
<SelectContent>
|
||||
<SelectItem value={OPENROUTER_DEFAULT_PROVIDER_NAME}>
|
||||
|
|
@ -1374,7 +1374,7 @@ const ApiOptions = ({
|
|||
))}
|
||||
</SelectContent>
|
||||
</Select>
|
||||
<div className="text-sm text-vscode-descriptionForeground">
|
||||
<div className="text-sm text-vscode-descriptionForeground mt-1">
|
||||
{t("settings:providers.openRouter.providerRouting.description")}{" "}
|
||||
<a href="https://openrouter.ai/docs/features/provider-routing">
|
||||
{t("settings:providers.openRouter.providerRouting.learnMore")}.
|
||||
|
|
@ -1441,7 +1441,7 @@ const ApiOptions = ({
|
|||
}
|
||||
}}>
|
||||
<SelectTrigger className="w-full">
|
||||
<SelectValue placeholder="Select" />
|
||||
<SelectValue placeholder={t("settings:common.select")} />
|
||||
</SelectTrigger>
|
||||
<SelectContent>
|
||||
{selectedProviderModelOptions.map((option) => (
|
||||
|
|
|
|||
|
|
@ -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<HTMLDivElement> & {
|
|||
alwaysAllowSubtasks?: boolean
|
||||
alwaysAllowExecute?: boolean
|
||||
allowedCommands?: string[]
|
||||
setCachedStateField: SetCachedStateField<keyof ExtensionStateContextType>
|
||||
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">
|
||||
<span className="font-medium">{t("settings:autoApprove.readOnly.label")}</span>
|
||||
</VSCodeCheckbox>
|
||||
<p className="text-vscode-descriptionForeground text-sm mt-0">
|
||||
<div className="text-vscode-descriptionForeground text-sm mt-1">
|
||||
{t("settings:autoApprove.readOnly.description")}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
|
|
@ -83,36 +95,32 @@ export const AutoApproveSettings = ({
|
|||
data-testid="always-allow-write-checkbox">
|
||||
<span className="font-medium">{t("settings:autoApprove.write.label")}</span>
|
||||
</VSCodeCheckbox>
|
||||
<p className="text-vscode-descriptionForeground text-sm mt-0">
|
||||
<div className="text-vscode-descriptionForeground text-sm mt-1">
|
||||
{t("settings:autoApprove.write.description")}
|
||||
</p>
|
||||
{alwaysAllowWrite && (
|
||||
<div
|
||||
style={{
|
||||
marginTop: 10,
|
||||
paddingLeft: 10,
|
||||
borderLeft: "2px solid var(--vscode-button-background)",
|
||||
}}>
|
||||
<div style={{ display: "flex", alignItems: "center", gap: "10px" }}>
|
||||
<input
|
||||
type="range"
|
||||
min="0"
|
||||
max="5000"
|
||||
step="100"
|
||||
value={writeDelayMs}
|
||||
onChange={(e) => setCachedStateField("writeDelayMs", parseInt(e.target.value))}
|
||||
data-testid="write-delay-slider"
|
||||
className="h-2 focus:outline-0 w-4/5 accent-vscode-button-background"
|
||||
/>
|
||||
<span style={{ minWidth: "45px", textAlign: "left" }}>{writeDelayMs}ms</span>
|
||||
</div>
|
||||
<p className="text-vscode-descriptionForeground text-sm mt-1">
|
||||
{t("settings:autoApprove.write.delayLabel")}
|
||||
</p>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{alwaysAllowWrite && (
|
||||
<div className="flex flex-col gap-3 pl-3 border-l-2 border-vscode-button-background">
|
||||
<div>
|
||||
<div className="flex items-center gap-2">
|
||||
<Slider
|
||||
min={0}
|
||||
max={5000}
|
||||
step={100}
|
||||
value={[writeDelayMs]}
|
||||
onValueChange={([value]) => setCachedStateField("writeDelayMs", value)}
|
||||
data-testid="write-delay-slider"
|
||||
/>
|
||||
<span className="w-20">{writeDelayMs}ms</span>
|
||||
</div>
|
||||
<div className="text-vscode-descriptionForeground text-sm mt-1">
|
||||
{t("settings:autoApprove.write.delayLabel")}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
|
||||
<div>
|
||||
<VSCodeCheckbox
|
||||
checked={alwaysAllowBrowser}
|
||||
|
|
@ -120,11 +128,10 @@ export const AutoApproveSettings = ({
|
|||
data-testid="always-allow-browser-checkbox">
|
||||
<span className="font-medium">{t("settings:autoApprove.browser.label")}</span>
|
||||
</VSCodeCheckbox>
|
||||
<p className="text-vscode-descriptionForeground text-sm mt-0">
|
||||
{t("settings:autoApprove.browser.description")}
|
||||
<br />
|
||||
{t("settings:autoApprove.browser.note")}
|
||||
</p>
|
||||
<div className="text-vscode-descriptionForeground text-sm mt-1">
|
||||
<div>{t("settings:autoApprove.browser.description")}</div>
|
||||
<div>{t("settings:autoApprove.browser.note")}</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
|
|
@ -134,38 +141,32 @@ export const AutoApproveSettings = ({
|
|||
data-testid="always-approve-resubmit-checkbox">
|
||||
<span className="font-medium">{t("settings:autoApprove.retry.label")}</span>
|
||||
</VSCodeCheckbox>
|
||||
<p className="text-vscode-descriptionForeground text-sm mt-0">
|
||||
<div className="text-vscode-descriptionForeground text-sm mt-1">
|
||||
{t("settings:autoApprove.retry.description")}
|
||||
</p>
|
||||
{alwaysApproveResubmit && (
|
||||
<div
|
||||
style={{
|
||||
marginTop: 10,
|
||||
paddingLeft: 10,
|
||||
borderLeft: "2px solid var(--vscode-button-background)",
|
||||
}}>
|
||||
<div style={{ display: "flex", alignItems: "center", gap: "10px" }}>
|
||||
<input
|
||||
type="range"
|
||||
min="5"
|
||||
max="100"
|
||||
step="1"
|
||||
value={requestDelaySeconds}
|
||||
onChange={(e) =>
|
||||
setCachedStateField("requestDelaySeconds", parseInt(e.target.value))
|
||||
}
|
||||
data-testid="request-delay-slider"
|
||||
className="h-2 focus:outline-0 w-4/5 accent-vscode-button-background"
|
||||
/>
|
||||
<span style={{ minWidth: "45px", textAlign: "left" }}>{requestDelaySeconds}s</span>
|
||||
</div>
|
||||
<p className="text-vscode-descriptionForeground text-sm mt-0">
|
||||
{t("settings:autoApprove.retry.delayLabel")}
|
||||
</p>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{alwaysApproveResubmit && (
|
||||
<div className="flex flex-col gap-3 pl-3 border-l-2 border-vscode-button-background">
|
||||
<div>
|
||||
<div className="flex items-center gap-2">
|
||||
<Slider
|
||||
min={5}
|
||||
max={100}
|
||||
step={1}
|
||||
value={[requestDelaySeconds]}
|
||||
onValueChange={([value]) => setCachedStateField("requestDelaySeconds", value)}
|
||||
data-testid="request-delay-slider"
|
||||
/>
|
||||
<span className="w-20">{requestDelaySeconds}s</span>
|
||||
</div>
|
||||
<div className="text-vscode-descriptionForeground text-sm mt-1">
|
||||
{t("settings:autoApprove.retry.delayLabel")}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
|
||||
<div>
|
||||
<VSCodeCheckbox
|
||||
checked={alwaysAllowMcp}
|
||||
|
|
@ -173,9 +174,9 @@ export const AutoApproveSettings = ({
|
|||
data-testid="always-allow-mcp-checkbox">
|
||||
<span className="font-medium">{t("settings:autoApprove.mcp.label")}</span>
|
||||
</VSCodeCheckbox>
|
||||
<p className="text-vscode-descriptionForeground text-sm mt-0">
|
||||
<div className="text-vscode-descriptionForeground text-sm mt-1">
|
||||
{t("settings:autoApprove.mcp.description")}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
|
|
@ -185,9 +186,9 @@ export const AutoApproveSettings = ({
|
|||
data-testid="always-allow-mode-switch-checkbox">
|
||||
<span className="font-medium">{t("settings:autoApprove.modeSwitch.label")}</span>
|
||||
</VSCodeCheckbox>
|
||||
<p className="text-vscode-descriptionForeground text-sm mt-0">
|
||||
<div className="text-vscode-descriptionForeground text-sm mt-1">
|
||||
{t("settings:autoApprove.modeSwitch.description")}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
|
|
@ -197,9 +198,9 @@ export const AutoApproveSettings = ({
|
|||
data-testid="always-allow-subtasks-checkbox">
|
||||
<span className="font-medium">{t("settings:autoApprove.subtasks.label")}</span>
|
||||
</VSCodeCheckbox>
|
||||
<p className="text-vscode-descriptionForeground text-sm mt-0">
|
||||
<div className="text-vscode-descriptionForeground text-sm mt-1">
|
||||
{t("settings:autoApprove.subtasks.description")}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
|
|
@ -209,71 +210,63 @@ export const AutoApproveSettings = ({
|
|||
data-testid="always-allow-execute-checkbox">
|
||||
<span className="font-medium">{t("settings:autoApprove.execute.label")}</span>
|
||||
</VSCodeCheckbox>
|
||||
<p className="text-vscode-descriptionForeground text-sm mt-0">
|
||||
<div className="text-vscode-descriptionForeground text-sm mt-1">
|
||||
{t("settings:autoApprove.execute.description")}
|
||||
</p>
|
||||
{alwaysAllowExecute && (
|
||||
<div
|
||||
style={{
|
||||
marginTop: 10,
|
||||
paddingLeft: 10,
|
||||
borderLeft: "2px solid var(--vscode-button-background)",
|
||||
}}>
|
||||
<span className="font-medium" data-testid="allowed-commands-heading">
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{alwaysAllowExecute && (
|
||||
<div className="flex flex-col gap-3 pl-3 border-l-2 border-vscode-button-background">
|
||||
<div>
|
||||
<label className="block font-medium mb-1" data-testid="allowed-commands-heading">
|
||||
{t("settings:autoApprove.execute.allowedCommands")}
|
||||
</span>
|
||||
<p className="text-vscode-descriptionForeground text-sm mt-0">
|
||||
</label>
|
||||
<div className="text-vscode-descriptionForeground text-sm mt-1">
|
||||
{t("settings:autoApprove.execute.allowedCommandsDescription")}
|
||||
</p>
|
||||
<div style={{ display: "flex", gap: "5px", marginTop: "10px" }}>
|
||||
<VSCodeTextField
|
||||
value={commandInput}
|
||||
onInput={(e: any) => setCommandInput(e.target.value)}
|
||||
onKeyDown={(e: any) => {
|
||||
if (e.key === "Enter") {
|
||||
e.preventDefault()
|
||||
handleAddCommand()
|
||||
}
|
||||
}}
|
||||
placeholder={t("settings:autoApprove.execute.commandPlaceholder")}
|
||||
data-testid="command-input"
|
||||
style={{ flexGrow: 1 }}
|
||||
/>
|
||||
<VSCodeButton onClick={handleAddCommand} data-testid="add-command-button">
|
||||
{t("settings:autoApprove.execute.addButton")}
|
||||
</VSCodeButton>
|
||||
</div>
|
||||
<div
|
||||
style={{
|
||||
marginTop: "10px",
|
||||
display: "flex",
|
||||
flexWrap: "wrap",
|
||||
gap: "5px",
|
||||
}}>
|
||||
{(allowedCommands ?? []).map((cmd, index) => (
|
||||
<div
|
||||
key={index}
|
||||
className="border border-vscode-input-border bg-primary text-primary-foreground flex items-center gap-1 rounded-xs px-1.5 p-0.5">
|
||||
<span>{cmd}</span>
|
||||
<VSCodeButton
|
||||
appearance="icon"
|
||||
className="text-primary-foreground"
|
||||
data-testid={`remove-command-${index}`}
|
||||
onClick={() => {
|
||||
const newCommands = (allowedCommands ?? []).filter(
|
||||
(_, i) => i !== index,
|
||||
)
|
||||
setCachedStateField("allowedCommands", newCommands)
|
||||
vscode.postMessage({ type: "allowedCommands", commands: newCommands })
|
||||
}}>
|
||||
<span className="codicon codicon-close" />
|
||||
</VSCodeButton>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
|
||||
<div className="flex gap-2">
|
||||
<VSCodeTextField
|
||||
value={commandInput}
|
||||
onInput={(e: any) => 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"
|
||||
/>
|
||||
<VSCodeButton onClick={handleAddCommand} data-testid="add-command-button">
|
||||
{t("settings:autoApprove.execute.addButton")}
|
||||
</VSCodeButton>
|
||||
</div>
|
||||
|
||||
<div className="flex flex-wrap gap-2">
|
||||
{(allowedCommands ?? []).map((cmd, index) => (
|
||||
<div
|
||||
key={index}
|
||||
className="border border-vscode-input-border bg-primary text-primary-foreground flex items-center gap-1 rounded-xs px-1.5 p-0.5">
|
||||
<span>{cmd}</span>
|
||||
<VSCodeButton
|
||||
appearance="icon"
|
||||
className="text-primary-foreground"
|
||||
data-testid={`remove-command-${index}`}
|
||||
onClick={() => {
|
||||
const newCommands = (allowedCommands ?? []).filter((_, i) => i !== index)
|
||||
setCachedStateField("allowedCommands", newCommands)
|
||||
vscode.postMessage({ type: "allowedCommands", commands: newCommands })
|
||||
}}>
|
||||
<span className="codicon codicon-close" />
|
||||
</VSCodeButton>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
</Section>
|
||||
</div>
|
||||
)
|
||||
|
|
|
|||
|
|
@ -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)}>
|
||||
<span className="font-medium">{t("settings:browser.enable.label")}</span>
|
||||
</VSCodeCheckbox>
|
||||
<p className="text-vscode-descriptionForeground text-sm mt-0">
|
||||
<div className="text-vscode-descriptionForeground text-sm mt-1">
|
||||
{t("settings:browser.enable.description")}
|
||||
</p>
|
||||
{browserToolEnabled && (
|
||||
<div
|
||||
style={{
|
||||
marginLeft: 0,
|
||||
paddingLeft: 10,
|
||||
borderLeft: "2px solid var(--vscode-button-background)",
|
||||
}}>
|
||||
<div>
|
||||
<label className="block font-medium mb-1">{t("settings:browser.viewport.label")}</label>
|
||||
<Select
|
||||
value={browserViewportSize}
|
||||
onValueChange={(value) => setCachedStateField("browserViewportSize", value)}>
|
||||
<SelectTrigger className="w-full">
|
||||
<SelectValue placeholder="Select" />
|
||||
</SelectTrigger>
|
||||
<SelectContent>
|
||||
<SelectGroup>
|
||||
{options.map(({ value, label }) => (
|
||||
<SelectItem key={value} value={value}>
|
||||
{label}
|
||||
</SelectItem>
|
||||
))}
|
||||
</SelectGroup>
|
||||
</SelectContent>
|
||||
</Select>
|
||||
<p className="text-vscode-descriptionForeground text-sm mt-1">
|
||||
{t("settings:browser.viewport.description")}
|
||||
</p>
|
||||
</div>
|
||||
<div>
|
||||
<div style={{ display: "flex", flexDirection: "column", gap: "5px" }}>
|
||||
<span className="font-medium">{t("settings:browser.screenshotQuality.label")}</span>
|
||||
<div style={{ display: "flex", alignItems: "center", gap: "5px" }}>
|
||||
<input
|
||||
type="range"
|
||||
min="1"
|
||||
max="100"
|
||||
step="1"
|
||||
value={screenshotQuality ?? 75}
|
||||
className="h-2 focus:outline-0 w-4/5 accent-vscode-button-background"
|
||||
onChange={(e) =>
|
||||
setCachedStateField("screenshotQuality", parseInt(e.target.value))
|
||||
}
|
||||
/>
|
||||
<span style={{ ...sliderLabelStyle }}>{screenshotQuality ?? 75}%</span>
|
||||
</div>
|
||||
</div>
|
||||
<p className="text-vscode-descriptionForeground text-sm mt-0">
|
||||
{t("settings:browser.screenshotQuality.description")}
|
||||
</p>
|
||||
</div>
|
||||
<div className="mt-4">
|
||||
<div className="mb-2">
|
||||
<VSCodeCheckbox
|
||||
checked={remoteBrowserEnabled}
|
||||
onChange={(e: any) => {
|
||||
// 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)
|
||||
}
|
||||
}}>
|
||||
<span className="font-medium">{t("settings:browser.remote.label")}</span>
|
||||
</VSCodeCheckbox>
|
||||
<p className="text-vscode-descriptionForeground text-sm mt-0 ml-6">
|
||||
{t("settings:browser.remote.description")}
|
||||
</p>
|
||||
</div>
|
||||
{remoteBrowserEnabled && (
|
||||
<>
|
||||
<div style={{ display: "flex", gap: "5px", marginTop: "10px" }}>
|
||||
<VSCodeTextField
|
||||
value={remoteBrowserHost ?? ""}
|
||||
onChange={(e: any) =>
|
||||
setCachedStateField(
|
||||
"remoteBrowserHost",
|
||||
e.target.value || undefined,
|
||||
)
|
||||
}
|
||||
placeholder={t("settings:browser.remote.urlPlaceholder")}
|
||||
style={{ flexGrow: 1 }}
|
||||
/>
|
||||
<VSCodeButton
|
||||
disabled={testingConnection}
|
||||
onClick={remoteBrowserHost ? testConnection : discoverBrowser}>
|
||||
{testingConnection || discovering
|
||||
? t("settings:browser.remote.testingButton")
|
||||
: t("settings:browser.remote.testButton")}
|
||||
</VSCodeButton>
|
||||
</div>
|
||||
{testResult && (
|
||||
<div
|
||||
className={`p-2 mt-2 mb-2 rounded text-sm ${
|
||||
testResult.success
|
||||
? "bg-green-800/20 text-green-400"
|
||||
: "bg-red-800/20 text-red-400"
|
||||
}`}>
|
||||
{testResult.message}
|
||||
</div>
|
||||
)}
|
||||
<p className="text-vscode-descriptionForeground text-sm mt-2">
|
||||
{t("settings:browser.remote.instructions")}
|
||||
</p>
|
||||
</>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{browserToolEnabled && (
|
||||
<div className="flex flex-col gap-3 pl-3 border-l-2 border-vscode-button-background">
|
||||
<div>
|
||||
<label className="block font-medium mb-1">{t("settings:browser.viewport.label")}</label>
|
||||
<Select
|
||||
value={browserViewportSize}
|
||||
onValueChange={(value) => setCachedStateField("browserViewportSize", value)}>
|
||||
<SelectTrigger className="w-full">
|
||||
<SelectValue placeholder={t("settings:common.select")} />
|
||||
</SelectTrigger>
|
||||
<SelectContent>
|
||||
<SelectGroup>
|
||||
{options.map(({ value, label }) => (
|
||||
<SelectItem key={value} value={value}>
|
||||
{label}
|
||||
</SelectItem>
|
||||
))}
|
||||
</SelectGroup>
|
||||
</SelectContent>
|
||||
</Select>
|
||||
<div className="text-vscode-descriptionForeground text-sm mt-1">
|
||||
{t("settings:browser.viewport.description")}
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<label className="block font-medium mb-1">
|
||||
{t("settings:browser.screenshotQuality.label")}
|
||||
</label>
|
||||
<div className="flex items-center gap-2">
|
||||
<Slider
|
||||
min={1}
|
||||
max={100}
|
||||
step={1}
|
||||
value={[screenshotQuality ?? 75]}
|
||||
onValueChange={([value]) => setCachedStateField("screenshotQuality", value)}
|
||||
/>
|
||||
<span className="w-10">{screenshotQuality ?? 75}%</span>
|
||||
</div>
|
||||
<div className="text-vscode-descriptionForeground text-sm mt-1">
|
||||
{t("settings:browser.screenshotQuality.description")}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<VSCodeCheckbox
|
||||
checked={remoteBrowserEnabled}
|
||||
onChange={(e: any) => {
|
||||
// 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)
|
||||
}
|
||||
}}>
|
||||
<label className="block font-medium mb-1">{t("settings:browser.remote.label")}</label>
|
||||
</VSCodeCheckbox>
|
||||
<div className="text-vscode-descriptionForeground text-sm mt-1">
|
||||
{t("settings:browser.remote.description")}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{remoteBrowserEnabled && (
|
||||
<>
|
||||
<div className="flex items-center gap-2">
|
||||
<VSCodeTextField
|
||||
value={remoteBrowserHost ?? ""}
|
||||
onChange={(e: any) =>
|
||||
setCachedStateField("remoteBrowserHost", e.target.value || undefined)
|
||||
}
|
||||
placeholder={t("settings:browser.remote.urlPlaceholder")}
|
||||
style={{ flexGrow: 1 }}
|
||||
/>
|
||||
<VSCodeButton
|
||||
disabled={testingConnection}
|
||||
onClick={remoteBrowserHost ? testConnection : discoverBrowser}>
|
||||
{testingConnection || discovering
|
||||
? t("settings:browser.remote.testingButton")
|
||||
: t("settings:browser.remote.testButton")}
|
||||
</VSCodeButton>
|
||||
</div>
|
||||
{testResult && (
|
||||
<div
|
||||
className={`p-2 rounded-xs text-sm ${
|
||||
testResult.success
|
||||
? "bg-green-800/20 text-green-400"
|
||||
: "bg-red-800/20 text-red-400"
|
||||
}`}>
|
||||
{testResult.message}
|
||||
</div>
|
||||
)}
|
||||
<div className="text-vscode-descriptionForeground text-sm mt-1">
|
||||
{t("settings:browser.remote.instructions")}
|
||||
</div>
|
||||
</>
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
</Section>
|
||||
</div>
|
||||
)
|
||||
|
|
|
|||
|
|
@ -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 = ({
|
|||
|
||||
<Section>
|
||||
<div>
|
||||
<div className="flex flex-col gap-2">
|
||||
<span className="font-medium">{t("settings:contextManagement.openTabs.label")}</span>
|
||||
<div className="flex items-center gap-2">
|
||||
<input
|
||||
type="range"
|
||||
min="0"
|
||||
max="500"
|
||||
step="1"
|
||||
value={maxOpenTabsContext ?? 20}
|
||||
onChange={(e) => 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"
|
||||
/>
|
||||
<span style={{ ...sliderLabelStyle }}>{maxOpenTabsContext ?? 20}</span>
|
||||
</div>
|
||||
<span className="block font-medium mb-1">{t("settings:contextManagement.openTabs.label")}</span>
|
||||
<div className="flex items-center gap-2">
|
||||
<Slider
|
||||
min={0}
|
||||
max={500}
|
||||
step={1}
|
||||
value={[maxOpenTabsContext ?? 20]}
|
||||
onValueChange={([value]) => setCachedStateField("maxOpenTabsContext", value)}
|
||||
data-testid="open-tabs-limit-slider"
|
||||
/>
|
||||
<span className="w-10">{maxOpenTabsContext ?? 20}</span>
|
||||
</div>
|
||||
<p className="text-vscode-descriptionForeground text-sm mt-0">
|
||||
<div className="text-vscode-descriptionForeground text-sm mt-1">
|
||||
{t("settings:contextManagement.openTabs.description")}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<div className="flex flex-col gap-2">
|
||||
<span className="font-medium">{t("settings:contextManagement.workspaceFiles.label")}</span>
|
||||
<div className="flex items-center gap-2">
|
||||
<input
|
||||
type="range"
|
||||
min="0"
|
||||
max="500"
|
||||
step="1"
|
||||
value={maxWorkspaceFiles ?? 200}
|
||||
onChange={(e) => 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"
|
||||
/>
|
||||
<span style={{ ...sliderLabelStyle }}>{maxWorkspaceFiles ?? 200}</span>
|
||||
</div>
|
||||
<span className="block font-medium mb-1">
|
||||
{t("settings:contextManagement.workspaceFiles.label")}
|
||||
</span>
|
||||
<div className="flex items-center gap-2">
|
||||
<Slider
|
||||
min={0}
|
||||
max={500}
|
||||
step={1}
|
||||
value={[maxWorkspaceFiles ?? 200]}
|
||||
onValueChange={([value]) => setCachedStateField("maxWorkspaceFiles", value)}
|
||||
data-testid="workspace-files-limit-slider"
|
||||
/>
|
||||
<span className="w-10">{maxWorkspaceFiles ?? 200}</span>
|
||||
</div>
|
||||
<p className="text-vscode-descriptionForeground text-sm mt-0">
|
||||
<div className="text-vscode-descriptionForeground text-sm mt-1">
|
||||
{t("settings:contextManagement.workspaceFiles.description")}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<VSCodeCheckbox
|
||||
checked={showRooIgnoredFiles}
|
||||
onChange={(e: any) => {
|
||||
setCachedStateField("showRooIgnoredFiles", e.target.checked)
|
||||
}}
|
||||
onChange={(e: any) => setCachedStateField("showRooIgnoredFiles", e.target.checked)}
|
||||
data-testid="show-rooignored-files-checkbox">
|
||||
<span className="font-medium">{t("settings:contextManagement.rooignore.label")}</span>
|
||||
<label className="block font-medium mb-1">
|
||||
{t("settings:contextManagement.rooignore.label")}
|
||||
</label>
|
||||
</VSCodeCheckbox>
|
||||
<p className="text-vscode-descriptionForeground text-sm mt-0">
|
||||
<div className="text-vscode-descriptionForeground text-sm mt-1">
|
||||
{t("settings:contextManagement.rooignore.description")}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</Section>
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -47,7 +47,7 @@ export const LanguageSettings = ({ language, setCachedStateField, className, ...
|
|||
<Section>
|
||||
<Select value={language} onValueChange={(value) => setCachedStateField("language", value)}>
|
||||
<SelectTrigger className="w-full">
|
||||
<SelectValue placeholder="Select" />
|
||||
<SelectValue placeholder={t("settings:common.select")} />
|
||||
</SelectTrigger>
|
||||
<SelectContent>
|
||||
<SelectGroup>
|
||||
|
|
|
|||
|
|
@ -3,6 +3,7 @@ import { VSCodeLink } from "@vscode/webview-ui-toolkit/react"
|
|||
import { Trans } from "react-i18next"
|
||||
import { ChevronsUpDown, Check, X } from "lucide-react"
|
||||
|
||||
import { useAppTranslation } from "@/i18n/TranslationContext"
|
||||
import { cn } from "@/lib/utils"
|
||||
import {
|
||||
Command,
|
||||
|
|
@ -54,6 +55,8 @@ export const ModelPicker = ({
|
|||
setApiConfigurationField,
|
||||
defaultModelInfo,
|
||||
}: ModelPickerProps) => {
|
||||
const { t } = useAppTranslation()
|
||||
|
||||
const [open, setOpen] = useState(false)
|
||||
const [isDescriptionExpanded, setIsDescriptionExpanded] = useState(false)
|
||||
const isInitialized = useRef(false)
|
||||
|
|
@ -117,7 +120,7 @@ export const ModelPicker = ({
|
|||
role="combobox"
|
||||
aria-expanded={open}
|
||||
className="w-full justify-between">
|
||||
<div>{selectedModelId ?? "Select"}</div>
|
||||
<div>{selectedModelId ?? t("settings:common.select")}</div>
|
||||
<ChevronsUpDown className="opacity-50" />
|
||||
</Button>
|
||||
</PopoverTrigger>
|
||||
|
|
|
|||
|
|
@ -6,6 +6,7 @@ import { Bell } from "lucide-react"
|
|||
import { SetCachedStateField } from "./types"
|
||||
import { SectionHeader } from "./SectionHeader"
|
||||
import { Section } from "./Section"
|
||||
import { Slider } from "../ui"
|
||||
|
||||
type NotificationSettingsProps = HTMLAttributes<HTMLDivElement> & {
|
||||
ttsEnabled?: boolean
|
||||
|
|
@ -41,31 +42,32 @@ export const NotificationSettings = ({
|
|||
data-testid="tts-enabled-checkbox">
|
||||
<span className="font-medium">{t("settings:notifications.tts.label")}</span>
|
||||
</VSCodeCheckbox>
|
||||
<p className="text-vscode-descriptionForeground text-sm mt-0">
|
||||
<div className="text-vscode-descriptionForeground text-sm mt-1">
|
||||
{t("settings:notifications.tts.description")}
|
||||
</p>
|
||||
{ttsEnabled && (
|
||||
<div className="pl-[10px] ml-0 border-l-2 border-l-vscode-button-background">
|
||||
<div className="flex items-center gap-[5px]">
|
||||
<input
|
||||
type="range"
|
||||
min="0.1"
|
||||
max="2.0"
|
||||
step="0.01"
|
||||
value={ttsSpeed ?? 1.0}
|
||||
onChange={(e) => setCachedStateField("ttsSpeed", parseFloat(e.target.value))}
|
||||
className="h-2 focus:outline-0 w-4/5 accent-vscode-button-background"
|
||||
aria-label="Speed"
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{ttsEnabled && (
|
||||
<div className="flex flex-col gap-3 pl-3 border-l-2 border-vscode-button-background">
|
||||
<div>
|
||||
<label className="block font-medium mb-1">
|
||||
{t("settings:notifications.tts.speedLabel")}
|
||||
</label>
|
||||
<div className="flex items-center gap-2">
|
||||
<Slider
|
||||
min={0.1}
|
||||
max={2.0}
|
||||
step={0.01}
|
||||
value={[ttsSpeed ?? 1.0]}
|
||||
onValueChange={([value]) => setCachedStateField("ttsSpeed", value)}
|
||||
data-testid="tts-speed-slider"
|
||||
/>
|
||||
<span className="min-w-[35px] text-left">{((ttsSpeed ?? 1.0) * 100).toFixed(0)}%</span>
|
||||
<span className="w-10">{((ttsSpeed ?? 1.0) * 100).toFixed(0)}%</span>
|
||||
</div>
|
||||
<p className="text-vscode-descriptionForeground text-sm mt-1">
|
||||
{t("settings:notifications.tts.speedLabel")}
|
||||
</p>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
|
||||
<div>
|
||||
<VSCodeCheckbox
|
||||
checked={soundEnabled}
|
||||
|
|
@ -73,33 +75,31 @@ export const NotificationSettings = ({
|
|||
data-testid="sound-enabled-checkbox">
|
||||
<span className="font-medium">{t("settings:notifications.sound.label")}</span>
|
||||
</VSCodeCheckbox>
|
||||
<p className="text-vscode-descriptionForeground text-sm mt-0">
|
||||
<div className="text-vscode-descriptionForeground text-sm mt-1">
|
||||
{t("settings:notifications.sound.description")}
|
||||
</p>
|
||||
{soundEnabled && (
|
||||
<div className="pl-[10px] ml-0 border-l-2 border-l-vscode-button-background">
|
||||
<div className="flex items-center gap-[5px]">
|
||||
<input
|
||||
type="range"
|
||||
min="0"
|
||||
max="1"
|
||||
step="0.01"
|
||||
value={soundVolume ?? 0.5}
|
||||
onChange={(e) => setCachedStateField("soundVolume", parseFloat(e.target.value))}
|
||||
className="h-2 focus:outline-0 w-4/5 accent-vscode-button-background"
|
||||
aria-label="Volume"
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{soundEnabled && (
|
||||
<div className="flex flex-col gap-3 pl-3 border-l-2 border-vscode-button-background">
|
||||
<div>
|
||||
<label className="block font-medium mb-1">
|
||||
{t("settings:notifications.sound.volumeLabel")}
|
||||
</label>
|
||||
<div className="flex items-center gap-2">
|
||||
<Slider
|
||||
min={0}
|
||||
max={1}
|
||||
step={0.01}
|
||||
value={[soundVolume ?? 0.5]}
|
||||
onValueChange={([value]) => setCachedStateField("soundVolume", value)}
|
||||
data-testid="sound-volume-slider"
|
||||
/>
|
||||
<span className="min-w-[35px] text-left">
|
||||
{((soundVolume ?? 0.5) * 100).toFixed(0)}%
|
||||
</span>
|
||||
<span className="w-10">{((soundVolume ?? 0.5) * 100).toFixed(0)}%</span>
|
||||
</div>
|
||||
<p className="text-vscode-descriptionForeground text-sm mt-1">
|
||||
{t("settings:notifications.sound.volumeLabel")}
|
||||
</p>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
</Section>
|
||||
</div>
|
||||
)
|
||||
|
|
|
|||
|
|
@ -5,5 +5,5 @@ import { cn } from "@/lib/utils"
|
|||
type SectionProps = HTMLAttributes<HTMLDivElement>
|
||||
|
||||
export const Section = ({ className, ...props }: SectionProps) => (
|
||||
<div className={cn("flex flex-col gap-2 p-5", className)} {...props} />
|
||||
<div className={cn("flex flex-col gap-3 p-5", className)} {...props} />
|
||||
)
|
||||
|
|
|
|||
|
|
@ -466,7 +466,6 @@ const SettingsView = forwardRef<SettingsViewRef, SettingsViewProps>(({ onDone },
|
|||
<div ref={advancedRef}>
|
||||
<AdvancedSettings
|
||||
rateLimitSeconds={rateLimitSeconds}
|
||||
terminalShellIntegrationTimeout={terminalShellIntegrationTimeout}
|
||||
diffEnabled={diffEnabled}
|
||||
fuzzyMatchThreshold={fuzzyMatchThreshold}
|
||||
setCachedStateField={setCachedStateField}
|
||||
|
|
|
|||
|
|
@ -3,18 +3,22 @@ import { useEffect, useState } from "react"
|
|||
import { useAppTranslation } from "@/i18n/TranslationContext"
|
||||
import { useDebounce } from "react-use"
|
||||
|
||||
import { Slider } from "@/components/ui"
|
||||
|
||||
interface TemperatureControlProps {
|
||||
value: number | undefined | null
|
||||
onChange: (value: number | undefined | null) => 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.
|
||||
}
|
||||
}}>
|
||||
<span className="font-medium">{t("settings:temperature.useCustom")}</span>
|
||||
<label className="block font-medium mb-1">{t("settings:temperature.useCustom")}</label>
|
||||
</VSCodeCheckbox>
|
||||
<div className="text-sm text-vscode-descriptionForeground">{t("settings:temperature.description")}</div>
|
||||
<div className="text-sm text-vscode-descriptionForeground mt-1">
|
||||
{t("settings:temperature.description")}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{isCustomTemperature && (
|
||||
<div
|
||||
style={{
|
||||
marginLeft: 0,
|
||||
paddingLeft: 10,
|
||||
borderLeft: "2px solid var(--vscode-button-background)",
|
||||
}}>
|
||||
<div style={{ display: "flex", alignItems: "center", gap: "5px" }}>
|
||||
<input
|
||||
type="range"
|
||||
min="0"
|
||||
max={maxValue}
|
||||
step="0.01"
|
||||
value={inputValue ?? 0}
|
||||
className="h-2 focus:outline-0 w-4/5 accent-vscode-button-background"
|
||||
onChange={(e) => setInputValue(parseFloat(e.target.value))}
|
||||
/>
|
||||
<span>{inputValue}</span>
|
||||
<div className="flex flex-col gap-3 pl-3 border-l-2 border-vscode-button-background">
|
||||
<div>
|
||||
<div className="flex items-center gap-2">
|
||||
<Slider
|
||||
min={0}
|
||||
max={maxValue}
|
||||
step={0.01}
|
||||
value={[inputValue ?? 0]}
|
||||
onValueChange={([value]) => setInputValue(value)}
|
||||
/>
|
||||
<span className="w-10">{inputValue}</span>
|
||||
</div>
|
||||
<div className="text-vscode-descriptionForeground text-sm mt-1">
|
||||
{t("settings:temperature.rangeDescription")}
|
||||
</div>
|
||||
</div>
|
||||
<p className="text-vscode-descriptionForeground text-sm mt-1">
|
||||
{t("settings:temperature.rangeDescription")}
|
||||
</p>
|
||||
</div>
|
||||
)}
|
||||
</>
|
||||
|
|
|
|||
|
|
@ -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 = ({
|
|||
|
||||
<Section>
|
||||
<div>
|
||||
<div className="flex flex-col gap-2">
|
||||
<span className="font-medium">{t("settings:terminal.outputLineLimit.label")}</span>
|
||||
<div className="flex items-center gap-2">
|
||||
<input
|
||||
type="range"
|
||||
min="100"
|
||||
max="5000"
|
||||
step="100"
|
||||
value={terminalOutputLineLimit ?? 500}
|
||||
onChange={(e) =>
|
||||
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"
|
||||
/>
|
||||
<span style={{ ...sliderLabelStyle }}>{terminalOutputLineLimit ?? 500}</span>
|
||||
</div>
|
||||
<label className="block font-medium mb-1">{t("settings:terminal.outputLineLimit.label")}</label>
|
||||
<div className="flex items-center gap-2">
|
||||
<Slider
|
||||
min={100}
|
||||
max={5000}
|
||||
step={100}
|
||||
value={[terminalOutputLineLimit ?? 500]}
|
||||
onValueChange={([value]) => setCachedStateField("terminalOutputLineLimit", value)}
|
||||
data-testid="terminal-output-limit-slider"
|
||||
/>
|
||||
<span className="w-10">{terminalOutputLineLimit ?? 500}</span>
|
||||
</div>
|
||||
<p className="text-vscode-descriptionForeground text-sm mt-0">
|
||||
<div className="text-vscode-descriptionForeground text-sm mt-1">
|
||||
{t("settings:terminal.outputLineLimit.description")}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<div className="flex flex-col gap-2">
|
||||
<span className="font-medium">{t("settings:terminal.shellIntegrationTimeout.label")}</span>
|
||||
<div className="flex items-center gap-2">
|
||||
<input
|
||||
type="range"
|
||||
min="1000"
|
||||
max="60000"
|
||||
step="1000"
|
||||
value={terminalShellIntegrationTimeout}
|
||||
onChange={(e) =>
|
||||
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"
|
||||
/>
|
||||
<span style={{ ...sliderLabelStyle }}>
|
||||
{(terminalShellIntegrationTimeout ?? 5000) / 1000}s
|
||||
</span>
|
||||
</div>
|
||||
<p className="text-vscode-descriptionForeground text-sm mt-0">
|
||||
{t("settings:terminal.shellIntegrationTimeout.description")}
|
||||
</p>
|
||||
<label className="block font-medium mb-1">
|
||||
{t("settings:terminal.shellIntegrationTimeout.label")}
|
||||
</label>
|
||||
<div className="flex items-center gap-2">
|
||||
<Slider
|
||||
min={1000}
|
||||
max={60000}
|
||||
step={1000}
|
||||
value={[terminalShellIntegrationTimeout ?? 5000]}
|
||||
onValueChange={([value]) =>
|
||||
setCachedStateField(
|
||||
"terminalShellIntegrationTimeout",
|
||||
Math.min(60000, Math.max(1000, value)),
|
||||
)
|
||||
}
|
||||
/>
|
||||
<span className="w-10">{(terminalShellIntegrationTimeout ?? 5000) / 1000}s</span>
|
||||
</div>
|
||||
<div className="text-vscode-descriptionForeground text-sm mt-1">
|
||||
{t("settings:terminal.shellIntegrationTimeout.description")}
|
||||
</div>
|
||||
</div>
|
||||
</Section>
|
||||
|
|
|
|||
|
|
@ -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) => (
|
||||
<button onClick={onClick} title={title} disabled={disabled} data-testid={dataTestId}>
|
||||
{children}
|
||||
</button>
|
||||
),
|
||||
VSCodeTextField: ({ value, onInput, placeholder, onKeyDown, "data-testid": dataTestId }: any) => (
|
||||
<input
|
||||
value={value}
|
||||
|
|
@ -20,22 +19,8 @@ jest.mock("@vscode/webview-ui-toolkit/react", () => ({
|
|||
),
|
||||
}))
|
||||
|
||||
jest.mock("vscrui", () => ({
|
||||
Dropdown: ({ id, value, onChange, options, role }: any) => (
|
||||
<div data-testid={`mock-dropdown-${id}`}>
|
||||
<select value={value} onChange={(e) => onChange({ value: e.target.value })} data-testid={id} role={role}>
|
||||
{options.map((opt: any) => (
|
||||
<option key={opt.value} value={opt.value}>
|
||||
{opt.label}
|
||||
</option>
|
||||
))}
|
||||
</select>
|
||||
</div>
|
||||
),
|
||||
}))
|
||||
|
||||
// Mock Dialog component
|
||||
jest.mock("@/components/ui/dialog", () => ({
|
||||
jest.mock("@/components/ui", () => ({
|
||||
...jest.requireActual("@/components/ui"),
|
||||
Dialog: ({ children, open, onOpenChange }: any) => (
|
||||
<div role="dialog" aria-modal="true" style={{ display: open ? "block" : "none" }} data-testid="dialog">
|
||||
{children}
|
||||
|
|
@ -43,10 +28,6 @@ jest.mock("@/components/ui/dialog", () => ({
|
|||
),
|
||||
DialogContent: ({ children }: any) => <div data-testid="dialog-content">{children}</div>,
|
||||
DialogTitle: ({ children }: any) => <div data-testid="dialog-title">{children}</div>,
|
||||
}))
|
||||
|
||||
// Mock UI components
|
||||
jest.mock("@/components/ui", () => ({
|
||||
Button: ({ children, onClick, disabled, variant, "data-testid": dataTestId }: any) => (
|
||||
<button onClick={onClick} disabled={disabled} data-testid={dataTestId}>
|
||||
{children}
|
||||
|
|
@ -61,6 +42,25 @@ jest.mock("@/components/ui", () => ({
|
|||
data-testid={dataTestId}
|
||||
/>
|
||||
),
|
||||
Select: ({ children, value, onValueChange }: any) => (
|
||||
<select
|
||||
value={value}
|
||||
onChange={(e) => {
|
||||
if (onValueChange) onValueChange(e.target.value)
|
||||
}}
|
||||
data-testid="select-component">
|
||||
<option value="Default Config">Default Config</option>
|
||||
<option value="Another Config">Another Config</option>
|
||||
</select>
|
||||
),
|
||||
SelectTrigger: ({ children }: any) => <div className="select-trigger-mock">{children}</div>,
|
||||
SelectValue: ({ children }: any) => <div className="select-value-mock">{children}</div>,
|
||||
SelectContent: ({ children }: any) => <div className="select-content-mock">{children}</div>,
|
||||
SelectItem: ({ children, value }: any) => (
|
||||
<option value={value} className="select-item-mock">
|
||||
{children}
|
||||
</option>
|
||||
),
|
||||
}))
|
||||
|
||||
describe("ApiConfigManager", () => {
|
||||
|
|
@ -215,7 +215,7 @@ describe("ApiConfigManager", () => {
|
|||
it("allows selecting a different config", () => {
|
||||
render(<ApiConfigManager {...defaultProps} />)
|
||||
|
||||
const select = screen.getByRole("combobox")
|
||||
const select = screen.getByTestId("select-component")
|
||||
fireEvent.change(select, { target: { value: "Another Config" } })
|
||||
|
||||
expect(mockOnSelectConfig).toHaveBeenCalledWith("Another Config")
|
||||
|
|
|
|||
|
|
@ -1,9 +1,31 @@
|
|||
// npx jest src/components/settings/__tests__/ContextManagementSettings.test.ts
|
||||
|
||||
import { render, screen, fireEvent } from "@testing-library/react"
|
||||
|
||||
import { ContextManagementSettings } from "../ContextManagementSettings"
|
||||
|
||||
class MockResizeObserver {
|
||||
observe() {}
|
||||
unobserve() {}
|
||||
disconnect() {}
|
||||
}
|
||||
|
||||
global.ResizeObserver = MockResizeObserver
|
||||
|
||||
jest.mock("@/components/ui", () => ({
|
||||
...jest.requireActual("@/components/ui"),
|
||||
Slider: ({ value, onValueChange, "data-testid": dataTestId }: any) => (
|
||||
<input
|
||||
type="range"
|
||||
value={value[0]}
|
||||
onChange={(e) => onValueChange([parseFloat(e.target.value)])}
|
||||
data-testid={dataTestId}
|
||||
/>
|
||||
),
|
||||
}))
|
||||
|
||||
describe("ContextManagementSettings", () => {
|
||||
const defaultProps = {
|
||||
terminalOutputLineLimit: 500,
|
||||
maxOpenTabsContext: 20,
|
||||
maxWorkspaceFiles: 200,
|
||||
showRooIgnoredFiles: false,
|
||||
|
|
@ -20,12 +42,10 @@ describe("ContextManagementSettings", () => {
|
|||
// Open tabs context limit
|
||||
const openTabsSlider = screen.getByTestId("open-tabs-limit-slider")
|
||||
expect(openTabsSlider).toBeInTheDocument()
|
||||
expect(openTabsSlider).toHaveValue("20")
|
||||
|
||||
// Workspace files limit
|
||||
const workspaceFilesSlider = screen.getByTestId("workspace-files-limit-slider")
|
||||
expect(workspaceFilesSlider).toBeInTheDocument()
|
||||
expect(workspaceFilesSlider).toHaveValue("200")
|
||||
|
||||
// Show .rooignore'd files
|
||||
const showRooIgnoredFilesCheckbox = screen.getByTestId("show-rooignored-files-checkbox")
|
||||
|
|
|
|||
|
|
@ -78,28 +78,22 @@ jest.mock("@vscode/webview-ui-toolkit/react", () => ({
|
|||
data-testid={dataTestId}
|
||||
/>
|
||||
),
|
||||
VSCodeTextArea: () => <textarea />,
|
||||
VSCodeLink: ({ children, href }: any) => <a href={href || "#"}>{children}</a>,
|
||||
VSCodeDropdown: ({ children, value, onChange }: any) => (
|
||||
<select value={value} onChange={onChange}>
|
||||
{children}
|
||||
</select>
|
||||
),
|
||||
VSCodeOption: ({ children, value }: any) => <option value={value}>{children}</option>,
|
||||
VSCodeRadio: ({ children, value, checked, onChange }: any) => (
|
||||
<input type="radio" value={value} checked={checked} onChange={onChange} />
|
||||
),
|
||||
VSCodeRadioGroup: ({ children, value, onChange }: any) => <div onChange={onChange}>{children}</div>,
|
||||
VSCodeSlider: ({ value, onChange, "data-testid": dataTestId }: any) => (
|
||||
}))
|
||||
|
||||
// Mock Slider component
|
||||
jest.mock("@/components/ui", () => ({
|
||||
...jest.requireActual("@/components/ui"),
|
||||
Slider: ({ value, onValueChange, "data-testid": dataTestId }: any) => (
|
||||
<input
|
||||
type="range"
|
||||
value={value}
|
||||
onChange={(e) => onChange({ target: { value: Number(e.target.value) } })}
|
||||
min={0}
|
||||
max={1}
|
||||
value={value[0]}
|
||||
onChange={(e) => onValueChange([parseFloat(e.target.value)])}
|
||||
data-testid={dataTestId}
|
||||
step={0.01}
|
||||
style={{ flexGrow: 1, height: "2px" }}
|
||||
/>
|
||||
),
|
||||
}))
|
||||
|
|
@ -127,6 +121,14 @@ const mockPostMessage = (state: any) => {
|
|||
)
|
||||
}
|
||||
|
||||
class MockResizeObserver {
|
||||
observe() {}
|
||||
unobserve() {}
|
||||
disconnect() {}
|
||||
}
|
||||
|
||||
global.ResizeObserver = MockResizeObserver
|
||||
|
||||
const renderSettingsView = () => {
|
||||
const onDone = jest.fn()
|
||||
const queryClient = new QueryClient()
|
||||
|
|
|
|||
|
|
@ -1,6 +1,29 @@
|
|||
// npx jest src/components/settings/__tests__/TemperatureControl.test.ts
|
||||
|
||||
import { render, screen, fireEvent } from "@testing-library/react"
|
||||
|
||||
import { TemperatureControl } from "../TemperatureControl"
|
||||
|
||||
class MockResizeObserver {
|
||||
observe() {}
|
||||
unobserve() {}
|
||||
disconnect() {}
|
||||
}
|
||||
|
||||
global.ResizeObserver = MockResizeObserver
|
||||
|
||||
jest.mock("@/components/ui", () => ({
|
||||
...jest.requireActual("@/components/ui"),
|
||||
Slider: ({ value, onValueChange, "data-testid": dataTestId }: any) => (
|
||||
<input
|
||||
type="range"
|
||||
value={value[0]}
|
||||
onChange={(e) => onValueChange([parseFloat(e.target.value)])}
|
||||
data-testid={dataTestId}
|
||||
/>
|
||||
),
|
||||
}))
|
||||
|
||||
describe("TemperatureControl", () => {
|
||||
it("renders with default temperature disabled", () => {
|
||||
const onChange = jest.fn()
|
||||
|
|
@ -29,67 +52,34 @@ describe("TemperatureControl", () => {
|
|||
|
||||
const checkbox = screen.getByRole("checkbox")
|
||||
|
||||
// Uncheck - should clear temperature
|
||||
// Uncheck - should clear temperature.
|
||||
fireEvent.click(checkbox)
|
||||
// Waiting for debounce
|
||||
|
||||
// Waiting for debounce.
|
||||
await new Promise((x) => setTimeout(x, 100))
|
||||
expect(onChange).toHaveBeenCalledWith(null)
|
||||
|
||||
// Check - should restore previous temperature
|
||||
// Check - should restore previous temperature.
|
||||
fireEvent.click(checkbox)
|
||||
// Waiting for debounce
|
||||
|
||||
// Waiting for debounce.
|
||||
await new Promise((x) => setTimeout(x, 100))
|
||||
expect(onChange).toHaveBeenCalledWith(0.7)
|
||||
})
|
||||
|
||||
it("updates temperature when input loses focus", async () => {
|
||||
const onChange = jest.fn()
|
||||
render(<TemperatureControl value={0.7} onChange={onChange} />)
|
||||
|
||||
const input = screen.getByRole("slider")
|
||||
fireEvent.change(input, { target: { value: "0.8" } })
|
||||
fireEvent.blur(input)
|
||||
|
||||
// Waiting for debounce
|
||||
await new Promise((x) => setTimeout(x, 100))
|
||||
expect(onChange).toHaveBeenCalledWith(0.8)
|
||||
})
|
||||
|
||||
it("respects maxValue prop", async () => {
|
||||
const onChange = jest.fn()
|
||||
render(<TemperatureControl value={1.5} onChange={onChange} maxValue={2} />)
|
||||
|
||||
const input = screen.getByRole("slider")
|
||||
|
||||
// Valid value within max
|
||||
fireEvent.change(input, { target: { value: "1.8" } })
|
||||
fireEvent.blur(input)
|
||||
// Waiting for debounce
|
||||
await new Promise((x) => setTimeout(x, 100))
|
||||
expect(onChange).toHaveBeenCalledWith(1.8)
|
||||
|
||||
// Invalid value above max
|
||||
fireEvent.change(input, { target: { value: "2.5" } })
|
||||
fireEvent.blur(input)
|
||||
expect(input).toHaveValue("2") // Clamped between 0 and 2
|
||||
// Waiting for debounce
|
||||
await new Promise((x) => setTimeout(x, 100))
|
||||
expect(onChange).toHaveBeenCalledWith(2)
|
||||
})
|
||||
|
||||
it("syncs checkbox state when value prop changes", () => {
|
||||
const onChange = jest.fn()
|
||||
const { rerender } = render(<TemperatureControl value={0.7} onChange={onChange} />)
|
||||
|
||||
// Initially checked
|
||||
// Initially checked.
|
||||
const checkbox = screen.getByRole("checkbox")
|
||||
expect(checkbox).toBeChecked()
|
||||
|
||||
// Update to undefined
|
||||
// Update to undefined.
|
||||
rerender(<TemperatureControl value={undefined} onChange={onChange} />)
|
||||
expect(checkbox).not.toBeChecked()
|
||||
|
||||
// Update back to a value
|
||||
// Update back to a value.
|
||||
rerender(<TemperatureControl value={0.5} onChange={onChange} />)
|
||||
expect(checkbox).toBeChecked()
|
||||
})
|
||||
|
|
|
|||
|
|
@ -2,7 +2,6 @@ import { render, screen, fireEvent } from "@testing-library/react"
|
|||
import { ThinkingBudget } from "../ThinkingBudget"
|
||||
import { ModelInfo } from "../../../../../src/shared/api"
|
||||
|
||||
// Mock Slider component
|
||||
jest.mock("@/components/ui", () => ({
|
||||
Slider: ({ value, onValueChange, min, max }: any) => (
|
||||
<input
|
||||
|
|
|
|||
|
|
@ -76,10 +76,3 @@ export const StyledMarkdown = styled.div`
|
|||
}
|
||||
}
|
||||
`
|
||||
|
||||
export const sliderLabelStyle = {
|
||||
minWidth: "45px",
|
||||
textAlign: "right" as const,
|
||||
lineHeight: "20px",
|
||||
paddingBottom: "2px",
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,4 +1,6 @@
|
|||
import React, { ReactNode } from "react"
|
||||
// npx jest webview-ui/src/components/ui/__tests__/select-dropdown.test.tsx
|
||||
|
||||
import { ReactNode } from "react"
|
||||
import { render, screen, fireEvent } from "@testing-library/react"
|
||||
import { SelectDropdown, DropdownOptionType } from "../select-dropdown"
|
||||
|
||||
|
|
@ -47,6 +49,10 @@ jest.mock("../dropdown-menu", () => {
|
|||
),
|
||||
|
||||
DropdownMenuSeparator: () => <div data-testid="dropdown-separator" />,
|
||||
|
||||
DropdownMenuShortcut: ({ children }: { children: ReactNode }) => (
|
||||
<span data-testid="dropdown-shortcut">{children}</span>
|
||||
),
|
||||
}
|
||||
})
|
||||
|
||||
|
|
|
|||
|
|
@ -9,7 +9,9 @@ import {
|
|||
DropdownMenuItem,
|
||||
DropdownMenuTrigger,
|
||||
DropdownMenuSeparator,
|
||||
DropdownMenuShortcut,
|
||||
} from "./dropdown-menu"
|
||||
import { Check } from "lucide-react"
|
||||
|
||||
export enum DropdownOptionType {
|
||||
ITEM = "item",
|
||||
|
|
@ -30,7 +32,6 @@ export interface SelectDropdownProps {
|
|||
onChange: (value: string) => void
|
||||
disabled?: boolean
|
||||
title?: string
|
||||
className?: string
|
||||
triggerClassName?: string
|
||||
contentClassName?: string
|
||||
sideOffset?: number
|
||||
|
|
@ -48,7 +49,6 @@ export const SelectDropdown = React.forwardRef<React.ElementRef<typeof DropdownM
|
|||
onChange,
|
||||
disabled = false,
|
||||
title = "",
|
||||
className = "",
|
||||
triggerClassName = "",
|
||||
contentClassName = "",
|
||||
sideOffset = 4,
|
||||
|
|
@ -141,12 +141,14 @@ export const SelectDropdown = React.forwardRef<React.ElementRef<typeof DropdownM
|
|||
<DropdownMenuItem
|
||||
key={`item-${option.value}`}
|
||||
disabled={option.disabled}
|
||||
className={cn(
|
||||
"cursor-pointer text-xs focus:bg-vscode-list-hoverBackground focus:text-vscode-list-hoverForeground",
|
||||
option.value === value && "bg-vscode-list-focusBackground",
|
||||
)}
|
||||
className="text-xs focus:bg-vscode-list-activeSelectionBackground cursor-pointer"
|
||||
onClick={() => handleSelect(option)}>
|
||||
{option.label}
|
||||
{option.value === value && (
|
||||
<DropdownMenuShortcut>
|
||||
<Check className="w-3 h-3" />
|
||||
</DropdownMenuShortcut>
|
||||
)}
|
||||
</DropdownMenuItem>
|
||||
)
|
||||
})}
|
||||
|
|
|
|||
|
|
@ -11,7 +11,7 @@ const Slider = React.forwardRef<
|
|||
ref={ref}
|
||||
className={cn("relative flex w-full touch-none select-none items-center", className)}
|
||||
{...props}>
|
||||
<SliderPrimitive.Track className="relative w-full h-[8px] grow overflow-hidden bg-vscode-button-secondaryBackground border border-[#767676] dark:border-[#858585] rounded-sm">
|
||||
<SliderPrimitive.Track className="relative w-full h-[8px] grow overflow-hidden bg-accent border border-[#767676] dark:border-[#858585] rounded-sm">
|
||||
<SliderPrimitive.Range className="absolute h-full bg-vscode-button-background" />
|
||||
</SliderPrimitive.Track>
|
||||
<SliderPrimitive.Thumb className="block h-3 w-3 rounded-full border border-primary/50 bg-primary shadow transition-colors cursor-pointer focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-ring disabled:pointer-events-none disabled:opacity-50" />
|
||||
|
|
|
|||
|
|
@ -3,7 +3,8 @@
|
|||
"save": "Desar",
|
||||
"done": "Fet",
|
||||
"cancel": "Cancel·lar",
|
||||
"reset": "Restablir"
|
||||
"reset": "Restablir",
|
||||
"select": "Seleccionar"
|
||||
},
|
||||
"header": {
|
||||
"title": "Configuració",
|
||||
|
|
@ -74,7 +75,7 @@
|
|||
},
|
||||
"providers": {
|
||||
"configProfile": "Perfil de configuració",
|
||||
"description": "Descripció",
|
||||
"description": "Deseu diferents configuracions d'API per canviar ràpidament entre proveïdors i configuracions.",
|
||||
"apiProvider": "Proveïdor d'API",
|
||||
"openRouterApiKey": "Clau API d'OpenRouter",
|
||||
"nameEmpty": "El nom no pot estar buit",
|
||||
|
|
|
|||
|
|
@ -3,7 +3,8 @@
|
|||
"save": "Speichern",
|
||||
"done": "Fertig",
|
||||
"cancel": "Abbrechen",
|
||||
"reset": "Zurücksetzen"
|
||||
"reset": "Zurücksetzen",
|
||||
"select": "Auswählen"
|
||||
},
|
||||
"header": {
|
||||
"title": "Einstellungen",
|
||||
|
|
@ -74,7 +75,7 @@
|
|||
},
|
||||
"providers": {
|
||||
"configProfile": "Konfigurationsprofil",
|
||||
"description": "Beschreibung",
|
||||
"description": "Speichern Sie verschiedene API-Konfigurationen, um schnell zwischen Anbietern und Einstellungen zu wechseln.",
|
||||
"apiProvider": "API-Anbieter",
|
||||
"model": "Modell",
|
||||
"nameEmpty": "Name darf nicht leer sein",
|
||||
|
|
|
|||
|
|
@ -3,7 +3,8 @@
|
|||
"save": "Save",
|
||||
"done": "Done",
|
||||
"cancel": "Cancel",
|
||||
"reset": "Reset"
|
||||
"reset": "Reset",
|
||||
"select": "Select"
|
||||
},
|
||||
"header": {
|
||||
"title": "Settings",
|
||||
|
|
@ -74,7 +75,7 @@
|
|||
},
|
||||
"providers": {
|
||||
"configProfile": "Configuration Profile",
|
||||
"description": "Description",
|
||||
"description": "Save different API configurations to quickly switch between providers and settings.",
|
||||
"apiProvider": "API Provider",
|
||||
"model": "Model",
|
||||
"nameEmpty": "Name cannot be empty",
|
||||
|
|
|
|||
|
|
@ -3,7 +3,8 @@
|
|||
"save": "Guardar",
|
||||
"done": "Hecho",
|
||||
"cancel": "Cancelar",
|
||||
"reset": "Restablecer"
|
||||
"reset": "Restablecer",
|
||||
"select": "Seleccionar"
|
||||
},
|
||||
"header": {
|
||||
"title": "Configuración",
|
||||
|
|
@ -74,7 +75,7 @@
|
|||
},
|
||||
"providers": {
|
||||
"configProfile": "Perfil de configuración",
|
||||
"description": "Descripción",
|
||||
"description": "Guarde diferentes configuraciones de API para cambiar rápidamente entre proveedores y ajustes.",
|
||||
"apiProvider": "Proveedor de API",
|
||||
"model": "Modelo",
|
||||
"nameEmpty": "El nombre no puede estar vacío",
|
||||
|
|
|
|||
|
|
@ -3,7 +3,8 @@
|
|||
"save": "Enregistrer",
|
||||
"done": "Terminé",
|
||||
"cancel": "Annuler",
|
||||
"reset": "Réinitialiser"
|
||||
"reset": "Réinitialiser",
|
||||
"select": "Sélectionner"
|
||||
},
|
||||
"header": {
|
||||
"title": "Paramètres",
|
||||
|
|
@ -74,7 +75,7 @@
|
|||
},
|
||||
"providers": {
|
||||
"configProfile": "Profil de configuration",
|
||||
"description": "Description",
|
||||
"description": "Enregistrez différentes configurations d'API pour basculer rapidement entre les fournisseurs et les paramètres.",
|
||||
"apiProvider": "Fournisseur d'API",
|
||||
"model": "Modèle",
|
||||
"nameEmpty": "Le nom ne peut pas être vide",
|
||||
|
|
|
|||
|
|
@ -3,7 +3,8 @@
|
|||
"save": "सहेजें",
|
||||
"done": "पूर्ण",
|
||||
"cancel": "रद्द करें",
|
||||
"reset": "रीसेट करें"
|
||||
"reset": "रीसेट करें",
|
||||
"select": "चुनें"
|
||||
},
|
||||
"header": {
|
||||
"title": "सेटिंग्स",
|
||||
|
|
@ -74,7 +75,7 @@
|
|||
},
|
||||
"providers": {
|
||||
"configProfile": "कॉन्फिगरेशन प्रोफाइल",
|
||||
"description": "विवरण",
|
||||
"description": "विभिन्न API कॉन्फ़िगरेशन सहेजें ताकि प्रदाताओं और सेटिंग्स के बीच त्वरित रूप से स्विच कर सकें।",
|
||||
"apiProvider": "API प्रदाता",
|
||||
"model": "मॉडल",
|
||||
"nameEmpty": "नाम खाली नहीं हो सकता",
|
||||
|
|
|
|||
|
|
@ -3,7 +3,8 @@
|
|||
"save": "Salva",
|
||||
"done": "Fatto",
|
||||
"cancel": "Annulla",
|
||||
"reset": "Ripristina"
|
||||
"reset": "Ripristina",
|
||||
"select": "Seleziona"
|
||||
},
|
||||
"header": {
|
||||
"title": "Impostazioni",
|
||||
|
|
@ -74,7 +75,7 @@
|
|||
},
|
||||
"providers": {
|
||||
"configProfile": "Profilo di configurazione",
|
||||
"description": "Descrizione",
|
||||
"description": "Salva diverse configurazioni API per passare rapidamente tra fornitori e impostazioni.",
|
||||
"apiProvider": "Fornitore API",
|
||||
"model": "Modello",
|
||||
"nameEmpty": "Il nome non può essere vuoto",
|
||||
|
|
|
|||
|
|
@ -3,7 +3,8 @@
|
|||
"save": "保存",
|
||||
"done": "完了",
|
||||
"cancel": "キャンセル",
|
||||
"reset": "リセット"
|
||||
"reset": "リセット",
|
||||
"select": "選択"
|
||||
},
|
||||
"header": {
|
||||
"title": "設定",
|
||||
|
|
@ -74,7 +75,7 @@
|
|||
},
|
||||
"providers": {
|
||||
"configProfile": "設定プロファイル",
|
||||
"description": "説明",
|
||||
"description": "異なるAPI設定を保存して、プロバイダーと設定をすばやく切り替えることができます。",
|
||||
"apiProvider": "APIプロバイダー",
|
||||
"model": "モデル",
|
||||
"nameEmpty": "名前を空にすることはできません",
|
||||
|
|
|
|||
|
|
@ -3,7 +3,8 @@
|
|||
"save": "저장",
|
||||
"done": "완료",
|
||||
"cancel": "취소",
|
||||
"reset": "초기화"
|
||||
"reset": "초기화",
|
||||
"select": "선택"
|
||||
},
|
||||
"header": {
|
||||
"title": "설정",
|
||||
|
|
@ -74,7 +75,7 @@
|
|||
},
|
||||
"providers": {
|
||||
"configProfile": "구성 프로필",
|
||||
"description": "설명",
|
||||
"description": "다양한 API 구성을 저장하여 제공자와 설정 간에 빠르게 전환할 수 있습니다.",
|
||||
"apiProvider": "API 제공자",
|
||||
"model": "모델",
|
||||
"nameEmpty": "이름은 비워둘 수 없습니다",
|
||||
|
|
|
|||
|
|
@ -3,7 +3,8 @@
|
|||
"save": "Zapisz",
|
||||
"done": "Gotowe",
|
||||
"cancel": "Anuluj",
|
||||
"reset": "Resetuj"
|
||||
"reset": "Resetuj",
|
||||
"select": "Wybierz"
|
||||
},
|
||||
"header": {
|
||||
"title": "Ustawienia",
|
||||
|
|
@ -74,7 +75,7 @@
|
|||
},
|
||||
"providers": {
|
||||
"configProfile": "Profil konfiguracji",
|
||||
"description": "Opis",
|
||||
"description": "Zapisz różne konfiguracje API, aby szybko przełączać się między dostawcami i ustawieniami.",
|
||||
"apiProvider": "Dostawca API",
|
||||
"model": "Model",
|
||||
"nameEmpty": "Nazwa nie może być pusta",
|
||||
|
|
|
|||
|
|
@ -3,7 +3,8 @@
|
|||
"save": "Salvar",
|
||||
"done": "Concluído",
|
||||
"cancel": "Cancelar",
|
||||
"reset": "Redefinir"
|
||||
"reset": "Redefinir",
|
||||
"select": "Selecionar"
|
||||
},
|
||||
"header": {
|
||||
"title": "Configurações",
|
||||
|
|
@ -74,7 +75,7 @@
|
|||
},
|
||||
"providers": {
|
||||
"configProfile": "Perfil de configuração",
|
||||
"description": "Descrição",
|
||||
"description": "Salve diferentes configurações de API para alternar rapidamente entre provedores e configurações.",
|
||||
"apiProvider": "Provedor de API",
|
||||
"model": "Modelo",
|
||||
"nameEmpty": "O nome não pode estar vazio",
|
||||
|
|
|
|||
|
|
@ -3,7 +3,8 @@
|
|||
"save": "Kaydet",
|
||||
"done": "Tamamlandı",
|
||||
"cancel": "İptal",
|
||||
"reset": "Sıfırla"
|
||||
"reset": "Sıfırla",
|
||||
"select": "Seç"
|
||||
},
|
||||
"header": {
|
||||
"title": "Ayarlar",
|
||||
|
|
@ -74,7 +75,7 @@
|
|||
},
|
||||
"providers": {
|
||||
"configProfile": "Yapılandırma Profili",
|
||||
"description": "Açıklama",
|
||||
"description": "Sağlayıcılar ve ayarlar arasında hızlıca geçiş yapmak için farklı API yapılandırmalarını kaydedin.",
|
||||
"apiProvider": "API Sağlayıcı",
|
||||
"model": "Model",
|
||||
"nameEmpty": "İsim boş olamaz",
|
||||
|
|
|
|||
|
|
@ -3,7 +3,8 @@
|
|||
"save": "Lưu",
|
||||
"done": "Hoàn thành",
|
||||
"cancel": "Hủy",
|
||||
"reset": "Đặt lại"
|
||||
"reset": "Đặt lại",
|
||||
"select": "Chọn"
|
||||
},
|
||||
"header": {
|
||||
"title": "Cài đặt",
|
||||
|
|
@ -74,7 +75,7 @@
|
|||
},
|
||||
"providers": {
|
||||
"configProfile": "Hồ sơ cấu hình",
|
||||
"description": "Mô tả",
|
||||
"description": "Lưu các cấu hình API khác nhau để nhanh chóng chuyển đổi giữa các nhà cung cấp và cài đặt.",
|
||||
"apiProvider": "Nhà cung cấp API",
|
||||
"model": "Mẫu",
|
||||
"nameEmpty": "Tên không được để trống",
|
||||
|
|
|
|||
|
|
@ -3,7 +3,8 @@
|
|||
"save": "保存",
|
||||
"done": "完成",
|
||||
"cancel": "取消",
|
||||
"reset": "重置"
|
||||
"reset": "重置",
|
||||
"select": "选择"
|
||||
},
|
||||
"header": {
|
||||
"title": "设置",
|
||||
|
|
@ -74,7 +75,7 @@
|
|||
},
|
||||
"providers": {
|
||||
"configProfile": "配置文件",
|
||||
"description": "描述",
|
||||
"description": "保存不同的 API 配置以快速切换提供商和设置。",
|
||||
"apiProvider": "API 提供商",
|
||||
"model": "模型",
|
||||
"nameEmpty": "名称不能为空",
|
||||
|
|
|
|||
|
|
@ -3,7 +3,8 @@
|
|||
"save": "儲存",
|
||||
"done": "完成",
|
||||
"cancel": "取消",
|
||||
"reset": "重設"
|
||||
"reset": "重設",
|
||||
"select": "選擇"
|
||||
},
|
||||
"header": {
|
||||
"title": "設定",
|
||||
|
|
@ -74,7 +75,7 @@
|
|||
},
|
||||
"providers": {
|
||||
"configProfile": "設定檔案",
|
||||
"description": "描述",
|
||||
"description": "儲存不同的 API 設定以快速切換提供者和設定。",
|
||||
"apiProvider": "API 提供者",
|
||||
"model": "模型",
|
||||
"nameEmpty": "名稱不能為空",
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue