From eedad6c5eadbedd5a399ca6d2a82dc5aaf561200 Mon Sep 17 00:00:00 2001 From: luulam Date: Sat, 22 Aug 2026 14:34:02 +0700 Subject: [PATCH] fix(combos): show effective strategy vs global default; keep explicit fallback override --- src/app/(dashboard)/dashboard/combos/page.js | 22 ++++++++++++++----- .../media-providers/combo/[id]/page.js | 14 +++++++++--- 2 files changed, 27 insertions(+), 9 deletions(-) diff --git a/src/app/(dashboard)/dashboard/combos/page.js b/src/app/(dashboard)/dashboard/combos/page.js index fa093bfd..5fa02323 100644 --- a/src/app/(dashboard)/dashboard/combos/page.js +++ b/src/app/(dashboard)/dashboard/combos/page.js @@ -51,6 +51,7 @@ export default function CombosPage() { const [editingCombo, setEditingCombo] = useState(null); const [activeProviders, setActiveProviders] = useState([]); const [comboStrategies, setComboStrategies] = useState({}); + const [globalComboStrategy, setGlobalComboStrategy] = useState("fallback"); const [capacityAdapter, setCapacityAdapter] = useState(EMPTY_CAPACITY_ADAPTER); const { getCaps } = useModelCaps(); const [confirmState, setConfirmState] = useState(null); @@ -77,6 +78,7 @@ export default function CombosPage() { setActiveProviders(providersData.connections || []); } setComboStrategies(settingsData.comboStrategies || {}); + setGlobalComboStrategy(settingsData.comboStrategy || "fallback"); const rawAdapter = settingsData.capacityAdapter || {}; const normalized = {}; for (const cap of CAPACITY_ADAPTER_CAPS) { @@ -159,14 +161,19 @@ export default function CombosPage() { }); }; - // Merge a per-combo strategy patch into settings.comboStrategies. Passing an empty - // patch (strategy back to default "fallback") drops the entry entirely. + // Merge a per-combo strategy patch into settings.comboStrategies. + // A "fallback" entry is only pruned when the global strategy is also fallback; + // otherwise it's kept so the combo explicitly overrides global round-robin/fusion. const handleSetComboStrategy = async (comboName, patch) => { try { const updated = { ...comboStrategies }; const next = { ...(updated[comboName] || {}), ...patch }; - // Prune to keep settings clean: default fallback with no extras = no entry. - if (!next.fallbackStrategy || next.fallbackStrategy === "fallback") { + // Prune to keep settings clean: default fallback with no extras = no entry, + // but only when it matches the global default (otherwise it's an override). + if ( + (!next.fallbackStrategy || next.fallbackStrategy === "fallback") && + (globalComboStrategy === "fallback" || !globalComboStrategy) + ) { delete updated[comboName]; } else { updated[comboName] = next; @@ -239,6 +246,7 @@ export default function CombosPage() { onEdit={() => setEditingCombo(combo)} onDelete={() => handleDelete(combo.id)} strategy={comboStrategies[combo.name] || {}} + globalStrategy={globalComboStrategy} onSetStrategy={(patch) => handleSetComboStrategy(combo.name, patch)} /> ))} @@ -294,9 +302,11 @@ const STRATEGY_OPTIONS = [ { value: "fusion", label: "Fusion — panel + judge" }, ]; -function ComboCard({ combo, getCaps, activeProviders = [], copied, onCopy, onEdit, onDelete, strategy = {}, onSetStrategy }) { +function ComboCard({ combo, getCaps, activeProviders = [], copied, onCopy, onEdit, onDelete, strategy = {}, globalStrategy = "fallback", onSetStrategy }) { const [showJudgeSelect, setShowJudgeSelect] = useState(false); - const current = strategy.fallbackStrategy || "fallback"; + // Show the effective strategy: per-combo override first, then the global + // default (combos without an entry fall through to settings.comboStrategy). + const current = strategy.fallbackStrategy || globalStrategy || "fallback"; const judge = strategy.judgeModel || ""; const isFusion = current === "fusion"; diff --git a/src/app/(dashboard)/dashboard/media-providers/combo/[id]/page.js b/src/app/(dashboard)/dashboard/media-providers/combo/[id]/page.js index 7ad8b673..974efe93 100644 --- a/src/app/(dashboard)/dashboard/media-providers/combo/[id]/page.js +++ b/src/app/(dashboard)/dashboard/media-providers/combo/[id]/page.js @@ -84,7 +84,9 @@ export default function ComboDetailPage() { setName(c.name); setProviders(c.models || []); const s = settingsRes.ok ? await settingsRes.json() : {}; - setRoundRobin(s.comboStrategies?.[c.name]?.fallbackStrategy === "round-robin"); + // Effective strategy: per-combo override first, then the global default. + const eff = s.comboStrategies?.[c.name]?.fallbackStrategy || s.comboStrategy || "fallback"; + setRoundRobin(eff === "round-robin"); const allLogs = logsRes.ok ? await logsRes.json() : []; setLogs(allLogs.filter((l) => typeof l === "string" && l.includes(c.name)).slice(0, 50)); } catch { /* noop */ } @@ -154,8 +156,14 @@ export default function ComboDetailPage() { const settingsRes = await fetch("/api/settings", { cache: "no-store" }); const s = settingsRes.ok ? await settingsRes.json() : {}; const updated = { ...(s.comboStrategies || {}) }; - if (enabled) updated[combo.name] = { fallbackStrategy: "round-robin" }; - else delete updated[combo.name]; + if (enabled) { + updated[combo.name] = { fallbackStrategy: "round-robin" }; + } else { + // Explicit fallback override so the combo stays ordered even when the + // global default is round-robin (a bare delete would inherit it). + if ((s.comboStrategy || "fallback") === "fallback") delete updated[combo.name]; + else updated[combo.name] = { fallbackStrategy: "fallback" }; + } await fetch("/api/settings", { method: "PATCH", headers: { "Content-Type": "application/json" },