diff --git a/app/ui/shell/pipeline_state.js b/app/ui/shell/pipeline_state.js index 0ff08d39..81c2ebcc 100644 --- a/app/ui/shell/pipeline_state.js +++ b/app/ui/shell/pipeline_state.js @@ -647,16 +647,14 @@ export function createPipelineState(rawApi, { answerMs = ANSWER_MS } = {}) { // too, and simply goes without that slot. const isWhole = typeof wholeSaved === "boolean" ? wholeSaved : [...byId.keys()].some((id) => !have.has(id)) && offered.some((d) => !gone.has(d.id) && !byId.has(d.id)); - // A whole pipeline started from a preset lacks the media inputs an update gave that preset since (a video or audio + // A pipeline started from a preset lacks the media inputs an update gave that preset since (a video or audio // reference slot): those take no setting and nothing else depends on them, so they are laid in beside the saved ones. let grown = []; - if (isWhole) { - try { - const isAsset = (s) => (s.roles || []).length && s.roles.every((r) => String(r.at || "").startsWith("assets.")); - const from = ((await API.pipelinePresets()).presets || []).find((pr) => (pr.slots || []).some((s) => isAsset(s) && byId.has(s.id))); - if (from) grown = from.slots.filter((s) => isAsset(s) && sound(s) && !byId.has(s.id) && !gone.has(s.id)); - } catch (_) { /* the saved pipeline goes in as it is */ } - } + try { + const isAsset = (s) => (s.roles || []).length && s.roles.every((r) => String(r.at || "").startsWith("assets.")); + const from = ((await API.pipelinePresets()).presets || []).find((pr) => (pr.slots || []).some((s) => isAsset(s) && byId.has(s.id))); + if (from) grown = from.slots.filter((s) => isAsset(s) && sound(s) && !byId.has(s.id) && !gone.has(s.id)); + } catch (_) { /* the saved pipeline goes in as it is */ } const lay = (extras) => isWhole ? JSON.parse(JSON.stringify([...saved.filter(sound), ...(extras ? grown : [])])) : offered.filter((def) => !gone.has(def.id)).map((def) => { const mine = byId.get(def.id); if (!mine) return JSON.parse(JSON.stringify(def)); @@ -670,7 +668,7 @@ export function createPipelineState(rawApi, { answerMs = ANSWER_MS } = {}) { if (typeof mine.group === "string" && mine.group) out.group = mine.group; if (typeof mine.bypassed === "boolean") out.bypassed = mine.bypassed; return out; - }).concat(extras ? saved.filter((s) => sound(s) && !have.has(s.id)) : []); + }).concat(extras ? [...saved.filter((s) => sound(s) && !have.has(s.id)), ...grown.filter((s) => !have.has(s.id))] : []); // Replaces whatever pipeline was live: nothing queued for the old one may land on the new. const mine = ++epoch; pendingValues = {}; // the new project's values win, not an edit made to the old one diff --git a/app/ui/shell/tests/pipeline_state.test.js b/app/ui/shell/tests/pipeline_state.test.js index 56a2b934..e9841c21 100644 --- a/app/ui/shell/tests/pipeline_state.test.js +++ b/app/ui/shell/tests/pipeline_state.test.js @@ -751,3 +751,22 @@ test("the Project tab, the frame grid and the scene tab read the pipeline throug assert.match(read("inspector/index.js"), /app\.pipeline\.view/); assert.match(read("inspector/scene.js"), /app\.pipeline\.view/); }); + +test("a preset-started project gains the media slots its preset got since, and nothing else", async () => { + const media = (id, at) => ({ id, node: "LoadIt", roles: [{ at, input: "media_id" }], inputs: { media_id: "" } }); + const oldPreset = [{ id: "ckpt", node: "Checkpoint", inputs: { file: "h3.safetensors" } }, media("ref_source_1", "assets.reference_1")]; + const newPreset = [...oldPreset, { id: "ckpt2", node: "Checkpoint", inputs: {} }, media("ref_video_source_1", "assets.reference_video_1")]; + const PS = load([]); + lastApi.pipelinePresets = async () => ({ presets: [{ id: "p", slots: newPreset }] }); + await PS.adopt(oldPreset, ["model", "gen"], {}, true); + assert.deepStrictEqual(PS.slots().map((s) => s.id), ["ckpt", "ref_source_1", "ref_video_source_1"]); +}); + +test("the same holds when the saved copy is not marked whole", async () => { + const media = (id, at) => ({ id, node: "LoadIt", roles: [{ at, input: "media_id" }], inputs: { media_id: "" } }); + const old = [{ id: "ckpt", node: "Checkpoint", inputs: {} }, media("ref_source_1", "assets.reference_1")]; + const PS = load([]); + lastApi.pipelinePresets = async () => ({ presets: [{ id: "p", slots: [...old, media("ref_video_source_1", "assets.reference_video_1")] }] }); + await PS.adopt(old, ["model"], {}, false); + assert.ok(PS.slots().some((s) => s.id === "ref_video_source_1")); +});