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:
@@ -39,6 +39,7 @@ export default function ClaudeToolCard({
|
||||
const [showManualConfigModal, setShowManualConfigModal] = useState(false);
|
||||
const [customBaseUrl, setCustomBaseUrl] = useState("");
|
||||
const [ccFilterNaming, setCcFilterNaming] = useState(false);
|
||||
const [exaMcpEnabled, setExaMcpEnabled] = useState(false);
|
||||
const hasInitializedModels = useRef(false);
|
||||
|
||||
const getConfigStatus = () => {
|
||||
@@ -58,15 +59,17 @@ export default function ClaudeToolCard({
|
||||
}, [apiKeys, selectedApiKey]);
|
||||
|
||||
useEffect(() => {
|
||||
if (initialStatus) setClaudeStatus(initialStatus);
|
||||
if (initialStatus) {
|
||||
setClaudeStatus(initialStatus);
|
||||
setExaMcpEnabled(!!initialStatus.exaMcpEnabled);
|
||||
}
|
||||
}, [initialStatus]);
|
||||
|
||||
useEffect(() => {
|
||||
if (isExpanded && !claudeStatus) {
|
||||
checkClaudeStatus();
|
||||
if (isExpanded) {
|
||||
if (!claudeStatus) checkClaudeStatus();
|
||||
fetchModelAliases();
|
||||
}
|
||||
if (isExpanded) fetchModelAliases();
|
||||
}, [isExpanded]);
|
||||
|
||||
useEffect(() => {
|
||||
@@ -123,6 +126,7 @@ export default function ClaudeToolCard({
|
||||
const res = await fetch("/api/cli-tools/claude-settings");
|
||||
const data = await res.json();
|
||||
setClaudeStatus(data);
|
||||
setExaMcpEnabled(!!data.exaMcpEnabled);
|
||||
} catch (error) {
|
||||
setClaudeStatus({ installed: false, error: error.message });
|
||||
} finally {
|
||||
@@ -162,12 +166,12 @@ export default function ClaudeToolCard({
|
||||
const res = await fetch("/api/cli-tools/claude-settings", {
|
||||
method: "POST",
|
||||
headers: { "Content-Type": "application/json" },
|
||||
body: JSON.stringify({ env }),
|
||||
body: JSON.stringify({ env, exaMcpEnabled }),
|
||||
});
|
||||
const data = await res.json();
|
||||
if (res.ok) {
|
||||
setMessage({ type: "success", text: "Settings applied successfully!" });
|
||||
setClaudeStatus(prev => ({ ...prev, hasBackup: true, settings: { ...prev?.settings, env } }));
|
||||
setClaudeStatus(prev => ({ ...prev, hasBackup: true, settings: { ...prev?.settings, env }, exaMcpEnabled }));
|
||||
} else {
|
||||
setMessage({ type: "error", text: data.error || "Failed to apply settings" });
|
||||
}
|
||||
@@ -188,6 +192,7 @@ export default function ClaudeToolCard({
|
||||
setMessage({ type: "success", text: "Settings reset successfully!" });
|
||||
tool.defaultModels.forEach((model) => onModelMappingChange(model.alias, model.defaultValue || ""));
|
||||
setSelectedApiKey("");
|
||||
setExaMcpEnabled(false);
|
||||
} else {
|
||||
setMessage({ type: "error", text: data.error || "Failed to reset settings" });
|
||||
}
|
||||
@@ -231,7 +236,7 @@ export default function ClaudeToolCard({
|
||||
<div className="flex items-start justify-between gap-3 hover:cursor-pointer sm:items-center" onClick={onToggle}>
|
||||
<div className="flex min-w-0 items-center gap-3">
|
||||
<div className="size-8 flex items-center justify-center shrink-0">
|
||||
<Image src="/providers/claude.png" alt={tool.name} width={32} height={32} className="size-8 object-contain rounded-lg" sizes="32px" onError={(e) => { e.target.style.display = "none"; }} />
|
||||
<Image src="/providers/claude.png" alt={tool.name} width={32} height={32} className="size-8 object-contain rounded-lg" sizes="32px" onError={(e) => { e.target.style.display = "none"; }} loading="lazy" decoding="async" />
|
||||
</div>
|
||||
<div className="min-w-0">
|
||||
<div className="flex min-w-0 flex-wrap items-center gap-2">
|
||||
@@ -352,6 +357,19 @@ export default function ClaudeToolCard({
|
||||
</Tooltip>
|
||||
</label>
|
||||
</div>
|
||||
|
||||
{/* Exa MCP — ~/.claude.json mcpServers (not settings.json) */}
|
||||
<div className="grid grid-cols-1 gap-1.5 sm:grid-cols-[8rem_auto_1fr_auto] sm:items-center sm:gap-2">
|
||||
<span className="text-xs font-semibold text-text-main sm:text-right sm:text-sm">Web Search</span>
|
||||
<span className="material-symbols-outlined hidden text-text-muted text-[14px] sm:inline">arrow_forward</span>
|
||||
<label className="flex items-center gap-1.5 cursor-pointer select-none">
|
||||
<input type="checkbox" checked={exaMcpEnabled} onChange={(e) => setExaMcpEnabled(e.target.checked)} className="w-3.5 h-3.5 accent-primary cursor-pointer" />
|
||||
<span className="text-xs text-text-muted">Exa MCP</span>
|
||||
<Tooltip text="Injects Exa MCP into ~/.claude.json so non-Claude models gain web search. Restart Claude Code after Apply.">
|
||||
<span className="material-symbols-outlined text-text-muted text-[14px] cursor-help">info</span>
|
||||
</Tooltip>
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{message && (
|
||||
@@ -377,7 +395,9 @@ export default function ClaudeToolCard({
|
||||
</div>
|
||||
)}
|
||||
|
||||
<ModelSelectModal isOpen={modalOpen} onClose={() => setModalOpen(false)} onSelect={handleModelSelect} selectedModel={currentEditingAlias ? modelMappings[currentEditingAlias] : null} activeProviders={activeProviders} modelAliases={modelAliases} title={`Select model for ${currentEditingAlias}`} />
|
||||
{modalOpen && (
|
||||
<ModelSelectModal isOpen={modalOpen} onClose={() => setModalOpen(false)} onSelect={handleModelSelect} selectedModel={currentEditingAlias ? modelMappings[currentEditingAlias] : null} activeProviders={activeProviders} modelAliases={modelAliases} title={`Select model for ${currentEditingAlias}`} />
|
||||
)}
|
||||
|
||||
<ManualConfigModal
|
||||
isOpen={showManualConfigModal}
|
||||
|
||||
Reference in New Issue
Block a user