diff --git a/webview-ui/src/components/settings/ApiOptions.tsx b/webview-ui/src/components/settings/ApiOptions.tsx index dcdf072a11c..b51b1713543 100644 --- a/webview-ui/src/components/settings/ApiOptions.tsx +++ b/webview-ui/src/components/settings/ApiOptions.tsx @@ -105,7 +105,11 @@ import { buildDocLink } from "@src/utils/docLinks" export interface ApiOptionsProps { uriScheme: string | undefined apiConfiguration: ProviderSettings - setApiConfigurationField: (field: K, value: ProviderSettings[K]) => void + setApiConfigurationField: ( + field: K, + value: ProviderSettings[K], + isUserAction?: boolean, + ) => void fromWelcomeView?: boolean errorMessage: string | undefined setErrorMessage: React.Dispatch> @@ -280,7 +284,7 @@ const ApiOptions = ({ const shouldSetDefault = !modelId if (shouldSetDefault) { - setApiConfigurationField(field, defaultValue) + setApiConfigurationField(field, defaultValue, false) } } diff --git a/webview-ui/src/components/settings/ModelPicker.tsx b/webview-ui/src/components/settings/ModelPicker.tsx index 2160d4b3c55..0753f9fc2bd 100644 --- a/webview-ui/src/components/settings/ModelPicker.tsx +++ b/webview-ui/src/components/settings/ModelPicker.tsx @@ -46,7 +46,11 @@ interface ModelPickerProps { serviceName: string serviceUrl: string apiConfiguration: ProviderSettings - setApiConfigurationField: (field: K, value: ProviderSettings[K]) => void + setApiConfigurationField: ( + field: K, + value: ProviderSettings[K], + isUserAction?: boolean, + ) => void organizationAllowList: OrganizationAllowList errorMessage?: string } @@ -124,7 +128,7 @@ export const ModelPicker = ({ useEffect(() => { if (!selectedModelId && !isInitialized.current) { const initialValue = modelIds.includes(selectedModelId) ? selectedModelId : defaultModelId - setApiConfigurationField(modelIdKey, initialValue) + setApiConfigurationField(modelIdKey, initialValue, false) // false = automatic initialization } isInitialized.current = true diff --git a/webview-ui/src/components/settings/SettingsView.tsx b/webview-ui/src/components/settings/SettingsView.tsx index 987d2451059..9866c1e235a 100644 --- a/webview-ui/src/components/settings/SettingsView.tsx +++ b/webview-ui/src/components/settings/SettingsView.tsx @@ -219,7 +219,7 @@ const SettingsView = forwardRef(({ onDone, t }, []) const setApiConfigurationField = useCallback( - (field: K, value: ProviderSettings[K]) => { + (field: K, value: ProviderSettings[K], isUserAction: boolean = true) => { setCachedState((prevState) => { if (prevState.apiConfiguration?.[field] === value) { return prevState @@ -227,9 +227,9 @@ const SettingsView = forwardRef(({ onDone, t const previousValue = prevState.apiConfiguration?.[field] - // Don't treat initial sync from undefined to a defined value as a user change - // This prevents the dirty state when the component initializes and auto-syncs the model ID - const isInitialSync = previousValue === undefined && value !== undefined + // Only skip change detection for automatic initialization (not user actions) + // This prevents the dirty state when the component initializes and auto-syncs values + const isInitialSync = !isUserAction && previousValue === undefined && value !== undefined if (!isInitialSync) { setChangeDetected(true) diff --git a/webview-ui/src/components/settings/ThinkingBudget.tsx b/webview-ui/src/components/settings/ThinkingBudget.tsx index c2aa2349830..7a85e61e7a5 100644 --- a/webview-ui/src/components/settings/ThinkingBudget.tsx +++ b/webview-ui/src/components/settings/ThinkingBudget.tsx @@ -20,7 +20,11 @@ import { useSelectedModel } from "@src/components/ui/hooks/useSelectedModel" interface ThinkingBudgetProps { apiConfiguration: ProviderSettings - setApiConfigurationField: (field: K, value: ProviderSettings[K]) => void + setApiConfigurationField: ( + field: K, + value: ProviderSettings[K], + isUserAction?: boolean, + ) => void modelInfo?: ModelInfo } @@ -71,7 +75,7 @@ export const ThinkingBudget = ({ apiConfiguration, setApiConfigurationField, mod // Set default reasoning effort when model supports it and no value is set useEffect(() => { if (isReasoningEffortSupported && !apiConfiguration.reasoningEffort && defaultReasoningEffort) { - setApiConfigurationField("reasoningEffort", defaultReasoningEffort) + setApiConfigurationField("reasoningEffort", defaultReasoningEffort, false) } }, [isReasoningEffortSupported, apiConfiguration.reasoningEffort, defaultReasoningEffort, setApiConfigurationField]) @@ -91,7 +95,7 @@ export const ThinkingBudget = ({ apiConfiguration, setApiConfigurationField, mod // appropriately. useEffect(() => { if (isReasoningBudgetSupported && customMaxThinkingTokens > modelMaxThinkingTokens) { - setApiConfigurationField("modelMaxThinkingTokens", modelMaxThinkingTokens) + setApiConfigurationField("modelMaxThinkingTokens", modelMaxThinkingTokens, false) } }, [isReasoningBudgetSupported, customMaxThinkingTokens, modelMaxThinkingTokens, setApiConfigurationField]) diff --git a/webview-ui/src/components/settings/__tests__/ThinkingBudget.spec.tsx b/webview-ui/src/components/settings/__tests__/ThinkingBudget.spec.tsx index fa7493edc66..9bc235e5f00 100644 --- a/webview-ui/src/components/settings/__tests__/ThinkingBudget.spec.tsx +++ b/webview-ui/src/components/settings/__tests__/ThinkingBudget.spec.tsx @@ -112,7 +112,7 @@ describe("ThinkingBudget", () => { ) // Effect should trigger and cap the value - expect(setApiConfigurationField).toHaveBeenCalledWith("modelMaxThinkingTokens", 8000) // 80% of 10000 + expect(setApiConfigurationField).toHaveBeenCalledWith("modelMaxThinkingTokens", 8000, false) // 80% of 10000 }) it("should use default thinking tokens if not provided", () => { diff --git a/webview-ui/src/components/settings/providers/HuggingFace.tsx b/webview-ui/src/components/settings/providers/HuggingFace.tsx index 8716739d80b..94f60cc4c82 100644 --- a/webview-ui/src/components/settings/providers/HuggingFace.tsx +++ b/webview-ui/src/components/settings/providers/HuggingFace.tsx @@ -34,7 +34,11 @@ type HuggingFaceModel = { type HuggingFaceProps = { apiConfiguration: ProviderSettings - setApiConfigurationField: (field: keyof ProviderSettings, value: ProviderSettings[keyof ProviderSettings]) => void + setApiConfigurationField: ( + field: keyof ProviderSettings, + value: ProviderSettings[keyof ProviderSettings], + isUserAction?: boolean, + ) => void } export const HuggingFace = ({ apiConfiguration, setApiConfigurationField }: HuggingFaceProps) => { @@ -93,7 +97,7 @@ export const HuggingFace = ({ apiConfiguration, setApiConfigurationField }: Hugg // Set to "auto" as default const defaultProvider = "auto" setSelectedProvider(defaultProvider) - setApiConfigurationField("huggingFaceInferenceProvider", defaultProvider) + setApiConfigurationField("huggingFaceInferenceProvider", defaultProvider, false) // false = automatic default } } } diff --git a/webview-ui/src/components/settings/providers/Unbound.tsx b/webview-ui/src/components/settings/providers/Unbound.tsx index a242ee4b3f1..2dd105a6637 100644 --- a/webview-ui/src/components/settings/providers/Unbound.tsx +++ b/webview-ui/src/components/settings/providers/Unbound.tsx @@ -17,7 +17,11 @@ import { ModelPicker } from "../ModelPicker" type UnboundProps = { apiConfiguration: ProviderSettings - setApiConfigurationField: (field: keyof ProviderSettings, value: ProviderSettings[keyof ProviderSettings]) => void + setApiConfigurationField: ( + field: keyof ProviderSettings, + value: ProviderSettings[keyof ProviderSettings], + isUserAction?: boolean, + ) => void routerModels?: RouterModels organizationAllowList: OrganizationAllowList modelValidationError?: string @@ -110,7 +114,7 @@ export const Unbound = ({ if (!currentModelId || !modelExists) { const firstAvailableModelId = Object.keys(updatedModels)[0] - setApiConfigurationField("unboundModelId", firstAvailableModelId) + setApiConfigurationField("unboundModelId", firstAvailableModelId, false) // false = automatic model selection } }