Files
Artificial-Sweetener-Simple…/simple_syrup/runtime/progress.py
T

109 lines
3.1 KiB
Python

# SimpleSyrup - workflow-focused ComfyUI extensions for image generation
# Copyright (C) 2026 Artificial Sweetener and contributors
# SPDX-License-Identifier: AGPL-3.0-or-later
"""Small progress reporting boundary for ComfyUI node work."""
from __future__ import annotations
from dataclasses import dataclass
from importlib import import_module
from typing import Protocol, cast
from ..shared.logging import get_logger
LOGGER = get_logger(__name__)
class ProgressReporter(Protocol):
"""Progress sink compatible with ComfyUI progress bars."""
def update(self, value: int) -> None:
"""Advance progress by value."""
class NullProgressReporter:
"""Ignore progress updates when no UI progress sink is available."""
def update(self, value: int) -> None:
"""Ignore progress updates."""
class PhaseProgressReporter(Protocol):
"""Report named phases for a bounded long-running operation."""
def advance(self, phase: str) -> None:
"""Report that the operation entered ``phase``."""
class NullPhaseProgressReporter:
"""Ignore named phase updates outside a Comfy execution context."""
def advance(self, phase: str) -> None:
"""Ignore one phase update."""
@dataclass
class ComfyPhaseProgressReporter:
"""Publish truthful phase boundaries through standard Comfy progress events."""
operation: str
subject: str
total_phases: int
def __post_init__(self) -> None:
"""Create a lazily imported Comfy progress bar and initialize state."""
if self.total_phases <= 0:
raise ValueError("total_phases must be positive.")
comfy_utils = import_module("comfy.utils")
self._progress_bar = comfy_utils.ProgressBar(self.total_phases)
self._completed_phases = 0
def advance(self, phase: str) -> None:
"""Publish one phase transition without inventing intra-phase percentages."""
self._completed_phases = (
self.total_phases
if phase == "completed"
else min(self.total_phases - 1, self._completed_phases + 1)
)
self._progress_bar.update_absolute(
self._completed_phases,
self.total_phases,
)
LOGGER.info(
"Long operation entered phase",
extra={
"operation": self.operation,
"subject": self.subject,
"phase": phase,
"completed_phases": self._completed_phases,
"total_phases": self.total_phases,
},
)
def create_comfy_progress(total: int) -> ProgressReporter:
"""Return a ComfyUI progress bar for total units of work."""
if total <= 0:
return NullProgressReporter()
comfy_utils = import_module("comfy.utils")
return cast(ProgressReporter, comfy_utils.ProgressBar(total))
def create_comfy_phase_progress(
*,
operation: str,
subject: str,
total_phases: int,
) -> PhaseProgressReporter:
"""Return a standard Comfy progress publisher for named phases."""
return ComfyPhaseProgressReporter(
operation=operation,
subject=subject,
total_phases=total_phases,
)