Preserve missing-node widget values

This commit is contained in:
2026-07-04 21:34:16 +02:00
parent dd3e51301c
commit 2603697fe9
4 changed files with 235 additions and 18 deletions
+121
View File
@@ -0,0 +1,121 @@
/** A widget the user converted into an input slot -- its value lives on the input, not the widget. */
export const isConvertedWidget = (w) => w?.type === "converted-widget" || w?.type === "hidden";
const hasStoredValue = (v) => v !== undefined;
const hasReportableValue = (v) => v !== undefined && v !== null && v !== "";
function shapeWidgets(shape) {
if (Array.isArray(shape?.widgets)) return shape.widgets;
return (shape?.widgetNames || []).map((name) => ({ name }));
}
export function serializedWidgetValues(node) {
const values = node?.last_serialization?.widgets_values;
return Array.isArray(values) ? values : [];
}
function findTargetWidgetIndex(widgets, name, used) {
return widgets.findIndex((w, i) => !used.has(i) && !isConvertedWidget(w) && w.name === name);
}
/**
* Plan widget value transfers.
*
* Installed nodes can match by widget name (plus curated widget remaps). Missing
* nodes often have no live widget objects, only ComfyUI's ordered
* last_serialization.widgets_values array, so they may also opt into same-index
* fallback.
*/
export function planWidgetTransfers(node, shape, rule = {}, options = {}) {
const targetWidgets = shapeWidgets(shape);
const liveWidgets = Array.isArray(node?.widgets) ? node.widgets : [];
const serializedValues = serializedWidgetValues(node);
const allowSerializedIndexFallback = !!options.allowSerializedIndexFallback;
const usedTargets = new Set();
const mappedSources = new Set();
const pendingWarns = [];
const pendingWarnSources = new Set();
const wMap = [];
const warns = [];
liveWidgets.forEach((w, sourceIndex) => {
if (w?.name == null || isConvertedWidget(w)) return;
const want = rule?.widgets?.[w.name] ?? w.name;
const targetIndex = findTargetWidgetIndex(targetWidgets, want, usedTargets);
if (targetIndex >= 0) {
usedTargets.add(targetIndex);
mappedSources.add(sourceIndex);
wMap.push({ from: w.name, fromIndex: sourceIndex, to: targetWidgets[targetIndex].name, toIndex: targetIndex });
} else if (hasReportableValue(w.value)) {
pendingWarns.push({ sourceIndex, message: `widget "${w.name}" value not carried` });
pendingWarnSources.add(sourceIndex);
}
});
if (allowSerializedIndexFallback) {
serializedValues.forEach((value, sourceIndex) => {
if (!hasStoredValue(value) || mappedSources.has(sourceIndex)) return;
const target = targetWidgets[sourceIndex];
if (target && !usedTargets.has(sourceIndex) && !isConvertedWidget(target) && target.name != null) {
usedTargets.add(sourceIndex);
mappedSources.add(sourceIndex);
wMap.push({ fromIndex: sourceIndex, toIndex: sourceIndex, to: target.name, serialized: true });
} else if (hasReportableValue(value) && !pendingWarnSources.has(sourceIndex)) {
warns.push(`serialized widget #${sourceIndex + 1} value not carried`);
}
});
}
pendingWarns.forEach((warn) => {
if (!mappedSources.has(warn.sourceIndex)) warns.push(warn.message);
});
return { wMap, warns };
}
function widgetByName(widgets, name) {
return widgets.find((w) => w?.name === name);
}
function sourceWidgetValue(node, transfer) {
const widgets = Array.isArray(node?.widgets) ? node.widgets : [];
const values = serializedWidgetValues(node);
if (transfer.serialized && Number.isInteger(transfer.fromIndex)) {
if (transfer.fromIndex < values.length && hasStoredValue(values[transfer.fromIndex])) return values[transfer.fromIndex];
}
if (transfer.from != null) {
const widget = widgetByName(widgets, transfer.from);
if (hasStoredValue(widget?.value)) return widget.value;
}
if (Number.isInteger(transfer.fromIndex)) {
const widget = widgets[transfer.fromIndex];
if (hasStoredValue(widget?.value)) return widget.value;
if (transfer.fromIndex < values.length && hasStoredValue(values[transfer.fromIndex])) return values[transfer.fromIndex];
}
return undefined;
}
function targetWidget(node, transfer) {
const widgets = Array.isArray(node?.widgets) ? node.widgets : [];
if (transfer.to != null) {
const widget = widgetByName(widgets, transfer.to);
if (widget) return widget;
}
if (Number.isInteger(transfer.toIndex)) return widgets[transfer.toIndex];
return null;
}
export function applyWidgetTransfers(source, target, transfers) {
let applied = 0;
(transfers || []).forEach((transfer) => {
const value = sourceWidgetValue(source, transfer);
const widget = targetWidget(target, transfer);
if (!widget || !hasStoredValue(value)) return;
widget.value = value;
try { widget.callback?.(widget.value); } catch {}
applied++;
});
return applied;
}