feat: split modal status pill into sync and wait stages
This commit is contained in:
@@ -194,6 +194,7 @@ Because the Modal queue route can spend noticeable time hashing, syncing, and cr
|
||||
|
||||
The extension also now renders its own global activity badge in the ComfyUI UI during Modal setup and remote execution. That badge stays visible even when ComfyUI's stock queue state does not fully reflect the pre-queue setup phase.
|
||||
The badge now also prunes orphaned prompt entries when a workflow finishes, so stale orange "setup running" indicators do not linger after the remote work is done.
|
||||
The badge now distinguishes queue-time graph/resource sync from remote-app wait time: it shows `Syncing graph with Modal` while the queue route is scanning and syncing, then `Waiting for Modal app` once the proxy has started but before the first streamed remote node begins executing.
|
||||
|
||||
For headless remote execution, the worker also installs a minimal `PromptServer.instance` shim before it imports external custom nodes. That keeps route-registering custom node packs from crashing at import time just because the remote worker is not running ComfyUI's full HTTP server.
|
||||
|
||||
|
||||
@@ -44,6 +44,7 @@
|
||||
- Some custom-node packs register HTTP routes or prompt handlers at import time through `PromptServer.instance`. A headless remote worker still needs to provide a minimal `PromptServer.instance` shim with `routes`, `app`, `supports`, and no-op send methods, or those packs fail during external custom-node initialization before any workflow execution begins.
|
||||
- For good remote UX, one generic "executing" node state is not enough. The frontend needs separate visual states for "setup", "ready in Modal", "currently executing remotely", "finished for this run", and "workflow fully done", with whole-workflow success clearing the temporary remote states back to the normal idle outline.
|
||||
- The global Modal activity badge needs the same cleanup discipline as the per-node overlays. If a prompt entry survives in badge state after the prompt itself is gone, the UI can get stuck showing an orange "setup running" badge even though the workflow finished; pruning orphaned prompt ids fixes that.
|
||||
- One orange badge state is not expressive enough. The frontend can use queue-time `modal_status setup` for "Syncing graph with Modal", then switch to a separate waiting state when the proxy begins executing but before the first streamed remote-node event arrives, which makes the remote lifecycle much easier to understand from the UI.
|
||||
- Warm-container caches and loader reuse can conflict with `modal.Volume.reload()`. If a request truly needs a fresh volume reload after uploading new assets, the worker may have to clear warm loader caches, drop reusable prompt executors, unload ComfyUI models, and retry the reload once to get past Modal's "open files preventing the operation" error.
|
||||
- The synthetic frontend events have to match ComfyUI's websocket adapter shapes exactly, not just semantically. In practice that means `status` detail must be the inner status payload, `executing` detail should be the display node id string, and queue-list initialization state benefits from a matching `"Waiting for a machine"` notification payload keyed by prompt id.
|
||||
- Even with correctly-shaped synthetic events, ComfyUI's built-in queue UI may still not present an obvious "workflow running" state during long pre-queue Modal setup. The extension should own a separate global activity badge so users have explicit feedback while the remote runtime is still being prepared.
|
||||
|
||||
@@ -34,7 +34,8 @@ def test_global_modal_status_badge_is_installed() -> None:
|
||||
source = _modal_toggle_source()
|
||||
|
||||
assert 'element.id = "comfy-modal-global-status";' in source
|
||||
assert "Modal setup running for" in source
|
||||
assert "Syncing graph with Modal" in source
|
||||
assert "Waiting for Modal app" in source
|
||||
assert "Modal workflow running on" in source
|
||||
assert "installGlobalStatusStyles()" in source
|
||||
assert "function pruneGlobalStatusStates()" in source
|
||||
@@ -58,6 +59,7 @@ def test_remote_modal_uses_distinct_ready_active_and_complete_colors() -> None:
|
||||
assert 'const READY_BORDER_COLOR = "#22c55e";' in source
|
||||
assert 'const ACTIVE_BORDER_COLOR = "#a855f7";' in source
|
||||
assert 'const COMPLETE_BORDER_COLOR = "#16a34a";' in source
|
||||
assert 'const STATE_WAITING = "waiting";' in source
|
||||
assert 'const STATE_READY = "ready";' in source
|
||||
assert 'const STATE_ACTIVE = "active";' in source
|
||||
assert 'detail.phase === "execution_success"' in source
|
||||
|
||||
+15
-2
@@ -13,6 +13,7 @@ const COMPLETE_BORDER_COLOR = "#16a34a";
|
||||
const ERROR_BORDER_COLOR = "#ef4444";
|
||||
|
||||
const STATE_SETUP = "setup";
|
||||
const STATE_WAITING = "waiting";
|
||||
const EXECUTION_PHASE = "executing";
|
||||
const STATE_READY = "ready";
|
||||
const STATE_ACTIVE = "active";
|
||||
@@ -155,6 +156,7 @@ function currentGlobalStatus() {
|
||||
return (
|
||||
phases.find((state) => state.phase === STATE_ERROR) ??
|
||||
phases.find((state) => state.phase === STATE_SETUP) ??
|
||||
phases.find((state) => state.phase === STATE_WAITING) ??
|
||||
phases.find((state) => state.phase === EXECUTION_PHASE) ??
|
||||
phases[0]
|
||||
);
|
||||
@@ -189,7 +191,14 @@ function refreshGlobalStatusElement() {
|
||||
dot.style.background = SETUP_BORDER_COLOR;
|
||||
dot.style.boxShadow = "0 0 0 6px rgba(245, 158, 11, 0.18)";
|
||||
dot.style.animation = "modal-status-pulse 1.1s ease-in-out infinite";
|
||||
text.textContent = `Modal setup running for ${activeState.nodeCount} ${nodeLabel}`;
|
||||
text.textContent = "Syncing graph with Modal";
|
||||
} else if (activeState.phase === STATE_WAITING) {
|
||||
element.style.borderColor = "rgba(245, 158, 11, 0.55)";
|
||||
element.style.background = "rgba(61, 42, 9, 0.94)";
|
||||
dot.style.background = SETUP_BORDER_COLOR;
|
||||
dot.style.boxShadow = "0 0 0 6px rgba(245, 158, 11, 0.18)";
|
||||
dot.style.animation = "modal-status-pulse 1.1s ease-in-out infinite";
|
||||
text.textContent = "Waiting for Modal app";
|
||||
} else if (activeState.phase === EXECUTION_PHASE) {
|
||||
element.style.borderColor = "rgba(34, 197, 94, 0.55)";
|
||||
element.style.background = "rgba(8, 49, 28, 0.94)";
|
||||
@@ -660,7 +669,11 @@ function handleExecutionPhase(event, phase) {
|
||||
return;
|
||||
}
|
||||
if (phase === EXECUTION_PHASE) {
|
||||
setGlobalStatusPhase(promptId, EXECUTION_PHASE, componentNodeIds.length);
|
||||
setGlobalStatusPhase(
|
||||
promptId,
|
||||
promptState.hasStreamedProgress ? EXECUTION_PHASE : STATE_WAITING,
|
||||
componentNodeIds.length,
|
||||
);
|
||||
setNodesPhase(componentNodeIds, STATE_READY, promptId, detail.exception_message);
|
||||
return;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user