Movie Editor: Restart ComfyUI button

FunPack menu -> 'Restart ComfyUI' relaunches the server process in place
(POST /funpack/movie/api/restart -> os.execv, mirroring ComfyUI-Manager's
reboot incl. the comfy-cli session case; no Manager dependency). Restart is
deferred 0.7s so the 200 flushes first. UI shows a blocking spinner overlay,
polls /health, and auto-reloads when ComfyUI is back. Confirms first.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
digital-garbage
2026-06-06 20:13:26 +03:00
co-authored by Claude Opus 4.8
parent 7f479a6a2d
commit 8b278c9515
4 changed files with 70 additions and 0 deletions
+1
View File
@@ -45,6 +45,7 @@
getModels: () => j("GET", API("/models")),
saveModels: (data) => j("PUT", API("/models"), data),
refreshModels: () => j("POST", API("/models/refresh")),
restart: () => j("POST", API("/restart")),
// generate
generate: (id, onlyScene) => j("POST", API(`/projects/${id}/generate`), { only_scene: onlyScene || null }),
+21
View File
@@ -9,6 +9,26 @@
function sel() { return S.get().selectedSceneId; }
function hasProject() { return !!S.get().project; }
async function restartComfy() {
if (!confirm("Restart ComfyUI now?\n\nThe server will be down for ~10–40s and any running generation will be lost. This page reloads automatically when it's back.")) return;
const ov = el("div", "restart-overlay");
const card = el("div", "restart-card");
card.append(el("div", "restart-spin"));
const msg = el("div", "restart-msg", "Restarting ComfyUI…\nThis page will reload when it's back.");
card.append(msg); ov.append(card); document.body.append(ov);
try { await window.MovieEditorAPI.restart(); } catch (_) { /* connection drops as it execv's — expected */ }
const start = Date.now();
const tick = async () => {
try {
const h = await window.MovieEditorAPI.health();
if (h && h.ok) { location.reload(); return; }
} catch (_) { /* still down */ }
if (Date.now() - start > 90000) msg.textContent = "Still waiting on ComfyUI…\nIt may need a manual restart — check the console.";
setTimeout(tick, 2000);
};
setTimeout(tick, 3500); // give it a moment to actually go down first
}
function menuSpec() {
const st = S.get();
const recent = (st.projects || []).slice(0, 8).map((p) => ({
@@ -53,6 +73,7 @@
FunPack: [
{ menulabel: "Libraries (in ComfyUI Studio)" },
{ label: "Open ComfyUI", hint: "↗", action: () => window.open("/", "_blank") },
{ label: "Restart ComfyUI", hint: "⟳", danger: true, action: restartComfy },
{ sep: true },
{ label: st.health?.reference_loaded ? "Pipeline reference: loaded" : "Pipeline reference: missing", disabled: true },
{ label: `Configured nodes: ${st.health?.configured_slots ?? 0}`, disabled: true },
+10
View File
@@ -293,6 +293,16 @@ input[type=number] { font-family: var(--font-mono); }
.wire-arrow { color: var(--teal); }
.wire-select { flex: 1; }
/* restart overlay */
.restart-overlay { position: fixed; inset: 0; z-index: 9999; display: flex; align-items: center; justify-content: center;
background: color-mix(in srgb, var(--ink-0) 88%, transparent); backdrop-filter: blur(3px); }
.restart-card { display: flex; flex-direction: column; align-items: center; gap: 16px; padding: 30px 40px;
background: var(--ink-1); border: 1px solid var(--line); border-radius: 14px; }
.restart-msg { color: var(--muted); text-align: center; white-space: pre-line; font-size: 13px; line-height: 1.5; }
.restart-spin { width: 30px; height: 30px; border-radius: 50%; border: 3px solid var(--line);
border-top-color: var(--amber); animation: spin .8s linear infinite; }
@keyframes spin { to { transform: rotate(360deg); } }
/* staggered zone reveal on load */
.zone { animation: rise .5s both; }
.media { animation-delay: .02s; }
+38
View File
@@ -25,6 +25,37 @@ from .backend.timeline import Project, build_combined_prompt
UI_PREFIX = "/funpack/movie"
def _restart_comfy() -> None:
"""Relaunch the ComfyUI process in place. Mirrors ComfyUI-Manager's reboot so it
works the same whether launched directly, as a module, or via comfy-cli."""
import os
import sys
try:
sys.stdout.close_log() # type: ignore[attr-defined] # Manager's tee logger, if present
except Exception:
pass
# comfy-cli watches for a .reboot file and relaunches us itself.
if "__COMFY_CLI_SESSION__" in os.environ:
try:
open(os.environ["__COMFY_CLI_SESSION__"] + ".reboot", "w").close()
except Exception:
pass
print("\n[FunPack] Restarting ComfyUI...\n", flush=True)
os._exit(0)
sys_argv = sys.argv.copy()
if "--windows-standalone-build" in sys_argv:
sys_argv.remove("--windows-standalone-build")
if sys_argv and sys_argv[0].endswith("__main__.py"): # python -m comfy
module_name = os.path.basename(os.path.dirname(sys_argv[0]))
cmds = [sys.executable, "-m", module_name] + sys_argv[1:]
elif sys.platform.startswith("win32"):
cmds = ['"' + sys.executable + '"', '"' + sys_argv[0] + '"'] + sys_argv[1:]
else:
cmds = [sys.executable] + sys_argv
print(f"\n[FunPack] Restarting ComfyUI... {cmds}\n", flush=True)
os.execv(sys.executable, cmds)
# ── helpers ──────────────────────────────────────────────────────────────────
def _media_from_history(hist_entry: dict) -> list[dict]:
@@ -267,6 +298,13 @@ if web is not None and PromptServer is not None:
raise web.HTTPNotFound(reason="Unknown node class")
return web.json_response(spec)
@routes.post(UI_PREFIX + "/api/restart")
async def _restart(_req):
import asyncio
# defer so this 200 flushes to the browser before the process is replaced
asyncio.get_event_loop().call_later(0.7, _restart_comfy)
return web.json_response({"restarting": True})
@routes.post(UI_PREFIX + "/api/models/refresh")
async def _models_refresh(_req):
try: