Movie Editor: fix COMBO inputs mistaken as connections; auto-wire output on input source set

nodes.py: "COMBO" (V1/new-style dropdown widget) is a string so it passed
the WIDGET_PRIMITIVES exclusion check and was incorrectly returned as a
graph-connection socket in connection_inputs() and _all_input_types(). Added
_WIDGET_TYPES = WIDGET_PRIMITIVES | {"COMBO"} and use it in both functions.
Old-style list combos (spec[0] is a list) were already excluded correctly.

models.js: when the user assigns an input source for a connection input of
type T, auto-wire the first unwired output of the same type T to the sole
pipeline destination of that type. Applies only when: (1) the selected source
is non-empty, (2) the slot has exactly one output of that type, (3) that
output has no wire set yet, and (4) there is exactly one destination available.
This covers the common image-processor case: set image input → IMAGE output
auto-wired to studio.source_image.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
digital-garbage
2026-06-07 00:39:25 +03:00
co-authored by Claude Sonnet 4.6
parent 30aec950cd
commit 3d0b86e3be
2 changed files with 27 additions and 3 deletions
+6 -2
View File
@@ -20,6 +20,10 @@ LINK_TYPES = {
"GUIDER", "NOISE", "UPSCALE_MODEL", "PHOTOMAKER", "WEBCAM",
}
WIDGET_PRIMITIVES = {"INT", "FLOAT", "STRING", "BOOLEAN"}
# "COMBO" is the V1/new-style dropdown widget type; it is NOT a graph connection even
# though it is a string. Old-style combos (spec[0] is a list) are already excluded by
# the isinstance(t, str) guard, but new-style ones need an explicit check.
_WIDGET_TYPES = WIDGET_PRIMITIVES | {"COMBO"}
# ComfyUI V3 dynamic match types that are semantically IMAGE-compatible.
_MATCHTYPE_ALIASES: dict[str, str] = {} # populated on first lookup — patterns are prefix-matched
@@ -79,7 +83,7 @@ def _all_input_types(node_def: dict) -> list[str]:
for group in ("required", "optional"):
for spec in (inp.get(group) or {}).values():
t = spec[0] if isinstance(spec, list) and spec else None
if isinstance(t, str):
if isinstance(t, str) and t not in _WIDGET_TYPES:
types.append(_normalize_type(t))
return types
@@ -109,7 +113,7 @@ def connection_inputs(node_def: dict) -> list[dict]:
if not isinstance(spec, list) or not spec:
continue
t = spec[0]
if not isinstance(t, str) or t in WIDGET_PRIMITIVES:
if not isinstance(t, str) or t in _WIDGET_TYPES:
continue
opts = spec[1] if len(spec) > 1 and isinstance(spec[1], dict) else {}
# V3 nodes can mark a required-group input as optional via the flag.
+21 -1
View File
@@ -269,7 +269,27 @@
const cur = slot.input_sources[ci.name] || "";
srcs.forEach((s) => { const o = el("option", null, s.label); o.value = s.value; if (s.value === cur) o.selected = true; sel.append(o); });
if (cur && !srcs.some((s) => s.value === cur)) { const o = el("option", null, cur + " (missing)"); o.value = cur; o.selected = true; sel.append(o); }
sel.onchange = async () => { slot.input_sources[ci.name] = sel.value; await persist(); };
sel.onchange = async () => {
slot.input_sources[ci.name] = sel.value;
// Auto-wire: if the user just assigned a non-auto source and this slot has
// exactly one output of the same type that is not yet wired, and there is
// exactly one pipeline destination for that type, wire it automatically.
if (sel.value && sel.value !== "") {
const matchOuts = (cand.outputs || []).filter((o) => o.type === ci.type);
if (matchOuts.length === 1) {
const outName = matchOuts[0].name;
const existing = wireTargets(slot.wires?.[outName]).filter(Boolean);
if (!existing.length) {
const dests = destinations(slot, ci.type).filter((d) => d.value);
if (dests.length === 1) {
slot.wires = slot.wires || {};
slot.wires[outName] = [dests[0].value];
}
}
}
}
await persist();
};
row.append(sel);
sbox.append(row);
});