import { app } from "../../scripts/app.js"; // Bar chart with nodes icon const STATS_ICON = ``; app.registerExtension({ name: "comfyui.nodes_stats", async setup() { try { const { ComfyButton } = await import( "../../scripts/ui/components/button.js" ); const btn = new ComfyButton({ icon: "bar-chart-2", content: "Node Stats", tooltip: "Show node and package usage statistics", action: () => showStatsDialog(), classList: "comfyui-button comfyui-menu-mobile-collapse", }); app.menu?.settingsGroup.element.before(btn.element); } catch (e) { console.log( "[nodes-stats] New menu API unavailable, falling back to legacy menu", e ); const btn = document.createElement("button"); btn.innerHTML = STATS_ICON; btn.title = "Node Stats"; btn.onclick = () => showStatsDialog(); btn.style.cssText = "display:flex;align-items:center;justify-content:center;padding:6px;background:none;border:none;cursor:pointer;color:var(--input-text,#ddd);"; const menu = document.querySelector(".comfy-menu"); if (menu) { menu.append(btn); } } }, }); async function showStatsDialog() { let data; try { const resp = await fetch("/nodes-stats/packages"); if (!resp.ok) { alert("Failed to load node stats: HTTP " + resp.status); return; } data = await resp.json(); if (!Array.isArray(data)) { alert("Failed to load node stats: unexpected response format"); return; } } catch (e) { alert("Failed to load node stats: " + e.message); return; } // Remove existing dialog if any const existing = document.getElementById("nodes-stats-dialog"); if (existing) existing.remove(); const overlay = document.createElement("div"); overlay.id = "nodes-stats-dialog"; overlay.style.cssText = "position:fixed;top:0;left:0;width:100%;height:100%;background:rgba(0,0,0,0.6);z-index:10000;display:flex;align-items:center;justify-content:center;"; overlay.addEventListener("click", (e) => { if (e.target === overlay) overlay.remove(); }); const dialog = document.createElement("div"); dialog.style.cssText = "background:#1e1e1e;color:#ddd;border-radius:8px;padding:24px;max-width:800px;width:90%;max-height:85vh;overflow-y:auto;font-family:monospace;font-size:13px;"; const custom = data.filter((p) => p.package !== "__builtin__"); const safeToRemove = custom.filter((p) => p.status === "safe_to_remove"); const considerRemoving = custom.filter((p) => p.status === "consider_removing"); const unusedNew = custom.filter((p) => p.status === "unused_new"); const used = custom.filter((p) => p.status === "used"); let html = `
| Package | Nodes | Used | Executions | Last Used | |
|---|---|---|---|---|---|
| ${hasNodes ? "▶" : " "} | ${escapeHtml(pkg.package)} | ${pkg.total_nodes} | ${pkg.used_nodes}/${pkg.total_nodes} | ${pkg.total_executions} | ${lastSeen} |