Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
64 lines
4.1 KiB
JavaScript
64 lines
4.1 KiB
JavaScript
// Custom Nodes: install, update and remove ComfyUI node packs. Three git operations, not a catalogue: you supply the URL.
|
||
// Packs register when ComfyUI starts, so every change ends with "restart needed", never a restart on its own.
|
||
import { composer as c } from "../../composer/composer.js";
|
||
|
||
export const packs = (app) => function mount() {
|
||
const page = c.region.stack({ gap: "md" });
|
||
let nodes = [], root = "", error = "", busy = "", checked = {}, dirty = false;
|
||
const tell = (text) => c.toast.warn({ text });
|
||
|
||
async function refresh() {
|
||
try { const r = await app.api.packs(); nodes = r.nodes || []; root = r.root || ""; error = ""; } catch (err) { nodes = []; error = err.message; }
|
||
draw();
|
||
}
|
||
async function run(key, what, work) {
|
||
if (busy) return;
|
||
busy = key; draw();
|
||
try {
|
||
const res = await work();
|
||
const req = res && res.requirements;
|
||
if (req && req.ran && !req.ok) tell(`${what}, but installing its dependencies failed: ${req.detail || ""}`);
|
||
dirty = true;
|
||
delete checked[key];
|
||
} catch (err) { tell(`${what} failed: ${err.message}`); }
|
||
busy = ""; await refresh();
|
||
}
|
||
const install = async () => {
|
||
const url = await c.modal.prompt({ title: "Add a custom node", label: "Repository git URL", placeholder: "https://github.com/owner/repo", confirmLabel: "Install" }).result;
|
||
if (url && url.trim()) run("__install__", `Installing ${url.trim()}`, () => app.api.pack("install", { url: url.trim() }));
|
||
};
|
||
const check = async () => {
|
||
busy = "__check__"; draw();
|
||
try { checked = (await app.api.pack("check")).checked || {}; } catch (err) { tell(`Could not check for updates: ${err.message}`); }
|
||
busy = ""; draw();
|
||
};
|
||
const remove = async (n) => {
|
||
if (await c.modal.dialogue({ title: "Remove node pack", message: `Delete ${root}/${n.name} and everything in it? This cannot be undone.`, tone: "danger", confirmLabel: "Delete" }).result)
|
||
run(n.name, `Removing ${n.name}`, () => app.api.pack("remove", { name: n.name }));
|
||
};
|
||
|
||
function row(n) {
|
||
const chk = checked[n.name], behind = chk && chk.checked && chk.behind > 0;
|
||
const bits = [n.is_funpack ? "FunPack itself" : null, n.git ? `${n.branch || "?"} · ${n.commit || "?"}` : "not a git checkout",
|
||
chk && chk.checked ? (behind ? `${chk.behind} behind` : chk.ahead > 0 ? `${chk.ahead} ahead of origin` : "up to date") : chk && chk.reason ? `not compared — ${chk.reason}` : null].filter(Boolean);
|
||
const acts = n.is_funpack ? c.text.sm({ text: "" }) : c.toolbar.default({ items: [
|
||
c.button.sm({ label: behind ? `Update (${chk.behind})` : "Update", tone: behind ? "primary" : "ghost", disabled: Boolean(busy) || !n.git, onClick: () => run(n.name, `Updating ${n.name}`, () => app.api.pack("update", { name: n.name })) }),
|
||
c.button.sm({ label: "Remove", tone: "danger", disabled: Boolean(busy), onClick: () => remove(n) })] });
|
||
return c.settingsRow.default({ label: n.name, hint: bits.join(" — "), control: acts });
|
||
}
|
||
function draw() {
|
||
page.set([
|
||
c.toolbar.default({ items: [c.button.sm({ label: "+ Add node", tone: "primary", disabled: Boolean(busy), onClick: install }),
|
||
c.button.sm({ label: busy === "__check__" ? "Checking…" : "Check for updates", tone: "ghost", disabled: Boolean(busy), title: "Fetch each pack's origin and report how far behind it is", onClick: check }),
|
||
busy && busy !== "__check__" ? c.text.sm({ text: busy === "__install__" ? "Cloning and installing…" : `Working on ${busy}…` }) : null].filter(Boolean) }),
|
||
dirty ? c.banner.info({ text: "Node packs are registered when ComfyUI starts, so these changes are not live yet.", action: { label: "Restart ComfyUI", onClick: () => app.maintenance.restart() } }) : null,
|
||
error ? c.banner.warn({ text: `Could not read custom_nodes: ${error}` }) : null,
|
||
!error && !nodes.length ? c.emptyState.default({ icon: "⧉", title: "No node packs", hint: "No custom node packs installed." }) : null,
|
||
...nodes.map(row),
|
||
root ? c.hint.default({ text: root }) : null,
|
||
].filter(Boolean));
|
||
}
|
||
draw(); refresh();
|
||
return { node: page.node, destroy: () => page.node.remove() };
|
||
};
|