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:
decoluaandCursor committed 2026-07-17 12:12:20 +07:00
1 parent 68566f53dc
commit ccb0842d0a
50 files changed
+612 -392

No files matched your search

@@ -38,15 +38,10 @@ export default function AntigravityToolCard({
}, [initialStatus]);
useEffect(() => {
if (isExpanded && !status) {
fetchStatus();
loadSavedMappings();
fetchModelAliases();
}
if (isExpanded) {
loadSavedMappings();
fetchModelAliases();
}
if (!isExpanded) return;
if (!status) fetchStatus();
loadSavedMappings();
fetchModelAliases();
}, [isExpanded]);
const loadSavedMappings = async () => {
@@ -243,6 +238,8 @@ export default function AntigravityToolCard({
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">
@@ -467,15 +464,17 @@ export default function AntigravityToolCard({
</Modal>
{/* Model Select Modal */}
<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}`}
/>
)}
</Card>
);
}
@@ -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}
@@ -30,11 +30,10 @@ export default function ClineToolCard({ tool, isExpanded, onToggle, baseUrl, api
}, [initialStatus]);
useEffect(() => {
if (isExpanded && !status) {
checkStatus();
if (isExpanded) {
if (!status) checkStatus();
fetchModelAliases();
}
if (isExpanded) fetchModelAliases();
}, [isExpanded]);
useEffect(() => {
@@ -157,7 +156,7 @@ export default function ClineToolCard({ tool, isExpanded, onToggle, baseUrl, api
<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/cline.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/cline.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">
@@ -280,15 +279,17 @@ export default function ClineToolCard({ tool, isExpanded, onToggle, baseUrl, api
</div>
)}
<ModelSelectModal
isOpen={modalOpen}
onClose={() => setModalOpen(false)}
onSelect={(model) => { setSelectedModel(model.value); setModalOpen(false); }}
selectedModel={selectedModel}
activeProviders={activeProviders}
modelAliases={modelAliases}
title="Select Model for Cline"
/>
{modalOpen && (
<ModelSelectModal
isOpen={modalOpen}
onClose={() => setModalOpen(false)}
onSelect={(model) => { setSelectedModel(model.value); setModalOpen(false); }}
selectedModel={selectedModel}
activeProviders={activeProviders}
modelAliases={modelAliases}
title="Select Model for Cline"
/>
)}
<ManualConfigModal
isOpen={showManualConfigModal}
@@ -34,11 +34,10 @@ export default function CodexToolCard({ tool, isExpanded, onToggle, baseUrl, api
}, [initialStatus]);
useEffect(() => {
if (isExpanded && !codexStatus) {
checkCodexStatus();
if (isExpanded) {
if (!codexStatus) checkCodexStatus();
fetchModelAliases();
}
if (isExpanded) fetchModelAliases();
}, [isExpanded]);
const fetchModelAliases = async () => {
@@ -199,7 +198,7 @@ model = "${effectiveSubagentModel}"
<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/codex.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/codex.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">
@@ -371,25 +370,29 @@ model = "${effectiveSubagentModel}"
</div>
)}
<ModelSelectModal
isOpen={modalOpen}
onClose={() => setModalOpen(false)}
onSelect={handleModelSelect}
selectedModel={selectedModel}
activeProviders={activeProviders}
modelAliases={modelAliases}
title="Select Model for Codex"
/>
{modalOpen && (
<ModelSelectModal
isOpen={modalOpen}
onClose={() => setModalOpen(false)}
onSelect={handleModelSelect}
selectedModel={selectedModel}
activeProviders={activeProviders}
modelAliases={modelAliases}
title="Select Model for Codex"
/>
)}
<ModelSelectModal
isOpen={subagentModalOpen}
onClose={() => setSubagentModalOpen(false)}
onSelect={(model) => { setSubagentModel(model.value); setSubagentModalOpen(false); }}
selectedModel={subagentModel}
activeProviders={activeProviders}
modelAliases={modelAliases}
title="Select Subagent Model for Codex"
/>
{subagentModalOpen && (
<ModelSelectModal
isOpen={subagentModalOpen}
onClose={() => setSubagentModalOpen(false)}
onSelect={(model) => { setSubagentModel(model.value); setSubagentModalOpen(false); }}
selectedModel={subagentModel}
activeProviders={activeProviders}
modelAliases={modelAliases}
title="Select Subagent Model for Codex"
/>
)}
<ManualConfigModal
isOpen={showManualConfigModal}
@@ -36,11 +36,10 @@ export default function CopilotToolCard({ tool, isExpanded, onToggle, baseUrl, a
}, [initialStatus]);
useEffect(() => {
if (isExpanded && !status) {
checkStatus();
if (isExpanded) {
if (!status) checkStatus();
fetchModelAliases();
}
if (isExpanded) fetchModelAliases();
}, [isExpanded]);
// Pre-fill from existing config
@@ -184,7 +183,7 @@ export default function CopilotToolCard({ tool, isExpanded, onToggle, baseUrl, a
<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/copilot.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/copilot.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">
@@ -290,27 +289,29 @@ export default function CopilotToolCard({ tool, isExpanded, onToggle, baseUrl, a
</div>
)}
<ModelSelectModal
isOpen={modalOpen}
onClose={() => {
setModalOpen(false);
saveModels(selectedModelsRef.current);
}}
onSelect={(model) => {
if (!selectedModels.includes(model.value)) {
setSelectedModels([...selectedModels, model.value]);
}
}}
onDeselect={(model) => {
setSelectedModels(selectedModels.filter(m => m !== model.value));
}}
selectedModel={null}
activeProviders={activeProviders}
modelAliases={modelAliases}
addedModelValues={selectedModels}
closeOnSelect={false}
title="Add Model for GitHub Copilot"
/>
{modalOpen && (
<ModelSelectModal
isOpen={modalOpen}
onClose={() => {
setModalOpen(false);
saveModels(selectedModelsRef.current);
}}
onSelect={(model) => {
if (!selectedModels.includes(model.value)) {
setSelectedModels([...selectedModels, model.value]);
}
}}
onDeselect={(model) => {
setSelectedModels(selectedModels.filter(m => m !== model.value));
}}
selectedModel={null}
activeProviders={activeProviders}
modelAliases={modelAliases}
addedModelValues={selectedModels}
closeOnSelect={false}
title="Add Model for GitHub Copilot"
/>
)}
<ManualConfigModal
isOpen={showManualConfigModal}
@@ -249,7 +249,7 @@ export default function CoworkToolCard({
<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={tool.image} 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={tool.image} 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">
@@ -514,27 +514,31 @@ export default function CoworkToolCard({
configs={getManualConfigs()}
/>
<ComboFormModal
isOpen={comboModalOpen}
combo={null}
onClose={() => setComboModalOpen(false)}
onSave={handleCreateCombo}
activeProviders={activeProviders}
forcePrefix="claude-"
title="Create Cowork Combo"
/>
{comboModalOpen && (
<ComboFormModal
isOpen={comboModalOpen}
combo={null}
onClose={() => setComboModalOpen(false)}
onSave={handleCreateCombo}
activeProviders={activeProviders}
forcePrefix="claude-"
title="Create Cowork Combo"
/>
)}
<ModelSelectModal
isOpen={modelSelectOpen}
onClose={() => setModelSelectOpen(false)}
onSelect={handleAddModel}
onDeselect={handleRemoveModel}
activeProviders={activeProviders}
modelAliases={modelAliases}
title="Select Cowork Model"
addedModelValues={selectedModels}
closeOnSelect={false}
/>
{modelSelectOpen && (
<ModelSelectModal
isOpen={modelSelectOpen}
onClose={() => setModelSelectOpen(false)}
onSelect={handleAddModel}
onDeselect={handleRemoveModel}
activeProviders={activeProviders}
modelAliases={modelAliases}
title="Select Cowork Model"
addedModelValues={selectedModels}
closeOnSelect={false}
/>
)}
<McpMarketplaceModal
isOpen={marketplaceOpen}
@@ -58,11 +58,10 @@ export default function DeepSeekTuiToolCard({
}, [initialStatus]);
useEffect(() => {
if (isExpanded && !deepseekStatus) {
checkStatus();
if (isExpanded) {
if (!deepseekStatus) checkStatus();
fetchModelAliases();
}
if (isExpanded) fetchModelAliases();
}, [isExpanded]);
const fetchModelAliases = async () => {
@@ -187,7 +186,7 @@ model = "${selectedModel || "provider/model-id"}"
<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={tool.image || "/providers/deepseek-tui.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={tool.image || "/providers/deepseek-tui.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">
@@ -317,15 +316,17 @@ model = "${selectedModel || "provider/model-id"}"
</div>
)}
<ModelSelectModal
isOpen={modalOpen}
onClose={() => setModalOpen(false)}
onSelect={handleModelSelect}
selectedModel={selectedModel}
activeProviders={activeProviders}
modelAliases={modelAliases}
title="Select Model for DeepSeek TUI"
/>
{modalOpen && (
<ModelSelectModal
isOpen={modalOpen}
onClose={() => setModalOpen(false)}
onSelect={handleModelSelect}
selectedModel={selectedModel}
activeProviders={activeProviders}
modelAliases={modelAliases}
title="Select Model for DeepSeek TUI"
/>
)}
<ManualConfigModal
isOpen={showManualConfigModal}
@@ -2,6 +2,7 @@
import { useState } from "react";
import { Card, ModelSelectModal } from "@/shared/components";
import { getProviderIconSrc, markProviderIconMissing } from "@/shared/utils/providerIcon";
import { useCopyToClipboard } from "@/shared/hooks/useCopyToClipboard";
import Image from "next/image";
import ApiKeySelect from "./ApiKeySelect";
@@ -217,21 +218,32 @@ export default function DefaultToolCard({ toolId, tool, isExpanded, onToggle, ba
className="size-8 object-contain rounded-lg"
sizes="32px"
onError={(e) => { e.target.style.display = "none"; }}
loading="lazy"
decoding="async"
/>
);
}
if (tool.icon) {
return <span className="material-symbols-outlined text-xl" style={{ color: tool.color }}>{tool.icon}</span>;
}
const iconSrc = getProviderIconSrc(toolId);
if (!iconSrc) {
return <span className="text-xs font-bold" style={{ color: tool.color }}>{(toolId || "?").slice(0, 2).toUpperCase()}</span>;
}
return (
<Image
src={`/providers/${toolId}.png`}
src={iconSrc}
alt={tool.name}
width={32}
height={32}
className="size-8 object-contain rounded-lg"
sizes="32px"
onError={(e) => { e.target.style.display = "none"; }}
onError={(e) => {
markProviderIconMissing(toolId);
e.target.style.display = "none";
}}
loading="lazy"
decoding="async"
/>
);
};
@@ -257,14 +269,16 @@ export default function DefaultToolCard({ toolId, tool, isExpanded, onToggle, ba
</div>
)}
<ModelSelectModal
isOpen={showModelModal}
onClose={() => setShowModelModal(false)}
onSelect={handleSelectModel}
selectedModel={modelValue}
activeProviders={activeProviders}
title="Select Model"
/>
{showModelModal && (
<ModelSelectModal
isOpen={showModelModal}
onClose={() => setShowModelModal(false)}
onSelect={handleSelectModel}
selectedModel={modelValue}
activeProviders={activeProviders}
title="Select Model"
/>
)}
</Card>
);
}
@@ -60,11 +60,10 @@ export default function DroidToolCard({
}, [initialStatus]);
useEffect(() => {
if (isExpanded && !droidStatus) {
checkDroidStatus();
if (isExpanded) {
if (!droidStatus) checkDroidStatus();
fetchModelAliases();
}
if (isExpanded) fetchModelAliases();
}, [isExpanded]);
const fetchModelAliases = async () => {
@@ -225,7 +224,7 @@ export default function DroidToolCard({
<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/droid.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/droid.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">
@@ -389,15 +388,17 @@ export default function DroidToolCard({
</div>
)}
<ModelSelectModal
isOpen={modalOpen}
onClose={() => setModalOpen(false)}
onSelect={handleModelSelect}
selectedModel={null}
activeProviders={activeProviders}
modelAliases={modelAliases}
title="Select Model for Factory Droid"
/>
{modalOpen && (
<ModelSelectModal
isOpen={modalOpen}
onClose={() => setModalOpen(false)}
onSelect={handleModelSelect}
selectedModel={null}
activeProviders={activeProviders}
modelAliases={modelAliases}
title="Select Model for Factory Droid"
/>
)}
<ManualConfigModal
isOpen={showManualConfigModal}
@@ -59,11 +59,10 @@ export default function GrokBuildToolCard({
}, [initialStatus]);
useEffect(() => {
if (isExpanded && !grokStatus) {
checkStatus();
if (isExpanded) {
if (!grokStatus) checkStatus();
fetchModelAliases();
}
if (isExpanded) fetchModelAliases();
}, [isExpanded]);
const fetchModelAliases = async () => {
@@ -203,6 +202,8 @@ api_key = "${keyToUse}"
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">
@@ -366,15 +367,17 @@ api_key = "${keyToUse}"
</div>
)}
<ModelSelectModal
isOpen={modalOpen}
onClose={() => setModalOpen(false)}
onSelect={handleModelSelect}
selectedModel={selectedModel}
activeProviders={activeProviders}
modelAliases={modelAliases}
title="Select Model for Grok Build"
/>
{modalOpen && (
<ModelSelectModal
isOpen={modalOpen}
onClose={() => setModalOpen(false)}
onSelect={handleModelSelect}
selectedModel={selectedModel}
activeProviders={activeProviders}
modelAliases={modelAliases}
title="Select Model for Grok Build"
/>
)}
<ManualConfigModal
isOpen={showManualConfigModal}
@@ -58,11 +58,10 @@ export default function HermesToolCard({
}, [initialStatus]);
useEffect(() => {
if (isExpanded && !hermesStatus) {
checkStatus();
if (isExpanded) {
if (!hermesStatus) checkStatus();
fetchModelAliases();
}
if (isExpanded) fetchModelAliases();
}, [isExpanded]);
const fetchModelAliases = async () => {
@@ -185,7 +184,7 @@ export default function HermesToolCard({
<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/hermes.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/hermes.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">
@@ -296,15 +295,17 @@ export default function HermesToolCard({
</div>
)}
<ModelSelectModal
isOpen={modalOpen}
onClose={() => setModalOpen(false)}
onSelect={handleModelSelect}
selectedModel={selectedModel}
activeProviders={activeProviders}
modelAliases={modelAliases}
title="Select Model for Hermes Agent"
/>
{modalOpen && (
<ModelSelectModal
isOpen={modalOpen}
onClose={() => setModalOpen(false)}
onSelect={handleModelSelect}
selectedModel={selectedModel}
activeProviders={activeProviders}
modelAliases={modelAliases}
title="Select Model for Hermes Agent"
/>
)}
<ManualConfigModal
isOpen={showManualConfigModal}
@@ -56,11 +56,10 @@ export default function JcodeToolCard({
}, [initialStatus]);
useEffect(() => {
if (isExpanded && !jcodeStatus) {
checkJcodeStatus();
if (isExpanded) {
if (!jcodeStatus) checkJcodeStatus();
fetchModelAliases();
}
if (isExpanded) fetchModelAliases();
}, [isExpanded]);
const fetchModelAliases = async () => {
@@ -215,7 +214,7 @@ id = "${selectedModel || "cc/claude-opus-4-7"}"`;
<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={tool.image || "/providers/jcode.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={tool.image || "/providers/jcode.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">
@@ -359,15 +358,17 @@ id = "${selectedModel || "cc/claude-opus-4-7"}"`;
</div>
)}
<ModelSelectModal
isOpen={modalOpen}
onClose={() => setModalOpen(false)}
onSelect={handleModelSelect}
selectedModel={selectedModel}
activeProviders={activeProviders}
modelAliases={modelAliases}
title="Select Model for jcode"
/>
{modalOpen && (
<ModelSelectModal
isOpen={modalOpen}
onClose={() => setModalOpen(false)}
onSelect={handleModelSelect}
selectedModel={selectedModel}
activeProviders={activeProviders}
modelAliases={modelAliases}
title="Select Model for jcode"
/>
)}
<ManualConfigModal
isOpen={showManualConfigModal}
@@ -30,11 +30,10 @@ export default function KiloToolCard({ tool, isExpanded, onToggle, baseUrl, apiK
}, [initialStatus]);
useEffect(() => {
if (isExpanded && !status) {
checkStatus();
if (isExpanded) {
if (!status) checkStatus();
fetchModelAliases();
}
if (isExpanded) fetchModelAliases();
}, [isExpanded]);
const fetchModelAliases = async () => {
@@ -144,7 +143,7 @@ export default function KiloToolCard({ tool, isExpanded, onToggle, baseUrl, apiK
<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/kilocode.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/kilocode.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">
@@ -254,15 +253,17 @@ export default function KiloToolCard({ tool, isExpanded, onToggle, baseUrl, apiK
</div>
)}
<ModelSelectModal
isOpen={modalOpen}
onClose={() => setModalOpen(false)}
onSelect={(model) => { setSelectedModel(model.value); setModalOpen(false); }}
selectedModel={selectedModel}
activeProviders={activeProviders}
modelAliases={modelAliases}
title="Select Model for Kilo Code"
/>
{modalOpen && (
<ModelSelectModal
isOpen={modalOpen}
onClose={() => setModalOpen(false)}
onSelect={(model) => { setSelectedModel(model.value); setModalOpen(false); }}
selectedModel={selectedModel}
activeProviders={activeProviders}
modelAliases={modelAliases}
title="Select Model for Kilo Code"
/>
)}
<ManualConfigModal
isOpen={showManualConfigModal}
@@ -22,6 +22,8 @@ export default function MitmLinkCard({ tool }) {
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">
@@ -143,6 +143,8 @@ export default function MitmToolCard({
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">
@@ -304,15 +306,17 @@ export default function MitmToolCard({
)}
{/* Model Select Modal */}
<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}`}
/>
)}
</>
);
}
@@ -57,11 +57,10 @@ export default function OpenClawToolCard({
}, [initialStatus]);
useEffect(() => {
if (isExpanded && !openclawStatus) {
checkOpenclawStatus();
if (isExpanded) {
if (!openclawStatus) checkOpenclawStatus();
fetchModelAliases();
}
if (isExpanded) fetchModelAliases();
}, [isExpanded]);
const fetchModelAliases = async () => {
@@ -233,7 +232,7 @@ export default function OpenClawToolCard({
<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/openclaw.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/openclaw.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">
@@ -367,15 +366,17 @@ export default function OpenClawToolCard({
</div>
)}
<ModelSelectModal
isOpen={modalOpen}
onClose={() => setModalOpen(false)}
onSelect={handleModelSelect}
selectedModel={selectedModel}
activeProviders={activeProviders}
modelAliases={modelAliases}
title="Select Model for Open Claw"
/>
{modalOpen && (
<ModelSelectModal
isOpen={modalOpen}
onClose={() => setModalOpen(false)}
onSelect={handleModelSelect}
selectedModel={selectedModel}
activeProviders={activeProviders}
modelAliases={modelAliases}
title="Select Model for Open Claw"
/>
)}
<ManualConfigModal
isOpen={showManualConfigModal}
@@ -41,11 +41,10 @@ export default function OpenCodeToolCard({ tool, isExpanded, onToggle, baseUrl,
}, [initialStatus]);
useEffect(() => {
if (isExpanded && !status) {
checkStatus();
if (isExpanded) {
if (!status) checkStatus();
fetchModelAliases();
}
if (isExpanded) fetchModelAliases();
}, [isExpanded]);
// Sync models from existing config
@@ -222,7 +221,7 @@ export default function OpenCodeToolCard({ tool, isExpanded, onToggle, baseUrl,
<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/opencode.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/opencode.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">
@@ -452,42 +451,46 @@ export default function OpenCodeToolCard({ tool, isExpanded, onToggle, baseUrl,
</div>
)}
<ModelSelectModal
isOpen={modalOpen}
onClose={() => {
setModalOpen(false);
saveModels(selectedModelsRef.current);
}}
onSelect={(model) => {
if (!selectedModels.includes(model.value)) {
setSelectedModels([...selectedModels, model.value]);
if (!activeModel) setActiveModel(model.value);
}
}}
onDeselect={(model) => {
const remaining = selectedModels.filter(m => m !== model.value);
setSelectedModels(remaining);
if (activeModel === model.value) {
setActiveModel(remaining[0] || "");
}
}}
selectedModel={null}
activeProviders={activeProviders}
modelAliases={modelAliases}
addedModelValues={selectedModels}
closeOnSelect={false}
title="Add Model for OpenCode"
/>
{modalOpen && (
<ModelSelectModal
isOpen={modalOpen}
onClose={() => {
setModalOpen(false);
saveModels(selectedModelsRef.current);
}}
onSelect={(model) => {
if (!selectedModels.includes(model.value)) {
setSelectedModels([...selectedModels, model.value]);
if (!activeModel) setActiveModel(model.value);
}
}}
onDeselect={(model) => {
const remaining = selectedModels.filter(m => m !== model.value);
setSelectedModels(remaining);
if (activeModel === model.value) {
setActiveModel(remaining[0] || "");
}
}}
selectedModel={null}
activeProviders={activeProviders}
modelAliases={modelAliases}
addedModelValues={selectedModels}
closeOnSelect={false}
title="Add Model for OpenCode"
/>
)}
<ModelSelectModal
isOpen={subagentModalOpen}
onClose={() => setSubagentModalOpen(false)}
onSelect={(model) => { setSubagentModel(model.value); setSubagentModalOpen(false); }}
selectedModel={subagentModel}
activeProviders={activeProviders}
modelAliases={modelAliases}
title="Select Subagent Model for OpenCode"
/>
{subagentModalOpen && (
<ModelSelectModal
isOpen={subagentModalOpen}
onClose={() => setSubagentModalOpen(false)}
onSelect={(model) => { setSubagentModel(model.value); setSubagentModalOpen(false); }}
selectedModel={subagentModel}
activeProviders={activeProviders}
modelAliases={modelAliases}
title="Select Subagent Model for OpenCode"
/>
)}
<ManualConfigModal
isOpen={showManualConfigModal}
@@ -21,7 +21,7 @@ export default function ToolSummaryCard({ toolId, tool, status }) {
<div className="flex items-center gap-3">
<div className="size-8 flex items-center justify-center shrink-0">
{tool.image ? (
<Image src={tool.image} 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={tool.image} 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" />
) : tool.icon ? (
<span className="material-symbols-outlined text-[28px]" style={{ color: tool.color }}>{tool.icon}</span>
) : null}