diff --git a/invokeai/frontend/web/package.json b/invokeai/frontend/web/package.json
index da5e58c9c9..5d704f67d2 100644
--- a/invokeai/frontend/web/package.json
+++ b/invokeai/frontend/web/package.json
@@ -99,6 +99,7 @@
"redux-deep-persist": "^1.0.7",
"redux-dynamic-middlewares": "^2.2.0",
"redux-persist": "^6.0.0",
+ "redux-remember": "^3.2.1",
"roarr": "^7.15.0",
"serialize-error": "^11.0.0",
"socket.io-client": "^4.6.0",
diff --git a/invokeai/frontend/web/src/app/components/InvokeAIUI.tsx b/invokeai/frontend/web/src/app/components/InvokeAIUI.tsx
index 97a8be6fc1..d9d99de6cf 100644
--- a/invokeai/frontend/web/src/app/components/InvokeAIUI.tsx
+++ b/invokeai/frontend/web/src/app/components/InvokeAIUI.tsx
@@ -1,8 +1,6 @@
import React, { lazy, memo, PropsWithChildren, useEffect } from 'react';
import { Provider } from 'react-redux';
-import { PersistGate } from 'redux-persist/integration/react';
import { store } from 'app/store/store';
-import { persistor } from '../store/persistor';
import { OpenAPI } from 'services/api';
import '@fontsource/inter/100.css';
import '@fontsource/inter/200.css';
@@ -57,13 +55,11 @@ const InvokeAIUI = ({ apiUrl, token, config, children }: Props) => {
return (
- } persistor={persistor}>
- }>
-
- {children}
-
-
-
+ }>
+
+ {children}
+
+
);
diff --git a/invokeai/frontend/web/src/app/store/actions.ts b/invokeai/frontend/web/src/app/store/actions.ts
index e69de29bb2..e07920ce0c 100644
--- a/invokeai/frontend/web/src/app/store/actions.ts
+++ b/invokeai/frontend/web/src/app/store/actions.ts
@@ -0,0 +1,4 @@
+import { createAction } from '@reduxjs/toolkit';
+import { InvokeTabName } from 'features/ui/store/tabMap';
+
+export const userInvoked = createAction('app/userInvoked');
diff --git a/invokeai/frontend/web/src/app/store/constants.ts b/invokeai/frontend/web/src/app/store/constants.ts
new file mode 100644
index 0000000000..6d48762bef
--- /dev/null
+++ b/invokeai/frontend/web/src/app/store/constants.ts
@@ -0,0 +1,8 @@
+export const LOCALSTORAGE_KEYS = [
+ 'chakra-ui-color-mode',
+ 'i18nextLng',
+ 'ROARR_FILTER',
+ 'ROARR_LOG',
+];
+
+export const LOCALSTORAGE_PREFIX = '@@invokeai-';
diff --git a/invokeai/frontend/web/src/app/store/enhancers/reduxRemember/serialize.ts b/invokeai/frontend/web/src/app/store/enhancers/reduxRemember/serialize.ts
new file mode 100644
index 0000000000..52995e0da3
--- /dev/null
+++ b/invokeai/frontend/web/src/app/store/enhancers/reduxRemember/serialize.ts
@@ -0,0 +1,36 @@
+import { canvasPersistDenylist } from 'features/canvas/store/canvasPersistDenylist';
+import { galleryPersistDenylist } from 'features/gallery/store/galleryPersistDenylist';
+import { resultsPersistDenylist } from 'features/gallery/store/resultsPersistDenylist';
+import { uploadsPersistDenylist } from 'features/gallery/store/uploadsPersistDenylist';
+import { lightboxPersistDenylist } from 'features/lightbox/store/lightboxPersistDenylist';
+import { nodesPersistDenylist } from 'features/nodes/store/nodesPersistDenylist';
+import { generationPersistDenylist } from 'features/parameters/store/generationPersistDenylist';
+import { postprocessingPersistDenylist } from 'features/parameters/store/postprocessingPersistDenylist';
+import { modelsPersistDenylist } from 'features/system/store/modelsPersistDenylist';
+import { systemPersistDenylist } from 'features/system/store/systemPersistDenylist';
+import { uiPersistDenylist } from 'features/ui/store/uiPersistDenylist';
+import { omit } from 'lodash-es';
+import { SerializeFunction } from 'redux-remember';
+
+const serializationDenylist: {
+ [key: string]: string[];
+} = {
+ canvas: canvasPersistDenylist,
+ gallery: galleryPersistDenylist,
+ generation: generationPersistDenylist,
+ lightbox: lightboxPersistDenylist,
+ models: modelsPersistDenylist,
+ nodes: nodesPersistDenylist,
+ postprocessing: postprocessingPersistDenylist,
+ results: resultsPersistDenylist,
+ system: systemPersistDenylist,
+ // config: configPersistDenyList,
+ ui: uiPersistDenylist,
+ uploads: uploadsPersistDenylist,
+ // hotkeys: hotkeysPersistDenylist,
+};
+
+export const serialize: SerializeFunction = (data, key) => {
+ const result = omit(data, serializationDenylist[key]);
+ return JSON.stringify(result);
+};
diff --git a/invokeai/frontend/web/src/app/store/enhancers/reduxRemember/unserialize.ts b/invokeai/frontend/web/src/app/store/enhancers/reduxRemember/unserialize.ts
new file mode 100644
index 0000000000..d775e06187
--- /dev/null
+++ b/invokeai/frontend/web/src/app/store/enhancers/reduxRemember/unserialize.ts
@@ -0,0 +1,49 @@
+import { canvasPersistDenylist } from 'features/canvas/store/canvasPersistDenylist';
+import { initialCanvasState } from 'features/canvas/store/canvasSlice';
+import { galleryPersistDenylist } from 'features/gallery/store/galleryPersistDenylist';
+import { initialGalleryState } from 'features/gallery/store/gallerySlice';
+import { resultsPersistDenylist } from 'features/gallery/store/resultsPersistDenylist';
+import { initialResultsState } from 'features/gallery/store/resultsSlice';
+import { uploadsPersistDenylist } from 'features/gallery/store/uploadsPersistDenylist';
+import { initialUploadsState } from 'features/gallery/store/uploadsSlice';
+import { lightboxPersistDenylist } from 'features/lightbox/store/lightboxPersistDenylist';
+import { initialLightboxState } from 'features/lightbox/store/lightboxSlice';
+import { nodesPersistDenylist } from 'features/nodes/store/nodesPersistDenylist';
+import { initialNodesState } from 'features/nodes/store/nodesSlice';
+import { generationPersistDenylist } from 'features/parameters/store/generationPersistDenylist';
+import { initialGenerationState } from 'features/parameters/store/generationSlice';
+import { postprocessingPersistDenylist } from 'features/parameters/store/postprocessingPersistDenylist';
+import { initialPostprocessingState } from 'features/parameters/store/postprocessingSlice';
+import { initialConfigState } from 'features/system/store/configSlice';
+import { initialModelsState } from 'features/system/store/modelSlice';
+import { modelsPersistDenylist } from 'features/system/store/modelsPersistDenylist';
+import { systemPersistDenylist } from 'features/system/store/systemPersistDenylist';
+import { initialSystemState } from 'features/system/store/systemSlice';
+import { initialHotkeysState } from 'features/ui/store/hotkeysSlice';
+import { uiPersistDenylist } from 'features/ui/store/uiPersistDenylist';
+import { initialUIState } from 'features/ui/store/uiSlice';
+import { defaultsDeep, merge, omit } from 'lodash-es';
+import { UnserializeFunction } from 'redux-remember';
+
+const initialStates: {
+ [key: string]: any;
+} = {
+ canvas: initialCanvasState,
+ gallery: initialGalleryState,
+ generation: initialGenerationState,
+ lightbox: initialLightboxState,
+ models: initialModelsState,
+ nodes: initialNodesState,
+ postprocessing: initialPostprocessingState,
+ results: initialResultsState,
+ system: initialSystemState,
+ config: initialConfigState,
+ ui: initialUIState,
+ uploads: initialUploadsState,
+ hotkeys: initialHotkeysState,
+};
+
+export const unserialize: UnserializeFunction = (data, key) => {
+ const result = defaultsDeep(JSON.parse(data), initialStates[key]);
+ return result;
+};
diff --git a/invokeai/frontend/web/src/app/store/middleware/devtools/actionSanitizer.ts b/invokeai/frontend/web/src/app/store/middleware/devtools/actionSanitizer.ts
new file mode 100644
index 0000000000..c9d8e45886
--- /dev/null
+++ b/invokeai/frontend/web/src/app/store/middleware/devtools/actionSanitizer.ts
@@ -0,0 +1,29 @@
+import { AnyAction } from '@reduxjs/toolkit';
+import { isAnyGraphBuilt } from 'features/nodes/store/actions';
+import { forEach } from 'lodash-es';
+import { Graph } from 'services/api';
+
+export const actionSanitizer = (action: A): A => {
+ if (isAnyGraphBuilt(action)) {
+ if (action.payload.nodes) {
+ const sanitizedNodes: Graph['nodes'] = {};
+
+ // Sanitize nodes as needed
+ forEach(action.payload.nodes, (node, key) => {
+ if (node.type === 'dataURL_image') {
+ const { dataURL, ...rest } = node;
+ sanitizedNodes[key] = { ...rest, dataURL: '' };
+ } else {
+ sanitizedNodes[key] = { ...node };
+ }
+ });
+
+ return {
+ ...action,
+ payload: { ...action.payload, nodes: sanitizedNodes },
+ };
+ }
+ }
+
+ return action;
+};
diff --git a/invokeai/frontend/web/src/app/store/middleware/devtools/actionsDenylist.ts b/invokeai/frontend/web/src/app/store/middleware/devtools/actionsDenylist.ts
new file mode 100644
index 0000000000..743537d7ea
--- /dev/null
+++ b/invokeai/frontend/web/src/app/store/middleware/devtools/actionsDenylist.ts
@@ -0,0 +1,11 @@
+export const actionsDenylist = [
+ 'canvas/setCursorPosition',
+ 'canvas/setStageCoordinates',
+ 'canvas/setStageScale',
+ 'canvas/setIsDrawing',
+ 'canvas/setBoundingBoxCoordinates',
+ 'canvas/setBoundingBoxDimensions',
+ 'canvas/setIsDrawing',
+ 'canvas/addPointToCurrentLine',
+ 'socket/generatorProgress',
+];
diff --git a/invokeai/frontend/web/src/app/store/middleware/devtools/stateSanitizer.ts b/invokeai/frontend/web/src/app/store/middleware/devtools/stateSanitizer.ts
new file mode 100644
index 0000000000..312b4db189
--- /dev/null
+++ b/invokeai/frontend/web/src/app/store/middleware/devtools/stateSanitizer.ts
@@ -0,0 +1,3 @@
+export const stateSanitizer = (state: S): S => {
+ return state;
+};
diff --git a/invokeai/frontend/web/src/app/store/middleware/listenerMiddleware/index.ts b/invokeai/frontend/web/src/app/store/middleware/listenerMiddleware/index.ts
index 5869038d6a..6e66e19780 100644
--- a/invokeai/frontend/web/src/app/store/middleware/listenerMiddleware/index.ts
+++ b/invokeai/frontend/web/src/app/store/middleware/listenerMiddleware/index.ts
@@ -11,12 +11,9 @@ import { addInitialImageSelectedListener } from './listeners/initialImageSelecte
import { addImageResultReceivedListener } from './listeners/invocationComplete';
import { addImageUploadedListener } from './listeners/imageUploaded';
import { addRequestedImageDeletionListener } from './listeners/imageDeleted';
-import {
- addUserInvokedCanvasListener,
- addUserInvokedCreateListener,
- addUserInvokedNodesListener,
-} from './listeners/userInvoked';
-import { addCanvasGraphBuiltListener } from './listeners/canvasGraphBuilt';
+import { addUserInvokedCanvasListener } from './listeners/userInvokedCanvas';
+import { addUserInvokedCreateListener } from './listeners/userInvokedCreate';
+import { addUserInvokedNodesListener } from './listeners/userInvokedNodes';
export const listenerMiddleware = createListenerMiddleware();
@@ -40,7 +37,7 @@ addImageUploadedListener();
addInitialImageSelectedListener();
addImageResultReceivedListener();
addRequestedImageDeletionListener();
+
addUserInvokedCanvasListener();
addUserInvokedCreateListener();
addUserInvokedNodesListener();
-// addCanvasGraphBuiltListener();
diff --git a/invokeai/frontend/web/src/app/store/middleware/listenerMiddleware/listeners/userInvoked.ts b/invokeai/frontend/web/src/app/store/middleware/listenerMiddleware/listeners/userInvokedCanvas.ts
similarity index 71%
rename from invokeai/frontend/web/src/app/store/middleware/listenerMiddleware/listeners/userInvoked.ts
rename to invokeai/frontend/web/src/app/store/middleware/listenerMiddleware/listeners/userInvokedCanvas.ts
index 63da80440e..2490a358fe 100644
--- a/invokeai/frontend/web/src/app/store/middleware/listenerMiddleware/listeners/userInvoked.ts
+++ b/invokeai/frontend/web/src/app/store/middleware/listenerMiddleware/listeners/userInvokedCanvas.ts
@@ -1,16 +1,8 @@
-import { createAction } from '@reduxjs/toolkit';
import { startAppListening } from '..';
-import { InvokeTabName } from 'features/ui/store/tabMap';
-import { buildLinearGraph } from 'features/nodes/util/buildLinearGraph';
import { sessionCreated, sessionInvoked } from 'services/thunks/session';
import { buildCanvasGraphAndBlobs } from 'features/nodes/util/buildCanvasGraph';
-import { buildNodesGraph } from 'features/nodes/util/buildNodesGraph';
import { log } from 'app/logging/useLogger';
-import {
- canvasGraphBuilt,
- createGraphBuilt,
- nodesGraphBuilt,
-} from 'features/nodes/store/actions';
+import { canvasGraphBuilt } from 'features/nodes/store/actions';
import { imageUploaded } from 'services/thunks/image';
import { v4 as uuidv4 } from 'uuid';
import { Graph } from 'services/api';
@@ -18,27 +10,10 @@ import {
canvasSessionIdChanged,
stagingAreaInitialized,
} from 'features/canvas/store/canvasSlice';
+import { userInvoked } from 'app/store/actions';
const moduleLog = log.child({ namespace: 'invoke' });
-export const userInvoked = createAction('app/userInvoked');
-
-export const addUserInvokedCreateListener = () => {
- startAppListening({
- predicate: (action): action is ReturnType =>
- userInvoked.match(action) && action.payload === 'generate',
- effect: (action, { getState, dispatch }) => {
- const state = getState();
-
- const graph = buildLinearGraph(state);
- dispatch(createGraphBuilt(graph));
- moduleLog({ data: graph }, 'Create graph built');
-
- dispatch(sessionCreated({ graph }));
- },
- });
-};
-
export const addUserInvokedCanvasListener = () => {
startAppListening({
predicate: (action): action is ReturnType =>
@@ -149,19 +124,3 @@ export const addUserInvokedCanvasListener = () => {
},
});
};
-
-export const addUserInvokedNodesListener = () => {
- startAppListening({
- predicate: (action): action is ReturnType =>
- userInvoked.match(action) && action.payload === 'nodes',
- effect: (action, { getState, dispatch }) => {
- const state = getState();
-
- const graph = buildNodesGraph(state);
- dispatch(nodesGraphBuilt(graph));
- moduleLog({ data: graph }, 'Nodes graph built');
-
- dispatch(sessionCreated({ graph }));
- },
- });
-};
diff --git a/invokeai/frontend/web/src/app/store/middleware/listenerMiddleware/listeners/userInvokedCreate.ts b/invokeai/frontend/web/src/app/store/middleware/listenerMiddleware/listeners/userInvokedCreate.ts
new file mode 100644
index 0000000000..d24385ea97
--- /dev/null
+++ b/invokeai/frontend/web/src/app/store/middleware/listenerMiddleware/listeners/userInvokedCreate.ts
@@ -0,0 +1,24 @@
+import { startAppListening } from '..';
+import { buildLinearGraph } from 'features/nodes/util/buildLinearGraph';
+import { sessionCreated } from 'services/thunks/session';
+import { log } from 'app/logging/useLogger';
+import { createGraphBuilt } from 'features/nodes/store/actions';
+import { userInvoked } from 'app/store/actions';
+
+const moduleLog = log.child({ namespace: 'invoke' });
+
+export const addUserInvokedCreateListener = () => {
+ startAppListening({
+ predicate: (action): action is ReturnType =>
+ userInvoked.match(action) && action.payload === 'generate',
+ effect: (action, { getState, dispatch }) => {
+ const state = getState();
+
+ const graph = buildLinearGraph(state);
+ dispatch(createGraphBuilt(graph));
+ moduleLog({ data: graph }, 'Create graph built');
+
+ dispatch(sessionCreated({ graph }));
+ },
+ });
+};
diff --git a/invokeai/frontend/web/src/app/store/middleware/listenerMiddleware/listeners/userInvokedNodes.ts b/invokeai/frontend/web/src/app/store/middleware/listenerMiddleware/listeners/userInvokedNodes.ts
new file mode 100644
index 0000000000..baf7bc5baf
--- /dev/null
+++ b/invokeai/frontend/web/src/app/store/middleware/listenerMiddleware/listeners/userInvokedNodes.ts
@@ -0,0 +1,24 @@
+import { startAppListening } from '..';
+import { sessionCreated } from 'services/thunks/session';
+import { buildNodesGraph } from 'features/nodes/util/buildNodesGraph';
+import { log } from 'app/logging/useLogger';
+import { nodesGraphBuilt } from 'features/nodes/store/actions';
+import { userInvoked } from 'app/store/actions';
+
+const moduleLog = log.child({ namespace: 'invoke' });
+
+export const addUserInvokedNodesListener = () => {
+ startAppListening({
+ predicate: (action): action is ReturnType =>
+ userInvoked.match(action) && action.payload === 'nodes',
+ effect: (action, { getState, dispatch }) => {
+ const state = getState();
+
+ const graph = buildNodesGraph(state);
+ dispatch(nodesGraphBuilt(graph));
+ moduleLog({ data: graph }, 'Nodes graph built');
+
+ dispatch(sessionCreated({ graph }));
+ },
+ });
+};
diff --git a/invokeai/frontend/web/src/app/store/persistor.ts b/invokeai/frontend/web/src/app/store/persistor.ts
deleted file mode 100644
index 85dc934943..0000000000
--- a/invokeai/frontend/web/src/app/store/persistor.ts
+++ /dev/null
@@ -1,4 +0,0 @@
-import { store } from 'app/store/store';
-import { persistStore } from 'redux-persist';
-
-export const persistor = persistStore(store);
diff --git a/invokeai/frontend/web/src/app/store/store.ts b/invokeai/frontend/web/src/app/store/store.ts
index 15eb045405..777f563cae 100644
--- a/invokeai/frontend/web/src/app/store/store.ts
+++ b/invokeai/frontend/web/src/app/store/store.ts
@@ -1,16 +1,13 @@
import {
- Action,
AnyAction,
+ Store,
ThunkDispatch,
combineReducers,
configureStore,
- isAnyOf,
} from '@reduxjs/toolkit';
-import { persistReducer } from 'redux-persist';
-import storage from 'redux-persist/lib/storage'; // defaults to localStorage for web
+import { rememberReducer, rememberEnhancer } from 'redux-remember';
import dynamicMiddlewares from 'redux-dynamic-middlewares';
-import { getPersistConfig } from 'redux-deep-persist';
import canvasReducer from 'features/canvas/store/canvasSlice';
import galleryReducer from 'features/gallery/store/gallerySlice';
@@ -26,35 +23,17 @@ import hotkeysReducer from 'features/ui/store/hotkeysSlice';
import modelsReducer from 'features/system/store/modelSlice';
import nodesReducer from 'features/nodes/store/nodesSlice';
-import { canvasDenylist } from 'features/canvas/store/canvasPersistDenylist';
-import { galleryDenylist } from 'features/gallery/store/galleryPersistDenylist';
-import { generationDenylist } from 'features/parameters/store/generationPersistDenylist';
-import { lightboxDenylist } from 'features/lightbox/store/lightboxPersistDenylist';
-import { modelsDenylist } from 'features/system/store/modelsPersistDenylist';
-import { nodesDenylist } from 'features/nodes/store/nodesPersistDenylist';
-import { postprocessingDenylist } from 'features/parameters/store/postprocessingPersistDenylist';
-import { systemDenylist } from 'features/system/store/systemPersistDenylist';
-import { uiDenylist } from 'features/ui/store/uiPersistDenylist';
import { listenerMiddleware } from './middleware/listenerMiddleware';
-import { isAnyGraphBuilt } from 'features/nodes/store/actions';
-import { forEach } from 'lodash-es';
-import { Graph } from 'services/api';
-/**
- * redux-persist provides an easy and reliable way to persist state across reloads.
- *
- * While we definitely want generation parameters to be persisted, there are a number
- * of things we do *not* want to be persisted across reloads:
- * - Gallery/selected image (user may add/delete images from disk between page loads)
- * - Connection/processing status
- * - Availability of external libraries like ESRGAN/GFPGAN
- *
- * These can be denylisted in redux-persist.
- *
- * The necesssary nested persistors with denylists are configured below.
- */
+import { actionSanitizer } from './middleware/devtools/actionSanitizer';
+import { stateSanitizer } from './middleware/devtools/stateSanitizer';
+import { actionsDenylist } from './middleware/devtools/actionsDenylist';
-const rootReducer = combineReducers({
+import { serialize } from './enhancers/reduxRemember/serialize';
+import { unserialize } from './enhancers/reduxRemember/unserialize';
+import { LOCALSTORAGE_PREFIX } from './constants';
+
+const allReducers = {
canvas: canvasReducer,
gallery: galleryReducer,
generation: generationReducer,
@@ -68,65 +47,38 @@ const rootReducer = combineReducers({
ui: uiReducer,
uploads: uploadsReducer,
hotkeys: hotkeysReducer,
-});
+};
-const rootPersistConfig = getPersistConfig({
- key: 'root',
- storage,
- rootReducer,
- blacklist: [
- ...canvasDenylist,
- ...galleryDenylist,
- ...generationDenylist,
- ...lightboxDenylist,
- ...modelsDenylist,
- ...nodesDenylist,
- ...postprocessingDenylist,
- // ...resultsDenylist,
- 'results',
- ...systemDenylist,
- ...uiDenylist,
- // ...uploadsDenylist,
- 'uploads',
- 'hotkeys',
- 'config',
+const rootReducer = combineReducers(allReducers);
+
+const rememberedRootReducer = rememberReducer(rootReducer);
+
+const rememberedKeys: (keyof typeof allReducers)[] = [
+ 'canvas',
+ 'gallery',
+ 'generation',
+ 'lightbox',
+ // 'models',
+ 'nodes',
+ 'postprocessing',
+ 'system',
+ 'ui',
+ // 'hotkeys',
+ // 'results',
+ // 'uploads',
+ // 'config',
+];
+
+export const store: Store = configureStore({
+ reducer: rememberedRootReducer,
+ enhancers: [
+ rememberEnhancer(window.localStorage, rememberedKeys, {
+ persistDebounce: 300,
+ serialize,
+ unserialize,
+ prefix: LOCALSTORAGE_PREFIX,
+ }),
],
-});
-
-const persistedReducer = persistReducer(rootPersistConfig, rootReducer);
-
-// TODO: rip the old middleware out when nodes is complete
-// export function buildMiddleware() {
-// if (import.meta.env.MODE === 'nodes' || import.meta.env.MODE === 'package') {
-// return socketMiddleware();
-// } else {
-// return socketioMiddleware();
-// }
-// }
-
-// const actionSanitizer = (action: AnyAction): AnyAction => {
-// if (isAnyGraphBuilt(action)) {
-// if (action.payload.nodes) {
-// const sanitizedNodes: Graph['nodes'] = {};
-// forEach(action.payload.nodes, (node, key) => {
-// if (node.type === 'dataURL_image') {
-// const { dataURL, ...rest } = node;
-// sanitizedNodes[key] = { ...rest, dataURL: '<>' };
-// }
-// });
-// const sanitizedAction: AnyAction = {
-// ...action,
-// payload: { ...action.payload, nodes: sanitizedNodes },
-// };
-// return sanitizedAction;
-// }
-// }
-
-// return action;
-// };
-
-export const store = configureStore({
- reducer: persistedReducer,
middleware: (getDefaultMiddleware) =>
getDefaultMiddleware({
immutableCheck: false,
@@ -135,43 +87,10 @@ export const store = configureStore({
.concat(dynamicMiddlewares)
.prepend(listenerMiddleware.middleware),
devTools: {
- // Uncommenting these very rapidly called actions makes the redux dev tools output much more readable
- actionsDenylist: [
- 'canvas/setCursorPosition',
- 'canvas/setStageCoordinates',
- 'canvas/setStageScale',
- 'canvas/setIsDrawing',
- 'canvas/setBoundingBoxCoordinates',
- 'canvas/setBoundingBoxDimensions',
- 'canvas/setIsDrawing',
- 'canvas/addPointToCurrentLine',
- 'socket/generatorProgress',
- ],
- actionSanitizer: (action) => {
- if (isAnyGraphBuilt(action)) {
- if (action.payload.nodes) {
- const sanitizedNodes: Graph['nodes'] = {};
-
- forEach(action.payload.nodes, (node, key) => {
- if (node.type === 'dataURL_image') {
- const { dataURL, ...rest } = node;
- sanitizedNodes[key] = { ...rest, dataURL: '<>' };
- } else {
- sanitizedNodes[key] = { ...node };
- }
- });
-
- return {
- ...action,
- payload: { ...action.payload, nodes: sanitizedNodes },
- };
- }
- }
-
- return action;
- },
- // stateSanitizer: (state) =>
- // state.data ? { ...state, data: '<>' } : state,
+ actionsDenylist,
+ actionSanitizer,
+ stateSanitizer,
+ trace: true,
},
});
diff --git a/invokeai/frontend/web/src/features/canvas/store/canvasPersistDenylist.ts b/invokeai/frontend/web/src/features/canvas/store/canvasPersistDenylist.ts
index abaefab8b0..1f44b43021 100644
--- a/invokeai/frontend/web/src/features/canvas/store/canvasPersistDenylist.ts
+++ b/invokeai/frontend/web/src/features/canvas/store/canvasPersistDenylist.ts
@@ -9,6 +9,12 @@ const itemsToDenylist: (keyof CanvasState)[] = [
'doesCanvasNeedScaling',
];
+export const canvasPersistDenylist: (keyof CanvasState)[] = [
+ 'cursorPosition',
+ 'isCanvasInitialized',
+ 'doesCanvasNeedScaling',
+];
+
export const canvasDenylist = itemsToDenylist.map(
(denylistItem) => `canvas.${denylistItem}`
);
diff --git a/invokeai/frontend/web/src/features/canvas/store/canvasSlice.ts b/invokeai/frontend/web/src/features/canvas/store/canvasSlice.ts
index 438f4df9e1..67adcc1769 100644
--- a/invokeai/frontend/web/src/features/canvas/store/canvasSlice.ts
+++ b/invokeai/frontend/web/src/features/canvas/store/canvasSlice.ts
@@ -40,7 +40,7 @@ export const initialLayerState: CanvasLayerState = {
},
};
-const initialCanvasState: CanvasState = {
+export const initialCanvasState: CanvasState = {
boundingBoxCoordinates: { x: 0, y: 0 },
boundingBoxDimensions: { width: 512, height: 512 },
boundingBoxPreviewFill: { r: 0, g: 0, b: 0, a: 0.5 },
diff --git a/invokeai/frontend/web/src/features/gallery/store/galleryPersistDenylist.ts b/invokeai/frontend/web/src/features/gallery/store/galleryPersistDenylist.ts
index 8c91a79dfb..dcec4fa373 100644
--- a/invokeai/frontend/web/src/features/gallery/store/galleryPersistDenylist.ts
+++ b/invokeai/frontend/web/src/features/gallery/store/galleryPersistDenylist.ts
@@ -8,6 +8,11 @@ const itemsToDenylist: (keyof GalleryState)[] = [
'shouldAutoSwitchToNewImages',
];
+export const galleryPersistDenylist: (keyof GalleryState)[] = [
+ 'currentCategory',
+ 'shouldAutoSwitchToNewImages',
+];
+
export const galleryDenylist = itemsToDenylist.map(
(denylistItem) => `gallery.${denylistItem}`
);
diff --git a/invokeai/frontend/web/src/features/gallery/store/gallerySelectors.ts b/invokeai/frontend/web/src/features/gallery/store/gallerySelectors.ts
index 0fc8d300e9..082e644b43 100644
--- a/invokeai/frontend/web/src/features/gallery/store/gallerySelectors.ts
+++ b/invokeai/frontend/web/src/features/gallery/store/gallerySelectors.ts
@@ -44,6 +44,11 @@ export const imageGallerySelector = createSelector(
const { isLightboxOpen } = lightbox;
+ const images =
+ currentCategory === 'results'
+ ? selectResultsEntities(state)
+ : selectUploadsAll(state);
+
return {
shouldPinGallery,
galleryImageMinimumWidth,
@@ -53,7 +58,7 @@ export const imageGallerySelector = createSelector(
: `repeat(auto-fill, minmax(${galleryImageMinimumWidth}px, auto))`,
shouldAutoSwitchToNewImages,
currentCategory,
- images: state[currentCategory].entities,
+ images,
galleryWidth,
shouldEnableResize:
isLightboxOpen ||
diff --git a/invokeai/frontend/web/src/features/gallery/store/gallerySlice.ts b/invokeai/frontend/web/src/features/gallery/store/gallerySlice.ts
index 630bd0d6b3..2326295451 100644
--- a/invokeai/frontend/web/src/features/gallery/store/gallerySlice.ts
+++ b/invokeai/frontend/web/src/features/gallery/store/gallerySlice.ts
@@ -17,7 +17,7 @@ export interface GalleryState {
currentCategory: 'results' | 'uploads';
}
-const initialState: GalleryState = {
+export const initialGalleryState: GalleryState = {
galleryImageMinimumWidth: 64,
galleryImageObjectFit: 'cover',
shouldAutoSwitchToNewImages: true,
@@ -28,7 +28,7 @@ const initialState: GalleryState = {
export const gallerySlice = createSlice({
name: 'gallery',
- initialState,
+ initialState: initialGalleryState,
reducers: {
imageSelected: (state, action: PayloadAction) => {
state.selectedImage = action.payload;
diff --git a/invokeai/frontend/web/src/features/gallery/store/resultsPersistDenylist.ts b/invokeai/frontend/web/src/features/gallery/store/resultsPersistDenylist.ts
index ef21f4b7b2..4b8ccac6a7 100644
--- a/invokeai/frontend/web/src/features/gallery/store/resultsPersistDenylist.ts
+++ b/invokeai/frontend/web/src/features/gallery/store/resultsPersistDenylist.ts
@@ -7,6 +7,8 @@ import { ResultsState } from './resultsSlice';
*/
const itemsToDenylist: (keyof ResultsState)[] = [];
+export const resultsPersistDenylist: (keyof ResultsState)[] = [];
+
export const resultsDenylist = itemsToDenylist.map(
(denylistItem) => `results.${denylistItem}`
);
diff --git a/invokeai/frontend/web/src/features/gallery/store/uploadsPersistDenylist.ts b/invokeai/frontend/web/src/features/gallery/store/uploadsPersistDenylist.ts
index ec4248e99c..97e23660a9 100644
--- a/invokeai/frontend/web/src/features/gallery/store/uploadsPersistDenylist.ts
+++ b/invokeai/frontend/web/src/features/gallery/store/uploadsPersistDenylist.ts
@@ -6,6 +6,7 @@ import { UploadsState } from './uploadsSlice';
* Currently denylisting uploads slice entirely, see persist config in store.ts
*/
const itemsToDenylist: (keyof UploadsState)[] = [];
+export const uploadsPersistDenylist: (keyof UploadsState)[] = [];
export const uploadsDenylist = itemsToDenylist.map(
(denylistItem) => `uploads.${denylistItem}`
diff --git a/invokeai/frontend/web/src/features/gallery/store/uploadsSlice.ts b/invokeai/frontend/web/src/features/gallery/store/uploadsSlice.ts
index 6e39733f1b..fcad240058 100644
--- a/invokeai/frontend/web/src/features/gallery/store/uploadsSlice.ts
+++ b/invokeai/frontend/web/src/features/gallery/store/uploadsSlice.ts
@@ -21,7 +21,7 @@ type AdditionalUploadsState = {
nextPage: number;
};
-const initialUploadsState =
+export const initialUploadsState =
uploadsAdapter.getInitialState({
page: 0,
pages: 0,
diff --git a/invokeai/frontend/web/src/features/lightbox/store/lightboxPersistDenylist.ts b/invokeai/frontend/web/src/features/lightbox/store/lightboxPersistDenylist.ts
index 96cf69f373..194fe50ca3 100644
--- a/invokeai/frontend/web/src/features/lightbox/store/lightboxPersistDenylist.ts
+++ b/invokeai/frontend/web/src/features/lightbox/store/lightboxPersistDenylist.ts
@@ -4,6 +4,9 @@ import { LightboxState } from './lightboxSlice';
* Lightbox slice persist denylist
*/
const itemsToDenylist: (keyof LightboxState)[] = ['isLightboxOpen'];
+export const lightboxPersistDenylist: (keyof LightboxState)[] = [
+ 'isLightboxOpen',
+];
export const lightboxDenylist = itemsToDenylist.map(
(denylistItem) => `lightbox.${denylistItem}`
diff --git a/invokeai/frontend/web/src/features/lightbox/store/lightboxSlice.ts b/invokeai/frontend/web/src/features/lightbox/store/lightboxSlice.ts
index f7f6507d93..ea73e5bb13 100644
--- a/invokeai/frontend/web/src/features/lightbox/store/lightboxSlice.ts
+++ b/invokeai/frontend/web/src/features/lightbox/store/lightboxSlice.ts
@@ -5,7 +5,7 @@ export interface LightboxState {
isLightboxOpen: boolean;
}
-const initialLightboxState: LightboxState = {
+export const initialLightboxState: LightboxState = {
isLightboxOpen: false,
};
diff --git a/invokeai/frontend/web/src/features/nodes/components/panels/TopCenterPanel.tsx b/invokeai/frontend/web/src/features/nodes/components/panels/TopCenterPanel.tsx
index b5783891ee..b97bf423e1 100644
--- a/invokeai/frontend/web/src/features/nodes/components/panels/TopCenterPanel.tsx
+++ b/invokeai/frontend/web/src/features/nodes/components/panels/TopCenterPanel.tsx
@@ -1,5 +1,5 @@
import { HStack } from '@chakra-ui/react';
-import { userInvoked } from 'app/store/middleware/listenerMiddleware/listeners/userInvoked';
+import { userInvoked } from 'app/store/actions';
import { useAppDispatch } from 'app/store/storeHooks';
import IAIButton from 'common/components/IAIButton';
import { memo, useCallback } from 'react';
diff --git a/invokeai/frontend/web/src/features/nodes/store/nodesPersistDenylist.ts b/invokeai/frontend/web/src/features/nodes/store/nodesPersistDenylist.ts
index 31d859ba8b..36da41f56c 100644
--- a/invokeai/frontend/web/src/features/nodes/store/nodesPersistDenylist.ts
+++ b/invokeai/frontend/web/src/features/nodes/store/nodesPersistDenylist.ts
@@ -4,6 +4,10 @@ import { NodesState } from './nodesSlice';
* Nodes slice persist denylist
*/
const itemsToDenylist: (keyof NodesState)[] = ['schema', 'invocationTemplates'];
+export const nodesPersistDenylist: (keyof NodesState)[] = [
+ 'schema',
+ 'invocationTemplates',
+];
export const nodesDenylist = itemsToDenylist.map(
(denylistItem) => `nodes.${denylistItem}`
diff --git a/invokeai/frontend/web/src/features/parameters/components/ProcessButtons/InvokeButton.tsx b/invokeai/frontend/web/src/features/parameters/components/ProcessButtons/InvokeButton.tsx
index 5532fab196..68d607c0fa 100644
--- a/invokeai/frontend/web/src/features/parameters/components/ProcessButtons/InvokeButton.tsx
+++ b/invokeai/frontend/web/src/features/parameters/components/ProcessButtons/InvokeButton.tsx
@@ -1,6 +1,6 @@
import { Box } from '@chakra-ui/react';
import { readinessSelector } from 'app/selectors/readinessSelector';
-import { userInvoked } from 'app/store/middleware/listenerMiddleware/listeners/userInvoked';
+import { userInvoked } from 'app/store/actions';
import { useAppDispatch, useAppSelector } from 'app/store/storeHooks';
import IAIButton, { IAIButtonProps } from 'common/components/IAIButton';
import IAIIconButton, {
diff --git a/invokeai/frontend/web/src/features/parameters/components/PromptInput/PromptInput.tsx b/invokeai/frontend/web/src/features/parameters/components/PromptInput/PromptInput.tsx
index 868da4c8b1..eb0340b0ee 100644
--- a/invokeai/frontend/web/src/features/parameters/components/PromptInput/PromptInput.tsx
+++ b/invokeai/frontend/web/src/features/parameters/components/PromptInput/PromptInput.tsx
@@ -15,7 +15,7 @@ import { activeTabNameSelector } from 'features/ui/store/uiSelectors';
import { isEqual } from 'lodash-es';
import { useHotkeys } from 'react-hotkeys-hook';
import { useTranslation } from 'react-i18next';
-import { userInvoked } from 'app/store/middleware/listenerMiddleware/listeners/userInvoked';
+import { userInvoked } from 'app/store/actions';
const promptInputSelector = createSelector(
[(state: RootState) => state.generation, activeTabNameSelector],
diff --git a/invokeai/frontend/web/src/features/parameters/store/generationPersistDenylist.ts b/invokeai/frontend/web/src/features/parameters/store/generationPersistDenylist.ts
index 70f35aa564..ab3e77801e 100644
--- a/invokeai/frontend/web/src/features/parameters/store/generationPersistDenylist.ts
+++ b/invokeai/frontend/web/src/features/parameters/store/generationPersistDenylist.ts
@@ -4,6 +4,7 @@ import { GenerationState } from './generationSlice';
* Generation slice persist denylist
*/
const itemsToDenylist: (keyof GenerationState)[] = [];
+export const generationPersistDenylist: (keyof GenerationState)[] = [];
export const generationDenylist = itemsToDenylist.map(
(denylistItem) => `generation.${denylistItem}`
diff --git a/invokeai/frontend/web/src/features/parameters/store/generationSlice.ts b/invokeai/frontend/web/src/features/parameters/store/generationSlice.ts
index d889dd88e1..51627ce24c 100644
--- a/invokeai/frontend/web/src/features/parameters/store/generationSlice.ts
+++ b/invokeai/frontend/web/src/features/parameters/store/generationSlice.ts
@@ -38,9 +38,10 @@ export interface GenerationState {
horizontalSymmetrySteps: number;
verticalSymmetrySteps: number;
isImageToImageEnabled: boolean;
+ model: string;
}
-const initialGenerationState: GenerationState = {
+export const initialGenerationState: GenerationState = {
cfgScale: 7.5,
height: 512,
img2imgStrength: 0.75,
@@ -70,6 +71,7 @@ const initialGenerationState: GenerationState = {
horizontalSymmetrySteps: 0,
verticalSymmetrySteps: 0,
isImageToImageEnabled: false,
+ model: '',
};
const initialState: GenerationState = initialGenerationState;
@@ -353,6 +355,9 @@ export const generationSlice = createSlice({
isImageToImageEnabledChanged: (state, action: PayloadAction) => {
state.isImageToImageEnabled = action.payload;
},
+ modelSelected: (state, action: PayloadAction) => {
+ state.model = action.payload;
+ },
},
});
@@ -396,6 +401,7 @@ export const {
setVerticalSymmetrySteps,
initialImageChanged,
isImageToImageEnabledChanged,
+ modelSelected,
} = generationSlice.actions;
export default generationSlice.reducer;
diff --git a/invokeai/frontend/web/src/features/parameters/store/postprocessingPersistDenylist.ts b/invokeai/frontend/web/src/features/parameters/store/postprocessingPersistDenylist.ts
index 947a136964..a6ba084c2e 100644
--- a/invokeai/frontend/web/src/features/parameters/store/postprocessingPersistDenylist.ts
+++ b/invokeai/frontend/web/src/features/parameters/store/postprocessingPersistDenylist.ts
@@ -4,6 +4,7 @@ import { PostprocessingState } from './postprocessingSlice';
* Postprocessing slice persist denylist
*/
const itemsToDenylist: (keyof PostprocessingState)[] = [];
+export const postprocessingPersistDenylist: (keyof PostprocessingState)[] = [];
export const postprocessingDenylist = itemsToDenylist.map(
(denylistItem) => `postprocessing.${denylistItem}`
diff --git a/invokeai/frontend/web/src/features/parameters/store/postprocessingSlice.ts b/invokeai/frontend/web/src/features/parameters/store/postprocessingSlice.ts
index 60991d3673..399a474008 100644
--- a/invokeai/frontend/web/src/features/parameters/store/postprocessingSlice.ts
+++ b/invokeai/frontend/web/src/features/parameters/store/postprocessingSlice.ts
@@ -20,7 +20,7 @@ export interface PostprocessingState {
upscalingStrength: number;
}
-const initialPostprocessingState: PostprocessingState = {
+export const initialPostprocessingState: PostprocessingState = {
codeformerFidelity: 0.75,
facetoolStrength: 0.75,
facetoolType: 'gfpgan',
@@ -34,11 +34,9 @@ const initialPostprocessingState: PostprocessingState = {
upscalingStrength: 0.75,
};
-const initialState: PostprocessingState = initialPostprocessingState;
-
export const postprocessingSlice = createSlice({
name: 'postprocessing',
- initialState,
+ initialState: initialPostprocessingState,
reducers: {
setFacetoolStrength: (state, action: PayloadAction) => {
state.facetoolStrength = action.payload;
diff --git a/invokeai/frontend/web/src/features/system/components/ModelSelect.tsx b/invokeai/frontend/web/src/features/system/components/ModelSelect.tsx
index d0ad89ba36..d6c7c154e0 100644
--- a/invokeai/frontend/web/src/features/system/components/ModelSelect.tsx
+++ b/invokeai/frontend/web/src/features/system/components/ModelSelect.tsx
@@ -6,16 +6,22 @@ import { useTranslation } from 'react-i18next';
import { useAppDispatch, useAppSelector } from 'app/store/storeHooks';
import IAISelect from 'common/components/IAISelect';
import {
- modelSelected,
+ // modelSelected,
selectedModelSelector,
+ selectModelsById,
selectModelsIds,
} from '../store/modelSlice';
import { RootState } from 'app/store/store';
+import generationSlice, {
+ modelSelected,
+} from 'features/parameters/store/generationSlice';
+import { generationSelector } from 'features/parameters/store/generationSelectors';
const selector = createSelector(
- [(state: RootState) => state],
- (state) => {
- const selectedModel = selectedModelSelector(state);
+ [(state: RootState) => state, generationSelector],
+ (state, generation) => {
+ // const selectedModel = selectedModelSelector(state);
+ const selectedModel = selectModelsById(state, generation.model);
const allModelNames = selectModelsIds(state);
return {
allModelNames,
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 0ca0b496fc..bf4004a79d 100644
--- a/invokeai/frontend/web/src/features/system/components/SettingsModal/SettingsModal.tsx
+++ b/invokeai/frontend/web/src/features/system/components/SettingsModal/SettingsModal.tsx
@@ -34,11 +34,11 @@ import {
} from 'features/ui/store/uiSlice';
import { UIState } from 'features/ui/store/uiTypes';
import { isEqual } from 'lodash-es';
-import { persistor } from 'app/store/persistor';
import { ChangeEvent, cloneElement, ReactElement, useCallback } from 'react';
import { useTranslation } from 'react-i18next';
import { VALID_LOG_LEVELS } from 'app/logging/useLogger';
import { LogLevelName } from 'roarr';
+import { LOCALSTORAGE_KEYS, LOCALSTORAGE_PREFIX } from 'app/store/constants';
const selector = createSelector(
[systemSelector, uiSelector],
@@ -119,15 +119,18 @@ const SettingsModal = ({ children }: SettingsModalProps) => {
shouldLogToConsole,
} = useAppSelector(selector);
- /**
- * Resets localstorage, then opens a secondary modal informing user to
- * refresh their browser.
- * */
const handleClickResetWebUI = useCallback(() => {
- persistor.purge().then(() => {
- onSettingsModalClose();
- onRefreshModalOpen();
+ // Only remove our keys
+ Object.keys(window.localStorage).forEach((key) => {
+ if (
+ LOCALSTORAGE_KEYS.includes(key) ||
+ key.startsWith(LOCALSTORAGE_PREFIX)
+ ) {
+ localStorage.removeItem(key);
+ }
});
+ onSettingsModalClose();
+ onRefreshModalOpen();
}, [onSettingsModalClose, onRefreshModalOpen]);
const handleLogLevelChanged = useCallback(
diff --git a/invokeai/frontend/web/src/features/system/store/configSlice.ts b/invokeai/frontend/web/src/features/system/store/configSlice.ts
index d668a59574..b773692908 100644
--- a/invokeai/frontend/web/src/features/system/store/configSlice.ts
+++ b/invokeai/frontend/web/src/features/system/store/configSlice.ts
@@ -3,7 +3,7 @@ import { createSlice } from '@reduxjs/toolkit';
import { AppConfig, PartialAppConfig } from 'app/types/invokeai';
import { merge } from 'lodash-es';
-const initialConfigState: AppConfig = {
+export const initialConfigState: AppConfig = {
shouldTransformUrls: false,
shouldFetchImages: false,
disabledTabs: [],
diff --git a/invokeai/frontend/web/src/features/system/store/modelSlice.ts b/invokeai/frontend/web/src/features/system/store/modelSlice.ts
index cb1cf05328..88ca71f2ff 100644
--- a/invokeai/frontend/web/src/features/system/store/modelSlice.ts
+++ b/invokeai/frontend/web/src/features/system/store/modelSlice.ts
@@ -27,12 +27,13 @@ export type ModelsState = typeof initialModelsState;
export const modelsSlice = createSlice({
name: 'models',
- initialState: initialModelsState,
+ initialState: modelsAdapter.getInitialState(),
+ // initialState: initialModelsState,
reducers: {
modelAdded: modelsAdapter.upsertOne,
- modelSelected: (state, action: PayloadAction) => {
- state.selectedModelName = action.payload;
- },
+ // modelSelected: (state, action: PayloadAction) => {
+ // state.selectedModelName = action.payload;
+ // },
},
extraReducers(builder) {
/**
@@ -44,18 +45,18 @@ export const modelsSlice = createSlice({
// If the current selected model is `''` or isn't actually in the list of models,
// choose a random model
- if (
- !state.selectedModelName ||
- !keys(models).includes(state.selectedModelName)
- ) {
- const randomModel = sample(models);
+ // if (
+ // !state.selectedModelName ||
+ // !keys(models).includes(state.selectedModelName)
+ // ) {
+ // const randomModel = sample(models);
- if (randomModel) {
- state.selectedModelName = randomModel.name;
- } else {
- state.selectedModelName = '';
- }
- }
+ // if (randomModel) {
+ // state.selectedModelName = randomModel.name;
+ // } else {
+ // state.selectedModelName = '';
+ // }
+ // }
});
},
});
@@ -75,6 +76,9 @@ export const {
selectTotal: selectModelsTotal,
} = modelsAdapter.getSelectors((state) => state.models);
-export const { modelAdded, modelSelected } = modelsSlice.actions;
+export const {
+ modelAdded,
+ // modelSelected
+} = modelsSlice.actions;
export default modelsSlice.reducer;
diff --git a/invokeai/frontend/web/src/features/system/store/modelsPersistDenylist.ts b/invokeai/frontend/web/src/features/system/store/modelsPersistDenylist.ts
index f374948e39..5b36a3f196 100644
--- a/invokeai/frontend/web/src/features/system/store/modelsPersistDenylist.ts
+++ b/invokeai/frontend/web/src/features/system/store/modelsPersistDenylist.ts
@@ -4,6 +4,7 @@ import { ModelsState } from './modelSlice';
* Models slice persist denylist
*/
const itemsToDenylist: (keyof ModelsState)[] = ['entities', 'ids'];
+export const modelsPersistDenylist: (keyof ModelsState)[] = ['entities', 'ids'];
export const modelsDenylist = itemsToDenylist.map(
(denylistItem) => `models.${denylistItem}`
diff --git a/invokeai/frontend/web/src/features/system/store/systemPersistDenylist.ts b/invokeai/frontend/web/src/features/system/store/systemPersistDenylist.ts
index 8a4d381775..70284e831a 100644
--- a/invokeai/frontend/web/src/features/system/store/systemPersistDenylist.ts
+++ b/invokeai/frontend/web/src/features/system/store/systemPersistDenylist.ts
@@ -21,6 +21,25 @@ const itemsToDenylist: (keyof SystemState)[] = [
'wereModelsReceived',
'wasSchemaParsed',
];
+export const systemPersistDenylist: (keyof SystemState)[] = [
+ 'currentIteration',
+ 'currentStatus',
+ 'currentStep',
+ 'isCancelable',
+ 'isConnected',
+ 'isESRGANAvailable',
+ 'isGFPGANAvailable',
+ 'isProcessing',
+ 'socketId',
+ 'totalIterations',
+ 'totalSteps',
+ 'openModel',
+ 'isCancelScheduled',
+ 'progressImage',
+ 'wereModelsReceived',
+ 'wasSchemaParsed',
+ 'isPersisted',
+];
export const systemDenylist = itemsToDenylist.map(
(denylistItem) => `system.${denylistItem}`
diff --git a/invokeai/frontend/web/src/features/system/store/systemSlice.ts b/invokeai/frontend/web/src/features/system/store/systemSlice.ts
index 16f118855f..025cebf0a6 100644
--- a/invokeai/frontend/web/src/features/system/store/systemSlice.ts
+++ b/invokeai/frontend/web/src/features/system/store/systemSlice.ts
@@ -89,9 +89,10 @@ export interface SystemState {
* TODO: get this from backend
*/
infillMethods: InfillMethod[];
+ isPersisted: boolean;
}
-const initialSystemState: SystemState = {
+export const initialSystemState: SystemState = {
isConnected: false,
isProcessing: false,
shouldDisplayGuides: true,
@@ -121,6 +122,7 @@ const initialSystemState: SystemState = {
statusTranslationKey: 'common.statusDisconnected',
canceledSession: '',
infillMethods: ['tile', 'patchmatch'],
+ isPersisted: false,
};
export const systemSlice = createSlice({
@@ -259,6 +261,9 @@ export const systemSlice = createSlice({
shouldLogToConsoleChanged: (state, action: PayloadAction) => {
state.shouldLogToConsole = action.payload;
},
+ isPersistedChanged: (state, action: PayloadAction) => {
+ state.isPersisted = action.payload;
+ },
},
extraReducers(builder) {
/**
@@ -476,6 +481,7 @@ export const {
subscribedNodeIdsSet,
consoleLogLevelChanged,
shouldLogToConsoleChanged,
+ isPersistedChanged,
} = systemSlice.actions;
export default systemSlice.reducer;
diff --git a/invokeai/frontend/web/src/features/ui/store/hotkeysSlice.ts b/invokeai/frontend/web/src/features/ui/store/hotkeysSlice.ts
index 4e72d1dce9..527e0b1740 100644
--- a/invokeai/frontend/web/src/features/ui/store/hotkeysSlice.ts
+++ b/invokeai/frontend/web/src/features/ui/store/hotkeysSlice.ts
@@ -6,15 +6,13 @@ type HotkeysState = {
shift: boolean;
};
-const initialHotkeysState: HotkeysState = {
+export const initialHotkeysState: HotkeysState = {
shift: false,
};
-const initialState: HotkeysState = initialHotkeysState;
-
export const hotkeysSlice = createSlice({
name: 'hotkeys',
- initialState,
+ initialState: initialHotkeysState,
reducers: {
shiftKeyPressed: (state, action: PayloadAction) => {
state.shift = action.payload;
diff --git a/invokeai/frontend/web/src/features/ui/store/uiPersistDenylist.ts b/invokeai/frontend/web/src/features/ui/store/uiPersistDenylist.ts
index ae357e7899..7f469e1f1a 100644
--- a/invokeai/frontend/web/src/features/ui/store/uiPersistDenylist.ts
+++ b/invokeai/frontend/web/src/features/ui/store/uiPersistDenylist.ts
@@ -4,6 +4,9 @@ import { UIState } from './uiTypes';
* UI slice persist denylist
*/
const itemsToDenylist: (keyof UIState)[] = ['floatingProgressImageRect'];
+export const uiPersistDenylist: (keyof UIState)[] = [
+ 'floatingProgressImageRect',
+];
export const uiDenylist = itemsToDenylist.map(
(denylistItem) => `ui.${denylistItem}`
diff --git a/invokeai/frontend/web/src/features/ui/store/uiSlice.ts b/invokeai/frontend/web/src/features/ui/store/uiSlice.ts
index 11abf6a20d..4a21a729d7 100644
--- a/invokeai/frontend/web/src/features/ui/store/uiSlice.ts
+++ b/invokeai/frontend/web/src/features/ui/store/uiSlice.ts
@@ -4,7 +4,7 @@ import { setActiveTabReducer } from './extraReducers';
import { InvokeTabName, tabMap } from './tabMap';
import { AddNewModelType, Coordinates, Rect, UIState } from './uiTypes';
-const initialUIState: UIState = {
+export const initialUIState: UIState = {
activeTab: 0,
currentTheme: 'dark',
parametersPanelScrollPosition: 0,
@@ -26,11 +26,9 @@ const initialUIState: UIState = {
shouldAutoShowProgressImages: false,
};
-const initialState: UIState = initialUIState;
-
export const uiSlice = createSlice({
name: 'ui',
- initialState,
+ initialState: initialUIState,
reducers: {
setActiveTab: (state, action: PayloadAction) => {
setActiveTabReducer(state, action.payload);
diff --git a/invokeai/frontend/web/src/i18n.ts b/invokeai/frontend/web/src/i18n.ts
index ed71d583b3..f28365db20 100644
--- a/invokeai/frontend/web/src/i18n.ts
+++ b/invokeai/frontend/web/src/i18n.ts
@@ -3,7 +3,8 @@ import LanguageDetector from 'i18next-browser-languagedetector';
import Backend from 'i18next-http-backend';
import { initReactI18next } from 'react-i18next';
-import translationEN from '../public/locales/en.json';
+import translationEN from '../dist/locales/en.json';
+import { LOCALSTORAGE_PREFIX } from 'app/store/constants';
if (import.meta.env.MODE === 'package') {
i18n.use(initReactI18next).init({
@@ -20,7 +21,11 @@ if (import.meta.env.MODE === 'package') {
} else {
i18n
.use(Backend)
- .use(LanguageDetector)
+ .use(
+ new LanguageDetector(null, {
+ lookupLocalStorage: `${LOCALSTORAGE_PREFIX}lng`,
+ })
+ )
.use(initReactI18next)
.init({
fallbackLng: 'en',
diff --git a/invokeai/frontend/web/src/services/events/middleware.ts b/invokeai/frontend/web/src/services/events/middleware.ts
index 9ece44665a..bd1d60099a 100644
--- a/invokeai/frontend/web/src/services/events/middleware.ts
+++ b/invokeai/frontend/web/src/services/events/middleware.ts
@@ -8,11 +8,7 @@ import {
import { socketSubscribed, socketUnsubscribed } from './actions';
import { AppThunkDispatch, RootState } from 'app/store/store';
import { getTimestamp } from 'common/util/getTimestamp';
-import {
- sessionInvoked,
- isFulfilledSessionCreatedAction,
- sessionCreated,
-} from 'services/thunks/session';
+import { sessionInvoked, sessionCreated } from 'services/thunks/session';
import { OpenAPI } from 'services/api';
import { setEventListeners } from 'services/events/util/setEventListeners';
import { log } from 'app/logging/useLogger';
diff --git a/invokeai/frontend/web/src/services/thunks/session.ts b/invokeai/frontend/web/src/services/thunks/session.ts
index bc26ee7aba..dca4134886 100644
--- a/invokeai/frontend/web/src/services/thunks/session.ts
+++ b/invokeai/frontend/web/src/services/thunks/session.ts
@@ -1,71 +1,10 @@
import { createAppAsyncThunk } from 'app/store/storeUtils';
import { SessionsService } from 'services/api';
-import { buildLinearGraph as buildGenerateGraph } from 'features/nodes/util/buildLinearGraph';
-import { buildCanvasGraphAndBlobs } from 'features/nodes/util/buildCanvasGraph';
-import { isAnyOf, isFulfilled } from '@reduxjs/toolkit';
-import { buildNodesGraph } from 'features/nodes/util/buildNodesGraph';
import { log } from 'app/logging/useLogger';
import { serializeError } from 'serialize-error';
const sessionLog = log.child({ namespace: 'session' });
-// export const generateGraphBuilt = createAppAsyncThunk(
-// 'api/generateGraphBuilt',
-// async (_, { dispatch, getState, rejectWithValue }) => {
-// try {
-// const graph = buildGenerateGraph(getState());
-// dispatch(sessionCreated({ graph }));
-// return graph;
-// } catch (err: any) {
-// sessionLog.error(
-// { error: serializeError(err) },
-// 'Problem building graph'
-// );
-// return rejectWithValue(err.message);
-// }
-// }
-// );
-
-// export const nodesGraphBuilt = createAppAsyncThunk(
-// 'api/nodesGraphBuilt',
-// async (_, { dispatch, getState, rejectWithValue }) => {
-// try {
-// const graph = buildNodesGraph(getState());
-// dispatch(sessionCreated({ graph }));
-// return graph;
-// } catch (err: any) {
-// sessionLog.error(
-// { error: serializeError(err) },
-// 'Problem building graph'
-// );
-// return rejectWithValue(err.message);
-// }
-// }
-// );
-
-// export const canvasGraphBuilt = createAppAsyncThunk(
-// 'api/canvasGraphBuilt',
-// async (_, { dispatch, getState, rejectWithValue }) => {
-// try {
-// const graph = await buildCanvasGraph(getState());
-// dispatch(sessionCreated({ graph }));
-// return graph;
-// } catch (err: any) {
-// sessionLog.error(
-// { error: serializeError(err) },
-// 'Problem building graph'
-// );
-// return rejectWithValue(err.message);
-// }
-// }
-// );
-
-// export const isFulfilledAnyGraphBuilt = isAnyOf(
-// generateGraphBuilt.fulfilled,
-// nodesGraphBuilt.fulfilled,
-// canvasGraphBuilt.fulfilled
-// );
-
type SessionCreatedArg = {
graph: Parameters<
(typeof SessionsService)['createSession']
@@ -96,11 +35,6 @@ export const sessionCreated = createAppAsyncThunk(
}
);
-/**
- * Function to check if an action is a fulfilled `SessionsService.createSession()` thunk
- */
-export const isFulfilledSessionCreatedAction = isFulfilled(sessionCreated);
-
type NodeAddedArg = Parameters<(typeof SessionsService)['addNode']>[0];
/**
diff --git a/invokeai/frontend/web/yarn.lock b/invokeai/frontend/web/yarn.lock
index 3901903bd4..18a08a01b5 100644
--- a/invokeai/frontend/web/yarn.lock
+++ b/invokeai/frontend/web/yarn.lock
@@ -5692,6 +5692,11 @@ redux-persist@^6.0.0:
resolved "https://registry.yarnpkg.com/redux-persist/-/redux-persist-6.0.0.tgz#b4d2972f9859597c130d40d4b146fecdab51b3a8"
integrity sha512-71LLMbUq2r02ng2We9S215LtPu3fY0KgaGE0k8WRgl6RkqxtGfl7HUozz1Dftwsb0D/5mZ8dwAaPbtnzfvbEwQ==
+redux-remember@^3.2.1:
+ version "3.2.1"
+ resolved "https://registry.yarnpkg.com/redux-remember/-/redux-remember-3.2.1.tgz#e58600336ac7341c56dfe9d69d95db234a7c404e"
+ integrity sha512-ep2E5KOJDGmrvbAuHVfmVpnuftqhJ2um6VpHw/iWa7WvAIFcPq/B678n51NBd/g8BWnNdQ5131cDRKWrRd041Q==
+
redux-thunk@^2.4.2:
version "2.4.2"
resolved "https://registry.yarnpkg.com/redux-thunk/-/redux-thunk-2.4.2.tgz#b9d05d11994b99f7a91ea223e8b04cf0afa5ef3b"