Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion webview-ui/src/components/settings/ApiOptions.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -171,7 +171,8 @@ const ApiOptions = ({

// Only update if the processed object is different from the current config.
if (JSON.stringify(currentConfigHeaders) !== JSON.stringify(newHeadersObject)) {
setApiConfigurationField("openAiHeaders", newHeadersObject)
// Pass false to indicate this is automatic sync, not a user action
setApiConfigurationField("openAiHeaders", newHeadersObject, false)
}
},
300,
Expand Down
21 changes: 19 additions & 2 deletions webview-ui/src/components/settings/SettingsView.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -256,17 +256,34 @@ const SettingsView = forwardRef<SettingsViewRef, SettingsViewProps>(({ onDone, t

const previousValue = prevState.apiConfiguration?.[field]

// Helper to check if two values are semantically equal
const areValuesEqual = (a: any, b: any): boolean => {
// Same reference
if (a === b) return true
// Both null/undefined
if (a == null && b == null) return true
// Different types
if (typeof a !== typeof b) return false
// For objects/arrays, do deep comparison via JSON (good enough for settings)
if (typeof a === "object" && typeof b === "object") {
return JSON.stringify(a) === JSON.stringify(b)
}
return false
}

// Only skip change detection for automatic initialization (not user actions)
// This prevents the dirty state when the component initializes and auto-syncs values
// Treat undefined, null, and empty string as uninitialized states
const isInitialSync =
!isUserAction &&
(previousValue === undefined || previousValue === "" || previousValue === null) &&
value !== undefined &&
value !== "" &&
value !== null

if (!isInitialSync) {
// Also skip if it's an automatic sync with semantically equal values
const isAutomaticNoOpSync = !isUserAction && areValuesEqual(previousValue, value)

if (!isInitialSync && !isAutomaticNoOpSync) {
setChangeDetected(true)
}
return { ...prevState, apiConfiguration: { ...prevState.apiConfiguration, [field]: value } }
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,11 @@ import { ThinkingBudget } from "../ThinkingBudget"

type OpenAICompatibleProps = {
apiConfiguration: ProviderSettings
setApiConfigurationField: (field: keyof ProviderSettings, value: ProviderSettings[keyof ProviderSettings]) => void
setApiConfigurationField: <K extends keyof ProviderSettings>(
field: K,
value: ProviderSettings[K],
isUserAction?: boolean,
) => void
organizationAllowList: OrganizationAllowList
modelValidationError?: string
simplifySettings?: boolean
Expand Down Expand Up @@ -88,7 +92,8 @@ export const OpenAICompatible = ({
useEffect(() => {
const timer = setTimeout(() => {
const headerObject = convertHeadersToObject(customHeaders)
setApiConfigurationField("openAiHeaders", headerObject)
// Pass false to indicate this is automatic sync, not a user action
setApiConfigurationField("openAiHeaders", headerObject, false)
}, 300)

return () => clearTimeout(timer)
Expand Down
Loading