Files

167 lines
5.3 KiB
JavaScript

/**
* String Extractor Widget for ComfyUI v4.4
*
* Debug confirms: beforeQueued and serializeValue interleave per batch item.
* Queue works correctly for sending values to Python.
*
* Problem 4 root cause: ComfyUI embeds workflow JSON in each image using
* app.graph.serialize() called once per prompt BEFORE serializeValue runs.
* So iw.value at serialize time is always the original value (1), not the
* per-item value.
*
* Fix: hook app.graph.serialize to temporarily set iw.value to the front
* of the queue (without shifting) so the serialized workflow captures the
* correct index for that batch item.
*/
import { app } from "../../../scripts/app.js";
const NODE_W = 380;
const C = {
green: "#4ade80",
text: "#94a3b8",
textDim: "#334155",
red: "#f87171",
};
app.registerExtension({
name: "Axces2000.StringExtractor",
async beforeRegisterNodeDef(nodeType, nodeData) {
if (nodeData.name !== "StringExtractor") return;
const onNodeCreated = nodeType.prototype.onNodeCreated;
nodeType.prototype.onNodeCreated = function () {
onNodeCreated?.apply(this, arguments);
const node = this;
node.serialize_widgets = true;
function getTextWidget() { return node.widgets?.find(w => w.name === "text"); }
function getIndexWidget() { return node.widgets?.find(w => w.name === "index"); }
function getModeWidget() { return node.widgets?.find(w => w.name === "mode"); }
function getLines() {
return String(getTextWidget()?.value ?? "").split("\n").filter(l => l.trim() !== "");
}
function getIndexValue() { return Number(getIndexWidget()?.value ?? 0); }
function getModeValue() { return getModeWidget()?.value ?? "Keep"; }
function isTextConnected() {
return node.inputs?.find(i => i.name === "text")?.link != null;
}
const iw = node.widgets?.find(w => w.name === "index");
if (iw) {
iw._sendQueue = [];
iw.serializeValue = async function () {
if (this._sendQueue && this._sendQueue.length > 0) {
const v = this._sendQueue.shift();
this.value = v;
node.setDirtyCanvas(true, false);
return v;
}
return this.value;
};
}
const controlWidget = node.addWidget(
"SE_CONTROL", "_se_control", null, () => {}, { serialize: false }
);
controlWidget.computeSize = () => [NODE_W, 22];
controlWidget.beforeQueued = function () {
const iw = getIndexWidget();
const mode = getModeValue();
const lines = getLines();
const count = lines.length;
if (!iw || count === 0 || mode === "Keep") return;
if (iw._nextSend == null) {
iw._nextSend = Number(iw.value);
}
const idx = iw._nextSend;
if (idx === 0) return;
// Peek: set iw.value to what this item will send, so that
// graph.serialize() (called right after beforeQueued) captures
// the correct index in the workflow JSON embedded in the image.
iw.value = idx;
iw._sendQueue.push(idx);
if (mode === "Increment") {
iw._nextSend = (idx % count) + 1;
} else {
iw._nextSend = Math.floor(Math.random() * count) + 1;
}
node.setDirtyCanvas(true, false);
};
controlWidget.draw = function (ctx, node, width, y) {
const px = 10;
const lines = getLines();
const count = lines.length;
const idx = getIndexValue();
const modeVal = getModeValue();
const textConn = isTextConnected();
ctx.font = "9px monospace"; ctx.textAlign = "left";
if (textConn) {
ctx.fillStyle = C.textDim;
ctx.fillText("text connected via input ↑", px + 2, y + 13);
} else if (count === 0) {
ctx.fillStyle = C.textDim;
ctx.fillText("No lines entered", px + 2, y + 13);
} else if (idx === 0) {
ctx.fillStyle = C.text;
ctx.fillText(
`${count} line${count !== 1 ? "s" : ""} · index 0 → full text passthrough`,
px + 2, y + 13
);
} else if (idx > count) {
ctx.fillStyle = C.red;
ctx.fillText(
`index ${idx} out of range (max ${count}) → full text, reset to 0`,
px + 2, y + 13
);
} else {
const prev = lines[idx - 1];
const preview = prev.slice(0, 34) + (prev.length > 34 ? "…" : "");
ctx.fillStyle = C.green;
ctx.fillText(
`Line ${idx}/${count} · "${preview}" [${modeVal}]`,
px + 2, y + 13
);
}
ctx.textAlign = "left";
};
node.size = [NODE_W + 20, node.computeSize()[1]];
};
},
setup() {
const origQueuePrompt = app.queuePrompt.bind(app);
app.queuePrompt = async function (number, batchCount) {
const result = await origQueuePrompt(number, batchCount);
for (const node of app.graph._nodes) {
if (node.type !== "StringExtractor") continue;
const iw = node.widgets?.find(w => w.name === "index");
if (!iw || iw._nextSend == null) continue;
iw.value = iw._nextSend;
iw._nextSend = null;
iw._sendQueue = [];
node.setDirtyCanvas(true, false);
}
return result;
};
},
});