ui: redesign followups 8 (#5445)

* feat(ui): get rid of convoluted socket vs appSocket redux actions

There's no need to have `socket...` and `appSocket...` actions.

I did this initially due to a misunderstanding about the sequence of handling from middleware to reducers.

* feat(ui): bump deps

Mainly bumping to get latest `redux-remember`.

A change to socket.io required a change to the types in `useSocketIO`.

* chore(ui): format

* feat(ui): add error handling to redux persistence layer

- Add an error handler to `redux-remember` config using our logger
- Add custom errors representing storage set and get failures
- Update storage driver to raise these accordingly
- wrap method to clear idbkeyval storage and tidy its logic up

* feat(ui): add debuggingLoggerMiddleware

This simply logs every action and a diff of the state change.

Due to the noise this creates, it's not added by default at all. Add it to the middlewares if you want to use it.

* feat(ui): add $socket to window if in dev mode

* fix(ui): do not enable cancel hotkeys on inputs

* fix(ui): use JSON.stringify for ROARR logger serializer

A recent change to ROARR introduced limits to the size of data that will logged. This ends up making our logs far less useful. Change the serializer back to what it was previously.

* feat(ui): change diff util, update debuggerLoggerMiddleware

The previous diff library would present deleted things as `undefined`. Unfortunately, a JSON.stringify cycle will strip those values out. The ROARR logger does this and so the diffs end up being a lot less useful, not showing removed keys.

The new diff library uses a different format for the delta that serializes nicely.

* feat(ui): add migrations to redux persistence layer

- All persisted slices must now have a slice config, consisting of their initial state and a migrate callback. The migrate callback is very simple for now, with no type safety. It adds missing properties to the state. A future enhancement might be to model the each slice's state with e.g. zod and have proper validation and types.
- Persisted slices now have a `_version` property
- The migrate callback is called inside `redux-remember`'s `unserialize` handler. I couldn't figure out a good way to put this into the reducer and do logging (reducers should have no side effects). Also I ran into a weird race condition that I couldn't figure out. And finally, the typings are tricky. This works for now.
- `generationSlice` and `canvasSlice` both need migrations for the new aspect ratio setup, this has been added
- Stuff related to persistence has been moved in to `store.ts` for simplicity

* feat(ui): clean up StorageError class

* fix(ui): scale method default is now 'auto'

* feat(ui): when changing controlnet model, enable autoconfig

* fix(ui): make embedding popover immediately accessible

Prevents hotkeys from being captured when embeddings are still loading.
This commit is contained in:
psychedelicious
2024-01-09 01:11:45 +11:00
committed by GitHub
parent 5779542084
commit 0fc08bb384
49 changed files with 1722 additions and 1567 deletions

View File

@@ -11,6 +11,7 @@ import { STAGE_PADDING_PERCENTAGE } from 'features/canvas/util/constants';
import floorCoordinates from 'features/canvas/util/floorCoordinates';
import getScaledBoundingBoxDimensions from 'features/canvas/util/getScaledBoundingBoxDimensions';
import roundDimensionsToMultiple from 'features/canvas/util/roundDimensionsToMultiple';
import { initialAspectRatioState } from 'features/parameters/components/ImageSize/constants';
import type { AspectRatioState } from 'features/parameters/components/ImageSize/types';
import { modelChanged } from 'features/parameters/store/generationSlice';
import type { PayloadActionWithOptimalDimension } from 'features/parameters/store/types';
@@ -23,7 +24,7 @@ import { clamp, cloneDeep } from 'lodash-es';
import type { RgbaColor } from 'react-colorful';
import { queueApi } from 'services/api/endpoints/queue';
import type { ImageDTO } from 'services/api/types';
import { appSocketQueueItemStatusChanged } from 'services/events/actions';
import { socketQueueItemStatusChanged } from 'services/events/actions';
import type {
BoundingBoxScaleMethod,
@@ -53,10 +54,11 @@ export const initialLayerState: CanvasLayerState = {
};
export const initialCanvasState: CanvasState = {
_version: 1,
boundingBoxCoordinates: { x: 0, y: 0 },
boundingBoxDimensions: { width: 512, height: 512 },
boundingBoxPreviewFill: { r: 0, g: 0, b: 0, a: 0.5 },
boundingBoxScaleMethod: 'none',
boundingBoxScaleMethod: 'auto',
brushColor: { r: 90, g: 90, b: 255, a: 1 },
brushSize: 50,
colorPickerColor: { r: 90, g: 90, b: 255, a: 1 },
@@ -695,7 +697,7 @@ export const canvasSlice = createSlice({
);
});
builder.addCase(appSocketQueueItemStatusChanged, (state, action) => {
builder.addCase(socketQueueItemStatusChanged, (state, action) => {
const batch_status = action.payload.data.batch_status;
if (!state.batchIds.includes(batch_status.batch_id)) {
return;
@@ -784,3 +786,12 @@ export const {
export default canvasSlice.reducer;
export const selectCanvasSlice = (state: RootState) => state.canvas;
/* eslint-disable-next-line @typescript-eslint/no-explicit-any */
export const migrateCanvasState = (state: any): any => {
if (!('_version' in state)) {
state._version = 1;
state.aspectRatio = initialAspectRatioState;
}
return state;
};

View File

@@ -117,6 +117,7 @@ export const isCanvasAnyLine = (
): obj is CanvasMaskLine | CanvasBaseLine => obj.kind === 'line';
export interface CanvasState {
_version: 1;
boundingBoxCoordinates: Vector2d;
boundingBoxDimensions: Dimensions;
boundingBoxPreviewFill: RgbaColor;

View File

@@ -9,7 +9,7 @@ import type {
ParameterT2IAdapterModel,
} from 'features/parameters/types/parameterSchemas';
import { cloneDeep, merge, uniq } from 'lodash-es';
import { appSocketInvocationError } from 'services/events/actions';
import { socketInvocationError } from 'services/events/actions';
import { v4 as uuidv4 } from 'uuid';
import { controlAdapterImageProcessed } from './actions';
@@ -51,10 +51,12 @@ export const {
selectTotal: selectControlAdapterTotal,
} = caAdapterSelectors;
export const initialControlAdapterState: ControlAdaptersState =
export const initialControlAdaptersState: ControlAdaptersState =
caAdapter.getInitialState<{
_version: 1;
pendingControlImages: string[];
}>({
_version: 1,
pendingControlImages: [],
});
@@ -96,7 +98,7 @@ export const selectValidT2IAdapters = (controlAdapters: ControlAdaptersState) =>
export const controlAdaptersSlice = createSlice({
name: 'controlAdapters',
initialState: initialControlAdapterState,
initialState: initialControlAdaptersState,
reducers: {
controlAdapterAdded: {
reducer: (
@@ -267,31 +269,29 @@ export const controlAdaptersSlice = createSlice({
const update: Update<ControlNetConfig | T2IAdapterConfig, string> = {
id,
changes: { model },
changes: { model, shouldAutoConfig: true },
};
update.changes.processedControlImage = null;
if (cn.shouldAutoConfig) {
let processorType: ControlAdapterProcessorType | undefined = undefined;
let processorType: ControlAdapterProcessorType | undefined = undefined;
for (const modelSubstring in CONTROLADAPTER_MODEL_DEFAULT_PROCESSORS) {
if (model.model_name.includes(modelSubstring)) {
processorType =
CONTROLADAPTER_MODEL_DEFAULT_PROCESSORS[modelSubstring];
break;
}
for (const modelSubstring in CONTROLADAPTER_MODEL_DEFAULT_PROCESSORS) {
if (model.model_name.includes(modelSubstring)) {
processorType =
CONTROLADAPTER_MODEL_DEFAULT_PROCESSORS[modelSubstring];
break;
}
}
if (processorType) {
update.changes.processorType = processorType;
update.changes.processorNode = CONTROLNET_PROCESSORS[processorType]
.default as RequiredControlAdapterProcessorNode;
} else {
update.changes.processorType = 'none';
update.changes.processorNode = CONTROLNET_PROCESSORS.none
.default as RequiredControlAdapterProcessorNode;
}
if (processorType) {
update.changes.processorType = processorType;
update.changes.processorNode = CONTROLNET_PROCESSORS[processorType]
.default as RequiredControlAdapterProcessorNode;
} else {
update.changes.processorType = 'none';
update.changes.processorNode = CONTROLNET_PROCESSORS.none
.default as RequiredControlAdapterProcessorNode;
}
caAdapter.updateOne(state, update);
@@ -435,7 +435,7 @@ export const controlAdaptersSlice = createSlice({
caAdapter.updateOne(state, update);
},
controlAdaptersReset: () => {
return cloneDeep(initialControlAdapterState);
return cloneDeep(initialControlAdaptersState);
},
pendingControlImagesCleared: (state) => {
state.pendingControlImages = [];
@@ -454,7 +454,7 @@ export const controlAdaptersSlice = createSlice({
}
});
builder.addCase(appSocketInvocationError, (state) => {
builder.addCase(socketInvocationError, (state) => {
state.pendingControlImages = [];
});
},
@@ -493,3 +493,11 @@ export const isAnyControlAdapterAdded = isAnyOf(
export const selectControlAdaptersSlice = (state: RootState) =>
state.controlAdapters;
/* eslint-disable-next-line @typescript-eslint/no-explicit-any */
export const migrateControlAdaptersState = (state: any): any => {
if (!('_version' in state)) {
state._version = 1;
}
return state;
};

View File

@@ -7,7 +7,9 @@ export const zSeedBehaviour = z.enum(['PER_ITERATION', 'PER_PROMPT']);
export type SeedBehaviour = z.infer<typeof zSeedBehaviour>;
export const isSeedBehaviour = (v: unknown): v is SeedBehaviour =>
zSeedBehaviour.safeParse(v).success;
export interface DynamicPromptsState {
_version: 1;
maxPrompts: number;
combinatorial: boolean;
prompts: string[];
@@ -18,6 +20,7 @@ export interface DynamicPromptsState {
}
export const initialDynamicPromptsState: DynamicPromptsState = {
_version: 1,
maxPrompts: 100,
combinatorial: true,
prompts: [],
@@ -78,3 +81,11 @@ export default dynamicPromptsSlice.reducer;
export const selectDynamicPromptsSlice = (state: RootState) =>
state.dynamicPrompts;
/* eslint-disable-next-line @typescript-eslint/no-explicit-any */
export const migrateDynamicPromptsState = (state: any): any => {
if (!('_version' in state)) {
state._version = 1;
}
return state;
};

View File

@@ -2,7 +2,6 @@ import type { ChakraProps } from '@chakra-ui/react';
import { useAppSelector } from 'app/store/storeHooks';
import { InvControl } from 'common/components/InvControl/InvControl';
import { InvSelect } from 'common/components/InvSelect/InvSelect';
import { InvSelectFallback } from 'common/components/InvSelect/InvSelectFallback';
import { useGroupedModelInvSelect } from 'common/components/InvSelect/useGroupedModelInvSelect';
import type { EmbeddingSelectProps } from 'features/embedding/types';
import { t } from 'i18next';
@@ -47,23 +46,16 @@ export const EmbeddingSelect = memo(
onChange: _onChange,
});
if (isLoading) {
return <InvSelectFallback label={t('common.loading')} />;
}
if (options.length === 0) {
return <InvSelectFallback label={t('embedding.noEmbeddingsLoaded')} />;
}
return (
<InvControl isDisabled={!options.length}>
<InvControl>
<InvSelect
placeholder={t('embedding.addEmbedding')}
placeholder={
isLoading ? t('common.loading') : t('embedding.addEmbedding')
}
defaultMenuIsOpen
autoFocus
value={null}
options={options}
isDisabled={!options.length}
noOptionsMessage={noOptionsMessage}
onChange={onChange}
onMenuClose={onClose}

View File

@@ -106,3 +106,11 @@ const isAnyBoardDeleted = isAnyOf(
);
export const selectGallerySlice = (state: RootState) => state.gallery;
/* eslint-disable-next-line @typescript-eslint/no-explicit-any */
export const migrateGalleryState = (state: any): any => {
if (!('_version' in state)) {
state._version = 1;
}
return state;
};

View File

@@ -7,12 +7,14 @@ import type {
} from 'features/parameters/types/parameterSchemas';
export interface HRFState {
_version: 1;
hrfEnabled: boolean;
hrfStrength: ParameterStrength;
hrfMethod: ParameterHRFMethod;
}
export const initialHRFState: HRFState = {
_version: 1,
hrfStrength: 0.45,
hrfEnabled: false,
hrfMethod: 'ESRGAN',
@@ -41,3 +43,11 @@ export const { setHrfEnabled, setHrfStrength, setHrfMethod } = hrfSlice.actions;
export default hrfSlice.reducer;
export const selectHrfSlice = (state: RootState) => state.hrf;
/* eslint-disable-next-line @typescript-eslint/no-explicit-any */
export const migrateHRFState = (state: any): any => {
if (!('_version' in state)) {
state._version = 1;
}
return state;
};

View File

@@ -14,16 +14,18 @@ export const defaultLoRAConfig = {
};
export type LoraState = {
_version: 1;
loras: Record<string, LoRA>;
};
export const intialLoraState: LoraState = {
export const initialLoraState: LoraState = {
_version: 1,
loras: {},
};
export const loraSlice = createSlice({
name: 'lora',
initialState: intialLoraState,
initialState: initialLoraState,
reducers: {
loraAdded: (state, action: PayloadAction<LoRAModelConfigEntity>) => {
const { model_name, id, base_model } = action.payload;
@@ -77,3 +79,11 @@ export const {
export default loraSlice.reducer;
export const selectLoraSlice = (state: RootState) => state.lora;
/* eslint-disable-next-line @typescript-eslint/no-explicit-any */
export const migrateLoRAState = (state: any): any => {
if (!('_version' in state)) {
state._version = 1;
}
return state;
};

View File

@@ -3,11 +3,13 @@ import { createSlice } from '@reduxjs/toolkit';
import type { RootState } from 'app/store/store';
type ModelManagerState = {
_version: 1;
searchFolder: string | null;
advancedAddScanModel: string | null;
};
const initialModelManagerState: ModelManagerState = {
export const initialModelManagerState: ModelManagerState = {
_version: 1,
searchFolder: null,
advancedAddScanModel: null,
};
@@ -31,3 +33,11 @@ export const { setSearchFolder, setAdvancedAddScanModel } =
export default modelManagerSlice.reducer;
export const selectModelManagerSlice = (state: RootState) => state.modelmanager;
/* eslint-disable-next-line @typescript-eslint/no-explicit-any */
export const migrateModelManagerState = (state: any): any => {
if (!('_version' in state)) {
state._version = 1;
}
return state;
};

View File

@@ -74,11 +74,11 @@ import {
} from 'reactflow';
import { receivedOpenAPISchema } from 'services/api/thunks/schema';
import {
appSocketGeneratorProgress,
appSocketInvocationComplete,
appSocketInvocationError,
appSocketInvocationStarted,
appSocketQueueItemStatusChanged,
socketGeneratorProgress,
socketInvocationComplete,
socketInvocationError,
socketInvocationStarted,
socketQueueItemStatusChanged,
} from 'services/events/actions';
import { v4 as uuidv4 } from 'uuid';
import type { z } from 'zod';
@@ -96,6 +96,7 @@ const initialNodeExecutionState: Omit<NodeExecutionState, 'nodeId'> = {
};
export const initialNodesState: NodesState = {
_version: 1,
nodes: [],
edges: [],
isReady: false,
@@ -838,14 +839,14 @@ const nodesSlice = createSlice({
}, {});
});
builder.addCase(appSocketInvocationStarted, (state, action) => {
builder.addCase(socketInvocationStarted, (state, action) => {
const { source_node_id } = action.payload.data;
const node = state.nodeExecutionStates[source_node_id];
if (node) {
node.status = zNodeStatus.enum.IN_PROGRESS;
}
});
builder.addCase(appSocketInvocationComplete, (state, action) => {
builder.addCase(socketInvocationComplete, (state, action) => {
const { source_node_id, result } = action.payload.data;
const nes = state.nodeExecutionStates[source_node_id];
if (nes) {
@@ -856,7 +857,7 @@ const nodesSlice = createSlice({
nes.outputs.push(result);
}
});
builder.addCase(appSocketInvocationError, (state, action) => {
builder.addCase(socketInvocationError, (state, action) => {
const { source_node_id } = action.payload.data;
const node = state.nodeExecutionStates[source_node_id];
if (node) {
@@ -866,7 +867,7 @@ const nodesSlice = createSlice({
node.progressImage = null;
}
});
builder.addCase(appSocketGeneratorProgress, (state, action) => {
builder.addCase(socketGeneratorProgress, (state, action) => {
const { source_node_id, step, total_steps, progress_image } =
action.payload.data;
const node = state.nodeExecutionStates[source_node_id];
@@ -876,7 +877,7 @@ const nodesSlice = createSlice({
node.progressImage = progress_image ?? null;
}
});
builder.addCase(appSocketQueueItemStatusChanged, (state, action) => {
builder.addCase(socketQueueItemStatusChanged, (state, action) => {
if (['in_progress'].includes(action.payload.data.queue_item.status)) {
forEach(state.nodeExecutionStates, (nes) => {
nes.status = zNodeStatus.enum.PENDING;
@@ -990,3 +991,11 @@ export const isAnyNodeOrEdgeMutation = isAnyOf(
export default nodesSlice.reducer;
export const selectNodesSlice = (state: RootState) => state.nodes;
/* eslint-disable-next-line @typescript-eslint/no-explicit-any */
export const migrateNodesState = (state: any): any => {
if (!('_version' in state)) {
state._version = 1;
}
return state;
};

View File

@@ -14,6 +14,7 @@ import type {
} from 'reactflow';
export type NodesState = {
_version: 1;
nodes: AnyNode[];
edges: InvocationNodeEdge[];
connectionStartParams: OnConnectStartParams | null;
@@ -39,6 +40,7 @@ export type NodesState = {
};
export type WorkflowsState = Omit<WorkflowV2, 'nodes' | 'edges'> & {
_version: 1;
isTouched: boolean;
};

View File

@@ -12,6 +12,7 @@ import type { FieldIdentifier } from 'features/nodes/types/field';
import { cloneDeep, isEqual, uniqBy } from 'lodash-es';
export const initialWorkflowState: WorkflowState = {
_version: 1,
name: '',
author: '',
description: '',
@@ -86,7 +87,7 @@ const workflowSlice = createSlice({
extraReducers: (builder) => {
builder.addCase(workflowLoaded, (state, action) => {
const { nodes: _nodes, edges: _edges, ...workflowExtra } = action.payload;
return { ...cloneDeep(workflowExtra), isTouched: true };
return { ...initialWorkflowState, ...cloneDeep(workflowExtra) };
});
builder.addCase(nodesDeleted, (state, action) => {
@@ -123,3 +124,11 @@ export const {
export default workflowSlice.reducer;
export const selectWorkflowSlice = (state: RootState) => state.workflow;
/* eslint-disable-next-line @typescript-eslint/no-explicit-any */
export const migrateWorkflowState = (state: any): any => {
if (!('_version' in state)) {
state._version = 1;
}
return state;
};

View File

@@ -29,6 +29,7 @@ import type { ImageDTO } from 'services/api/types';
import type { GenerationState } from './types';
export const initialGenerationState: GenerationState = {
_version: 1,
cfgScale: 7.5,
cfgRescaleMultiplier: 0,
height: 512,
@@ -276,3 +277,12 @@ export const { selectOptimalDimension } = generationSlice.selectors;
export default generationSlice.reducer;
export const selectGenerationSlice = (state: RootState) => state.generation;
/* eslint-disable-next-line @typescript-eslint/no-explicit-any */
export const migrateGenerationState = (state: any): GenerationState => {
if (!('_version' in state)) {
state._version = 1;
state.aspectRatio = initialAspectRatioState;
}
return state;
};

View File

@@ -14,10 +14,12 @@ export const isParamESRGANModelName = (v: unknown): v is ParamESRGANModelName =>
zParamESRGANModelName.safeParse(v).success;
export interface PostprocessingState {
_version: 1;
esrganModelName: ParamESRGANModelName;
}
export const initialPostprocessingState: PostprocessingState = {
_version: 1,
esrganModelName: 'RealESRGAN_x4plus.pth',
};
@@ -40,3 +42,11 @@ export default postprocessingSlice.reducer;
export const selectPostprocessingSlice = (state: RootState) =>
state.postprocessing;
/* eslint-disable-next-line @typescript-eslint/no-explicit-any */
export const migratePostprocessingState = (state: any): any => {
if (!('_version' in state)) {
state._version = 1;
}
return state;
};

View File

@@ -19,6 +19,7 @@ import type {
} from 'features/parameters/types/parameterSchemas';
export interface GenerationState {
_version: 1;
cfgScale: ParameterCFGScale;
cfgRescaleMultiplier: ParameterCFGRescaleMultiplier;
height: ParameterHeight;

View File

@@ -9,6 +9,7 @@ import type {
} from 'features/parameters/types/parameterSchemas';
type SDXLState = {
_version: 1;
positiveStylePrompt: ParameterPositiveStylePromptSDXL;
negativeStylePrompt: ParameterNegativeStylePromptSDXL;
shouldConcatSDXLStylePrompt: boolean;
@@ -22,6 +23,7 @@ type SDXLState = {
};
export const initialSDXLState: SDXLState = {
_version: 1,
positiveStylePrompt: '',
negativeStylePrompt: '',
shouldConcatSDXLStylePrompt: true,
@@ -96,3 +98,11 @@ export const {
export default sdxlSlice.reducer;
export const selectSdxlSlice = (state: RootState) => state.sdxl;
/* eslint-disable-next-line @typescript-eslint/no-explicit-any */
export const migrateSDXLState = (state: any): any => {
if (!('_version' in state)) {
state._version = 1;
}
return state;
};

View File

@@ -8,23 +8,24 @@ import { t } from 'i18next';
import { startCase } from 'lodash-es';
import type { LogLevelName } from 'roarr';
import {
appSocketConnected,
appSocketDisconnected,
appSocketGeneratorProgress,
appSocketGraphExecutionStateComplete,
appSocketInvocationComplete,
appSocketInvocationError,
appSocketInvocationRetrievalError,
appSocketInvocationStarted,
appSocketModelLoadCompleted,
appSocketModelLoadStarted,
appSocketQueueItemStatusChanged,
appSocketSessionRetrievalError,
socketConnected,
socketDisconnected,
socketGeneratorProgress,
socketGraphExecutionStateComplete,
socketInvocationComplete,
socketInvocationError,
socketInvocationRetrievalError,
socketInvocationStarted,
socketModelLoadCompleted,
socketModelLoadStarted,
socketQueueItemStatusChanged,
socketSessionRetrievalError,
} from 'services/events/actions';
import type { Language, SystemState } from './types';
export const initialSystemState: SystemState = {
_version: 1,
isInitialized: false,
isConnected: false,
shouldConfirmOnDelete: true,
@@ -92,7 +93,7 @@ export const systemSlice = createSlice({
/**
* Socket Connected
*/
builder.addCase(appSocketConnected, (state) => {
builder.addCase(socketConnected, (state) => {
state.isConnected = true;
state.denoiseProgress = null;
state.status = 'CONNECTED';
@@ -101,7 +102,7 @@ export const systemSlice = createSlice({
/**
* Socket Disconnected
*/
builder.addCase(appSocketDisconnected, (state) => {
builder.addCase(socketDisconnected, (state) => {
state.isConnected = false;
state.denoiseProgress = null;
state.status = 'DISCONNECTED';
@@ -110,7 +111,7 @@ export const systemSlice = createSlice({
/**
* Invocation Started
*/
builder.addCase(appSocketInvocationStarted, (state) => {
builder.addCase(socketInvocationStarted, (state) => {
state.denoiseProgress = null;
state.status = 'PROCESSING';
});
@@ -118,7 +119,7 @@ export const systemSlice = createSlice({
/**
* Generator Progress
*/
builder.addCase(appSocketGeneratorProgress, (state, action) => {
builder.addCase(socketGeneratorProgress, (state, action) => {
const {
step,
total_steps,
@@ -144,7 +145,7 @@ export const systemSlice = createSlice({
/**
* Invocation Complete
*/
builder.addCase(appSocketInvocationComplete, (state) => {
builder.addCase(socketInvocationComplete, (state) => {
state.denoiseProgress = null;
state.status = 'CONNECTED';
});
@@ -152,20 +153,20 @@ export const systemSlice = createSlice({
/**
* Graph Execution State Complete
*/
builder.addCase(appSocketGraphExecutionStateComplete, (state) => {
builder.addCase(socketGraphExecutionStateComplete, (state) => {
state.denoiseProgress = null;
state.status = 'CONNECTED';
});
builder.addCase(appSocketModelLoadStarted, (state) => {
builder.addCase(socketModelLoadStarted, (state) => {
state.status = 'LOADING_MODEL';
});
builder.addCase(appSocketModelLoadCompleted, (state) => {
builder.addCase(socketModelLoadCompleted, (state) => {
state.status = 'CONNECTED';
});
builder.addCase(appSocketQueueItemStatusChanged, (state, action) => {
builder.addCase(socketQueueItemStatusChanged, (state, action) => {
if (
['completed', 'canceled', 'failed'].includes(
action.payload.data.queue_item.status
@@ -211,9 +212,17 @@ export const {
export default systemSlice.reducer;
const isAnyServerError = isAnyOf(
appSocketInvocationError,
appSocketSessionRetrievalError,
appSocketInvocationRetrievalError
socketInvocationError,
socketSessionRetrievalError,
socketInvocationRetrievalError
);
export const selectSystemSlice = (state: RootState) => state.system;
/* eslint-disable-next-line @typescript-eslint/no-explicit-any */
export const migrateSystemState = (state: any): any => {
if (!('_version' in state)) {
state._version = 1;
}
return state;
};

View File

@@ -43,6 +43,7 @@ export const isLanguage = (v: unknown): v is Language =>
zLanguage.safeParse(v).success;
export interface SystemState {
_version: 1;
isInitialized: boolean;
isConnected: boolean;
shouldConfirmOnDelete: boolean;

View File

@@ -7,6 +7,7 @@ import type { InvokeTabName } from './tabMap';
import type { UIState } from './uiTypes';
export const initialUIState: UIState = {
_version: 1,
activeTab: 'txt2img',
shouldShowImageDetails: false,
shouldShowExistingModelsInSearch: false,
@@ -63,3 +64,11 @@ export const {
export default uiSlice.reducer;
export const selectUiSlice = (state: RootState) => state.ui;
/* eslint-disable-next-line @typescript-eslint/no-explicit-any */
export const migrateUIState = (state: any): any => {
if (!('_version' in state)) {
state._version = 1;
}
return state;
};

View File

@@ -1,6 +1,7 @@
import type { InvokeTabName } from './tabMap';
export interface UIState {
_version: 1;
activeTab: InvokeTabName;
shouldShowImageDetails: boolean;
shouldShowExistingModelsInSearch: boolean;