From d15a8aa9a26ba88e9359678262033c85b790b6a7 Mon Sep 17 00:00:00 2001 From: Will Miao Date: Sat, 18 Jul 2026 22:57:20 +0800 Subject: [PATCH] fix(workflow): accept non-string widget values and support GlobalSeed node in gen-params (#1026) --- py/routes/handlers/misc_handlers.py | 4 +- static/js/utils/genParamsMapper.js | 43 +++++++++++++++-- static/js/utils/uiHelpers.js | 4 +- tests/frontend/utils/genParamsMapper.test.js | 50 ++++++++++++++++++++ 4 files changed, 93 insertions(+), 8 deletions(-) diff --git a/py/routes/handlers/misc_handlers.py b/py/routes/handlers/misc_handlers.py index a44884ca..3ab19b7e 100644 --- a/py/routes/handlers/misc_handlers.py +++ b/py/routes/handlers/misc_handlers.py @@ -3471,7 +3471,7 @@ class NodeRegistryHandler: status=400, ) - if not isinstance(value, str) or not value: + if value is None or (isinstance(value, str) and not value): return web.json_response( {"success": False, "error": "Missing value parameter"}, status=400 ) @@ -3578,7 +3578,7 @@ class NodeRegistryHandler: status=400, ) - if not isinstance(value, str) or not value: + if value is None or (isinstance(value, str) and not value): return web.json_response( {"success": False, "error": "Missing value parameter"}, status=400 ) diff --git a/static/js/utils/genParamsMapper.js b/static/js/utils/genParamsMapper.js index 2ba38087..55bc7ba1 100644 --- a/static/js/utils/genParamsMapper.js +++ b/static/js/utils/genParamsMapper.js @@ -141,6 +141,20 @@ const PARAM_TO_WIDGET_CANDIDATES = { scheduler: ['scheduler'], }; +// --------------------------------------------------------------------------- +// Node-type-specific widget name overrides. +// Keys are ComfyUI node class names (e.g. "GlobalSeed //Inspire"). +// Values are partial PARAM_TO_WIDGET_CANDIDATES maps; the per-node candidates +// are tried *before* the global ones. Only the params listed here are +// overridden — every other param still uses the global candidates. +// --------------------------------------------------------------------------- +const NODE_TYPE_WIDGET_OVERRIDES = { + // Inspire Pack — Global Seed node stores the seed in a widget named "value" + 'GlobalSeed //Inspire': { + seed: ['value'], + }, +}; + // --------------------------------------------------------------------------- // Parse a combined sampler+scheduler value (space-separated or underscore) // e.g., "Euler a Karras", "DPM++ 2M beta", "er_sde_beta" @@ -235,7 +249,7 @@ function resolveSamplerScheduler(rawValue) { // Find which gen params can be sent to a given node, matching by widget names // Returns array of { widgetName, value } objects // --------------------------------------------------------------------------- -function findMatchingWidgets(nodeWidgetNames, resolvedParams) { +function findMatchingWidgets(nodeWidgetNames, resolvedParams, nodeType) { if (!nodeWidgetNames || !Array.isArray(nodeWidgetNames) || nodeWidgetNames.length === 0) { return []; } @@ -243,6 +257,26 @@ function findMatchingWidgets(nodeWidgetNames, resolvedParams) { const widgetSet = new Set(nodeWidgetNames.map(w => String(w).toLowerCase())); const updates = []; + // Resolve node-type-specific overrides (if any) + const typeOverrides = + nodeType && typeof nodeType === 'string' + ? (NODE_TYPE_WIDGET_OVERRIDES[nodeType] || {}) + : {}; + + /** + * Build the effective candidate list for a parameter: + * type-specific overrides (if any) come first, then the global candidates. + */ + function getCandidates(key) { + const global = PARAM_TO_WIDGET_CANDIDATES[key] || [key]; + const extra = typeOverrides[key]; + if (extra && Array.isArray(extra) && extra.length > 0) { + // Prepend type-specific candidates; keep global as fallback + return [...extra, ...global]; + } + return global; + } + // Simple numeric/string params: seed, steps, cfg const simpleParams = [ { key: 'seed', value: resolvedParams.seed }, @@ -251,7 +285,7 @@ function findMatchingWidgets(nodeWidgetNames, resolvedParams) { ]; for (const { key, value } of simpleParams) { if (value === undefined || value === null || value === '') continue; - const candidates = PARAM_TO_WIDGET_CANDIDATES[key] || [key]; + const candidates = getCandidates(key); for (const candidate of candidates) { if (widgetSet.has(candidate.toLowerCase())) { updates.push({ widgetName: candidate, value }); @@ -262,7 +296,7 @@ function findMatchingWidgets(nodeWidgetNames, resolvedParams) { // Sampler if (resolvedParams.sampler) { - const candidates = PARAM_TO_WIDGET_CANDIDATES.sampler; + const candidates = getCandidates('sampler'); for (const candidate of candidates) { if (widgetSet.has(candidate.toLowerCase())) { updates.push({ widgetName: candidate, value: resolvedParams.sampler }); @@ -273,7 +307,7 @@ function findMatchingWidgets(nodeWidgetNames, resolvedParams) { // Scheduler if (resolvedParams.scheduler) { - const candidates = PARAM_TO_WIDGET_CANDIDATES.scheduler; + const candidates = getCandidates('scheduler'); for (const candidate of candidates) { if (widgetSet.has(candidate.toLowerCase())) { updates.push({ widgetName: candidate, value: resolvedParams.scheduler }); @@ -290,6 +324,7 @@ export { SCHEDULER_SUFFIXES, SCHEDULER_ONLY_VALUES, PARAM_TO_WIDGET_CANDIDATES, + NODE_TYPE_WIDGET_OVERRIDES, parseCombinedSamplerName, resolveSamplerScheduler, findMatchingWidgets, diff --git a/static/js/utils/uiHelpers.js b/static/js/utils/uiHelpers.js index ec9d2441..537d0e40 100644 --- a/static/js/utils/uiHelpers.js +++ b/static/js/utils/uiHelpers.js @@ -1144,8 +1144,8 @@ export async function sendGenParamsToWorkflow(genParams) { const node = targetNodes[nodeKey]; if (!node) continue; - const widgetNames = node.widget_names || []; - const updates = findMatchingWidgets(widgetNames, raw); + const widgetNames = getWidgetNames(node); + const updates = findMatchingWidgets(widgetNames, raw, node.type_name); if (updates.length === 0) { showToast(`Node "${node.title || node.type}" has no matching widgets for these parameters`, {}, 'warning'); diff --git a/tests/frontend/utils/genParamsMapper.test.js b/tests/frontend/utils/genParamsMapper.test.js index b072f2ee..afa5d899 100644 --- a/tests/frontend/utils/genParamsMapper.test.js +++ b/tests/frontend/utils/genParamsMapper.test.js @@ -9,6 +9,7 @@ import { parseCombinedSamplerName, resolveSamplerScheduler, findMatchingWidgets, + NODE_TYPE_WIDGET_OVERRIDES, } from '../../../static/js/utils/genParamsMapper.js'; // --------------------------------------------------------------------------- @@ -243,4 +244,53 @@ describe('findMatchingWidgets', () => { const updates = findMatchingWidgets(['seed', 'steps', 'cfg', 'sampler_name', 'scheduler'], resolved); expect(updates.map(u => u.widgetName)).toEqual(['seed', 'steps', 'cfg', 'sampler_name', 'scheduler']); }); + + // --- node-type-specific overrides --- + it('matches GlobalSeed //Inspire value widget for seed param', () => { + const updates = findMatchingWidgets( + ['value', 'mode', 'action', 'last_seed'], + { seed: 42 }, + 'GlobalSeed //Inspire' + ); + expect(updates).toHaveLength(1); + expect(updates[0]).toEqual({ widgetName: 'value', value: 42 }); + }); + + it('ignores nodeType when it does not match any override entry', () => { + const updates = findMatchingWidgets( + ['value', 'mode', 'action', 'last_seed'], + { seed: 42 }, + 'SomeOtherNode' + ); + expect(updates).toEqual([]); + }); + + it('still falls back to global candidates when override candidates do not match', () => { + // GlobalSeed override does not include steps — should use global candidate "steps" + const updates = findMatchingWidgets( + ['steps', 'cfg', 'sampler_name'], + { steps: 20 }, + 'GlobalSeed //Inspire' + ); + expect(updates).toHaveLength(1); + expect(updates[0]).toEqual({ widgetName: 'steps', value: 20 }); + }); + + it('prefers overrides when both override and global candidates match', () => { + // If a hypothetical node has both "value" and "seed" widgets AND a + // GlobalSeed override, the override candidate "value" should take precedence + const updates = findMatchingWidgets( + ['seed', 'noise_seed', 'value', 'mode'], + { seed: 99 }, + 'GlobalSeed //Inspire' + ); + expect(updates).toHaveLength(1); + expect(updates[0].widgetName).toBe('value'); + }); + + it('omits nodeType argument and still matches via global candidates', () => { + const updates = findMatchingWidgets(['seed', 'steps', 'cfg'], { seed: 7 }); + expect(updates).toHaveLength(1); + expect(updates[0]).toEqual({ widgetName: 'seed', value: 7 }); + }); });