Persist output types across save/load via hidden output_types widget

Types were lost on workflow reload because only key names were stored.
Now both keys and types are saved in hidden widgets and restored by
onConfigure, so colored connector dots persist without needing Refresh.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
2026-02-23 16:16:50 +01:00
parent f5e242950d
commit a747f86daa
2 changed files with 20 additions and 11 deletions

View File

@@ -106,6 +106,7 @@ class JSONLoaderDynamic:
}, },
"optional": { "optional": {
"output_keys": ("STRING", {"default": ""}), "output_keys": ("STRING", {"default": ""}),
"output_types": ("STRING", {"default": ""}),
}, },
} }

View File

@@ -11,11 +11,10 @@ app.registerExtension({
nodeType.prototype.onNodeCreated = function () { nodeType.prototype.onNodeCreated = function () {
origOnNodeCreated?.apply(this, arguments); origOnNodeCreated?.apply(this, arguments);
// Hide the output_keys widget (managed internally by JS) // Hide internal widgets (managed by JS)
const okWidget = this.widgets?.find(w => w.name === "output_keys"); for (const name of ["output_keys", "output_types"]) {
if (okWidget) { const w = this.widgets?.find(w => w.name === name);
okWidget.type = "hidden"; if (w) { w.type = "hidden"; w.computeSize = () => [0, -4]; }
okWidget.computeSize = () => [0, -4];
} }
// Remove all 32 default outputs from Python RETURN_TYPES // Remove all 32 default outputs from Python RETURN_TYPES
@@ -42,9 +41,11 @@ app.registerExtension({
); );
const { keys, types } = await resp.json(); const { keys, types } = await resp.json();
// Update output_keys widget for Python to read at execution time // Store keys and types in hidden widgets for persistence
const okWidget = this.widgets?.find(w => w.name === "output_keys"); const okWidget = this.widgets?.find(w => w.name === "output_keys");
if (okWidget) okWidget.value = keys.join(","); if (okWidget) okWidget.value = keys.join(",");
const otWidget = this.widgets?.find(w => w.name === "output_types");
if (otWidget) otWidget.value = types.join(",");
// Build a map of current output names to slot indices // Build a map of current output names to slot indices
const oldSlots = {}; const oldSlots = {};
@@ -105,20 +106,27 @@ app.registerExtension({
nodeType.prototype.onConfigure = function (info) { nodeType.prototype.onConfigure = function (info) {
origOnConfigure?.apply(this, arguments); origOnConfigure?.apply(this, arguments);
const okWidget = this.widgets?.find(w => w.name === "output_keys"); // Hide internal widgets
if (okWidget) { for (const name of ["output_keys", "output_types"]) {
okWidget.type = "hidden"; const w = this.widgets?.find(w => w.name === name);
okWidget.computeSize = () => [0, -4]; if (w) { w.type = "hidden"; w.computeSize = () => [0, -4]; }
} }
const okWidget = this.widgets?.find(w => w.name === "output_keys");
const otWidget = this.widgets?.find(w => w.name === "output_types");
const keys = okWidget?.value const keys = okWidget?.value
? okWidget.value.split(",").filter(k => k.trim()) ? okWidget.value.split(",").filter(k => k.trim())
: []; : [];
const types = otWidget?.value
? otWidget.value.split(",")
: [];
// On load, LiteGraph already restored serialized outputs with links. // On load, LiteGraph already restored serialized outputs with links.
// Just rename to match keys (preserves links) and trim excess. // Rename and set types to match stored state (preserves links).
for (let i = 0; i < this.outputs.length && i < keys.length; i++) { for (let i = 0; i < this.outputs.length && i < keys.length; i++) {
this.outputs[i].name = keys[i].trim(); this.outputs[i].name = keys[i].trim();
if (types[i]) this.outputs[i].type = types[i];
} }
// Remove any extra outputs beyond the key count // Remove any extra outputs beyond the key count