From 093a343b9c72f8d2e19444c5f807dd04df0f0e71 Mon Sep 17 00:00:00 2001 From: Vito Sansevero Date: Wed, 7 Oct 2026 11:12:25 -0700 Subject: [PATCH] fix: read the matching output of multi-output settings nodes KSampler settings linked to a node like SamplerCombo all resolved to its first value, so steps, cfg and sampler showed the sampler name. Scalar resolution now prefers the linked node's input with the same field name. --- tests/js/comfy-metadata.test.js | 19 +++++++++++++++++++ web/js/comfy-metadata.js | 23 +++++++++++++++-------- 2 files changed, 34 insertions(+), 8 deletions(-) diff --git a/tests/js/comfy-metadata.test.js b/tests/js/comfy-metadata.test.js index 2fe064a..8be12db 100644 --- a/tests/js/comfy-metadata.test.js +++ b/tests/js/comfy-metadata.test.js @@ -147,6 +147,25 @@ test("custom sampler graphs (Flux: SamplerCustomAdvanced + CFGGuider) resolve ev ); }); +test("multi-output settings nodes (SamplerCombo) map each field to its own value", () => { + const prompt = { + 5: { + class_type: "KSampler", + inputs: { + seed: ["162", 0], steps: ["165", 2], cfg: ["165", 3], sampler_name: ["165", 0], + positive: ["6", 0], negative: ["7", 0], model: ["4", 0], + }, + }, + 4: { class_type: "CheckpointLoaderSimple", inputs: { ckpt_name: "m.safetensors" } }, + 6: { class_type: "CLIPTextEncode", inputs: { text: "a cat" } }, + 7: { class_type: "CLIPTextEncode", inputs: { text: "blurry" } }, + 162: { class_type: "SeedHistory", inputs: { seed: 1495646836, seed_history_ui: "" } }, + 165: { class_type: "SamplerCombo", inputs: { sampler_name: "euler_ancestral", scheduler: "normal", steps: 50, cfg: 7 } }, + }; + const r = extractGenerationParams({ prompt }); + assert.deepEqual([r.seed, r.steps, r.cfgScale, r.sampler], [1495646836, 50, 7, "euler_ancestral"]); +}); + test("workflow-only images account for the hidden control_after_generate widget", () => { const workflow = { nodes: [ diff --git a/web/js/comfy-metadata.js b/web/js/comfy-metadata.js index 51f6993..497de09 100644 --- a/web/js/comfy-metadata.js +++ b/web/js/comfy-metadata.js @@ -81,13 +81,20 @@ return [id, node]; } - /** Resolve a scalar input, following a link to a primitive node if needed. */ - function resolveScalar(ctx, value, seen = new Set()) { + /** + * Resolve a scalar input, following a link to a primitive node if needed. + * Multi-output settings nodes (e.g. SamplerCombo) expose several values, so an + * input with the same `name` as the requested field wins over the first scalar. + */ + function resolveScalar(ctx, value, name, seen = new Set()) { if (!isLink(value)) return value; const [, node] = enter(ctx, value, seen); if (!node) return undefined; + if (name && Object.prototype.hasOwnProperty.call(node.inputs, name)) { + return resolveScalar(ctx, node.inputs[name], name, seen); + } for (const v of Object.values(node.inputs)) { - const resolved = resolveScalar(ctx, v, seen); + const resolved = resolveScalar(ctx, v, name, seen); if (resolved !== undefined && typeof resolved !== "object") return resolved; } return undefined; @@ -99,7 +106,7 @@ const [, node] = enter(ctx, link, seen); if (!node) return undefined; const key = keys.find((k) => k in node.inputs); - return key === undefined ? undefined : resolveScalar(ctx, node.inputs[key], seen); + return key === undefined ? undefined : resolveScalar(ctx, node.inputs[key], key, seen); } /** Resolve a string input; text-producing nodes may be chained (concat, primitives). */ @@ -179,12 +186,12 @@ negativePrompt: traceConditioning(ctx, inputs.negative, "negative"), checkpoint: traceModelName(ctx, inputs.model) || findAnyModelName(nodes), seed: - resolveScalar(ctx, inputs.seed ?? inputs.noise_seed) ?? + resolveScalar(ctx, inputs.seed ?? inputs.noise_seed, "seed" in inputs ? "seed" : "noise_seed") ?? fromCustom(custom && custom.inputs.noise, ["noise_seed", "seed"]), - steps: resolveScalar(ctx, inputs.steps) ?? fromCustom(custom && custom.inputs.sigmas, ["steps"]), - cfgScale: resolveScalar(ctx, inputs.cfg), + steps: resolveScalar(ctx, inputs.steps, "steps") ?? fromCustom(custom && custom.inputs.sigmas, ["steps"]), + cfgScale: resolveScalar(ctx, inputs.cfg, "cfg"), sampler: - resolveScalar(ctx, inputs.sampler_name) ?? + resolveScalar(ctx, inputs.sampler_name, "sampler_name") ?? fromCustom(custom && custom.inputs.sampler, ["sampler_name"]), }; }