One toggle wrote both the generation info and the workflow, and they are not the same thing. The generation info is a short block naming what made the image, and is what a site reads to credit the model and the LoRAs. The workflow is the entire graph - every node, every setting, every path on the machine - handed to everyone the picture is sent to. Wanting the first without the second is an ordinary thing to want, and there was no way to ask for it. Two switches now, both on by default, with a test that each of the four combinations writes exactly the chunks it should. Turning the generation info off also skips building it, which skips hashing the checkpoint and every LoRA. The widgets are named in the words a person would use - Save as, Generation info, Workflow, Checkpoint - leaving the input ids, which the API and saved workflows speak, alone. Adding a widget in the middle of a node shifts every saved value after it onto the wrong widget, so this node keeps its values by name too, and knows what the old three-value shape meant: an empty checkpoint name would otherwise have landed on the workflow switch and read as "off". Both paths verified - the old shape migrates to the right widgets, and a save and load with the widget array deliberately reversed comes back correct. The by-name helper is shared with the prompt node rather than written twice. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
53 lines
2.0 KiB
JavaScript
53 lines
2.0 KiB
JavaScript
// Save and restore a node's widget values by name, not by position.
|
|
//
|
|
// ComfyUI writes widgets_values in the order of node.widgets and reads them
|
|
// back the same way. Anything that changes that order between a save and a load
|
|
// - an extension reordering widgets for legibility, or a widget added to the
|
|
// schema in a later version - shifts every value after it onto the wrong
|
|
// widget, silently and permanently. Booleans are the worst of it: the wrong
|
|
// value is still a valid one, so nothing complains.
|
|
//
|
|
// Writing the values by name as well costs one small object in the workflow and
|
|
// makes the order stop mattering.
|
|
|
|
const BY_NAME = "warppipeWidgets";
|
|
|
|
/**
|
|
* @param node the node to protect
|
|
* @param migrate optional (node, widgets_values) => void, called only for a
|
|
* workflow saved before this ran, to place old values on the
|
|
* widgets they belong to now.
|
|
*/
|
|
export function keepWidgetValuesByName(node, migrate) {
|
|
const priorSerialize = node.onSerialize;
|
|
node.onSerialize = function (o) {
|
|
priorSerialize?.call(this, o);
|
|
o[BY_NAME] = Object.fromEntries(
|
|
(node.widgets || []).filter((w) => w.name).map((w) => [w.name, w.value])
|
|
);
|
|
};
|
|
|
|
const priorConfigure = node.onConfigure;
|
|
node.onConfigure = function (o) {
|
|
priorConfigure?.call(this, o);
|
|
const saved = o?.[BY_NAME];
|
|
if (saved) {
|
|
for (const widget of node.widgets || []) {
|
|
if (widget.name in saved) widget.value = saved[widget.name];
|
|
}
|
|
return;
|
|
}
|
|
// Older than this: the values were placed by position, which may have put
|
|
// them on the wrong widgets. Only the node itself knows what the old shape
|
|
// meant, so it says.
|
|
migrate?.(node, o?.widgets_values);
|
|
};
|
|
}
|
|
|
|
/** Set widget values by name, ignoring any this node does not have. */
|
|
export function setWidgetValues(node, values) {
|
|
for (const widget of node.widgets || []) {
|
|
if (Object.hasOwn(values, widget.name)) widget.value = values[widget.name];
|
|
}
|
|
}
|