Movie Editor: flag slots with unfed required inputs; only for active slots; honest 'ready'
- validateSlot now checks required connection INPUTS are actually fed (explicit input source, incoming wire, or a single auto-wire producer) — matching the builder's blocking — so a node like ImageTransform with an unwired 'image' input is flagged instead of showing 'ready'. Flagged slots get a clear red highlight (bg + ring). - 'Pipeline ready' banner now reads 'Not ready — N slot error(s) to fix' when errors. - Only ACTIVE slots are validated/blocking: a slot that feeds nothing (no output wire, not referenced by a core override) is inert and no longer blocks generation or gets highlighted — builder skips inert slots in auto-wire, frontend mirrors the rule. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 4.8
parent
3ae218c5bf
commit
3eef6de2b8
@@ -361,7 +361,18 @@ def build(object_info: dict, models_config: dict, params: dict, media: dict | No
|
||||
|
||||
# 5. auto-wire remaining unbound typed inputs by unique producer.
|
||||
producers = _producers(graph, slots, slot_node_id, slot_def, object_info)
|
||||
_autowire(graph, slots, slot_node_id, slot_def, object_info, producers, report)
|
||||
# A slot only matters if it's wired into the pipeline (an output wire, or referenced
|
||||
# as a source by a core override). Inert/unused slots are NOT validated or auto-wired,
|
||||
# so an unused node's required input never blocks generation.
|
||||
active_slots = set()
|
||||
for s in slots:
|
||||
if any(t for tg in (s.get("wires") or {}).values() for t in (tg if isinstance(tg, list) else [tg]) if t):
|
||||
active_slots.add(s["id"])
|
||||
for ovs in ((models_config or {}).get("core_overrides") or {}).values():
|
||||
for src in (ovs or {}).values():
|
||||
if isinstance(src, str) and src.startswith("out:"):
|
||||
active_slots.add(src.split(":", 2)[1])
|
||||
_autowire(graph, slots, slot_node_id, slot_def, object_info, producers, report, active_slots)
|
||||
|
||||
return graph, report
|
||||
|
||||
@@ -510,12 +521,14 @@ def _node_labels(slots, slot_node_id, object_info):
|
||||
return label
|
||||
|
||||
|
||||
def _autowire(graph, slots, slot_node_id, slot_def, object_info, producers, report):
|
||||
def _autowire(graph, slots, slot_node_id, slot_def, object_info, producers, report, active_slots=None):
|
||||
label = _node_labels(slots, slot_node_id, object_info)
|
||||
L = lambda nid: label.get(nid, nid)
|
||||
|
||||
targets = list(OPEN_PORTS) # (core_id, input, type, required)
|
||||
for s in slots: # slot connection inputs (e.g. image-proc vae/image/length)
|
||||
if active_slots is not None and s["id"] not in active_slots:
|
||||
continue # inert slot (feeds nothing) — don't auto-wire or block on its inputs
|
||||
nd = slot_def[s["id"]]
|
||||
for ci in connection_inputs(nd or {}):
|
||||
targets.append((slot_node_id[s["id"]], ci["name"], ci["type"], ci.get("required", False)))
|
||||
|
||||
@@ -113,6 +113,15 @@
|
||||
// Roles the fixed FunPack path cannot generate without (at least one slot each).
|
||||
const ESSENTIAL = [["unet", "Unet / Diffusion Model"], ["clip", "CLIP / Text Encoder"], ["video_vae", "Video VAE"]];
|
||||
|
||||
// A slot is "active" (part of the pipeline) if it wires an output somewhere or a core
|
||||
// override sources from it. Inert slots are left alone (no required-input errors).
|
||||
function slotIsActive(slot) {
|
||||
if (Object.values(slot.wires || {}).some((tg) => wireTargets(tg).some(Boolean))) return true;
|
||||
const ov = config.core_overrides || {};
|
||||
return Object.values(ov).some((ins) =>
|
||||
Object.values(ins || {}).some((src) => typeof src === "string" && src.startsWith(`out:${slot.id}:`)));
|
||||
}
|
||||
|
||||
function validateSlot(slot) {
|
||||
const issues = [];
|
||||
const spec = specFor(slot);
|
||||
@@ -143,6 +152,25 @@
|
||||
issues.push({ level: "error", msg: `"${o.name}" is wired to a destination that no longer exists.` });
|
||||
});
|
||||
});
|
||||
|
||||
// Required connection INPUTS must actually be fed (matches the builder's blocking):
|
||||
// explicit input source, an incoming wire from another node, or a unique auto-wire
|
||||
// producer. Only checked for ACTIVE slots (wired into the pipeline) — an unused node
|
||||
// that feeds nothing must not be flagged or block generation.
|
||||
if (slotIsActive(slot)) (spec.connection_inputs || []).forEach((ci) => {
|
||||
if (!ci.required) return;
|
||||
const src = (slot.input_sources || {})[ci.name];
|
||||
if (src && src !== "auto") return; // explicitly sourced (incl. "timeline")
|
||||
const incoming = (config.slots || []).some((s2) =>
|
||||
s2.id !== slot.id && Object.values(s2.wires || {}).some((tg) => wireTargets(tg).includes(`node:${slot.id}:${ci.name}`)));
|
||||
if (incoming) return;
|
||||
const prod = sources(slot, ci.type).filter((o) => o.value && o.value !== "timeline").length;
|
||||
if (prod === 1) return; // a single producer auto-wires
|
||||
if (prod > 1)
|
||||
issues.push({ level: "error", msg: `Input "${ci.name}" (${ci.type}): ${prod} possible sources — set its Input source.` });
|
||||
else
|
||||
issues.push({ level: "error", msg: `Input "${ci.name}" (${ci.type}) has no source — set its Input source or add a node that outputs ${ci.type}.` });
|
||||
});
|
||||
return issues;
|
||||
}
|
||||
|
||||
@@ -559,7 +587,7 @@
|
||||
if (v.warns) bits.push(`${v.warns} warning${v.warns > 1 ? "s" : ""}`);
|
||||
const bar = el("div", "valid-banner " + (v.errors ? "bad" : (v.warns ? "warn" : "ok")));
|
||||
bar.append(el("span", "valid-dot", v.errors ? "!" : "✓"));
|
||||
bar.append(el("span", null, v.errors ? `Pipeline ready but ${bits.join(", ")} in slot config.`
|
||||
bar.append(el("span", null, v.errors ? `Not ready — ${bits.join(", ")} to fix.`
|
||||
: (v.warns ? `Pipeline ready — ${bits.join(", ")}.` : "Pipeline ready.")));
|
||||
sec.append(bar);
|
||||
}
|
||||
|
||||
@@ -437,7 +437,9 @@ input[type=number] { font-family: var(--font-mono); }
|
||||
.slot-card { background: var(--ink-1); border: 1px solid var(--line); border-radius: 10px; padding: 12px; }
|
||||
.slot-card { border-left: 3px solid var(--amber); }
|
||||
.slot-card.slot-warn { border-left-color: var(--amber); }
|
||||
.slot-card.slot-bad { border-left-color: var(--danger); }
|
||||
.slot-card.slot-bad { border-color: var(--danger); border-left-color: var(--danger);
|
||||
background: color-mix(in srgb, var(--danger) 9%, var(--ink-1));
|
||||
box-shadow: 0 0 0 1px color-mix(in srgb, var(--danger) 35%, transparent); }
|
||||
.slot-head { display: flex; align-items: center; gap: 10px; cursor: pointer; user-select: none; }
|
||||
.slot-card.open .slot-head { margin-bottom: 10px; }
|
||||
.slot-chev { color: var(--faint); width: 12px; flex: 0 0 auto; font-size: 11px; }
|
||||
|
||||
Reference in New Issue
Block a user