From f88ccabe30986320d71d3c26b06ff4d8b86fc1d0 Mon Sep 17 00:00:00 2001 From: psychedelicious <4822129+psychedelicious@users.noreply.github.com> Date: Fri, 26 May 2023 13:55:44 +1000 Subject: [PATCH 01/16] fix(ui): gallery not loading on page load --- invokeai/frontend/web/src/services/thunks/gallery.ts | 8 -------- 1 file changed, 8 deletions(-) diff --git a/invokeai/frontend/web/src/services/thunks/gallery.ts b/invokeai/frontend/web/src/services/thunks/gallery.ts index 5321b7ca3e..25d8ce0738 100644 --- a/invokeai/frontend/web/src/services/thunks/gallery.ts +++ b/invokeai/frontend/web/src/services/thunks/gallery.ts @@ -11,10 +11,6 @@ export const receivedResultImagesPage = createAppAsyncThunk( async (_arg, { getState, rejectWithValue }) => { const { page, pages, nextPage } = getState().results; - if (nextPage === page) { - return rejectWithValue([]); - } - const response = await ImagesService.listImagesWithMetadata({ imageType: 'results', imageCategory: 'general', @@ -33,10 +29,6 @@ export const receivedUploadImagesPage = createAppAsyncThunk( async (_arg, { getState, rejectWithValue }) => { const { page, pages, nextPage } = getState().uploads; - if (nextPage === page) { - return rejectWithValue([]); - } - const response = await ImagesService.listImagesWithMetadata({ imageType: 'uploads', imageCategory: 'general', From 17164a37a8f97dd52c10b5f8308d988075545b7e Mon Sep 17 00:00:00 2001 From: psychedelicious <4822129+psychedelicious@users.noreply.github.com> Date: Fri, 26 May 2023 13:56:53 +1000 Subject: [PATCH 02/16] fix(ui): fix gallery auto switch --- .../listeners/socketio/invocationComplete.ts | 14 ++++++++++---- 1 file changed, 10 insertions(+), 4 deletions(-) diff --git a/invokeai/frontend/web/src/app/store/middleware/listenerMiddleware/listeners/socketio/invocationComplete.ts b/invokeai/frontend/web/src/app/store/middleware/listenerMiddleware/listeners/socketio/invocationComplete.ts index 70ae1d2c71..cfd9706306 100644 --- a/invokeai/frontend/web/src/app/store/middleware/listenerMiddleware/listeners/socketio/invocationComplete.ts +++ b/invokeai/frontend/web/src/app/store/middleware/listenerMiddleware/listeners/socketio/invocationComplete.ts @@ -2,13 +2,11 @@ import { addImageToStagingArea } from 'features/canvas/store/canvasSlice'; import { startAppListening } from '../..'; import { log } from 'app/logging/useLogger'; import { invocationComplete } from 'services/events/actions'; -import { - imageMetadataReceived, - imageUrlsReceived, -} from 'services/thunks/image'; +import { imageMetadataReceived } from 'services/thunks/image'; import { sessionCanceled } from 'services/thunks/session'; import { isImageOutput } from 'services/types/guards'; import { progressImageSet } from 'features/system/store/systemSlice'; +import { imageSelected } from 'features/gallery/store/gallerySlice'; const moduleLog = log.child({ namespace: 'socketio' }); const nodeDenylist = ['dataURL_image']; @@ -46,6 +44,14 @@ export const addInvocationCompleteListener = () => { }) ); + const [{ payload: imageDTO }] = await take( + imageMetadataReceived.fulfilled.match + ); + + if (getState().gallery.shouldAutoSwitchToNewImages) { + dispatch(imageSelected(imageDTO)); + } + // Handle canvas image if ( graph_execution_state_id === From 0d5f44b1535cf4a476a40f3089dec6ae40cb5dde Mon Sep 17 00:00:00 2001 From: psychedelicious <4822129+psychedelicious@users.noreply.github.com> Date: Fri, 26 May 2023 14:19:32 +1000 Subject: [PATCH 03/16] feat(ui): improve image upload handling --- .../middleware/listenerMiddleware/index.ts | 14 +++++++-- .../listeners/canvasMerged.ts | 3 +- .../listeners/canvasSavedToGallery.ts | 1 - .../listeners/imageUploaded.ts | 29 +++++++++++++++++-- .../listeners/userInvokedCanvas.ts | 18 ++++-------- .../src/common/components/ImageUploader.tsx | 1 - .../frontend/web/src/services/thunks/image.ts | 5 +--- 7 files changed, 45 insertions(+), 26 deletions(-) diff --git a/invokeai/frontend/web/src/app/store/middleware/listenerMiddleware/index.ts b/invokeai/frontend/web/src/app/store/middleware/listenerMiddleware/index.ts index c04a3943f3..d762e73d95 100644 --- a/invokeai/frontend/web/src/app/store/middleware/listenerMiddleware/index.ts +++ b/invokeai/frontend/web/src/app/store/middleware/listenerMiddleware/index.ts @@ -8,7 +8,10 @@ import type { TypedStartListening, TypedAddListener } from '@reduxjs/toolkit'; import type { RootState, AppDispatch } from '../../store'; import { addInitialImageSelectedListener } from './listeners/initialImageSelected'; -import { addImageUploadedListener } from './listeners/imageUploaded'; +import { + addImageUploadedFulfilledListener, + addImageUploadedRejectedListener, +} from './listeners/imageUploaded'; import { addRequestedImageDeletionListener } from './listeners/imageDeleted'; import { addUserInvokedCanvasListener } from './listeners/userInvokedCanvas'; import { addUserInvokedNodesListener } from './listeners/userInvokedNodes'; @@ -47,23 +50,28 @@ export type AppListenerEffect = ListenerEffect< AppDispatch >; -addImageUploadedListener(); +// Image uploads +addImageUploadedFulfilledListener(); +addImageUploadedRejectedListener(); + addInitialImageSelectedListener(); + addRequestedImageDeletionListener(); +// Invoking stuff addUserInvokedCanvasListener(); addUserInvokedNodesListener(); addUserInvokedTextToImageListener(); addUserInvokedImageToImageListener(); addSessionReadyToInvokeListener(); +// Canvas actions addCanvasSavedToGalleryListener(); addCanvasDownloadedAsImageListener(); addCanvasCopiedToClipboardListener(); addCanvasMergedListener(); // socketio - addGeneratorProgressListener(); addGraphExecutionStateCompleteListener(); addInvocationCompleteListener(); diff --git a/invokeai/frontend/web/src/app/store/middleware/listenerMiddleware/listeners/canvasMerged.ts b/invokeai/frontend/web/src/app/store/middleware/listenerMiddleware/listeners/canvasMerged.ts index 1e2d99541c..fbc9c9c225 100644 --- a/invokeai/frontend/web/src/app/store/middleware/listenerMiddleware/listeners/canvasMerged.ts +++ b/invokeai/frontend/web/src/app/store/middleware/listenerMiddleware/listeners/canvasMerged.ts @@ -52,7 +52,6 @@ export const addCanvasMergedListener = () => { dispatch( imageUploaded({ - imageType: 'intermediates', formData: { file: new File([blob], filename, { type: 'image/png' }), }, @@ -65,7 +64,7 @@ export const addCanvasMergedListener = () => { action.meta.arg.formData.file.name === filename ); - const mergedCanvasImage = payload.response; + const mergedCanvasImage = payload; dispatch( setMergedCanvas({ diff --git a/invokeai/frontend/web/src/app/store/middleware/listenerMiddleware/listeners/canvasSavedToGallery.ts b/invokeai/frontend/web/src/app/store/middleware/listenerMiddleware/listeners/canvasSavedToGallery.ts index d8237d1d5c..2df3dacea2 100644 --- a/invokeai/frontend/web/src/app/store/middleware/listenerMiddleware/listeners/canvasSavedToGallery.ts +++ b/invokeai/frontend/web/src/app/store/middleware/listenerMiddleware/listeners/canvasSavedToGallery.ts @@ -29,7 +29,6 @@ export const addCanvasSavedToGalleryListener = () => { dispatch( imageUploaded({ - imageType: 'results', formData: { file: new File([blob], 'mergedCanvas.png', { type: 'image/png' }), }, diff --git a/invokeai/frontend/web/src/app/store/middleware/listenerMiddleware/listeners/imageUploaded.ts b/invokeai/frontend/web/src/app/store/middleware/listenerMiddleware/listeners/imageUploaded.ts index b37cd3d139..c0ed294850 100644 --- a/invokeai/frontend/web/src/app/store/middleware/listenerMiddleware/listeners/imageUploaded.ts +++ b/invokeai/frontend/web/src/app/store/middleware/listenerMiddleware/listeners/imageUploaded.ts @@ -7,17 +7,23 @@ import { initialImageSelected } from 'features/parameters/store/actions'; import { setInitialCanvasImage } from 'features/canvas/store/canvasSlice'; import { resultAdded } from 'features/gallery/store/resultsSlice'; import { isResultsImageDTO, isUploadsImageDTO } from 'services/types/guards'; +import { log } from 'app/logging/useLogger'; -export const addImageUploadedListener = () => { +const moduleLog = log.child({ namespace: 'image' }); + +export const addImageUploadedFulfilledListener = () => { startAppListening({ predicate: (action): action is ReturnType => imageUploaded.fulfilled.match(action) && - action.payload.response.is_intermediate === false, + action.payload.is_intermediate === false, effect: (action, { dispatch, getState }) => { - const { response: image } = action.payload; + const image = action.payload; + + moduleLog.debug({ arg: '', image }, 'Image uploaded'); const state = getState(); + // Handle uploads if (isUploadsImageDTO(image)) { dispatch(uploadAdded(image)); @@ -36,9 +42,26 @@ export const addImageUploadedListener = () => { } } + // Handle results + // TODO: Can this ever happen? I don't think so... if (isResultsImageDTO(image)) { dispatch(resultAdded(image)); } }, }); }; + +export const addImageUploadedRejectedListener = () => { + startAppListening({ + actionCreator: imageUploaded.rejected, + effect: (action, { dispatch }) => { + dispatch( + addToast({ + title: 'Image Upload Failed', + description: action.error.message, + status: 'error', + }) + ); + }, + }); +}; diff --git a/invokeai/frontend/web/src/app/store/middleware/listenerMiddleware/listeners/userInvokedCanvas.ts b/invokeai/frontend/web/src/app/store/middleware/listenerMiddleware/listeners/userInvokedCanvas.ts index 46f6efe934..641cbb5ca5 100644 --- a/invokeai/frontend/web/src/app/store/middleware/listenerMiddleware/listeners/userInvokedCanvas.ts +++ b/invokeai/frontend/web/src/app/store/middleware/listenerMiddleware/listeners/userInvokedCanvas.ts @@ -106,19 +106,16 @@ export const addUserInvokedCanvasListener = () => { ); // Wait for the image to be uploaded - const [{ payload: basePayload }] = await take( + const [{ payload: baseImageDTO }] = await take( (action): action is ReturnType => imageUploaded.fulfilled.match(action) && action.meta.arg.formData.file.name === baseFilename ); // Update the base node with the image name and type - const { image_name: baseName, image_type: baseType } = - basePayload.response; - baseNode.image = { - image_name: baseName, - image_type: baseType, + image_name: baseImageDTO.image_name, + image_type: baseImageDTO.image_type, }; } @@ -135,19 +132,16 @@ export const addUserInvokedCanvasListener = () => { ); // Wait for the mask to be uploaded - const [{ payload: maskPayload }] = await take( + const [{ payload: maskImageDTO }] = await take( (action): action is ReturnType => imageUploaded.fulfilled.match(action) && action.meta.arg.formData.file.name === maskFilename ); // Update the base node with the image name and type - const { image_name: maskName, image_type: maskType } = - maskPayload.response; - baseNode.mask = { - image_name: maskName, - image_type: maskType, + image_name: maskImageDTO.image_name, + image_type: maskImageDTO.image_type, }; } diff --git a/invokeai/frontend/web/src/common/components/ImageUploader.tsx b/invokeai/frontend/web/src/common/components/ImageUploader.tsx index db6b9ee517..628d44b6f1 100644 --- a/invokeai/frontend/web/src/common/components/ImageUploader.tsx +++ b/invokeai/frontend/web/src/common/components/ImageUploader.tsx @@ -68,7 +68,6 @@ const ImageUploader = (props: ImageUploaderProps) => { async (file: File) => { dispatch( imageUploaded({ - imageType: 'uploads', formData: { file }, activeTabName, }) diff --git a/invokeai/frontend/web/src/services/thunks/image.ts b/invokeai/frontend/web/src/services/thunks/image.ts index 34b369e3eb..17176ac09b 100644 --- a/invokeai/frontend/web/src/services/thunks/image.ts +++ b/invokeai/frontend/web/src/services/thunks/image.ts @@ -53,11 +53,8 @@ export const imageUploaded = createAppAsyncThunk( // strip out `activeTabName` from arg - the route does not need it const { activeTabName, ...rest } = arg; const response = await ImagesService.uploadImage(rest); - const { location } = getHeaders(response); - imagesLog.debug({ arg: '', response, location }, 'Image uploaded'); - - return { response, location }; + return response; } ); From 6059db4f158268e5cd280f4ee45fdafee79851aa Mon Sep 17 00:00:00 2001 From: psychedelicious <4822129+psychedelicious@users.noreply.github.com> Date: Fri, 26 May 2023 14:31:32 +1000 Subject: [PATCH 04/16] feat(ui): improve image delete handling --- .../middleware/listenerMiddleware/index.ts | 13 ++++- .../listeners/imageDeleted.ts | 56 +++++++++++++++++-- .../features/gallery/store/resultsSlice.ts | 12 ---- .../features/gallery/store/uploadsSlice.ts | 12 ---- .../frontend/web/src/services/thunks/image.ts | 5 +- 5 files changed, 63 insertions(+), 35 deletions(-) diff --git a/invokeai/frontend/web/src/app/store/middleware/listenerMiddleware/index.ts b/invokeai/frontend/web/src/app/store/middleware/listenerMiddleware/index.ts index d762e73d95..9dc25b262a 100644 --- a/invokeai/frontend/web/src/app/store/middleware/listenerMiddleware/index.ts +++ b/invokeai/frontend/web/src/app/store/middleware/listenerMiddleware/index.ts @@ -12,7 +12,12 @@ import { addImageUploadedFulfilledListener, addImageUploadedRejectedListener, } from './listeners/imageUploaded'; -import { addRequestedImageDeletionListener } from './listeners/imageDeleted'; +import { + addImageDeletedFulfilledListener, + addImageDeletedPendingListener, + addImageDeletedRejectedListener, + addRequestedImageDeletionListener, +} from './listeners/imageDeleted'; import { addUserInvokedCanvasListener } from './listeners/userInvokedCanvas'; import { addUserInvokedNodesListener } from './listeners/userInvokedNodes'; import { addUserInvokedTextToImageListener } from './listeners/userInvokedTextToImage'; @@ -50,13 +55,17 @@ export type AppListenerEffect = ListenerEffect< AppDispatch >; -// Image uploads +// Image uploaded addImageUploadedFulfilledListener(); addImageUploadedRejectedListener(); addInitialImageSelectedListener(); +// Image deleted addRequestedImageDeletionListener(); +addImageDeletedPendingListener(); +addImageDeletedFulfilledListener(); +addImageDeletedRejectedListener(); // Invoking stuff addUserInvokedCanvasListener(); diff --git a/invokeai/frontend/web/src/app/store/middleware/listenerMiddleware/listeners/imageDeleted.ts b/invokeai/frontend/web/src/app/store/middleware/listenerMiddleware/listeners/imageDeleted.ts index 42a62b3d80..804c713fc5 100644 --- a/invokeai/frontend/web/src/app/store/middleware/listenerMiddleware/listeners/imageDeleted.ts +++ b/invokeai/frontend/web/src/app/store/middleware/listenerMiddleware/listeners/imageDeleted.ts @@ -4,9 +4,14 @@ import { imageDeleted } from 'services/thunks/image'; import { log } from 'app/logging/useLogger'; import { clamp } from 'lodash-es'; import { imageSelected } from 'features/gallery/store/gallerySlice'; +import { uploadsAdapter } from 'features/gallery/store/uploadsSlice'; +import { resultsAdapter } from 'features/gallery/store/resultsSlice'; const moduleLog = log.child({ namespace: 'addRequestedImageDeletionListener' }); +/** + * Called when the user requests an image deletion + */ export const addRequestedImageDeletionListener = () => { startAppListening({ actionCreator: requestedImageDeletion, @@ -19,11 +24,6 @@ export const addRequestedImageDeletionListener = () => { const { image_name, image_type } = image; - if (image_type !== 'uploads' && image_type !== 'results') { - moduleLog.warn({ data: image }, `Invalid image type ${image_type}`); - return; - } - const selectedImageName = getState().gallery.selectedImage?.image_name; if (selectedImageName === image_name) { @@ -57,3 +57,49 @@ export const addRequestedImageDeletionListener = () => { }, }); }; + +/** + * Called when the actual delete request is sent to the server + */ +export const addImageDeletedPendingListener = () => { + startAppListening({ + actionCreator: imageDeleted.pending, + effect: (action, { dispatch, getState }) => { + const { imageName, imageType } = action.meta.arg; + // Preemptively remove the image from the gallery + if (imageType === 'uploads') { + uploadsAdapter.removeOne(getState().uploads, imageName); + } + if (imageType === 'results') { + resultsAdapter.removeOne(getState().results, imageName); + } + }, + }); +}; + +/** + * Called on successful delete + */ +export const addImageDeletedFulfilledListener = () => { + startAppListening({ + actionCreator: imageDeleted.fulfilled, + effect: (action, { dispatch, getState }) => { + moduleLog.info({ data: { image: action.meta.arg } }, 'Image deleted'); + }, + }); +}; + +/** + * Called on failed delete + */ +export const addImageDeletedRejectedListener = () => { + startAppListening({ + actionCreator: imageDeleted.rejected, + effect: (action, { dispatch, getState }) => { + moduleLog.warn( + { data: { image: action.meta.arg } }, + 'Unable to delete image' + ); + }, + }); +}; diff --git a/invokeai/frontend/web/src/features/gallery/store/resultsSlice.ts b/invokeai/frontend/web/src/features/gallery/store/resultsSlice.ts index 125f4ff5d5..87d17e6ee8 100644 --- a/invokeai/frontend/web/src/features/gallery/store/resultsSlice.ts +++ b/invokeai/frontend/web/src/features/gallery/store/resultsSlice.ts @@ -97,18 +97,6 @@ const resultsSlice = createSlice({ }); } }); - - /** - * Delete Image - PENDING - * Pre-emptively remove the image from the gallery - */ - builder.addCase(imageDeleted.pending, (state, action) => { - const { imageType, imageName } = action.meta.arg; - - if (imageType === 'results') { - resultsAdapter.removeOne(state, imageName); - } - }); }, }); diff --git a/invokeai/frontend/web/src/features/gallery/store/uploadsSlice.ts b/invokeai/frontend/web/src/features/gallery/store/uploadsSlice.ts index 5e458503ec..3e1abc9bb4 100644 --- a/invokeai/frontend/web/src/features/gallery/store/uploadsSlice.ts +++ b/invokeai/frontend/web/src/features/gallery/store/uploadsSlice.ts @@ -83,18 +83,6 @@ const uploadsSlice = createSlice({ }); } }); - - /** - * Delete Image - pending - * Pre-emptively remove the image from the gallery - */ - builder.addCase(imageDeleted.pending, (state, action) => { - const { imageType, imageName } = action.meta.arg; - - if (imageType === 'uploads') { - uploadsAdapter.removeOne(state, imageName); - } - }); }, }); diff --git a/invokeai/frontend/web/src/services/thunks/image.ts b/invokeai/frontend/web/src/services/thunks/image.ts index 17176ac09b..00add17d41 100644 --- a/invokeai/frontend/web/src/services/thunks/image.ts +++ b/invokeai/frontend/web/src/services/thunks/image.ts @@ -67,9 +67,6 @@ export const imageDeleted = createAppAsyncThunk( 'api/imageDeleted', async (arg: ImageDeletedArg) => { const response = await ImagesService.deleteImage(arg); - - imagesLog.debug({ arg, response }, 'Image deleted'); - return response; } ); @@ -77,7 +74,7 @@ export const imageDeleted = createAppAsyncThunk( type ImageUpdatedArg = Parameters<(typeof ImagesService)['updateImage']>[0]; /** - * `ImagesService.deleteImage()` thunk + * `ImagesService.updateImage()` thunk */ export const imageUpdated = createAppAsyncThunk( 'api/imageUpdated', From b3f71b307826b3f4c1c38730d1c43fbe8cfbb3dd Mon Sep 17 00:00:00 2001 From: psychedelicious <4822129+psychedelicious@users.noreply.github.com> Date: Fri, 26 May 2023 14:42:11 +1000 Subject: [PATCH 05/16] feat(ui): improve image metadata handling --- .../middleware/listenerMiddleware/index.ts | 8 +++ .../listeners/imageMetadataReceived.ts | 49 +++++++++++++++++++ .../features/gallery/store/resultsSlice.ts | 11 ----- .../frontend/web/src/services/thunks/image.ts | 3 +- 4 files changed, 58 insertions(+), 13 deletions(-) create mode 100644 invokeai/frontend/web/src/app/store/middleware/listenerMiddleware/listeners/imageMetadataReceived.ts diff --git a/invokeai/frontend/web/src/app/store/middleware/listenerMiddleware/index.ts b/invokeai/frontend/web/src/app/store/middleware/listenerMiddleware/index.ts index 9dc25b262a..be3a798015 100644 --- a/invokeai/frontend/web/src/app/store/middleware/listenerMiddleware/index.ts +++ b/invokeai/frontend/web/src/app/store/middleware/listenerMiddleware/index.ts @@ -36,6 +36,10 @@ import { addSocketDisconnectedListener } from './listeners/socketio/socketDiscon import { addSocketSubscribedListener } from './listeners/socketio/socketSubscribed'; import { addSocketUnsubscribedListener } from './listeners/socketio/socketUnsubscribed'; import { addSessionReadyToInvokeListener } from './listeners/sessionReadyToInvoke'; +import { + addImageMetadataReceivedFulfilledListener, + addImageMetadataReceivedRejectedListener, +} from './listeners/imageMetadataReceived'; export const listenerMiddleware = createListenerMiddleware(); @@ -67,6 +71,10 @@ addImageDeletedPendingListener(); addImageDeletedFulfilledListener(); addImageDeletedRejectedListener(); +// Image metadata +addImageMetadataReceivedFulfilledListener(); +addImageMetadataReceivedRejectedListener(); + // Invoking stuff addUserInvokedCanvasListener(); addUserInvokedNodesListener(); diff --git a/invokeai/frontend/web/src/app/store/middleware/listenerMiddleware/listeners/imageMetadataReceived.ts b/invokeai/frontend/web/src/app/store/middleware/listenerMiddleware/listeners/imageMetadataReceived.ts new file mode 100644 index 0000000000..417b7c49cf --- /dev/null +++ b/invokeai/frontend/web/src/app/store/middleware/listenerMiddleware/listeners/imageMetadataReceived.ts @@ -0,0 +1,49 @@ +import { log } from 'app/logging/useLogger'; +import { startAppListening } from '..'; +import { imageMetadataReceived } from 'services/thunks/image'; +import { + ResultsImageDTO, + resultsAdapter, +} from 'features/gallery/store/resultsSlice'; +import { + UploadsImageDTO, + uploadsAdapter, +} from 'features/gallery/store/uploadsSlice'; + +const moduleLog = log.child({ namespace: 'image' }); + +export const addImageMetadataReceivedFulfilledListener = () => { + startAppListening({ + actionCreator: imageMetadataReceived.fulfilled, + effect: (action, { getState, dispatch }) => { + const image = action.payload; + moduleLog.debug({ data: { image } }, 'Image metadata received'); + + if (image.image_type === 'results') { + resultsAdapter.upsertOne( + getState().results, + action.payload as ResultsImageDTO + ); + } + + if (image.image_type === 'uploads') { + uploadsAdapter.upsertOne( + getState().uploads, + action.payload as UploadsImageDTO + ); + } + }, + }); +}; + +export const addImageMetadataReceivedRejectedListener = () => { + startAppListening({ + actionCreator: imageMetadataReceived.rejected, + effect: (action, { getState, dispatch }) => { + moduleLog.debug( + { data: { image: action.meta.arg } }, + 'Problem receiving image metadata' + ); + }, + }); +}; diff --git a/invokeai/frontend/web/src/features/gallery/store/resultsSlice.ts b/invokeai/frontend/web/src/features/gallery/store/resultsSlice.ts index 87d17e6ee8..0a0ba01c40 100644 --- a/invokeai/frontend/web/src/features/gallery/store/resultsSlice.ts +++ b/invokeai/frontend/web/src/features/gallery/store/resultsSlice.ts @@ -69,17 +69,6 @@ const resultsSlice = createSlice({ state.isLoading = false; }); - /** - * Image Metadata Received - FULFILLED - */ - builder.addCase(imageMetadataReceived.fulfilled, (state, action) => { - const { image_type } = action.payload; - - if (image_type === 'results') { - resultsAdapter.upsertOne(state, action.payload as ResultsImageDTO); - } - }); - /** * Image URLs Received - FULFILLED */ diff --git a/invokeai/frontend/web/src/services/thunks/image.ts b/invokeai/frontend/web/src/services/thunks/image.ts index 00add17d41..4326b82811 100644 --- a/invokeai/frontend/web/src/services/thunks/image.ts +++ b/invokeai/frontend/web/src/services/thunks/image.ts @@ -2,7 +2,6 @@ import { log } from 'app/logging/useLogger'; import { createAppAsyncThunk } from 'app/store/storeUtils'; import { InvokeTabName } from 'features/ui/store/tabMap'; import { ImagesService } from 'services/api'; -import { getHeaders } from 'services/util/getHeaders'; const imagesLog = log.child({ namespace: 'image' }); @@ -81,7 +80,7 @@ export const imageUpdated = createAppAsyncThunk( async (arg: ImageUpdatedArg) => { const response = await ImagesService.updateImage(arg); - imagesLog.debug({ arg, response }, 'Image updated'); + imagesLog.debug({ data: { arg, response } }, 'Image updated'); return response; } From 1d4d705795e591fdce8b4af49a93cd8dc4db5821 Mon Sep 17 00:00:00 2001 From: psychedelicious <4822129+psychedelicious@users.noreply.github.com> Date: Fri, 26 May 2023 14:50:47 +1000 Subject: [PATCH 06/16] feat(ui): improve image urls handling --- .../middleware/listenerMiddleware/index.ts | 8 +++ .../listeners/imageUrlsReceived.ts | 51 +++++++++++++++++++ .../features/gallery/store/resultsSlice.ts | 23 --------- .../features/gallery/store/uploadsSlice.ts | 19 ------- .../frontend/web/src/services/thunks/image.ts | 9 ---- 5 files changed, 59 insertions(+), 51 deletions(-) create mode 100644 invokeai/frontend/web/src/app/store/middleware/listenerMiddleware/listeners/imageUrlsReceived.ts diff --git a/invokeai/frontend/web/src/app/store/middleware/listenerMiddleware/index.ts b/invokeai/frontend/web/src/app/store/middleware/listenerMiddleware/index.ts index be3a798015..8467b24c08 100644 --- a/invokeai/frontend/web/src/app/store/middleware/listenerMiddleware/index.ts +++ b/invokeai/frontend/web/src/app/store/middleware/listenerMiddleware/index.ts @@ -40,6 +40,10 @@ import { addImageMetadataReceivedFulfilledListener, addImageMetadataReceivedRejectedListener, } from './listeners/imageMetadataReceived'; +import { + addImageUrlsReceivedFulfilledListener, + addImageUrlsReceivedRejectedListener, +} from './listeners/imageUrlsReceived'; export const listenerMiddleware = createListenerMiddleware(); @@ -75,6 +79,10 @@ addImageDeletedRejectedListener(); addImageMetadataReceivedFulfilledListener(); addImageMetadataReceivedRejectedListener(); +// Image URLs +addImageUrlsReceivedFulfilledListener(); +addImageUrlsReceivedRejectedListener(); + // Invoking stuff addUserInvokedCanvasListener(); addUserInvokedNodesListener(); diff --git a/invokeai/frontend/web/src/app/store/middleware/listenerMiddleware/listeners/imageUrlsReceived.ts b/invokeai/frontend/web/src/app/store/middleware/listenerMiddleware/listeners/imageUrlsReceived.ts new file mode 100644 index 0000000000..4ff2a02118 --- /dev/null +++ b/invokeai/frontend/web/src/app/store/middleware/listenerMiddleware/listeners/imageUrlsReceived.ts @@ -0,0 +1,51 @@ +import { log } from 'app/logging/useLogger'; +import { startAppListening } from '..'; +import { imageUrlsReceived } from 'services/thunks/image'; +import { resultsAdapter } from 'features/gallery/store/resultsSlice'; +import { uploadsAdapter } from 'features/gallery/store/uploadsSlice'; + +const moduleLog = log.child({ namespace: 'image' }); + +export const addImageUrlsReceivedFulfilledListener = () => { + startAppListening({ + actionCreator: imageUrlsReceived.fulfilled, + effect: (action, { getState, dispatch }) => { + const image = action.payload; + moduleLog.debug({ data: { image } }, 'Image URLs received'); + + const { image_type, image_name, image_url, thumbnail_url } = image; + + if (image_type === 'results') { + resultsAdapter.updateOne(getState().results, { + id: image_name, + changes: { + image_url, + thumbnail_url, + }, + }); + } + + if (image_type === 'uploads') { + uploadsAdapter.updateOne(getState().uploads, { + id: image_name, + changes: { + image_url, + thumbnail_url, + }, + }); + } + }, + }); +}; + +export const addImageUrlsReceivedRejectedListener = () => { + startAppListening({ + actionCreator: imageUrlsReceived.rejected, + effect: (action, { getState, dispatch }) => { + moduleLog.debug( + { data: { image: action.meta.arg } }, + 'Problem getting image URLs' + ); + }, + }); +}; diff --git a/invokeai/frontend/web/src/features/gallery/store/resultsSlice.ts b/invokeai/frontend/web/src/features/gallery/store/resultsSlice.ts index 0a0ba01c40..18d0466033 100644 --- a/invokeai/frontend/web/src/features/gallery/store/resultsSlice.ts +++ b/invokeai/frontend/web/src/features/gallery/store/resultsSlice.ts @@ -4,11 +4,6 @@ import { receivedResultImagesPage, IMAGES_PER_PAGE, } from 'services/thunks/gallery'; -import { - imageDeleted, - imageMetadataReceived, - imageUrlsReceived, -} from 'services/thunks/image'; import { ImageDTO } from 'services/api'; import { dateComparator } from 'common/util/dateComparator'; @@ -68,24 +63,6 @@ const resultsSlice = createSlice({ state.nextPage = items.length < IMAGES_PER_PAGE ? page : page + 1; state.isLoading = false; }); - - /** - * Image URLs Received - FULFILLED - */ - builder.addCase(imageUrlsReceived.fulfilled, (state, action) => { - const { image_name, image_type, image_url, thumbnail_url } = - action.payload; - - if (image_type === 'results') { - resultsAdapter.updateOne(state, { - id: image_name, - changes: { - image_url: image_url, - thumbnail_url: thumbnail_url, - }, - }); - } - }); }, }); diff --git a/invokeai/frontend/web/src/features/gallery/store/uploadsSlice.ts b/invokeai/frontend/web/src/features/gallery/store/uploadsSlice.ts index 3e1abc9bb4..1a03ed3391 100644 --- a/invokeai/frontend/web/src/features/gallery/store/uploadsSlice.ts +++ b/invokeai/frontend/web/src/features/gallery/store/uploadsSlice.ts @@ -5,7 +5,6 @@ import { receivedUploadImagesPage, IMAGES_PER_PAGE, } from 'services/thunks/gallery'; -import { imageDeleted, imageUrlsReceived } from 'services/thunks/image'; import { ImageDTO } from 'services/api'; import { dateComparator } from 'common/util/dateComparator'; @@ -65,24 +64,6 @@ const uploadsSlice = createSlice({ state.nextPage = items.length < IMAGES_PER_PAGE ? page : page + 1; state.isLoading = false; }); - - /** - * Image URLs Received - FULFILLED - */ - builder.addCase(imageUrlsReceived.fulfilled, (state, action) => { - const { image_name, image_type, image_url, thumbnail_url } = - action.payload; - - if (image_type === 'uploads') { - uploadsAdapter.updateOne(state, { - id: image_name, - changes: { - image_url: image_url, - thumbnail_url: thumbnail_url, - }, - }); - } - }); }, }); diff --git a/invokeai/frontend/web/src/services/thunks/image.ts b/invokeai/frontend/web/src/services/thunks/image.ts index 4326b82811..f0c0456202 100644 --- a/invokeai/frontend/web/src/services/thunks/image.ts +++ b/invokeai/frontend/web/src/services/thunks/image.ts @@ -1,10 +1,7 @@ -import { log } from 'app/logging/useLogger'; import { createAppAsyncThunk } from 'app/store/storeUtils'; import { InvokeTabName } from 'features/ui/store/tabMap'; import { ImagesService } from 'services/api'; -const imagesLog = log.child({ namespace: 'image' }); - type imageUrlsReceivedArg = Parameters< (typeof ImagesService)['getImageUrls'] >[0]; @@ -16,7 +13,6 @@ export const imageUrlsReceived = createAppAsyncThunk( 'api/imageUrlsReceived', async (arg: imageUrlsReceivedArg) => { const response = await ImagesService.getImageUrls(arg); - imagesLog.info({ arg, response }, 'Received image urls'); return response; } ); @@ -32,7 +28,6 @@ export const imageMetadataReceived = createAppAsyncThunk( 'api/imageMetadataReceived', async (arg: imageMetadataReceivedArg) => { const response = await ImagesService.getImageMetadata(arg); - imagesLog.info({ arg, response }, 'Received image record'); return response; } ); @@ -52,7 +47,6 @@ export const imageUploaded = createAppAsyncThunk( // strip out `activeTabName` from arg - the route does not need it const { activeTabName, ...rest } = arg; const response = await ImagesService.uploadImage(rest); - return response; } ); @@ -79,9 +73,6 @@ export const imageUpdated = createAppAsyncThunk( 'api/imageUpdated', async (arg: ImageUpdatedArg) => { const response = await ImagesService.updateImage(arg); - - imagesLog.debug({ data: { arg, response } }, 'Image updated'); - return response; } ); From 8f190169db49c617494f7723dae0a3b2b955958e Mon Sep 17 00:00:00 2001 From: psychedelicious <4822129+psychedelicious@users.noreply.github.com> Date: Fri, 26 May 2023 15:53:08 +1000 Subject: [PATCH 07/16] feat(ui): improve session creation handling --- invokeai/frontend/web/public/locales/en.json | 1 + .../middleware/listenerMiddleware/index.ts | 10 +++ .../listeners/sessionCreated.ts | 45 ++++++++++++++ .../src/features/system/store/systemSlice.ts | 25 +++++++- .../web/src/services/events/middleware.ts | 11 +--- .../web/src/services/thunks/session.ts | 61 ++++++------------- 6 files changed, 99 insertions(+), 54 deletions(-) create mode 100644 invokeai/frontend/web/src/app/store/middleware/listenerMiddleware/listeners/sessionCreated.ts diff --git a/invokeai/frontend/web/public/locales/en.json b/invokeai/frontend/web/public/locales/en.json index 94dff3934a..b96175c25d 100644 --- a/invokeai/frontend/web/public/locales/en.json +++ b/invokeai/frontend/web/public/locales/en.json @@ -546,6 +546,7 @@ "availableSchedulers": "Available Schedulers" }, "toast": { + "problemCreatingSession": "Problem Creating Session", "serverError": "Server Error", "disconnected": "Disconnected from Server", "connected": "Connected to Server", diff --git a/invokeai/frontend/web/src/app/store/middleware/listenerMiddleware/index.ts b/invokeai/frontend/web/src/app/store/middleware/listenerMiddleware/index.ts index 8467b24c08..5c726c317f 100644 --- a/invokeai/frontend/web/src/app/store/middleware/listenerMiddleware/index.ts +++ b/invokeai/frontend/web/src/app/store/middleware/listenerMiddleware/index.ts @@ -44,6 +44,11 @@ import { addImageUrlsReceivedFulfilledListener, addImageUrlsReceivedRejectedListener, } from './listeners/imageUrlsReceived'; +import { + addSessionCreatedFulfilledListener, + addSessionCreatedPendingListener, + addSessionCreatedRejectedListener, +} from './listeners/sessionCreated'; export const listenerMiddleware = createListenerMiddleware(); @@ -106,3 +111,8 @@ addSocketConnectedListener(); addSocketDisconnectedListener(); addSocketSubscribedListener(); addSocketUnsubscribedListener(); + +// Sessions +addSessionCreatedPendingListener(); +addSessionCreatedFulfilledListener(); +addSessionCreatedRejectedListener(); diff --git a/invokeai/frontend/web/src/app/store/middleware/listenerMiddleware/listeners/sessionCreated.ts b/invokeai/frontend/web/src/app/store/middleware/listenerMiddleware/listeners/sessionCreated.ts new file mode 100644 index 0000000000..e960510d0f --- /dev/null +++ b/invokeai/frontend/web/src/app/store/middleware/listenerMiddleware/listeners/sessionCreated.ts @@ -0,0 +1,45 @@ +import { log } from 'app/logging/useLogger'; +import { startAppListening } from '..'; +import { sessionCreated } from 'services/thunks/session'; +import { serializeError } from 'serialize-error'; + +const moduleLog = log.child({ namespace: 'session' }); + +export const addSessionCreatedPendingListener = () => { + startAppListening({ + actionCreator: sessionCreated.pending, + effect: (action, { getState, dispatch }) => { + // + }, + }); +}; + +export const addSessionCreatedFulfilledListener = () => { + startAppListening({ + actionCreator: sessionCreated.fulfilled, + effect: (action, { getState, dispatch }) => { + const session = action.payload; + moduleLog.info({ data: { session } }, `Session created (${session.id})`); + }, + }); +}; + +export const addSessionCreatedRejectedListener = () => { + startAppListening({ + actionCreator: sessionCreated.rejected, + effect: (action, { getState, dispatch }) => { + if (action.payload) { + const { arg, error } = action.payload; + moduleLog.error( + { + data: { + arg, + error: serializeError(error), + }, + }, + `Problem creating session` + ); + } + }, + }); +}; diff --git a/invokeai/frontend/web/src/features/system/store/systemSlice.ts b/invokeai/frontend/web/src/features/system/store/systemSlice.ts index 06fa0e47d8..8845e0b843 100644 --- a/invokeai/frontend/web/src/features/system/store/systemSlice.ts +++ b/invokeai/frontend/web/src/features/system/store/systemSlice.ts @@ -16,7 +16,11 @@ import { import { ProgressImage } from 'services/events/types'; import { makeToast } from '../../../app/components/Toaster'; -import { sessionCanceled, sessionInvoked } from 'services/thunks/session'; +import { + sessionCanceled, + sessionCreated, + sessionInvoked, +} from 'services/thunks/session'; import { receivedModels } from 'services/thunks/model'; import { parsedOpenAPISchema } from 'features/nodes/store/nodesSlice'; import { LogLevelName } from 'roarr'; @@ -353,7 +357,7 @@ export const systemSlice = createSlice({ }); /** - * Session Canceled + * Session Canceled - FULFILLED */ builder.addCase(sessionCanceled.fulfilled, (state, action) => { state.canceledSession = action.meta.arg.sessionId; @@ -370,6 +374,23 @@ export const systemSlice = createSlice({ ); }); + /** + * Session Created - REJECTED + */ + builder.addCase(sessionCreated.rejected, (state, action) => { + state.isProcessing = false; + state.isCancelable = false; + state.isCancelScheduled = false; + state.currentStep = 0; + state.totalSteps = 0; + state.statusTranslationKey = 'common.statusConnected'; + state.progressImage = null; + + state.toastQueue.push( + makeToast({ title: t('toast.problemCreatingSession'), status: 'error' }) + ); + }); + /** * Session Canceled */ diff --git a/invokeai/frontend/web/src/services/events/middleware.ts b/invokeai/frontend/web/src/services/events/middleware.ts index a78e0de97b..f1eb844f2c 100644 --- a/invokeai/frontend/web/src/services/events/middleware.ts +++ b/invokeai/frontend/web/src/services/events/middleware.ts @@ -8,11 +8,7 @@ import { import { socketSubscribed, socketUnsubscribed } from './actions'; import { AppThunkDispatch, RootState } from 'app/store/store'; import { getTimestamp } from 'common/util/getTimestamp'; -import { - sessionInvoked, - sessionCreated, - sessionWithoutGraphCreated, -} from 'services/thunks/session'; +import { sessionCreated } from 'services/thunks/session'; import { OpenAPI } from 'services/api'; import { setEventListeners } from 'services/events/util/setEventListeners'; import { log } from 'app/logging/useLogger'; @@ -66,10 +62,7 @@ export const socketMiddleware = () => { socket.connect(); } - if ( - sessionCreated.fulfilled.match(action) || - sessionWithoutGraphCreated.fulfilled.match(action) - ) { + if (sessionCreated.fulfilled.match(action)) { const sessionId = action.payload.id; const oldSessionId = getState().system.sessionId; diff --git a/invokeai/frontend/web/src/services/thunks/session.ts b/invokeai/frontend/web/src/services/thunks/session.ts index a1ee5a34ed..546789d5b7 100644 --- a/invokeai/frontend/web/src/services/thunks/session.ts +++ b/invokeai/frontend/web/src/services/thunks/session.ts @@ -1,7 +1,6 @@ import { createAppAsyncThunk } from 'app/store/storeUtils'; -import { SessionsService } from 'services/api'; +import { GraphExecutionState, SessionsService } from 'services/api'; import { log } from 'app/logging/useLogger'; -import { serializeError } from 'serialize-error'; const sessionLog = log.child({ namespace: 'session' }); @@ -11,51 +10,27 @@ type SessionCreatedArg = { >[0]['requestBody']; }; +type SessionCreatedThunkConfig = { + rejectValue: { arg: SessionCreatedArg; error: unknown }; +}; + /** * `SessionsService.createSession()` thunk */ -export const sessionCreated = createAppAsyncThunk( - 'api/sessionCreated', - async (arg: SessionCreatedArg, { rejectWithValue }) => { - try { - const response = await SessionsService.createSession({ - requestBody: arg.graph, - }); - sessionLog.info({ arg, response }, `Session created (${response.id})`); - return response; - } catch (err: any) { - sessionLog.error( - { - error: serializeError(err), - }, - 'Problem creating session' - ); - return rejectWithValue(err.message); - } +export const sessionCreated = createAppAsyncThunk< + GraphExecutionState, + SessionCreatedArg, + SessionCreatedThunkConfig +>('api/sessionCreated', async (arg: SessionCreatedArg, { rejectWithValue }) => { + try { + const response = await SessionsService.createSession({ + requestBody: arg.graph, + }); + return response; + } catch (error) { + return rejectWithValue({ arg, error }); } -); - -/** - * `SessionsService.createSession()` without graph thunk - */ -export const sessionWithoutGraphCreated = createAppAsyncThunk( - 'api/sessionWithoutGraphCreated', - async (_, { rejectWithValue }) => { - try { - const response = await SessionsService.createSession({}); - sessionLog.info({ response }, `Session created (${response.id})`); - return response; - } catch (err: any) { - sessionLog.error( - { - error: serializeError(err), - }, - 'Problem creating session' - ); - return rejectWithValue(err.message); - } - } -); +}); type NodeAddedArg = Parameters<(typeof SessionsService)['addNode']>[0]; From b599c40099dfc42b40fba123fb8d9bf4e0cde86f Mon Sep 17 00:00:00 2001 From: psychedelicious <4822129+psychedelicious@users.noreply.github.com> Date: Fri, 26 May 2023 16:17:12 +1000 Subject: [PATCH 08/16] feat(ui): improve session invoked handling --- invokeai/frontend/web/public/locales/en.json | 1 - .../middleware/listenerMiddleware/index.ts | 12 ++- .../listeners/sessionInvoked.ts | 45 ++++++++++ .../listeners/sessionReadyToInvoke.ts | 7 +- .../src/features/system/store/systemSlice.ts | 51 +++++------ .../web/src/services/thunks/session.ts | 90 ++++++------------- 6 files changed, 115 insertions(+), 91 deletions(-) create mode 100644 invokeai/frontend/web/src/app/store/middleware/listenerMiddleware/listeners/sessionInvoked.ts diff --git a/invokeai/frontend/web/public/locales/en.json b/invokeai/frontend/web/public/locales/en.json index b96175c25d..94dff3934a 100644 --- a/invokeai/frontend/web/public/locales/en.json +++ b/invokeai/frontend/web/public/locales/en.json @@ -546,7 +546,6 @@ "availableSchedulers": "Available Schedulers" }, "toast": { - "problemCreatingSession": "Problem Creating Session", "serverError": "Server Error", "disconnected": "Disconnected from Server", "connected": "Connected to Server", diff --git a/invokeai/frontend/web/src/app/store/middleware/listenerMiddleware/index.ts b/invokeai/frontend/web/src/app/store/middleware/listenerMiddleware/index.ts index 5c726c317f..dae56804af 100644 --- a/invokeai/frontend/web/src/app/store/middleware/listenerMiddleware/index.ts +++ b/invokeai/frontend/web/src/app/store/middleware/listenerMiddleware/index.ts @@ -49,6 +49,11 @@ import { addSessionCreatedPendingListener, addSessionCreatedRejectedListener, } from './listeners/sessionCreated'; +import { + addSessionInvokedFulfilledListener, + addSessionInvokedPendingListener, + addSessionInvokedRejectedListener, +} from './listeners/sessionInvoked'; export const listenerMiddleware = createListenerMiddleware(); @@ -88,13 +93,18 @@ addImageMetadataReceivedRejectedListener(); addImageUrlsReceivedFulfilledListener(); addImageUrlsReceivedRejectedListener(); -// Invoking stuff +// Invoking on tabs addUserInvokedCanvasListener(); addUserInvokedNodesListener(); addUserInvokedTextToImageListener(); addUserInvokedImageToImageListener(); addSessionReadyToInvokeListener(); +// Actual session invoking +addSessionInvokedPendingListener(); +addSessionInvokedFulfilledListener(); +addSessionInvokedRejectedListener(); + // Canvas actions addCanvasSavedToGalleryListener(); addCanvasDownloadedAsImageListener(); diff --git a/invokeai/frontend/web/src/app/store/middleware/listenerMiddleware/listeners/sessionInvoked.ts b/invokeai/frontend/web/src/app/store/middleware/listenerMiddleware/listeners/sessionInvoked.ts new file mode 100644 index 0000000000..bddadbc359 --- /dev/null +++ b/invokeai/frontend/web/src/app/store/middleware/listenerMiddleware/listeners/sessionInvoked.ts @@ -0,0 +1,45 @@ +import { log } from 'app/logging/useLogger'; +import { startAppListening } from '..'; +import { sessionInvoked } from 'services/thunks/session'; +import { serializeError } from 'serialize-error'; + +const moduleLog = log.child({ namespace: 'session' }); + +export const addSessionInvokedPendingListener = () => { + startAppListening({ + actionCreator: sessionInvoked.pending, + effect: (action, { getState, dispatch }) => { + // + }, + }); +}; + +export const addSessionInvokedFulfilledListener = () => { + startAppListening({ + actionCreator: sessionInvoked.fulfilled, + effect: (action, { getState, dispatch }) => { + const { sessionId } = action.meta.arg; + moduleLog.info({ data: { sessionId } }, `Session invoked (${sessionId})`); + }, + }); +}; + +export const addSessionInvokedRejectedListener = () => { + startAppListening({ + actionCreator: sessionInvoked.rejected, + effect: (action, { getState, dispatch }) => { + if (action.payload) { + const { arg, error } = action.payload; + moduleLog.error( + { + data: { + arg, + error: serializeError(error), + }, + }, + `Problem invoking session` + ); + } + }, + }); +}; diff --git a/invokeai/frontend/web/src/app/store/middleware/listenerMiddleware/listeners/sessionReadyToInvoke.ts b/invokeai/frontend/web/src/app/store/middleware/listenerMiddleware/listeners/sessionReadyToInvoke.ts index eb65017a25..4fae2d96c0 100644 --- a/invokeai/frontend/web/src/app/store/middleware/listenerMiddleware/listeners/sessionReadyToInvoke.ts +++ b/invokeai/frontend/web/src/app/store/middleware/listenerMiddleware/listeners/sessionReadyToInvoke.ts @@ -3,7 +3,7 @@ import { sessionInvoked } from 'services/thunks/session'; import { log } from 'app/logging/useLogger'; import { sessionReadyToInvoke } from 'features/system/store/actions'; -const moduleLog = log.child({ namespace: 'invoke' }); +const moduleLog = log.child({ namespace: 'session' }); export const addSessionReadyToInvokeListener = () => { startAppListening({ @@ -11,7 +11,10 @@ export const addSessionReadyToInvokeListener = () => { effect: (action, { getState, dispatch }) => { const { sessionId } = getState().system; if (sessionId) { - moduleLog.info({ sessionId }, `Session invoked (${sessionId})})`); + moduleLog.info( + { sessionId }, + `Session ready to invoke (${sessionId})})` + ); dispatch(sessionInvoked({ sessionId })); } }, diff --git a/invokeai/frontend/web/src/features/system/store/systemSlice.ts b/invokeai/frontend/web/src/features/system/store/systemSlice.ts index 8845e0b843..403fd60501 100644 --- a/invokeai/frontend/web/src/features/system/store/systemSlice.ts +++ b/invokeai/frontend/web/src/features/system/store/systemSlice.ts @@ -1,5 +1,5 @@ import { UseToastOptions } from '@chakra-ui/react'; -import type { PayloadAction } from '@reduxjs/toolkit'; +import { PayloadAction, isAnyOf } from '@reduxjs/toolkit'; import { createSlice } from '@reduxjs/toolkit'; import * as InvokeAI from 'app/types/invokeai'; import { @@ -349,13 +349,6 @@ export const systemSlice = createSlice({ state.statusTranslationKey = 'common.statusPreparing'; }); - builder.addCase(sessionInvoked.rejected, (state, action) => { - const error = action.payload as string | undefined; - state.toastQueue.push( - makeToast({ title: error || t('toast.serverError'), status: 'error' }) - ); - }); - /** * Session Canceled - FULFILLED */ @@ -374,23 +367,6 @@ export const systemSlice = createSlice({ ); }); - /** - * Session Created - REJECTED - */ - builder.addCase(sessionCreated.rejected, (state, action) => { - state.isProcessing = false; - state.isCancelable = false; - state.isCancelScheduled = false; - state.currentStep = 0; - state.totalSteps = 0; - state.statusTranslationKey = 'common.statusConnected'; - state.progressImage = null; - - state.toastQueue.push( - makeToast({ title: t('toast.problemCreatingSession'), status: 'error' }) - ); - }); - /** * Session Canceled */ @@ -437,6 +413,26 @@ export const systemSlice = createSlice({ builder.addCase(imageUploaded.fulfilled, (state) => { state.isUploading = false; }); + + // *** Matchers - must be after all cases *** + + /** + * Session Invoked - REJECTED + * Session Created - REJECTED + */ + builder.addMatcher(isAnySessionRejected, (state, action) => { + state.isProcessing = false; + state.isCancelable = false; + state.isCancelScheduled = false; + state.currentStep = 0; + state.totalSteps = 0; + state.statusTranslationKey = 'common.statusConnected'; + state.progressImage = null; + + state.toastQueue.push( + makeToast({ title: t('toast.serverError'), status: 'error' }) + ); + }); }, }); @@ -465,3 +461,8 @@ export const { } = systemSlice.actions; export default systemSlice.reducer; + +const isAnySessionRejected = isAnyOf( + sessionCreated.rejected, + sessionInvoked.rejected +); diff --git a/invokeai/frontend/web/src/services/thunks/session.ts b/invokeai/frontend/web/src/services/thunks/session.ts index 546789d5b7..efab401520 100644 --- a/invokeai/frontend/web/src/services/thunks/session.ts +++ b/invokeai/frontend/web/src/services/thunks/session.ts @@ -1,6 +1,7 @@ import { createAppAsyncThunk } from 'app/store/storeUtils'; import { GraphExecutionState, SessionsService } from 'services/api'; import { log } from 'app/logging/useLogger'; +import { isObject } from 'lodash-es'; const sessionLog = log.child({ namespace: 'session' }); @@ -21,7 +22,7 @@ export const sessionCreated = createAppAsyncThunk< GraphExecutionState, SessionCreatedArg, SessionCreatedThunkConfig ->('api/sessionCreated', async (arg: SessionCreatedArg, { rejectWithValue }) => { +>('api/sessionCreated', async (arg, { rejectWithValue }) => { try { const response = await SessionsService.createSession({ requestBody: arg.graph, @@ -32,76 +33,41 @@ export const sessionCreated = createAppAsyncThunk< } }); -type NodeAddedArg = Parameters<(typeof SessionsService)['addNode']>[0]; +type SessionInvokedArg = { sessionId: string }; -/** - * `SessionsService.addNode()` thunk - */ -export const nodeAdded = createAppAsyncThunk( - 'api/nodeAdded', - async ( - arg: { node: NodeAddedArg['requestBody']; sessionId: string }, - _thunkApi - ) => { - const response = await SessionsService.addNode({ - requestBody: arg.node, - sessionId: arg.sessionId, - }); +type SessionInvokedThunkConfig = { + rejectValue: { + arg: SessionInvokedArg; + error: unknown; + }; +}; - sessionLog.info({ arg, response }, `Node added (${response})`); - - return response; - } -); - -type NodeUpdatedArg = Parameters<(typeof SessionsService)['updateNode']>[0]; - -/** - * `SessionsService.addNode()` thunk - */ -export const nodeUpdated = createAppAsyncThunk( - 'api/nodeUpdated', - async ( - arg: { node: NodeUpdatedArg['requestBody']; sessionId: string }, - _thunkApi - ) => { - const response = await SessionsService.updateNode({ - requestBody: arg.node, - sessionId: arg.sessionId, - nodePath: arg.node.id, - }); - - sessionLog.info({ arg, response }, `Node updated (${response})`); - - return response; - } -); +const isErrorWithStatus = (error: unknown): error is { status: number } => + isObject(error) && 'status' in error; /** * `SessionsService.invokeSession()` thunk */ -export const sessionInvoked = createAppAsyncThunk( - 'api/sessionInvoked', - async (arg: { sessionId: string }, { rejectWithValue }) => { - const { sessionId } = arg; +export const sessionInvoked = createAppAsyncThunk< + any, + SessionInvokedArg, + SessionInvokedThunkConfig +>('api/sessionInvoked', async (arg, { rejectWithValue }) => { + const { sessionId } = arg; - try { - const response = await SessionsService.invokeSession({ - sessionId, - all: true, - }); - sessionLog.info({ arg, response }, `Session invoked (${sessionId})`); - - return response; - } catch (error) { - const err = error as any; - if (err.status === 403) { - return rejectWithValue(err.body.detail); - } - throw error; + try { + const response = await SessionsService.invokeSession({ + sessionId, + all: true, + }); + return response; + } catch (error) { + if (isErrorWithStatus(error) && error.status === 403) { + return rejectWithValue({ arg, error: (error as any).body.detail }); } + return rejectWithValue({ arg, error }); } -); +}); type SessionCanceledArg = Parameters< (typeof SessionsService)['cancelSessionInvoke'] From 30e0033ebe8955317cc962b93549a9c44031ea28 Mon Sep 17 00:00:00 2001 From: psychedelicious <4822129+psychedelicious@users.noreply.github.com> Date: Fri, 26 May 2023 16:23:18 +1000 Subject: [PATCH 09/16] fix(ui): fix results not added to gallery --- .../listeners/imageMetadataReceived.ts | 16 ++++++---------- .../listeners/imageUploaded.ts | 8 ++++---- .../src/features/gallery/store/resultsSlice.ts | 4 ++-- .../src/features/gallery/store/uploadsSlice.ts | 4 ++-- 4 files changed, 14 insertions(+), 18 deletions(-) diff --git a/invokeai/frontend/web/src/app/store/middleware/listenerMiddleware/listeners/imageMetadataReceived.ts b/invokeai/frontend/web/src/app/store/middleware/listenerMiddleware/listeners/imageMetadataReceived.ts index 417b7c49cf..aa386a2f7f 100644 --- a/invokeai/frontend/web/src/app/store/middleware/listenerMiddleware/listeners/imageMetadataReceived.ts +++ b/invokeai/frontend/web/src/app/store/middleware/listenerMiddleware/listeners/imageMetadataReceived.ts @@ -3,11 +3,11 @@ import { startAppListening } from '..'; import { imageMetadataReceived } from 'services/thunks/image'; import { ResultsImageDTO, - resultsAdapter, + resultUpserted, } from 'features/gallery/store/resultsSlice'; import { UploadsImageDTO, - uploadsAdapter, + uploadUpserted, } from 'features/gallery/store/uploadsSlice'; const moduleLog = log.child({ namespace: 'image' }); @@ -20,17 +20,13 @@ export const addImageMetadataReceivedFulfilledListener = () => { moduleLog.debug({ data: { image } }, 'Image metadata received'); if (image.image_type === 'results') { - resultsAdapter.upsertOne( - getState().results, - action.payload as ResultsImageDTO - ); + console.log('upsert results'); + dispatch(resultUpserted(action.payload as ResultsImageDTO)); } if (image.image_type === 'uploads') { - uploadsAdapter.upsertOne( - getState().uploads, - action.payload as UploadsImageDTO - ); + console.log('upsert uploads'); + dispatch(uploadUpserted(action.payload as UploadsImageDTO)); } }, }); diff --git a/invokeai/frontend/web/src/app/store/middleware/listenerMiddleware/listeners/imageUploaded.ts b/invokeai/frontend/web/src/app/store/middleware/listenerMiddleware/listeners/imageUploaded.ts index c0ed294850..5b177eae91 100644 --- a/invokeai/frontend/web/src/app/store/middleware/listenerMiddleware/listeners/imageUploaded.ts +++ b/invokeai/frontend/web/src/app/store/middleware/listenerMiddleware/listeners/imageUploaded.ts @@ -1,11 +1,11 @@ import { startAppListening } from '..'; -import { uploadAdded } from 'features/gallery/store/uploadsSlice'; +import { uploadUpserted } from 'features/gallery/store/uploadsSlice'; import { imageSelected } from 'features/gallery/store/gallerySlice'; import { imageUploaded } from 'services/thunks/image'; import { addToast } from 'features/system/store/systemSlice'; import { initialImageSelected } from 'features/parameters/store/actions'; import { setInitialCanvasImage } from 'features/canvas/store/canvasSlice'; -import { resultAdded } from 'features/gallery/store/resultsSlice'; +import { resultUpserted } from 'features/gallery/store/resultsSlice'; import { isResultsImageDTO, isUploadsImageDTO } from 'services/types/guards'; import { log } from 'app/logging/useLogger'; @@ -25,7 +25,7 @@ export const addImageUploadedFulfilledListener = () => { // Handle uploads if (isUploadsImageDTO(image)) { - dispatch(uploadAdded(image)); + dispatch(uploadUpserted(image)); dispatch(addToast({ title: 'Image Uploaded', status: 'success' })); @@ -45,7 +45,7 @@ export const addImageUploadedFulfilledListener = () => { // Handle results // TODO: Can this ever happen? I don't think so... if (isResultsImageDTO(image)) { - dispatch(resultAdded(image)); + dispatch(resultUpserted(image)); } }, }); diff --git a/invokeai/frontend/web/src/features/gallery/store/resultsSlice.ts b/invokeai/frontend/web/src/features/gallery/store/resultsSlice.ts index 18d0466033..e601af0443 100644 --- a/invokeai/frontend/web/src/features/gallery/store/resultsSlice.ts +++ b/invokeai/frontend/web/src/features/gallery/store/resultsSlice.ts @@ -37,7 +37,7 @@ const resultsSlice = createSlice({ name: 'results', initialState: initialResultsState, reducers: { - resultAdded: resultsAdapter.upsertOne, + resultUpserted: resultsAdapter.upsertOne, }, extraReducers: (builder) => { /** @@ -74,6 +74,6 @@ export const { selectTotal: selectResultsTotal, } = resultsAdapter.getSelectors((state) => state.results); -export const { resultAdded } = resultsSlice.actions; +export const { resultUpserted } = resultsSlice.actions; export default resultsSlice.reducer; diff --git a/invokeai/frontend/web/src/features/gallery/store/uploadsSlice.ts b/invokeai/frontend/web/src/features/gallery/store/uploadsSlice.ts index 1a03ed3391..599e1b3830 100644 --- a/invokeai/frontend/web/src/features/gallery/store/uploadsSlice.ts +++ b/invokeai/frontend/web/src/features/gallery/store/uploadsSlice.ts @@ -38,7 +38,7 @@ const uploadsSlice = createSlice({ name: 'uploads', initialState: initialUploadsState, reducers: { - uploadAdded: uploadsAdapter.upsertOne, + uploadUpserted: uploadsAdapter.upsertOne, }, extraReducers: (builder) => { /** @@ -75,6 +75,6 @@ export const { selectTotal: selectUploadsTotal, } = uploadsAdapter.getSelectors((state) => state.uploads); -export const { uploadAdded } = uploadsSlice.actions; +export const { uploadUpserted } = uploadsSlice.actions; export default uploadsSlice.reducer; From 39088e42cc3ce75c5cd5051d7178875a9c8a8114 Mon Sep 17 00:00:00 2001 From: psychedelicious <4822129+psychedelicious@users.noreply.github.com> Date: Fri, 26 May 2023 16:25:08 +1000 Subject: [PATCH 10/16] fix(ui): remove console logs --- .../listenerMiddleware/listeners/imageMetadataReceived.ts | 2 -- 1 file changed, 2 deletions(-) diff --git a/invokeai/frontend/web/src/app/store/middleware/listenerMiddleware/listeners/imageMetadataReceived.ts b/invokeai/frontend/web/src/app/store/middleware/listenerMiddleware/listeners/imageMetadataReceived.ts index aa386a2f7f..c93ed2820f 100644 --- a/invokeai/frontend/web/src/app/store/middleware/listenerMiddleware/listeners/imageMetadataReceived.ts +++ b/invokeai/frontend/web/src/app/store/middleware/listenerMiddleware/listeners/imageMetadataReceived.ts @@ -20,12 +20,10 @@ export const addImageMetadataReceivedFulfilledListener = () => { moduleLog.debug({ data: { image } }, 'Image metadata received'); if (image.image_type === 'results') { - console.log('upsert results'); dispatch(resultUpserted(action.payload as ResultsImageDTO)); } if (image.image_type === 'uploads') { - console.log('upsert uploads'); dispatch(uploadUpserted(action.payload as UploadsImageDTO)); } }, From 249522b568452bd3076fd7d88022e09a62a9088e Mon Sep 17 00:00:00 2001 From: psychedelicious <4822129+psychedelicious@users.noreply.github.com> Date: Fri, 26 May 2023 16:42:24 +1000 Subject: [PATCH 11/16] fix(ui): fix gallery not loading more images correctly after generation --- .../web/src/features/gallery/store/resultsSlice.ts | 13 +++++++++++-- .../frontend/web/src/services/thunks/gallery.ts | 12 ++++++++---- 2 files changed, 19 insertions(+), 6 deletions(-) diff --git a/invokeai/frontend/web/src/features/gallery/store/resultsSlice.ts b/invokeai/frontend/web/src/features/gallery/store/resultsSlice.ts index e601af0443..36f4c49401 100644 --- a/invokeai/frontend/web/src/features/gallery/store/resultsSlice.ts +++ b/invokeai/frontend/web/src/features/gallery/store/resultsSlice.ts @@ -1,4 +1,8 @@ -import { createEntityAdapter, createSlice } from '@reduxjs/toolkit'; +import { + PayloadAction, + createEntityAdapter, + createSlice, +} from '@reduxjs/toolkit'; import { RootState } from 'app/store/store'; import { receivedResultImagesPage, @@ -21,6 +25,7 @@ type AdditionalResultsState = { pages: number; isLoading: boolean; nextPage: number; + upsertedImageCount: number; }; export const initialResultsState = @@ -29,6 +34,7 @@ export const initialResultsState = pages: 0, isLoading: false, nextPage: 0, + upsertedImageCount: 0, }); export type ResultsState = typeof initialResultsState; @@ -37,7 +43,10 @@ const resultsSlice = createSlice({ name: 'results', initialState: initialResultsState, reducers: { - resultUpserted: resultsAdapter.upsertOne, + resultUpserted: (state, action: PayloadAction) => { + resultsAdapter.upsertOne(state, action.payload); + state.upsertedImageCount += 1; + }, }, extraReducers: (builder) => { /** diff --git a/invokeai/frontend/web/src/services/thunks/gallery.ts b/invokeai/frontend/web/src/services/thunks/gallery.ts index 25d8ce0738..85e60326a4 100644 --- a/invokeai/frontend/web/src/services/thunks/gallery.ts +++ b/invokeai/frontend/web/src/services/thunks/gallery.ts @@ -9,12 +9,14 @@ const galleryLog = log.child({ namespace: 'gallery' }); export const receivedResultImagesPage = createAppAsyncThunk( 'results/receivedResultImagesPage', async (_arg, { getState, rejectWithValue }) => { - const { page, pages, nextPage } = getState().results; + const { page, pages, nextPage, upsertedImageCount } = getState().results; + + const pageOffset = Math.floor(upsertedImageCount / IMAGES_PER_PAGE); const response = await ImagesService.listImagesWithMetadata({ imageType: 'results', imageCategory: 'general', - page: getState().results.nextPage, + page: nextPage + pageOffset, perPage: IMAGES_PER_PAGE, }); @@ -27,12 +29,14 @@ export const receivedResultImagesPage = createAppAsyncThunk( export const receivedUploadImagesPage = createAppAsyncThunk( 'uploads/receivedUploadImagesPage', async (_arg, { getState, rejectWithValue }) => { - const { page, pages, nextPage } = getState().uploads; + const { page, pages, nextPage, upsertedImageCount } = getState().uploads; + + const pageOffset = Math.floor(upsertedImageCount / IMAGES_PER_PAGE); const response = await ImagesService.listImagesWithMetadata({ imageType: 'uploads', imageCategory: 'general', - page: getState().uploads.nextPage, + page: nextPage + pageOffset, perPage: IMAGES_PER_PAGE, }); From 7b0938e7e48b99216c12ba073eddaedb9e14e6b2 Mon Sep 17 00:00:00 2001 From: psychedelicious <4822129+psychedelicious@users.noreply.github.com> Date: Fri, 26 May 2023 16:43:25 +1000 Subject: [PATCH 12/16] feat(ui): add comments for weird stuff --- invokeai/frontend/web/src/services/thunks/gallery.ts | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/invokeai/frontend/web/src/services/thunks/gallery.ts b/invokeai/frontend/web/src/services/thunks/gallery.ts index 85e60326a4..9e7ee6d5c6 100644 --- a/invokeai/frontend/web/src/services/thunks/gallery.ts +++ b/invokeai/frontend/web/src/services/thunks/gallery.ts @@ -11,6 +11,8 @@ export const receivedResultImagesPage = createAppAsyncThunk( async (_arg, { getState, rejectWithValue }) => { const { page, pages, nextPage, upsertedImageCount } = getState().results; + // If many images have been upserted, we need to offset the page number + // TODO: add an offset param to the list images endpoint const pageOffset = Math.floor(upsertedImageCount / IMAGES_PER_PAGE); const response = await ImagesService.listImagesWithMetadata({ @@ -31,6 +33,8 @@ export const receivedUploadImagesPage = createAppAsyncThunk( async (_arg, { getState, rejectWithValue }) => { const { page, pages, nextPage, upsertedImageCount } = getState().uploads; + // If many images have been upserted, we need to offset the page number + // TODO: add an offset param to the list images endpoint const pageOffset = Math.floor(upsertedImageCount / IMAGES_PER_PAGE); const response = await ImagesService.listImagesWithMetadata({ From 96b4d35d43279c1349b445dc6a741853dcc0073d Mon Sep 17 00:00:00 2001 From: psychedelicious <4822129+psychedelicious@users.noreply.github.com> Date: Fri, 26 May 2023 16:44:13 +1000 Subject: [PATCH 13/16] fix(ui): fix uploads not loading more images correctly after generation --- .../web/src/features/gallery/store/uploadsSlice.ts | 13 +++++++++++-- 1 file changed, 11 insertions(+), 2 deletions(-) diff --git a/invokeai/frontend/web/src/features/gallery/store/uploadsSlice.ts b/invokeai/frontend/web/src/features/gallery/store/uploadsSlice.ts index 599e1b3830..3058e82673 100644 --- a/invokeai/frontend/web/src/features/gallery/store/uploadsSlice.ts +++ b/invokeai/frontend/web/src/features/gallery/store/uploadsSlice.ts @@ -1,4 +1,8 @@ -import { createEntityAdapter, createSlice } from '@reduxjs/toolkit'; +import { + PayloadAction, + createEntityAdapter, + createSlice, +} from '@reduxjs/toolkit'; import { RootState } from 'app/store/store'; import { @@ -22,6 +26,7 @@ type AdditionalUploadsState = { pages: number; isLoading: boolean; nextPage: number; + upsertedImageCount: number; }; export const initialUploadsState = @@ -30,6 +35,7 @@ export const initialUploadsState = pages: 0, nextPage: 0, isLoading: false, + upsertedImageCount: 0, }); export type UploadsState = typeof initialUploadsState; @@ -38,7 +44,10 @@ const uploadsSlice = createSlice({ name: 'uploads', initialState: initialUploadsState, reducers: { - uploadUpserted: uploadsAdapter.upsertOne, + uploadUpserted: (state, action: PayloadAction) => { + uploadsAdapter.upsertOne(state, action.payload); + state.upsertedImageCount += 1; + }, }, extraReducers: (builder) => { /** From c6f935bf1a79bdec83946b8606aae8e31956c966 Mon Sep 17 00:00:00 2001 From: psychedelicious <4822129+psychedelicious@users.noreply.github.com> Date: Fri, 26 May 2023 16:53:13 +1000 Subject: [PATCH 14/16] feat(ui): improve gallery page handling --- .../middleware/listenerMiddleware/index.ts | 14 ++++++++ .../listeners/receivedResultImagesPage.ts | 33 +++++++++++++++++++ .../listeners/receivedUploadImagesPage.ts | 33 +++++++++++++++++++ .../web/src/services/thunks/gallery.ts | 30 ++++++++++++----- 4 files changed, 102 insertions(+), 8 deletions(-) create mode 100644 invokeai/frontend/web/src/app/store/middleware/listenerMiddleware/listeners/receivedResultImagesPage.ts create mode 100644 invokeai/frontend/web/src/app/store/middleware/listenerMiddleware/listeners/receivedUploadImagesPage.ts diff --git a/invokeai/frontend/web/src/app/store/middleware/listenerMiddleware/index.ts b/invokeai/frontend/web/src/app/store/middleware/listenerMiddleware/index.ts index dae56804af..2996b0f34a 100644 --- a/invokeai/frontend/web/src/app/store/middleware/listenerMiddleware/index.ts +++ b/invokeai/frontend/web/src/app/store/middleware/listenerMiddleware/index.ts @@ -54,6 +54,14 @@ import { addSessionInvokedPendingListener, addSessionInvokedRejectedListener, } from './listeners/sessionInvoked'; +import { + addReceivedResultImagesPageFulfilledListener, + addReceivedResultImagesPageRejectedListener, +} from './listeners/receivedResultImagesPage'; +import { + addReceivedUploadImagesPageFulfilledListener, + addReceivedUploadImagesPageRejectedListener, +} from './listeners/receivedUploadImagesPage'; export const listenerMiddleware = createListenerMiddleware(); @@ -126,3 +134,9 @@ addSocketUnsubscribedListener(); addSessionCreatedPendingListener(); addSessionCreatedFulfilledListener(); addSessionCreatedRejectedListener(); + +// Gallery pages +addReceivedResultImagesPageFulfilledListener(); +addReceivedResultImagesPageRejectedListener(); +addReceivedUploadImagesPageFulfilledListener(); +addReceivedUploadImagesPageRejectedListener(); diff --git a/invokeai/frontend/web/src/app/store/middleware/listenerMiddleware/listeners/receivedResultImagesPage.ts b/invokeai/frontend/web/src/app/store/middleware/listenerMiddleware/listeners/receivedResultImagesPage.ts new file mode 100644 index 0000000000..5e4bff4b48 --- /dev/null +++ b/invokeai/frontend/web/src/app/store/middleware/listenerMiddleware/listeners/receivedResultImagesPage.ts @@ -0,0 +1,33 @@ +import { log } from 'app/logging/useLogger'; +import { startAppListening } from '..'; +import { receivedResultImagesPage } from 'services/thunks/gallery'; +import { serializeError } from 'serialize-error'; + +const moduleLog = log.child({ namespace: 'gallery' }); + +export const addReceivedResultImagesPageFulfilledListener = () => { + startAppListening({ + actionCreator: receivedResultImagesPage.fulfilled, + effect: (action, { getState, dispatch }) => { + const page = action.payload; + moduleLog.info( + { data: { page } }, + `Received ${page.items.length} results` + ); + }, + }); +}; + +export const addReceivedResultImagesPageRejectedListener = () => { + startAppListening({ + actionCreator: receivedResultImagesPage.rejected, + effect: (action, { getState, dispatch }) => { + if (action.payload) { + moduleLog.debug( + { data: { error: serializeError(action.payload.error) } }, + 'Problem receiving results' + ); + } + }, + }); +}; diff --git a/invokeai/frontend/web/src/app/store/middleware/listenerMiddleware/listeners/receivedUploadImagesPage.ts b/invokeai/frontend/web/src/app/store/middleware/listenerMiddleware/listeners/receivedUploadImagesPage.ts new file mode 100644 index 0000000000..7b593987e5 --- /dev/null +++ b/invokeai/frontend/web/src/app/store/middleware/listenerMiddleware/listeners/receivedUploadImagesPage.ts @@ -0,0 +1,33 @@ +import { log } from 'app/logging/useLogger'; +import { startAppListening } from '..'; +import { receivedUploadImagesPage } from 'services/thunks/gallery'; +import { serializeError } from 'serialize-error'; + +const moduleLog = log.child({ namespace: 'gallery' }); + +export const addReceivedUploadImagesPageFulfilledListener = () => { + startAppListening({ + actionCreator: receivedUploadImagesPage.fulfilled, + effect: (action, { getState, dispatch }) => { + const page = action.payload; + moduleLog.info( + { data: { page } }, + `Received ${page.items.length} uploads` + ); + }, + }); +}; + +export const addReceivedUploadImagesPageRejectedListener = () => { + startAppListening({ + actionCreator: receivedUploadImagesPage.rejected, + effect: (action, { getState, dispatch }) => { + if (action.payload) { + moduleLog.debug( + { data: { error: serializeError(action.payload.error) } }, + 'Problem receiving uploads' + ); + } + }, + }); +}; diff --git a/invokeai/frontend/web/src/services/thunks/gallery.ts b/invokeai/frontend/web/src/services/thunks/gallery.ts index 9e7ee6d5c6..8a6505b6c2 100644 --- a/invokeai/frontend/web/src/services/thunks/gallery.ts +++ b/invokeai/frontend/web/src/services/thunks/gallery.ts @@ -1,12 +1,20 @@ import { log } from 'app/logging/useLogger'; import { createAppAsyncThunk } from 'app/store/storeUtils'; -import { ImagesService } from 'services/api'; +import { ImagesService, PaginatedResults_ImageDTO_ } from 'services/api'; export const IMAGES_PER_PAGE = 20; -const galleryLog = log.child({ namespace: 'gallery' }); +type ReceivedResultImagesPageThunkConfig = { + rejectValue: { + error: unknown; + }; +}; -export const receivedResultImagesPage = createAppAsyncThunk( +export const receivedResultImagesPage = createAppAsyncThunk< + PaginatedResults_ImageDTO_, + void, + ReceivedResultImagesPageThunkConfig +>( 'results/receivedResultImagesPage', async (_arg, { getState, rejectWithValue }) => { const { page, pages, nextPage, upsertedImageCount } = getState().results; @@ -22,13 +30,21 @@ export const receivedResultImagesPage = createAppAsyncThunk( perPage: IMAGES_PER_PAGE, }); - galleryLog.info({ response }, `Received ${response.items.length} results`); - return response; } ); -export const receivedUploadImagesPage = createAppAsyncThunk( +type ReceivedUploadImagesPageThunkConfig = { + rejectValue: { + error: unknown; + }; +}; + +export const receivedUploadImagesPage = createAppAsyncThunk< + PaginatedResults_ImageDTO_, + void, + ReceivedUploadImagesPageThunkConfig +>( 'uploads/receivedUploadImagesPage', async (_arg, { getState, rejectWithValue }) => { const { page, pages, nextPage, upsertedImageCount } = getState().uploads; @@ -44,8 +60,6 @@ export const receivedUploadImagesPage = createAppAsyncThunk( perPage: IMAGES_PER_PAGE, }); - galleryLog.info({ response }, `Received ${response.items.length} uploads`); - return response; } ); From 0ea35b1e3d8495ae9eb5ccddd48b284d43bd45cc Mon Sep 17 00:00:00 2001 From: psychedelicious <4822129+psychedelicious@users.noreply.github.com> Date: Fri, 26 May 2023 16:59:13 +1000 Subject: [PATCH 15/16] feat(ui): improve session canceled handling --- .../middleware/listenerMiddleware/index.ts | 24 +++++++--- .../listeners/sessionCanceled.ts | 48 +++++++++++++++++++ .../web/src/services/thunks/gallery.ts | 1 - .../web/src/services/thunks/session.ts | 32 +++++++------ 4 files changed, 83 insertions(+), 22 deletions(-) create mode 100644 invokeai/frontend/web/src/app/store/middleware/listenerMiddleware/listeners/sessionCanceled.ts diff --git a/invokeai/frontend/web/src/app/store/middleware/listenerMiddleware/index.ts b/invokeai/frontend/web/src/app/store/middleware/listenerMiddleware/index.ts index 2996b0f34a..1fbc2f978c 100644 --- a/invokeai/frontend/web/src/app/store/middleware/listenerMiddleware/index.ts +++ b/invokeai/frontend/web/src/app/store/middleware/listenerMiddleware/index.ts @@ -54,6 +54,11 @@ import { addSessionInvokedPendingListener, addSessionInvokedRejectedListener, } from './listeners/sessionInvoked'; +import { + addSessionCanceledFulfilledListener, + addSessionCanceledPendingListener, + addSessionCanceledRejectedListener, +} from './listeners/sessionCanceled'; import { addReceivedResultImagesPageFulfilledListener, addReceivedResultImagesPageRejectedListener, @@ -101,18 +106,13 @@ addImageMetadataReceivedRejectedListener(); addImageUrlsReceivedFulfilledListener(); addImageUrlsReceivedRejectedListener(); -// Invoking on tabs +// User Invoked addUserInvokedCanvasListener(); addUserInvokedNodesListener(); addUserInvokedTextToImageListener(); addUserInvokedImageToImageListener(); addSessionReadyToInvokeListener(); -// Actual session invoking -addSessionInvokedPendingListener(); -addSessionInvokedFulfilledListener(); -addSessionInvokedRejectedListener(); - // Canvas actions addCanvasSavedToGalleryListener(); addCanvasDownloadedAsImageListener(); @@ -130,11 +130,21 @@ addSocketDisconnectedListener(); addSocketSubscribedListener(); addSocketUnsubscribedListener(); -// Sessions +// Session Created addSessionCreatedPendingListener(); addSessionCreatedFulfilledListener(); addSessionCreatedRejectedListener(); +// Session Invoked +addSessionInvokedPendingListener(); +addSessionInvokedFulfilledListener(); +addSessionInvokedRejectedListener(); + +// Session Canceled +addSessionCanceledPendingListener(); +addSessionCanceledFulfilledListener(); +addSessionCanceledRejectedListener(); + // Gallery pages addReceivedResultImagesPageFulfilledListener(); addReceivedResultImagesPageRejectedListener(); diff --git a/invokeai/frontend/web/src/app/store/middleware/listenerMiddleware/listeners/sessionCanceled.ts b/invokeai/frontend/web/src/app/store/middleware/listenerMiddleware/listeners/sessionCanceled.ts new file mode 100644 index 0000000000..6274ad4dc8 --- /dev/null +++ b/invokeai/frontend/web/src/app/store/middleware/listenerMiddleware/listeners/sessionCanceled.ts @@ -0,0 +1,48 @@ +import { log } from 'app/logging/useLogger'; +import { startAppListening } from '..'; +import { sessionCanceled } from 'services/thunks/session'; +import { serializeError } from 'serialize-error'; + +const moduleLog = log.child({ namespace: 'session' }); + +export const addSessionCanceledPendingListener = () => { + startAppListening({ + actionCreator: sessionCanceled.pending, + effect: (action, { getState, dispatch }) => { + // + }, + }); +}; + +export const addSessionCanceledFulfilledListener = () => { + startAppListening({ + actionCreator: sessionCanceled.fulfilled, + effect: (action, { getState, dispatch }) => { + const { sessionId } = action.meta.arg; + moduleLog.debug( + { data: { sessionId } }, + `Session canceled (${sessionId})` + ); + }, + }); +}; + +export const addSessionCanceledRejectedListener = () => { + startAppListening({ + actionCreator: sessionCanceled.rejected, + effect: (action, { getState, dispatch }) => { + if (action.payload) { + const { arg, error } = action.payload; + moduleLog.error( + { + data: { + arg, + error: serializeError(error), + }, + }, + `Problem canceling session` + ); + } + }, + }); +}; diff --git a/invokeai/frontend/web/src/services/thunks/gallery.ts b/invokeai/frontend/web/src/services/thunks/gallery.ts index 8a6505b6c2..11960e00d2 100644 --- a/invokeai/frontend/web/src/services/thunks/gallery.ts +++ b/invokeai/frontend/web/src/services/thunks/gallery.ts @@ -1,4 +1,3 @@ -import { log } from 'app/logging/useLogger'; import { createAppAsyncThunk } from 'app/store/storeUtils'; import { ImagesService, PaginatedResults_ImageDTO_ } from 'services/api'; diff --git a/invokeai/frontend/web/src/services/thunks/session.ts b/invokeai/frontend/web/src/services/thunks/session.ts index efab401520..cf87fb30f5 100644 --- a/invokeai/frontend/web/src/services/thunks/session.ts +++ b/invokeai/frontend/web/src/services/thunks/session.ts @@ -49,7 +49,7 @@ const isErrorWithStatus = (error: unknown): error is { status: number } => * `SessionsService.invokeSession()` thunk */ export const sessionInvoked = createAppAsyncThunk< - any, + void, SessionInvokedArg, SessionInvokedThunkConfig >('api/sessionInvoked', async (arg, { rejectWithValue }) => { @@ -72,24 +72,28 @@ export const sessionInvoked = createAppAsyncThunk< type SessionCanceledArg = Parameters< (typeof SessionsService)['cancelSessionInvoke'] >[0]; - +type SessionCanceledThunkConfig = { + rejectValue: { + arg: SessionCanceledArg; + error: unknown; + }; +}; /** * `SessionsService.cancelSession()` thunk */ -export const sessionCanceled = createAppAsyncThunk( - 'api/sessionCanceled', - async (arg: SessionCanceledArg, _thunkApi) => { - const { sessionId } = arg; +export const sessionCanceled = createAppAsyncThunk< + void, + SessionCanceledArg, + SessionCanceledThunkConfig +>('api/sessionCanceled', async (arg: SessionCanceledArg, _thunkApi) => { + const { sessionId } = arg; - const response = await SessionsService.cancelSessionInvoke({ - sessionId, - }); + const response = await SessionsService.cancelSessionInvoke({ + sessionId, + }); - sessionLog.info({ arg, response }, `Session canceled (${sessionId})`); - - return response; - } -); + return response; +}); type SessionsListedArg = Parameters< (typeof SessionsService)['listSessions'] From a25bae2545e2dfa7821ff5b8aadbec56940981cb Mon Sep 17 00:00:00 2001 From: psychedelicious <4822129+psychedelicious@users.noreply.github.com> Date: Fri, 26 May 2023 17:27:42 +1000 Subject: [PATCH 16/16] fix(ui): tweak log levels --- .../middleware/listenerMiddleware/listeners/imageDeleted.ts | 4 ++-- .../listenerMiddleware/listeners/receivedResultImagesPage.ts | 2 +- .../listenerMiddleware/listeners/receivedUploadImagesPage.ts | 2 +- .../listenerMiddleware/listeners/sessionCreated.ts | 2 +- .../listenerMiddleware/listeners/sessionInvoked.ts | 5 ++++- .../listenerMiddleware/listeners/sessionReadyToInvoke.ts | 2 +- .../listeners/socketio/graphExecutionStateComplete.ts | 2 +- .../listeners/socketio/invocationComplete.ts | 2 +- .../listenerMiddleware/listeners/socketio/invocationError.ts | 2 +- .../listeners/socketio/invocationStarted.ts | 2 +- .../listenerMiddleware/listeners/userInvokedCanvas.ts | 2 +- .../listenerMiddleware/listeners/userInvokedImageToImage.ts | 2 +- .../listenerMiddleware/listeners/userInvokedNodes.ts | 2 +- .../listenerMiddleware/listeners/userInvokedTextToImage.ts | 2 +- 14 files changed, 18 insertions(+), 15 deletions(-) diff --git a/invokeai/frontend/web/src/app/store/middleware/listenerMiddleware/listeners/imageDeleted.ts b/invokeai/frontend/web/src/app/store/middleware/listenerMiddleware/listeners/imageDeleted.ts index 804c713fc5..cd4771b96a 100644 --- a/invokeai/frontend/web/src/app/store/middleware/listenerMiddleware/listeners/imageDeleted.ts +++ b/invokeai/frontend/web/src/app/store/middleware/listenerMiddleware/listeners/imageDeleted.ts @@ -84,7 +84,7 @@ export const addImageDeletedFulfilledListener = () => { startAppListening({ actionCreator: imageDeleted.fulfilled, effect: (action, { dispatch, getState }) => { - moduleLog.info({ data: { image: action.meta.arg } }, 'Image deleted'); + moduleLog.debug({ data: { image: action.meta.arg } }, 'Image deleted'); }, }); }; @@ -96,7 +96,7 @@ export const addImageDeletedRejectedListener = () => { startAppListening({ actionCreator: imageDeleted.rejected, effect: (action, { dispatch, getState }) => { - moduleLog.warn( + moduleLog.debug( { data: { image: action.meta.arg } }, 'Unable to delete image' ); diff --git a/invokeai/frontend/web/src/app/store/middleware/listenerMiddleware/listeners/receivedResultImagesPage.ts b/invokeai/frontend/web/src/app/store/middleware/listenerMiddleware/listeners/receivedResultImagesPage.ts index 5e4bff4b48..bcdd11ef97 100644 --- a/invokeai/frontend/web/src/app/store/middleware/listenerMiddleware/listeners/receivedResultImagesPage.ts +++ b/invokeai/frontend/web/src/app/store/middleware/listenerMiddleware/listeners/receivedResultImagesPage.ts @@ -10,7 +10,7 @@ export const addReceivedResultImagesPageFulfilledListener = () => { actionCreator: receivedResultImagesPage.fulfilled, effect: (action, { getState, dispatch }) => { const page = action.payload; - moduleLog.info( + moduleLog.debug( { data: { page } }, `Received ${page.items.length} results` ); diff --git a/invokeai/frontend/web/src/app/store/middleware/listenerMiddleware/listeners/receivedUploadImagesPage.ts b/invokeai/frontend/web/src/app/store/middleware/listenerMiddleware/listeners/receivedUploadImagesPage.ts index 7b593987e5..68813aae27 100644 --- a/invokeai/frontend/web/src/app/store/middleware/listenerMiddleware/listeners/receivedUploadImagesPage.ts +++ b/invokeai/frontend/web/src/app/store/middleware/listenerMiddleware/listeners/receivedUploadImagesPage.ts @@ -10,7 +10,7 @@ export const addReceivedUploadImagesPageFulfilledListener = () => { actionCreator: receivedUploadImagesPage.fulfilled, effect: (action, { getState, dispatch }) => { const page = action.payload; - moduleLog.info( + moduleLog.debug( { data: { page } }, `Received ${page.items.length} uploads` ); diff --git a/invokeai/frontend/web/src/app/store/middleware/listenerMiddleware/listeners/sessionCreated.ts b/invokeai/frontend/web/src/app/store/middleware/listenerMiddleware/listeners/sessionCreated.ts index e960510d0f..fb8a64d2e3 100644 --- a/invokeai/frontend/web/src/app/store/middleware/listenerMiddleware/listeners/sessionCreated.ts +++ b/invokeai/frontend/web/src/app/store/middleware/listenerMiddleware/listeners/sessionCreated.ts @@ -19,7 +19,7 @@ export const addSessionCreatedFulfilledListener = () => { actionCreator: sessionCreated.fulfilled, effect: (action, { getState, dispatch }) => { const session = action.payload; - moduleLog.info({ data: { session } }, `Session created (${session.id})`); + moduleLog.debug({ data: { session } }, `Session created (${session.id})`); }, }); }; diff --git a/invokeai/frontend/web/src/app/store/middleware/listenerMiddleware/listeners/sessionInvoked.ts b/invokeai/frontend/web/src/app/store/middleware/listenerMiddleware/listeners/sessionInvoked.ts index bddadbc359..272d1d9e1d 100644 --- a/invokeai/frontend/web/src/app/store/middleware/listenerMiddleware/listeners/sessionInvoked.ts +++ b/invokeai/frontend/web/src/app/store/middleware/listenerMiddleware/listeners/sessionInvoked.ts @@ -19,7 +19,10 @@ export const addSessionInvokedFulfilledListener = () => { actionCreator: sessionInvoked.fulfilled, effect: (action, { getState, dispatch }) => { const { sessionId } = action.meta.arg; - moduleLog.info({ data: { sessionId } }, `Session invoked (${sessionId})`); + moduleLog.debug( + { data: { sessionId } }, + `Session invoked (${sessionId})` + ); }, }); }; diff --git a/invokeai/frontend/web/src/app/store/middleware/listenerMiddleware/listeners/sessionReadyToInvoke.ts b/invokeai/frontend/web/src/app/store/middleware/listenerMiddleware/listeners/sessionReadyToInvoke.ts index 4fae2d96c0..8d4262e7da 100644 --- a/invokeai/frontend/web/src/app/store/middleware/listenerMiddleware/listeners/sessionReadyToInvoke.ts +++ b/invokeai/frontend/web/src/app/store/middleware/listenerMiddleware/listeners/sessionReadyToInvoke.ts @@ -11,7 +11,7 @@ export const addSessionReadyToInvokeListener = () => { effect: (action, { getState, dispatch }) => { const { sessionId } = getState().system; if (sessionId) { - moduleLog.info( + moduleLog.debug( { sessionId }, `Session ready to invoke (${sessionId})})` ); diff --git a/invokeai/frontend/web/src/app/store/middleware/listenerMiddleware/listeners/socketio/graphExecutionStateComplete.ts b/invokeai/frontend/web/src/app/store/middleware/listenerMiddleware/listeners/socketio/graphExecutionStateComplete.ts index c8ac46f6f1..a66a7fb547 100644 --- a/invokeai/frontend/web/src/app/store/middleware/listenerMiddleware/listeners/socketio/graphExecutionStateComplete.ts +++ b/invokeai/frontend/web/src/app/store/middleware/listenerMiddleware/listeners/socketio/graphExecutionStateComplete.ts @@ -10,7 +10,7 @@ export const addGraphExecutionStateCompleteListener = () => { effect: (action, { dispatch, getState }) => { moduleLog.debug( action.payload, - `Graph execution state complete (${action.payload.data.graph_execution_state_id})` + `Session invocation complete (${action.payload.data.graph_execution_state_id})` ); }, }); diff --git a/invokeai/frontend/web/src/app/store/middleware/listenerMiddleware/listeners/socketio/invocationComplete.ts b/invokeai/frontend/web/src/app/store/middleware/listenerMiddleware/listeners/socketio/invocationComplete.ts index cfd9706306..95e6d831c0 100644 --- a/invokeai/frontend/web/src/app/store/middleware/listenerMiddleware/listeners/socketio/invocationComplete.ts +++ b/invokeai/frontend/web/src/app/store/middleware/listenerMiddleware/listeners/socketio/invocationComplete.ts @@ -15,7 +15,7 @@ export const addInvocationCompleteListener = () => { startAppListening({ actionCreator: invocationComplete, effect: async (action, { dispatch, getState, take }) => { - moduleLog.info( + moduleLog.debug( action.payload, `Invocation complete (${action.payload.data.node.type})` ); diff --git a/invokeai/frontend/web/src/app/store/middleware/listenerMiddleware/listeners/socketio/invocationError.ts b/invokeai/frontend/web/src/app/store/middleware/listenerMiddleware/listeners/socketio/invocationError.ts index d0e4d975be..3a98af120a 100644 --- a/invokeai/frontend/web/src/app/store/middleware/listenerMiddleware/listeners/socketio/invocationError.ts +++ b/invokeai/frontend/web/src/app/store/middleware/listenerMiddleware/listeners/socketio/invocationError.ts @@ -8,7 +8,7 @@ export const addInvocationErrorListener = () => { startAppListening({ actionCreator: invocationError, effect: (action, { dispatch, getState }) => { - moduleLog.debug( + moduleLog.error( action.payload, `Invocation error (${action.payload.data.node.type})` ); diff --git a/invokeai/frontend/web/src/app/store/middleware/listenerMiddleware/listeners/socketio/invocationStarted.ts b/invokeai/frontend/web/src/app/store/middleware/listenerMiddleware/listeners/socketio/invocationStarted.ts index 373802fa16..f898c62b23 100644 --- a/invokeai/frontend/web/src/app/store/middleware/listenerMiddleware/listeners/socketio/invocationStarted.ts +++ b/invokeai/frontend/web/src/app/store/middleware/listenerMiddleware/listeners/socketio/invocationStarted.ts @@ -19,7 +19,7 @@ export const addInvocationStartedListener = () => { return; } - moduleLog.info( + moduleLog.debug( action.payload, `Invocation started (${action.payload.data.node.type})` ); diff --git a/invokeai/frontend/web/src/app/store/middleware/listenerMiddleware/listeners/userInvokedCanvas.ts b/invokeai/frontend/web/src/app/store/middleware/listenerMiddleware/listeners/userInvokedCanvas.ts index 641cbb5ca5..ae388b85cf 100644 --- a/invokeai/frontend/web/src/app/store/middleware/listenerMiddleware/listeners/userInvokedCanvas.ts +++ b/invokeai/frontend/web/src/app/store/middleware/listenerMiddleware/listeners/userInvokedCanvas.ts @@ -91,7 +91,7 @@ export const addUserInvokedCanvasListener = () => { dispatch(canvasGraphBuilt(graph)); - moduleLog({ data: graph }, 'Canvas graph built'); + moduleLog.debug({ data: graph }, 'Canvas graph built'); // If we are generating img2img or inpaint, we need to upload the init images if (baseNode.type === 'img2img' || baseNode.type === 'inpaint') { diff --git a/invokeai/frontend/web/src/app/store/middleware/listenerMiddleware/listeners/userInvokedImageToImage.ts b/invokeai/frontend/web/src/app/store/middleware/listenerMiddleware/listeners/userInvokedImageToImage.ts index 8940237782..7dcbe8a41d 100644 --- a/invokeai/frontend/web/src/app/store/middleware/listenerMiddleware/listeners/userInvokedImageToImage.ts +++ b/invokeai/frontend/web/src/app/store/middleware/listenerMiddleware/listeners/userInvokedImageToImage.ts @@ -17,7 +17,7 @@ export const addUserInvokedImageToImageListener = () => { const graph = buildImageToImageGraph(state); dispatch(imageToImageGraphBuilt(graph)); - moduleLog({ data: graph }, 'Image to Image graph built'); + moduleLog.debug({ data: graph }, 'Image to Image graph built'); dispatch(sessionCreated({ graph })); diff --git a/invokeai/frontend/web/src/app/store/middleware/listenerMiddleware/listeners/userInvokedNodes.ts b/invokeai/frontend/web/src/app/store/middleware/listenerMiddleware/listeners/userInvokedNodes.ts index 45dcf7b0b2..6fda3db0d6 100644 --- a/invokeai/frontend/web/src/app/store/middleware/listenerMiddleware/listeners/userInvokedNodes.ts +++ b/invokeai/frontend/web/src/app/store/middleware/listenerMiddleware/listeners/userInvokedNodes.ts @@ -17,7 +17,7 @@ export const addUserInvokedNodesListener = () => { const graph = buildNodesGraph(state); dispatch(nodesGraphBuilt(graph)); - moduleLog({ data: graph }, 'Nodes graph built'); + moduleLog.debug({ data: graph }, 'Nodes graph built'); dispatch(sessionCreated({ graph })); diff --git a/invokeai/frontend/web/src/app/store/middleware/listenerMiddleware/listeners/userInvokedTextToImage.ts b/invokeai/frontend/web/src/app/store/middleware/listenerMiddleware/listeners/userInvokedTextToImage.ts index f7245b9301..6042d86cb7 100644 --- a/invokeai/frontend/web/src/app/store/middleware/listenerMiddleware/listeners/userInvokedTextToImage.ts +++ b/invokeai/frontend/web/src/app/store/middleware/listenerMiddleware/listeners/userInvokedTextToImage.ts @@ -19,7 +19,7 @@ export const addUserInvokedTextToImageListener = () => { dispatch(textToImageGraphBuilt(graph)); - moduleLog({ data: graph }, 'Text to Image graph built'); + moduleLog.debug({ data: graph }, 'Text to Image graph built'); dispatch(sessionCreated({ graph }));