Easy Gen: Interrupt button on the progress panel

Easy Gen had no way to stop a run — the only exit was the ComfyUI queue. Adds
the same "■ Interrupt" control the Cutting Room's player readout uses (shared
.gen-interrupt styling), living inside the progress panel so it exists only
while there is something to stop.

ComfyUI records an interrupted prompt in history as status_str "error" with no
media, which the status endpoint cannot tell apart from a real crash — so watch()
remembers that the stop was asked for and reports "Generation stopped." instead
of "Generation failed.", while still showing any partial media the run wrote.
A failed interrupt call re-enables the button and says so rather than leaving a
dead control over a run that is still going.

Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
digital-garbage
2026-07-26 20:22:48 +03:00
co-authored by Claude
parent 8e9c0479a8
commit 3222fbcb31
3 changed files with 47 additions and 5 deletions
+6
View File
@@ -12,6 +12,12 @@
still happens inside Studio dead-last — after shortcut expansion and after the scene split
— which is what makes a `$var` work *inside* a shortcut's replacement while a value
containing a comma or a trigger word can never move a scene cut.
- **An Interrupt button in Easy Gen.** The progress panel now carries the same
**■ Interrupt** control the Cutting Room's player has — Easy Gen previously had no way to
stop a run short of the ComfyUI queue. A stopped run reports "Generation stopped." instead
of a failure (an interrupted job is recorded as an error with no media, which is
indistinguishable from a real crash at the API level), and any partial media it did write
is still shown.
### Changed
- **Variable values are now auto-growing text boxes** (both UIs) instead of one-line inputs —
+4 -1
View File
@@ -58,7 +58,10 @@ a.brand:hover .brand-sub { color: var(--amber-2); }
height: 6px; border-radius: 4px; background: linear-gradient(90deg, var(--amber), var(--amber-2));
width: 0%; transition: width .2s ease; margin-bottom: 6px;
}
.easy-progress-label { font-size: 12px; color: var(--muted); font-family: var(--font-mono); }
.easy-progress-row { display: flex; align-items: center; gap: 10px; }
.easy-progress-label { flex: 1; min-width: 0; font-size: 12px; color: var(--muted); font-family: var(--font-mono);
overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.easy-progress-stop:disabled { opacity: .6; cursor: default; }
.easy-bar {
flex: none; display: flex; align-items: flex-end; gap: 12px;
+37 -4
View File
@@ -22,7 +22,14 @@
progressWrap.hidden = true;
const bar = el("div", "easy-progress-bar");
const label = el("div", "easy-progress-label");
progressWrap.append(bar, label);
// Same "■ Interrupt" affordance as the Cutting Room's player readout, and it lives in the
// progress panel so it only exists while there's something to stop.
const stopBtn = el("button", "gen-interrupt easy-progress-stop", "■ Interrupt");
stopBtn.type = "button";
stopBtn.title = "Stop the current generation";
const progressRow = el("div", "easy-progress-row");
progressRow.append(label, stopBtn);
progressWrap.append(bar, progressRow);
root.append(inner, closeBtn, badge, progressWrap);
let pollTimer = null;
@@ -138,8 +145,29 @@
progressWrap.hidden = false;
label.textContent = "Starting…";
bar.style.width = "0%";
// An interrupted job lands in ComfyUI's history as status_str "error" with no media —
// indistinguishable from a real failure at the API level. Remember that the user asked
// for it so the poll below reports "stopped", not "Generation failed".
let interrupted = false;
stopBtn.disabled = false;
stopBtn.textContent = "■ Interrupt";
stopBtn.onclick = async () => {
interrupted = true;
stopBtn.disabled = true;
stopBtn.textContent = "Interrupting…";
label.textContent = "Interrupting…";
try { await API.interrupt(); }
catch (e) {
// The run may well still be going — say so instead of leaving a dead button.
interrupted = false;
stopBtn.disabled = false;
stopBtn.textContent = "■ Interrupt";
label.textContent = "Could not interrupt: " + (e.message || e);
}
};
progressTimer = setInterval(async () => {
if (interrupted) return; // don't paint "Sampling…" over "Interrupting…"
try {
const pr = await API.progress();
if (pr.max > 0) {
@@ -165,14 +193,19 @@
stopPolling();
progressWrap.hidden = true;
const media = res.media && res.media.length ? res.media[res.media.length - 1] : null;
if (media) setGenerated(media, projectId); else showEmpty("Generation finished but produced no media.");
if (onDone) onDone(true, res);
// A run stopped mid-sampling can still have written partial media — show it if so,
// it's the whole point of interrupting a long run you can already tell is wrong.
if (media) setGenerated(media, projectId);
else if (interrupted) showEmpty("Generation stopped.");
else showEmpty("Generation finished but produced no media.");
if (onDone) onDone(!interrupted, res);
return;
}
if (res.state === "error") {
stopPolling();
progressWrap.hidden = true;
showError(res.error || "Generation failed.");
if (interrupted) showEmpty("Generation stopped.");
else showError(res.error || "Generation failed.");
if (onDone) onDone(false, res);
return;
}