diff --git a/ui/desktop/src/components/settings/providers/modal/ProviderConfiguationModal.tsx b/ui/desktop/src/components/settings/providers/modal/ProviderConfiguationModal.tsx index 688d86cfc767..1244bac42053 100644 --- a/ui/desktop/src/components/settings/providers/modal/ProviderConfiguationModal.tsx +++ b/ui/desktop/src/components/settings/providers/modal/ProviderConfiguationModal.tsx @@ -104,9 +104,20 @@ export default function ProviderConfigurationModal({ } const toSubmit = Object.fromEntries( - Object.entries(configValues) - .filter(([_k, entry]) => !!entry.value) - .map(([k, entry]) => [k, entry.value || '']) + parameters + .map((parameter) => { + const entry = configValues[parameter.name]; + if (entry?.value) { + return [parameter.name, entry.value] as const; + } + + if (!parameter.secret && typeof entry?.serverValue === 'string' && entry.serverValue) { + return [parameter.name, entry.serverValue] as const; + } + + return null; + }) + .filter((item): item is readonly [string, string] => item !== null) ); try { diff --git a/ui/desktop/src/components/settings/providers/modal/subcomponents/handlers/DefaultSubmitHandler.test.ts b/ui/desktop/src/components/settings/providers/modal/subcomponents/handlers/DefaultSubmitHandler.test.ts new file mode 100644 index 000000000000..aa190e697934 --- /dev/null +++ b/ui/desktop/src/components/settings/providers/modal/subcomponents/handlers/DefaultSubmitHandler.test.ts @@ -0,0 +1,61 @@ +import { beforeEach, describe, expect, it, vi } from 'vitest'; + +import { providerConfigSubmitHandler } from './DefaultSubmitHandler'; +import { getProviderModels, readConfig } from '../../../../../../api'; + +vi.mock('../../../../../../api', () => ({ + getProviderModels: vi.fn(), + readConfig: vi.fn(), +})); + +describe('providerConfigSubmitHandler', () => { + beforeEach(() => { + vi.clearAllMocks(); + }); + + it('keeps existing OPENAI_HOST when saving only another field', async () => { + const upsertFn = vi.fn().mockResolvedValue(undefined); + vi.mocked(getProviderModels).mockResolvedValue({} as never); + + vi.mocked(readConfig).mockImplementation(async ({ body }) => { + if (body.key === 'OPENAI_HOST') { + return { data: 'https://proxy.example.com/v1' } as never; + } + if (body.key === 'OPENAI_BASE_PATH') { + return { data: 'v1/chat/completions' } as never; + } + return { data: null } as never; + }); + + await providerConfigSubmitHandler( + upsertFn, + { + name: 'openai', + metadata: { + config_keys: [ + { name: 'OPENAI_API_KEY', required: false, secret: true }, + { + name: 'OPENAI_HOST', + required: true, + secret: false, + default: 'https://api.openai.com', + }, + { + name: 'OPENAI_BASE_PATH', + required: true, + secret: false, + default: 'v1/chat/completions', + }, + ], + }, + }, + { + OPENAI_API_KEY: 'new-key', + } + ); + + expect(upsertFn).toHaveBeenCalledWith('OPENAI_API_KEY', 'new-key', true); + expect(upsertFn).toHaveBeenCalledWith('OPENAI_HOST', 'https://proxy.example.com/v1', false); + expect(upsertFn).not.toHaveBeenCalledWith('OPENAI_HOST', 'https://api.openai.com', false); + }); +}); diff --git a/ui/desktop/src/components/settings/providers/modal/subcomponents/handlers/DefaultSubmitHandler.tsx b/ui/desktop/src/components/settings/providers/modal/subcomponents/handlers/DefaultSubmitHandler.tsx index 26a853f6031f..610f9fd2477b 100644 --- a/ui/desktop/src/components/settings/providers/modal/subcomponents/handlers/DefaultSubmitHandler.tsx +++ b/ui/desktop/src/components/settings/providers/modal/subcomponents/handlers/DefaultSubmitHandler.tsx @@ -80,7 +80,9 @@ export const providerConfigSubmitHandler = async ( const value = configValues[parameter.name] !== undefined ? configValues[parameter.name] - : parameter.default; + : previousConfigValues[parameter.name]?.value !== undefined + ? previousConfigValues[parameter.name].value + : parameter.default; if (value === undefined || value === null) { return;