Show selected GPU in Modal status
This commit is contained in:
@@ -115,7 +115,7 @@ Ordinary remote components without `Modal Map Input` still preserve ComfyUI's zi
|
||||
|
||||
Mapped components can contain both one-time execute targets and per-item execute targets. For example, two remote samplers may share one upstream model loader while only one sampler fans out over latents. Modal-Sync keeps the invariant upstream work separate from the per-item work so the sibling branch still runs once.
|
||||
|
||||
Mapped progress is summarized at the global status pill and representative node with counts such as `3/16`. Node-local bars remain reserved for real streamed node progress from executing remote nodes.
|
||||
Mapped progress is summarized at the global status pill and representative node with counts such as `3/16`. The global pill shows the workflow's selected GPU on a smaller second line throughout setup, app rebuilding, execution, and finalization. Node-local bars remain reserved for real streamed node progress from executing remote nodes.
|
||||
|
||||
## How Modal-Sync Works
|
||||
|
||||
|
||||
@@ -247,6 +247,7 @@ def _emit_modal_status(
|
||||
client_id: str | None,
|
||||
prompt_id: str | None,
|
||||
node_ids: list[str],
|
||||
modal_gpu: str | None = None,
|
||||
component_node_ids_by_representative: dict[str, list[str]] | None = None,
|
||||
active_node_id: str | None = None,
|
||||
active_node_class_type: str | None = None,
|
||||
@@ -266,6 +267,8 @@ def _emit_modal_status(
|
||||
"prompt_id": prompt_id,
|
||||
"node_ids": list(node_ids),
|
||||
}
|
||||
if modal_gpu is not None:
|
||||
payload["modal_gpu"] = modal_gpu
|
||||
if component_node_ids_by_representative:
|
||||
payload["components"] = [
|
||||
{
|
||||
@@ -4249,6 +4252,7 @@ def setup_modal_queue_route(
|
||||
json_data: dict[str, Any] | None = None
|
||||
workflow: dict[str, Any] | None = None
|
||||
remote_node_ids: list[str] = []
|
||||
request_modal_gpu: str | None = None
|
||||
summary = RewriteSummary()
|
||||
try:
|
||||
request_started_at = time.perf_counter()
|
||||
@@ -4293,6 +4297,7 @@ def setup_modal_queue_route(
|
||||
request_settings.modal_gpu,
|
||||
prompt_id,
|
||||
)
|
||||
request_modal_gpu = request_settings.modal_gpu
|
||||
|
||||
def emit_setup_status(
|
||||
message: str,
|
||||
@@ -4306,6 +4311,7 @@ def setup_modal_queue_route(
|
||||
client_id=client_id,
|
||||
prompt_id=prompt_id,
|
||||
node_ids=remote_node_ids,
|
||||
modal_gpu=request_settings.modal_gpu,
|
||||
component_node_ids_by_representative=(
|
||||
summary.component_node_ids_by_representative or None
|
||||
),
|
||||
@@ -4369,6 +4375,7 @@ def setup_modal_queue_route(
|
||||
client_id=client_id,
|
||||
prompt_id=prompt_id,
|
||||
node_ids=remote_node_ids,
|
||||
modal_gpu=request_settings.modal_gpu,
|
||||
component_node_ids_by_representative=summary.component_node_ids_by_representative,
|
||||
status_message="Submitting Modal workflow",
|
||||
)
|
||||
@@ -4377,6 +4384,7 @@ def setup_modal_queue_route(
|
||||
json_data,
|
||||
modal_response_payload=(
|
||||
{
|
||||
"modal_gpu": request_settings.modal_gpu,
|
||||
"modal_remote_node_ids": list(summary.remote_node_ids),
|
||||
"modal_components": [
|
||||
{
|
||||
@@ -4406,6 +4414,7 @@ def setup_modal_queue_route(
|
||||
client_id=str(json_data.get("client_id")) if json_data.get("client_id") else None,
|
||||
prompt_id=str(json_data.get("prompt_id")) if json_data.get("prompt_id") else None,
|
||||
node_ids=remote_node_ids,
|
||||
modal_gpu=request_modal_gpu,
|
||||
error_message=str(exc),
|
||||
)
|
||||
return web.json_response({"error": str(exc), "node_errors": []}, status=400)
|
||||
@@ -4418,6 +4427,7 @@ def setup_modal_queue_route(
|
||||
client_id=str(json_data.get("client_id")) if json_data.get("client_id") else None,
|
||||
prompt_id=str(json_data.get("prompt_id")) if json_data.get("prompt_id") else None,
|
||||
node_ids=remote_node_ids,
|
||||
modal_gpu=request_modal_gpu,
|
||||
error_message=str(exc),
|
||||
)
|
||||
return web.json_response({"error": str(exc), "node_errors": []}, status=400)
|
||||
@@ -4430,6 +4440,7 @@ def setup_modal_queue_route(
|
||||
client_id=str(json_data.get("client_id")) if json_data.get("client_id") else None,
|
||||
prompt_id=str(json_data.get("prompt_id")) if json_data.get("prompt_id") else None,
|
||||
node_ids=remote_node_ids,
|
||||
modal_gpu=request_modal_gpu,
|
||||
error_message=str(exc),
|
||||
)
|
||||
return web.json_response({"error": str(exc), "node_errors": []}, status=500)
|
||||
|
||||
@@ -323,6 +323,7 @@
|
||||
- Compatibility wrappers around ComfyUI callables must transparently forward positional and keyword arguments. ComfyUI added `disable_dynamic` to `load_diffusion_model_state_dict`, and an older fixed-signature wrapper caused every GPU-snapshot container to fail its UNET loader prewarm and be restarted by Modal before it could serve the request.
|
||||
- Modal GPU selection and the image's PyTorch CUDA wheel selection are one compatibility decision. B300 requires CUDA 13.1 or newer, so `B300` and upgradeable `B200+` specifications must select the CUDA 13.2 wheel index instead of inheriting the CUDA 12.8 default; count suffixes such as `B300:2` should not affect that choice. Record the resolved build in both logs and the runtime fingerprint so an incompatible image cannot masquerade as the expected deployment.
|
||||
- A runtime-fingerprint method on a GPU-backed Modal class is not a metadata-only probe: invoking it can provision that class's configured GPU. Resolve a GPU-specific persistent app name before lookup so a B300 workflow never contacts an A100 class, while retaining the stable instance-derived Volume and Dict names for shared assets and caches.
|
||||
- Global execution UI must retain the selected GPU as prompt-scoped state rather than reread the live workflow selection on every redraw. Users can change the workflow GPU while an older prompt is still running, and replayed backend status events need to carry the authoritative target so the pill cannot relabel active work.
|
||||
- A ComfyUI `LATENT` is not guaranteed to contain one plain tensor under `samples`. Multimodal nodes such as MiniMax H3 use `comfy.nested_tensor.NestedTensor` to retain ordered video and audio tensors, so boundary transport must encode that wrapper explicitly and reconstruct it after transferring each tensor member.
|
||||
- Remote status maps use executable prompt paths such as `105:14`, while an inner node opened in the subgraph canvas still has the local id `14`. Node decoration must resolve `workflowNodePath(node)` before reading phase, progress, cache, component, or badge state; otherwise only the outer subgraph instance can reflect descendant execution.
|
||||
- Files written by remote ComfyUI output nodes live on the Modal worker's ephemeral filesystem and must be captured before the invocation returns. Snapshot the effective `folder_paths` output tree immediately before execution, bundle new or replaced regular files into the durable invocation result, and materialize them locally with validated relative paths, content digests, atomic no-overwrite publication, and stable retry naming. Returning the encoded artifact is substantially smaller than returning its decoded `IMAGE` or `VIDEO` tensor.
|
||||
|
||||
@@ -2740,6 +2740,7 @@ def _emit_local_modal_status(
|
||||
client_id: str | None,
|
||||
phase: str,
|
||||
node_ids: list[str],
|
||||
modal_gpu: str | None = None,
|
||||
active_node_id: str | None = None,
|
||||
active_node_class_type: str | None = None,
|
||||
active_node_role: str | None = None,
|
||||
@@ -2762,6 +2763,8 @@ def _emit_local_modal_status(
|
||||
"prompt_id": prompt_id,
|
||||
"node_ids": list(node_ids),
|
||||
}
|
||||
if modal_gpu is not None:
|
||||
payload["modal_gpu"] = modal_gpu
|
||||
if active_node_id is not None:
|
||||
payload["active_node_id"] = active_node_id
|
||||
if active_node_class_type is not None:
|
||||
@@ -2811,6 +2814,11 @@ def _emit_local_remote_startup_status(
|
||||
client_id=client_id,
|
||||
phase=phase,
|
||||
node_ids=node_ids,
|
||||
modal_gpu=(
|
||||
str(payload["modal_gpu"])
|
||||
if payload.get("modal_gpu") is not None
|
||||
else None
|
||||
),
|
||||
status_message=status_message,
|
||||
)
|
||||
|
||||
@@ -3520,6 +3528,7 @@ def _consume_remote_payload_stream(
|
||||
extra_data = payload.get("extra_data") or {}
|
||||
client_id = str(extra_data.get("client_id")) if extra_data.get("client_id") is not None else None
|
||||
node_ids = [str(node_id) for node_id in payload.get("component_node_ids", [])]
|
||||
modal_gpu = str(payload["modal_gpu"]) if payload.get("modal_gpu") is not None else None
|
||||
suppress_status_stream = bool(payload.get("suppress_status_stream"))
|
||||
result_payload: bytes | bytearray | None = None
|
||||
suppressed_progress_node_metadata: dict[str, dict[str, str | None]] = {}
|
||||
@@ -3794,6 +3803,7 @@ def _consume_remote_payload_stream(
|
||||
client_id=client_id,
|
||||
phase="finalizing",
|
||||
node_ids=node_ids,
|
||||
modal_gpu=modal_gpu,
|
||||
status_message="Receiving Modal outputs",
|
||||
)
|
||||
continue
|
||||
@@ -3802,6 +3812,7 @@ def _consume_remote_payload_stream(
|
||||
client_id=client_id,
|
||||
phase=remote_phase,
|
||||
node_ids=node_ids,
|
||||
modal_gpu=modal_gpu,
|
||||
active_node_id=(
|
||||
str(stream_event["active_node_id"])
|
||||
if stream_event.get("active_node_id") is not None
|
||||
|
||||
@@ -85,6 +85,7 @@ function resetFrontendState() {
|
||||
modalToggle.modalPromptStates.clear();
|
||||
modalToggle.modalTerminalPromptStates.clear();
|
||||
modalToggle.modalQueuedPromptIds.clear();
|
||||
modalToggle.modalGlobalStatusStates.clear();
|
||||
}
|
||||
|
||||
resetFrontendState();
|
||||
@@ -543,6 +544,30 @@ const serializedWorkflow = { nodes: [] };
|
||||
modalToggle.stampModalGpuOnWorkflow(serializedWorkflow);
|
||||
assert.equal(serializedWorkflow.extra.comfy_modal.gpu, "B300");
|
||||
|
||||
resetFrontendState();
|
||||
modalToggle.handleModalStatus({
|
||||
detail: {
|
||||
prompt_id: "prompt-gpu-status",
|
||||
phase: "setup",
|
||||
node_ids: ["70"],
|
||||
modal_gpu: "B300",
|
||||
status_message: "Rebuilding Modal app",
|
||||
},
|
||||
});
|
||||
assert.equal(modalToggle.currentGlobalStatus()?.statusMessage, "Rebuilding Modal app");
|
||||
assert.equal(modalToggle.currentGlobalStatus()?.modalGpu, "B300");
|
||||
modalToggle.handleModalProgress({
|
||||
detail: {
|
||||
prompt_id: "prompt-gpu-status",
|
||||
node_id: "70",
|
||||
display_node_id: "70",
|
||||
real_node_id: "70",
|
||||
value: 1,
|
||||
max: 4,
|
||||
},
|
||||
});
|
||||
assert.equal(modalToggle.currentGlobalStatus()?.modalGpu, "B300");
|
||||
|
||||
class MenuNode {}
|
||||
const menuNode = new MenuNode();
|
||||
menuNode.id = 101;
|
||||
|
||||
@@ -656,6 +656,7 @@ def test_queue_prompt_route_does_not_warm_modal_at_queue_time(
|
||||
|
||||
response_payload = json.loads(response.text)
|
||||
assert response_payload["prompt_id"] == "prompt-queue-warmup"
|
||||
assert response_payload["modal_gpu"] == "B300"
|
||||
assert response_payload["modal_remote_node_ids"] == ["1"]
|
||||
assert observed_rewrite_settings[0].modal_gpu == "B300"
|
||||
queued_extra_data = prompt_server.prompt_queue.items[0][3]
|
||||
@@ -3885,6 +3886,7 @@ def test_emit_modal_status_targets_prompt_client(
|
||||
client_id="client-1",
|
||||
prompt_id="prompt-1",
|
||||
node_ids=["4", "5"],
|
||||
modal_gpu="B300",
|
||||
component_node_ids_by_representative={"4": ["4", "5"]},
|
||||
active_node_id="5",
|
||||
active_node_class_type="KSampler",
|
||||
@@ -3898,6 +3900,7 @@ def test_emit_modal_status_targets_prompt_client(
|
||||
"phase": "executing",
|
||||
"prompt_id": "prompt-1",
|
||||
"node_ids": ["4", "5"],
|
||||
"modal_gpu": "B300",
|
||||
"active_node_id": "5",
|
||||
"active_node_class_type": "KSampler",
|
||||
"active_node_role": "sampling",
|
||||
@@ -3919,6 +3922,7 @@ def test_emit_modal_status_targets_prompt_client(
|
||||
"phase": "executing",
|
||||
"prompt_id": "prompt-1",
|
||||
"node_ids": ["4", "5"],
|
||||
"modal_gpu": "B300",
|
||||
"active_node_id": "5",
|
||||
"active_node_class_type": "KSampler",
|
||||
"active_node_role": "sampling",
|
||||
|
||||
@@ -5800,6 +5800,7 @@ def test_remote_modal_consumes_streamed_progress_and_result(
|
||||
"prompt_id": "prompt-1",
|
||||
"component_id": "component-1",
|
||||
"component_node_ids": ["7", "8"],
|
||||
"modal_gpu": "B300",
|
||||
"extra_data": {"client_id": "client-1"},
|
||||
}
|
||||
result = remote_modal_app_module._consume_remote_payload_stream(
|
||||
@@ -5841,6 +5842,7 @@ def test_remote_modal_consumes_streamed_progress_and_result(
|
||||
"phase": "executing",
|
||||
"prompt_id": "prompt-1",
|
||||
"node_ids": ["7", "8"],
|
||||
"modal_gpu": "B300",
|
||||
"active_node_id": "7",
|
||||
"active_node_class_type": "UNETLoader",
|
||||
"active_node_role": "model_load",
|
||||
@@ -5864,6 +5866,7 @@ def test_remote_modal_consumes_streamed_progress_and_result(
|
||||
"phase": "finalizing",
|
||||
"prompt_id": "prompt-1",
|
||||
"node_ids": ["7", "8"],
|
||||
"modal_gpu": "B300",
|
||||
"status_message": "Receiving Modal outputs",
|
||||
},
|
||||
"client-1",
|
||||
@@ -5896,6 +5899,7 @@ def test_emit_local_remote_dispatch_status_marks_component_starting(
|
||||
"prompt_id": "prompt-1",
|
||||
"component_id": "component-1",
|
||||
"component_node_ids": ["7", "8"],
|
||||
"modal_gpu": "B300",
|
||||
"extra_data": {"client_id": "client-1"},
|
||||
}
|
||||
)
|
||||
@@ -5907,6 +5911,7 @@ def test_emit_local_remote_dispatch_status_marks_component_starting(
|
||||
"phase": "starting",
|
||||
"prompt_id": "prompt-1",
|
||||
"node_ids": ["7", "8"],
|
||||
"modal_gpu": "B300",
|
||||
"status_message": "Starting Modal component",
|
||||
},
|
||||
"client-1",
|
||||
|
||||
@@ -34,6 +34,9 @@ def test_global_modal_status_badge_is_installed() -> None:
|
||||
source = _modal_toggle_source()
|
||||
|
||||
assert 'element.id = "comfy-modal-global-status";' in source
|
||||
assert 'class="modal-status-gpu" hidden' in source
|
||||
assert "gpuText.textContent = activeState.modalGpu ?? \"\";" in source
|
||||
assert "font-size: 10px;" in source
|
||||
assert "Syncing graph with Modal" in source
|
||||
assert "Waiting for Modal startup" in source
|
||||
assert "Receiving Modal outputs" in source
|
||||
@@ -44,6 +47,7 @@ def test_global_modal_status_badge_is_installed() -> None:
|
||||
assert "statusMessage: state.statusMessage ?? null," in source
|
||||
assert "statusCurrent: state.statusCurrent ?? null," in source
|
||||
assert "statusTotal: state.statusTotal ?? null," in source
|
||||
assert "modalGpu: state.modalGpu ?? null," in source
|
||||
assert "linear-gradient(90deg" in source
|
||||
assert "installGlobalStatusStyles()" in source
|
||||
assert "function pruneGlobalStatusStates()" in source
|
||||
|
||||
+61
-10
@@ -230,7 +230,7 @@ function ensureGlobalStatusElement() {
|
||||
element.style.fontWeight = "600";
|
||||
element.style.pointerEvents = "none";
|
||||
element.innerHTML =
|
||||
'<span class="modal-status-dot"></span><span class="modal-status-text"></span>';
|
||||
'<span class="modal-status-dot"></span><span class="modal-status-copy"><span class="modal-status-text"></span><span class="modal-status-gpu" hidden></span></span>';
|
||||
document.body.appendChild(element);
|
||||
modalGlobalStatusElement = element;
|
||||
return element;
|
||||
@@ -442,7 +442,7 @@ function reconcilePromptGlobalStatus(promptId) {
|
||||
|
||||
/**
|
||||
* Return the most important active global Modal state.
|
||||
* @returns {{ phase: string, promptId: string, nodeCount: number } | null}
|
||||
* @returns {{ phase: string, promptId: string, nodeCount: number, modalGpu: string | null } | null}
|
||||
*/
|
||||
function currentGlobalStatus() {
|
||||
pruneGlobalStatusStates();
|
||||
@@ -459,6 +459,7 @@ function currentGlobalStatus() {
|
||||
statusMessage: state.statusMessage ?? null,
|
||||
statusCurrent: state.statusCurrent ?? null,
|
||||
statusTotal: state.statusTotal ?? null,
|
||||
modalGpu: state.modalGpu ?? null,
|
||||
updatedAt: state.updatedAt,
|
||||
}));
|
||||
phases.sort((left, right) => right.updatedAt - left.updatedAt);
|
||||
@@ -488,11 +489,13 @@ function refreshGlobalStatusElement() {
|
||||
if (!activeState) {
|
||||
element.style.display = "none";
|
||||
element.dataset.phase = "";
|
||||
element.dataset.modalGpu = "";
|
||||
return;
|
||||
}
|
||||
|
||||
const dot = element.querySelector(".modal-status-dot");
|
||||
const text = element.querySelector(".modal-status-text");
|
||||
const gpuText = element.querySelector(".modal-status-gpu");
|
||||
const nodeLabel = activeState.nodeCount === 1 ? "node" : "nodes";
|
||||
const hasBatchProgress =
|
||||
activeState.phase === EXECUTION_PHASE &&
|
||||
@@ -514,6 +517,9 @@ function refreshGlobalStatusElement() {
|
||||
|
||||
element.style.display = "inline-flex";
|
||||
element.dataset.phase = activeState.phase;
|
||||
element.dataset.modalGpu = activeState.modalGpu ?? "";
|
||||
gpuText.textContent = activeState.modalGpu ?? "";
|
||||
gpuText.hidden = !activeState.modalGpu;
|
||||
|
||||
if (activeState.phase === STATE_SETUP) {
|
||||
element.style.borderColor = "rgba(245, 158, 11, 0.55)";
|
||||
@@ -594,7 +600,7 @@ function refreshGlobalStatusElement() {
|
||||
* @param {string} promptId
|
||||
* @param {string} phase
|
||||
* @param {number} nodeCount
|
||||
* @param {{ message?: string | null, current?: number | null, total?: number | null } | null} details
|
||||
* @param {{ message?: string | null, current?: number | null, total?: number | null, modalGpu?: string | null } | null} details
|
||||
*/
|
||||
function setGlobalStatusPhase(promptId, phase, nodeCount, details = null) {
|
||||
if (!promptId) {
|
||||
@@ -604,6 +610,11 @@ function setGlobalStatusPhase(promptId, phase, nodeCount, details = null) {
|
||||
return;
|
||||
}
|
||||
const existingState = modalGlobalStatusStates.get(promptId);
|
||||
const promptState = ensurePromptState(promptId);
|
||||
const detailModalGpu = MODAL_GPU_TYPES.includes(details?.modalGpu) ? details.modalGpu : null;
|
||||
if (promptState && detailModalGpu) {
|
||||
promptState.modalGpu = detailModalGpu;
|
||||
}
|
||||
modalGlobalStatusStates.set(promptId, {
|
||||
phase: effectiveGlobalStatusPhase(promptId, phase),
|
||||
nodeCount: promptRemoteNodeCount(promptId, nodeCount),
|
||||
@@ -612,6 +623,7 @@ function setGlobalStatusPhase(promptId, phase, nodeCount, details = null) {
|
||||
statusMessage: details?.message ?? existingState?.statusMessage ?? null,
|
||||
statusCurrent: details?.current ?? existingState?.statusCurrent ?? null,
|
||||
statusTotal: details?.total ?? existingState?.statusTotal ?? null,
|
||||
modalGpu: detailModalGpu ?? existingState?.modalGpu ?? promptState?.modalGpu ?? null,
|
||||
updatedAt: nowMs(),
|
||||
});
|
||||
refreshGlobalStatusElement();
|
||||
@@ -641,6 +653,8 @@ function setGlobalStatusBatchProgress(promptId, value, maxValue) {
|
||||
statusMessage: existingState?.statusMessage ?? null,
|
||||
statusCurrent: existingState?.statusCurrent ?? null,
|
||||
statusTotal: existingState?.statusTotal ?? null,
|
||||
modalGpu:
|
||||
existingState?.modalGpu ?? modalPromptStates.get(promptId)?.modalGpu ?? null,
|
||||
updatedAt: nowMs(),
|
||||
});
|
||||
refreshGlobalStatusElement();
|
||||
@@ -1024,7 +1038,7 @@ function refreshModalUiAfterVisibilityChange() {
|
||||
/**
|
||||
* Return the prompt metadata bucket, creating it if needed.
|
||||
* @param {string} promptId
|
||||
* @returns {{ startedAt: number, remoteNodeIds: string[], componentsByRepresentative: Map<string, string[]>, componentNodeIdsByMember: Map<string, string[]>, representativeNodeIdByMember: Map<string, string>, componentLabelByMember: Map<string, string>, laneNodeIdsByLane: Map<string, string> }}
|
||||
* @returns {{ startedAt: number, modalGpu: string | null, remoteNodeIds: string[], componentsByRepresentative: Map<string, string[]>, componentNodeIdsByMember: Map<string, string[]>, representativeNodeIdByMember: Map<string, string>, componentLabelByMember: Map<string, string>, laneNodeIdsByLane: Map<string, string> }}
|
||||
*/
|
||||
function ensurePromptState(promptId) {
|
||||
if (isPromptTerminal(promptId)) {
|
||||
@@ -1034,6 +1048,7 @@ function ensurePromptState(promptId) {
|
||||
const startedAt = modalReplayedEventUpdatedAtMs ?? nowMs();
|
||||
modalPromptStates.set(promptId, {
|
||||
startedAt,
|
||||
modalGpu: null,
|
||||
remoteNodeIds: [],
|
||||
componentsByRepresentative: new Map(),
|
||||
componentNodeIdsByMember: new Map(),
|
||||
@@ -3120,16 +3135,21 @@ function handleModalStatus(event) {
|
||||
if (!promptState) {
|
||||
return;
|
||||
}
|
||||
const modalGpu = MODAL_GPU_TYPES.includes(detail.modal_gpu) ? detail.modal_gpu : null;
|
||||
if (modalGpu) {
|
||||
promptState.modalGpu = modalGpu;
|
||||
}
|
||||
|
||||
if (detail.phase === STATE_SETUP) {
|
||||
if (isPromptQueuedBehindActiveModal(promptId)) {
|
||||
return;
|
||||
}
|
||||
beginSyntheticExecutionUi(promptId, nodeIds);
|
||||
beginSyntheticExecutionUi(promptId, nodeIds, modalGpu);
|
||||
setGlobalStatusPhase(promptId, STATE_SETUP, nodeIds.length, {
|
||||
message: detail.status_message ?? null,
|
||||
current: detail.status_current ?? null,
|
||||
total: detail.status_total ?? null,
|
||||
modalGpu,
|
||||
});
|
||||
setPromptActiveNode(promptId, null);
|
||||
setNodesPhase(nodeIds, STATE_SETUP, promptId);
|
||||
@@ -3144,6 +3164,7 @@ function handleModalStatus(event) {
|
||||
message: detail.status_message ?? null,
|
||||
current: detail.status_current ?? null,
|
||||
total: detail.status_total ?? null,
|
||||
modalGpu,
|
||||
});
|
||||
setPromptActiveNode(promptId, null);
|
||||
return;
|
||||
@@ -3157,6 +3178,7 @@ function handleModalStatus(event) {
|
||||
message: detail.status_message ?? "Starting Modal component",
|
||||
current: detail.status_current ?? null,
|
||||
total: detail.status_total ?? null,
|
||||
modalGpu,
|
||||
});
|
||||
setPromptActiveNode(promptId, null);
|
||||
setNodesPhase(nodeIds, STATE_STARTING, promptId);
|
||||
@@ -3169,6 +3191,7 @@ function handleModalStatus(event) {
|
||||
message: detail.status_message ?? null,
|
||||
current: detail.status_current ?? null,
|
||||
total: detail.status_total ?? null,
|
||||
modalGpu,
|
||||
});
|
||||
setPromptActiveNode(promptId, null);
|
||||
setNodesPhase(nodeIds, STATE_FINALIZING, promptId);
|
||||
@@ -3178,7 +3201,7 @@ function handleModalStatus(event) {
|
||||
if (detail.phase === STATE_ERROR) {
|
||||
clearPromptQueued(promptId);
|
||||
endSyntheticExecutionUi(promptId, true);
|
||||
setGlobalStatusPhase(promptId, STATE_ERROR, nodeIds.length);
|
||||
setGlobalStatusPhase(promptId, STATE_ERROR, nodeIds.length, { modalGpu });
|
||||
setTimeout(() => clearGlobalStatusPhase(promptId), ERROR_CLEAR_DELAY_MS);
|
||||
clearPromptRemoteNodeVisuals(promptId);
|
||||
markPromptTerminal(promptId, STATE_ERROR);
|
||||
@@ -3202,10 +3225,11 @@ function handleModalStatus(event) {
|
||||
promptState.hasStreamedProgress = true;
|
||||
if (nextActiveNodeId) {
|
||||
promptState.hasRemoteExecutionStarted = true;
|
||||
setGlobalStatusPhase(promptId, EXECUTION_PHASE, nodeIds.length);
|
||||
setGlobalStatusPhase(promptId, EXECUTION_PHASE, nodeIds.length, { modalGpu });
|
||||
} else {
|
||||
setGlobalStatusPhase(promptId, STATE_WAITING, nodeIds.length, {
|
||||
message: detail.status_message ?? "Waiting for Modal container",
|
||||
modalGpu,
|
||||
});
|
||||
}
|
||||
setNodesPhase(nodeIds, STATE_READY, promptId);
|
||||
@@ -3549,8 +3573,9 @@ function statusPayload(queueRemaining) {
|
||||
* Start a synthetic running state so ComfyUI shows active execution while the Modal route is still preparing.
|
||||
* @param {string} promptId
|
||||
* @param {string[]} remoteNodeIds
|
||||
* @param {string | null} modalGpu
|
||||
*/
|
||||
function beginSyntheticExecutionUi(promptId, remoteNodeIds) {
|
||||
function beginSyntheticExecutionUi(promptId, remoteNodeIds, modalGpu = null) {
|
||||
if (remoteNodeIds.length === 0 || syntheticPromptUiStates.has(promptId)) {
|
||||
return;
|
||||
}
|
||||
@@ -3558,7 +3583,7 @@ function beginSyntheticExecutionUi(promptId, remoteNodeIds) {
|
||||
|
||||
const displayNode = remoteNodeIds[0];
|
||||
syntheticPromptUiStates.set(promptId, { displayNode });
|
||||
setGlobalStatusPhase(promptId, STATE_SETUP, remoteNodeIds.length);
|
||||
setGlobalStatusPhase(promptId, STATE_SETUP, remoteNodeIds.length, { modalGpu });
|
||||
dispatchSyntheticApiEvent("status", statusPayload(1));
|
||||
dispatchSyntheticApiEvent("notification", {
|
||||
id: promptId,
|
||||
@@ -3670,6 +3695,7 @@ function patchQueuePrompt() {
|
||||
api.queuePrompt = async function modalQueuePrompt(number, data, options) {
|
||||
const { output: prompt, workflow } = data;
|
||||
stampModalGpuOnWorkflow(workflow);
|
||||
const modalGpu = selectedModalGpu(workflow);
|
||||
const promptId = createPromptId();
|
||||
clearPromptTerminal(promptId);
|
||||
clearSupersededCancellingPrompts(promptId);
|
||||
@@ -3680,7 +3706,7 @@ function patchQueuePrompt() {
|
||||
if (remoteNodeIds.length > 0) {
|
||||
if (!queuedBehindActiveModal) {
|
||||
setNodesPhase(remoteNodeIds, STATE_SETUP, promptId);
|
||||
beginSyntheticExecutionUi(promptId, remoteNodeIds);
|
||||
beginSyntheticExecutionUi(promptId, remoteNodeIds, modalGpu);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3719,6 +3745,9 @@ function patchQueuePrompt() {
|
||||
|
||||
const responsePayload = await response.json();
|
||||
if (remoteNodeIds.length > 0) {
|
||||
const acceptedModalGpu = MODAL_GPU_TYPES.includes(responsePayload.modal_gpu)
|
||||
? responsePayload.modal_gpu
|
||||
: modalGpu;
|
||||
const resolvedRemoteNodeIds = (responsePayload.modal_remote_node_ids ?? []).map((nodeIdValue) =>
|
||||
String(nodeIdValue),
|
||||
);
|
||||
@@ -3735,6 +3764,7 @@ function patchQueuePrompt() {
|
||||
endSyntheticExecutionUi(promptId);
|
||||
setGlobalStatusPhase(promptId, STATE_WAITING, acceptedRemoteNodeIds.length, {
|
||||
message: "Waiting for Modal startup",
|
||||
modalGpu: acceptedModalGpu,
|
||||
});
|
||||
setNodesPhase(acceptedRemoteNodeIds, STATE_READY, promptId);
|
||||
}
|
||||
@@ -3769,6 +3799,27 @@ function installGlobalStatusStyles() {
|
||||
100% { transform: scale(0.9); opacity: 0.7; }
|
||||
}
|
||||
|
||||
#comfy-modal-global-status .modal-status-copy {
|
||||
display: flex;
|
||||
min-width: 0;
|
||||
flex-direction: column;
|
||||
align-items: flex-start;
|
||||
line-height: 1.15;
|
||||
}
|
||||
|
||||
#comfy-modal-global-status .modal-status-gpu {
|
||||
margin-top: 3px;
|
||||
color: rgba(226, 232, 240, 0.76);
|
||||
font-size: 10px;
|
||||
font-weight: 500;
|
||||
letter-spacing: 0.04em;
|
||||
line-height: 1;
|
||||
}
|
||||
|
||||
#comfy-modal-global-status .modal-status-gpu[hidden] {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.comfy-modal-vue-node-decoration {
|
||||
position: absolute;
|
||||
inset: -3px;
|
||||
|
||||
Reference in New Issue
Block a user