fix: prevent false unsaved changes prompt with OpenAI Compatible headers (#8230) (#11334)

fix: prevent false unsaved changes prompt with OpenAI Compatible headers

Mark automatic header syncs in ApiOptions and OpenAICompatible as
non-user actions (isUserAction: false) and enhance SettingsView change
detection to skip automatic syncs with semantically equal values.

Root cause: two components (ApiOptions and OpenAICompatible) manage
openAiHeaders state and automatically sync it back on mount/remount.
These syncs were treated as user changes, triggering a false dirty state.

Co-authored-by: Robert McIntyre <robertjmcintyre@users.noreply.github.com>
This commit is contained in:
Daniel 2026-02-09 12:40:36 -05:00 committed by GitHub
parent 1e0fc89fa1
commit 2b72a585dc
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
3 changed files with 22 additions and 5 deletions

View file

@ -155,7 +155,7 @@ const ApiOptions = ({
// Only update if the processed object is different from the current config.
if (JSON.stringify(currentConfigHeaders) !== JSON.stringify(newHeadersObject)) {
setApiConfigurationField("openAiHeaders", newHeadersObject)
setApiConfigurationField("openAiHeaders", newHeadersObject, false)
}
},
300,

View file

@ -262,9 +262,19 @@ 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 => {
if (a === b) return true
if (a == null && b == null) return true
if (typeof a !== typeof b) return false
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) &&
@ -272,7 +282,10 @@ const SettingsView = forwardRef<SettingsViewRef, SettingsViewProps>(({ onDone, t
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 } }

View file

@ -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
@ -88,7 +92,7 @@ export const OpenAICompatible = ({
useEffect(() => {
const timer = setTimeout(() => {
const headerObject = convertHeadersToObject(customHeaders)
setApiConfigurationField("openAiHeaders", headerObject)
setApiConfigurationField("openAiHeaders", headerObject, false)
}, 300)
return () => clearTimeout(timer)