Files
JeyzerMC-comfyui-flakes/web/flake_stack.js
T
Mehdi Chaid 13da022e49 fix: revert widget detach in #216 — keep model_family in node.widgets (#223)
Closes #223. The previous #216 fix (commit 2db236a) detached the
familyWidget from node.widgets to hide it. That introduced three
regressions:

1. Generation failed with "Required input is missing: model_family".
   The widget was the only carrier for the required model_family value
   in the serialized prompt; once detached, prompt validation rejected
   every job.

2. Node sizes were reset on reload. _updateFamilyVisibility called
   node.setSize(node.computeSize()) on every state transition, and on
   workflow load the 200ms poll fired a transition after onConfigure
   restored the saved size — overriding it.

3. Flake Stack items vanished on reload. Detaching the widget shifted
   the widget value order in the saved JSON: only [flakes_json_value]
   was serialized, but the reload code (correctly) still has the
   widget present, so the saved JSON string landed in model_family
   and flakes_json kept its empty default.

Fix:
- Keep familyWidget permanently in node.widgets so serialization is
  stable and the required input is always sent.
- Hide it visually by stacking computeSize=[0,-4], type="hidden",
  hidden=true, AND a no-op draw override (covers ComfyUI frontends
  that don't honor type/hidden for canvas-drawn COMBO widgets).
- Drop node.setSize from the visibility transition (rely on
  setDirtyCanvas to repaint).
- Defer the initial sync to setTimeout(0) so onConfigure has hydrated
  link state before we read it.
- Add _recoverShiftedWidgets in flake_stack.js onConfigure for
  FlakeStack and FlakeCombo: if model_family looks like JSON and
  flakes_json is at its empty default, swap them so workflows saved
  by the broken version recover their stack contents on next open.
2026-05-20 23:38:41 -04:00

219 lines
9.3 KiB
JavaScript

import { app } from "../../scripts/app.js";
import { setupFlakeWidget } from "./widgets/flake-stack.js";
import { setupFlakeComboWidget } from "./widgets/flake-combo.js";
import { setupFlakeModelPresetWidget } from "./widgets/flake-model-preset.js";
import { setupFlakeModelComboWidget } from "./widgets/flake-model-combo.js";
import { setupFlakeDataSplitSelect, setupIntoFlakeDataSelect, DEFAULT_SPLIT_PINS, DEFAULT_INTO_PINS } from "./widgets/flake-data-select.js";
import { setupPreviewFlakeDataWidget } from "./widgets/flake-preview.js";
import { setupFlakeGenerateWidget } from "./widgets/flake-generate.js";
import "./queue.js";
function removeHiddenInputs(node, names) {
for (let i = node.inputs.length - 1; i >= 0; i--) {
if (names.includes(node.inputs[i].name)) {
node.inputs.splice(i, 1);
}
}
}
function setDefaultSize(node, minWidth) {
const size = node.computeSize();
if (size[0] < minWidth) {
node.setSize([minWidth, size[1]]);
} else {
node.setSize(size);
}
}
// Recover from workflows saved by the broken v0.1.x where model_family was
// detached from node.widgets when flake_data was connected — that shifted
// the widget value order so onConfigure assigned `flakes_json` (a JSON
// string) into model_family and left flakes_json at its default "[]".
//
// Heuristic: if model_family looks like JSON ("[" or "{") and flakes_json
// is empty/default, swap them. Same for `preset` on the model nodes (the
// preset is stored as a yaml-relative name, never starts with [).
function _recoverShiftedWidgets(node, otherWidgetName) {
if (!node.widgets) return;
const family = node.widgets.find(w => w.name === "model_family");
const other = node.widgets.find(w => w.name === otherWidgetName);
if (!family || !other) return;
const fv = String(family.value || "");
if (!fv.startsWith("[") && !fv.startsWith("{")) return;
const ov = String(other.value || "");
const otherIsEmpty = ov === "" || ov === "[]" || ov === "Select a preset..." || ov === "No model preset is selected";
if (!otherIsEmpty) return;
other.value = fv;
family.value = "SDXL/Base";
}
app.registerExtension({
name: "comfyui-flakes.FlakeStack",
async beforeRegisterNodeDef(nodeType, nodeData) {
if (nodeData.name === "FlakeStack") {
const origOnNodeCreated = nodeType.prototype.onNodeCreated;
nodeType.prototype.onNodeCreated = function () {
const r = origOnNodeCreated?.apply(this, arguments);
setupFlakeWidget(this);
removeHiddenInputs(this, ["model_family", "flakes_json"]);
if (!this._configured) {
setDefaultSize(this, 340);
}
return r;
};
const origOnConfigure = nodeType.prototype.onConfigure;
nodeType.prototype.onConfigure = function () {
const r = origOnConfigure?.apply(this, arguments);
this._configured = true;
removeHiddenInputs(this, ["model_family", "flakes_json"]);
_recoverShiftedWidgets(this, "flakes_json");
this._flakes_render?.();
return r;
};
}
if (nodeData.name === "FlakeModelPreset") {
const origOnNodeCreated = nodeType.prototype.onNodeCreated;
nodeType.prototype.onNodeCreated = function () {
const r = origOnNodeCreated?.apply(this, arguments);
setupFlakeModelPresetWidget(this);
removeHiddenInputs(this, ["model_family", "preset"]);
if (!this._configured) {
setDefaultSize(this, 300);
}
return r;
};
const origOnConfigure = nodeType.prototype.onConfigure;
nodeType.prototype.onConfigure = function () {
const r = origOnConfigure?.apply(this, arguments);
this._configured = true;
removeHiddenInputs(this, ["model_family", "preset"]);
this._preset_render?.();
return r;
};
}
if (nodeData.name === "FlakeCombo") {
const origOnNodeCreated = nodeType.prototype.onNodeCreated;
nodeType.prototype.onNodeCreated = function () {
const r = origOnNodeCreated?.apply(this, arguments);
setupFlakeComboWidget(this);
removeHiddenInputs(this, ["model_family", "flakes_json"]);
if (!this._configured) {
setDefaultSize(this, 340);
}
return r;
};
const origOnConfigure = nodeType.prototype.onConfigure;
nodeType.prototype.onConfigure = function () {
const r = origOnConfigure?.apply(this, arguments);
this._configured = true;
removeHiddenInputs(this, ["model_family", "flakes_json"]);
_recoverShiftedWidgets(this, "flakes_json");
this._combo_render?.();
return r;
};
}
if (nodeData.name === "FlakeModelCombo") {
const origOnNodeCreated = nodeType.prototype.onNodeCreated;
nodeType.prototype.onNodeCreated = function () {
const r = origOnNodeCreated?.apply(this, arguments);
setupFlakeModelComboWidget(this);
removeHiddenInputs(this, ["model_family", "preset"]);
if (!this._configured) {
setDefaultSize(this, 300);
}
return r;
};
const origOnConfigure = nodeType.prototype.onConfigure;
nodeType.prototype.onConfigure = function () {
const r = origOnConfigure?.apply(this, arguments);
this._configured = true;
removeHiddenInputs(this, ["model_family", "preset"]);
this._model_combo_render?.();
return r;
};
}
if (nodeData.name === "FlakeDataSplitSelect") {
const origOnNodeCreated = nodeType.prototype.onNodeCreated;
nodeType.prototype.onNodeCreated = function () {
const r = origOnNodeCreated?.apply(this, arguments);
setupFlakeDataSplitSelect(this);
if (!this._configured) {
setDefaultSize(this, 260);
}
return r;
};
const origOnConfigure = nodeType.prototype.onConfigure;
nodeType.prototype.onConfigure = function () {
const r = origOnConfigure?.apply(this, arguments);
this._configured = true;
if (!this.properties) this.properties = {};
// Migrate old property name
if (!this.properties._split_pins && this.properties._selected_split_pins) {
this.properties._split_pins = this.properties._selected_split_pins;
}
this._splitPinUpdate?.();
return r;
};
}
if (nodeData.name === "IntoFlakeDataSelect") {
const origOnNodeCreated = nodeType.prototype.onNodeCreated;
nodeType.prototype.onNodeCreated = function () {
const r = origOnNodeCreated?.apply(this, arguments);
setupIntoFlakeDataSelect(this);
if (!this._configured) {
setDefaultSize(this, 260);
}
return r;
};
const origOnConfigure = nodeType.prototype.onConfigure;
nodeType.prototype.onConfigure = function () {
const r = origOnConfigure?.apply(this, arguments);
this._configured = true;
if (!this.properties) this.properties = {};
// Migrate old property name
if (!this.properties._into_pins && this.properties._selected_into_pins) {
this.properties._into_pins = this.properties._selected_into_pins;
}
setupIntoFlakeDataSelect(this);
this._intoPinUpdate?.();
return r;
};
}
if (nodeData.name === "PreviewFlakeData") {
const origOnNodeCreated = nodeType.prototype.onNodeCreated;
nodeType.prototype.onNodeCreated = function () {
const r = origOnNodeCreated?.apply(this, arguments);
setupPreviewFlakeDataWidget(this);
if (!this._configured) {
setDefaultSize(this, 260);
}
return r;
};
}
if (nodeData.name === "FlakeGenerate") {
const origOnNodeCreated = nodeType.prototype.onNodeCreated;
nodeType.prototype.onNodeCreated = function () {
const r = origOnNodeCreated?.apply(this, arguments);
setupFlakeGenerateWidget(this);
if (!this._configured) {
setDefaultSize(this, 300);
}
return r;
};
const origOnConfigure = nodeType.prototype.onConfigure;
nodeType.prototype.onConfigure = function () {
const r = origOnConfigure?.apply(this, arguments);
this._configured = true;
return r;
};
}
},
});