Files
millerlight-ComfyUI-KVTools/web/extensions/extension.js
T
2025-08-05 23:47:38 +02:00

249 lines
6.8 KiB
JavaScript

// KVTools: KVGet-Dropdown (autoload, ES module)
// Files served at /extensions/ComfyUI-KVTools/*
import { app } from "/scripts/app.js";
const EXT = "KVTools.KVGetDropdown.autoload";
let REG = null;
async function loadRegistry() {
if (REG) return REG;
try {
const r = await fetch("/extensions/ComfyUI-KVTools/kv_registry.json", { cache: "no-store" });
if (!r.ok) throw new Error(`HTTP ${r.status}`);
REG = await r.json();
console.log("[KVTools] registry OK");
} catch (e) {
console.warn("[KVTools] registry load failed:", e);
REG = { base_dir: "", files: {} };
}
return REG;
}
function W(node, name) {
return (node.widgets || []).find(w => w.name === name);
}
function setVal(node, w, val) {
if (!w) return;
const v = String(val ?? "").trim();
w.value = v;
const i = (node.widgets || []).indexOf(w);
if (i >= 0 && Array.isArray(node.widgets_values)) node.widgets_values[i] = v;
}
function parseKV(text) {
const out = [];
if (!text) return out;
for (const raw of String(text).split(/\r?\n/)) {
const line = raw.trim();
if (!line || line.startsWith("#")) continue;
const m = line.match(/^\s*([^=:#]+)\s*[:=]\s*(.*)\s*$/);
if (m) out.push(m[1].trim());
}
return Array.from(new Set(out));
}
function parseMaybeJSON(text) {
try {
const o = JSON.parse(text);
if (o && typeof o === "object" && !Array.isArray(o)) return Object.keys(o);
} catch {}
return null;
}
function keysFromInline(up) {
const dataW = W(up, "data");
if (!dataW) return [];
const data = dataW.value ?? "";
const fmtW = W(up, "format") || W(up, "file_format");
const fmt = String(fmtW?.value ?? "auto").toLowerCase();
if (fmt === "json" || fmt === "auto") {
const k = parseMaybeJSON(data);
if (k?.length) return k.sort();
}
return parseKV(data).sort();
}
function upstreamFileName(node) {
const inp = (node.inputs || []).find(i => i && i.name === "store");
if (!inp || inp.link == null || !node.graph) return null;
const link = node.graph.links[inp.link];
if (!link) return null;
const up = node.graph.getNodeById(link.origin_id);
if (!up) return null;
const fw = W(up, "file_name");
if (fw) {
const v = String(fw.value || "").trim();
if (v) return v;
}
const pw = W(up, "path");
if (pw) {
const p = String(pw.value || "").trim();
if (p) {
try {
return p.split(/[\\/]/).pop();
} catch {}
}
}
return null;
}
function keysFromRegistryByFile(fileName) {
if (!fileName) return [];
const files = REG?.files || {};
const entry = files[fileName];
return Array.isArray(entry?.keys) ? entry.keys.slice() : [];
}
function collectKeys(node) {
const f = upstreamFileName(node);
if (f) {
const kk = keysFromRegistryByFile(f);
if (kk.length) return kk;
}
const inp = (node.inputs || []).find(i => i && i.name === "store");
if (inp && inp.link != null && node.graph) {
const link = node.graph.links[inp.link];
if (link) {
const up = node.graph.getNodeById(link.origin_id);
if (up) {
const kk = keysFromInline(up);
if (kk.length) return kk;
}
}
}
return ["(no keys)"];
}
function hideWidget(w) {
if (!w) return;
w.hidden = true;
w.widget_options = { hidden: true };
w.computeSize = () => [0, 0];
}
function ensureAsTypeDefault(node) {
const w = (node.widgets || []).find(w => w.name === "as_type");
if (w && !["string", "int", "float", "bool"].includes(String(w.value))) {
const i = node.widgets.indexOf(w);
w.value = "string";
if (i >= 0 && Array.isArray(node.widgets_values)) node.widgets_values[i] = "string";
}
}
function buildCombo(node, keyW) {
if (node.__kvtools_combo) {
const idx = node.widgets.indexOf(node.__kvtools_combo);
if (idx >= 0) node.widgets.splice(idx, 1);
node.__kvtools_combo = null;
}
const keys = collectKeys(node);
const initial = keys.includes(String(keyW.value)) ? String(keyW.value) : keys[0];
const combo = node.addWidget(
"combo",
"key_select",
initial,
(val) => {
if (val && val !== "(no keys)") setVal(node, keyW, val);
node.setDirtyCanvas(true, true);
},
{ values: keys.slice() }
);
combo.serialize = false;
combo.options = combo.options || {};
combo.options.values = keys.slice();
const wi = node.widgets.indexOf(combo);
if (wi > 0) {
node.widgets.splice(wi, 1);
node.widgets.unshift(combo);
}
if (initial && initial !== "(no keys)") setVal(node, keyW, initial);
hideWidget(keyW);
hideWidget(W(node, "keys_hint"));
node.widgets_changed = true;
if (node.onResize) try { node.onResize(node.size); } catch {}
node.setDirtyCanvas(true, true);
node.__kvtools_combo = combo;
console.log("[KVTools] combo built on node", node.id, "keys:", keys);
}
function isCandidateNode(node) {
const hasStoreInput = !!(node.inputs || []).find(i => i && i.name === "store");
const hasKeyWidget = !!W(node, "key");
return hasStoreInput && hasKeyWidget;
}
function attachNode(node) {
if (!node || node.__kvtools_dropdown_attached) return;
if (!isCandidateNode(node)) return;
loadRegistry().then(() => {
const keyW = W(node, "key");
if (!keyW) {
console.warn("[KVTools] node has no 'key' widget", node.id);
return;
}
// Nur aufbauen, wenn Link gesetzt ist
const wait = setInterval(() => {
const inp = (node.inputs || []).find(i => i.name === "store");
if (inp && inp.link != null) {
clearInterval(wait);
buildCombo(node, keyW);
ensureAsTypeDefault(node);
}
}, 500);
const btn = node.addWidget("button", "KVTools: refresh keys", null, () => {
buildCombo(node, keyW);
ensureAsTypeDefault(node);
});
btn.serialize = false;
const orig = node.onConnectionsChange?.bind(node);
node.onConnectionsChange = function (type, index, connected, link_info) {
buildCombo(node, keyW);
ensureAsTypeDefault(node);
if (orig) orig(type, index, connected, link_info);
};
node.__kvtools_dropdown_attached = true;
console.log("[KVTools] attached to node", node.id, "title:", node.title, "class:", node.comfyClass);
});
}
async function attachAll() {
await loadRegistry(); // <-- Warte auf Registry, bevor du weitermachst
if (!app.graph?._nodes?.length) return;
for (const n of app.graph._nodes) {
attachNode(n);
}
// Beobachte, ob später noch Nodes hinzukommen
const origAddNode = app.graph.add;
app.graph.add = function (node) {
const result = origAddNode.call(this, node);
attachNode(node);
return result;
};
}
app.registerExtension({
name: EXT,
setup() {
console.log("[KVTools] extension registered, waiting for graph…");
const wait = setInterval(() => {
if (app.graph) {
clearInterval(wait);
setTimeout(attachAll, 200);
}
}, 200);
}
});