fix(combos): show effective strategy vs global default; keep explicit fallback override
This commit is contained in:
@@ -51,6 +51,7 @@ export default function CombosPage() {
|
|||||||
const [editingCombo, setEditingCombo] = useState(null);
|
const [editingCombo, setEditingCombo] = useState(null);
|
||||||
const [activeProviders, setActiveProviders] = useState([]);
|
const [activeProviders, setActiveProviders] = useState([]);
|
||||||
const [comboStrategies, setComboStrategies] = useState({});
|
const [comboStrategies, setComboStrategies] = useState({});
|
||||||
|
const [globalComboStrategy, setGlobalComboStrategy] = useState("fallback");
|
||||||
const [capacityAdapter, setCapacityAdapter] = useState(EMPTY_CAPACITY_ADAPTER);
|
const [capacityAdapter, setCapacityAdapter] = useState(EMPTY_CAPACITY_ADAPTER);
|
||||||
const { getCaps } = useModelCaps();
|
const { getCaps } = useModelCaps();
|
||||||
const [confirmState, setConfirmState] = useState(null);
|
const [confirmState, setConfirmState] = useState(null);
|
||||||
@@ -77,6 +78,7 @@ export default function CombosPage() {
|
|||||||
setActiveProviders(providersData.connections || []);
|
setActiveProviders(providersData.connections || []);
|
||||||
}
|
}
|
||||||
setComboStrategies(settingsData.comboStrategies || {});
|
setComboStrategies(settingsData.comboStrategies || {});
|
||||||
|
setGlobalComboStrategy(settingsData.comboStrategy || "fallback");
|
||||||
const rawAdapter = settingsData.capacityAdapter || {};
|
const rawAdapter = settingsData.capacityAdapter || {};
|
||||||
const normalized = {};
|
const normalized = {};
|
||||||
for (const cap of CAPACITY_ADAPTER_CAPS) {
|
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
|
// Merge a per-combo strategy patch into settings.comboStrategies.
|
||||||
// patch (strategy back to default "fallback") drops the entry entirely.
|
// 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) => {
|
const handleSetComboStrategy = async (comboName, patch) => {
|
||||||
try {
|
try {
|
||||||
const updated = { ...comboStrategies };
|
const updated = { ...comboStrategies };
|
||||||
const next = { ...(updated[comboName] || {}), ...patch };
|
const next = { ...(updated[comboName] || {}), ...patch };
|
||||||
// Prune to keep settings clean: default fallback with no extras = no entry.
|
// Prune to keep settings clean: default fallback with no extras = no entry,
|
||||||
if (!next.fallbackStrategy || next.fallbackStrategy === "fallback") {
|
// 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];
|
delete updated[comboName];
|
||||||
} else {
|
} else {
|
||||||
updated[comboName] = next;
|
updated[comboName] = next;
|
||||||
@@ -239,6 +246,7 @@ export default function CombosPage() {
|
|||||||
onEdit={() => setEditingCombo(combo)}
|
onEdit={() => setEditingCombo(combo)}
|
||||||
onDelete={() => handleDelete(combo.id)}
|
onDelete={() => handleDelete(combo.id)}
|
||||||
strategy={comboStrategies[combo.name] || {}}
|
strategy={comboStrategies[combo.name] || {}}
|
||||||
|
globalStrategy={globalComboStrategy}
|
||||||
onSetStrategy={(patch) => handleSetComboStrategy(combo.name, patch)}
|
onSetStrategy={(patch) => handleSetComboStrategy(combo.name, patch)}
|
||||||
/>
|
/>
|
||||||
))}
|
))}
|
||||||
@@ -294,9 +302,11 @@ const STRATEGY_OPTIONS = [
|
|||||||
{ value: "fusion", label: "Fusion — panel + judge" },
|
{ 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 [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 judge = strategy.judgeModel || "";
|
||||||
const isFusion = current === "fusion";
|
const isFusion = current === "fusion";
|
||||||
|
|
||||||
|
|||||||
@@ -84,7 +84,9 @@ export default function ComboDetailPage() {
|
|||||||
setName(c.name);
|
setName(c.name);
|
||||||
setProviders(c.models || []);
|
setProviders(c.models || []);
|
||||||
const s = settingsRes.ok ? await settingsRes.json() : {};
|
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() : [];
|
const allLogs = logsRes.ok ? await logsRes.json() : [];
|
||||||
setLogs(allLogs.filter((l) => typeof l === "string" && l.includes(c.name)).slice(0, 50));
|
setLogs(allLogs.filter((l) => typeof l === "string" && l.includes(c.name)).slice(0, 50));
|
||||||
} catch { /* noop */ }
|
} catch { /* noop */ }
|
||||||
@@ -154,8 +156,14 @@ export default function ComboDetailPage() {
|
|||||||
const settingsRes = await fetch("/api/settings", { cache: "no-store" });
|
const settingsRes = await fetch("/api/settings", { cache: "no-store" });
|
||||||
const s = settingsRes.ok ? await settingsRes.json() : {};
|
const s = settingsRes.ok ? await settingsRes.json() : {};
|
||||||
const updated = { ...(s.comboStrategies || {}) };
|
const updated = { ...(s.comboStrategies || {}) };
|
||||||
if (enabled) updated[combo.name] = { fallbackStrategy: "round-robin" };
|
if (enabled) {
|
||||||
else delete updated[combo.name];
|
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", {
|
await fetch("/api/settings", {
|
||||||
method: "PATCH",
|
method: "PATCH",
|
||||||
headers: { "Content-Type": "application/json" },
|
headers: { "Content-Type": "application/json" },
|
||||||
|
|||||||
Reference in New Issue
Block a user