From 6452d0fc28fa9635a4b4b22f884253bec3d8c477 Mon Sep 17 00:00:00 2001 From: psychedelicious <4822129+psychedelicious@users.noreply.github.com> Date: Sat, 22 Jul 2023 22:48:39 +1000 Subject: [PATCH] fix(ui): fix all circular dependencies --- invokeai/frontend/web/scripts/typegen.ts | 2 +- .../components/ImageDnd/ImageDndContext.tsx | 1 - .../app/components/ImageDnd/typesafeDnd.tsx | 2 +- .../web/src/app/components/Toaster.ts | 22 +----- invokeai/frontend/web/src/app/constants.ts | 64 ----------------- .../addFirstListImagesListener.ts.ts | 6 +- .../listeners/boardAndImagesDeleted.ts | 2 +- .../listeners/boardIdSelected.ts | 6 +- .../listeners/imageDeleted.ts | 6 +- .../listeners/imageToDeleteSelected.ts | 4 +- .../listeners/initialImageSelected.ts | 2 +- .../listeners/modelSelected.ts | 6 +- .../socketio/socketInvocationComplete.ts | 2 +- invokeai/frontend/web/src/app/store/store.ts | 13 ++-- .../frontend/web/src/app/store/storeUtils.ts | 8 --- .../IAICanvasToolbar/IAICanvasBoundingBox.tsx | 9 ++- .../src/features/canvas/store/canvasSlice.ts | 7 +- .../controlNet/store/controlNetSlice.ts | 3 - .../ParamDynamicPromptsCombinatorial.tsx | 2 +- .../components/ParamDynamicPromptsEnabled.tsx | 2 +- .../ParamDynamicPromptsMaxPrompts.tsx | 5 +- .../{slice.ts => dynamicPromptsSlice.ts} | 7 -- .../Boards/BoardsList/GenericBoard.tsx | 2 +- .../components/Boards/DeleteBoardModal.tsx | 6 +- .../components/ImageGrid/GalleryImageGrid.tsx | 2 +- .../gallery/hooks/useNextPrevImage.ts | 6 +- .../src/features/gallery/store/boardSlice.ts | 3 - .../gallery/store/gallerySelectors.ts | 2 +- .../features/gallery/store/gallerySlice.ts | 27 +------ .../web/src/features/gallery/store/types.ts | 26 +++++++ .../web/src/features/gallery/store/util.ts | 55 -------------- .../components/DeleteImageModal.tsx | 8 +-- .../components/ImageUsageMessage.tsx | 4 +- .../features/imageDeletion/store/actions.ts | 8 +++ .../store/imageDeletionSelectors.ts | 55 ++++++++++++++ .../imageDeletion/store/imageDeletionSlice.ts | 72 +------------------ .../src/features/imageDeletion/store/types.ts | 6 ++ .../features/nodes/components/AddNodeMenu.tsx | 7 +- .../components/IAINode/IAINodeResizer.tsx | 2 +- .../features/nodes/components/NodeWrapper.tsx | 3 +- .../nodes/components/ui/ClearNodesButton.tsx | 2 +- .../nodes/components/ui/LoadNodesButton.tsx | 2 +- .../nodes/store/nodesPersistDenylist.ts | 2 +- .../src/features/nodes/store/nodesSlice.ts | 19 +---- .../web/src/features/nodes/store/types.ts | 16 +++++ .../web/src/features/nodes/types/constants.ts | 2 + .../Advanced/ParamAdvancedCollapse.tsx | 2 +- .../Parameters/Advanced/ParamClipSkip.tsx | 20 +----- .../BoundingBox/ParamBoundingBoxHeight.tsx | 13 ++-- .../BoundingBox/ParamBoundingBoxWidth.tsx | 13 ++-- .../ControlNet/ParamControlNetCollapse.tsx | 6 +- .../Parameters/Core/ParamAspectRatio.tsx | 4 +- .../Parameters/Core/ParamCFGScale.tsx | 9 +-- .../Parameters/Core/ParamHeight.tsx | 11 ++- .../Parameters/Core/ParamScheduler.tsx | 15 ++-- .../components/Parameters/Core/ParamSteps.tsx | 9 +-- .../components/Parameters/Core/ParamWidth.tsx | 12 ++-- .../ImageToImage/ImageToImageStrength.tsx | 8 +-- .../parameters/store/generationSlice.ts | 34 +++++---- .../features/parameters/types/constants.ts | 19 +++++ .../parameters/types/parameterSchemas.ts | 52 +++++++++++++- .../system/components/LanguagePicker.tsx | 27 ++----- .../SettingsClearIntermediates.tsx | 4 +- .../SettingsModal/SettingsModal.tsx | 35 ++------- .../SettingsModal/SettingsSchedulers.tsx | 12 ++-- .../components/SettingsModal/StyledFlex.tsx | 23 ++++++ .../src/features/system/store/constants.ts | 20 ++++++ .../src/features/system/store/systemSlice.ts | 5 +- .../web/src/features/system/util/makeToast.ts | 20 ++++++ .../AddModelsPanel/AdvancedAddCheckpoint.tsx | 2 +- .../AddModelsPanel/AdvancedAddDiffusers.tsx | 2 +- .../AddModelsPanel/FoundModelsList.tsx | 2 +- .../AddModelsPanel/SimpleAddModels.tsx | 2 +- .../subpanels/MergeModelsPanel.tsx | 2 +- .../ModelManagerPanel/CheckpointModelEdit.tsx | 2 +- .../ModelManagerPanel/DiffusersModelEdit.tsx | 2 +- .../ModelManagerPanel/ModelConvert.tsx | 2 +- .../ModelManagerPanel/ModelListItem.tsx | 2 +- .../SyncModelsButton.tsx | 2 +- .../web/src/features/ui/store/hotkeysSlice.ts | 3 - .../web/src/features/ui/store/uiSlice.ts | 10 --- .../web/src/features/ui/store/uiTypes.ts | 2 - .../web/src/services/api/endpoints/boards.ts | 3 +- .../web/src/services/api/endpoints/images.ts | 35 ++++++++- .../web/src/services/api/endpoints/util.ts | 51 ------------- .../src/services/api/hooks/useBoardName.ts | 2 +- .../src/services/api/hooks/useBoardTotal.ts | 2 +- .../web/src/services/api/thunks/session.ts | 11 ++- .../services/events/util/setEventListeners.ts | 4 +- 89 files changed, 446 insertions(+), 588 deletions(-) delete mode 100644 invokeai/frontend/web/src/app/store/storeUtils.ts rename invokeai/frontend/web/src/features/dynamicPrompts/store/{slice.ts => dynamicPromptsSlice.ts} (86%) create mode 100644 invokeai/frontend/web/src/features/gallery/store/types.ts delete mode 100644 invokeai/frontend/web/src/features/gallery/store/util.ts create mode 100644 invokeai/frontend/web/src/features/imageDeletion/store/actions.ts create mode 100644 invokeai/frontend/web/src/features/imageDeletion/store/imageDeletionSelectors.ts create mode 100644 invokeai/frontend/web/src/features/imageDeletion/store/types.ts create mode 100644 invokeai/frontend/web/src/features/nodes/store/types.ts create mode 100644 invokeai/frontend/web/src/features/system/components/SettingsModal/StyledFlex.tsx create mode 100644 invokeai/frontend/web/src/features/system/store/constants.ts create mode 100644 invokeai/frontend/web/src/features/system/util/makeToast.ts delete mode 100644 invokeai/frontend/web/src/services/api/endpoints/util.ts diff --git a/invokeai/frontend/web/scripts/typegen.ts b/invokeai/frontend/web/scripts/typegen.ts index 39d0b25d30..015ae918ab 100644 --- a/invokeai/frontend/web/scripts/typegen.ts +++ b/invokeai/frontend/web/scripts/typegen.ts @@ -10,7 +10,7 @@ async function main() { ); const types = await openapiTS(OPENAPI_URL, { exportType: true, - transform: (schemaObject, metadata) => { + transform: (schemaObject) => { if ('format' in schemaObject && schemaObject.format === 'binary') { return schemaObject.nullable ? 'Blob | null' : 'Blob'; } diff --git a/invokeai/frontend/web/src/app/components/ImageDnd/ImageDndContext.tsx b/invokeai/frontend/web/src/app/components/ImageDnd/ImageDndContext.tsx index 91e274930c..24bdceac3a 100644 --- a/invokeai/frontend/web/src/app/components/ImageDnd/ImageDndContext.tsx +++ b/invokeai/frontend/web/src/app/components/ImageDnd/ImageDndContext.tsx @@ -39,7 +39,6 @@ const ImageDndContext = (props: ImageDndContextProps) => { const handleDragEnd = useCallback( (event: DragEndEvent) => { console.log('dragEnd', event.active.data.current); - const activeData = event.active.data.current; const overData = event.over?.data.current; if (!activeDragData || !overData) { return; diff --git a/invokeai/frontend/web/src/app/components/ImageDnd/typesafeDnd.tsx b/invokeai/frontend/web/src/app/components/ImageDnd/typesafeDnd.tsx index 1b2b10c897..5f08466710 100644 --- a/invokeai/frontend/web/src/app/components/ImageDnd/typesafeDnd.tsx +++ b/invokeai/frontend/web/src/app/components/ImageDnd/typesafeDnd.tsx @@ -11,7 +11,7 @@ import { useDraggable as useOriginalDraggable, useDroppable as useOriginalDroppable, } from '@dnd-kit/core'; -import { BoardId } from 'features/gallery/store/gallerySlice'; +import { BoardId } from 'features/gallery/store/types'; import { ImageDTO } from 'services/api/types'; type BaseDropData = { diff --git a/invokeai/frontend/web/src/app/components/Toaster.ts b/invokeai/frontend/web/src/app/components/Toaster.ts index 66ba1d4925..dff2a7c7f5 100644 --- a/invokeai/frontend/web/src/app/components/Toaster.ts +++ b/invokeai/frontend/web/src/app/components/Toaster.ts @@ -1,28 +1,10 @@ -import { useToast, UseToastOptions } from '@chakra-ui/react'; +import { useToast } from '@chakra-ui/react'; import { useAppDispatch, useAppSelector } from 'app/store/storeHooks'; import { toastQueueSelector } from 'features/system/store/systemSelectors'; import { addToast, clearToastQueue } from 'features/system/store/systemSlice'; +import { MakeToastArg, makeToast } from 'features/system/util/makeToast'; import { useCallback, useEffect } from 'react'; -export type MakeToastArg = string | UseToastOptions; - -/** - * Makes a toast from a string or a UseToastOptions object. - * If a string is passed, the toast will have the status 'info' and will be closable with a duration of 2500ms. - */ -export const makeToast = (arg: MakeToastArg): UseToastOptions => { - if (typeof arg === 'string') { - return { - title: arg, - status: 'info', - isClosable: true, - duration: 2500, - }; - } - - return { status: 'info', isClosable: true, duration: 2500, ...arg }; -}; - /** * Logical component. Watches the toast queue and makes toasts when the queue is not empty. * @returns null diff --git a/invokeai/frontend/web/src/app/constants.ts b/invokeai/frontend/web/src/app/constants.ts index c52e3bd48f..1194ea467b 100644 --- a/invokeai/frontend/web/src/app/constants.ts +++ b/invokeai/frontend/web/src/app/constants.ts @@ -1,66 +1,2 @@ -// zod needs the array to be `as const` to infer the type correctly - -import { SchedulerParam } from 'features/parameters/types/parameterSchemas'; - -// this is the source of the `SchedulerParam` type, which is generated by zod -export const SCHEDULER_NAMES_AS_CONST = [ - 'euler', - 'deis', - 'ddim', - 'ddpm', - 'dpmpp_2s', - 'dpmpp_2m', - 'dpmpp_2m_sde', - 'dpmpp_sde', - 'heun', - 'kdpm_2', - 'lms', - 'pndm', - 'unipc', - 'euler_k', - 'dpmpp_2s_k', - 'dpmpp_2m_k', - 'dpmpp_2m_sde_k', - 'dpmpp_sde_k', - 'heun_k', - 'lms_k', - 'euler_a', - 'kdpm_2_a', -] as const; - -export const DEFAULT_SCHEDULER_NAME = 'euler'; - -export const SCHEDULER_NAMES: SchedulerParam[] = [...SCHEDULER_NAMES_AS_CONST]; - -export const SCHEDULER_LABEL_MAP: Record = { - euler: 'Euler', - deis: 'DEIS', - ddim: 'DDIM', - ddpm: 'DDPM', - dpmpp_sde: 'DPM++ SDE', - dpmpp_2s: 'DPM++ 2S', - dpmpp_2m: 'DPM++ 2M', - dpmpp_2m_sde: 'DPM++ 2M SDE', - heun: 'Heun', - kdpm_2: 'KDPM 2', - lms: 'LMS', - pndm: 'PNDM', - unipc: 'UniPC', - euler_k: 'Euler Karras', - dpmpp_sde_k: 'DPM++ SDE Karras', - dpmpp_2s_k: 'DPM++ 2S Karras', - dpmpp_2m_k: 'DPM++ 2M Karras', - dpmpp_2m_sde_k: 'DPM++ 2M SDE Karras', - heun_k: 'Heun Karras', - lms_k: 'LMS Karras', - euler_a: 'Euler Ancestral', - kdpm_2_a: 'KDPM 2 Ancestral', -}; - -export type Scheduler = (typeof SCHEDULER_NAMES)[number]; - export const NUMPY_RAND_MIN = 0; - export const NUMPY_RAND_MAX = 2147483647; - -export const NODE_MIN_WIDTH = 250; diff --git a/invokeai/frontend/web/src/app/store/middleware/listenerMiddleware/listeners/addFirstListImagesListener.ts.ts b/invokeai/frontend/web/src/app/store/middleware/listenerMiddleware/listeners/addFirstListImagesListener.ts.ts index 8a1dd2943c..315dd2f5f9 100644 --- a/invokeai/frontend/web/src/app/store/middleware/listenerMiddleware/listeners/addFirstListImagesListener.ts.ts +++ b/invokeai/frontend/web/src/app/store/middleware/listenerMiddleware/listeners/addFirstListImagesListener.ts.ts @@ -1,8 +1,6 @@ import { createAction } from '@reduxjs/toolkit'; -import { - IMAGE_CATEGORIES, - imageSelected, -} from 'features/gallery/store/gallerySlice'; +import { imageSelected } from 'features/gallery/store/gallerySlice'; +import { IMAGE_CATEGORIES } from 'features/gallery/store/types'; import { ImageCache, getListImagesUrl, diff --git a/invokeai/frontend/web/src/app/store/middleware/listenerMiddleware/listeners/boardAndImagesDeleted.ts b/invokeai/frontend/web/src/app/store/middleware/listenerMiddleware/listeners/boardAndImagesDeleted.ts index 8c5572f399..5320846e15 100644 --- a/invokeai/frontend/web/src/app/store/middleware/listenerMiddleware/listeners/boardAndImagesDeleted.ts +++ b/invokeai/frontend/web/src/app/store/middleware/listenerMiddleware/listeners/boardAndImagesDeleted.ts @@ -1,6 +1,6 @@ import { resetCanvas } from 'features/canvas/store/canvasSlice'; import { controlNetReset } from 'features/controlNet/store/controlNetSlice'; -import { getImageUsage } from 'features/imageDeletion/store/imageDeletionSlice'; +import { getImageUsage } from 'features/imageDeletion/store/imageDeletionSelectors'; import { nodeEditorReset } from 'features/nodes/store/nodesSlice'; import { clearInitialImage } from 'features/parameters/store/generationSlice'; import { startAppListening } from '..'; diff --git a/invokeai/frontend/web/src/app/store/middleware/listenerMiddleware/listeners/boardIdSelected.ts b/invokeai/frontend/web/src/app/store/middleware/listenerMiddleware/listeners/boardIdSelected.ts index bf4dc7918c..f9c856d6cb 100644 --- a/invokeai/frontend/web/src/app/store/middleware/listenerMiddleware/listeners/boardIdSelected.ts +++ b/invokeai/frontend/web/src/app/store/middleware/listenerMiddleware/listeners/boardIdSelected.ts @@ -1,11 +1,13 @@ import { isAnyOf } from '@reduxjs/toolkit'; import { - ASSETS_CATEGORIES, - IMAGE_CATEGORIES, boardIdSelected, galleryViewChanged, imageSelected, } from 'features/gallery/store/gallerySlice'; +import { + ASSETS_CATEGORIES, + IMAGE_CATEGORIES, +} from 'features/gallery/store/types'; import { imagesApi } from 'services/api/endpoints/images'; import { startAppListening } from '..'; 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 716b31768c..2643a461d7 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 @@ -3,10 +3,8 @@ import { resetCanvas } from 'features/canvas/store/canvasSlice'; import { controlNetReset } from 'features/controlNet/store/controlNetSlice'; import { selectListImagesBaseQueryArgs } from 'features/gallery/store/gallerySelectors'; import { imageSelected } from 'features/gallery/store/gallerySlice'; -import { - imageDeletionConfirmed, - isModalOpenChanged, -} from 'features/imageDeletion/store/imageDeletionSlice'; +import { imageDeletionConfirmed } from 'features/imageDeletion/store/actions'; +import { isModalOpenChanged } from 'features/imageDeletion/store/imageDeletionSlice'; import { nodeEditorReset } from 'features/nodes/store/nodesSlice'; import { clearInitialImage } from 'features/parameters/store/generationSlice'; import { clamp } from 'lodash-es'; diff --git a/invokeai/frontend/web/src/app/store/middleware/listenerMiddleware/listeners/imageToDeleteSelected.ts b/invokeai/frontend/web/src/app/store/middleware/listenerMiddleware/listeners/imageToDeleteSelected.ts index 77ebd5c994..646cd259ad 100644 --- a/invokeai/frontend/web/src/app/store/middleware/listenerMiddleware/listeners/imageToDeleteSelected.ts +++ b/invokeai/frontend/web/src/app/store/middleware/listenerMiddleware/listeners/imageToDeleteSelected.ts @@ -1,8 +1,8 @@ +import { imageDeletionConfirmed } from 'features/imageDeletion/store/actions'; +import { selectImageUsage } from 'features/imageDeletion/store/imageDeletionSelectors'; import { - imageDeletionConfirmed, imageToDeleteSelected, isModalOpenChanged, - selectImageUsage, } from 'features/imageDeletion/store/imageDeletionSlice'; import { startAppListening } from '..'; diff --git a/invokeai/frontend/web/src/app/store/middleware/listenerMiddleware/listeners/initialImageSelected.ts b/invokeai/frontend/web/src/app/store/middleware/listenerMiddleware/listeners/initialImageSelected.ts index 0cd68cf6fa..17a52f2bfc 100644 --- a/invokeai/frontend/web/src/app/store/middleware/listenerMiddleware/listeners/initialImageSelected.ts +++ b/invokeai/frontend/web/src/app/store/middleware/listenerMiddleware/listeners/initialImageSelected.ts @@ -1,7 +1,7 @@ -import { makeToast } from 'app/components/Toaster'; import { initialImageSelected } from 'features/parameters/store/actions'; import { initialImageChanged } from 'features/parameters/store/generationSlice'; import { addToast } from 'features/system/store/systemSlice'; +import { makeToast } from 'features/system/util/makeToast'; import { t } from 'i18next'; import { startAppListening } from '..'; diff --git a/invokeai/frontend/web/src/app/store/middleware/listenerMiddleware/listeners/modelSelected.ts b/invokeai/frontend/web/src/app/store/middleware/listenerMiddleware/listeners/modelSelected.ts index 6612963096..7880386057 100644 --- a/invokeai/frontend/web/src/app/store/middleware/listenerMiddleware/listeners/modelSelected.ts +++ b/invokeai/frontend/web/src/app/store/middleware/listenerMiddleware/listeners/modelSelected.ts @@ -1,5 +1,5 @@ -import { makeToast } from 'app/components/Toaster'; -import { $logger, logger } from 'app/logging/logger'; +import { logger } from 'app/logging/logger'; +import { controlNetRemoved } from 'features/controlNet/store/controlNetSlice'; import { loraRemoved } from 'features/lora/store/loraSlice'; import { modelSelected } from 'features/parameters/store/actions'; import { @@ -8,9 +8,9 @@ import { } from 'features/parameters/store/generationSlice'; import { zMainModel } from 'features/parameters/types/parameterSchemas'; import { addToast } from 'features/system/store/systemSlice'; +import { makeToast } from 'features/system/util/makeToast'; import { forEach } from 'lodash-es'; import { startAppListening } from '..'; -import { controlNetRemoved } from 'features/controlNet/store/controlNetSlice'; export const addModelSelectedListener = () => { startAppListening({ diff --git a/invokeai/frontend/web/src/app/store/middleware/listenerMiddleware/listeners/socketio/socketInvocationComplete.ts b/invokeai/frontend/web/src/app/store/middleware/listenerMiddleware/listeners/socketio/socketInvocationComplete.ts index 1ba653dba3..174a7de075 100644 --- a/invokeai/frontend/web/src/app/store/middleware/listenerMiddleware/listeners/socketio/socketInvocationComplete.ts +++ b/invokeai/frontend/web/src/app/store/middleware/listenerMiddleware/listeners/socketio/socketInvocationComplete.ts @@ -2,11 +2,11 @@ import { logger } from 'app/logging/logger'; import { parseify } from 'common/util/serialize'; import { addImageToStagingArea } from 'features/canvas/store/canvasSlice'; import { - IMAGE_CATEGORIES, boardIdSelected, galleryViewChanged, imageSelected, } from 'features/gallery/store/gallerySlice'; +import { IMAGE_CATEGORIES } from 'features/gallery/store/types'; import { progressImageSet } from 'features/system/store/systemSlice'; import { imagesAdapter, imagesApi } from 'services/api/endpoints/images'; import { isImageOutput } from 'services/api/guards'; diff --git a/invokeai/frontend/web/src/app/store/store.ts b/invokeai/frontend/web/src/app/store/store.ts index 4725a2f921..ad128a0fec 100644 --- a/invokeai/frontend/web/src/app/store/store.ts +++ b/invokeai/frontend/web/src/app/store/store.ts @@ -4,14 +4,11 @@ import { autoBatchEnhancer, combineReducers, configureStore, + createAsyncThunk, } from '@reduxjs/toolkit'; - -import dynamicMiddlewares from 'redux-dynamic-middlewares'; -import { rememberEnhancer, rememberReducer } from 'redux-remember'; - import canvasReducer from 'features/canvas/store/canvasSlice'; import controlNetReducer from 'features/controlNet/store/controlNetSlice'; -import dynamicPromptsReducer from 'features/dynamicPrompts/store/slice'; +import dynamicPromptsReducer from 'features/dynamicPrompts/store/dynamicPromptsSlice'; import boardsReducer from 'features/gallery/store/boardSlice'; import galleryReducer from 'features/gallery/store/gallerySlice'; import imageDeletionReducer from 'features/imageDeletion/store/imageDeletionSlice'; @@ -24,9 +21,8 @@ import systemReducer from 'features/system/store/systemSlice'; import modelmanagerReducer from 'features/ui/components/tabs/ModelManager/store/modelManagerSlice'; import hotkeysReducer from 'features/ui/store/hotkeysSlice'; import uiReducer from 'features/ui/store/uiSlice'; - -import { listenerMiddleware } from './middleware/listenerMiddleware'; - +import dynamicMiddlewares from 'redux-dynamic-middlewares'; +import { rememberEnhancer, rememberReducer } from 'redux-remember'; import { api } from 'services/api'; import { LOCALSTORAGE_PREFIX } from './constants'; import { serialize } from './enhancers/reduxRemember/serialize'; @@ -34,6 +30,7 @@ import { unserialize } from './enhancers/reduxRemember/unserialize'; import { actionSanitizer } from './middleware/devtools/actionSanitizer'; import { actionsDenylist } from './middleware/devtools/actionsDenylist'; import { stateSanitizer } from './middleware/devtools/stateSanitizer'; +import { listenerMiddleware } from './middleware/listenerMiddleware'; const allReducers = { canvas: canvasReducer, diff --git a/invokeai/frontend/web/src/app/store/storeUtils.ts b/invokeai/frontend/web/src/app/store/storeUtils.ts deleted file mode 100644 index 1ccaad9f89..0000000000 --- a/invokeai/frontend/web/src/app/store/storeUtils.ts +++ /dev/null @@ -1,8 +0,0 @@ -import { createAsyncThunk } from '@reduxjs/toolkit'; -import { AppDispatch, RootState } from 'app/store/store'; - -// https://redux-toolkit.js.org/usage/usage-with-typescript#defining-a-pre-typed-createasyncthunk -export const createAppAsyncThunk = createAsyncThunk.withTypes<{ - state: RootState; - dispatch: AppDispatch; -}>(); diff --git a/invokeai/frontend/web/src/features/canvas/components/IAICanvasToolbar/IAICanvasBoundingBox.tsx b/invokeai/frontend/web/src/features/canvas/components/IAICanvasToolbar/IAICanvasBoundingBox.tsx index 3086e001c2..41c281d259 100644 --- a/invokeai/frontend/web/src/features/canvas/components/IAICanvasToolbar/IAICanvasBoundingBox.tsx +++ b/invokeai/frontend/web/src/features/canvas/components/IAICanvasToolbar/IAICanvasBoundingBox.tsx @@ -1,10 +1,10 @@ import { createSelector } from '@reduxjs/toolkit'; +import { stateSelector } from 'app/store/store'; import { useAppDispatch, useAppSelector } from 'app/store/storeHooks'; import { roundDownToMultiple, roundToMultiple, } from 'common/util/roundDownToMultiple'; -import { canvasSelector } from 'features/canvas/store/canvasSelectors'; import { setBoundingBoxCoordinates, setBoundingBoxDimensions, @@ -13,7 +13,6 @@ import { setIsTransformingBoundingBox, setShouldSnapToGrid, } from 'features/canvas/store/canvasSlice'; -import { uiSelector } from 'features/ui/store/uiSelectors'; import Konva from 'konva'; import { GroupConfig } from 'konva/lib/Group'; import { KonvaEventObject } from 'konva/lib/Node'; @@ -25,8 +24,8 @@ import { useHotkeys } from 'react-hotkeys-hook'; import { Group, Rect, Transformer } from 'react-konva'; const boundingBoxPreviewSelector = createSelector( - [canvasSelector, uiSelector], - (canvas, ui) => { + [stateSelector], + ({ canvas, generation }) => { const { boundingBoxCoordinates, boundingBoxDimensions, @@ -38,7 +37,7 @@ const boundingBoxPreviewSelector = createSelector( shouldSnapToGrid, } = canvas; - const { aspectRatio } = ui; + const { aspectRatio } = generation; return { boundingBoxCoordinates, diff --git a/invokeai/frontend/web/src/features/canvas/store/canvasSlice.ts b/invokeai/frontend/web/src/features/canvas/store/canvasSlice.ts index 550cfff4bc..54ebfa2934 100644 --- a/invokeai/frontend/web/src/features/canvas/store/canvasSlice.ts +++ b/invokeai/frontend/web/src/features/canvas/store/canvasSlice.ts @@ -4,14 +4,13 @@ import { roundDownToMultiple, roundToMultiple, } from 'common/util/roundDownToMultiple'; -import { IRect, Vector2d } from 'konva/lib/types'; -import { clamp, cloneDeep } from 'lodash-es'; -// +import { setAspectRatio } from 'features/parameters/store/generationSlice'; import { setActiveTab, - setAspectRatio, setShouldUseCanvasBetaLayout, } from 'features/ui/store/uiSlice'; +import { IRect, Vector2d } from 'konva/lib/types'; +import { clamp, cloneDeep } from 'lodash-es'; import { RgbaColor } from 'react-colorful'; import { sessionCanceled } from 'services/api/thunks/session'; import { ImageDTO } from 'services/api/types'; diff --git a/invokeai/frontend/web/src/features/controlNet/store/controlNetSlice.ts b/invokeai/frontend/web/src/features/controlNet/store/controlNetSlice.ts index 2f8668115a..196023491c 100644 --- a/invokeai/frontend/web/src/features/controlNet/store/controlNetSlice.ts +++ b/invokeai/frontend/web/src/features/controlNet/store/controlNetSlice.ts @@ -1,5 +1,4 @@ import { PayloadAction, createSlice } from '@reduxjs/toolkit'; -import { RootState } from 'app/store/store'; import { ControlNetModelParam } from 'features/parameters/types/parameterSchemas'; import { cloneDeep, forEach } from 'lodash-es'; import { imagesApi } from 'services/api/endpoints/images'; @@ -365,5 +364,3 @@ export const { } = controlNetSlice.actions; export default controlNetSlice.reducer; - -export const controlNetSelector = (state: RootState) => state.controlNet; diff --git a/invokeai/frontend/web/src/features/dynamicPrompts/components/ParamDynamicPromptsCombinatorial.tsx b/invokeai/frontend/web/src/features/dynamicPrompts/components/ParamDynamicPromptsCombinatorial.tsx index cb930acd3b..809ec0df10 100644 --- a/invokeai/frontend/web/src/features/dynamicPrompts/components/ParamDynamicPromptsCombinatorial.tsx +++ b/invokeai/frontend/web/src/features/dynamicPrompts/components/ParamDynamicPromptsCombinatorial.tsx @@ -4,7 +4,7 @@ import { useAppDispatch, useAppSelector } from 'app/store/storeHooks'; import { defaultSelectorOptions } from 'app/store/util/defaultMemoizeOptions'; import IAISwitch from 'common/components/IAISwitch'; import { useCallback } from 'react'; -import { combinatorialToggled } from '../store/slice'; +import { combinatorialToggled } from '../store/dynamicPromptsSlice'; const selector = createSelector( stateSelector, diff --git a/invokeai/frontend/web/src/features/dynamicPrompts/components/ParamDynamicPromptsEnabled.tsx b/invokeai/frontend/web/src/features/dynamicPrompts/components/ParamDynamicPromptsEnabled.tsx index 7a3b335659..f92fa410f2 100644 --- a/invokeai/frontend/web/src/features/dynamicPrompts/components/ParamDynamicPromptsEnabled.tsx +++ b/invokeai/frontend/web/src/features/dynamicPrompts/components/ParamDynamicPromptsEnabled.tsx @@ -4,7 +4,7 @@ import { useAppDispatch, useAppSelector } from 'app/store/storeHooks'; import { defaultSelectorOptions } from 'app/store/util/defaultMemoizeOptions'; import IAISwitch from 'common/components/IAISwitch'; import { useCallback } from 'react'; -import { isEnabledToggled } from '../store/slice'; +import { isEnabledToggled } from '../store/dynamicPromptsSlice'; const selector = createSelector( stateSelector, diff --git a/invokeai/frontend/web/src/features/dynamicPrompts/components/ParamDynamicPromptsMaxPrompts.tsx b/invokeai/frontend/web/src/features/dynamicPrompts/components/ParamDynamicPromptsMaxPrompts.tsx index d8b5721fbe..5bee317d22 100644 --- a/invokeai/frontend/web/src/features/dynamicPrompts/components/ParamDynamicPromptsMaxPrompts.tsx +++ b/invokeai/frontend/web/src/features/dynamicPrompts/components/ParamDynamicPromptsMaxPrompts.tsx @@ -4,7 +4,10 @@ import { useAppDispatch, useAppSelector } from 'app/store/storeHooks'; import { defaultSelectorOptions } from 'app/store/util/defaultMemoizeOptions'; import IAISlider from 'common/components/IAISlider'; import { useCallback } from 'react'; -import { maxPromptsChanged, maxPromptsReset } from '../store/slice'; +import { + maxPromptsChanged, + maxPromptsReset, +} from '../store/dynamicPromptsSlice'; const selector = createSelector( stateSelector, diff --git a/invokeai/frontend/web/src/features/dynamicPrompts/store/slice.ts b/invokeai/frontend/web/src/features/dynamicPrompts/store/dynamicPromptsSlice.ts similarity index 86% rename from invokeai/frontend/web/src/features/dynamicPrompts/store/slice.ts rename to invokeai/frontend/web/src/features/dynamicPrompts/store/dynamicPromptsSlice.ts index 8c33feb20c..d96c8ff0b0 100644 --- a/invokeai/frontend/web/src/features/dynamicPrompts/store/slice.ts +++ b/invokeai/frontend/web/src/features/dynamicPrompts/store/dynamicPromptsSlice.ts @@ -1,5 +1,4 @@ import { PayloadAction, createSlice } from '@reduxjs/toolkit'; -import { RootState } from 'app/store/store'; export interface DynamicPromptsState { isEnabled: boolean; @@ -32,9 +31,6 @@ export const dynamicPromptsSlice = createSlice({ state.isEnabled = !state.isEnabled; }, }, - extraReducers: (builder) => { - // - }, }); export const { @@ -45,6 +41,3 @@ export const { } = dynamicPromptsSlice.actions; export default dynamicPromptsSlice.reducer; - -export const dynamicPromptsSelector = (state: RootState) => - state.dynamicPrompts; diff --git a/invokeai/frontend/web/src/features/gallery/components/Boards/BoardsList/GenericBoard.tsx b/invokeai/frontend/web/src/features/gallery/components/Boards/BoardsList/GenericBoard.tsx index fa7f944a24..0d630c524d 100644 --- a/invokeai/frontend/web/src/features/gallery/components/Boards/BoardsList/GenericBoard.tsx +++ b/invokeai/frontend/web/src/features/gallery/components/Boards/BoardsList/GenericBoard.tsx @@ -2,7 +2,7 @@ import { As, Badge, Flex } from '@chakra-ui/react'; import { TypesafeDroppableData } from 'app/components/ImageDnd/typesafeDnd'; import IAIDroppable from 'common/components/IAIDroppable'; import { IAINoContentFallback } from 'common/components/IAIImageFallback'; -import { BoardId } from 'features/gallery/store/gallerySlice'; +import { BoardId } from 'features/gallery/store/types'; import { ReactNode } from 'react'; import BoardContextMenu from '../BoardContextMenu'; diff --git a/invokeai/frontend/web/src/features/gallery/components/Boards/DeleteBoardModal.tsx b/invokeai/frontend/web/src/features/gallery/components/Boards/DeleteBoardModal.tsx index 3824fd6e0d..192c19c423 100644 --- a/invokeai/frontend/web/src/features/gallery/components/Boards/DeleteBoardModal.tsx +++ b/invokeai/frontend/web/src/features/gallery/components/Boards/DeleteBoardModal.tsx @@ -11,14 +11,12 @@ import { } from '@chakra-ui/react'; import { createSelector } from '@reduxjs/toolkit'; import { skipToken } from '@reduxjs/toolkit/dist/query'; +import { ImageUsage } from 'app/contexts/AddImageToBoardContext'; import { stateSelector } from 'app/store/store'; import { useAppSelector } from 'app/store/storeHooks'; import IAIButton from 'common/components/IAIButton'; import ImageUsageMessage from 'features/imageDeletion/components/ImageUsageMessage'; -import { - ImageUsage, - getImageUsage, -} from 'features/imageDeletion/store/imageDeletionSlice'; +import { getImageUsage } from 'features/imageDeletion/store/imageDeletionSelectors'; import { some } from 'lodash-es'; import { memo, useCallback, useMemo, useRef } from 'react'; import { useTranslation } from 'react-i18next'; diff --git a/invokeai/frontend/web/src/features/gallery/components/ImageGrid/GalleryImageGrid.tsx b/invokeai/frontend/web/src/features/gallery/components/ImageGrid/GalleryImageGrid.tsx index 8da4adbcb3..4a56fe0e9a 100644 --- a/invokeai/frontend/web/src/features/gallery/components/ImageGrid/GalleryImageGrid.tsx +++ b/invokeai/frontend/web/src/features/gallery/components/ImageGrid/GalleryImageGrid.tsx @@ -2,8 +2,8 @@ import { Box, Flex } from '@chakra-ui/react'; import { useAppSelector } from 'app/store/storeHooks'; import IAIButton from 'common/components/IAIButton'; import { IAINoContentFallback } from 'common/components/IAIImageFallback'; -import { IMAGE_LIMIT } from 'features/gallery//store/gallerySlice'; import { selectListImagesBaseQueryArgs } from 'features/gallery/store/gallerySelectors'; +import { IMAGE_LIMIT } from 'features/gallery/store/types'; import { UseOverlayScrollbarsParams, useOverlayScrollbars, diff --git a/invokeai/frontend/web/src/features/gallery/hooks/useNextPrevImage.ts b/invokeai/frontend/web/src/features/gallery/hooks/useNextPrevImage.ts index 7b4b8bed7b..f2572a23b5 100644 --- a/invokeai/frontend/web/src/features/gallery/hooks/useNextPrevImage.ts +++ b/invokeai/frontend/web/src/features/gallery/hooks/useNextPrevImage.ts @@ -1,10 +1,7 @@ import { createSelector } from '@reduxjs/toolkit'; import { stateSelector } from 'app/store/store'; import { useAppDispatch, useAppSelector } from 'app/store/storeHooks'; -import { - IMAGE_LIMIT, - imageSelected, -} from 'features/gallery/store/gallerySlice'; +import { imageSelected } from 'features/gallery/store/gallerySlice'; import { clamp, isEqual } from 'lodash-es'; import { useCallback } from 'react'; import { @@ -14,6 +11,7 @@ import { useLazyListImagesQuery, } from 'services/api/endpoints/images'; import { selectListImagesBaseQueryArgs } from '../store/gallerySelectors'; +import { IMAGE_LIMIT } from '../store/types'; export const nextPrevImageButtonsSelector = createSelector( [stateSelector, selectListImagesBaseQueryArgs], diff --git a/invokeai/frontend/web/src/features/gallery/store/boardSlice.ts b/invokeai/frontend/web/src/features/gallery/store/boardSlice.ts index e6b59eee9a..ad43498e51 100644 --- a/invokeai/frontend/web/src/features/gallery/store/boardSlice.ts +++ b/invokeai/frontend/web/src/features/gallery/store/boardSlice.ts @@ -1,5 +1,4 @@ import { PayloadAction, createSlice } from '@reduxjs/toolkit'; -import { RootState } from 'app/store/store'; type BoardsState = { searchText: string; @@ -27,6 +26,4 @@ const boardsSlice = createSlice({ export const { setBoardSearchText, setUpdateBoardModalOpen } = boardsSlice.actions; -export const boardsSelector = (state: RootState) => state.boards; - export default boardsSlice.reducer; diff --git a/invokeai/frontend/web/src/features/gallery/store/gallerySelectors.ts b/invokeai/frontend/web/src/features/gallery/store/gallerySelectors.ts index db520c2f35..b589550157 100644 --- a/invokeai/frontend/web/src/features/gallery/store/gallerySelectors.ts +++ b/invokeai/frontend/web/src/features/gallery/store/gallerySelectors.ts @@ -6,7 +6,7 @@ import { ASSETS_CATEGORIES, IMAGE_CATEGORIES, INITIAL_IMAGE_LIMIT, -} from './gallerySlice'; +} from './types'; export const gallerySelector = (state: RootState) => state.gallery; diff --git a/invokeai/frontend/web/src/features/gallery/store/gallerySlice.ts b/invokeai/frontend/web/src/features/gallery/store/gallerySlice.ts index b754018c1f..125463ba94 100644 --- a/invokeai/frontend/web/src/features/gallery/store/gallerySlice.ts +++ b/invokeai/frontend/web/src/features/gallery/store/gallerySlice.ts @@ -2,32 +2,7 @@ import type { PayloadAction } from '@reduxjs/toolkit'; import { createSlice, isAnyOf } from '@reduxjs/toolkit'; import { uniq } from 'lodash-es'; import { boardsApi } from 'services/api/endpoints/boards'; -import { ImageCategory } from 'services/api/types'; - -export const IMAGE_CATEGORIES: ImageCategory[] = ['general']; -export const ASSETS_CATEGORIES: ImageCategory[] = [ - 'control', - 'mask', - 'user', - 'other', -]; -export const INITIAL_IMAGE_LIMIT = 100; -export const IMAGE_LIMIT = 20; - -export type GalleryView = 'images' | 'assets'; -// export type BoardId = 'no_board' | (string & Record); -export type BoardId = string | undefined; - -type GalleryState = { - selection: string[]; - shouldAutoSwitch: boolean; - autoAddBoardId: string | undefined; - galleryImageMinimumWidth: number; - selectedBoardId: BoardId; - galleryView: GalleryView; - batchImageNames: string[]; - isBatchEnabled: boolean; -}; +import { BoardId, GalleryState, GalleryView } from './types'; export const initialGalleryState: GalleryState = { selection: [], diff --git a/invokeai/frontend/web/src/features/gallery/store/types.ts b/invokeai/frontend/web/src/features/gallery/store/types.ts new file mode 100644 index 0000000000..d19a6fded3 --- /dev/null +++ b/invokeai/frontend/web/src/features/gallery/store/types.ts @@ -0,0 +1,26 @@ +import { ImageCategory } from 'services/api/types'; + +export const IMAGE_CATEGORIES: ImageCategory[] = ['general']; +export const ASSETS_CATEGORIES: ImageCategory[] = [ + 'control', + 'mask', + 'user', + 'other', +]; +export const INITIAL_IMAGE_LIMIT = 100; +export const IMAGE_LIMIT = 20; + +export type GalleryView = 'images' | 'assets'; +// export type BoardId = 'no_board' | (string & Record); +export type BoardId = string | undefined; + +export type GalleryState = { + selection: string[]; + shouldAutoSwitch: boolean; + autoAddBoardId: string | undefined; + galleryImageMinimumWidth: number; + selectedBoardId: BoardId; + galleryView: GalleryView; + batchImageNames: string[]; + isBatchEnabled: boolean; +}; diff --git a/invokeai/frontend/web/src/features/gallery/store/util.ts b/invokeai/frontend/web/src/features/gallery/store/util.ts deleted file mode 100644 index 3df5b2af31..0000000000 --- a/invokeai/frontend/web/src/features/gallery/store/util.ts +++ /dev/null @@ -1,55 +0,0 @@ -import { isEqual } from 'lodash-es'; -import { ImageCategory, ImageDTO } from 'services/api/types'; -import { ASSETS_CATEGORIES, BoardId, IMAGE_CATEGORIES } from './gallerySlice'; - -export const getCategoriesQueryParamForBoard = ( - board_id: BoardId -): ImageCategory[] | undefined => { - if (board_id === 'assets') { - return ASSETS_CATEGORIES; - } - - if (board_id === 'images') { - return IMAGE_CATEGORIES; - } - - // 'no_board' board, 'batch' board, user boards - return undefined; -}; - -export const getBoardIdQueryParamForBoard = ( - board_id: BoardId -): string | null => { - if (board_id === undefined) { - return 'none'; - } - - // user boards - return board_id; -}; - -export const getBoardIdFromBoardAndCategoriesQueryParam = ( - board_id: string | undefined, - categories: ImageCategory[] | undefined -): BoardId => { - if (board_id === undefined && isEqual(categories, IMAGE_CATEGORIES)) { - return 'images'; - } - - if (board_id === undefined && isEqual(categories, ASSETS_CATEGORIES)) { - return 'assets'; - } - - if (board_id === 'none') { - return 'no_board'; - } - - return board_id ?? 'UNKNOWN_BOARD'; -}; - -export const getCategories = (imageDTO: ImageDTO) => { - if (IMAGE_CATEGORIES.includes(imageDTO.image_category)) { - return IMAGE_CATEGORIES; - } - return ASSETS_CATEGORIES; -}; diff --git a/invokeai/frontend/web/src/features/imageDeletion/components/DeleteImageModal.tsx b/invokeai/frontend/web/src/features/imageDeletion/components/DeleteImageModal.tsx index 8306437cc7..0e72ea96ad 100644 --- a/invokeai/frontend/web/src/features/imageDeletion/components/DeleteImageModal.tsx +++ b/invokeai/frontend/web/src/features/imageDeletion/components/DeleteImageModal.tsx @@ -16,16 +16,16 @@ import IAIButton from 'common/components/IAIButton'; import IAISwitch from 'common/components/IAISwitch'; import { setShouldConfirmOnDelete } from 'features/system/store/systemSlice'; +import { stateSelector } from 'app/store/store'; import { ChangeEvent, memo, useCallback, useRef } from 'react'; import { useTranslation } from 'react-i18next'; -import ImageUsageMessage from './ImageUsageMessage'; -import { stateSelector } from 'app/store/store'; +import { imageDeletionConfirmed } from '../store/actions'; +import { selectImageUsage } from '../store/imageDeletionSelectors'; import { - imageDeletionConfirmed, imageToDeleteCleared, isModalOpenChanged, - selectImageUsage, } from '../store/imageDeletionSlice'; +import ImageUsageMessage from './ImageUsageMessage'; const selector = createSelector( [stateSelector, selectImageUsage], diff --git a/invokeai/frontend/web/src/features/imageDeletion/components/ImageUsageMessage.tsx b/invokeai/frontend/web/src/features/imageDeletion/components/ImageUsageMessage.tsx index 64d001f8ba..3ed7f3f05f 100644 --- a/invokeai/frontend/web/src/features/imageDeletion/components/ImageUsageMessage.tsx +++ b/invokeai/frontend/web/src/features/imageDeletion/components/ImageUsageMessage.tsx @@ -1,7 +1,7 @@ +import { ListItem, Text, UnorderedList } from '@chakra-ui/react'; import { some } from 'lodash-es'; import { memo } from 'react'; -import { ImageUsage } from '../store/imageDeletionSlice'; -import { ListItem, Text, UnorderedList } from '@chakra-ui/react'; +import { ImageUsage } from '../store/types'; type Props = { imageUsage?: ImageUsage; topMessage?: string; diff --git a/invokeai/frontend/web/src/features/imageDeletion/store/actions.ts b/invokeai/frontend/web/src/features/imageDeletion/store/actions.ts new file mode 100644 index 0000000000..c67d7d944d --- /dev/null +++ b/invokeai/frontend/web/src/features/imageDeletion/store/actions.ts @@ -0,0 +1,8 @@ +import { createAction } from '@reduxjs/toolkit'; +import { ImageDTO } from 'services/api/types'; +import { ImageUsage } from './types'; + +export const imageDeletionConfirmed = createAction<{ + imageDTO: ImageDTO; + imageUsage: ImageUsage; +}>('imageDeletion/imageDeletionConfirmed'); diff --git a/invokeai/frontend/web/src/features/imageDeletion/store/imageDeletionSelectors.ts b/invokeai/frontend/web/src/features/imageDeletion/store/imageDeletionSelectors.ts new file mode 100644 index 0000000000..bd8e117496 --- /dev/null +++ b/invokeai/frontend/web/src/features/imageDeletion/store/imageDeletionSelectors.ts @@ -0,0 +1,55 @@ +import { createSelector } from '@reduxjs/toolkit'; +import { RootState } from 'app/store/store'; +import { defaultSelectorOptions } from 'app/store/util/defaultMemoizeOptions'; +import { some } from 'lodash-es'; +import { ImageUsage } from './types'; + +export const getImageUsage = (state: RootState, image_name: string) => { + const { generation, canvas, nodes, controlNet } = state; + const isInitialImage = generation.initialImage?.imageName === image_name; + + const isCanvasImage = canvas.layerState.objects.some( + (obj) => obj.kind === 'image' && obj.imageName === image_name + ); + + const isNodesImage = nodes.nodes.some((node) => { + return some( + node.data.inputs, + (input) => + input.type === 'image' && input.value?.image_name === image_name + ); + }); + + const isControlNetImage = some( + controlNet.controlNets, + (c) => + c.controlImage === image_name || c.processedControlImage === image_name + ); + + const imageUsage: ImageUsage = { + isInitialImage, + isCanvasImage, + isNodesImage, + isControlNetImage, + }; + + return imageUsage; +}; + +export const selectImageUsage = createSelector( + [(state: RootState) => state], + (state) => { + const { imageToDelete } = state.imageDeletion; + + if (!imageToDelete) { + return; + } + + const { image_name } = imageToDelete; + + const imageUsage = getImageUsage(state, image_name); + + return imageUsage; + }, + defaultSelectorOptions +); diff --git a/invokeai/frontend/web/src/features/imageDeletion/store/imageDeletionSlice.ts b/invokeai/frontend/web/src/features/imageDeletion/store/imageDeletionSlice.ts index df90da5088..0bfd9a537d 100644 --- a/invokeai/frontend/web/src/features/imageDeletion/store/imageDeletionSlice.ts +++ b/invokeai/frontend/web/src/features/imageDeletion/store/imageDeletionSlice.ts @@ -1,12 +1,4 @@ -import { - PayloadAction, - createAction, - createSelector, - createSlice, -} from '@reduxjs/toolkit'; -import { RootState } from 'app/store/store'; -import { defaultSelectorOptions } from 'app/store/util/defaultMemoizeOptions'; -import { some } from 'lodash-es'; +import { PayloadAction, createSlice } from '@reduxjs/toolkit'; import { ImageDTO } from 'services/api/types'; type DeleteImageState = { @@ -43,65 +35,3 @@ export const { } = imageDeletion.actions; export default imageDeletion.reducer; - -export type ImageUsage = { - isInitialImage: boolean; - isCanvasImage: boolean; - isNodesImage: boolean; - isControlNetImage: boolean; -}; - -export const getImageUsage = (state: RootState, image_name: string) => { - const { generation, canvas, nodes, controlNet } = state; - const isInitialImage = generation.initialImage?.imageName === image_name; - - const isCanvasImage = canvas.layerState.objects.some( - (obj) => obj.kind === 'image' && obj.imageName === image_name - ); - - const isNodesImage = nodes.nodes.some((node) => { - return some( - node.data.inputs, - (input) => - input.type === 'image' && input.value?.image_name === image_name - ); - }); - - const isControlNetImage = some( - controlNet.controlNets, - (c) => - c.controlImage === image_name || c.processedControlImage === image_name - ); - - const imageUsage: ImageUsage = { - isInitialImage, - isCanvasImage, - isNodesImage, - isControlNetImage, - }; - - return imageUsage; -}; - -export const selectImageUsage = createSelector( - [(state: RootState) => state], - (state) => { - const { imageToDelete } = state.imageDeletion; - - if (!imageToDelete) { - return; - } - - const { image_name } = imageToDelete; - - const imageUsage = getImageUsage(state, image_name); - - return imageUsage; - }, - defaultSelectorOptions -); - -export const imageDeletionConfirmed = createAction<{ - imageDTO: ImageDTO; - imageUsage: ImageUsage; -}>('imageDeletion/imageDeletionConfirmed'); diff --git a/invokeai/frontend/web/src/features/imageDeletion/store/types.ts b/invokeai/frontend/web/src/features/imageDeletion/store/types.ts new file mode 100644 index 0000000000..b3f4dc9c8d --- /dev/null +++ b/invokeai/frontend/web/src/features/imageDeletion/store/types.ts @@ -0,0 +1,6 @@ +export type ImageUsage = { + isInitialImage: boolean; + isCanvasImage: boolean; + isNodesImage: boolean; + isControlNetImage: boolean; +}; diff --git a/invokeai/frontend/web/src/features/nodes/components/AddNodeMenu.tsx b/invokeai/frontend/web/src/features/nodes/components/AddNodeMenu.tsx index 408bf74eed..a1a1acf1f8 100644 --- a/invokeai/frontend/web/src/features/nodes/components/AddNodeMenu.tsx +++ b/invokeai/frontend/web/src/features/nodes/components/AddNodeMenu.tsx @@ -1,6 +1,7 @@ import { Flex, Text } from '@chakra-ui/react'; import { createSelector } from '@reduxjs/toolkit'; import { useAppToaster } from 'app/components/Toaster'; +import { stateSelector } from 'app/store/store'; import { useAppDispatch, useAppSelector } from 'app/store/storeHooks'; import { defaultSelectorOptions } from 'app/store/util/defaultMemoizeOptions'; import IAIMantineSearchableSelect from 'common/components/IAIMantineSearchableSelect'; @@ -9,7 +10,7 @@ import { forwardRef, useCallback } from 'react'; import 'reactflow/dist/style.css'; import { AnyInvocationType } from 'services/events/types'; import { useBuildInvocation } from '../hooks/useBuildInvocation'; -import { nodeAdded, nodesSelector } from '../store/nodesSlice'; +import { nodeAdded } from '../store/nodesSlice'; type NodeTemplate = { label: string; @@ -18,8 +19,8 @@ type NodeTemplate = { }; const selector = createSelector( - nodesSelector, - (nodes) => { + [stateSelector], + ({ nodes }) => { const data: NodeTemplate[] = map(nodes.invocationTemplates, (template) => { return { label: template.title, diff --git a/invokeai/frontend/web/src/features/nodes/components/IAINode/IAINodeResizer.tsx b/invokeai/frontend/web/src/features/nodes/components/IAINode/IAINodeResizer.tsx index 1217540827..1aca32ec70 100644 --- a/invokeai/frontend/web/src/features/nodes/components/IAINode/IAINodeResizer.tsx +++ b/invokeai/frontend/web/src/features/nodes/components/IAINode/IAINodeResizer.tsx @@ -1,4 +1,4 @@ -import { NODE_MIN_WIDTH } from 'app/constants'; +import { NODE_MIN_WIDTH } from 'features/nodes/types/constants'; import { memo } from 'react'; import { NodeResizeControl, NodeResizerProps } from 'reactflow'; diff --git a/invokeai/frontend/web/src/features/nodes/components/NodeWrapper.tsx b/invokeai/frontend/web/src/features/nodes/components/NodeWrapper.tsx index 882c6efd69..bc7944a28b 100644 --- a/invokeai/frontend/web/src/features/nodes/components/NodeWrapper.tsx +++ b/invokeai/frontend/web/src/features/nodes/components/NodeWrapper.tsx @@ -1,9 +1,8 @@ import { Box, useToken } from '@chakra-ui/react'; -import { NODE_MIN_WIDTH } from 'app/constants'; - import { useAppSelector } from 'app/store/storeHooks'; import { PropsWithChildren } from 'react'; import { DRAG_HANDLE_CLASSNAME } from '../hooks/useBuildInvocation'; +import { NODE_MIN_WIDTH } from '../types/constants'; type NodeWrapperProps = PropsWithChildren & { selected: boolean; diff --git a/invokeai/frontend/web/src/features/nodes/components/ui/ClearNodesButton.tsx b/invokeai/frontend/web/src/features/nodes/components/ui/ClearNodesButton.tsx index c5de45a826..86d9d08a84 100644 --- a/invokeai/frontend/web/src/features/nodes/components/ui/ClearNodesButton.tsx +++ b/invokeai/frontend/web/src/features/nodes/components/ui/ClearNodesButton.tsx @@ -9,7 +9,7 @@ import { Text, useDisclosure, } from '@chakra-ui/react'; -import { makeToast } from 'app/components/Toaster'; +import { makeToast } from 'features/system/util/makeToast'; import { RootState } from 'app/store/store'; import { useAppDispatch, useAppSelector } from 'app/store/storeHooks'; import IAIIconButton from 'common/components/IAIIconButton'; diff --git a/invokeai/frontend/web/src/features/nodes/components/ui/LoadNodesButton.tsx b/invokeai/frontend/web/src/features/nodes/components/ui/LoadNodesButton.tsx index 10aecc9fcc..706fbd8b31 100644 --- a/invokeai/frontend/web/src/features/nodes/components/ui/LoadNodesButton.tsx +++ b/invokeai/frontend/web/src/features/nodes/components/ui/LoadNodesButton.tsx @@ -1,5 +1,5 @@ import { FileButton } from '@mantine/core'; -import { makeToast } from 'app/components/Toaster'; +import { makeToast } from 'features/system/util/makeToast'; import { useAppDispatch } from 'app/store/storeHooks'; import IAIIconButton from 'common/components/IAIIconButton'; import { loadFileEdges, loadFileNodes } from 'features/nodes/store/nodesSlice'; diff --git a/invokeai/frontend/web/src/features/nodes/store/nodesPersistDenylist.ts b/invokeai/frontend/web/src/features/nodes/store/nodesPersistDenylist.ts index ba5c120dae..5cbc3c381d 100644 --- a/invokeai/frontend/web/src/features/nodes/store/nodesPersistDenylist.ts +++ b/invokeai/frontend/web/src/features/nodes/store/nodesPersistDenylist.ts @@ -1,4 +1,4 @@ -import { NodesState } from './nodesSlice'; +import { NodesState } from './types'; /** * Nodes slice persist denylist diff --git a/invokeai/frontend/web/src/features/nodes/store/nodesSlice.ts b/invokeai/frontend/web/src/features/nodes/store/nodesSlice.ts index 997d0493dd..436396fb38 100644 --- a/invokeai/frontend/web/src/features/nodes/store/nodesSlice.ts +++ b/invokeai/frontend/web/src/features/nodes/store/nodesSlice.ts @@ -1,5 +1,4 @@ import { createSlice, PayloadAction } from '@reduxjs/toolkit'; -import { RootState } from 'app/store/store'; import { ControlNetModelParam, LoRAModelParam, @@ -7,7 +6,6 @@ import { VaeModelParam, } from 'features/parameters/types/parameterSchemas'; import { cloneDeep, uniqBy } from 'lodash-es'; -import { OpenAPIV3 } from 'openapi-types'; import { RgbaColor } from 'react-colorful'; import { addEdge, @@ -19,24 +17,11 @@ import { Node, NodeChange, OnConnectStartParams, - ReactFlowInstance, } from 'reactflow'; import { receivedOpenAPISchema } from 'services/api/thunks/schema'; import { ImageField } from 'services/api/types'; import { InvocationTemplate, InvocationValue } from '../types/types'; - -export type NodesState = { - nodes: Node[]; - edges: Edge[]; - schema: OpenAPIV3.Document | null; - invocationTemplates: Record; - connectionStartParams: OnConnectStartParams | null; - shouldShowGraphOverlay: boolean; - shouldShowFieldTypeLegend: boolean; - shouldShowMinimapPanel: boolean; - editorInstance: ReactFlowInstance | undefined; - progressNodeSize: { width: number; height: number }; -}; +import { NodesState } from './types'; export const initialNodesState: NodesState = { nodes: [], @@ -194,5 +179,3 @@ export const { } = nodesSlice.actions; export default nodesSlice.reducer; - -export const nodesSelector = (state: RootState) => state.nodes; diff --git a/invokeai/frontend/web/src/features/nodes/store/types.ts b/invokeai/frontend/web/src/features/nodes/store/types.ts new file mode 100644 index 0000000000..14cb92006b --- /dev/null +++ b/invokeai/frontend/web/src/features/nodes/store/types.ts @@ -0,0 +1,16 @@ +import { OpenAPIV3 } from 'openapi-types'; +import { Edge, Node, OnConnectStartParams, ReactFlowInstance } from 'reactflow'; +import { InvocationTemplate, InvocationValue } from '../types/types'; + +export type NodesState = { + nodes: Node[]; + edges: Edge[]; + schema: OpenAPIV3.Document | null; + invocationTemplates: Record; + connectionStartParams: OnConnectStartParams | null; + shouldShowGraphOverlay: boolean; + shouldShowFieldTypeLegend: boolean; + shouldShowMinimapPanel: boolean; + editorInstance: ReactFlowInstance | undefined; + progressNodeSize: { width: number; height: number }; +}; diff --git a/invokeai/frontend/web/src/features/nodes/types/constants.ts b/invokeai/frontend/web/src/features/nodes/types/constants.ts index 3a70e52ee5..c07efc9c27 100644 --- a/invokeai/frontend/web/src/features/nodes/types/constants.ts +++ b/invokeai/frontend/web/src/features/nodes/types/constants.ts @@ -157,3 +157,5 @@ export const FIELDS: Record = { description: 'A RGBA color.', }, }; + +export const NODE_MIN_WIDTH = 250; diff --git a/invokeai/frontend/web/src/features/parameters/components/Parameters/Advanced/ParamAdvancedCollapse.tsx b/invokeai/frontend/web/src/features/parameters/components/Parameters/Advanced/ParamAdvancedCollapse.tsx index ab44bda16b..66c76ae7cb 100644 --- a/invokeai/frontend/web/src/features/parameters/components/Parameters/Advanced/ParamAdvancedCollapse.tsx +++ b/invokeai/frontend/web/src/features/parameters/components/Parameters/Advanced/ParamAdvancedCollapse.tsx @@ -20,7 +20,7 @@ const selector = createSelector( export default function ParamAdvancedCollapse() { const { activeLabel } = useAppSelector(selector); const shouldShowAdvancedOptions = useAppSelector( - (state: RootState) => state.ui.shouldShowAdvancedOptions + (state: RootState) => state.generation.shouldShowAdvancedOptions ); if (!shouldShowAdvancedOptions) { diff --git a/invokeai/frontend/web/src/features/parameters/components/Parameters/Advanced/ParamClipSkip.tsx b/invokeai/frontend/web/src/features/parameters/components/Parameters/Advanced/ParamClipSkip.tsx index a2162e6c9d..f51e42b6a1 100644 --- a/invokeai/frontend/web/src/features/parameters/components/Parameters/Advanced/ParamClipSkip.tsx +++ b/invokeai/frontend/web/src/features/parameters/components/Parameters/Advanced/ParamClipSkip.tsx @@ -2,28 +2,10 @@ import { RootState } from 'app/store/store'; import { useAppDispatch, useAppSelector } from 'app/store/storeHooks'; import IAISlider from 'common/components/IAISlider'; import { setClipSkip } from 'features/parameters/store/generationSlice'; +import { clipSkipMap } from 'features/parameters/types/constants'; import { useCallback, useMemo } from 'react'; import { useTranslation } from 'react-i18next'; -export const clipSkipMap = { - 'sd-1': { - maxClip: 12, - markers: [0, 1, 2, 3, 4, 8, 12], - }, - 'sd-2': { - maxClip: 24, - markers: [0, 1, 2, 3, 5, 10, 15, 20, 24], - }, - sdxl: { - maxClip: 24, - markers: [0, 1, 2, 3, 5, 10, 15, 20, 24], - }, - 'sdxl-refiner': { - maxClip: 24, - markers: [0, 1, 2, 3, 5, 10, 15, 20, 24], - }, -}; - export default function ParamClipSkip() { const clipSkip = useAppSelector( (state: RootState) => state.generation.clipSkip diff --git a/invokeai/frontend/web/src/features/parameters/components/Parameters/Canvas/BoundingBox/ParamBoundingBoxHeight.tsx b/invokeai/frontend/web/src/features/parameters/components/Parameters/Canvas/BoundingBox/ParamBoundingBoxHeight.tsx index 12fc4abcf7..64e44e63d0 100644 --- a/invokeai/frontend/web/src/features/parameters/components/Parameters/Canvas/BoundingBox/ParamBoundingBoxHeight.tsx +++ b/invokeai/frontend/web/src/features/parameters/components/Parameters/Canvas/BoundingBox/ParamBoundingBoxHeight.tsx @@ -1,23 +1,20 @@ import { createSelector } from '@reduxjs/toolkit'; +import { stateSelector } from 'app/store/store'; import { useAppDispatch, useAppSelector } from 'app/store/storeHooks'; import { defaultSelectorOptions } from 'app/store/util/defaultMemoizeOptions'; import IAISlider from 'common/components/IAISlider'; import { roundToMultiple } from 'common/util/roundDownToMultiple'; -import { - canvasSelector, - isStagingSelector, -} from 'features/canvas/store/canvasSelectors'; +import { isStagingSelector } from 'features/canvas/store/canvasSelectors'; import { setBoundingBoxDimensions } from 'features/canvas/store/canvasSlice'; -import { uiSelector } from 'features/ui/store/uiSelectors'; import { memo } from 'react'; import { useTranslation } from 'react-i18next'; const selector = createSelector( - [canvasSelector, isStagingSelector, uiSelector], - (canvas, isStaging, ui) => { + [stateSelector, isStagingSelector], + ({ canvas, generation }, isStaging) => { const { boundingBoxDimensions } = canvas; - const { aspectRatio } = ui; + const { aspectRatio } = generation; return { boundingBoxDimensions, isStaging, diff --git a/invokeai/frontend/web/src/features/parameters/components/Parameters/Canvas/BoundingBox/ParamBoundingBoxWidth.tsx b/invokeai/frontend/web/src/features/parameters/components/Parameters/Canvas/BoundingBox/ParamBoundingBoxWidth.tsx index 5c9a82110a..d5671fb0c0 100644 --- a/invokeai/frontend/web/src/features/parameters/components/Parameters/Canvas/BoundingBox/ParamBoundingBoxWidth.tsx +++ b/invokeai/frontend/web/src/features/parameters/components/Parameters/Canvas/BoundingBox/ParamBoundingBoxWidth.tsx @@ -1,23 +1,20 @@ import { createSelector } from '@reduxjs/toolkit'; +import { stateSelector } from 'app/store/store'; import { useAppDispatch, useAppSelector } from 'app/store/storeHooks'; import { defaultSelectorOptions } from 'app/store/util/defaultMemoizeOptions'; import IAISlider from 'common/components/IAISlider'; import { roundToMultiple } from 'common/util/roundDownToMultiple'; -import { - canvasSelector, - isStagingSelector, -} from 'features/canvas/store/canvasSelectors'; +import { isStagingSelector } from 'features/canvas/store/canvasSelectors'; import { setBoundingBoxDimensions } from 'features/canvas/store/canvasSlice'; -import { uiSelector } from 'features/ui/store/uiSelectors'; import { memo } from 'react'; import { useTranslation } from 'react-i18next'; const selector = createSelector( - [canvasSelector, isStagingSelector, uiSelector], - (canvas, isStaging, ui) => { + [stateSelector, isStagingSelector], + ({ canvas, generation }, isStaging) => { const { boundingBoxDimensions } = canvas; - const { aspectRatio } = ui; + const { aspectRatio } = generation; return { boundingBoxDimensions, isStaging, diff --git a/invokeai/frontend/web/src/features/parameters/components/Parameters/ControlNet/ParamControlNetCollapse.tsx b/invokeai/frontend/web/src/features/parameters/components/Parameters/ControlNet/ParamControlNetCollapse.tsx index 7f62c8b6c8..99ab512948 100644 --- a/invokeai/frontend/web/src/features/parameters/components/Parameters/ControlNet/ParamControlNetCollapse.tsx +++ b/invokeai/frontend/web/src/features/parameters/components/Parameters/ControlNet/ParamControlNetCollapse.tsx @@ -1,5 +1,6 @@ import { Divider, Flex } from '@chakra-ui/react'; import { createSelector } from '@reduxjs/toolkit'; +import { stateSelector } from 'app/store/store'; import { useAppDispatch, useAppSelector } from 'app/store/storeHooks'; import { defaultSelectorOptions } from 'app/store/util/defaultMemoizeOptions'; import IAICollapse from 'common/components/IAICollapse'; @@ -9,7 +10,6 @@ import ParamControlNetFeatureToggle from 'features/controlNet/components/paramet import { controlNetAdded, controlNetModelChanged, - controlNetSelector, } from 'features/controlNet/store/controlNetSlice'; import { getValidControlNets } from 'features/controlNet/util/getValidControlNets'; import { useFeatureStatus } from 'features/system/hooks/useFeatureStatus'; @@ -21,8 +21,8 @@ import { useGetControlNetModelsQuery } from 'services/api/endpoints/models'; import { v4 as uuidv4 } from 'uuid'; const selector = createSelector( - controlNetSelector, - (controlNet) => { + [stateSelector], + ({ controlNet }) => { const { controlNets, isEnabled } = controlNet; const validControlNets = getValidControlNets(controlNets); diff --git a/invokeai/frontend/web/src/features/parameters/components/Parameters/Core/ParamAspectRatio.tsx b/invokeai/frontend/web/src/features/parameters/components/Parameters/Core/ParamAspectRatio.tsx index e0bce7254e..657034e362 100644 --- a/invokeai/frontend/web/src/features/parameters/components/Parameters/Core/ParamAspectRatio.tsx +++ b/invokeai/frontend/web/src/features/parameters/components/Parameters/Core/ParamAspectRatio.tsx @@ -2,7 +2,7 @@ import { ButtonGroup, Flex } from '@chakra-ui/react'; import { RootState } from 'app/store/store'; import { useAppDispatch, useAppSelector } from 'app/store/storeHooks'; import IAIButton from 'common/components/IAIButton'; -import { setAspectRatio } from 'features/ui/store/uiSlice'; +import { setAspectRatio } from 'features/parameters/store/generationSlice'; import { activeTabNameSelector } from '../../../../ui/store/uiSelectors'; const aspectRatios = [ @@ -14,7 +14,7 @@ const aspectRatios = [ export default function ParamAspectRatio() { const aspectRatio = useAppSelector( - (state: RootState) => state.ui.aspectRatio + (state: RootState) => state.generation.aspectRatio ); const dispatch = useAppDispatch(); diff --git a/invokeai/frontend/web/src/features/parameters/components/Parameters/Core/ParamCFGScale.tsx b/invokeai/frontend/web/src/features/parameters/components/Parameters/Core/ParamCFGScale.tsx index d32ff960d5..54a7ccbe65 100644 --- a/invokeai/frontend/web/src/features/parameters/components/Parameters/Core/ParamCFGScale.tsx +++ b/invokeai/frontend/web/src/features/parameters/components/Parameters/Core/ParamCFGScale.tsx @@ -1,19 +1,16 @@ import { createSelector } from '@reduxjs/toolkit'; +import { stateSelector } from 'app/store/store'; import { useAppDispatch, useAppSelector } from 'app/store/storeHooks'; import { defaultSelectorOptions } from 'app/store/util/defaultMemoizeOptions'; import IAINumberInput from 'common/components/IAINumberInput'; import IAISlider from 'common/components/IAISlider'; -import { generationSelector } from 'features/parameters/store/generationSelectors'; import { setCfgScale } from 'features/parameters/store/generationSlice'; -import { configSelector } from 'features/system/store/configSelectors'; -import { hotkeysSelector } from 'features/ui/store/hotkeysSlice'; -import { uiSelector } from 'features/ui/store/uiSelectors'; import { memo, useCallback } from 'react'; import { useTranslation } from 'react-i18next'; const selector = createSelector( - [generationSelector, configSelector, uiSelector, hotkeysSelector], - (generation, config, ui, hotkeys) => { + [stateSelector], + ({ generation, config, ui, hotkeys }) => { const { initial, min, sliderMax, inputMax } = config.sd.guidance; const { cfgScale } = generation; const { shouldUseSliders } = ui; diff --git a/invokeai/frontend/web/src/features/parameters/components/Parameters/Core/ParamHeight.tsx b/invokeai/frontend/web/src/features/parameters/components/Parameters/Core/ParamHeight.tsx index 63abe0ddf9..37ed43ddc0 100644 --- a/invokeai/frontend/web/src/features/parameters/components/Parameters/Core/ParamHeight.tsx +++ b/invokeai/frontend/web/src/features/parameters/components/Parameters/Core/ParamHeight.tsx @@ -1,23 +1,20 @@ import { createSelector } from '@reduxjs/toolkit'; +import { stateSelector } from 'app/store/store'; import { useAppDispatch, useAppSelector } from 'app/store/storeHooks'; import { defaultSelectorOptions } from 'app/store/util/defaultMemoizeOptions'; import IAISlider, { IAIFullSliderProps } from 'common/components/IAISlider'; import { roundToMultiple } from 'common/util/roundDownToMultiple'; -import { generationSelector } from 'features/parameters/store/generationSelectors'; import { setHeight, setWidth } from 'features/parameters/store/generationSlice'; -import { configSelector } from 'features/system/store/configSelectors'; -import { hotkeysSelector } from 'features/ui/store/hotkeysSlice'; -import { uiSelector } from 'features/ui/store/uiSelectors'; import { memo, useCallback } from 'react'; import { useTranslation } from 'react-i18next'; const selector = createSelector( - [generationSelector, hotkeysSelector, configSelector, uiSelector], - (generation, hotkeys, config, ui) => { + [stateSelector], + ({ generation, hotkeys, config }) => { const { initial, min, sliderMax, inputMax, fineStep, coarseStep } = config.sd.height; const { height } = generation; - const { aspectRatio } = ui; + const { aspectRatio } = generation; const step = hotkeys.shift ? fineStep : coarseStep; diff --git a/invokeai/frontend/web/src/features/parameters/components/Parameters/Core/ParamScheduler.tsx b/invokeai/frontend/web/src/features/parameters/components/Parameters/Core/ParamScheduler.tsx index 5be09a2ff5..da1b359b37 100644 --- a/invokeai/frontend/web/src/features/parameters/components/Parameters/Core/ParamScheduler.tsx +++ b/invokeai/frontend/web/src/features/parameters/components/Parameters/Core/ParamScheduler.tsx @@ -1,12 +1,15 @@ import { createSelector } from '@reduxjs/toolkit'; -import { SCHEDULER_LABEL_MAP, SCHEDULER_NAMES } from 'app/constants'; import { useAppDispatch, useAppSelector } from 'app/store/storeHooks'; import { defaultSelectorOptions } from 'app/store/util/defaultMemoizeOptions'; import IAIMantineSearchableSelect from 'common/components/IAIMantineSearchableSelect'; import { generationSelector } from 'features/parameters/store/generationSelectors'; import { setScheduler } from 'features/parameters/store/generationSlice'; -import { SchedulerParam } from 'features/parameters/types/parameterSchemas'; +import { + SCHEDULER_LABEL_MAP, + SchedulerParam, +} from 'features/parameters/types/parameterSchemas'; import { uiSelector } from 'features/ui/store/uiSelectors'; +import { map } from 'lodash-es'; import { memo, useCallback } from 'react'; import { useTranslation } from 'react-i18next'; @@ -16,10 +19,10 @@ const selector = createSelector( const { scheduler } = generation; const { favoriteSchedulers: enabledSchedulers } = ui; - const data = SCHEDULER_NAMES.map((schedulerName) => ({ - value: schedulerName, - label: SCHEDULER_LABEL_MAP[schedulerName as SchedulerParam], - group: enabledSchedulers.includes(schedulerName) + const data = map(SCHEDULER_LABEL_MAP, (label, name) => ({ + value: name, + label: label, + group: enabledSchedulers.includes(name as SchedulerParam) ? 'Favorites' : undefined, })).sort((a, b) => a.label.localeCompare(b.label)); diff --git a/invokeai/frontend/web/src/features/parameters/components/Parameters/Core/ParamSteps.tsx b/invokeai/frontend/web/src/features/parameters/components/Parameters/Core/ParamSteps.tsx index d939113c7c..3728e59158 100644 --- a/invokeai/frontend/web/src/features/parameters/components/Parameters/Core/ParamSteps.tsx +++ b/invokeai/frontend/web/src/features/parameters/components/Parameters/Core/ParamSteps.tsx @@ -1,23 +1,20 @@ import { createSelector } from '@reduxjs/toolkit'; +import { stateSelector } from 'app/store/store'; import { useAppDispatch, useAppSelector } from 'app/store/storeHooks'; import { defaultSelectorOptions } from 'app/store/util/defaultMemoizeOptions'; import IAINumberInput from 'common/components/IAINumberInput'; import IAISlider from 'common/components/IAISlider'; -import { generationSelector } from 'features/parameters/store/generationSelectors'; import { clampSymmetrySteps, setSteps, } from 'features/parameters/store/generationSlice'; -import { configSelector } from 'features/system/store/configSelectors'; -import { hotkeysSelector } from 'features/ui/store/hotkeysSlice'; -import { uiSelector } from 'features/ui/store/uiSelectors'; import { memo, useCallback } from 'react'; import { useTranslation } from 'react-i18next'; const selector = createSelector( - [generationSelector, configSelector, uiSelector, hotkeysSelector], - (generation, config, ui, hotkeys) => { + [stateSelector], + ({ generation, config, ui, hotkeys }) => { const { initial, min, sliderMax, inputMax, fineStep, coarseStep } = config.sd.steps; const { steps } = generation; diff --git a/invokeai/frontend/web/src/features/parameters/components/Parameters/Core/ParamWidth.tsx b/invokeai/frontend/web/src/features/parameters/components/Parameters/Core/ParamWidth.tsx index 991db19097..55daadf9ea 100644 --- a/invokeai/frontend/web/src/features/parameters/components/Parameters/Core/ParamWidth.tsx +++ b/invokeai/frontend/web/src/features/parameters/components/Parameters/Core/ParamWidth.tsx @@ -1,23 +1,19 @@ import { createSelector } from '@reduxjs/toolkit'; +import { stateSelector } from 'app/store/store'; import { useAppDispatch, useAppSelector } from 'app/store/storeHooks'; import { defaultSelectorOptions } from 'app/store/util/defaultMemoizeOptions'; import IAISlider, { IAIFullSliderProps } from 'common/components/IAISlider'; import { roundToMultiple } from 'common/util/roundDownToMultiple'; -import { generationSelector } from 'features/parameters/store/generationSelectors'; import { setHeight, setWidth } from 'features/parameters/store/generationSlice'; -import { configSelector } from 'features/system/store/configSelectors'; -import { hotkeysSelector } from 'features/ui/store/hotkeysSlice'; -import { uiSelector } from 'features/ui/store/uiSelectors'; import { memo, useCallback } from 'react'; import { useTranslation } from 'react-i18next'; const selector = createSelector( - [generationSelector, hotkeysSelector, configSelector, uiSelector], - (generation, hotkeys, config, ui) => { + [stateSelector], + ({ generation, hotkeys, config, ui }) => { const { initial, min, sliderMax, inputMax, fineStep, coarseStep } = config.sd.width; - const { width } = generation; - const { aspectRatio } = ui; + const { width, aspectRatio } = generation; const step = hotkeys.shift ? fineStep : coarseStep; diff --git a/invokeai/frontend/web/src/features/parameters/components/Parameters/ImageToImage/ImageToImageStrength.tsx b/invokeai/frontend/web/src/features/parameters/components/Parameters/ImageToImage/ImageToImageStrength.tsx index eea5ec3c27..b45fc9f386 100644 --- a/invokeai/frontend/web/src/features/parameters/components/Parameters/ImageToImage/ImageToImageStrength.tsx +++ b/invokeai/frontend/web/src/features/parameters/components/Parameters/ImageToImage/ImageToImageStrength.tsx @@ -1,17 +1,15 @@ import { createSelector } from '@reduxjs/toolkit'; +import { stateSelector } from 'app/store/store'; import { useAppDispatch, useAppSelector } from 'app/store/storeHooks'; import { defaultSelectorOptions } from 'app/store/util/defaultMemoizeOptions'; import IAISlider from 'common/components/IAISlider'; -import { generationSelector } from 'features/parameters/store/generationSelectors'; import { setImg2imgStrength } from 'features/parameters/store/generationSlice'; -import { configSelector } from 'features/system/store/configSelectors'; -import { hotkeysSelector } from 'features/ui/store/hotkeysSlice'; import { memo, useCallback } from 'react'; import { useTranslation } from 'react-i18next'; const selector = createSelector( - [generationSelector, hotkeysSelector, configSelector], - (generation, hotkeys, config) => { + [stateSelector], + ({ generation, hotkeys, config }) => { const { initial, min, sliderMax, inputMax, fineStep, coarseStep } = config.sd.img2imgStrength; const { img2imgStrength } = generation; diff --git a/invokeai/frontend/web/src/features/parameters/store/generationSlice.ts b/invokeai/frontend/web/src/features/parameters/store/generationSlice.ts index c5ec7930a4..9a1d514a49 100644 --- a/invokeai/frontend/web/src/features/parameters/store/generationSlice.ts +++ b/invokeai/frontend/web/src/features/parameters/store/generationSlice.ts @@ -1,15 +1,10 @@ import type { PayloadAction } from '@reduxjs/toolkit'; import { createSlice } from '@reduxjs/toolkit'; -import { DEFAULT_SCHEDULER_NAME } from 'app/constants'; import { roundToMultiple } from 'common/util/roundDownToMultiple'; import { configChanged } from 'features/system/store/configSlice'; -import { - setAspectRatio, - setShouldShowAdvancedOptions, -} from 'features/ui/store/uiSlice'; import { clamp } from 'lodash-es'; import { ImageDTO, MainModelField } from 'services/api/types'; -import { clipSkipMap } from '../components/Parameters/Advanced/ParamClipSkip'; +import { clipSkipMap } from '../types/constants'; import { CfgScaleParam, HeightParam, @@ -60,6 +55,8 @@ export interface GenerationState { seamlessYAxis: boolean; clipSkip: number; shouldUseCpuNoise: boolean; + shouldShowAdvancedOptions: boolean; + aspectRatio: number | null; } export const initialGenerationState: GenerationState = { @@ -71,7 +68,7 @@ export const initialGenerationState: GenerationState = { perlin: 0, positivePrompt: '', negativePrompt: '', - scheduler: DEFAULT_SCHEDULER_NAME, + scheduler: 'euler', seamBlur: 16, seamSize: 96, seamSteps: 30, @@ -96,6 +93,8 @@ export const initialGenerationState: GenerationState = { seamlessYAxis: false, clipSkip: 0, shouldUseCpuNoise: true, + shouldShowAdvancedOptions: false, + aspectRatio: null, }; const initialState: GenerationState = initialGenerationState; @@ -248,6 +247,19 @@ export const generationSlice = createSlice({ shouldUseCpuNoiseChanged: (state, action: PayloadAction) => { state.shouldUseCpuNoise = action.payload; }, + setShouldShowAdvancedOptions: (state, action: PayloadAction) => { + state.shouldShowAdvancedOptions = action.payload; + if (!action.payload) { + state.clipSkip = 0; + } + }, + setAspectRatio: (state, action: PayloadAction) => { + const newAspectRatio = action.payload; + state.aspectRatio = newAspectRatio; + if (newAspectRatio) { + state.height = roundToMultiple(state.width / newAspectRatio, 8); + } + }, }, extraReducers: (builder) => { builder.addCase(configChanged, (state, action) => { @@ -270,12 +282,6 @@ export const generationSlice = createSlice({ const advancedOptionsStatus = action.payload; if (!advancedOptionsStatus) state.clipSkip = 0; }); - builder.addCase(setAspectRatio, (state, action) => { - const ratio = action.payload; - if (ratio) { - state.height = roundToMultiple(state.width / ratio, 8); - } - }); }, }); @@ -319,6 +325,8 @@ export const { setSeamlessYAxis, setClipSkip, shouldUseCpuNoiseChanged, + setShouldShowAdvancedOptions, + setAspectRatio, } = generationSlice.actions; export default generationSlice.reducer; diff --git a/invokeai/frontend/web/src/features/parameters/types/constants.ts b/invokeai/frontend/web/src/features/parameters/types/constants.ts index 04917646d1..ca52bfb95a 100644 --- a/invokeai/frontend/web/src/features/parameters/types/constants.ts +++ b/invokeai/frontend/web/src/features/parameters/types/constants.ts @@ -4,3 +4,22 @@ export const MODEL_TYPE_MAP = { sdxl: 'Stable Diffusion XL', 'sdxl-refiner': 'Stable Diffusion XL Refiner', }; + +export const clipSkipMap = { + 'sd-1': { + maxClip: 12, + markers: [0, 1, 2, 3, 4, 8, 12], + }, + 'sd-2': { + maxClip: 24, + markers: [0, 1, 2, 3, 5, 10, 15, 20, 24], + }, + sdxl: { + maxClip: 24, + markers: [0, 1, 2, 3, 5, 10, 15, 20, 24], + }, + 'sdxl-refiner': { + maxClip: 24, + markers: [0, 1, 2, 3, 5, 10, 15, 20, 24], + }, +}; diff --git a/invokeai/frontend/web/src/features/parameters/types/parameterSchemas.ts b/invokeai/frontend/web/src/features/parameters/types/parameterSchemas.ts index c2b6eb6187..cea5fb9987 100644 --- a/invokeai/frontend/web/src/features/parameters/types/parameterSchemas.ts +++ b/invokeai/frontend/web/src/features/parameters/types/parameterSchemas.ts @@ -1,4 +1,4 @@ -import { NUMPY_RAND_MAX, SCHEDULER_NAMES_AS_CONST } from 'app/constants'; +import { NUMPY_RAND_MAX } from 'app/constants'; import { z } from 'zod'; /** @@ -73,7 +73,30 @@ export const isValidCfgScale = (val: unknown): val is CfgScaleParam => /** * Zod schema for scheduler parameter */ -export const zScheduler = z.enum(SCHEDULER_NAMES_AS_CONST); +export const zScheduler = z.enum([ + 'euler', + 'deis', + 'ddim', + 'ddpm', + 'dpmpp_2s', + 'dpmpp_2m', + 'dpmpp_2m_sde', + 'dpmpp_sde', + 'heun', + 'kdpm_2', + 'lms', + 'pndm', + 'unipc', + 'euler_k', + 'dpmpp_2s_k', + 'dpmpp_2m_k', + 'dpmpp_2m_sde_k', + 'dpmpp_sde_k', + 'heun_k', + 'lms_k', + 'euler_a', + 'kdpm_2_a', +]); /** * Type alias for scheduler parameter, inferred from its zod schema */ @@ -84,6 +107,31 @@ export type SchedulerParam = z.infer; export const isValidScheduler = (val: unknown): val is SchedulerParam => zScheduler.safeParse(val).success; +export const SCHEDULER_LABEL_MAP: Record = { + euler: 'Euler', + deis: 'DEIS', + ddim: 'DDIM', + ddpm: 'DDPM', + dpmpp_sde: 'DPM++ SDE', + dpmpp_2s: 'DPM++ 2S', + dpmpp_2m: 'DPM++ 2M', + dpmpp_2m_sde: 'DPM++ 2M SDE', + heun: 'Heun', + kdpm_2: 'KDPM 2', + lms: 'LMS', + pndm: 'PNDM', + unipc: 'UniPC', + euler_k: 'Euler Karras', + dpmpp_sde_k: 'DPM++ SDE Karras', + dpmpp_2s_k: 'DPM++ 2S Karras', + dpmpp_2m_k: 'DPM++ 2M Karras', + dpmpp_2m_sde_k: 'DPM++ 2M SDE Karras', + heun_k: 'Heun Karras', + lms_k: 'LMS Karras', + euler_a: 'Euler Ancestral', + kdpm_2_a: 'KDPM 2 Ancestral', +}; + /** * Zod schema for seed parameter */ diff --git a/invokeai/frontend/web/src/features/system/components/LanguagePicker.tsx b/invokeai/frontend/web/src/features/system/components/LanguagePicker.tsx index 3e4e423c3f..0a88ac9215 100644 --- a/invokeai/frontend/web/src/features/system/components/LanguagePicker.tsx +++ b/invokeai/frontend/web/src/features/system/components/LanguagePicker.tsx @@ -7,32 +7,13 @@ import { MenuOptionGroup, Tooltip, } from '@chakra-ui/react'; -import { useTranslation } from 'react-i18next'; -import i18n from 'i18n'; import { useAppDispatch, useAppSelector } from 'app/store/storeHooks'; +import { map } from 'lodash-es'; +import { useTranslation } from 'react-i18next'; +import { IoLanguage } from 'react-icons/io5'; +import { LANGUAGES } from '../store/constants'; import { languageSelector } from '../store/systemSelectors'; import { languageChanged } from '../store/systemSlice'; -import { map } from 'lodash-es'; -import { IoLanguage } from 'react-icons/io5'; - -export const LANGUAGES = { - ar: i18n.t('common.langArabic', { lng: 'ar' }), - nl: i18n.t('common.langDutch', { lng: 'nl' }), - en: i18n.t('common.langEnglish', { lng: 'en' }), - fr: i18n.t('common.langFrench', { lng: 'fr' }), - de: i18n.t('common.langGerman', { lng: 'de' }), - he: i18n.t('common.langHebrew', { lng: 'he' }), - it: i18n.t('common.langItalian', { lng: 'it' }), - ja: i18n.t('common.langJapanese', { lng: 'ja' }), - ko: i18n.t('common.langKorean', { lng: 'ko' }), - pl: i18n.t('common.langPolish', { lng: 'pl' }), - pt_BR: i18n.t('common.langBrPortuguese', { lng: 'pt_BR' }), - pt: i18n.t('common.langPortuguese', { lng: 'pt' }), - ru: i18n.t('common.langRussian', { lng: 'ru' }), - zh_CN: i18n.t('common.langSimplifiedChinese', { lng: 'zh_CN' }), - es: i18n.t('common.langSpanish', { lng: 'es' }), - uk: i18n.t('common.langUkranian', { lng: 'ua' }), -}; export default function LanguagePicker() { const { t } = useTranslation(); diff --git a/invokeai/frontend/web/src/features/system/components/SettingsModal/SettingsClearIntermediates.tsx b/invokeai/frontend/web/src/features/system/components/SettingsModal/SettingsClearIntermediates.tsx index 9d095f3511..85fbf016b8 100644 --- a/invokeai/frontend/web/src/features/system/components/SettingsModal/SettingsClearIntermediates.tsx +++ b/invokeai/frontend/web/src/features/system/components/SettingsModal/SettingsClearIntermediates.tsx @@ -1,5 +1,6 @@ import { Heading, Text } from '@chakra-ui/react'; import { useAppDispatch } from 'app/store/storeHooks'; +import { controlNetReset } from 'features/controlNet/store/controlNetSlice'; import { useCallback, useEffect } from 'react'; import IAIButton from '../../../../common/components/IAIButton'; import { @@ -8,8 +9,7 @@ import { } from '../../../../services/api/endpoints/images'; import { resetCanvas } from '../../../canvas/store/canvasSlice'; import { addToast } from '../../store/systemSlice'; -import { StyledFlex } from './SettingsModal'; -import { controlNetReset } from 'features/controlNet/store/controlNetSlice'; +import StyledFlex from './StyledFlex'; export default function SettingsClearIntermediates() { const dispatch = useAppDispatch(); diff --git a/invokeai/frontend/web/src/features/system/components/SettingsModal/SettingsModal.tsx b/invokeai/frontend/web/src/features/system/components/SettingsModal/SettingsModal.tsx index 7df4c39cbb..2deccfa46d 100644 --- a/invokeai/frontend/web/src/features/system/components/SettingsModal/SettingsModal.tsx +++ b/invokeai/frontend/web/src/features/system/components/SettingsModal/SettingsModal.tsx @@ -14,12 +14,12 @@ import { import { createSelector } from '@reduxjs/toolkit'; import { VALID_LOG_LEVELS } from 'app/logging/logger'; import { LOCALSTORAGE_KEYS, LOCALSTORAGE_PREFIX } from 'app/store/constants'; +import { stateSelector } from 'app/store/store'; import { useAppDispatch, useAppSelector } from 'app/store/storeHooks'; import IAIButton from 'common/components/IAIButton'; import IAIMantineSelect from 'common/components/IAIMantineSelect'; -import { systemSelector } from 'features/system/store/systemSelectors'; +import { setShouldShowAdvancedOptions } from 'features/parameters/store/generationSlice'; import { - SystemState, consoleLogLevelChanged, setEnableImageDebugging, setIsNodesEnabled, @@ -27,18 +27,14 @@ import { shouldAntialiasProgressImageChanged, shouldLogToConsoleChanged, } from 'features/system/store/systemSlice'; -import { uiSelector } from 'features/ui/store/uiSelectors'; import { - setShouldShowAdvancedOptions, setShouldShowProgressInViewer, setShouldUseCanvasBetaLayout, setShouldUseSliders, } from 'features/ui/store/uiSlice'; -import { UIState } from 'features/ui/store/uiTypes'; import { isEqual } from 'lodash-es'; import { ChangeEvent, - PropsWithChildren, ReactElement, cloneElement, useCallback, @@ -49,10 +45,11 @@ import { LogLevelName } from 'roarr'; import SettingSwitch from './SettingSwitch'; import SettingsClearIntermediates from './SettingsClearIntermediates'; import SettingsSchedulers from './SettingsSchedulers'; +import StyledFlex from './StyledFlex'; const selector = createSelector( - [systemSelector, uiSelector], - (system: SystemState, ui: UIState) => { + [stateSelector], + ({ system, ui, generation }) => { const { shouldConfirmOnDelete, enableImageDebugging, @@ -66,9 +63,10 @@ const selector = createSelector( shouldUseCanvasBetaLayout, shouldUseSliders, shouldShowProgressInViewer, - shouldShowAdvancedOptions, } = ui; + const { shouldShowAdvancedOptions } = generation; + return { shouldConfirmOnDelete, enableImageDebugging, @@ -349,22 +347,3 @@ const SettingsModal = ({ children, config }: SettingsModalProps) => { }; export default SettingsModal; - -export const StyledFlex = (props: PropsWithChildren) => { - return ( - - {props.children} - - ); -}; diff --git a/invokeai/frontend/web/src/features/system/components/SettingsModal/SettingsSchedulers.tsx b/invokeai/frontend/web/src/features/system/components/SettingsModal/SettingsSchedulers.tsx index 959559548e..6d2e963b6a 100644 --- a/invokeai/frontend/web/src/features/system/components/SettingsModal/SettingsSchedulers.tsx +++ b/invokeai/frontend/web/src/features/system/components/SettingsModal/SettingsSchedulers.tsx @@ -1,16 +1,18 @@ -import { SCHEDULER_LABEL_MAP, SCHEDULER_NAMES } from 'app/constants'; import { RootState } from 'app/store/store'; import { useAppDispatch, useAppSelector } from 'app/store/storeHooks'; import IAIMantineMultiSelect from 'common/components/IAIMantineMultiSelect'; -import { SchedulerParam } from 'features/parameters/types/parameterSchemas'; +import { + SCHEDULER_LABEL_MAP, + SchedulerParam, +} from 'features/parameters/types/parameterSchemas'; import { favoriteSchedulersChanged } from 'features/ui/store/uiSlice'; import { map } from 'lodash-es'; import { useCallback } from 'react'; import { useTranslation } from 'react-i18next'; -const data = map(SCHEDULER_NAMES, (s) => ({ - value: s, - label: SCHEDULER_LABEL_MAP[s], +const data = map(SCHEDULER_LABEL_MAP, (value, label) => ({ + value, + label, })).sort((a, b) => a.label.localeCompare(b.label)); export default function SettingsSchedulers() { diff --git a/invokeai/frontend/web/src/features/system/components/SettingsModal/StyledFlex.tsx b/invokeai/frontend/web/src/features/system/components/SettingsModal/StyledFlex.tsx new file mode 100644 index 0000000000..fd0580f4e2 --- /dev/null +++ b/invokeai/frontend/web/src/features/system/components/SettingsModal/StyledFlex.tsx @@ -0,0 +1,23 @@ +import { Flex } from '@chakra-ui/react'; +import { PropsWithChildren } from 'react'; + +const StyledFlex = (props: PropsWithChildren) => { + return ( + + {props.children} + + ); +}; + +export default StyledFlex; diff --git a/invokeai/frontend/web/src/features/system/store/constants.ts b/invokeai/frontend/web/src/features/system/store/constants.ts new file mode 100644 index 0000000000..4e7dd0b9a8 --- /dev/null +++ b/invokeai/frontend/web/src/features/system/store/constants.ts @@ -0,0 +1,20 @@ +import i18n from 'i18n'; + +export const LANGUAGES = { + ar: i18n.t('common.langArabic', { lng: 'ar' }), + nl: i18n.t('common.langDutch', { lng: 'nl' }), + en: i18n.t('common.langEnglish', { lng: 'en' }), + fr: i18n.t('common.langFrench', { lng: 'fr' }), + de: i18n.t('common.langGerman', { lng: 'de' }), + he: i18n.t('common.langHebrew', { lng: 'he' }), + it: i18n.t('common.langItalian', { lng: 'it' }), + ja: i18n.t('common.langJapanese', { lng: 'ja' }), + ko: i18n.t('common.langKorean', { lng: 'ko' }), + pl: i18n.t('common.langPolish', { lng: 'pl' }), + pt_BR: i18n.t('common.langBrPortuguese', { lng: 'pt_BR' }), + pt: i18n.t('common.langPortuguese', { lng: 'pt' }), + ru: i18n.t('common.langRussian', { lng: 'ru' }), + zh_CN: i18n.t('common.langSimplifiedChinese', { lng: 'zh_CN' }), + es: i18n.t('common.langSpanish', { lng: 'es' }), + uk: i18n.t('common.langUkranian', { lng: 'ua' }), +}; diff --git a/invokeai/frontend/web/src/features/system/store/systemSlice.ts b/invokeai/frontend/web/src/features/system/store/systemSlice.ts index aca126bb13..629a4f0139 100644 --- a/invokeai/frontend/web/src/features/system/store/systemSlice.ts +++ b/invokeai/frontend/web/src/features/system/store/systemSlice.ts @@ -1,6 +1,5 @@ import { UseToastOptions } from '@chakra-ui/react'; import { PayloadAction, createSlice } from '@reduxjs/toolkit'; - import { InvokeLogLevel } from 'app/logging/logger'; import { userInvoked } from 'app/store/actions'; import { nodeTemplatesBuilt } from 'features/nodes/store/nodesSlice'; @@ -22,8 +21,8 @@ import { appSocketUnsubscribed, } from 'services/events/actions'; import { ProgressImage } from 'services/events/types'; -import { makeToast } from '../../../app/components/Toaster'; -import { LANGUAGES } from '../components/LanguagePicker'; +import { makeToast } from '../util/makeToast'; +import { LANGUAGES } from './constants'; export type CancelStrategy = 'immediate' | 'scheduled'; diff --git a/invokeai/frontend/web/src/features/system/util/makeToast.ts b/invokeai/frontend/web/src/features/system/util/makeToast.ts new file mode 100644 index 0000000000..2d5e88bdb7 --- /dev/null +++ b/invokeai/frontend/web/src/features/system/util/makeToast.ts @@ -0,0 +1,20 @@ +import { UseToastOptions } from '@chakra-ui/react'; + +export type MakeToastArg = string | UseToastOptions; + +/** + * Makes a toast from a string or a UseToastOptions object. + * If a string is passed, the toast will have the status 'info' and will be closable with a duration of 2500ms. + */ +export const makeToast = (arg: MakeToastArg): UseToastOptions => { + if (typeof arg === 'string') { + return { + title: arg, + status: 'info', + isClosable: true, + duration: 2500, + }; + } + + return { status: 'info', isClosable: true, duration: 2500, ...arg }; +}; diff --git a/invokeai/frontend/web/src/features/ui/components/tabs/ModelManager/subpanels/AddModelsPanel/AdvancedAddCheckpoint.tsx b/invokeai/frontend/web/src/features/ui/components/tabs/ModelManager/subpanels/AddModelsPanel/AdvancedAddCheckpoint.tsx index ededadaa06..fd5106b289 100644 --- a/invokeai/frontend/web/src/features/ui/components/tabs/ModelManager/subpanels/AddModelsPanel/AdvancedAddCheckpoint.tsx +++ b/invokeai/frontend/web/src/features/ui/components/tabs/ModelManager/subpanels/AddModelsPanel/AdvancedAddCheckpoint.tsx @@ -1,6 +1,6 @@ import { Flex } from '@chakra-ui/react'; import { useForm } from '@mantine/form'; -import { makeToast } from 'app/components/Toaster'; +import { makeToast } from 'features/system/util/makeToast'; import { useAppDispatch } from 'app/store/storeHooks'; import IAIButton from 'common/components/IAIButton'; import IAIMantineTextInput from 'common/components/IAIMantineInput'; diff --git a/invokeai/frontend/web/src/features/ui/components/tabs/ModelManager/subpanels/AddModelsPanel/AdvancedAddDiffusers.tsx b/invokeai/frontend/web/src/features/ui/components/tabs/ModelManager/subpanels/AddModelsPanel/AdvancedAddDiffusers.tsx index ce8da9289b..376631bd1f 100644 --- a/invokeai/frontend/web/src/features/ui/components/tabs/ModelManager/subpanels/AddModelsPanel/AdvancedAddDiffusers.tsx +++ b/invokeai/frontend/web/src/features/ui/components/tabs/ModelManager/subpanels/AddModelsPanel/AdvancedAddDiffusers.tsx @@ -1,6 +1,6 @@ import { Flex } from '@chakra-ui/react'; import { useForm } from '@mantine/form'; -import { makeToast } from 'app/components/Toaster'; +import { makeToast } from 'features/system/util/makeToast'; import { useAppDispatch } from 'app/store/storeHooks'; import IAIButton from 'common/components/IAIButton'; import IAIMantineTextInput from 'common/components/IAIMantineInput'; diff --git a/invokeai/frontend/web/src/features/ui/components/tabs/ModelManager/subpanels/AddModelsPanel/FoundModelsList.tsx b/invokeai/frontend/web/src/features/ui/components/tabs/ModelManager/subpanels/AddModelsPanel/FoundModelsList.tsx index bcb0e02298..10f297ce07 100644 --- a/invokeai/frontend/web/src/features/ui/components/tabs/ModelManager/subpanels/AddModelsPanel/FoundModelsList.tsx +++ b/invokeai/frontend/web/src/features/ui/components/tabs/ModelManager/subpanels/AddModelsPanel/FoundModelsList.tsx @@ -1,5 +1,5 @@ import { Flex, Text } from '@chakra-ui/react'; -import { makeToast } from 'app/components/Toaster'; +import { makeToast } from 'features/system/util/makeToast'; import { RootState } from 'app/store/store'; import { useAppDispatch, useAppSelector } from 'app/store/storeHooks'; import IAIButton from 'common/components/IAIButton'; diff --git a/invokeai/frontend/web/src/features/ui/components/tabs/ModelManager/subpanels/AddModelsPanel/SimpleAddModels.tsx b/invokeai/frontend/web/src/features/ui/components/tabs/ModelManager/subpanels/AddModelsPanel/SimpleAddModels.tsx index 7811e462dc..cb0ce627a0 100644 --- a/invokeai/frontend/web/src/features/ui/components/tabs/ModelManager/subpanels/AddModelsPanel/SimpleAddModels.tsx +++ b/invokeai/frontend/web/src/features/ui/components/tabs/ModelManager/subpanels/AddModelsPanel/SimpleAddModels.tsx @@ -5,7 +5,7 @@ import { useTranslation } from 'react-i18next'; import { SelectItem } from '@mantine/core'; import { useForm } from '@mantine/form'; -import { makeToast } from 'app/components/Toaster'; +import { makeToast } from 'features/system/util/makeToast'; import { RootState } from 'app/store/store'; import IAIButton from 'common/components/IAIButton'; import IAIMantineTextInput from 'common/components/IAIMantineInput'; diff --git a/invokeai/frontend/web/src/features/ui/components/tabs/ModelManager/subpanels/MergeModelsPanel.tsx b/invokeai/frontend/web/src/features/ui/components/tabs/ModelManager/subpanels/MergeModelsPanel.tsx index 6edd0e8d81..19ca10e240 100644 --- a/invokeai/frontend/web/src/features/ui/components/tabs/ModelManager/subpanels/MergeModelsPanel.tsx +++ b/invokeai/frontend/web/src/features/ui/components/tabs/ModelManager/subpanels/MergeModelsPanel.tsx @@ -1,5 +1,5 @@ import { Flex, Radio, RadioGroup, Text, Tooltip } from '@chakra-ui/react'; -import { makeToast } from 'app/components/Toaster'; +import { makeToast } from 'features/system/util/makeToast'; import { useAppDispatch } from 'app/store/storeHooks'; import IAIButton from 'common/components/IAIButton'; import IAIInput from 'common/components/IAIInput'; diff --git a/invokeai/frontend/web/src/features/ui/components/tabs/ModelManager/subpanels/ModelManagerPanel/CheckpointModelEdit.tsx b/invokeai/frontend/web/src/features/ui/components/tabs/ModelManager/subpanels/ModelManagerPanel/CheckpointModelEdit.tsx index 2f2706d640..cb297d3afe 100644 --- a/invokeai/frontend/web/src/features/ui/components/tabs/ModelManager/subpanels/ModelManagerPanel/CheckpointModelEdit.tsx +++ b/invokeai/frontend/web/src/features/ui/components/tabs/ModelManager/subpanels/ModelManagerPanel/CheckpointModelEdit.tsx @@ -1,6 +1,6 @@ import { Badge, Divider, Flex, Text } from '@chakra-ui/react'; import { useForm } from '@mantine/form'; -import { makeToast } from 'app/components/Toaster'; +import { makeToast } from 'features/system/util/makeToast'; import { useAppDispatch, useAppSelector } from 'app/store/storeHooks'; import IAIButton from 'common/components/IAIButton'; import IAIMantineTextInput from 'common/components/IAIMantineInput'; diff --git a/invokeai/frontend/web/src/features/ui/components/tabs/ModelManager/subpanels/ModelManagerPanel/DiffusersModelEdit.tsx b/invokeai/frontend/web/src/features/ui/components/tabs/ModelManager/subpanels/ModelManagerPanel/DiffusersModelEdit.tsx index 37a08959be..39ba4bc4ce 100644 --- a/invokeai/frontend/web/src/features/ui/components/tabs/ModelManager/subpanels/ModelManagerPanel/DiffusersModelEdit.tsx +++ b/invokeai/frontend/web/src/features/ui/components/tabs/ModelManager/subpanels/ModelManagerPanel/DiffusersModelEdit.tsx @@ -1,6 +1,6 @@ import { Divider, Flex, Text } from '@chakra-ui/react'; import { useForm } from '@mantine/form'; -import { makeToast } from 'app/components/Toaster'; +import { makeToast } from 'features/system/util/makeToast'; import { useAppDispatch, useAppSelector } from 'app/store/storeHooks'; import IAIButton from 'common/components/IAIButton'; import IAIMantineTextInput from 'common/components/IAIMantineInput'; diff --git a/invokeai/frontend/web/src/features/ui/components/tabs/ModelManager/subpanels/ModelManagerPanel/ModelConvert.tsx b/invokeai/frontend/web/src/features/ui/components/tabs/ModelManager/subpanels/ModelManagerPanel/ModelConvert.tsx index 741afba025..1aec7d5c05 100644 --- a/invokeai/frontend/web/src/features/ui/components/tabs/ModelManager/subpanels/ModelManagerPanel/ModelConvert.tsx +++ b/invokeai/frontend/web/src/features/ui/components/tabs/ModelManager/subpanels/ModelManagerPanel/ModelConvert.tsx @@ -7,7 +7,7 @@ import { Tooltip, UnorderedList, } from '@chakra-ui/react'; -import { makeToast } from 'app/components/Toaster'; +import { makeToast } from 'features/system/util/makeToast'; // import { convertToDiffusers } from 'app/socketio/actions'; import { useAppDispatch } from 'app/store/storeHooks'; import IAIAlertDialog from 'common/components/IAIAlertDialog'; diff --git a/invokeai/frontend/web/src/features/ui/components/tabs/ModelManager/subpanels/ModelManagerPanel/ModelListItem.tsx b/invokeai/frontend/web/src/features/ui/components/tabs/ModelManager/subpanels/ModelManagerPanel/ModelListItem.tsx index 4de5131f65..7f4fb0c736 100644 --- a/invokeai/frontend/web/src/features/ui/components/tabs/ModelManager/subpanels/ModelManagerPanel/ModelListItem.tsx +++ b/invokeai/frontend/web/src/features/ui/components/tabs/ModelManager/subpanels/ModelManagerPanel/ModelListItem.tsx @@ -1,6 +1,6 @@ import { DeleteIcon } from '@chakra-ui/icons'; import { Badge, Flex, Text, Tooltip } from '@chakra-ui/react'; -import { makeToast } from 'app/components/Toaster'; +import { makeToast } from 'features/system/util/makeToast'; import { useAppDispatch, useAppSelector } from 'app/store/storeHooks'; import IAIAlertDialog from 'common/components/IAIAlertDialog'; import IAIButton from 'common/components/IAIButton'; diff --git a/invokeai/frontend/web/src/features/ui/components/tabs/ModelManager/subpanels/ModelManagerSettingsPanel/SyncModelsButton.tsx b/invokeai/frontend/web/src/features/ui/components/tabs/ModelManager/subpanels/ModelManagerSettingsPanel/SyncModelsButton.tsx index e42794c0b4..6405fba1a7 100644 --- a/invokeai/frontend/web/src/features/ui/components/tabs/ModelManager/subpanels/ModelManagerSettingsPanel/SyncModelsButton.tsx +++ b/invokeai/frontend/web/src/features/ui/components/tabs/ModelManager/subpanels/ModelManagerSettingsPanel/SyncModelsButton.tsx @@ -1,4 +1,4 @@ -import { makeToast } from 'app/components/Toaster'; +import { makeToast } from 'features/system/util/makeToast'; import { useAppDispatch } from 'app/store/storeHooks'; import IAIButton from 'common/components/IAIButton'; import IAIIconButton from 'common/components/IAIIconButton'; diff --git a/invokeai/frontend/web/src/features/ui/store/hotkeysSlice.ts b/invokeai/frontend/web/src/features/ui/store/hotkeysSlice.ts index 527e0b1740..2c16d6b5f4 100644 --- a/invokeai/frontend/web/src/features/ui/store/hotkeysSlice.ts +++ b/invokeai/frontend/web/src/features/ui/store/hotkeysSlice.ts @@ -1,6 +1,5 @@ import type { PayloadAction } from '@reduxjs/toolkit'; import { createSlice } from '@reduxjs/toolkit'; -import { RootState } from 'app/store/store'; type HotkeysState = { shift: boolean; @@ -23,5 +22,3 @@ export const hotkeysSlice = createSlice({ export const { shiftKeyPressed } = hotkeysSlice.actions; export default hotkeysSlice.reducer; - -export const hotkeysSelector = (state: RootState) => state.hotkeys; diff --git a/invokeai/frontend/web/src/features/ui/store/uiSlice.ts b/invokeai/frontend/web/src/features/ui/store/uiSlice.ts index c653e40368..81243aa03f 100644 --- a/invokeai/frontend/web/src/features/ui/store/uiSlice.ts +++ b/invokeai/frontend/web/src/features/ui/store/uiSlice.ts @@ -19,8 +19,6 @@ export const initialUIState: UIState = { shouldHidePreview: false, shouldShowProgressInViewer: true, shouldShowEmbeddingPicker: false, - shouldShowAdvancedOptions: false, - aspectRatio: null, favoriteSchedulers: [], }; @@ -98,12 +96,6 @@ export const uiSlice = createSlice({ toggleEmbeddingPicker: (state) => { state.shouldShowEmbeddingPicker = !state.shouldShowEmbeddingPicker; }, - setShouldShowAdvancedOptions: (state, action: PayloadAction) => { - state.shouldShowAdvancedOptions = action.payload; - }, - setAspectRatio: (state, action: PayloadAction) => { - state.aspectRatio = action.payload; - }, }, extraReducers(builder) { builder.addCase(initialImageChanged, (state) => { @@ -130,8 +122,6 @@ export const { setShouldShowProgressInViewer, favoriteSchedulersChanged, toggleEmbeddingPicker, - setShouldShowAdvancedOptions, - setAspectRatio, } = uiSlice.actions; export default uiSlice.reducer; diff --git a/invokeai/frontend/web/src/features/ui/store/uiTypes.ts b/invokeai/frontend/web/src/features/ui/store/uiTypes.ts index 325e8e898f..71c83b1630 100644 --- a/invokeai/frontend/web/src/features/ui/store/uiTypes.ts +++ b/invokeai/frontend/web/src/features/ui/store/uiTypes.ts @@ -25,7 +25,5 @@ export interface UIState { shouldShowGallery: boolean; shouldShowProgressInViewer: boolean; shouldShowEmbeddingPicker: boolean; - shouldShowAdvancedOptions: boolean; - aspectRatio: number | null; favoriteSchedulers: SchedulerParam[]; } diff --git a/invokeai/frontend/web/src/services/api/endpoints/boards.ts b/invokeai/frontend/web/src/services/api/endpoints/boards.ts index 779e5708fe..73b894b492 100644 --- a/invokeai/frontend/web/src/services/api/endpoints/boards.ts +++ b/invokeai/frontend/web/src/services/api/endpoints/boards.ts @@ -2,8 +2,7 @@ import { Update } from '@reduxjs/toolkit'; import { ASSETS_CATEGORIES, IMAGE_CATEGORIES, - boardIdSelected, -} from 'features/gallery/store/gallerySlice'; +} from 'features/gallery/store/types'; import { BoardDTO, ImageDTO, diff --git a/invokeai/frontend/web/src/services/api/endpoints/images.ts b/invokeai/frontend/web/src/services/api/endpoints/images.ts index 56b9e14a88..e8740a418b 100644 --- a/invokeai/frontend/web/src/services/api/endpoints/images.ts +++ b/invokeai/frontend/web/src/services/api/endpoints/images.ts @@ -5,8 +5,7 @@ import { ASSETS_CATEGORIES, BoardId, IMAGE_CATEGORIES, -} from 'features/gallery/store/gallerySlice'; -import { getCategories } from 'features/gallery/store/util'; +} from 'features/gallery/store/types'; import queryString from 'query-string'; import { ApiFullTagDescription, api } from '..'; import { components, paths } from '../schema'; @@ -16,7 +15,37 @@ import { OffsetPaginatedResults_ImageDTO_, PostUploadAction, } from '../types'; -import { getIsImageInDateRange } from './util'; + +const getIsImageInDateRange = ( + data: ImageCache | undefined, + imageDTO: ImageDTO +) => { + if (!data) { + return false; + } + const cacheImageDTOS = imagesSelectors.selectAll(data); + + if (cacheImageDTOS.length > 1) { + // Images are sorted by `created_at` DESC + // check if the image is newer than the oldest image in the cache + const createdDate = new Date(imageDTO.created_at); + const oldestDate = new Date( + cacheImageDTOS[cacheImageDTOS.length - 1].created_at + ); + return createdDate >= oldestDate; + } else if ([0, 1].includes(cacheImageDTOS.length)) { + // if there are only 1 or 0 images in the cache, we consider the image to be in the date range + return true; + } + return false; +}; + +const getCategories = (imageDTO: ImageDTO) => { + if (IMAGE_CATEGORIES.includes(imageDTO.image_category)) { + return IMAGE_CATEGORIES; + } + return ASSETS_CATEGORIES; +}; export type ListImagesArgs = NonNullable< paths['/api/v1/images/']['get']['parameters']['query'] diff --git a/invokeai/frontend/web/src/services/api/endpoints/util.ts b/invokeai/frontend/web/src/services/api/endpoints/util.ts deleted file mode 100644 index 0172c1af44..0000000000 --- a/invokeai/frontend/web/src/services/api/endpoints/util.ts +++ /dev/null @@ -1,51 +0,0 @@ -import { ImageDTO } from '../types'; -import { ImageCache, imagesSelectors } from './images'; - -export const getIsImageInDateRange = ( - data: ImageCache | undefined, - imageDTO: ImageDTO -) => { - if (!data) { - return false; - } - const cacheImageDTOS = imagesSelectors.selectAll(data); - - if (cacheImageDTOS.length > 1) { - // Images are sorted by `created_at` DESC - // check if the image is newer than the oldest image in the cache - const createdDate = new Date(imageDTO.created_at); - const oldestDate = new Date( - cacheImageDTOS[cacheImageDTOS.length - 1].created_at - ); - return createdDate >= oldestDate; - } else if ([0, 1].includes(cacheImageDTOS.length)) { - // if there are only 1 or 0 images in the cache, we consider the image to be in the date range - return true; - } - return false; -}; - -// /** -// * Determines the action we should take when an image may need to be added or updated in a cache. -// */ -// export const getCacheAction = ( -// data: ImageCache | undefined, -// imageDTO: ImageDTO -// ): 'add' | 'update' | 'none' => { -// const isInDateRange = getIsImageInDateRange(data, imageDTO); -// const isCacheFullyPopulated = data && data.total === data.ids.length; -// const shouldUpdateCache = -// Boolean(isInDateRange) || Boolean(isCacheFullyPopulated); - -// const isImageInCache = data && data.ids.includes(imageDTO.image_name); - -// if (shouldUpdateCache && isImageInCache) { -// return 'update'; -// } - -// if (shouldUpdateCache && !isImageInCache) { -// return 'add'; -// } - -// return 'none'; -// }; diff --git a/invokeai/frontend/web/src/services/api/hooks/useBoardName.ts b/invokeai/frontend/web/src/services/api/hooks/useBoardName.ts index d6b010e3ab..748f2c8f6e 100644 --- a/invokeai/frontend/web/src/services/api/hooks/useBoardName.ts +++ b/invokeai/frontend/web/src/services/api/hooks/useBoardName.ts @@ -1,4 +1,4 @@ -import { BoardId } from 'features/gallery/store/gallerySlice'; +import { BoardId } from 'features/gallery/store/types'; import { useListAllBoardsQuery } from '../endpoints/boards'; export const useBoardName = (board_id: BoardId | null | undefined) => { diff --git a/invokeai/frontend/web/src/services/api/hooks/useBoardTotal.ts b/invokeai/frontend/web/src/services/api/hooks/useBoardTotal.ts index 1a9e69ff2d..dd144ffe00 100644 --- a/invokeai/frontend/web/src/services/api/hooks/useBoardTotal.ts +++ b/invokeai/frontend/web/src/services/api/hooks/useBoardTotal.ts @@ -1,5 +1,5 @@ import { useAppSelector } from 'app/store/storeHooks'; -import { BoardId } from 'features/gallery/store/gallerySlice'; +import { BoardId } from 'features/gallery/store/types'; import { useMemo } from 'react'; import { useGetBoardAssetsTotalQuery, diff --git a/invokeai/frontend/web/src/services/api/thunks/session.ts b/invokeai/frontend/web/src/services/api/thunks/session.ts index 1b4be92bc3..6d20b9dd33 100644 --- a/invokeai/frontend/web/src/services/api/thunks/session.ts +++ b/invokeai/frontend/web/src/services/api/thunks/session.ts @@ -1,5 +1,4 @@ -import { isAnyOf } from '@reduxjs/toolkit'; -import { createAppAsyncThunk } from 'app/store/storeUtils'; +import { createAsyncThunk, isAnyOf } from '@reduxjs/toolkit'; import { isObject } from 'lodash-es'; import { $client } from 'services/api/client'; import { paths } from 'services/api/schema'; @@ -25,7 +24,7 @@ type CreateSessionThunkConfig = { /** * `SessionsService.createSession()` thunk */ -export const sessionCreated = createAppAsyncThunk< +export const sessionCreated = createAsyncThunk< CreateSessionResponse, CreateSessionArg, CreateSessionThunkConfig @@ -63,7 +62,7 @@ const isErrorWithStatus = (error: unknown): error is { status: number } => /** * `SessionsService.invokeSession()` thunk */ -export const sessionInvoked = createAppAsyncThunk< +export const sessionInvoked = createAsyncThunk< InvokedSessionResponse, InvokedSessionArg, InvokedSessionThunkConfig @@ -101,7 +100,7 @@ type CancelSessionThunkConfig = { /** * `SessionsService.cancelSession()` thunk */ -export const sessionCanceled = createAppAsyncThunk< +export const sessionCanceled = createAsyncThunk< CancelSessionResponse, CancelSessionArg, CancelSessionThunkConfig @@ -141,7 +140,7 @@ type ListSessionsThunkConfig = { /** * `SessionsService.listSessions()` thunk */ -export const listedSessions = createAppAsyncThunk< +export const listedSessions = createAsyncThunk< ListSessionsResponse, ListSessionsArg, ListSessionsThunkConfig diff --git a/invokeai/frontend/web/src/services/events/util/setEventListeners.ts b/invokeai/frontend/web/src/services/events/util/setEventListeners.ts index eaa5596e37..a13c128811 100644 --- a/invokeai/frontend/web/src/services/events/util/setEventListeners.ts +++ b/invokeai/frontend/web/src/services/events/util/setEventListeners.ts @@ -1,9 +1,9 @@ import { MiddlewareAPI } from '@reduxjs/toolkit'; import { logger } from 'app/logging/logger'; import { AppDispatch, RootState } from 'app/store/store'; +import { addToast } from 'features/system/store/systemSlice'; +import { makeToast } from 'features/system/util/makeToast'; import { Socket } from 'socket.io-client'; -import { makeToast } from '../../../app/components/Toaster'; -import { addToast } from '../../../features/system/store/systemSlice'; import { socketConnected, socketDisconnected,