feat(combos): hide preset buttons and migrate legacy mimo vision adapter

Co-Authored-By: Claude Code <noreply@anthropic.com>
This commit is contained in:
decolua
2026-09-22 15:32:10 +07:00
parent b53260ca54
commit 1a02713150
3 changed files with 21 additions and 12 deletions

View File

@@ -13,18 +13,19 @@ import { getCapabilitiesForModel } from "../providers/capabilities.js";
const CAPABILITY_KEYS = ["vision", "pdf", "audioInput", "videoInput"];
const HARD_CAPS = new Set(CAPABILITY_KEYS);
const DEFAULT_FALLBACK_MODEL = "oc/mimo-v2.6-flash-free";
const upgradeLegacyModel = (m) => (m === "oc/mimo-v2.5-free" ? DEFAULT_FALLBACK_MODEL : m);
// Normalize a capability entry to { enabled, roundRobin, models }. Backward-compat:
// accept the legacy array form [{model, enabled}] (treated as enabled, fallback).
function normalizeCapEntry(entry) {
if (Array.isArray(entry)) {
return { enabled: true, roundRobin: false, models: entry.map((e) => e?.model || e).filter(Boolean) };
return { enabled: true, roundRobin: false, models: entry.map((e) => upgradeLegacyModel(e?.model || e)).filter(Boolean) };
}
if (entry && typeof entry === "object") {
return {
enabled: entry.enabled !== false,
roundRobin: !!entry.roundRobin,
models: Array.isArray(entry.models) ? entry.models.filter(Boolean) : [],
models: Array.isArray(entry.models) ? entry.models.map(upgradeLegacyModel).filter(Boolean) : [],
};
}
return { enabled: false, roundRobin: false, models: [] };

View File

@@ -17,9 +17,9 @@ const VALID_NAME_REGEX = /^[a-zA-Z0-9_.\-]+$/;
// A request needing a capability the target model/combo lacks switches straight
// to the first enabled model here instead of erroring or dropping the data.
const CAPACITY_ADAPTER_CAPS = [
{ key: "vision", label: "Vision", icon: "visibility", desc: "Images" },
{ key: "vision", label: "Vision", icon: "visibility", desc: "images (png, jpg, webp, …)" },
// pdf, videoInput temporarily hidden — no translator support yet for those blocks.
{ key: "audioInput", label: "Audio", icon: "graphic_eq", desc: "Audio input" },
{ key: "audioInput", label: "Audio", icon: "graphic_eq", desc: "audio input" },
];
const DEFAULT_FALLBACK_MODEL = "oc/mimo-v2.6-flash-free";
const EMPTY_CAP_ENTRY = { enabled: true, roundRobin: false, models: [] };
@@ -29,16 +29,18 @@ const EMPTY_CAPACITY_ADAPTER = {
audioInput: { ...EMPTY_CAP_ENTRY },
videoInput: { ...EMPTY_CAP_ENTRY },
};
const upgradeLegacyModel = (m) => (m === "oc/mimo-v2.5-free" ? DEFAULT_FALLBACK_MODEL : m);
// Backward-compat: legacy stored form was an array of {model, enabled}.
function normalizeCapEntry(entry) {
if (Array.isArray(entry)) {
return { enabled: true, roundRobin: false, models: entry.map((e) => e?.model || e).filter(Boolean) };
return { enabled: true, roundRobin: false, models: entry.map((e) => upgradeLegacyModel(e?.model || e)).filter(Boolean) };
}
if (entry && typeof entry === "object") {
return {
enabled: entry.enabled !== false,
roundRobin: !!entry.roundRobin,
models: Array.isArray(entry.models) ? entry.models.filter(Boolean) : [],
models: Array.isArray(entry.models) ? entry.models.map(upgradeLegacyModel).filter(Boolean) : [],
};
}
return { ...EMPTY_CAP_ENTRY };
@@ -371,7 +373,7 @@ export default function CombosPage() {
<li><span className="font-medium text-text-main">Round Robin</span> — rotates models across requests to spread load</li>
<li><span className="font-medium text-text-main">Fusion</span> — queries all models in parallel, then a judge synthesizes one answer. Best quality, but costs the most: every request bills all panel models + the judge (N+1 calls)</li>
</ul>
<p className="text-xs text-text-muted mt-3 max-w-2xl">
<p className="hidden text-xs text-text-muted mt-3 max-w-2xl">
<span className="font-medium text-text-main">Cursor / Claude Default</span> create combos named exactly like those clients&apos; model IDs (e.g. <code className="font-mono">composer-2.5</code>, <code className="font-mono">opus</code>), seeded with the matching <code className="font-mono">cu/…</code> or <code className="font-mono">cc/…</code> route so traffic can hit 9router without the prefix.
{" "}Note: Cursor IDE itself often blocks built-in Composer / Grok from Override OpenAI Base URL (&quot;model does not support custom API&quot;); add them via Cursor&apos;s <span className="font-medium text-text-main">Add Custom Model</span> using the combo name, or pick a model Cursor allows through the custom endpoint.
</p>
@@ -380,7 +382,7 @@ export default function CombosPage() {
<Button icon="add" onClick={() => setShowCreateModal(true)} className="w-full sm:w-auto whitespace-nowrap">
Create Combo
</Button>
<div className="grid grid-cols-2 gap-2 sm:flex sm:flex-col">
<div className="hidden">
<Button
variant="secondary"
size="sm"
@@ -707,10 +709,6 @@ function CapacityAdapterSection({ capacityAdapter, onChange, activeProviders, ge
<p className="text-xs text-text-muted mt-0.5">
Your model can&apos;t read image/audio? Auto-switches to a model in the pool below.
</p>
<ul className="mt-1.5 text-[11px] text-text-muted flex flex-col gap-0.5">
<li><span className="font-medium text-text-main">Vision</span> — images (png, jpg, webp, …)</li>
<li><span className="font-medium text-text-main">Audio</span> — audio input</li>
</ul>
</div>
</div>
<div className="flex flex-col gap-4">

View File

@@ -86,6 +86,16 @@ export function mergeWithDefaults(raw) {
}
}
}
if (merged.capacityAdapter && typeof merged.capacityAdapter === "object") {
for (const capKey of Object.keys(merged.capacityAdapter)) {
const entry = merged.capacityAdapter[capKey];
if (Array.isArray(entry?.models)) {
entry.models = entry.models.map((m) =>
m === "oc/mimo-v2.5-free" ? "oc/mimo-v2.6-flash-free" : m
);
}
}
}
return merged;
}