diff --git a/invokeai/app/services/events.py b/invokeai/app/services/events.py index 1c6cf204bc..f52323c46a 100644 --- a/invokeai/app/services/events.py +++ b/invokeai/app/services/events.py @@ -3,7 +3,13 @@ from typing import Any, Optional from invokeai.app.models.image import ProgressImage -from invokeai.app.services.session_queue.session_queue_common import EnqueueBatchResult, SessionQueueItem +from invokeai.app.services.model_record_service import BaseModelType, ModelType, SubModelType +from invokeai.app.services.session_queue.session_queue_common import ( + BatchStatus, + EnqueueBatchResult, + SessionQueueItem, + SessionQueueStatus, +) from invokeai.app.util.misc import get_timestamp from invokeai.backend.model_manager import SubModelType from invokeai.backend.model_manager.download import DownloadJobBase @@ -256,21 +262,31 @@ class EventServiceBase: ), ) - def emit_queue_item_status_changed(self, session_queue_item: SessionQueueItem) -> None: + def emit_queue_item_status_changed( + self, + session_queue_item: SessionQueueItem, + batch_status: BatchStatus, + queue_status: SessionQueueStatus, + ) -> None: """Emitted when a queue item's status changes""" self.__emit_queue_event( event_name="queue_item_status_changed", payload=dict( - queue_id=session_queue_item.queue_id, - queue_item_id=session_queue_item.item_id, - status=session_queue_item.status, - batch_id=session_queue_item.batch_id, - session_id=session_queue_item.session_id, - error=session_queue_item.error, - created_at=str(session_queue_item.created_at) if session_queue_item.created_at else None, - updated_at=str(session_queue_item.updated_at) if session_queue_item.updated_at else None, - started_at=str(session_queue_item.started_at) if session_queue_item.started_at else None, - completed_at=str(session_queue_item.completed_at) if session_queue_item.completed_at else None, + queue_id=queue_status.queue_id, + queue_item=dict( + queue_id=session_queue_item.queue_id, + item_id=session_queue_item.item_id, + status=session_queue_item.status, + batch_id=session_queue_item.batch_id, + session_id=session_queue_item.session_id, + error=session_queue_item.error, + created_at=str(session_queue_item.created_at) if session_queue_item.created_at else None, + updated_at=str(session_queue_item.updated_at) if session_queue_item.updated_at else None, + started_at=str(session_queue_item.started_at) if session_queue_item.started_at else None, + completed_at=str(session_queue_item.completed_at) if session_queue_item.completed_at else None, + ), + batch_status=batch_status.dict(), + queue_status=queue_status.dict(), ), ) diff --git a/invokeai/app/services/model_record_service.py b/invokeai/app/services/model_record_service.py index 51db9db31c..192662f683 100644 --- a/invokeai/app/services/model_record_service.py +++ b/invokeai/app/services/model_record_service.py @@ -8,7 +8,7 @@ from abc import abstractmethod from pathlib import Path from typing import Optional, Union -from invokeai.backend.model_manager import ModelConfigBase, ModelType, SubModelType +from invokeai.backend.model_manager import ModelConfigBase, BaseModelType, ModelType, SubModelType from invokeai.backend.model_manager.storage import ( ModelConfigStore, diff --git a/invokeai/app/services/session_queue/session_queue_sqlite.py b/invokeai/app/services/session_queue/session_queue_sqlite.py index 19a5346e10..f995576311 100644 --- a/invokeai/app/services/session_queue/session_queue_sqlite.py +++ b/invokeai/app/services/session_queue/session_queue_sqlite.py @@ -427,7 +427,13 @@ class SqliteSessionQueue(SessionQueueBase): finally: self.__lock.release() queue_item = self.get_queue_item(item_id) - self.__invoker.services.events.emit_queue_item_status_changed(queue_item) + batch_status = self.get_batch_status(queue_id=queue_item.queue_id, batch_id=queue_item.batch_id) + queue_status = self.get_queue_status(queue_id=queue_item.queue_id) + self.__invoker.services.events.emit_queue_item_status_changed( + session_queue_item=queue_item, + batch_status=batch_status, + queue_status=queue_status, + ) return queue_item def is_empty(self, queue_id: str) -> IsEmptyResult: @@ -609,7 +615,13 @@ class SqliteSessionQueue(SessionQueueBase): queue_batch_id=current_queue_item.batch_id, graph_execution_state_id=current_queue_item.session_id, ) - self.__invoker.services.events.emit_queue_item_status_changed(current_queue_item) + batch_status = self.get_batch_status(queue_id=queue_id, batch_id=current_queue_item.batch_id) + queue_status = self.get_queue_status(queue_id=queue_id) + self.__invoker.services.events.emit_queue_item_status_changed( + session_queue_item=current_queue_item, + batch_status=batch_status, + queue_status=queue_status, + ) except Exception: self.__conn.rollback() raise @@ -655,7 +667,13 @@ class SqliteSessionQueue(SessionQueueBase): queue_batch_id=current_queue_item.batch_id, graph_execution_state_id=current_queue_item.session_id, ) - self.__invoker.services.events.emit_queue_item_status_changed(current_queue_item) + batch_status = self.get_batch_status(queue_id=queue_id, batch_id=current_queue_item.batch_id) + queue_status = self.get_queue_status(queue_id=queue_id) + self.__invoker.services.events.emit_queue_item_status_changed( + session_queue_item=current_queue_item, + batch_status=batch_status, + queue_status=queue_status, + ) except Exception: self.__conn.rollback() raise diff --git a/invokeai/frontend/web/src/app/store/middleware/listenerMiddleware/listeners/socketio/socketQueueItemStatusChanged.ts b/invokeai/frontend/web/src/app/store/middleware/listenerMiddleware/listeners/socketio/socketQueueItemStatusChanged.ts index 4af35dbe9c..a7d4c68437 100644 --- a/invokeai/frontend/web/src/app/store/middleware/listenerMiddleware/listeners/socketio/socketQueueItemStatusChanged.ts +++ b/invokeai/frontend/web/src/app/store/middleware/listenerMiddleware/listeners/socketio/socketQueueItemStatusChanged.ts @@ -11,44 +11,70 @@ export const addSocketQueueItemStatusChangedEventListener = () => { actionCreator: socketQueueItemStatusChanged, effect: async (action, { dispatch }) => { const log = logger('socketio'); - const { - queue_item_id: item_id, - queue_batch_id, - status, - } = action.payload.data; + + // we've got new status for the queue item, batch and queue + const { queue_item, batch_status, queue_status } = action.payload.data; + log.debug( action.payload, - `Queue item ${item_id} status updated: ${status}` + `Queue item ${queue_item.item_id} status updated: ${queue_item.status}` ); - dispatch(appSocketQueueItemStatusChanged(action.payload)); + // Update this specific queue item in the list of queue items (this is the queue item DTO, without the session) dispatch( queueApi.util.updateQueryData('listQueueItems', undefined, (draft) => { queueItemsAdapter.updateOne(draft, { - id: item_id, - changes: action.payload.data, + id: queue_item.item_id, + changes: queue_item, }); }) ); + // Update the queue status (we do not get the processor status here) + dispatch( + queueApi.util.updateQueryData('getQueueStatus', undefined, (draft) => { + if (!draft) { + return; + } + Object.assign(draft.queue, queue_status); + }) + ); + + // Update the batch status + dispatch( + queueApi.util.updateQueryData( + 'getBatchStatus', + { batch_id: batch_status.batch_id }, + () => batch_status + ) + ); + + // Update the queue item status (this is the full queue item, including the session) + dispatch( + queueApi.util.updateQueryData( + 'getQueueItem', + queue_item.item_id, + (draft) => { + if (!draft) { + return; + } + Object.assign(draft, queue_item); + } + ) + ); + + // Invalidate caches for things we cannot update + // TODO: technically, we could possibly update the current session queue item, but feels safer to just request it again dispatch( queueApi.util.invalidateTags([ 'CurrentSessionQueueItem', 'NextSessionQueueItem', 'InvocationCacheStatus', - { type: 'SessionQueueItem', id: item_id }, - { type: 'SessionQueueItemDTO', id: item_id }, - { type: 'BatchStatus', id: queue_batch_id }, ]) ); - const req = dispatch( - queueApi.endpoints.getQueueStatus.initiate(undefined, { - forceRefetch: true, - }) - ); - await req.unwrap(); - req.unsubscribe(); + // Pass the event along + dispatch(appSocketQueueItemStatusChanged(action.payload)); }, }); }; diff --git a/invokeai/frontend/web/src/features/canvas/store/canvasSlice.ts b/invokeai/frontend/web/src/features/canvas/store/canvasSlice.ts index df601e9e67..77fae4e0a1 100644 --- a/invokeai/frontend/web/src/features/canvas/store/canvasSlice.ts +++ b/invokeai/frontend/web/src/features/canvas/store/canvasSlice.ts @@ -29,6 +29,7 @@ import { isCanvasBaseImage, isCanvasMaskLine, } from './canvasTypes'; +import { appSocketQueueItemStatusChanged } from 'services/events/actions'; export const initialLayerState: CanvasLayerState = { objects: [], @@ -786,6 +787,18 @@ export const canvasSlice = createSlice({ }, }, extraReducers: (builder) => { + builder.addCase(appSocketQueueItemStatusChanged, (state, action) => { + const batch_status = action.payload.data.batch_status; + if (!state.batchIds.includes(batch_status.batch_id)) { + return; + } + + if (batch_status.in_progress === 0 && batch_status.pending === 0) { + state.batchIds = state.batchIds.filter( + (id) => id !== batch_status.batch_id + ); + } + }); builder.addCase(setAspectRatio, (state, action) => { const ratio = action.payload; if (ratio) { diff --git a/invokeai/frontend/web/src/features/gallery/components/CurrentImage/CurrentImageButtons.tsx b/invokeai/frontend/web/src/features/gallery/components/CurrentImage/CurrentImageButtons.tsx index 32f03eb6c0..57f06a0cea 100644 --- a/invokeai/frontend/web/src/features/gallery/components/CurrentImage/CurrentImageButtons.tsx +++ b/invokeai/frontend/web/src/features/gallery/components/CurrentImage/CurrentImageButtons.tsx @@ -134,13 +134,7 @@ const CurrentImageButtons = () => { recallAllParameters(metadata); }, [metadata, recallAllParameters]); - useHotkeys( - 'a', - () => { - handleClickUseAllParameters; - }, - [metadata, recallAllParameters] - ); + useHotkeys('a', handleClickUseAllParameters, [metadata]); const handleUseSeed = useCallback(() => { recallSeed(metadata?.seed); diff --git a/invokeai/frontend/web/src/features/nodes/store/nodesSlice.ts b/invokeai/frontend/web/src/features/nodes/store/nodesSlice.ts index 5ff0046d2d..cb8f3b7d28 100644 --- a/invokeai/frontend/web/src/features/nodes/store/nodesSlice.ts +++ b/invokeai/frontend/web/src/features/nodes/store/nodesSlice.ts @@ -984,7 +984,7 @@ const nodesSlice = createSlice({ } }); builder.addCase(appSocketQueueItemStatusChanged, (state, action) => { - if (['in_progress'].includes(action.payload.data.status)) { + if (['in_progress'].includes(action.payload.data.queue_item.status)) { forEach(state.nodeExecutionStates, (nes) => { nes.status = NodeStatus.PENDING; nes.error = null; diff --git a/invokeai/frontend/web/src/features/system/store/systemSlice.ts b/invokeai/frontend/web/src/features/system/store/systemSlice.ts index 9a110f5f23..76697d3030 100644 --- a/invokeai/frontend/web/src/features/system/store/systemSlice.ts +++ b/invokeai/frontend/web/src/features/system/store/systemSlice.ts @@ -164,7 +164,9 @@ export const systemSlice = createSlice({ builder.addCase(appSocketQueueItemStatusChanged, (state, action) => { if ( - ['completed', 'canceled', 'failed'].includes(action.payload.data.status) + ['completed', 'canceled', 'failed'].includes( + action.payload.data.queue_item.status + ) ) { state.status = 'CONNECTED'; state.denoiseProgress = null; diff --git a/invokeai/frontend/web/src/services/api/endpoints/queue.ts b/invokeai/frontend/web/src/services/api/endpoints/queue.ts index 4393ab7e81..ab75964e89 100644 --- a/invokeai/frontend/web/src/services/api/endpoints/queue.ts +++ b/invokeai/frontend/web/src/services/api/endpoints/queue.ts @@ -135,12 +135,7 @@ export const queueApi = api.injectEndpoints({ url: `queue/${$queueId.get()}/prune`, method: 'PUT', }), - invalidatesTags: [ - 'SessionQueueStatus', - 'BatchStatus', - 'SessionQueueItem', - 'SessionQueueItemDTO', - ], + invalidatesTags: ['SessionQueueStatus', 'BatchStatus'], onQueryStarted: async (arg, api) => { const { dispatch, queryFulfilled } = api; try { @@ -165,8 +160,6 @@ export const queueApi = api.injectEndpoints({ 'BatchStatus', 'CurrentSessionQueueItem', 'NextSessionQueueItem', - 'SessionQueueItem', - 'SessionQueueItemDTO', ], onQueryStarted: async (arg, api) => { const { dispatch, queryFulfilled } = api; @@ -218,7 +211,6 @@ export const queueApi = api.injectEndpoints({ url: `queue/${$queueId.get()}/status`, method: 'GET', }), - providesTags: ['SessionQueueStatus'], }), getBatchStatus: build.query< @@ -269,7 +261,11 @@ export const queueApi = api.injectEndpoints({ (draft) => { queueItemsAdapter.updateOne(draft, { id: item_id, - changes: { status: data.status }, + changes: { + status: data.status, + completed_at: data.completed_at, + updated_at: data.updated_at, + }, }); } ) @@ -284,7 +280,6 @@ export const queueApi = api.injectEndpoints({ } return [ { type: 'SessionQueueItem', id: result.item_id }, - { type: 'SessionQueueItemDTO', id: result.item_id }, { type: 'BatchStatus', id: result.batch_id }, ]; }, @@ -307,11 +302,7 @@ export const queueApi = api.injectEndpoints({ // no-op } }, - invalidatesTags: [ - 'SessionQueueItem', - 'SessionQueueItemDTO', - 'BatchStatus', - ], + invalidatesTags: ['SessionQueueStatus', 'BatchStatus'], }), listQueueItems: build.query< EntityState & { diff --git a/invokeai/frontend/web/src/services/api/index.ts b/invokeai/frontend/web/src/services/api/index.ts index 7a10377323..f423b2b0ed 100644 --- a/invokeai/frontend/web/src/services/api/index.ts +++ b/invokeai/frontend/web/src/services/api/index.ts @@ -21,8 +21,6 @@ export const tagTypes = [ 'ImageMetadataFromFile', 'IntermediatesCount', 'SessionQueueItem', - 'SessionQueueItemDTO', - 'SessionQueueItemDTOList', 'SessionQueueStatus', 'SessionProcessorStatus', 'CurrentSessionQueueItem', diff --git a/invokeai/frontend/web/src/services/api/schema.d.ts b/invokeai/frontend/web/src/services/api/schema.d.ts index ffca43c1f1..5bc2228eba 100644 --- a/invokeai/frontend/web/src/services/api/schema.d.ts +++ b/invokeai/frontend/web/src/services/api/schema.d.ts @@ -9701,11 +9701,23 @@ export type components = { ui_order?: number; }; /** - * StableDiffusionOnnxModelFormat + * T2IAdapterModelFormat * @description An enumeration. * @enum {string} */ - StableDiffusionOnnxModelFormat: "olive" | "onnx"; + T2IAdapterModelFormat: "diffusers"; + /** + * ControlNetModelFormat + * @description An enumeration. + * @enum {string} + */ + ControlNetModelFormat: "checkpoint" | "diffusers"; + /** + * StableDiffusion2ModelFormat + * @description An enumeration. + * @enum {string} + */ + StableDiffusion2ModelFormat: "checkpoint" | "diffusers"; /** * StableDiffusionXLModelFormat * @description An enumeration. @@ -9713,11 +9725,11 @@ export type components = { */ StableDiffusionXLModelFormat: "checkpoint" | "diffusers"; /** - * StableDiffusion2ModelFormat + * StableDiffusionOnnxModelFormat * @description An enumeration. * @enum {string} */ - StableDiffusion2ModelFormat: "checkpoint" | "diffusers"; + StableDiffusionOnnxModelFormat: "olive" | "onnx"; /** * CLIPVisionModelFormat * @description An enumeration. @@ -9736,18 +9748,6 @@ export type components = { * @enum {string} */ StableDiffusion1ModelFormat: "checkpoint" | "diffusers"; - /** - * T2IAdapterModelFormat - * @description An enumeration. - * @enum {string} - */ - T2IAdapterModelFormat: "diffusers"; - /** - * ControlNetModelFormat - * @description An enumeration. - * @enum {string} - */ - ControlNetModelFormat: "checkpoint" | "diffusers"; }; responses: never; parameters: never; diff --git a/invokeai/frontend/web/src/services/events/types.ts b/invokeai/frontend/web/src/services/events/types.ts index 47a3d83eba..543107bb13 100644 --- a/invokeai/frontend/web/src/services/events/types.ts +++ b/invokeai/frontend/web/src/services/events/types.ts @@ -170,16 +170,40 @@ export type InvocationRetrievalErrorEvent = { */ export type QueueItemStatusChangedEvent = { queue_id: string; - queue_item_id: number; - queue_batch_id: string; - session_id: string; - graph_execution_state_id: string; - status: components['schemas']['SessionQueueItemDTO']['status']; - error: string | undefined; - created_at: string; - updated_at: string; - started_at: string | undefined; - completed_at: string | undefined; + queue_item: { + queue_id: string; + item_id: number; + batch_id: string; + session_id: string; + status: components['schemas']['SessionQueueItemDTO']['status']; + error: string | undefined; + created_at: string; + updated_at: string; + started_at: string | undefined; + completed_at: string | undefined; + }; + batch_status: { + queue_id: string; + batch_id: string; + pending: number; + in_progress: number; + completed: number; + failed: number; + canceled: number; + total: number; + }; + queue_status: { + queue_id: string; + item_id?: number; + batch_id?: string; + session_id?: string; + pending: number; + in_progress: number; + completed: number; + failed: number; + canceled: number; + total: number; + }; }; export type ClientEmitSubscribeQueue = {