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:
decolua
2026-07-17 12:12:20 +07:00
co-authored by Cursor
parent 68566f53dc
commit ccb0842d0a
50 changed files with 612 additions and 392 deletions
@@ -1265,22 +1265,24 @@ export default function ProviderLimits() {
/>
)}
{hiddenQuotaRows.length > 0 && (
<div className="mt-2 flex flex-wrap items-center gap-1 border-t border-black/5 pt-2 text-[10px] text-text-muted dark:border-white/5">
<span className="material-symbols-outlined text-[14px]">
<div className="mt-2 flex min-w-0 items-center gap-1 border-t border-black/5 pt-2 text-[10px] text-text-muted dark:border-white/5">
<span className="material-symbols-outlined shrink-0 text-[14px]">
visibility_off
</span>
<span>Hidden:</span>
{hiddenQuotaRows.map((quotaRow) => (
<button
key={getQuotaVisibilityKey(quotaRow)}
type="button"
onClick={() => handleShowQuota(conn.provider, quotaRow)}
className="rounded-md border border-black/10 px-1.5 py-0.5 transition-colors hover:bg-black/5 hover:text-text-primary dark:border-white/10 dark:hover:bg-white/5"
title="Show this quota row"
>
{quotaRow.name}
</button>
))}
<span className="shrink-0">Hidden:</span>
<div className="flex min-w-0 flex-1 items-center gap-1 overflow-x-auto whitespace-nowrap">
{hiddenQuotaRows.map((quotaRow) => (
<button
key={getQuotaVisibilityKey(quotaRow)}
type="button"
onClick={() => handleShowQuota(conn.provider, quotaRow)}
className="shrink-0 rounded-md border border-black/10 px-1.5 py-0.5 transition-colors hover:bg-black/5 hover:text-text-primary dark:border-white/10 dark:hover:bg-white/5"
title="Show this quota row"
>
{quotaRow.name}
</button>
))}
</div>
</div>
)}
</div>
@@ -10,6 +10,7 @@ import {
} from "@xyflow/react";
import "@xyflow/react/dist/style.css";
import { AI_PROVIDERS } from "@/shared/constants/providers";
import { getProviderIconSrc, markProviderIconMissing } from "@/shared/utils/providerIcon";
// Force-stop FE animation if a provider stays active longer than this
const FE_ACTIVE_TIMEOUT_MS = 60000;
@@ -19,9 +20,8 @@ function getProviderConfig(providerId) {
return AI_PROVIDERS[providerId] || { color: "#6b7280", name: providerId };
}
// Use local provider images from /public/providers/
function getProviderImageUrl(providerId) {
return `/providers/${providerId}.png`;
return getProviderIconSrc(providerId);
}
// Custom provider node - rectangle with image + name
@@ -47,8 +47,19 @@ function ProviderNode({ data }) {
className="w-8 h-8 rounded-md flex items-center justify-center shrink-0"
style={{ backgroundColor: `${color}15` }}
>
{!imgError ? (
<img src={imageUrl} alt={label} className="w-6 h-6 rounded-sm object-contain" onError={() => setImgError(true)} />
{imageUrl && !imgError ? (
<img
src={imageUrl}
alt={label}
className="w-6 h-6 rounded-sm object-contain"
loading="lazy"
decoding="async"
onError={() => {
const m = imageUrl?.match(/^\/providers\/([^/]+)\.png$/i);
if (m) markProviderIconMissing(m[1]);
setImgError(true);
}}
/>
) : (
<span className="text-sm font-bold" style={{ color }}>{textIcon}</span>
)}
@@ -86,7 +97,7 @@ function RouterNode({ data }) {
<Handle type="source" position={Position.Left} id="left" className="!bg-transparent !border-0 !w-0 !h-0" />
<Handle type="source" position={Position.Right} id="right" className="!bg-transparent !border-0 !w-0 !h-0" />
<img src="/favicon.svg" alt="9Router" className="w-6 h-6 mr-2" />
<img src="/favicon.svg" alt="9Router" className="w-6 h-6 mr-2" loading="lazy" decoding="async" />
<span className="text-sm font-bold text-primary">9Router</span>
{data.activeCount > 0 && (
<span className="ml-2 px-1.5 py-0.5 rounded-full bg-primary text-white text-xs font-bold">