Add missing zimageturbo_lora_dynamic_v4.js file (Issue #47)

This commit is contained in:
ussoewwin
2026-05-17 05:26:27 +09:00
parent 81b177486f
commit 9743db3801
+204
View File
@@ -0,0 +1,204 @@
import { app } from "../../scripts/app.js";
console.log("★★★ zimageturbo_lora_dynamic_v4.js: Z-Image-Turbo LoRA Stack V4 ★★★");
const HIDDEN_TAG = "tschide";
app.registerExtension({
name: "nunchaku.zimageturbo_lora_dynamic_v4",
async beforeRegisterNodeDef(nodeType, nodeData, app) {
if (nodeData.name === "NunchakuZImageTurboLoraStackV4") {
nodeType["@visibleLoraCount"] = { type: "number", default: 1, min: 1, max: 10, step: 1 };
}
},
nodeCreated(node) {
if (node.comfyClass !== "NunchakuZImageTurboLoraStackV4") return;
if (!node.properties) node.properties = {};
if (node.properties["visibleLoraCount"] === undefined) node.properties["visibleLoraCount"] = 1;
// Immediately hide lora_count widget if it exists
const initialLoraCountWidget = node.widgets?.find(w => w.name === "lora_count");
if (initialLoraCountWidget) {
if (!initialLoraCountWidget.origType) {
initialLoraCountWidget.origType = initialLoraCountWidget.type;
initialLoraCountWidget.origComputeSize = initialLoraCountWidget.computeSize;
}
initialLoraCountWidget.type = HIDDEN_TAG;
initialLoraCountWidget.computeSize = () => [0, -4];
}
node.cachedWidgets = {};
let cacheReady = false;
const initCache = () => {
if (cacheReady) return;
const all = [...node.widgets];
// Cache lora_count widget (required for Python backend, but hidden in UI)
const loraCountWidget = all.find(w => w.name === "lora_count");
if (loraCountWidget) {
node.cachedLoraCount = loraCountWidget;
// Store original properties for restoration if needed
if (!loraCountWidget.origType) {
loraCountWidget.origType = loraCountWidget.type;
loraCountWidget.origComputeSize = loraCountWidget.computeSize;
}
// Hide V4's lora_count widget using HIDDEN_TAG and computeSize
loraCountWidget.type = HIDDEN_TAG;
loraCountWidget.computeSize = () => [0, -4];
}
// Cache toggle_all widget
const toggleAllWidget = all.find(w => w.name === "toggle_all");
if (toggleAllWidget) {
node.cachedToggleAll = toggleAllWidget;
}
for (let i = 1; i <= 10; i++) {
const wEnabled = all.find(w => w.name === `enabled_${i}`);
const wName = all.find(w => w.name === `lora_name_${i}`);
const wStrength = all.find(w => w.name === `lora_strength_${i}`);
if (wEnabled && wName && wStrength) {
node.cachedWidgets[i] = [wEnabled, wName, wStrength];
wEnabled.type = "toggle";
wName.type = "combo";
wStrength.type = "number";
if (wEnabled.computeSize) delete wEnabled.computeSize;
if (wName.computeSize) delete wName.computeSize;
if (wStrength.computeSize) delete wStrength.computeSize;
}
}
cacheReady = true;
};
const ensureControlWidget = () => {
const name = "🔢 LoRA Count";
// Remove old button widgets
for (let i = node.widgets.length - 1; i >= 0; i--) {
const w = node.widgets[i];
if (w.name === "🔢 Set LoRA Count" || w.type === "button") {
node.widgets.splice(i, 1);
}
}
let w = node.widgets.find(x => x.name === name);
if (!w) {
const values = ["1", "2", "3", "4", "5", "6", "7", "8", "9", "10"];
w = node.addWidget("combo", name, "1", (v) => {
const num = parseInt(v);
if (!isNaN(num)) {
node.properties["visibleLoraCount"] = num;
if (node.cachedLoraCount) {
node.cachedLoraCount.value = num;
}
node.updateLoraSlots();
}
}, { values });
}
w.value = node.properties["visibleLoraCount"].toString();
if (node.cachedLoraCount) {
node.cachedLoraCount.value = node.properties["visibleLoraCount"];
}
return w;
};
const ensureToggleAllWidget = () => {
if (!node.cachedToggleAll) return null;
if (!node.cachedToggleAll.origCallback) {
node.cachedToggleAll.origCallback = node.cachedToggleAll.callback;
}
node.cachedToggleAll.callback = (value) => {
if (node.cachedToggleAll.origCallback) {
node.cachedToggleAll.origCallback(value);
}
const count = parseInt(node.properties["visibleLoraCount"] || 1);
for (let i = 1; i <= count; i++) {
const pair = node.cachedWidgets[i];
if (pair && pair[0]) {
pair[0].value = value;
}
}
};
return node.cachedToggleAll;
};
// Plain logic restored
node.updateLoraSlots = function () {
if (!cacheReady) initCache();
const count = parseInt(this.properties["visibleLoraCount"] || 1);
const controlWidget = ensureControlWidget();
this.widgets = [controlWidget];
if (node.cachedLoraCount) {
node.cachedLoraCount.type = HIDDEN_TAG;
node.cachedLoraCount.computeSize = () => [0, -4];
node.cachedLoraCount.value = count;
this.widgets.push(node.cachedLoraCount);
}
const toggleAllWidget = ensureToggleAllWidget();
if (toggleAllWidget) {
this.widgets.push(toggleAllWidget);
}
for (let i = 1; i <= count; i++) {
const pair = this.cachedWidgets[i];
if (pair && pair.length >= 3) {
const wEnabled = pair[0];
const wName = pair[1];
const wStrength = pair[2];
// Restore standard widgets
wEnabled.type = "toggle";
wName.type = "combo";
wStrength.type = "number";
// Clear custom compute sizes if any
if (wEnabled.computeSize) delete wEnabled.computeSize;
if (wName.computeSize) delete wName.computeSize;
if (wStrength.computeSize) delete wStrength.computeSize;
this.widgets.push(wEnabled);
this.widgets.push(wName);
this.widgets.push(wStrength);
}
}
const HEADER_H = 60;
// 3 lines per slot (Toggle ~20 + Name ~26 + Strength ~26 + margins)
const SLOT_H = 80;
const TOGGLE_ALL_H = toggleAllWidget ? 30 : 0;
const PADDING = 20;
const targetH = HEADER_H + TOGGLE_ALL_H + (count * SLOT_H) + PADDING;
this.setSize([this.size[0], targetH]);
if (app.canvas) app.canvas.setDirty(true, true);
};
node.onPropertyChanged = function (property, value) {
if (property === "visibleLoraCount") {
const w = this.widgets.find(x => x.name === "🔢 LoRA Count");
if (w) w.value = value.toString();
this.updateLoraSlots();
}
};
const origOnConfigure = node.onConfigure;
node.onConfigure = function () {
if (origOnConfigure) origOnConfigure.apply(this, arguments);
setTimeout(() => node.updateLoraSlots(), 100);
};
setTimeout(() => {
initCache();
node.updateLoraSlots();
}, 100);
}
});