Adds error handling to & improves model switching UI

This commit is contained in:
psychedelicious 2022-10-28 18:50:47 +11:00
parent 27a7980dad
commit 0adb7d4676
20 changed files with 1068 additions and 958 deletions

File diff suppressed because it is too large Load Diff

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

517
frontend/dist/assets/index.c9288511.js vendored Normal file

File diff suppressed because one or more lines are too long

View File

@ -6,8 +6,8 @@
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>InvokeAI - A Stable Diffusion Toolkit</title>
<link rel="shortcut icon" type="icon" href="./assets/favicon.0d253ced.ico" />
<script type="module" crossorigin src="./assets/index.647e65c7.js"></script>
<link rel="stylesheet" href="./assets/index.549d985b.css">
<script type="module" crossorigin src="./assets/index.c9288511.js"></script>
<link rel="stylesheet" href="./assets/index.9dddc756.css">
</head>
<body>

View File

@ -142,11 +142,13 @@ export declare type SystemConfig = {
model_hash: string;
app_id: string;
app_version: string;
available_models?: ModelList;
model_list: ModelList;
};
export declare type ModelStatus = 'active' | 'cached' | 'not loaded';
export declare type Model = {
status: 'active' | 'cached' | 'not loaded';
status: ModelStatus;
description: string;
};
@ -156,6 +158,11 @@ export declare type ModelList = Record<string, Model>;
* These types type data received from the server via socketio.
*/
export declare type ModelChangeResponse = {
model_name: string;
model_list: ModelList;
};
export declare type SystemStatusResponse = SystemStatus;
export declare type SystemConfigResponse = SystemConfig;

View File

@ -31,4 +31,4 @@ export const requestSystemConfig = createAction<undefined>(
'socketio/requestSystemConfig'
);
export const setModel = createAction<string>('socketio/setModel');
export const requestModelChange = createAction<string>('socketio/requestModelChange');

View File

@ -8,6 +8,8 @@ import {
import {
addLogEntry,
errorOccurred,
setCurrentStatus,
setIsCancelable,
setIsProcessing,
} from '../../features/system/systemSlice';
import { inpaintingImageElementRef } from '../../features/tabs/Inpainting/InpaintingCanvas';
@ -177,8 +179,11 @@ const makeSocketIOEmitters = (
emitRequestSystemConfig: () => {
socketio.emit('requestSystemConfig');
},
emitSetModel: (modelName: string) => {
socketio.emit('setModel', modelName);
emitRequestModelChange: (modelName: string) => {
dispatch(setCurrentStatus('Changing Model'));
dispatch(setIsProcessing(true));
dispatch(setIsCancelable(false));
socketio.emit('requestModelChange', modelName);
},
};
};

View File

@ -13,6 +13,8 @@ import {
setSystemConfig,
processingCanceled,
errorOccurred,
setModelList,
setIsCancelable,
} from '../../features/system/systemSlice';
import {
@ -289,6 +291,34 @@ const makeSocketIOListeners = (
onSystemConfig: (data: InvokeAI.SystemConfig) => {
dispatch(setSystemConfig(data));
},
onModelChanged: (data: InvokeAI.ModelChangeResponse) => {
const { model_name, model_list } = data;
dispatch(setModelList(model_list));
dispatch(setCurrentStatus('Connected'));
dispatch(setIsProcessing(false));
dispatch(setIsCancelable(false));
dispatch(
addLogEntry({
timestamp: dateFormat(new Date(), 'isoDateTime'),
message: `Model changed: ${model_name}`,
level: 'info',
})
);
},
onModelChangeFailed: (data: InvokeAI.ModelChangeResponse) => {
const { model_name, model_list } = data;
dispatch(setModelList(model_list));
dispatch(setIsProcessing(false));
dispatch(setIsCancelable(false));
dispatch(errorOccurred());
dispatch(
addLogEntry({
timestamp: dateFormat(new Date(), 'isoDateTime'),
message: `Model change failed: ${model_name}`,
level: 'error',
})
);
},
};
};

View File

@ -46,6 +46,8 @@ export const socketioMiddleware = () => {
onInitialImageUploaded,
onMaskImageUploaded,
onSystemConfig,
onModelChanged,
onModelChangeFailed,
} = makeSocketIOListeners(store);
const {
@ -59,7 +61,7 @@ export const socketioMiddleware = () => {
emitUploadInitialImage,
emitUploadMaskImage,
emitRequestSystemConfig,
emitSetModel,
emitRequestModelChange,
} = makeSocketIOEmitters(store, socketio);
/**
@ -114,6 +116,14 @@ export const socketioMiddleware = () => {
onSystemConfig(data);
});
socketio.on('modelChanged', (data: InvokeAI.ModelChangeResponse) => {
onModelChanged(data);
});
socketio.on('modelChangeFailed', (data: InvokeAI.ModelChangeResponse) => {
onModelChangeFailed(data);
});
areListenersSet = true;
}
@ -171,8 +181,8 @@ export const socketioMiddleware = () => {
break;
}
case 'socketio/setModel': {
emitSetModel(action.payload);
case 'socketio/requestModelChange': {
emitRequestModelChange(action.payload);
break;
}
}

View File

@ -44,9 +44,8 @@ const memoEqualityCheck = (
*/
const HoverableImage = memo((props: HoverableImageProps) => {
const dispatch = useAppDispatch();
const { activeTabName, galleryImageObjectFit } = useAppSelector(
hoverableImageSelector
);
const { activeTabName, galleryImageObjectFit, galleryImageMinimumWidth } =
useAppSelector(hoverableImageSelector);
const { image, isSelected } = props;
const { url, uuid, metadata } = image;
@ -173,7 +172,7 @@ const HoverableImage = memo((props: HoverableImageProps) => {
/>
)}
</div>
{isHovered && (
{isHovered && galleryImageMinimumWidth >= 64 && (
<div className="hoverable-image-delete-button">
<Tooltip label={'Delete image'} hasArrow>
<DeleteImageModal image={image}>

View File

@ -42,6 +42,7 @@ export const hoverableImageSelector = createSelector(
(options: OptionsState, gallery: GalleryState) => {
return {
galleryImageObjectFit: gallery.galleryImageObjectFit,
galleryImageMinimumWidth: gallery.galleryImageMinimumWidth,
activeTabName: tabMap[options.activeTab],
};
}

View File

@ -13,6 +13,7 @@ const cancelButtonSelector = createSelector(
return {
isProcessing: system.isProcessing,
isConnected: system.isConnected,
isCancelable: system.isCancelable,
};
},
{
@ -24,17 +25,18 @@ const cancelButtonSelector = createSelector(
export default function CancelButton() {
const dispatch = useAppDispatch();
const { isProcessing, isConnected } = useAppSelector(cancelButtonSelector);
const { isProcessing, isConnected, isCancelable } =
useAppSelector(cancelButtonSelector);
const handleClickCancel = () => dispatch(cancelProcessing());
useHotkeys(
'shift+x',
() => {
if (isConnected || isProcessing) {
if ((isConnected || isProcessing) && isCancelable) {
handleClickCancel();
}
},
[isConnected, isProcessing]
[isConnected, isProcessing, isCancelable]
);
return (
@ -42,7 +44,7 @@ export default function CancelButton() {
icon={<MdCancel />}
tooltip="Cancel"
aria-label="Cancel"
isDisabled={!isConnected || !isProcessing}
isDisabled={!isConnected || !isProcessing || !isCancelable}
onClick={handleClickCancel}
styleClass="cancel-btn"
/>

View File

@ -0,0 +1,43 @@
.model-list {
display: flex;
flex-direction: column;
row-gap: 0.5rem;
.model-list-header {
}
.model-list-list {
display: flex;
flex-direction: column;
row-gap: 0.5rem;
.model-list-item {
display: flex;
column-gap: 0.5rem;
width: 100%;
justify-content: space-between;
align-items: center;
.model-list-item-name {
}
.model-list-item-description {
font-size: 0.9rem;
}
.model-list-item-status {
&.active {
color: var(--status-good-color);
}
&.cached {
color: var(--status-working-color);
}
&.not-loaded {
color: var(--text-color-secondary);
}
}
.model-list-item-load-btn {
}
}
}
}

View File

@ -0,0 +1,63 @@
import { Button, Tooltip, Spacer, Heading } from '@chakra-ui/react';
import _ from 'lodash';
import { Model, ModelStatus } from '../../../app/invokeai';
import { requestModelChange } from '../../../app/socketio/actions';
import { RootState, useAppDispatch, useAppSelector } from '../../../app/store';
type ModelListItemProps = {
name: string;
status: ModelStatus;
description: string;
};
const ModelListItem = (props: ModelListItemProps) => {
const dispatch = useAppDispatch();
const { name, status, description } = props;
const handleChangeModel = () => {
dispatch(requestModelChange(name));
};
return (
<div className="model-list-item">
<Tooltip label={description} hasArrow placement="top">
<div className="model-list-item-name">{name}</div>
</Tooltip>
<Spacer />
<div className={`model-list-item-status ${status.split(' ').join('-')}`}>
{status}
</div>
<div className="model-list-item-load-btn">
<Button
size={'sm'}
onClick={handleChangeModel}
isDisabled={status === 'active'}
>
Load
</Button>
</div>
</div>
);
};
const ModelList = () => {
const { model_list } = useAppSelector((state: RootState) => state.system);
return (
<div className="model-list">
<Heading size={'md'} className="model-list-header">
Available Models
</Heading>
<div className="model-list-list">
{_.map(model_list, (model: Model, key) => (
<ModelListItem
key={key}
name={key}
status={model.status}
description={model.description}
/>
))}
</div>
</div>
);
};
export default ModelList;

View File

@ -2,11 +2,13 @@
.settings-modal {
background-color: var(--settings-modal-bg) !important;
max-height: 36rem;
font-family: Inter;
.settings-modal-content {
display: grid;
row-gap: 2rem;
overflow-y: scroll;
}
.settings-modal-header {

View File

@ -14,10 +14,8 @@ import {
} from '@chakra-ui/react';
import { createSelector } from '@reduxjs/toolkit';
import _, { isEqual } from 'lodash';
import { ChangeEvent, cloneElement, ReactElement } from 'react';
import { setModel } from '../../../app/socketio/actions';
import { RootState, useAppDispatch, useAppSelector } from '../../../app/store';
import IAISelect from '../../../common/components/IAISelect';
import { cloneElement, ReactElement } from 'react';
import { RootState, useAppSelector } from '../../../app/store';
import { persistor } from '../../../main';
import {
setShouldConfirmOnDelete,
@ -25,6 +23,7 @@ import {
setShouldDisplayInProgress,
SystemState,
} from '../systemSlice';
import ModelList from './ModelList';
import SettingsModalItem from './SettingsModalItem';
const systemSelector = createSelector(
@ -34,15 +33,13 @@ const systemSelector = createSelector(
shouldDisplayInProgress,
shouldConfirmOnDelete,
shouldDisplayGuides,
available_models,
model_list,
} = system;
return {
shouldDisplayInProgress,
shouldConfirmOnDelete,
shouldDisplayGuides,
models: available_models
? _.map(available_models, (model, key) => key)
: [],
models: _.map(model_list, (_model, key) => key),
};
},
{
@ -62,7 +59,6 @@ type SettingsModalProps = {
* Secondary post-reset modal is included here.
*/
const SettingsModal = ({ children }: SettingsModalProps) => {
const dispatch = useAppDispatch();
const {
isOpen: isSettingsModalOpen,
onOpen: onSettingsModalOpen,
@ -79,7 +75,6 @@ const SettingsModal = ({ children }: SettingsModalProps) => {
shouldDisplayInProgress,
shouldConfirmOnDelete,
shouldDisplayGuides,
models,
} = useAppSelector(systemSelector);
/**
@ -93,10 +88,6 @@ const SettingsModal = ({ children }: SettingsModalProps) => {
});
};
const handleChangeModel = (e: ChangeEvent<HTMLSelectElement>) => {
dispatch(setModel(e.target.value));
};
return (
<>
{cloneElement(children, {
@ -109,11 +100,7 @@ const SettingsModal = ({ children }: SettingsModalProps) => {
<ModalHeader className="settings-modal-header">Settings</ModalHeader>
<ModalCloseButton />
<ModalBody className="settings-modal-content">
<IAISelect
label="Model"
validValues={models}
onChange={handleChangeModel}
/>
<ModelList />
<div className="settings-modal-items">
<SettingsModalItem
settingTitle="Display In-Progress Images (slower)"

View File

@ -35,7 +35,7 @@ export interface SystemState
currentStatusHasSteps: boolean;
shouldDisplayGuides: boolean;
wasErrorSeen: boolean;
available_models?: InvokeAI.ModelList;
isCancelable: boolean;
}
const initialSystemState = {
@ -61,8 +61,10 @@ const initialSystemState = {
model_hash: '',
app_id: '',
app_version: '',
model_list: {},
hasError: false,
wasErrorSeen: true,
isCancelable: true,
};
const initialState: SystemState = initialSystemState;
@ -158,6 +160,15 @@ export const systemSlice = createSlice({
state.currentStatusHasSteps = false;
state.currentStatus = 'Processing canceled';
},
setModelList: (
state,
action: PayloadAction<InvokeAI.ModelList | Record<string, never>>
) => {
state.model_list = action.payload;
},
setIsCancelable: (state, action: PayloadAction<boolean>) => {
state.isCancelable = action.payload;
},
},
});
@ -177,6 +188,8 @@ export const {
processingCanceled,
errorOccurred,
errorSeen,
setModelList,
setIsCancelable,
} = systemSlice.actions;
export default systemSlice.reducer;

View File

@ -13,6 +13,7 @@
@use '../features/system/SiteHeader.scss';
@use '../features/system/StatusIndicator.scss';
@use '../features/system/SettingsModal/SettingsModal.scss';
@use '../features/system/SettingsModal/ModelList.scss';
@use '../features/system/HotkeysModal/HotkeysModal.scss';
@use '../features/system/Console.scss';