fix(dashboard): cut duplicate API/icon spam, lazy-load provider assets
Share one /api/models fetch via useModelCaps cache, mount ModelSelectModal only when open, stop double fetchModelAliases on CLI tool cards, and resolve provider icons through a session 404 cache with missing PNGs + loading=lazy. Also include Claude Exa MCP toggle (claude-settings + ClaudeToolCard) that was already in the working tree. Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
1 parent
68566f53dc
commit
ccb0842d0a
50 files changed
+612
-392
No files matched your search
@@ -7,6 +7,7 @@ import { CSS } from "@dnd-kit/utilities";
|
||||
import { restrictToVerticalAxis, restrictToParentElement } from "@dnd-kit/modifiers";
|
||||
import { Card, Button, Modal, Input, CardSkeleton, ModelSelectModal, ConfirmModal, CapacityBadges, Select } from "@/shared/components";
|
||||
import { useCopyToClipboard } from "@/shared/hooks/useCopyToClipboard";
|
||||
import { useModelCaps } from "@/shared/hooks/useModelCaps";
|
||||
import { isOpenAICompatibleProvider, isAnthropicCompatibleProvider } from "@/shared/constants/providers";
|
||||
|
||||
// Validate combo name: only a-z, A-Z, 0-9, -, _
|
||||
@@ -19,7 +20,7 @@ export default function CombosPage() {
|
||||
const [editingCombo, setEditingCombo] = useState(null);
|
||||
const [activeProviders, setActiveProviders] = useState([]);
|
||||
const [comboStrategies, setComboStrategies] = useState({});
|
||||
const [modelCaps, setModelCaps] = useState({});
|
||||
const { getCaps } = useModelCaps();
|
||||
const [confirmState, setConfirmState] = useState(null);
|
||||
const { copied, copy } = useCopyToClipboard();
|
||||
|
||||
@@ -29,11 +30,10 @@ export default function CombosPage() {
|
||||
|
||||
const fetchData = async () => {
|
||||
try {
|
||||
const [combosRes, providersRes, settingsRes, modelsRes] = await Promise.all([
|
||||
const [combosRes, providersRes, settingsRes] = await Promise.all([
|
||||
fetch("/api/combos"),
|
||||
fetch("/api/providers"),
|
||||
fetch("/api/settings"),
|
||||
fetch("/api/models"),
|
||||
]);
|
||||
const combosData = await combosRes.json();
|
||||
const providersData = await providersRes.json();
|
||||
@@ -44,13 +44,6 @@ export default function CombosPage() {
|
||||
if (providersRes.ok) {
|
||||
setActiveProviders(providersData.connections || []);
|
||||
}
|
||||
if (modelsRes.ok) {
|
||||
const md = await modelsRes.json();
|
||||
// Build fullModel -> caps map for badge lookup
|
||||
const map = {};
|
||||
for (const m of md.models || []) if (m.caps) map[m.fullModel] = m.caps;
|
||||
setModelCaps(map);
|
||||
}
|
||||
setComboStrategies(settingsData.comboStrategies || {});
|
||||
} catch (error) {
|
||||
console.log("Error fetching data:", error);
|
||||
@@ -189,7 +182,7 @@ export default function CombosPage() {
|
||||
<ComboCard
|
||||
key={combo.id}
|
||||
combo={combo}
|
||||
modelCaps={modelCaps}
|
||||
getCaps={getCaps}
|
||||
activeProviders={activeProviders}
|
||||
copied={copied}
|
||||
onCopy={copy}
|
||||
@@ -203,23 +196,26 @@ export default function CombosPage() {
|
||||
)}
|
||||
|
||||
{/* Create Modal - Use key to force remount and reset state */}
|
||||
<ComboFormModal
|
||||
key="create"
|
||||
isOpen={showCreateModal}
|
||||
onClose={() => setShowCreateModal(false)}
|
||||
onSave={handleCreate}
|
||||
activeProviders={activeProviders}
|
||||
/>
|
||||
{showCreateModal && (
|
||||
<ComboFormModal
|
||||
key="create"
|
||||
isOpen={showCreateModal}
|
||||
onClose={() => setShowCreateModal(false)}
|
||||
onSave={handleCreate}
|
||||
activeProviders={activeProviders}
|
||||
/>
|
||||
)}
|
||||
|
||||
{/* Edit Modal - Use key to force remount and reset state */}
|
||||
<ComboFormModal
|
||||
key={editingCombo?.id || "new"}
|
||||
isOpen={!!editingCombo}
|
||||
combo={editingCombo}
|
||||
onClose={() => setEditingCombo(null)}
|
||||
onSave={(data) => handleUpdate(editingCombo.id, data)}
|
||||
activeProviders={activeProviders}
|
||||
/>
|
||||
{editingCombo && (
|
||||
<ComboFormModal
|
||||
key={editingCombo.id}
|
||||
isOpen={!!editingCombo}
|
||||
combo={editingCombo}
|
||||
onClose={() => setEditingCombo(null)}
|
||||
onSave={(data) => handleUpdate(editingCombo.id, data)}
|
||||
activeProviders={activeProviders}
|
||||
/>
|
||||
)}
|
||||
|
||||
{/* Confirm Delete Modal */}
|
||||
<ConfirmModal
|
||||
@@ -240,7 +236,7 @@ const STRATEGY_OPTIONS = [
|
||||
{ value: "fusion", label: "Fusion — panel + judge" },
|
||||
];
|
||||
|
||||
function ComboCard({ combo, modelCaps = {}, activeProviders = [], copied, onCopy, onEdit, onDelete, strategy = {}, onSetStrategy }) {
|
||||
function ComboCard({ combo, getCaps, activeProviders = [], copied, onCopy, onEdit, onDelete, strategy = {}, onSetStrategy }) {
|
||||
const [showJudgeSelect, setShowJudgeSelect] = useState(false);
|
||||
const current = strategy.fallbackStrategy || "fallback";
|
||||
const judge = strategy.judgeModel || "";
|
||||
@@ -262,7 +258,7 @@ function ComboCard({ combo, modelCaps = {}, activeProviders = [], copied, onCopy
|
||||
combo.models.slice(0, 3).map((model, index) => (
|
||||
<code key={index} className="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={modelCaps[model]} />
|
||||
<CapacityBadges caps={getCaps?.(model)} />
|
||||
</code>
|
||||
))
|
||||
)}
|
||||
@@ -340,15 +336,17 @@ function ComboCard({ combo, modelCaps = {}, activeProviders = [], copied, onCopy
|
||||
</div>
|
||||
|
||||
{/* Judge model picker (single-select; combo members make natural judges too) */}
|
||||
<ModelSelectModal
|
||||
isOpen={showJudgeSelect}
|
||||
onClose={() => setShowJudgeSelect(false)}
|
||||
onSelect={(m) => { onSetStrategy({ judgeModel: m?.value || "" }); setShowJudgeSelect(false); }}
|
||||
activeProviders={activeProviders}
|
||||
title="Select Judge Model"
|
||||
addedModelValues={judge ? [judge] : []}
|
||||
closeOnSelect={true}
|
||||
/>
|
||||
{showJudgeSelect && (
|
||||
<ModelSelectModal
|
||||
isOpen={showJudgeSelect}
|
||||
onClose={() => setShowJudgeSelect(false)}
|
||||
onSelect={(m) => { onSetStrategy({ judgeModel: m?.value || "" }); setShowJudgeSelect(false); }}
|
||||
activeProviders={activeProviders}
|
||||
title="Select Judge Model"
|
||||
addedModelValues={judge ? [judge] : []}
|
||||
closeOnSelect={true}
|
||||
/>
|
||||
)}
|
||||
</Card>
|
||||
);
|
||||
}
|
||||
@@ -637,18 +635,20 @@ function ComboFormModal({ isOpen, combo, onClose, onSave, activeProviders, kindF
|
||||
</Modal>
|
||||
|
||||
{/* Model Select Modal */}
|
||||
<ModelSelectModal
|
||||
isOpen={showModelSelect}
|
||||
onClose={() => setShowModelSelect(false)}
|
||||
onSelect={handleAddModel}
|
||||
onDeselect={handleDeselectModel}
|
||||
activeProviders={activeProviders}
|
||||
modelAliases={modelAliases}
|
||||
title="Add Model to Combo"
|
||||
kindFilter={kindFilter}
|
||||
addedModelValues={models}
|
||||
closeOnSelect={false}
|
||||
/>
|
||||
{showModelSelect && (
|
||||
<ModelSelectModal
|
||||
isOpen={showModelSelect}
|
||||
onClose={() => setShowModelSelect(false)}
|
||||
onSelect={handleAddModel}
|
||||
onDeselect={handleDeselectModel}
|
||||
activeProviders={activeProviders}
|
||||
modelAliases={modelAliases}
|
||||
title="Add Model to Combo"
|
||||
kindFilter={kindFilter}
|
||||
addedModelValues={models}
|
||||
closeOnSelect={false}
|
||||
/>
|
||||
)}
|
||||
</>
|
||||
);
|
||||
}
|
||||
Reference in new issue
Block a user