mirror of
https://github.com/invoke-ai/InvokeAI
synced 2024-08-30 20:32:17 +00:00
feat(app): generic progress events
Some processes have steps, like denoising or a tiled spandel. Denoising has its own step callback but we don't have any generic way to signal progress. Processes like a tiled spandrel run show indeterminate progress in the client. This change introduces a new event to handle this: `InvocationGenericProgressEvent` A simplified helper is added to the invocation API so nodes can easily emit progress as they do their thing.
This commit is contained in:
parent
571ba87e13
commit
487815b181
@ -22,6 +22,7 @@ from invokeai.app.services.events.events_common import (
|
||||
InvocationCompleteEvent,
|
||||
InvocationDenoiseProgressEvent,
|
||||
InvocationErrorEvent,
|
||||
InvocationGenericProgressEvent,
|
||||
InvocationStartedEvent,
|
||||
ModelEventBase,
|
||||
ModelInstallCancelledEvent,
|
||||
@ -56,6 +57,7 @@ class BulkDownloadSubscriptionEvent(BaseModel):
|
||||
QUEUE_EVENTS = {
|
||||
InvocationStartedEvent,
|
||||
InvocationDenoiseProgressEvent,
|
||||
InvocationGenericProgressEvent,
|
||||
InvocationCompleteEvent,
|
||||
InvocationErrorEvent,
|
||||
QueueItemStatusChangedEvent,
|
||||
|
@ -3,6 +3,8 @@
|
||||
|
||||
from typing import TYPE_CHECKING, Optional
|
||||
|
||||
from PIL.Image import Image as PILImageType
|
||||
|
||||
from invokeai.app.services.events.events_common import (
|
||||
BatchEnqueuedEvent,
|
||||
BulkDownloadCompleteEvent,
|
||||
@ -17,6 +19,7 @@ from invokeai.app.services.events.events_common import (
|
||||
InvocationCompleteEvent,
|
||||
InvocationDenoiseProgressEvent,
|
||||
InvocationErrorEvent,
|
||||
InvocationGenericProgressEvent,
|
||||
InvocationStartedEvent,
|
||||
ModelInstallCancelledEvent,
|
||||
ModelInstallCompleteEvent,
|
||||
@ -58,6 +61,29 @@ class EventServiceBase:
|
||||
"""Emitted when an invocation is started"""
|
||||
self.dispatch(InvocationStartedEvent.build(queue_item, invocation))
|
||||
|
||||
def emit_invocation_generic_progress(
|
||||
self,
|
||||
queue_item: "SessionQueueItem",
|
||||
invocation: "BaseInvocation",
|
||||
name: str,
|
||||
step: int | None = None,
|
||||
total_steps: int | None = None,
|
||||
message: str | None = None,
|
||||
image: PILImageType | None = None,
|
||||
) -> None:
|
||||
"""Emitted at each step during an invocation"""
|
||||
self.dispatch(
|
||||
InvocationGenericProgressEvent.build(
|
||||
queue_item,
|
||||
invocation,
|
||||
name,
|
||||
step,
|
||||
total_steps,
|
||||
message,
|
||||
image,
|
||||
)
|
||||
)
|
||||
|
||||
def emit_invocation_denoise_progress(
|
||||
self,
|
||||
queue_item: "SessionQueueItem",
|
||||
|
@ -3,7 +3,8 @@ from typing import TYPE_CHECKING, Any, ClassVar, Coroutine, Generic, Optional, P
|
||||
|
||||
from fastapi_events.handlers.local import local_handler
|
||||
from fastapi_events.registry.payload_schema import registry as payload_schema
|
||||
from pydantic import BaseModel, ConfigDict, Field
|
||||
from PIL.Image import Image as PILImageType
|
||||
from pydantic import BaseModel, ConfigDict, Field, model_validator
|
||||
|
||||
from invokeai.app.services.session_processor.session_processor_common import ProgressImage
|
||||
from invokeai.app.services.session_queue.session_queue_common import (
|
||||
@ -17,6 +18,7 @@ from invokeai.app.services.shared.graph import AnyInvocation, AnyInvocationOutpu
|
||||
from invokeai.app.util.misc import get_timestamp
|
||||
from invokeai.backend.model_manager.config import AnyModelConfig, SubModelType
|
||||
from invokeai.backend.stable_diffusion.diffusers_pipeline import PipelineIntermediateState
|
||||
from invokeai.backend.util.util import image_to_dataURL
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from invokeai.app.services.download.download_base import DownloadJob
|
||||
@ -120,6 +122,65 @@ class InvocationStartedEvent(InvocationEventBase):
|
||||
)
|
||||
|
||||
|
||||
@payload_schema.register
|
||||
class InvocationGenericProgressEvent(InvocationEventBase):
|
||||
"""Event model for invocation_generic_progress"""
|
||||
|
||||
__event_name__ = "invocation_generic_progress"
|
||||
|
||||
name: str = Field(description="The name of the progress type")
|
||||
step: int | None = Field(
|
||||
default=None,
|
||||
description="The current step. Omit for indeterminate progress.",
|
||||
)
|
||||
total_steps: int | None = Field(
|
||||
default=None,
|
||||
description="The total number of steps. Omit for indeterminate progress.",
|
||||
)
|
||||
image: ProgressImage | None = Field(default=None, description="An image sent at each step during processing")
|
||||
message: str | None = Field(default=None, description="A message to display with the progress")
|
||||
|
||||
@model_validator(mode="after")
|
||||
def validate_step_total_steps(self):
|
||||
if (self.step is None) is not (self.total_steps is None):
|
||||
raise ValueError("must provide both step and total_steps or neither")
|
||||
return self
|
||||
|
||||
@classmethod
|
||||
def build(
|
||||
cls,
|
||||
queue_item: SessionQueueItem,
|
||||
invocation: AnyInvocation,
|
||||
name: str,
|
||||
step: int | None = None,
|
||||
total_steps: int | None = None,
|
||||
message: str | None = None,
|
||||
image: PILImageType | None = None,
|
||||
) -> "InvocationGenericProgressEvent":
|
||||
image_ = (
|
||||
ProgressImage(
|
||||
dataURL=image_to_dataURL(image, image_format="JPEG"),
|
||||
width=image.width,
|
||||
height=image.height,
|
||||
)
|
||||
if image
|
||||
else None
|
||||
)
|
||||
return cls(
|
||||
queue_id=queue_item.queue_id,
|
||||
item_id=queue_item.item_id,
|
||||
batch_id=queue_item.batch_id,
|
||||
session_id=queue_item.session_id,
|
||||
invocation=invocation,
|
||||
invocation_source_id=queue_item.session.prepared_source_mapping[invocation.id],
|
||||
name=name,
|
||||
step=step,
|
||||
total_steps=total_steps,
|
||||
image=image_,
|
||||
message=message,
|
||||
)
|
||||
|
||||
|
||||
@payload_schema.register
|
||||
class InvocationDenoiseProgressEvent(InvocationEventBase):
|
||||
"""Event model for invocation_denoise_progress"""
|
||||
|
@ -557,6 +557,50 @@ class UtilInterface(InvocationContextInterface):
|
||||
is_canceled=self.is_canceled,
|
||||
)
|
||||
|
||||
def signal_progress(
|
||||
self,
|
||||
name: str,
|
||||
step: int | None = None,
|
||||
total_steps: int | None = None,
|
||||
message: str | None = None,
|
||||
image: Image | None = None,
|
||||
) -> None:
|
||||
"""Signals the progress of some long-running invocation process. The progress is displayed in the UI.
|
||||
|
||||
Each progress event is grouped by both the given `name` and the invocation's ID. Once the invocation completes,
|
||||
future progress events with the same name will be grouped separately.
|
||||
|
||||
For progress that has a known number of steps, provide both `step` and `total_steps`. For indeterminate
|
||||
progress, omit both `step` and `total_steps`. An error will be raised if only one of `step` and `total_steps`
|
||||
is provided.
|
||||
|
||||
For the best user experience:
|
||||
- Signal process once with `step=0, total_steps=total_steps` before processing begins.
|
||||
- Signal process after each step completes with `step=current_step, total_steps=total_steps`.
|
||||
- Signal process once with `step=total_steps, total_steps=total_steps` after processing completes, if this
|
||||
wasn't already done.
|
||||
- If the process is indeterminate, signal progress with `step=None, total_steps=None` at regular intervals.
|
||||
|
||||
Args:
|
||||
name: The name of the action. This is used to group progress events together.
|
||||
step: The current step of the action. Omit for indeterminate progress.
|
||||
total_steps: The total number of steps of the action. Omit for indeterminate progress.
|
||||
message: An optional message to display. If omitted, no message will be displayed.
|
||||
image: An optional image to display. If omitted, no image will be displayed.
|
||||
|
||||
Raises:
|
||||
pydantic.ValidationError: If only one of `step` and `total_steps` is provided.
|
||||
"""
|
||||
self._services.events.emit_invocation_generic_progress(
|
||||
queue_item=self._data.queue_item,
|
||||
invocation=self._data.invocation,
|
||||
name=name,
|
||||
step=step,
|
||||
total_steps=total_steps,
|
||||
message=message,
|
||||
image=image,
|
||||
)
|
||||
|
||||
|
||||
class InvocationContext:
|
||||
"""Provides access to various services and data for the current invocation.
|
||||
|
Loading…
Reference in New Issue
Block a user