diff --git a/app/ui/features/generate/index.js b/app/ui/features/generate/index.js index 374bfb5c..dcf24b37 100644 --- a/app/ui/features/generate/index.js +++ b/app/ui/features/generate/index.js @@ -5,6 +5,7 @@ import { QUEUED, RUNNING } from "../../shell/run.js"; import { inPlace } from "../../shell/place.js"; import { buildInputs, rolesAt } from "./inputs.js"; import { offer } from "../../shell/actions.js"; +import { names } from "../../shell/bin.js"; const MAX_TAKES = 24; const expand = (body) => fetch("/funpack/api/prompt/expand", { method: "POST", headers: { "Content-Type": "application/json" }, @@ -96,14 +97,14 @@ export default { if (group.every((s) => s.excluded) || !isGenerative(root)) continue; // all left out, or not made by the model // The unit is made once at the length of its clips together (each cut adds one shared frame). const frames = group.reduce((t, s) => t + effFrames(s, snap), 0) - (group.length - 1); - const { inputs, unwired, noPrompt, notes } = await buildInputs({ project: snap, scene: root, slots: frozen, expand: same, frames, hooks: app.inputHooks, ...(fixedSeed ? { seed: null } : {}), prefix: (app.promptPrefix || []).flatMap((f) => { try { return f(root, snap); } catch { return []; } }) }); + const { inputs, unwired, left, noPrompt, notes } = await buildInputs({ project: snap, scene: root, slots: frozen, expand: same, frames, hooks: app.inputHooks, ...(fixedSeed ? { seed: null } : {}), prefix: (app.promptPrefix || []).flatMap((f) => { try { return f(root, snap); } catch { return []; } }) }); app.lastRun.typed = (root.text || "").trim(); // what a Chat comment made now would be about const seeded = rolesAt(frozen, "generation.seed")[0]; if (seeded) app.lastRun.seed = (inputs[seeded.slot.id] || {})[seeded.role.input] ?? (seeded.slot.inputs || {})[seeded.role.input]; // shown in ⏱ Sampler, to keep a liked one if (stopped) break; if (noPrompt && !made) tell("This pipeline has no prompt input, so the scene text is not sent."); notes.forEach(tell); - if (unwired) tell(`${unwired} reference(s) did not fit this pipeline and are not used.`); + if (unwired) tell(`${unwired} reference(s) did not fit this pipeline and are not used: ${(left || []).map((l) => `${names.get(l.id) || l.id} (looked for ${l.at.replace("assets.", "")}: ${l.why})`).join("; ")}.`); // The page's own guard does not see another tab: ComfyUI's queue does. Asked per shot: between this run's shots it is empty. if (app.api.projectQueued && (await app.api.projectQueued(pid))) { tell("This project already has a run in ComfyUI's queue (another tab or window?). Wait for it, or Stop it there."); break; } if (stopped) break; diff --git a/app/ui/features/generate/inputs.js b/app/ui/features/generate/inputs.js index d98cb299..30f71a39 100644 --- a/app/ui/features/generate/inputs.js +++ b/app/ui/features/generate/inputs.js @@ -31,7 +31,7 @@ export async function buildInputs({ project, scene, slots, expand, frames, hooks if (off) put(off, like.unsend.input, false); } const plan = sceneReferences(scene, project.fixed_references); - const { overrides, unwired } = wireReferences(plan.ids, slots, source ? [source.slot.id] : [], plan.pins, (id) => kinds.get(id)); + const { overrides, unwired, left } = wireReferences(plan.ids, slots, source ? [source.slot.id] : [], plan.pins, (id) => kinds.get(id)); for (const [id, fields] of Object.entries(overrides)) raw[id] = { ...raw[id], ...fields }; const prompt = rolesAt(slots, "generation.prompt")[0]; @@ -55,5 +55,5 @@ export async function buildInputs({ project, scene, slots, expand, frames, hooks // Judged on what is SENT (a carried frame counts; a picture t2v blanks, or one this pipeline has no input for, does not). const picture = (source && (raw[source.slot.id] || {}).media_id) || Object.keys(overrides).length; if (prompt && !String((raw[prompt.slot.id] || {})[prompt.role.input] ?? "").trim() && !picture) notes.push("This shot has no text and no start picture, so the model makes whatever it likes."); - return { inputs: raw, unwired, noPrompt: !prompt, notes }; + return { inputs: raw, unwired, left, noPrompt: !prompt, notes }; } diff --git a/app/ui/shell/reference_wiring.js b/app/ui/shell/reference_wiring.js index 0a1f3c31..5bf6b26b 100644 --- a/app/ui/shell/reference_wiring.js +++ b/app/ui/shell/reference_wiring.js @@ -21,6 +21,7 @@ export function wireReferences(mediaIds, slots, reservedSlotIds, pins, kindOf) { const overrides = {}; const claimed = new Set(reservedSlotIds || []); let unwired = 0; + const left = []; // what was not wired, and the role it looked for: said by name, not just counted // Each reference gets its slot number (1-based), so a pinned one keeps its number when a reference // before it is switched off; the rest take the free numbers in the order they were added. // Pictures, videos and audio each have inputs of their own on the model, numbered per kind; only pictures are pinnable. @@ -36,7 +37,7 @@ export function wireReferences(mediaIds, slots, reservedSlotIds, pins, kindOf) { }); placed.forEach(({ id: mediaId, at }) => { const slot = pool.find((s) => (s.roles || []).some((r) => r.at === at)); - if (!slot) { unwired += 1; return; } + if (!slot) { unwired += 1; left.push({ id: mediaId, at, why: "no slot with that role" }); return; } // The wire's target is the role's own data (`wireTo`), never a slot id // this file would otherwise have to know: the preset declares where its // own reference lands, the same way it declares everything else. @@ -48,7 +49,7 @@ export function wireReferences(mediaIds, slots, reservedSlotIds, pins, kindOf) { // the same failure either way: the second silently overwrites the first // unless it is counted unwired instead. const dest = role && role.wireTo ? `${role.wireTo.slot}.${role.wireTo.input}` : slot.id; - if (claimed.has(slot.id) || claimed.has(dest)) { unwired += 1; return; } + if (claimed.has(slot.id) || claimed.has(dest)) { unwired += 1; left.push({ id: mediaId, at, why: "its slot is already taken" }); return; } claimed.add(slot.id); claimed.add(dest); overrides[slot.id] = { ...overrides[slot.id], media_id: mediaId }; @@ -57,7 +58,7 @@ export function wireReferences(mediaIds, slots, reservedSlotIds, pins, kindOf) { [role.wireTo.input]: [slot.id, 0] }; } }); - return { overrides, unwired }; + return { overrides, unwired, left }; } /**