Preserve missing-node widget values
This commit is contained in:
@@ -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;
|
||||
}
|
||||
Reference in New Issue
Block a user