From 90083cc88ddda1a48f7c575e99d348690c00989f Mon Sep 17 00:00:00 2001 From: psychedelicious <4822129+psychedelicious@users.noreply.github.com> Date: Mon, 9 Oct 2023 19:28:01 +1100 Subject: [PATCH 1/4] fix(ui): fix use all hotkey --- .../components/CurrentImage/CurrentImageButtons.tsx | 8 +------- 1 file changed, 1 insertion(+), 7 deletions(-) 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); From 55b40a94256e7693e7c274a037e5d032854e3fcb Mon Sep 17 00:00:00 2001 From: psychedelicious <4822129+psychedelicious@users.noreply.github.com> Date: Mon, 9 Oct 2023 11:04:03 +1100 Subject: [PATCH 2/4] feat(events): add batch status and queue status to queue item status changed events The UI will always re-fetch queue and batch status on receiving this event, so we may as well jsut include that data in the event and save the extra network roundtrips. --- invokeai/app/services/events.py | 39 +++++++++++++------ .../session_queue/session_queue_sqlite.py | 24 ++++++++++-- 2 files changed, 48 insertions(+), 15 deletions(-) diff --git a/invokeai/app/services/events.py b/invokeai/app/services/events.py index 3b36ffb917..0a02a03539 100644 --- a/invokeai/app/services/events.py +++ b/invokeai/app/services/events.py @@ -4,7 +4,12 @@ from typing import Any, Optional from invokeai.app.models.image import ProgressImage from invokeai.app.services.model_manager_service import BaseModelType, ModelInfo, ModelType, SubModelType -from invokeai.app.services.session_queue.session_queue_common import EnqueueBatchResult, SessionQueueItem +from invokeai.app.services.session_queue.session_queue_common import ( + BatchStatus, + EnqueueBatchResult, + SessionQueueItem, + SessionQueueStatus, +) from invokeai.app.util.misc import get_timestamp @@ -262,21 +267,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/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 From ca95a3bd0dc2e5f60abfd1891f0e7f84907cf719 Mon Sep 17 00:00:00 2001 From: psychedelicious <4822129+psychedelicious@users.noreply.github.com> Date: Mon, 9 Oct 2023 11:15:19 +1100 Subject: [PATCH 3/4] fix(ui): fix canvas soft-lock if canceled before first generation The canvas needs to be set to staging mode as soon as a canvas-destined batch is enqueued. If the batch is is fully canceled before an image is generated, we need to remove that batch from the canvas `batchIds` watchlist, else canvas gets stuck in staging mode with no way to exit. The changes here allow the batch status to be tracked, and if a batch has all its items completed, we can remove it from the `batchIds` watchlist. The `batchIds` watchlist now accurately represents *incomplete* canvas batches, fixing this cause of soft lock. --- .../socketio/socketQueueItemStatusChanged.ts | 60 +++++++++++++------ .../src/features/canvas/store/canvasSlice.ts | 13 ++++ .../src/features/nodes/store/nodesSlice.ts | 2 +- .../src/features/system/store/systemSlice.ts | 4 +- .../web/src/services/api/endpoints/queue.ts | 23 +++---- .../frontend/web/src/services/api/index.ts | 2 - .../frontend/web/src/services/api/schema.d.ts | 32 +++++----- .../frontend/web/src/services/events/types.ts | 44 ++++++++++---- 8 files changed, 115 insertions(+), 65 deletions(-) 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..32f87bf34c 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,66 @@ 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; + + 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)); 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, }); }) ); + dispatch( + queueApi.util.updateQueryData('getQueueStatus', undefined, (draft) => { + Object.assign(draft.queue, queue_status); + }) + ); + dispatch( + queueApi.util.updateQueryData('getQueueStatus', undefined, (draft) => { + if (!draft) { + return; + } + Object.assign(draft.queue, queue_status); + }) + ); + + dispatch( + queueApi.util.updateQueryData( + 'getBatchStatus', + { batch_id: batch_status.batch_id }, + () => batch_status + ) + ); + + dispatch( + queueApi.util.updateQueryData( + 'getQueueItem', + queue_item.item_id, + (draft) => { + if (!draft) { + return; + } + Object.assign(draft, queue_item); + } + ) + ); + 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(); }, }); }; 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/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 = { From 1f751f8c21eaa6ae95b06a8b1882f603ead1380b Mon Sep 17 00:00:00 2001 From: psychedelicious <4822129+psychedelicious@users.noreply.github.com> Date: Mon, 9 Oct 2023 18:46:37 +1100 Subject: [PATCH 4/4] fix(ui): remove extraneous cache update --- .../socketio/socketQueueItemStatusChanged.ts | 16 ++++++++++------ 1 file changed, 10 insertions(+), 6 deletions(-) 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 32f87bf34c..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 @@ -12,14 +12,15 @@ export const addSocketQueueItemStatusChangedEventListener = () => { effect: async (action, { dispatch }) => { const log = logger('socketio'); + // 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 ${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, { @@ -29,11 +30,7 @@ export const addSocketQueueItemStatusChangedEventListener = () => { }) ); - dispatch( - queueApi.util.updateQueryData('getQueueStatus', undefined, (draft) => { - Object.assign(draft.queue, queue_status); - }) - ); + // Update the queue status (we do not get the processor status here) dispatch( queueApi.util.updateQueryData('getQueueStatus', undefined, (draft) => { if (!draft) { @@ -43,6 +40,7 @@ export const addSocketQueueItemStatusChangedEventListener = () => { }) ); + // Update the batch status dispatch( queueApi.util.updateQueryData( 'getBatchStatus', @@ -51,6 +49,7 @@ export const addSocketQueueItemStatusChangedEventListener = () => { ) ); + // Update the queue item status (this is the full queue item, including the session) dispatch( queueApi.util.updateQueryData( 'getQueueItem', @@ -64,6 +63,8 @@ export const addSocketQueueItemStatusChangedEventListener = () => { ) ); + // 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', @@ -71,6 +72,9 @@ export const addSocketQueueItemStatusChangedEventListener = () => { 'InvocationCacheStatus', ]) ); + + // Pass the event along + dispatch(appSocketQueueItemStatusChanged(action.payload)); }, }); };