diff --git a/apps/codex-plus-manager/src/App.tsx b/apps/codex-plus-manager/src/App.tsx index e9f83d18..2cb44950 100644 --- a/apps/codex-plus-manager/src/App.tsx +++ b/apps/codex-plus-manager/src/App.tsx @@ -63,10 +63,12 @@ import { Input } from "@/components/ui/input"; import { Label } from "@/components/ui/label"; import { Textarea } from "@/components/ui/textarea"; import { + createModelWindowRow, mergeModelWindowRows, modelWindowRowsFromProfile, serializeModelWindowRows, type ModelWindowRow, + validateModelWindowRows, } from "./model-windows"; import { getLanguage, t, tf, toggleLanguage } from "@/i18n"; @@ -1396,13 +1398,13 @@ export function App() { const saveSettingsValue = async (next: BackendSettings, silent = true) => { const normalized = normalizeSettings(next); - setSettingsForm(normalized); const result = await run(() => call("save_settings", { settings: normalized })); - if (result) { - setSettings(result); - setSettingsForm(normalizeSettings(result.settings)); - if (!silent || !isSuccessStatus(result.status)) showNotice(t("设置保存"), result.message, result.status); - } + if (!result) return false; + if (!silent || !isSuccessStatus(result.status)) showNotice(t("设置保存"), result.message, result.status); + if (!isSuccessStatus(result.status)) return false; + setSettings(result); + setSettingsForm(normalizeSettings(result.settings)); + return true; }; const resetSettings = async () => { @@ -1569,13 +1571,14 @@ export function App() { const saveRelayFile = async (kind: "config" | "auth", contents: string, silent = false) => { const result = await run(() => call("save_relay_file", { request: { kind, contents } })); - if (result) { - setRelayFiles(result); - if (!silent || !isSuccessStatus(result.status)) { - showNotice(kind === "config" ? "config.toml" : "auth.json", result.message, result.status); - } - await refreshRelay(true); + if (!result) return false; + if (!silent || !isSuccessStatus(result.status)) { + showNotice(kind === "config" ? "config.toml" : "auth.json", result.message, result.status); } + if (!isSuccessStatus(result.status)) return false; + setRelayFiles(result); + await refreshRelay(true); + return true; }; const upsertContextEntry = async (next: BackendSettings, kind: ContextKind, id: string, tomlBody: string) => { @@ -1663,12 +1666,12 @@ export function App() { const switchRelayProfile = async (next: BackendSettings, previousActiveRelayId = settingsForm.activeRelayId) => { if (relaySwitching) { showNotice(t("供应商切换中"), t("上一次切换还没有完成,请稍后再试。"), "failed"); - return; + return false; } let switchSettings = normalizeSettings(next); if (!switchSettings.relayProfilesEnabled) { showNotice(t("供应商配置已关闭"), t("当前不会写入 Codex config.toml / auth.json。打开供应商配置总开关后再切换。"), "failed"); - return; + return false; } const targetBeforeSnapshot = activeRelayProfile(switchSettings); logDiagnostic("switchRelayProfile.start", { @@ -1686,9 +1689,13 @@ export function App() { error: validationError, }); showNotice(t("供应商配置可能不正确"), validationError, "failed"); - return; + return false; } - switchSettings = await snapshotActiveRelayFilesBeforeSwitch(switchSettings, previousActiveRelayId); + const snapshottedSettings = previousActiveRelayId === switchSettings.activeRelayId + ? switchSettings + : await snapshotActiveRelayFilesBeforeSwitch(switchSettings, previousActiveRelayId); + if (!snapshottedSettings) return false; + switchSettings = snapshottedSettings; const selectedAfterSave = activeRelayProfile(switchSettings); const command = relayProfileSwitchCommand(selectedAfterSave); @@ -1709,9 +1716,19 @@ export function App() { logDiagnostic("switchRelayProfile.apply_no_result", { targetRelayId: selectedAfterSave.id, }); - return; + return false; } const selectedSettings = normalizeSettings(result.settings); + if (!isSuccessStatus(result.status)) { + logDiagnostic("switchRelayProfile.apply_failed", { + targetRelayId: selectedAfterSave.id, + status: result.status, + message: result.message, + activeRelayId: selectedSettings.activeRelayId, + }); + showNotice(t("供应商切换"), result.message, result.status); + return false; + } setSettings({ status: result.status, message: result.message, @@ -1726,16 +1743,6 @@ export function App() { ...result.relay, }); await refreshRelayFiles(true); - if (!isSuccessStatus(result.status)) { - logDiagnostic("switchRelayProfile.apply_failed", { - targetRelayId: selectedAfterSave.id, - status: result.status, - message: result.message, - activeRelayId: selectedSettings.activeRelayId, - }); - showNotice(t("供应商切换"), result.message, result.status); - return; - } const currentSelected = activeRelayProfile(selectedSettings); logDiagnostic("switchRelayProfile.ok", { targetRelayId: currentSelected.id, @@ -1743,6 +1750,7 @@ export function App() { status: result.status, }); showNotice(t("供应商切换"), relayProfileModeSwitchedText(currentSelected), result.status); + return true; } finally { setRelaySwitching(false); } @@ -1751,7 +1759,7 @@ export function App() { const snapshotActiveRelayFilesBeforeSwitch = async ( next: BackendSettings, previousActiveRelayId: string, - ): Promise => { + ): Promise => { const profileId = previousActiveRelayId.trim(); if (!profileId) return next; const result = await run(() => @@ -1759,11 +1767,11 @@ export function App() { request: { settings: next, profileId }, }), ); - if (!result) return next; + if (!result) return null; const normalized = normalizeSettings(result.settings); if (!isSuccessStatus(result.status)) { showNotice(t("供应商切换"), result.message, result.status); - return next; + return null; } return normalized; }; @@ -2218,7 +2226,7 @@ type Actions = { checkUpdate: () => Promise; performUpdate: () => Promise; saveSettings: () => Promise; - saveSettingsValue: (settings: BackendSettings, silent?: boolean) => Promise; + saveSettingsValue: (settings: BackendSettings, silent?: boolean) => Promise; refreshSettings: (silent?: boolean) => Promise; resetSettings: () => Promise; resetImageOverlaySettings: () => Promise; @@ -2253,7 +2261,7 @@ type Actions = { applyRelayInjection: () => Promise; applyPureApiInjection: () => Promise; clearRelayInjection: () => Promise; - saveRelayFile: (kind: "config" | "auth", contents: string, silent?: boolean) => Promise; + saveRelayFile: (kind: "config" | "auth", contents: string, silent?: boolean) => Promise; upsertContextEntry: ( settings: BackendSettings, kind: ContextKind, @@ -2266,7 +2274,7 @@ type Actions = { diagnoseRelayProfile: (profile: RelayProfile) => Promise; testStepwiseSettings: (settings: BackendSettings) => Promise; fetchRelayProfileModels: (profile: RelayProfile) => Promise; - switchRelayProfile: (settings: BackendSettings, previousActiveRelayId?: string) => Promise; + switchRelayProfile: (settings: BackendSettings, previousActiveRelayId?: string) => Promise; relaySwitching: boolean; switchOfficialMode: () => Promise; switchPureApiMode: () => Promise; @@ -2412,8 +2420,9 @@ function RelayScreen({ : null); const isNewProfile = !!newProfileDraft; const saveRelaySettings = async (next: BackendSettings) => { - onFormChange(next); - await actions.saveSettingsValue(next, true); + const saved = await actions.saveSettingsValue(next, true); + if (saved) onFormChange(next); + return saved; }; const createNewAggregateProfile = () => { const draft = createAggregateRelayProfile(normalized); @@ -3649,7 +3658,7 @@ function RelayProfileList({ actions, }: { form: BackendSettings; - onFormChange: (value: BackendSettings) => void; + onFormChange: (value: BackendSettings) => boolean | Promise; onEdit: (id: string) => void; disabled?: boolean; actions: Actions; @@ -3702,7 +3711,7 @@ function SortableRelayProfileCard({ form: BackendSettings; profile: RelayProfile; index: number; - onFormChange: (value: BackendSettings) => void; + onFormChange: (value: BackendSettings) => boolean | Promise; onEdit: (id: string) => void; disabled?: boolean; actions: Actions; @@ -3799,8 +3808,17 @@ function SortableRelayProfileCard({