import { app } from "../../scripts/app.js"; const EXTENSION = "ethanfel.prompt_builder.seed_control"; const NODE_NAME = "SxCPSeedControl"; const SEED_AXES = [ "category", "subcategory", "content", "person", "scene", "pose", "role", "expression", "composition", ]; function widget(node, name) { return node.widgets?.find((w) => w.name === name); } function isSeedControlNode(node) { return node?._sxcpSeedControlNode || node?.comfyClass === NODE_NAME || node?.type === NODE_NAME; } function resizeNode(node) { const size = node.computeSize?.(); if (size) node.setSize?.(size); app.graph?.setDirtyCanvas(true, true); } function randomSeed() { if (globalThis.crypto?.getRandomValues) { const values = new Uint32Array(1); globalThis.crypto.getRandomValues(values); return values[0]; } return Math.floor(Math.random() * 0x100000000); } function setWidgetValue(node, w, value) { if (!w) return; const oldValue = w.value; w.value = value; w.callback?.(value, app.canvas, node); node.onWidgetChanged?.(w.name, value, oldValue, w); } function materializeNode(node) { const changes = []; for (const axis of SEED_AXES) { const modeWidget = widget(node, `${axis}_seed_mode`); const seedWidget = widget(node, `${axis}_seed`); if (modeWidget?.value !== "random" || !seedWidget) continue; const seed = randomSeed(); changes.push({ node, modeWidget, previousMode: modeWidget.value, seedWidget, previousSeed: seedWidget.value, seed, }); setWidgetValue(node, seedWidget, seed); setWidgetValue(node, modeWidget, "fixed"); } if (changes.length) resizeNode(node); return changes; } function materializeAllForQueue() { const changes = []; for (const node of app.graph?._nodes || []) { if (isSeedControlNode(node)) changes.push(...materializeNode(node)); } if (changes.length) app.graph?.setDirtyCanvas(true, true); return changes; } function restoreRandomModes(changes) { for (const change of changes) { setWidgetValue(change.node, change.modeWidget, change.previousMode); } if (changes.length) app.graph?.setDirtyCanvas(true, true); } function lockRandomSeeds(node) { const changes = materializeNode(node); if (!changes.length) { alert("No random seed modes to lock."); return; } resizeNode(node); } function setupNode(node) { node._sxcpSeedControlNode = true; if (!node._sxcpLockRandomSeedsButton) { node._sxcpLockRandomSeedsButton = node.addWidget("button", "Lock Random Seeds Now", null, () => lockRandomSeeds(node)); } resizeNode(node); } app.registerExtension({ name: EXTENSION, async setup() { if (app._sxcpSeedControlQueuePatched) return; const originalQueuePrompt = app.queuePrompt; if (!originalQueuePrompt) return; app._sxcpSeedControlQueuePatched = true; app.queuePrompt = async function () { const randomSeedChanges = materializeAllForQueue(); try { return await originalQueuePrompt.apply(this, arguments); } finally { restoreRandomModes(randomSeedChanges); } }; }, async beforeRegisterNodeDef(nodeType, nodeData) { if (nodeData.name !== NODE_NAME) return; const onNodeCreated = nodeType.prototype.onNodeCreated; nodeType.prototype.onNodeCreated = function () { const result = onNodeCreated?.apply(this, arguments); setupNode(this); return result; }; const onConfigure = nodeType.prototype.onConfigure; nodeType.prototype.onConfigure = function () { const result = onConfigure?.apply(this, arguments); queueMicrotask(() => setupNode(this)); return result; }; }, });