fix: Generate refuses, saying why, until the open project's pipeline is live; Sampler shows what will really run when a save fails; takes check the right pipeline
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 5.5
parent
a9f6a7f4ad
commit
b65ef15199
+2
-2
@@ -25,11 +25,11 @@ const on = (fn) => { heard.add(fn); return () => heard.delete(fn); };
|
||||
const project = createProject({ keepConsistent: syncSeparated, onChange: () => say("change"), onOpen: () => say("open"), onError: (err) => console.warn(err), beforeSwitch: () => pipeline.settled() });
|
||||
const pipeline = createPipelineState(api);
|
||||
const generate = createGenerate({ pipeline });
|
||||
linkPipeline({ project, pipeline, onOpen: (fn) => on((what) => { if (what === "open") fn(); }), say: (text) => c.toast.warn({ text }) });
|
||||
const link = linkPipeline({ project, pipeline, onOpen: (fn) => on((what) => { if (what === "open") fn(); }), say: (text) => c.toast.warn({ text }) });
|
||||
const frame = build(document.getElementById("app"));
|
||||
const selection = createSelection({ project });
|
||||
selection.on(() => say("select"));
|
||||
const app = { editorSettings: [], timelineLanes: [], timelineView: {}, mediaMenu: [], mediaPeek: {}, sceneSections: [], clipTags: [], has: new Set(), actions: [], inputHooks: [], promptPrefix: [], lastRun: { typed: undefined, n: 0 }, runner: {}, project, pipeline, generate, api, on, say, title: frame.setZoneTitle, selection, playhead: createPlayhead(), theme: window.ComposerTheme, dock: createDock(), maintenance: createMaintenance({ api, flush: async () => { await project.flush(); if (project.unsaved) throw new Error("Your latest edits could not be saved, so nothing was changed. Try again in a moment."); } }), keys: createKeys(), resetLayout: () => { frame.resetLayout(); app.dock.reset(); } };
|
||||
const app = { editorSettings: [], timelineLanes: [], timelineView: {}, mediaMenu: [], mediaPeek: {}, sceneSections: [], clipTags: [], has: new Set(), actions: [], inputHooks: [], promptPrefix: [], lastRun: { typed: undefined, n: 0 }, runner: {}, project, pipeline, pipelineOwned: link.owns, generate, api, on, say, title: frame.setZoneTitle, selection, playhead: createPlayhead(), theme: window.ComposerTheme, dock: createDock(), maintenance: createMaintenance({ api, flush: async () => { await project.flush(); if (project.unsaved) throw new Error("Your latest edits could not be saved, so nothing was changed. Try again in a moment."); } }), keys: createKeys(), resetLayout: () => { frame.resetLayout(); app.dock.reset(); } };
|
||||
const settings = createSettings({ menubar: hostFor("menubar.menus") });
|
||||
app.openSettings = (id) => settings.open(id);
|
||||
const result = await loadFeatures(paths, { app });
|
||||
|
||||
@@ -83,3 +83,13 @@ test("a batch with no seed input says so and makes nothing", async () => {
|
||||
assert.equal(queued, 0);
|
||||
assert.match(document.body.textContent, /no seed input/);
|
||||
});
|
||||
|
||||
test("Generate refuses, saying why, while the open project's pipeline is not the live one", async () => {
|
||||
let queued = 0;
|
||||
const { host, app } = rig("idle", { generate: async () => { queued += 1; return true; } });
|
||||
app.pipelineOwned = () => false;
|
||||
host.querySelector("button").click();
|
||||
await new Promise((r) => setTimeout(r, 20));
|
||||
assert.equal(queued, 0);
|
||||
assert.match(document.body.textContent, /pipeline is not loaded yet/);
|
||||
});
|
||||
|
||||
@@ -51,7 +51,6 @@ export default {
|
||||
// `times` > 1: the same shots again, only the seed differing (the prompt's random picks are drawn once), so the takes can be compared.
|
||||
async function runUnits(units, times = 1) {
|
||||
if (busy) { if (times > 1) tell("A run is already going: wait for it, or Stop it."); return; }
|
||||
if (times > 1 && !rolesAt(app.pipeline.slots(), "generation.seed").length) return tell("This pipeline has no seed input the app controls, so the takes would all come out the same.");
|
||||
busy = true; stopped = false; draw();
|
||||
const pid = p.project.id;
|
||||
let made = 0;
|
||||
@@ -59,6 +58,8 @@ export default {
|
||||
try {
|
||||
// A project's pipeline still going in is the one this run uses, not the last one's.
|
||||
if (app.pipeline.settled && !(await app.pipeline.settled())) { tell("Not started: this project's pipeline is still loading. ComfyUI is slow to answer; try again in a moment."); return; }
|
||||
if (app.pipelineOwned && !app.pipelineOwned()) { tell("Not started: this project's pipeline is not loaded yet (ComfyUI did not answer). It goes in as soon as ComfyUI answers."); return; }
|
||||
if (times > 1 && !rolesAt(app.pipeline.slots(), "generation.seed").length) { tell("This pipeline has no seed input the app controls, so the takes would all come out the same."); return; }
|
||||
const snap = times > 1 ? structuredClone(p.project) : null; // takes differ in the seed alone: later edits to the project do not reach them
|
||||
const drawn = new Map();
|
||||
const same = times > 1 ? (body) => { const key = JSON.stringify(body); if (!drawn.has(key)) drawn.set(key, expand(body)); return drawn.get(key); } : expand;
|
||||
|
||||
@@ -64,3 +64,22 @@ test("typing a value back to what the window opened with is saved, though the wi
|
||||
}
|
||||
assert.deepStrictEqual(saved, [20, 7]);
|
||||
});
|
||||
|
||||
test("a value that could not be saved is not left on screen: the window shows what will run and says why", async () => {
|
||||
const live = [{ id: "sampler", node: "S", inputs: { steps: 7 }, roles: [{ at: "generation.sampling", input: "steps", label: "Steps" }] }];
|
||||
const ps = { slots: () => live, activeModules: () => [], useful: () => true, usefulness: () => () => true, currentValues: () => ({}), loading: () => false, loadError: () => null,
|
||||
subscribe: () => () => {}, ensureLoaded: async () => {}, save: async () => {}, saveNotes: () => ["Could not save: down"] };
|
||||
const host = document.createElement("div");
|
||||
document.body.append(host);
|
||||
hub.setup({ host, app: { pipeline: ps, api: { describeNodes: async () => ({ nodes: { S: { widgets: [{ name: "steps", type: "INT", min: 1, max: 100 }] } } }) }, on: () => () => {}, actions: null } });
|
||||
await new Promise((r) => setTimeout(r, 0));
|
||||
host.querySelector("button").click();
|
||||
await new Promise((r) => setTimeout(r, 0));
|
||||
let input = [...document.querySelectorAll("input[type=number]")].pop();
|
||||
input.value = "25";
|
||||
input.dispatchEvent(new KeyboardEvent("keydown", { key: "Enter", bubbles: true }));
|
||||
await new Promise((r) => setTimeout(r, 0));
|
||||
input = [...document.querySelectorAll("input[type=number]")].pop();
|
||||
assert.equal(input.value, "7");
|
||||
assert.match(document.body.textContent, /Could not save: down/);
|
||||
});
|
||||
|
||||
@@ -24,7 +24,13 @@ export default {
|
||||
if (!w) return null;
|
||||
const set = async (v) => { // compared with the slot as it is NOW: the one drawn may be several saves old
|
||||
const now = (ps.slots() || []).find((s) => s.id === slot.id) || slot;
|
||||
if (JSON.stringify((now.inputs || {})[role.input]) !== JSON.stringify(v)) await ps.save({ inputs: { [slot.id]: { [role.input]: v } } });
|
||||
if (JSON.stringify((now.inputs || {})[role.input]) === JSON.stringify(v)) return;
|
||||
await ps.save({ inputs: { [slot.id]: { [role.input]: v } } });
|
||||
const after = (ps.slots() || []).find((s) => s.id === slot.id) || {};
|
||||
if (JSON.stringify((after.inputs || {})[role.input]) !== JSON.stringify(v)) { // not saved: show what will really run, and why
|
||||
c.toast.warn({ text: [...(ps.saveNotes ? ps.saveNotes() : [])].find((n) => /could not save/i.test(n)) || `${role.label || role.input} was not saved.` });
|
||||
draw();
|
||||
}
|
||||
};
|
||||
return c.settingsRow.default({ label: role.label || w.name, hint: w.tooltip || "", control: widgetControl({ ...w, name: role.label || w.name }, (slot.inputs || {})[role.input] ?? w.default, set) });
|
||||
};
|
||||
|
||||
@@ -52,5 +52,6 @@ export function linkPipeline({ project, pipeline, onOpen, say = () => {} }) {
|
||||
store(project.project.models || {}, slots);
|
||||
});
|
||||
onOpen(() => { retries = 0; return adopt(); }); // each project opened gets its own retries
|
||||
return { adopt };
|
||||
// Whether the live pipeline is the open project's (false while it is going in, or ComfyUI has not answered).
|
||||
return { adopt, owns: () => !!project.project && owner === project.project.id };
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user