From a4d1c2363428daf6c0429ab9114c03b05ee7ff5e Mon Sep 17 00:00:00 2001 From: Supra4E8C Date: Thu, 30 Apr 2026 19:52:01 +0800 Subject: [PATCH] feat: add toggle functionality for enabling/disabling OpenAI providers --- .../providers/OpenAISection/OpenAISection.tsx | 18 +++++++++- src/pages/AiProvidersOpenAIEditLayout.tsx | 4 +++ src/pages/AiProvidersPage.tsx | 33 +++++++++++++++++++ src/services/api/providers.ts | 4 +++ src/services/api/transformers.ts | 2 ++ src/types/provider.ts | 1 + 6 files changed, 61 insertions(+), 1 deletion(-) diff --git a/src/components/providers/OpenAISection/OpenAISection.tsx b/src/components/providers/OpenAISection/OpenAISection.tsx index 9db58d5..cb851df 100644 --- a/src/components/providers/OpenAISection/OpenAISection.tsx +++ b/src/components/providers/OpenAISection/OpenAISection.tsx @@ -6,6 +6,7 @@ import { Card } from '@/components/ui/Card'; import { EmptyState } from '@/components/ui/EmptyState'; import { SelectionCheckbox } from '@/components/ui/SelectionCheckbox'; import { Select } from '@/components/ui/Select'; +import { ToggleSwitch } from '@/components/ui/ToggleSwitch'; import { IconCheck, IconChevronDown, @@ -53,6 +54,7 @@ interface OpenAISectionProps { onAdd: () => void; onEdit: (index: number) => void; onDelete: (index: number) => void; + onToggle: (index: number, enabled: boolean) => void; } interface IndexedOpenAIProvider { @@ -80,11 +82,13 @@ export function OpenAISection({ onAdd, onEdit, onDelete, + onToggle, }: OpenAISectionProps) { const { t } = useTranslation(); const pageTransitionLayer = usePageTransitionLayer(); const isTransitionAnimating = pageTransitionLayer?.isAnimating ?? false; const actionsDisabled = disableControls || loading || isSwitching; + const toggleDisabled = disableControls || loading || isSwitching; const [sortOption, setSortOption] = useState('priority'); const [sortDirection, setSortDirection] = useState('asc'); const [selectedModels, setSelectedModels] = useState>(new Set()); @@ -529,12 +533,13 @@ export function OpenAISection({ const apiKeyEntries = provider.apiKeyEntries || []; const statusData = statusBarCache.get(getOpenAIProviderKey(provider, originalIndex)) || EMPTY_STATUS_BAR; + const providerDisabled = provider.disabled === true; return (
{provider.name}
@@ -554,6 +559,11 @@ export function OpenAISection({ {t('common.base_url')}: {provider.baseUrl}
+ {providerDisabled && ( +
+ {t('ai_providers.config_disabled_badge')} +
+ )} {headerEntries.length > 0 && (
{headerEntries.map(([key, value]) => ( @@ -651,6 +661,12 @@ export function OpenAISection({ > {t('common.delete')} + void onToggle(originalIndex, value)} + />
); diff --git a/src/pages/AiProvidersOpenAIEditLayout.tsx b/src/pages/AiProvidersOpenAIEditLayout.tsx index 98a8547..6ef9997 100644 --- a/src/pages/AiProvidersOpenAIEditLayout.tsx +++ b/src/pages/AiProvidersOpenAIEditLayout.tsx @@ -478,6 +478,9 @@ export function AiProvidersOpenAIEditLayout() { if (form.priority !== undefined && Number.isFinite(form.priority)) { payload.priority = Math.trunc(form.priority); } + if (initialData?.disabled !== undefined) { + payload.disabled = initialData.disabled; + } const resolvedTestModel = testModel.trim(); if (resolvedTestModel) payload.testModel = resolvedTestModel; const models = entriesToModels(form.modelEntries); @@ -519,6 +522,7 @@ export function AiProvidersOpenAIEditLayout() { editIndex, form, handleBack, + initialData?.disabled, providers, setDraftBaseline, showNotification, diff --git a/src/pages/AiProvidersPage.tsx b/src/pages/AiProvidersPage.tsx index dd76570..5986047 100644 --- a/src/pages/AiProvidersPage.tsx +++ b/src/pages/AiProvidersPage.tsx @@ -296,6 +296,38 @@ export function AiProvidersPage() { } }; + const setOpenAIProviderEnabled = async (index: number, enabled: boolean) => { + const current = openaiProviders[index]; + if (!current) return; + + const switchingKey = `openai:${current.name}:${index}`; + setConfigSwitchingKey(switchingKey); + + const previousList = openaiProviders; + const nextItem: OpenAIProviderConfig = { ...current, disabled: !enabled }; + const nextList = previousList.map((item, idx) => (idx === index ? nextItem : item)); + + setOpenaiProviders(nextList); + updateConfigValue('openai-compatibility', nextList); + clearCache('openai-compatibility'); + + try { + await providersApi.updateOpenAIProviderDisabled(index, !enabled); + showNotification( + enabled ? t('notification.config_enabled') : t('notification.config_disabled'), + 'success' + ); + } catch (err: unknown) { + const message = getErrorMessage(err); + setOpenaiProviders(previousList); + updateConfigValue('openai-compatibility', previousList); + clearCache('openai-compatibility'); + showNotification(`${t('notification.update_failed')}: ${message}`, 'error'); + } finally { + setConfigSwitchingKey(null); + } + }; + const deleteProviderEntry = async (type: 'codex' | 'claude', index: number) => { const source = type === 'codex' ? codexConfigs : claudeConfigs; const entry = source[index]; @@ -471,6 +503,7 @@ export function AiProvidersPage() { onAdd={() => openEditor('/ai-providers/openai/new')} onEdit={(index) => openEditor(`/ai-providers/openai/${index}`)} onDelete={deleteOpenai} + onToggle={(index, enabled) => void setOpenAIProviderEnabled(index, enabled)} /> diff --git a/src/services/api/providers.ts b/src/services/api/providers.ts index e1d5445..b2fad9e 100644 --- a/src/services/api/providers.ts +++ b/src/services/api/providers.ts @@ -145,6 +145,7 @@ const serializeOpenAIProvider = (provider: OpenAIProviderConfig) => { : [] }; if (provider.prefix?.trim()) payload.prefix = provider.prefix.trim(); + if (provider.disabled !== undefined) payload.disabled = provider.disabled; const headers = serializeHeaders(provider.headers); if (headers) payload.headers = headers; const models = serializeModelAliases(provider.models); @@ -227,6 +228,9 @@ export const providersApi = { updateOpenAIProvider: (index: number, value: OpenAIProviderConfig) => apiClient.patch('/openai-compatibility', { index, value: serializeOpenAIProvider(value) }), + updateOpenAIProviderDisabled: (index: number, disabled: boolean) => + apiClient.patch('/openai-compatibility', { index, value: { disabled } }), + deleteOpenAIProvider: (name: string) => apiClient.delete(`/openai-compatibility?name=${encodeURIComponent(name)}`) }; diff --git a/src/services/api/transformers.ts b/src/services/api/transformers.ts index 8cef6f0..a0e4345 100644 --- a/src/services/api/transformers.ts +++ b/src/services/api/transformers.ts @@ -254,6 +254,8 @@ const normalizeOpenAIProvider = (provider: unknown): OpenAIProviderConfig | null apiKeyEntries }; + const disabled = normalizeBoolean(provider.disabled ?? provider['disabled']); + if (disabled !== undefined) result.disabled = disabled; const prefix = normalizePrefix(provider.prefix ?? provider['prefix']); if (prefix) result.prefix = prefix; if (headers) result.headers = headers; diff --git a/src/types/provider.ts b/src/types/provider.ts index 5dcb0c5..c028483 100644 --- a/src/types/provider.ts +++ b/src/types/provider.ts @@ -54,6 +54,7 @@ export interface OpenAIProviderConfig { prefix?: string; baseUrl: string; apiKeyEntries: ApiKeyEntry[]; + disabled?: boolean; headers?: Record; models?: ModelAlias[]; priority?: number;