feat(combos): display vision adapter models in an ordered table view

Co-Authored-By: Claude Code <noreply@anthropic.com>
This commit is contained in:
decolua
2026-09-26 11:43:22 +07:00
parent 975f28a57c
commit 3e4323e2fd

View File

@@ -734,8 +734,15 @@ function CapacityAdapterCap({ cap, entry, onChange, activeProviders, getCaps })
const patch = (p) => onChange({ ...entry, ...p });
const handleAdd = (model) => {
if (models.includes(model.value)) return;
patch({ models: [...models, model.value] });
const value = model?.value || model?.name || model;
if (!value || models.includes(value)) return;
patch({ models: [...models, value] });
};
const handleDeselect = (model) => {
const value = model?.value || model?.name || model;
const next = models.filter((m) => m !== value);
patch({ models: next.length === 0 ? [DEFAULT_FALLBACK_MODEL] : next });
};
const handleRemove = (index) => {
@@ -754,7 +761,7 @@ function CapacityAdapterCap({ cap, entry, onChange, activeProviders, getCaps })
return (
<Card padding="sm" className={`group ${!enabled ? "opacity-50" : ""}`}>
<div className="flex min-w-0 flex-col gap-3 sm:flex-row sm:items-center sm:justify-between">
{/* Master toggle + icon + label + chips */}
{/* Master toggle + icon + label */}
<div className="flex min-w-0 flex-1 items-start gap-2.5 sm:items-center">
<Toggle
checked={enabled}
@@ -769,33 +776,6 @@ function CapacityAdapterCap({ cap, entry, onChange, activeProviders, getCaps })
<code className="font-mono text-sm font-medium">{cap.label}</code>
<span className="text-[10px] text-text-muted">— {cap.desc}</span>
</div>
<div className="mt-1 flex min-w-0 flex-wrap items-center gap-1">
{models.length === 0 ? (
<span className="text-xs text-text-muted italic">No models</span>
) : (
models.slice(0, 3).map((model, index) => (
<code
key={`${model}-${index}`}
className="group/chip inline-flex items-center gap-1 rounded bg-black/5 px-1.5 py-0.5 font-mono text-xs text-text-muted dark:bg-white/5"
>
<span>{model}</span>
<CapacityBadges caps={getCaps?.(model)} />
<button onClick={() => handleMove(index, -1)} disabled={index === 0} className={`leading-none opacity-0 group-hover/chip:opacity-100 ${index === 0 ? "text-text-muted/20" : "text-text-muted hover:text-primary"}`}>
<span className="material-symbols-outlined text-[12px]">arrow_upward</span>
</button>
<button onClick={() => handleMove(index, 1)} disabled={index === models.length - 1} className={`leading-none opacity-0 group-hover/chip:opacity-100 ${index === models.length - 1 ? "text-text-muted/20" : "text-text-muted hover:text-primary"}`}>
<span className="material-symbols-outlined text-[12px]">arrow_downward</span>
</button>
<button onClick={() => handleRemove(index)} className="leading-none opacity-0 group-hover/chip:opacity-100 text-text-muted hover:text-red-500">
<span className="material-symbols-outlined text-[12px]">close</span>
</button>
</code>
))
)}
{models.length > 3 && (
<span className="text-[10px] text-text-muted">+{models.length - 3} more</span>
)}
</div>
</div>
</div>
@@ -823,11 +803,97 @@ function CapacityAdapterCap({ cap, entry, onChange, activeProviders, getCaps })
</div>
</div>
{/* Model pool list/table */}
{models.length === 0 ? (
<div className="mt-3 py-2 text-center text-xs text-text-muted italic">
No models in pool (will fallback to {DEFAULT_FALLBACK_MODEL})
</div>
) : (
<div className="mt-3 overflow-hidden rounded-lg border border-border/50">
<table className="w-full text-left text-xs">
<thead>
<tr className="border-b border-border/40 bg-black/[0.02] text-text-muted dark:bg-white/[0.02]">
<th className="w-12 px-3 py-1.5 font-medium text-center">#</th>
<th className="px-3 py-1.5 font-medium">Model</th>
<th className="w-24 px-3 py-1.5 font-medium text-center">Order</th>
<th className="w-12 px-3 py-1.5 font-medium text-right"></th>
</tr>
</thead>
<tbody className="divide-y divide-border/30 font-mono">
{models.map((model, index) => (
<tr key={`${model}-${index}`} className="hover:bg-black/[0.02] dark:hover:bg-white/[0.02] transition-colors">
<td className="px-3 py-2 text-center text-text-muted text-[11px] font-sans">
#{index + 1}
</td>
<td className="px-3 py-2 text-text-main">
<div className="flex items-center gap-1.5 flex-wrap">
<span className="truncate">{model}</span>
<CapacityBadges caps={getCaps?.(model)} />
{model === DEFAULT_FALLBACK_MODEL && (
<span className="rounded bg-emerald-500/10 px-1.5 py-0.5 font-sans text-[10px] font-medium text-emerald-600 dark:text-emerald-400">
free default
</span>
)}
</div>
</td>
<td className="px-3 py-2 text-center">
<div className="inline-flex items-center gap-1">
<button
type="button"
onClick={() => handleMove(index, -1)}
disabled={!enabled || index === 0}
className={`p-1 rounded transition-colors ${
!enabled || index === 0
? "text-text-muted/20 cursor-not-allowed"
: "text-text-muted hover:text-primary hover:bg-black/5 dark:hover:bg-white/5"
}`}
title="Move up"
>
<span className="material-symbols-outlined text-[16px] leading-none">arrow_upward</span>
</button>
<button
type="button"
onClick={() => handleMove(index, 1)}
disabled={!enabled || index === models.length - 1}
className={`p-1 rounded transition-colors ${
!enabled || index === models.length - 1
? "text-text-muted/20 cursor-not-allowed"
: "text-text-muted hover:text-primary hover:bg-black/5 dark:hover:bg-white/5"
}`}
title="Move down"
>
<span className="material-symbols-outlined text-[16px] leading-none">arrow_downward</span>
</button>
</div>
</td>
<td className="px-3 py-2 text-right">
<button
type="button"
onClick={() => handleRemove(index)}
disabled={!enabled}
className={`p-1 rounded transition-colors ${
!enabled
? "text-text-muted/20 cursor-not-allowed"
: "text-text-muted hover:text-red-500 hover:bg-red-500/10"
}`}
title="Remove model"
>
<span className="material-symbols-outlined text-[16px] leading-none">close</span>
</button>
</td>
</tr>
))}
</tbody>
</table>
</div>
)}
{showModelSelect && (
<ModelSelectModal
isOpen={showModelSelect}
onClose={() => setShowModelSelect(false)}
onSelect={handleAdd}
onDeselect={handleDeselect}
activeProviders={activeProviders}
title={`Add ${cap.label} Model`}
addedModelValues={models}