From c2820126070db73b0b300ea635484f9f09b0034e Mon Sep 17 00:00:00 2001 From: chengyongru Date: Fri, 12 Jun 2026 10:16:54 +0800 Subject: [PATCH] fix: resolve auto custom provider settings state maintainer edit: use the resolved provider row when WebUI settings evaluates auto-selected providers, so named custom providers follow their apiBase-based configured state instead of the legacy has_api_key fallback. --- .../src/components/settings/SettingsView.tsx | 7 ++ webui/src/tests/settings-view.test.tsx | 81 +++++++++++++++++++ 2 files changed, 88 insertions(+) diff --git a/webui/src/components/settings/SettingsView.tsx b/webui/src/components/settings/SettingsView.tsx index b1ea148d5..f5f08c608 100644 --- a/webui/src/components/settings/SettingsView.tsx +++ b/webui/src/components/settings/SettingsView.tsx @@ -341,6 +341,13 @@ function settingsProviderConfigured( ): boolean { const row = settingsProviderRow(payload, provider); if (row) return row.configured; + if (provider === "auto") { + const resolvedRow = settingsProviderRow( + payload, + payload.agent.resolved_provider ?? payload.agent.provider, + ); + if (resolvedRow) return resolvedRow.configured; + } return payload.agent.has_api_key; } diff --git a/webui/src/tests/settings-view.test.tsx b/webui/src/tests/settings-view.test.tsx index 15d0dbc54..eac0b9c6b 100644 --- a/webui/src/tests/settings-view.test.tsx +++ b/webui/src/tests/settings-view.test.tsx @@ -100,6 +100,46 @@ function settingsPayload(): SettingsPayload { }; } +function autoDynamicProviderPayload( + options: { + configured: boolean; + hasApiKey: boolean; + apiBase: string | null; + apiKeyHint: string | null; + }, +): SettingsPayload { + const base = settingsPayload(); + return { + ...base, + agent: { + ...base.agent, + model: "companyProxy/gpt-4o", + provider: "companyProxy", + resolved_provider: "companyProxy", + has_api_key: options.hasApiKey, + }, + model_presets: [ + { + ...base.model_presets[0], + model: "companyProxy/gpt-4o", + provider: "auto", + }, + ], + providers: [ + { + name: "companyProxy", + label: "Company Proxy", + configured: options.configured, + auth_type: "api_key", + api_key_required: false, + api_key_hint: options.apiKeyHint, + api_base: options.apiBase, + default_api_base: null, + }, + ], + }; +} + const installedAnyGen = { name: "anygen", display_name: "AnyGen", @@ -336,6 +376,47 @@ describe("SettingsView Apps catalog", () => { expect(screen.getByRole("button", { name: "256K" })).toBeInTheDocument(); }); + it("uses the resolved provider row for auto dynamic providers without api keys", async () => { + vi.stubGlobal("fetch", vi.fn(() => new Promise(() => {}))); + + renderSettingsView({ + initialSection: "models", + initialSettings: autoDynamicProviderPayload({ + configured: true, + hasApiKey: false, + apiBase: "https://proxy.example.test/v1", + apiKeyHint: null, + }), + }); + + const configurationButton = await screen.findByRole("button", { + name: "Current configuration", + }); + expect(configurationButton).toHaveTextContent("companyProxy/gpt-4o"); + expect(configurationButton).toHaveTextContent("Company Proxy"); + expect(configurationButton).not.toHaveTextContent("Not configured"); + }); + + it("does not treat auto dynamic provider api keys as configured without apiBase", async () => { + vi.stubGlobal("fetch", vi.fn(() => new Promise(() => {}))); + + renderSettingsView({ + initialSection: "models", + initialSettings: autoDynamicProviderPayload({ + configured: false, + hasApiKey: true, + apiBase: null, + apiKeyHint: "sk-...", + }), + }); + + const configurationButton = await screen.findByRole("button", { + name: "Current configuration", + }); + expect(configurationButton).toHaveTextContent("Not configured"); + expect(configurationButton).toHaveTextContent("Company Proxy ยท companyProxy/gpt-4o"); + }); + it("marks the current model as unconfigured when its provider needs setup", async () => { const payload: SettingsPayload = { ...settingsPayload(),