fix(combos): show effective strategy vs global default; keep explicit fallback override

This commit is contained in:
2026-08-22 14:34:02 +07:00
parent 99752a397c
commit eedad6c5ea
2 changed files with 27 additions and 9 deletions

View File

@@ -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";

View File

@@ -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" },