Files

104 lines
3.8 KiB
JavaScript

// Minimax H3 Multi Guide (mo-akkakk): one frame index per guide.
// - frame_idx_N is shown when guide_N is connected (frame_idx_0 always); audio_frame_idx_N when audio_N is connected.
// - Each frame index belongs to its connection: when a guide is disconnected, ComfyUI moves the guides below it
// up one slot, and their frame indexes move up with them.
// Widgets are only hidden, never removed or reordered, so saved workflows keep their values.
import { app } from "../../scripts/app.js";
const NODE = "MiniMaxH3MultiGuide";
const GROUPS = [
{ slot: /^(?:guides\.)?guide_(\d+)$/, widget: (i) => `frame_idx_${i}`, wre: /^frame_idx_(\d+)$/, always: 0 },
{ slot: /^(?:audios\.)?audio_(\d+)$/, widget: (i) => `audio_frame_idx_${i}`, wre: /^audio_frame_idx_(\d+)$/, always: -1 },
];
function setup(node) {
if (node.__h3MultiGuideUi) return;
node.__h3MultiGuideUi = true;
const byLink = new Map(); // link id -> frame index of the guide on that link
const find = (name) => node.widgets?.find((w) => w.name === name);
const links = (g) => {
const out = [];
for (const inp of node.inputs ?? []) {
const m = g.slot.exec(inp.name);
if (m && inp.link != null) out.push([Number(m[1]), inp.link]);
}
return out;
};
const remember = () => {
const live = new Set();
for (const g of GROUPS) {
for (const [i, l] of links(g)) {
live.add(l);
const w = find(g.widget(i));
if (w) byLink.set(l, w.value);
}
}
for (const l of [...byLink.keys()]) if (!live.has(l)) byLink.delete(l);
};
const apply = () => {
const used = GROUPS.map(() => new Set());
GROUPS.forEach((g, k) => {
for (const [i, l] of links(g)) {
used[k].add(i);
// a guide that moved to another slot brings its frame index along
const w = find(g.widget(i));
if (w && byLink.has(l) && w.value !== byLink.get(l)) w.value = byLink.get(l);
}
});
remember();
let changed = false;
for (const w of node.widgets ?? []) {
GROUPS.forEach((g, k) => {
const m = g.wre.exec(w.name);
if (!m) return;
const i = Number(m[1]);
const hide = !(i === g.always || used[k].has(i));
if (w.hidden !== hide) {
w.hidden = hide;
changed = true;
}
});
}
if (changed) {
const size = node.computeSize();
node.setSize([Math.max(node.size?.[0] ?? 0, size[0]), size[1]]);
}
(node.graph ?? app.graph)?.setDirtyCanvas?.(true, true);
};
// typing a frame index updates the guide it belongs to
for (const w of node.widgets ?? []) {
if (!GROUPS.some((g) => g.wre.test(w.name))) continue;
const cb = w.callback;
w.callback = function (...args) {
const r = cb?.apply(this, args);
remember();
return r;
};
}
const onConnectionsChange = node.onConnectionsChange;
node.onConnectionsChange = function (...args) {
const r = onConnectionsChange?.apply(this, args);
setTimeout(apply, 0);
return r;
};
// saved workflows restore links and widget values after the node is created
const onConfigure = node.onConfigure;
node.onConfigure = function (...args) {
const r = onConfigure?.apply(this, args);
byLink.clear();
setTimeout(() => { remember(); apply(); }, 0);
return r;
};
apply();
}
app.registerExtension({
name: "mo-akkakk.MiniMaxH3.MultiGuideUI",
async nodeCreated(node) {
if (node.comfyClass === NODE) setup(node);
},
});