Compare commits

...

2 Commits

6 changed files with 60 additions and 0 deletions
@@ -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<SortOption>('priority');
const [sortDirection, setSortDirection] = useState<SortDirection>('asc');
const [selectedModels, setSelectedModels] = useState<Set<string>>(new Set());
@@ -529,6 +533,7 @@ export function OpenAISection({
const apiKeyEntries = provider.apiKeyEntries || [];
const statusData =
statusBarCache.get(getOpenAIProviderKey(provider, originalIndex)) || EMPTY_STATUS_BAR;
const providerDisabled = provider.disabled === true;
return (
<div
@@ -554,6 +559,11 @@ export function OpenAISection({
<span className={styles.fieldLabel}>{t('common.base_url')}:</span>
<span className={styles.fieldValue}>{provider.baseUrl}</span>
</div>
{providerDisabled && (
<div className="status-badge warning" style={{ marginTop: 8, marginBottom: 0 }}>
{t('ai_providers.config_disabled_badge')}
</div>
)}
{headerEntries.length > 0 && (
<div className={styles.headerBadgeList}>
{headerEntries.map(([key, value]) => (
@@ -651,6 +661,12 @@ export function OpenAISection({
>
{t('common.delete')}
</Button>
<ToggleSwitch
label={t('ai_providers.config_toggle_label')}
checked={!providerDisabled}
disabled={toggleDisabled}
onChange={(value) => void onToggle(originalIndex, value)}
/>
</div>
</div>
);
@@ -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,
+33
View File
@@ -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)}
/>
</div>
</div>
+4
View File
@@ -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)}`)
};
+2
View File
@@ -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;
+1
View File
@@ -54,6 +54,7 @@ export interface OpenAIProviderConfig {
prefix?: string;
baseUrl: string;
apiKeyEntries: ApiKeyEntry[];
disabled?: boolean;
headers?: Record<string, string>;
models?: ModelAlias[];
priority?: number;