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:
co-authored by
Claude
parent
8e9c0479a8
commit
3222fbcb31
@@ -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 —
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user