feat(ui): being dismantling old sio stuff, fix recall seed/prompt/init

- still need to fix up metadataviewer's recall features
This commit is contained in:
psychedelicious
2023-04-30 00:57:00 +10:00
parent 2eb7c25bae
commit 258895bcc9
28 changed files with 1231 additions and 1389 deletions

View File

@ -1,7 +1,7 @@
import React, { lazy, memo, PropsWithChildren, useEffect } from 'react'; import React, { lazy, memo, PropsWithChildren, useEffect } from 'react';
import { Provider } from 'react-redux'; import { Provider } from 'react-redux';
import { PersistGate } from 'redux-persist/integration/react'; import { PersistGate } from 'redux-persist/integration/react';
import { buildMiddleware, store } from 'app/store/store'; import { store } from 'app/store/store';
import { persistor } from '../store/persistor'; import { persistor } from '../store/persistor';
import { OpenAPI } from 'services/api'; import { OpenAPI } from 'services/api';
import '@fontsource/inter/100.css'; import '@fontsource/inter/100.css';
@ -19,6 +19,7 @@ import { addMiddleware, resetMiddlewares } from 'redux-dynamic-middlewares';
import { PartialAppConfig } from 'app/types/invokeai'; import { PartialAppConfig } from 'app/types/invokeai';
import '../../i18n'; import '../../i18n';
import { socketMiddleware } from 'services/events/middleware';
const App = lazy(() => import('./App')); const App = lazy(() => import('./App'));
const ThemeLocaleProvider = lazy(() => import('./ThemeLocaleProvider')); const ThemeLocaleProvider = lazy(() => import('./ThemeLocaleProvider'));
@ -50,7 +51,7 @@ const InvokeAIUI = ({ apiUrl, token, config, children }: Props) => {
// the `apiUrl`/`token` dynamically. // the `apiUrl`/`token` dynamically.
// rebuild socket middleware with token and apiUrl // rebuild socket middleware with token and apiUrl
addMiddleware(buildMiddleware()); addMiddleware(socketMiddleware());
}, [apiUrl, token]); }, [apiUrl, token]);
return ( return (

View File

@ -1,7 +1,5 @@
// TODO: use Enums? // TODO: use Enums?
import { InProgressImageType } from 'features/system/store/systemSlice';
export const DIFFUSERS_SCHEDULERS: Array<string> = [ export const DIFFUSERS_SCHEDULERS: Array<string> = [
'ddim', 'ddim',
'plms', 'plms',
@ -33,17 +31,8 @@ export const UPSCALING_LEVELS: Array<{ key: string; value: number }> = [
export const NUMPY_RAND_MIN = 0; export const NUMPY_RAND_MIN = 0;
export const NUMPY_RAND_MAX = 4294967295; export const NUMPY_RAND_MAX = 2147483647;
export const FACETOOL_TYPES = ['gfpgan', 'codeformer'] as const; export const FACETOOL_TYPES = ['gfpgan', 'codeformer'] as const;
export const IN_PROGRESS_IMAGE_TYPES: Array<{
key: string;
value: InProgressImageType;
}> = [
{ key: 'None', value: 'none' },
{ key: 'Fast', value: 'latents' },
{ key: 'Accurate', value: 'full-res' },
];
export const NODE_MIN_WIDTH = 250; export const NODE_MIN_WIDTH = 250;

View File

@ -1,65 +1,67 @@
import { createAction } from '@reduxjs/toolkit'; // import { createAction } from '@reduxjs/toolkit';
import * as InvokeAI from 'app/types/invokeai'; // import * as InvokeAI from 'app/types/invokeai';
import { GalleryCategory } from 'features/gallery/store/gallerySlice'; // import { GalleryCategory } from 'features/gallery/store/gallerySlice';
import { InvokeTabName } from 'features/ui/store/tabMap'; // import { InvokeTabName } from 'features/ui/store/tabMap';
/** // /**
* We can't use redux-toolkit's createSlice() to make these actions, // * We can't use redux-toolkit's createSlice() to make these actions,
* because they have no associated reducer. They only exist to dispatch // * because they have no associated reducer. They only exist to dispatch
* requests to the server via socketio. These actions will be handled // * requests to the server via socketio. These actions will be handled
* by the middleware. // * by the middleware.
*/ // */
export const generateImage = createAction<InvokeTabName>( // export const generateImage = createAction<InvokeTabName>(
'socketio/generateImage' // 'socketio/generateImage'
); // );
export const runESRGAN = createAction<InvokeAI._Image>('socketio/runESRGAN'); // export const runESRGAN = createAction<InvokeAI._Image>('socketio/runESRGAN');
export const runFacetool = createAction<InvokeAI._Image>( // export const runFacetool = createAction<InvokeAI._Image>(
'socketio/runFacetool' // 'socketio/runFacetool'
); // );
export const deleteImage = createAction<InvokeAI._Image>( // export const deleteImage = createAction<InvokeAI._Image>(
'socketio/deleteImage' // 'socketio/deleteImage'
); // );
export const requestImages = createAction<GalleryCategory>( // export const requestImages = createAction<GalleryCategory>(
'socketio/requestImages' // 'socketio/requestImages'
); // );
export const requestNewImages = createAction<GalleryCategory>( // export const requestNewImages = createAction<GalleryCategory>(
'socketio/requestNewImages' // 'socketio/requestNewImages'
); // );
export const cancelProcessing = createAction<undefined>( // export const cancelProcessing = createAction<undefined>(
'socketio/cancelProcessing' // 'socketio/cancelProcessing'
); // );
export const requestSystemConfig = createAction<undefined>( // export const requestSystemConfig = createAction<undefined>(
'socketio/requestSystemConfig' // 'socketio/requestSystemConfig'
); // );
export const searchForModels = createAction<string>('socketio/searchForModels'); // export const searchForModels = createAction<string>('socketio/searchForModels');
export const addNewModel = createAction< // export const addNewModel = createAction<
InvokeAI.InvokeModelConfigProps | InvokeAI.InvokeDiffusersModelConfigProps // InvokeAI.InvokeModelConfigProps | InvokeAI.InvokeDiffusersModelConfigProps
>('socketio/addNewModel'); // >('socketio/addNewModel');
export const deleteModel = createAction<string>('socketio/deleteModel'); // export const deleteModel = createAction<string>('socketio/deleteModel');
export const convertToDiffusers = // export const convertToDiffusers =
createAction<InvokeAI.InvokeModelConversionProps>( // createAction<InvokeAI.InvokeModelConversionProps>(
'socketio/convertToDiffusers' // 'socketio/convertToDiffusers'
); // );
export const mergeDiffusersModels = // export const mergeDiffusersModels =
createAction<InvokeAI.InvokeModelMergingProps>( // createAction<InvokeAI.InvokeModelMergingProps>(
'socketio/mergeDiffusersModels' // 'socketio/mergeDiffusersModels'
); // );
export const requestModelChange = createAction<string>( // export const requestModelChange = createAction<string>(
'socketio/requestModelChange' // 'socketio/requestModelChange'
); // );
export const saveStagingAreaImageToGallery = createAction<string>( // export const saveStagingAreaImageToGallery = createAction<string>(
'socketio/saveStagingAreaImageToGallery' // 'socketio/saveStagingAreaImageToGallery'
); // );
export const emptyTempFolder = createAction<undefined>( // export const emptyTempFolder = createAction<undefined>(
'socketio/requestEmptyTempFolder' // 'socketio/requestEmptyTempFolder'
); // );
export default {};

View File

@ -1,207 +1,209 @@
import { AnyAction, Dispatch, MiddlewareAPI } from '@reduxjs/toolkit'; // import { AnyAction, Dispatch, MiddlewareAPI } from '@reduxjs/toolkit';
import * as InvokeAI from 'app/types/invokeai'; // import * as InvokeAI from 'app/types/invokeai';
import type { RootState } from 'app/store/store'; // import type { RootState } from 'app/store/store';
import { // import {
frontendToBackendParameters, // frontendToBackendParameters,
FrontendToBackendParametersConfig, // FrontendToBackendParametersConfig,
} from 'common/util/parameterTranslation'; // } from 'common/util/parameterTranslation';
import dateFormat from 'dateformat'; // import dateFormat from 'dateformat';
import { // import {
GalleryCategory, // GalleryCategory,
GalleryState, // GalleryState,
removeImage, // removeImage,
} from 'features/gallery/store/gallerySlice'; // } from 'features/gallery/store/gallerySlice';
import { // import {
generationRequested, // generationRequested,
modelChangeRequested, // modelChangeRequested,
modelConvertRequested, // modelConvertRequested,
modelMergingRequested, // modelMergingRequested,
setIsProcessing, // setIsProcessing,
} from 'features/system/store/systemSlice'; // } from 'features/system/store/systemSlice';
import { InvokeTabName } from 'features/ui/store/tabMap'; // import { InvokeTabName } from 'features/ui/store/tabMap';
import { Socket } from 'socket.io-client'; // import { Socket } from 'socket.io-client';
/** // /**
* Returns an object containing all functions which use `socketio.emit()`. // * Returns an object containing all functions which use `socketio.emit()`.
* i.e. those which make server requests. // * i.e. those which make server requests.
*/ // */
const makeSocketIOEmitters = ( // const makeSocketIOEmitters = (
store: MiddlewareAPI<Dispatch<AnyAction>, RootState>, // store: MiddlewareAPI<Dispatch<AnyAction>, RootState>,
socketio: Socket // socketio: Socket
) => { // ) => {
// We need to dispatch actions to redux and get pieces of state from the store. // // We need to dispatch actions to redux and get pieces of state from the store.
const { dispatch, getState } = store; // const { dispatch, getState } = store;
return { // return {
emitGenerateImage: (generationMode: InvokeTabName) => { // emitGenerateImage: (generationMode: InvokeTabName) => {
dispatch(setIsProcessing(true)); // dispatch(setIsProcessing(true));
const state: RootState = getState(); // const state: RootState = getState();
const { // const {
generation: generationState, // generation: generationState,
postprocessing: postprocessingState, // postprocessing: postprocessingState,
system: systemState, // system: systemState,
canvas: canvasState, // canvas: canvasState,
} = state; // } = state;
const frontendToBackendParametersConfig: FrontendToBackendParametersConfig = // const frontendToBackendParametersConfig: FrontendToBackendParametersConfig =
{ // {
generationMode, // generationMode,
generationState, // generationState,
postprocessingState, // postprocessingState,
canvasState, // canvasState,
systemState, // systemState,
}; // };
dispatch(generationRequested()); // dispatch(generationRequested());
const { generationParameters, esrganParameters, facetoolParameters } = // const { generationParameters, esrganParameters, facetoolParameters } =
frontendToBackendParameters(frontendToBackendParametersConfig); // frontendToBackendParameters(frontendToBackendParametersConfig);
socketio.emit( // socketio.emit(
'generateImage', // 'generateImage',
generationParameters, // generationParameters,
esrganParameters, // esrganParameters,
facetoolParameters // facetoolParameters
); // );
// we need to truncate the init_mask base64 else it takes up the whole log // // we need to truncate the init_mask base64 else it takes up the whole log
// TODO: handle maintaining masks for reproducibility in future // // TODO: handle maintaining masks for reproducibility in future
if (generationParameters.init_mask) { // if (generationParameters.init_mask) {
generationParameters.init_mask = generationParameters.init_mask // generationParameters.init_mask = generationParameters.init_mask
.substr(0, 64) // .substr(0, 64)
.concat('...'); // .concat('...');
} // }
if (generationParameters.init_img) { // if (generationParameters.init_img) {
generationParameters.init_img = generationParameters.init_img // generationParameters.init_img = generationParameters.init_img
.substr(0, 64) // .substr(0, 64)
.concat('...'); // .concat('...');
} // }
dispatch( // dispatch(
addLogEntry({ // addLogEntry({
timestamp: dateFormat(new Date(), 'isoDateTime'), // timestamp: dateFormat(new Date(), 'isoDateTime'),
message: `Image generation requested: ${JSON.stringify({ // message: `Image generation requested: ${JSON.stringify({
...generationParameters, // ...generationParameters,
...esrganParameters, // ...esrganParameters,
...facetoolParameters, // ...facetoolParameters,
})}`, // })}`,
}) // })
); // );
}, // },
emitRunESRGAN: (imageToProcess: InvokeAI._Image) => { // emitRunESRGAN: (imageToProcess: InvokeAI._Image) => {
dispatch(setIsProcessing(true)); // dispatch(setIsProcessing(true));
const { // const {
postprocessing: { // postprocessing: {
upscalingLevel, // upscalingLevel,
upscalingDenoising, // upscalingDenoising,
upscalingStrength, // upscalingStrength,
}, // },
} = getState(); // } = getState();
const esrganParameters = { // const esrganParameters = {
upscale: [upscalingLevel, upscalingDenoising, upscalingStrength], // upscale: [upscalingLevel, upscalingDenoising, upscalingStrength],
}; // };
socketio.emit('runPostprocessing', imageToProcess, { // socketio.emit('runPostprocessing', imageToProcess, {
type: 'esrgan', // type: 'esrgan',
...esrganParameters, // ...esrganParameters,
}); // });
dispatch( // dispatch(
addLogEntry({ // addLogEntry({
timestamp: dateFormat(new Date(), 'isoDateTime'), // timestamp: dateFormat(new Date(), 'isoDateTime'),
message: `ESRGAN upscale requested: ${JSON.stringify({ // message: `ESRGAN upscale requested: ${JSON.stringify({
file: imageToProcess.url, // file: imageToProcess.url,
...esrganParameters, // ...esrganParameters,
})}`, // })}`,
}) // })
); // );
}, // },
emitRunFacetool: (imageToProcess: InvokeAI._Image) => { // emitRunFacetool: (imageToProcess: InvokeAI._Image) => {
dispatch(setIsProcessing(true)); // dispatch(setIsProcessing(true));
const { // const {
postprocessing: { facetoolType, facetoolStrength, codeformerFidelity }, // postprocessing: { facetoolType, facetoolStrength, codeformerFidelity },
} = getState(); // } = getState();
const facetoolParameters: Record<string, unknown> = { // const facetoolParameters: Record<string, unknown> = {
facetool_strength: facetoolStrength, // facetool_strength: facetoolStrength,
}; // };
if (facetoolType === 'codeformer') { // if (facetoolType === 'codeformer') {
facetoolParameters.codeformer_fidelity = codeformerFidelity; // facetoolParameters.codeformer_fidelity = codeformerFidelity;
} // }
socketio.emit('runPostprocessing', imageToProcess, { // socketio.emit('runPostprocessing', imageToProcess, {
type: facetoolType, // type: facetoolType,
...facetoolParameters, // ...facetoolParameters,
}); // });
dispatch( // dispatch(
addLogEntry({ // addLogEntry({
timestamp: dateFormat(new Date(), 'isoDateTime'), // timestamp: dateFormat(new Date(), 'isoDateTime'),
message: `Face restoration (${facetoolType}) requested: ${JSON.stringify( // message: `Face restoration (${facetoolType}) requested: ${JSON.stringify(
{ // {
file: imageToProcess.url, // file: imageToProcess.url,
...facetoolParameters, // ...facetoolParameters,
} // }
)}`, // )}`,
}) // })
); // );
}, // },
emitDeleteImage: (imageToDelete: InvokeAI._Image) => { // emitDeleteImage: (imageToDelete: InvokeAI._Image) => {
const { url, uuid, category, thumbnail } = imageToDelete; // const { url, uuid, category, thumbnail } = imageToDelete;
dispatch(removeImage(imageToDelete)); // dispatch(removeImage(imageToDelete));
socketio.emit('deleteImage', url, thumbnail, uuid, category); // socketio.emit('deleteImage', url, thumbnail, uuid, category);
}, // },
emitRequestImages: (category: GalleryCategory) => { // emitRequestImages: (category: GalleryCategory) => {
const gallery: GalleryState = getState().gallery; // const gallery: GalleryState = getState().gallery;
const { earliest_mtime } = gallery.categories[category]; // const { earliest_mtime } = gallery.categories[category];
socketio.emit('requestImages', category, earliest_mtime); // socketio.emit('requestImages', category, earliest_mtime);
}, // },
emitRequestNewImages: (category: GalleryCategory) => { // emitRequestNewImages: (category: GalleryCategory) => {
const gallery: GalleryState = getState().gallery; // const gallery: GalleryState = getState().gallery;
const { latest_mtime } = gallery.categories[category]; // const { latest_mtime } = gallery.categories[category];
socketio.emit('requestLatestImages', category, latest_mtime); // socketio.emit('requestLatestImages', category, latest_mtime);
}, // },
emitCancelProcessing: () => { // emitCancelProcessing: () => {
socketio.emit('cancel'); // socketio.emit('cancel');
}, // },
emitRequestSystemConfig: () => { // emitRequestSystemConfig: () => {
socketio.emit('requestSystemConfig'); // socketio.emit('requestSystemConfig');
}, // },
emitSearchForModels: (modelFolder: string) => { // emitSearchForModels: (modelFolder: string) => {
socketio.emit('searchForModels', modelFolder); // socketio.emit('searchForModels', modelFolder);
}, // },
emitAddNewModel: (modelConfig: InvokeAI.InvokeModelConfigProps) => { // emitAddNewModel: (modelConfig: InvokeAI.InvokeModelConfigProps) => {
socketio.emit('addNewModel', modelConfig); // socketio.emit('addNewModel', modelConfig);
}, // },
emitDeleteModel: (modelName: string) => { // emitDeleteModel: (modelName: string) => {
socketio.emit('deleteModel', modelName); // socketio.emit('deleteModel', modelName);
}, // },
emitConvertToDiffusers: ( // emitConvertToDiffusers: (
modelToConvert: InvokeAI.InvokeModelConversionProps // modelToConvert: InvokeAI.InvokeModelConversionProps
) => { // ) => {
dispatch(modelConvertRequested()); // dispatch(modelConvertRequested());
socketio.emit('convertToDiffusers', modelToConvert); // socketio.emit('convertToDiffusers', modelToConvert);
}, // },
emitMergeDiffusersModels: ( // emitMergeDiffusersModels: (
modelMergeInfo: InvokeAI.InvokeModelMergingProps // modelMergeInfo: InvokeAI.InvokeModelMergingProps
) => { // ) => {
dispatch(modelMergingRequested()); // dispatch(modelMergingRequested());
socketio.emit('mergeDiffusersModels', modelMergeInfo); // socketio.emit('mergeDiffusersModels', modelMergeInfo);
}, // },
emitRequestModelChange: (modelName: string) => { // emitRequestModelChange: (modelName: string) => {
dispatch(modelChangeRequested()); // dispatch(modelChangeRequested());
socketio.emit('requestModelChange', modelName); // socketio.emit('requestModelChange', modelName);
}, // },
emitSaveStagingAreaImageToGallery: (url: string) => { // emitSaveStagingAreaImageToGallery: (url: string) => {
socketio.emit('requestSaveStagingAreaImageToGallery', url); // socketio.emit('requestSaveStagingAreaImageToGallery', url);
}, // },
emitRequestEmptyTempFolder: () => { // emitRequestEmptyTempFolder: () => {
socketio.emit('requestEmptyTempFolder'); // socketio.emit('requestEmptyTempFolder');
}, // },
}; // };
}; // };
export default makeSocketIOEmitters; // export default makeSocketIOEmitters;
export default {};

View File

@ -1,500 +1,502 @@
import { AnyAction, Dispatch, MiddlewareAPI } from '@reduxjs/toolkit'; // import { AnyAction, Dispatch, MiddlewareAPI } from '@reduxjs/toolkit';
import dateFormat from 'dateformat'; // import dateFormat from 'dateformat';
import i18n from 'i18n'; // import i18n from 'i18n';
import { v4 as uuidv4 } from 'uuid'; // import { v4 as uuidv4 } from 'uuid';
import * as InvokeAI from 'app/types/invokeai'; // import * as InvokeAI from 'app/types/invokeai';
import { // import {
addToast, // addToast,
errorOccurred, // errorOccurred,
processingCanceled, // processingCanceled,
setCurrentStatus, // setCurrentStatus,
setFoundModels, // setFoundModels,
setIsCancelable, // setIsCancelable,
setIsConnected, // setIsConnected,
setIsProcessing, // setIsProcessing,
setModelList, // setModelList,
setSearchFolder, // setSearchFolder,
setSystemConfig, // setSystemConfig,
setSystemStatus, // setSystemStatus,
} from 'features/system/store/systemSlice'; // } from 'features/system/store/systemSlice';
import { // import {
addGalleryImages, // addGalleryImages,
addImage, // addImage,
clearIntermediateImage, // clearIntermediateImage,
GalleryState, // GalleryState,
removeImage, // removeImage,
setIntermediateImage, // setIntermediateImage,
} from 'features/gallery/store/gallerySlice'; // } from 'features/gallery/store/gallerySlice';
import type { RootState } from 'app/store/store'; // import type { RootState } from 'app/store/store';
import { addImageToStagingArea } from 'features/canvas/store/canvasSlice'; // import { addImageToStagingArea } from 'features/canvas/store/canvasSlice';
import { // import {
clearInitialImage, // clearInitialImage,
initialImageSelected, // initialImageSelected,
setInfillMethod, // setInfillMethod,
// setInitialImage, // // setInitialImage,
setMaskPath, // setMaskPath,
} from 'features/parameters/store/generationSlice'; // } from 'features/parameters/store/generationSlice';
import { tabMap } from 'features/ui/store/tabMap'; // import { tabMap } from 'features/ui/store/tabMap';
import { // import {
requestImages, // requestImages,
requestNewImages, // requestNewImages,
requestSystemConfig, // requestSystemConfig,
} from './actions'; // } from './actions';
/** // /**
* Returns an object containing listener callbacks for socketio events. // * Returns an object containing listener callbacks for socketio events.
* TODO: This file is large, but simple. Should it be split up further? // * TODO: This file is large, but simple. Should it be split up further?
*/ // */
const makeSocketIOListeners = ( // const makeSocketIOListeners = (
store: MiddlewareAPI<Dispatch<AnyAction>, RootState> // store: MiddlewareAPI<Dispatch<AnyAction>, RootState>
) => { // ) => {
const { dispatch, getState } = store; // const { dispatch, getState } = store;
return { // return {
/** // /**
* Callback to run when we receive a 'connect' event. // * Callback to run when we receive a 'connect' event.
*/ // */
onConnect: () => { // onConnect: () => {
try { // try {
dispatch(setIsConnected(true)); // dispatch(setIsConnected(true));
dispatch(setCurrentStatus(i18n.t('common.statusConnected'))); // dispatch(setCurrentStatus(i18n.t('common.statusConnected')));
dispatch(requestSystemConfig()); // dispatch(requestSystemConfig());
const gallery: GalleryState = getState().gallery; // const gallery: GalleryState = getState().gallery;
if (gallery.categories.result.latest_mtime) { // if (gallery.categories.result.latest_mtime) {
dispatch(requestNewImages('result')); // dispatch(requestNewImages('result'));
} else { // } else {
dispatch(requestImages('result')); // dispatch(requestImages('result'));
} // }
if (gallery.categories.user.latest_mtime) { // if (gallery.categories.user.latest_mtime) {
dispatch(requestNewImages('user')); // dispatch(requestNewImages('user'));
} else { // } else {
dispatch(requestImages('user')); // dispatch(requestImages('user'));
} // }
} catch (e) { // } catch (e) {
console.error(e); // console.error(e);
} // }
}, // },
/** // /**
* Callback to run when we receive a 'disconnect' event. // * Callback to run when we receive a 'disconnect' event.
*/ // */
onDisconnect: () => { // onDisconnect: () => {
try { // try {
dispatch(setIsConnected(false)); // dispatch(setIsConnected(false));
dispatch(setCurrentStatus(i18n.t('common.statusDisconnected'))); // dispatch(setCurrentStatus(i18n.t('common.statusDisconnected')));
dispatch( // dispatch(
addLogEntry({ // addLogEntry({
timestamp: dateFormat(new Date(), 'isoDateTime'), // timestamp: dateFormat(new Date(), 'isoDateTime'),
message: `Disconnected from server`, // message: `Disconnected from server`,
level: 'warning', // level: 'warning',
}) // })
); // );
} catch (e) { // } catch (e) {
console.error(e); // console.error(e);
} // }
}, // },
/** // /**
* Callback to run when we receive a 'generationResult' event. // * Callback to run when we receive a 'generationResult' event.
*/ // */
onGenerationResult: (data: InvokeAI.ImageResultResponse) => { // onGenerationResult: (data: InvokeAI.ImageResultResponse) => {
try { // try {
const state = getState(); // const state = getState();
const { activeTab } = state.ui; // const { activeTab } = state.ui;
const { shouldLoopback } = state.postprocessing; // const { shouldLoopback } = state.postprocessing;
const { boundingBox: _, generationMode, ...rest } = data; // const { boundingBox: _, generationMode, ...rest } = data;
const newImage = { // const newImage = {
uuid: uuidv4(), // uuid: uuidv4(),
...rest, // ...rest,
}; // };
if (['txt2img', 'img2img'].includes(generationMode)) { // if (['txt2img', 'img2img'].includes(generationMode)) {
dispatch( // dispatch(
addImage({ // addImage({
category: 'result', // category: 'result',
image: { ...newImage, category: 'result' }, // image: { ...newImage, category: 'result' },
}) // })
); // );
} // }
if (generationMode === 'unifiedCanvas' && data.boundingBox) { // if (generationMode === 'unifiedCanvas' && data.boundingBox) {
const { boundingBox } = data; // const { boundingBox } = data;
dispatch( // dispatch(
addImageToStagingArea({ // addImageToStagingArea({
image: { ...newImage, category: 'temp' }, // image: { ...newImage, category: 'temp' },
boundingBox, // boundingBox,
}) // })
); // );
if (state.canvas.shouldAutoSave) { // if (state.canvas.shouldAutoSave) {
dispatch( // dispatch(
addImage({ // addImage({
image: { ...newImage, category: 'result' }, // image: { ...newImage, category: 'result' },
category: 'result', // category: 'result',
}) // })
); // );
} // }
} // }
// TODO: fix // // TODO: fix
// if (shouldLoopback) { // // if (shouldLoopback) {
// const activeTabName = tabMap[activeTab]; // // const activeTabName = tabMap[activeTab];
// switch (activeTabName) { // // switch (activeTabName) {
// case 'img2img': { // // case 'img2img': {
// dispatch(initialImageSelected(newImage.uuid)); // // dispatch(initialImageSelected(newImage.uuid));
// // dispatch(setInitialImage(newImage)); // // // dispatch(setInitialImage(newImage));
// break; // // break;
// } // // }
// } // // }
// } // // }
dispatch(clearIntermediateImage()); // dispatch(clearIntermediateImage());
dispatch( // dispatch(
addLogEntry({ // addLogEntry({
timestamp: dateFormat(new Date(), 'isoDateTime'), // timestamp: dateFormat(new Date(), 'isoDateTime'),
message: `Image generated: ${data.url}`, // message: `Image generated: ${data.url}`,
}) // })
); // );
} catch (e) { // } catch (e) {
console.error(e); // console.error(e);
} // }
}, // },
/** // /**
* Callback to run when we receive a 'intermediateResult' event. // * Callback to run when we receive a 'intermediateResult' event.
*/ // */
onIntermediateResult: (data: InvokeAI.ImageResultResponse) => { // onIntermediateResult: (data: InvokeAI.ImageResultResponse) => {
try { // try {
dispatch( // dispatch(
setIntermediateImage({ // setIntermediateImage({
uuid: uuidv4(), // uuid: uuidv4(),
...data, // ...data,
category: 'result', // category: 'result',
}) // })
); // );
if (!data.isBase64) { // if (!data.isBase64) {
dispatch( // dispatch(
addLogEntry({ // addLogEntry({
timestamp: dateFormat(new Date(), 'isoDateTime'), // timestamp: dateFormat(new Date(), 'isoDateTime'),
message: `Intermediate image generated: ${data.url}`, // message: `Intermediate image generated: ${data.url}`,
}) // })
); // );
} // }
} catch (e) { // } catch (e) {
console.error(e); // console.error(e);
} // }
}, // },
/** // /**
* Callback to run when we receive an 'esrganResult' event. // * Callback to run when we receive an 'esrganResult' event.
*/ // */
onPostprocessingResult: (data: InvokeAI.ImageResultResponse) => { // onPostprocessingResult: (data: InvokeAI.ImageResultResponse) => {
try { // try {
dispatch( // dispatch(
addImage({ // addImage({
category: 'result', // category: 'result',
image: { // image: {
uuid: uuidv4(), // uuid: uuidv4(),
...data, // ...data,
category: 'result', // category: 'result',
}, // },
}) // })
); // );
dispatch( // dispatch(
addLogEntry({ // addLogEntry({
timestamp: dateFormat(new Date(), 'isoDateTime'), // timestamp: dateFormat(new Date(), 'isoDateTime'),
message: `Postprocessed: ${data.url}`, // message: `Postprocessed: ${data.url}`,
}) // })
); // );
} catch (e) { // } catch (e) {
console.error(e); // console.error(e);
} // }
}, // },
/** // /**
* Callback to run when we receive a 'progressUpdate' event. // * Callback to run when we receive a 'progressUpdate' event.
* TODO: Add additional progress phases // * TODO: Add additional progress phases
*/ // */
onProgressUpdate: (data: InvokeAI.SystemStatus) => { // onProgressUpdate: (data: InvokeAI.SystemStatus) => {
try { // try {
dispatch(setIsProcessing(true)); // dispatch(setIsProcessing(true));
dispatch(setSystemStatus(data)); // dispatch(setSystemStatus(data));
} catch (e) { // } catch (e) {
console.error(e); // console.error(e);
} // }
}, // },
/** // /**
* Callback to run when we receive a 'progressUpdate' event. // * Callback to run when we receive a 'progressUpdate' event.
*/ // */
onError: (data: InvokeAI.ErrorResponse) => { // onError: (data: InvokeAI.ErrorResponse) => {
const { message, additionalData } = data; // const { message, additionalData } = data;
if (additionalData) { // if (additionalData) {
// TODO: handle more data than short message // // TODO: handle more data than short message
} // }
try { // try {
dispatch( // dispatch(
addLogEntry({ // addLogEntry({
timestamp: dateFormat(new Date(), 'isoDateTime'), // timestamp: dateFormat(new Date(), 'isoDateTime'),
message: `Server error: ${message}`, // message: `Server error: ${message}`,
level: 'error', // level: 'error',
}) // })
); // );
dispatch(errorOccurred()); // dispatch(errorOccurred());
dispatch(clearIntermediateImage()); // dispatch(clearIntermediateImage());
} catch (e) { // } catch (e) {
console.error(e); // console.error(e);
} // }
}, // },
/** // /**
* Callback to run when we receive a 'galleryImages' event. // * Callback to run when we receive a 'galleryImages' event.
*/ // */
onGalleryImages: (data: InvokeAI.GalleryImagesResponse) => { // onGalleryImages: (data: InvokeAI.GalleryImagesResponse) => {
const { images, areMoreImagesAvailable, category } = data; // const { images, areMoreImagesAvailable, category } = data;
/** // /**
* the logic here ideally would be in the reducer but we have a side effect: // * the logic here ideally would be in the reducer but we have a side effect:
* generating a uuid. so the logic needs to be here, outside redux. // * generating a uuid. so the logic needs to be here, outside redux.
*/ // */
// Generate a UUID for each image // // Generate a UUID for each image
const preparedImages = images.map((image): InvokeAI._Image => { // const preparedImages = images.map((image): InvokeAI._Image => {
return { // return {
uuid: uuidv4(), // uuid: uuidv4(),
...image, // ...image,
}; // };
}); // });
dispatch( // dispatch(
addGalleryImages({ // addGalleryImages({
images: preparedImages, // images: preparedImages,
areMoreImagesAvailable, // areMoreImagesAvailable,
category, // category,
}) // })
); // );
dispatch( // dispatch(
addLogEntry({ // addLogEntry({
timestamp: dateFormat(new Date(), 'isoDateTime'), // timestamp: dateFormat(new Date(), 'isoDateTime'),
message: `Loaded ${images.length} images`, // message: `Loaded ${images.length} images`,
}) // })
); // );
}, // },
/** // /**
* Callback to run when we receive a 'processingCanceled' event. // * Callback to run when we receive a 'processingCanceled' event.
*/ // */
onProcessingCanceled: () => { // onProcessingCanceled: () => {
dispatch(processingCanceled()); // dispatch(processingCanceled());
const { intermediateImage } = getState().gallery; // const { intermediateImage } = getState().gallery;
if (intermediateImage) { // if (intermediateImage) {
if (!intermediateImage.isBase64) { // if (!intermediateImage.isBase64) {
dispatch( // dispatch(
addImage({ // addImage({
category: 'result', // category: 'result',
image: intermediateImage, // image: intermediateImage,
}) // })
); // );
dispatch( // dispatch(
addLogEntry({ // addLogEntry({
timestamp: dateFormat(new Date(), 'isoDateTime'), // timestamp: dateFormat(new Date(), 'isoDateTime'),
message: `Intermediate image saved: ${intermediateImage.url}`, // message: `Intermediate image saved: ${intermediateImage.url}`,
}) // })
); // );
} // }
dispatch(clearIntermediateImage()); // dispatch(clearIntermediateImage());
} // }
dispatch( // dispatch(
addLogEntry({ // addLogEntry({
timestamp: dateFormat(new Date(), 'isoDateTime'), // timestamp: dateFormat(new Date(), 'isoDateTime'),
message: `Processing canceled`, // message: `Processing canceled`,
level: 'warning', // level: 'warning',
}) // })
); // );
}, // },
/** // /**
* Callback to run when we receive a 'imageDeleted' event. // * Callback to run when we receive a 'imageDeleted' event.
*/ // */
onImageDeleted: (data: InvokeAI.ImageDeletedResponse) => { // onImageDeleted: (data: InvokeAI.ImageDeletedResponse) => {
const { url } = data; // const { url } = data;
// remove image from gallery // // remove image from gallery
dispatch(removeImage(data)); // dispatch(removeImage(data));
// remove references to image in options // // remove references to image in options
const { // const {
generation: { initialImage, maskPath }, // generation: { initialImage, maskPath },
} = getState(); // } = getState();
if ( // if (
initialImage === url || // initialImage === url ||
(initialImage as InvokeAI._Image)?.url === url // (initialImage as InvokeAI._Image)?.url === url
) { // ) {
dispatch(clearInitialImage()); // dispatch(clearInitialImage());
} // }
if (maskPath === url) { // if (maskPath === url) {
dispatch(setMaskPath('')); // dispatch(setMaskPath(''));
} // }
dispatch( // dispatch(
addLogEntry({ // addLogEntry({
timestamp: dateFormat(new Date(), 'isoDateTime'), // timestamp: dateFormat(new Date(), 'isoDateTime'),
message: `Image deleted: ${url}`, // message: `Image deleted: ${url}`,
}) // })
); // );
}, // },
onSystemConfig: (data: InvokeAI.SystemConfig) => { // onSystemConfig: (data: InvokeAI.SystemConfig) => {
dispatch(setSystemConfig(data)); // dispatch(setSystemConfig(data));
if (!data.infill_methods.includes('patchmatch')) { // if (!data.infill_methods.includes('patchmatch')) {
dispatch(setInfillMethod(data.infill_methods[0])); // dispatch(setInfillMethod(data.infill_methods[0]));
} // }
}, // },
onFoundModels: (data: InvokeAI.FoundModelResponse) => { // onFoundModels: (data: InvokeAI.FoundModelResponse) => {
const { search_folder, found_models } = data; // const { search_folder, found_models } = data;
dispatch(setSearchFolder(search_folder)); // dispatch(setSearchFolder(search_folder));
dispatch(setFoundModels(found_models)); // dispatch(setFoundModels(found_models));
}, // },
onNewModelAdded: (data: InvokeAI.ModelAddedResponse) => { // onNewModelAdded: (data: InvokeAI.ModelAddedResponse) => {
const { new_model_name, model_list, update } = data; // const { new_model_name, model_list, update } = data;
dispatch(setModelList(model_list)); // dispatch(setModelList(model_list));
dispatch(setIsProcessing(false)); // dispatch(setIsProcessing(false));
dispatch(setCurrentStatus(i18n.t('modelManager.modelAdded'))); // dispatch(setCurrentStatus(i18n.t('modelManager.modelAdded')));
dispatch( // dispatch(
addLogEntry({ // addLogEntry({
timestamp: dateFormat(new Date(), 'isoDateTime'), // timestamp: dateFormat(new Date(), 'isoDateTime'),
message: `Model Added: ${new_model_name}`, // message: `Model Added: ${new_model_name}`,
level: 'info', // level: 'info',
}) // })
); // );
dispatch( // dispatch(
addToast({ // addToast({
title: !update // title: !update
? `${i18n.t('modelManager.modelAdded')}: ${new_model_name}` // ? `${i18n.t('modelManager.modelAdded')}: ${new_model_name}`
: `${i18n.t('modelManager.modelUpdated')}: ${new_model_name}`, // : `${i18n.t('modelManager.modelUpdated')}: ${new_model_name}`,
status: 'success', // status: 'success',
duration: 2500, // duration: 2500,
isClosable: true, // isClosable: true,
}) // })
); // );
}, // },
onModelDeleted: (data: InvokeAI.ModelDeletedResponse) => { // onModelDeleted: (data: InvokeAI.ModelDeletedResponse) => {
const { deleted_model_name, model_list } = data; // const { deleted_model_name, model_list } = data;
dispatch(setModelList(model_list)); // dispatch(setModelList(model_list));
dispatch(setIsProcessing(false)); // dispatch(setIsProcessing(false));
dispatch( // dispatch(
addLogEntry({ // addLogEntry({
timestamp: dateFormat(new Date(), 'isoDateTime'), // timestamp: dateFormat(new Date(), 'isoDateTime'),
message: `${i18n.t( // message: `${i18n.t(
'modelManager.modelAdded' // 'modelManager.modelAdded'
)}: ${deleted_model_name}`, // )}: ${deleted_model_name}`,
level: 'info', // level: 'info',
}) // })
); // );
dispatch( // dispatch(
addToast({ // addToast({
title: `${i18n.t( // title: `${i18n.t(
'modelManager.modelEntryDeleted' // 'modelManager.modelEntryDeleted'
)}: ${deleted_model_name}`, // )}: ${deleted_model_name}`,
status: 'success', // status: 'success',
duration: 2500, // duration: 2500,
isClosable: true, // isClosable: true,
}) // })
); // );
}, // },
onModelConverted: (data: InvokeAI.ModelConvertedResponse) => { // onModelConverted: (data: InvokeAI.ModelConvertedResponse) => {
const { converted_model_name, model_list } = data; // const { converted_model_name, model_list } = data;
dispatch(setModelList(model_list)); // dispatch(setModelList(model_list));
dispatch(setCurrentStatus(i18n.t('common.statusModelConverted'))); // dispatch(setCurrentStatus(i18n.t('common.statusModelConverted')));
dispatch(setIsProcessing(false)); // dispatch(setIsProcessing(false));
dispatch(setIsCancelable(true)); // dispatch(setIsCancelable(true));
dispatch( // dispatch(
addLogEntry({ // addLogEntry({
timestamp: dateFormat(new Date(), 'isoDateTime'), // timestamp: dateFormat(new Date(), 'isoDateTime'),
message: `Model converted: ${converted_model_name}`, // message: `Model converted: ${converted_model_name}`,
level: 'info', // level: 'info',
}) // })
); // );
dispatch( // dispatch(
addToast({ // addToast({
title: `${i18n.t( // title: `${i18n.t(
'modelManager.modelConverted' // 'modelManager.modelConverted'
)}: ${converted_model_name}`, // )}: ${converted_model_name}`,
status: 'success', // status: 'success',
duration: 2500, // duration: 2500,
isClosable: true, // isClosable: true,
}) // })
); // );
}, // },
onModelsMerged: (data: InvokeAI.ModelsMergedResponse) => { // onModelsMerged: (data: InvokeAI.ModelsMergedResponse) => {
const { merged_models, merged_model_name, model_list } = data; // const { merged_models, merged_model_name, model_list } = data;
dispatch(setModelList(model_list)); // dispatch(setModelList(model_list));
dispatch(setCurrentStatus(i18n.t('common.statusMergedModels'))); // dispatch(setCurrentStatus(i18n.t('common.statusMergedModels')));
dispatch(setIsProcessing(false)); // dispatch(setIsProcessing(false));
dispatch(setIsCancelable(true)); // dispatch(setIsCancelable(true));
dispatch( // dispatch(
addLogEntry({ // addLogEntry({
timestamp: dateFormat(new Date(), 'isoDateTime'), // timestamp: dateFormat(new Date(), 'isoDateTime'),
message: `Models merged: ${merged_models}`, // message: `Models merged: ${merged_models}`,
level: 'info', // level: 'info',
}) // })
); // );
dispatch( // dispatch(
addToast({ // addToast({
title: `${i18n.t('modelManager.modelsMerged')}: ${merged_model_name}`, // title: `${i18n.t('modelManager.modelsMerged')}: ${merged_model_name}`,
status: 'success', // status: 'success',
duration: 2500, // duration: 2500,
isClosable: true, // isClosable: true,
}) // })
); // );
}, // },
onModelChanged: (data: InvokeAI.ModelChangeResponse) => { // onModelChanged: (data: InvokeAI.ModelChangeResponse) => {
const { model_name, model_list } = data; // const { model_name, model_list } = data;
dispatch(setModelList(model_list)); // dispatch(setModelList(model_list));
dispatch(setCurrentStatus(i18n.t('common.statusModelChanged'))); // dispatch(setCurrentStatus(i18n.t('common.statusModelChanged')));
dispatch(setIsProcessing(false)); // dispatch(setIsProcessing(false));
dispatch(setIsCancelable(true)); // dispatch(setIsCancelable(true));
dispatch( // dispatch(
addLogEntry({ // addLogEntry({
timestamp: dateFormat(new Date(), 'isoDateTime'), // timestamp: dateFormat(new Date(), 'isoDateTime'),
message: `Model changed: ${model_name}`, // message: `Model changed: ${model_name}`,
level: 'info', // level: 'info',
}) // })
); // );
}, // },
onModelChangeFailed: (data: InvokeAI.ModelChangeResponse) => { // onModelChangeFailed: (data: InvokeAI.ModelChangeResponse) => {
const { model_name, model_list } = data; // const { model_name, model_list } = data;
dispatch(setModelList(model_list)); // dispatch(setModelList(model_list));
dispatch(setIsProcessing(false)); // dispatch(setIsProcessing(false));
dispatch(setIsCancelable(true)); // dispatch(setIsCancelable(true));
dispatch(errorOccurred()); // dispatch(errorOccurred());
dispatch( // dispatch(
addLogEntry({ // addLogEntry({
timestamp: dateFormat(new Date(), 'isoDateTime'), // timestamp: dateFormat(new Date(), 'isoDateTime'),
message: `Model change failed: ${model_name}`, // message: `Model change failed: ${model_name}`,
level: 'error', // level: 'error',
}) // })
); // );
}, // },
onTempFolderEmptied: () => { // onTempFolderEmptied: () => {
dispatch( // dispatch(
addToast({ // addToast({
title: i18n.t('toast.tempFoldersEmptied'), // title: i18n.t('toast.tempFoldersEmptied'),
status: 'success', // status: 'success',
duration: 2500, // duration: 2500,
isClosable: true, // isClosable: true,
}) // })
); // );
}, // },
}; // };
}; // };
export default makeSocketIOListeners; // export default makeSocketIOListeners;
export default {};

View File

@ -1,246 +1,248 @@
import { Middleware } from '@reduxjs/toolkit'; // import { Middleware } from '@reduxjs/toolkit';
import { io } from 'socket.io-client'; // import { io } from 'socket.io-client';
import makeSocketIOEmitters from './emitters'; // import makeSocketIOEmitters from './emitters';
import makeSocketIOListeners from './listeners'; // import makeSocketIOListeners from './listeners';
import * as InvokeAI from 'app/types/invokeai'; // import * as InvokeAI from 'app/types/invokeai';
/** // /**
* Creates a socketio middleware to handle communication with server. // * Creates a socketio middleware to handle communication with server.
* // *
* Special `socketio/actionName` actions are created in actions.ts and // * Special `socketio/actionName` actions are created in actions.ts and
* exported for use by the application, which treats them like any old // * exported for use by the application, which treats them like any old
* action, using `dispatch` to dispatch them. // * action, using `dispatch` to dispatch them.
* // *
* These actions are intercepted here, where `socketio.emit()` calls are // * These actions are intercepted here, where `socketio.emit()` calls are
* made on their behalf - see `emitters.ts`. The emitter functions // * made on their behalf - see `emitters.ts`. The emitter functions
* are the outbound communication to the server. // * are the outbound communication to the server.
* // *
* Listeners are also established here - see `listeners.ts`. The listener // * Listeners are also established here - see `listeners.ts`. The listener
* functions receive communication from the server and usually dispatch // * functions receive communication from the server and usually dispatch
* some new action to handle whatever data was sent from the server. // * some new action to handle whatever data was sent from the server.
*/ // */
export const socketioMiddleware = () => { // export const socketioMiddleware = () => {
const { origin } = new URL(window.location.href); // const { origin } = new URL(window.location.href);
const socketio = io(origin, { // const socketio = io(origin, {
timeout: 60000, // timeout: 60000,
path: `${window.location.pathname}socket.io`, // path: `${window.location.pathname}socket.io`,
}); // });
socketio.disconnect(); // socketio.disconnect();
let areListenersSet = false; // let areListenersSet = false;
const middleware: Middleware = (store) => (next) => (action) => { // const middleware: Middleware = (store) => (next) => (action) => {
const { // const {
onConnect, // onConnect,
onDisconnect, // onDisconnect,
onError, // onError,
onPostprocessingResult, // onPostprocessingResult,
onGenerationResult, // onGenerationResult,
onIntermediateResult, // onIntermediateResult,
onProgressUpdate, // onProgressUpdate,
onGalleryImages, // onGalleryImages,
onProcessingCanceled, // onProcessingCanceled,
onImageDeleted, // onImageDeleted,
onSystemConfig, // onSystemConfig,
onModelChanged, // onModelChanged,
onFoundModels, // onFoundModels,
onNewModelAdded, // onNewModelAdded,
onModelDeleted, // onModelDeleted,
onModelConverted, // onModelConverted,
onModelsMerged, // onModelsMerged,
onModelChangeFailed, // onModelChangeFailed,
onTempFolderEmptied, // onTempFolderEmptied,
} = makeSocketIOListeners(store); // } = makeSocketIOListeners(store);
const { // const {
emitGenerateImage, // emitGenerateImage,
emitRunESRGAN, // emitRunESRGAN,
emitRunFacetool, // emitRunFacetool,
emitDeleteImage, // emitDeleteImage,
emitRequestImages, // emitRequestImages,
emitRequestNewImages, // emitRequestNewImages,
emitCancelProcessing, // emitCancelProcessing,
emitRequestSystemConfig, // emitRequestSystemConfig,
emitSearchForModels, // emitSearchForModels,
emitAddNewModel, // emitAddNewModel,
emitDeleteModel, // emitDeleteModel,
emitConvertToDiffusers, // emitConvertToDiffusers,
emitMergeDiffusersModels, // emitMergeDiffusersModels,
emitRequestModelChange, // emitRequestModelChange,
emitSaveStagingAreaImageToGallery, // emitSaveStagingAreaImageToGallery,
emitRequestEmptyTempFolder, // emitRequestEmptyTempFolder,
} = makeSocketIOEmitters(store, socketio); // } = makeSocketIOEmitters(store, socketio);
/** // /**
* If this is the first time the middleware has been called (e.g. during store setup), // * If this is the first time the middleware has been called (e.g. during store setup),
* initialize all our socket.io listeners. // * initialize all our socket.io listeners.
*/ // */
if (!areListenersSet) { // if (!areListenersSet) {
socketio.on('connect', () => onConnect()); // socketio.on('connect', () => onConnect());
socketio.on('disconnect', () => onDisconnect()); // socketio.on('disconnect', () => onDisconnect());
socketio.on('error', (data: InvokeAI.ErrorResponse) => onError(data)); // socketio.on('error', (data: InvokeAI.ErrorResponse) => onError(data));
socketio.on('generationResult', (data: InvokeAI.ImageResultResponse) => // socketio.on('generationResult', (data: InvokeAI.ImageResultResponse) =>
onGenerationResult(data) // onGenerationResult(data)
); // );
socketio.on( // socketio.on(
'postprocessingResult', // 'postprocessingResult',
(data: InvokeAI.ImageResultResponse) => onPostprocessingResult(data) // (data: InvokeAI.ImageResultResponse) => onPostprocessingResult(data)
); // );
socketio.on('intermediateResult', (data: InvokeAI.ImageResultResponse) => // socketio.on('intermediateResult', (data: InvokeAI.ImageResultResponse) =>
onIntermediateResult(data) // onIntermediateResult(data)
); // );
socketio.on('progressUpdate', (data: InvokeAI.SystemStatus) => // socketio.on('progressUpdate', (data: InvokeAI.SystemStatus) =>
onProgressUpdate(data) // onProgressUpdate(data)
); // );
socketio.on('galleryImages', (data: InvokeAI.GalleryImagesResponse) => // socketio.on('galleryImages', (data: InvokeAI.GalleryImagesResponse) =>
onGalleryImages(data) // onGalleryImages(data)
); // );
socketio.on('processingCanceled', () => { // socketio.on('processingCanceled', () => {
onProcessingCanceled(); // onProcessingCanceled();
}); // });
socketio.on('imageDeleted', (data: InvokeAI.ImageDeletedResponse) => { // socketio.on('imageDeleted', (data: InvokeAI.ImageDeletedResponse) => {
onImageDeleted(data); // onImageDeleted(data);
}); // });
socketio.on('systemConfig', (data: InvokeAI.SystemConfig) => { // socketio.on('systemConfig', (data: InvokeAI.SystemConfig) => {
onSystemConfig(data); // onSystemConfig(data);
}); // });
socketio.on('foundModels', (data: InvokeAI.FoundModelResponse) => { // socketio.on('foundModels', (data: InvokeAI.FoundModelResponse) => {
onFoundModels(data); // onFoundModels(data);
}); // });
socketio.on('newModelAdded', (data: InvokeAI.ModelAddedResponse) => { // socketio.on('newModelAdded', (data: InvokeAI.ModelAddedResponse) => {
onNewModelAdded(data); // onNewModelAdded(data);
}); // });
socketio.on('modelDeleted', (data: InvokeAI.ModelDeletedResponse) => { // socketio.on('modelDeleted', (data: InvokeAI.ModelDeletedResponse) => {
onModelDeleted(data); // onModelDeleted(data);
}); // });
socketio.on('modelConverted', (data: InvokeAI.ModelConvertedResponse) => { // socketio.on('modelConverted', (data: InvokeAI.ModelConvertedResponse) => {
onModelConverted(data); // onModelConverted(data);
}); // });
socketio.on('modelsMerged', (data: InvokeAI.ModelsMergedResponse) => { // socketio.on('modelsMerged', (data: InvokeAI.ModelsMergedResponse) => {
onModelsMerged(data); // onModelsMerged(data);
}); // });
socketio.on('modelChanged', (data: InvokeAI.ModelChangeResponse) => { // socketio.on('modelChanged', (data: InvokeAI.ModelChangeResponse) => {
onModelChanged(data); // onModelChanged(data);
}); // });
socketio.on('modelChangeFailed', (data: InvokeAI.ModelChangeResponse) => { // socketio.on('modelChangeFailed', (data: InvokeAI.ModelChangeResponse) => {
onModelChangeFailed(data); // onModelChangeFailed(data);
}); // });
socketio.on('tempFolderEmptied', () => { // socketio.on('tempFolderEmptied', () => {
onTempFolderEmptied(); // onTempFolderEmptied();
}); // });
areListenersSet = true; // areListenersSet = true;
} // }
/** // /**
* Handle redux actions caught by middleware. // * Handle redux actions caught by middleware.
*/ // */
switch (action.type) { // switch (action.type) {
case 'socketio/generateImage': { // case 'socketio/generateImage': {
emitGenerateImage(action.payload); // emitGenerateImage(action.payload);
break; // break;
} // }
case 'socketio/runESRGAN': { // case 'socketio/runESRGAN': {
emitRunESRGAN(action.payload); // emitRunESRGAN(action.payload);
break; // break;
} // }
case 'socketio/runFacetool': { // case 'socketio/runFacetool': {
emitRunFacetool(action.payload); // emitRunFacetool(action.payload);
break; // break;
} // }
case 'socketio/deleteImage': { // case 'socketio/deleteImage': {
emitDeleteImage(action.payload); // emitDeleteImage(action.payload);
break; // break;
} // }
case 'socketio/requestImages': { // case 'socketio/requestImages': {
emitRequestImages(action.payload); // emitRequestImages(action.payload);
break; // break;
} // }
case 'socketio/requestNewImages': { // case 'socketio/requestNewImages': {
emitRequestNewImages(action.payload); // emitRequestNewImages(action.payload);
break; // break;
} // }
case 'socketio/cancelProcessing': { // case 'socketio/cancelProcessing': {
emitCancelProcessing(); // emitCancelProcessing();
break; // break;
} // }
case 'socketio/requestSystemConfig': { // case 'socketio/requestSystemConfig': {
emitRequestSystemConfig(); // emitRequestSystemConfig();
break; // break;
} // }
case 'socketio/searchForModels': { // case 'socketio/searchForModels': {
emitSearchForModels(action.payload); // emitSearchForModels(action.payload);
break; // break;
} // }
case 'socketio/addNewModel': { // case 'socketio/addNewModel': {
emitAddNewModel(action.payload); // emitAddNewModel(action.payload);
break; // break;
} // }
case 'socketio/deleteModel': { // case 'socketio/deleteModel': {
emitDeleteModel(action.payload); // emitDeleteModel(action.payload);
break; // break;
} // }
case 'socketio/convertToDiffusers': { // case 'socketio/convertToDiffusers': {
emitConvertToDiffusers(action.payload); // emitConvertToDiffusers(action.payload);
break; // break;
} // }
case 'socketio/mergeDiffusersModels': { // case 'socketio/mergeDiffusersModels': {
emitMergeDiffusersModels(action.payload); // emitMergeDiffusersModels(action.payload);
break; // break;
} // }
case 'socketio/requestModelChange': { // case 'socketio/requestModelChange': {
emitRequestModelChange(action.payload); // emitRequestModelChange(action.payload);
break; // break;
} // }
case 'socketio/saveStagingAreaImageToGallery': { // case 'socketio/saveStagingAreaImageToGallery': {
emitSaveStagingAreaImageToGallery(action.payload); // emitSaveStagingAreaImageToGallery(action.payload);
break; // break;
} // }
case 'socketio/requestEmptyTempFolder': { // case 'socketio/requestEmptyTempFolder': {
emitRequestEmptyTempFolder(); // emitRequestEmptyTempFolder();
break; // break;
} // }
} // }
next(action); // next(action);
}; // };
return middleware; // return middleware;
}; // };
export default {};

View File

@ -19,7 +19,6 @@ import hotkeysReducer from 'features/ui/store/hotkeysSlice';
import modelsReducer from 'features/system/store/modelSlice'; import modelsReducer from 'features/system/store/modelSlice';
import nodesReducer from 'features/nodes/store/nodesSlice'; import nodesReducer from 'features/nodes/store/nodesSlice';
import { socketioMiddleware } from '../socketio/middleware';
import { socketMiddleware } from 'services/events/middleware'; import { socketMiddleware } from 'services/events/middleware';
import { canvasDenylist } from 'features/canvas/store/canvasPersistDenylist'; import { canvasDenylist } from 'features/canvas/store/canvasPersistDenylist';
import { galleryDenylist } from 'features/gallery/store/galleryPersistDenylist'; import { galleryDenylist } from 'features/gallery/store/galleryPersistDenylist';
@ -87,13 +86,13 @@ const rootPersistConfig = getPersistConfig({
const persistedReducer = persistReducer(rootPersistConfig, rootReducer); const persistedReducer = persistReducer(rootPersistConfig, rootReducer);
// TODO: rip the old middleware out when nodes is complete // TODO: rip the old middleware out when nodes is complete
export function buildMiddleware() { // export function buildMiddleware() {
if (import.meta.env.MODE === 'nodes' || import.meta.env.MODE === 'package') { // if (import.meta.env.MODE === 'nodes' || import.meta.env.MODE === 'package') {
return socketMiddleware(); // return socketMiddleware();
} else { // } else {
return socketioMiddleware(); // return socketioMiddleware();
} // }
} // }
export const store = configureStore({ export const store = configureStore({
reducer: persistedReducer, reducer: persistedReducer,

View File

@ -111,9 +111,9 @@ export type FacetoolMetadata = CommonPostProcessedImageMetadata & {
export type PostProcessedImageMetadata = ESRGANMetadata | FacetoolMetadata; export type PostProcessedImageMetadata = ESRGANMetadata | FacetoolMetadata;
// Metadata includes the system config and image metadata. // Metadata includes the system config and image metadata.
export type Metadata = SystemGenerationMetadata & { // export type Metadata = SystemGenerationMetadata & {
image: GeneratedImageMetadata | PostProcessedImageMetadata; // image: GeneratedImageMetadata | PostProcessedImageMetadata;
}; // };
// An Image has a UUID, url, modified timestamp, width, height and maybe metadata // An Image has a UUID, url, modified timestamp, width, height and maybe metadata
export type _Image = { export type _Image = {
@ -150,31 +150,31 @@ export type GalleryImages = {
* Types related to the system status. * Types related to the system status.
*/ */
// This represents the processing status of the backend. // // This represents the processing status of the backend.
export type SystemStatus = { // export type SystemStatus = {
isProcessing: boolean; // isProcessing: boolean;
currentStep: number; // currentStep: number;
totalSteps: number; // totalSteps: number;
currentIteration: number; // currentIteration: number;
totalIterations: number; // totalIterations: number;
currentStatus: string; // currentStatus: string;
currentStatusHasSteps: boolean; // currentStatusHasSteps: boolean;
hasError: boolean; // hasError: boolean;
}; // };
export type SystemGenerationMetadata = { // export type SystemGenerationMetadata = {
model: string; // model: string;
model_weights?: string; // model_weights?: string;
model_id?: string; // model_id?: string;
model_hash: string; // model_hash: string;
app_id: string; // app_id: string;
app_version: string; // app_version: string;
}; // };
export type SystemConfig = SystemGenerationMetadata & { // export type SystemConfig = SystemGenerationMetadata & {
model_list: ModelList; // model_list: ModelList;
infill_methods: string[]; // infill_methods: string[];
}; // };
export type ModelStatus = 'active' | 'cached' | 'not loaded'; export type ModelStatus = 'active' | 'cached' | 'not loaded';
@ -286,9 +286,9 @@ export type FoundModelResponse = {
found_models: FoundModel[]; found_models: FoundModel[];
}; };
export type SystemStatusResponse = SystemStatus; // export type SystemStatusResponse = SystemStatus;
export type SystemConfigResponse = SystemConfig; // export type SystemConfigResponse = SystemConfig;
export type ImageResultResponse = Omit<_Image, 'uuid'> & { export type ImageResultResponse = Omit<_Image, 'uuid'> & {
boundingBox?: IRect; boundingBox?: IRect;

View File

@ -1,6 +1,6 @@
import { ButtonGroup, Flex } from '@chakra-ui/react'; import { ButtonGroup, Flex } from '@chakra-ui/react';
import { createSelector } from '@reduxjs/toolkit'; import { createSelector } from '@reduxjs/toolkit';
import { saveStagingAreaImageToGallery } from 'app/socketio/actions'; // import { saveStagingAreaImageToGallery } from 'app/socketio/actions';
import { useAppDispatch, useAppSelector } from 'app/store/storeHooks'; import { useAppDispatch, useAppSelector } from 'app/store/storeHooks';
import IAIIconButton from 'common/components/IAIIconButton'; import IAIIconButton from 'common/components/IAIIconButton';
import { canvasSelector } from 'features/canvas/store/canvasSelectors'; import { canvasSelector } from 'features/canvas/store/canvasSelectors';

View File

@ -1,5 +1,5 @@
import { createSelector } from '@reduxjs/toolkit'; import { createSelector } from '@reduxjs/toolkit';
import { isEqual } from 'lodash-es'; import { get, isEqual, isNumber, isString } from 'lodash-es';
import { import {
ButtonGroup, ButtonGroup,
@ -10,7 +10,7 @@ import {
useDisclosure, useDisclosure,
useToast, useToast,
} from '@chakra-ui/react'; } from '@chakra-ui/react';
import { runESRGAN, runFacetool } from 'app/socketio/actions'; // import { runESRGAN, runFacetool } from 'app/socketio/actions';
import { useAppDispatch, useAppSelector } from 'app/store/storeHooks'; import { useAppDispatch, useAppSelector } from 'app/store/storeHooks';
import IAIButton from 'common/components/IAIButton'; import IAIButton from 'common/components/IAIButton';
import IAIIconButton from 'common/components/IAIIconButton'; import IAIIconButton from 'common/components/IAIIconButton';
@ -68,6 +68,7 @@ import { requestCanvasRescale } from 'features/canvas/store/thunks/requestCanvas
import { useGetUrl } from 'common/util/getUrl'; import { useGetUrl } from 'common/util/getUrl';
import { useFeatureStatus } from 'features/system/hooks/useFeatureStatus'; import { useFeatureStatus } from 'features/system/hooks/useFeatureStatus';
import { imageDeleted } from 'services/thunks/image'; import { imageDeleted } from 'services/thunks/image';
import { useParameters } from 'features/parameters/hooks/useParameters';
const currentImageButtonsSelector = createSelector( const currentImageButtonsSelector = createSelector(
[ [
@ -112,6 +113,8 @@ const currentImageButtonsSelector = createSelector(
isLightboxOpen, isLightboxOpen,
shouldHidePreview, shouldHidePreview,
image, image,
seed: image?.metadata?.invokeai?.node?.seed,
prompt: image?.metadata?.invokeai?.node?.prompt,
}; };
}, },
{ {
@ -161,16 +164,8 @@ const CurrentImageButtons = (props: CurrentImageButtonsProps) => {
const toast = useToast(); const toast = useToast();
const { t } = useTranslation(); const { t } = useTranslation();
const setBothPrompts = useSetBothPrompts();
const handleClickUseAsInitialImage = useCallback(() => { const { recallPrompt, recallSeed, sendToImageToImage } = useParameters();
if (!image) return;
if (isLightboxOpen) dispatch(setIsLightboxOpen(false));
dispatch(initialImageSelected({ name: image.name, type: image.type }));
// dispatch(setInitialImage(currentImage));
// dispatch(setActiveTab('img2img'));
}, [dispatch, image, isLightboxOpen]);
const handleCopyImage = useCallback(async () => { const handleCopyImage = useCallback(async () => {
if (!image?.url) { if (!image?.url) {
@ -217,30 +212,6 @@ const CurrentImageButtons = (props: CurrentImageButtonsProps) => {
}); });
}, [toast, shouldTransformUrls, getUrl, t, image]); }, [toast, shouldTransformUrls, getUrl, t, image]);
useHotkeys(
'shift+i',
() => {
if (image) {
handleClickUseAsInitialImage();
toast({
title: t('toast.sentToImageToImage'),
status: 'success',
duration: 2500,
isClosable: true,
});
} else {
toast({
title: t('toast.imageNotLoaded'),
description: t('toast.imageNotLoadedDesc'),
status: 'error',
duration: 2500,
isClosable: true,
});
}
},
[image]
);
const handlePreviewVisibility = useCallback(() => { const handlePreviewVisibility = useCallback(() => {
dispatch(setShouldHidePreview(!shouldHidePreview)); dispatch(setShouldHidePreview(!shouldHidePreview));
}, [dispatch, shouldHidePreview]); }, [dispatch, shouldHidePreview]);
@ -259,7 +230,8 @@ const CurrentImageButtons = (props: CurrentImageButtonsProps) => {
useHotkeys( useHotkeys(
'a', 'a',
() => { () => {
if (['txt2img', 'img2img'].includes(image?.metadata?.sd_metadata?.type)) { const type = image?.metadata?.invokeai?.node?.types;
if (isString(type) && ['txt2img', 'img2img'].includes(type)) {
handleClickUseAllParameters(); handleClickUseAllParameters();
toast({ toast({
title: t('toast.parametersSet'), title: t('toast.parametersSet'),
@ -280,63 +252,23 @@ const CurrentImageButtons = (props: CurrentImageButtonsProps) => {
[image] [image]
); );
const handleClickUseSeed = () => { const handleUseSeed = useCallback(() => {
image?.metadata && dispatch(setSeed(image.metadata.sd_metadata.seed)); recallSeed(image?.metadata?.invokeai?.node?.seed);
}; }, [image, recallSeed]);
useHotkeys( useHotkeys('s', handleUseSeed, [image]);
's',
() => {
if (image?.metadata?.sd_metadata?.seed) {
handleClickUseSeed();
toast({
title: t('toast.seedSet'),
status: 'success',
duration: 2500,
isClosable: true,
});
} else {
toast({
title: t('toast.seedNotSet'),
description: t('toast.seedNotSetDesc'),
status: 'error',
duration: 2500,
isClosable: true,
});
}
},
[image]
);
const handleClickUsePrompt = useCallback(() => { const handleUsePrompt = useCallback(() => {
if (image?.metadata?.sd_metadata?.prompt) { recallPrompt(image?.metadata?.invokeai?.node?.prompt);
setBothPrompts(image?.metadata?.sd_metadata?.prompt); }, [image, recallPrompt]);
}
}, [image?.metadata?.sd_metadata?.prompt, setBothPrompts]);
useHotkeys( useHotkeys('p', handleUsePrompt, [image]);
'p',
() => { const handleSendToImageToImage = useCallback(() => {
if (image?.metadata?.sd_metadata?.prompt) { sendToImageToImage(image);
handleClickUsePrompt(); }, [image, sendToImageToImage]);
toast({
title: t('toast.promptSet'), useHotkeys('shift+i', handleSendToImageToImage, [image]);
status: 'success',
duration: 2500,
isClosable: true,
});
} else {
toast({
title: t('toast.promptNotSet'),
description: t('toast.promptNotSetDesc'),
status: 'error',
duration: 2500,
isClosable: true,
});
}
},
[image]
);
const handleClickUpscale = useCallback(() => { const handleClickUpscale = useCallback(() => {
// selectedImage && dispatch(runESRGAN(selectedImage)); // selectedImage && dispatch(runESRGAN(selectedImage));
@ -496,7 +428,7 @@ const CurrentImageButtons = (props: CurrentImageButtonsProps) => {
> >
<IAIButton <IAIButton
size="sm" size="sm"
onClick={handleClickUseAsInitialImage} onClick={handleSendToImageToImage}
leftIcon={<FaShare />} leftIcon={<FaShare />}
> >
{t('parameters.sendToImg2Img')} {t('parameters.sendToImg2Img')}
@ -570,8 +502,8 @@ const CurrentImageButtons = (props: CurrentImageButtonsProps) => {
icon={<FaQuoteRight />} icon={<FaQuoteRight />}
tooltip={`${t('parameters.usePrompt')} (P)`} tooltip={`${t('parameters.usePrompt')} (P)`}
aria-label={`${t('parameters.usePrompt')} (P)`} aria-label={`${t('parameters.usePrompt')} (P)`}
isDisabled={!image?.metadata?.sd_metadata?.prompt} isDisabled={!image?.metadata?.invokeai?.node?.prompt}
onClick={handleClickUsePrompt} onClick={handleUsePrompt}
/> />
<IAIIconButton <IAIIconButton
@ -579,7 +511,7 @@ const CurrentImageButtons = (props: CurrentImageButtonsProps) => {
tooltip={`${t('parameters.useSeed')} (S)`} tooltip={`${t('parameters.useSeed')} (S)`}
aria-label={`${t('parameters.useSeed')} (S)`} aria-label={`${t('parameters.useSeed')} (S)`}
isDisabled={!image?.metadata?.sd_metadata?.seed} isDisabled={!image?.metadata?.sd_metadata?.seed}
onClick={handleClickUseSeed} onClick={handleUseSeed}
/> />
<IAIIconButton <IAIIconButton

View File

@ -5,65 +5,37 @@ import {
Image, Image,
MenuItem, MenuItem,
MenuList, MenuList,
Text,
useDisclosure, useDisclosure,
useTheme, useTheme,
useToast, useToast,
} from '@chakra-ui/react'; } from '@chakra-ui/react';
import { useAppDispatch, useAppSelector } from 'app/store/storeHooks'; import { useAppDispatch, useAppSelector } from 'app/store/storeHooks';
import { import { imageSelected } from 'features/gallery/store/gallerySlice';
imageSelected, import { DragEvent, memo, useCallback, useState } from 'react';
setCurrentImage, import { FaCheck, FaExpand, FaShare, FaTrash } from 'react-icons/fa';
} from 'features/gallery/store/gallerySlice';
import {
initialImageSelected,
setAllImageToImageParameters,
setAllParameters,
setSeed,
} from 'features/parameters/store/generationSlice';
import { DragEvent, memo, useState } from 'react';
import {
FaCheck,
FaExpand,
FaLink,
FaShare,
FaTrash,
FaTrashAlt,
} from 'react-icons/fa';
import DeleteImageModal from './DeleteImageModal'; import DeleteImageModal from './DeleteImageModal';
import { ContextMenu } from 'chakra-ui-contextmenu'; import { ContextMenu } from 'chakra-ui-contextmenu';
import * as InvokeAI from 'app/types/invokeai'; import * as InvokeAI from 'app/types/invokeai';
import { import { resizeAndScaleCanvas } from 'features/canvas/store/canvasSlice';
resizeAndScaleCanvas,
setInitialCanvasImage,
} from 'features/canvas/store/canvasSlice';
import { gallerySelector } from 'features/gallery/store/gallerySelectors'; import { gallerySelector } from 'features/gallery/store/gallerySelectors';
import { setActiveTab } from 'features/ui/store/uiSlice'; import { setActiveTab } from 'features/ui/store/uiSlice';
import { useTranslation } from 'react-i18next'; import { useTranslation } from 'react-i18next';
import useSetBothPrompts from 'features/parameters/hooks/usePrompt';
import { setIsLightboxOpen } from 'features/lightbox/store/lightboxSlice';
import IAIIconButton from 'common/components/IAIIconButton'; import IAIIconButton from 'common/components/IAIIconButton';
import { useGetUrl } from 'common/util/getUrl'; import { useGetUrl } from 'common/util/getUrl';
import { ExternalLinkIcon } from '@chakra-ui/icons'; import { ExternalLinkIcon } from '@chakra-ui/icons';
import { BiZoomIn } from 'react-icons/bi';
import { IoArrowUndoCircleOutline } from 'react-icons/io5'; import { IoArrowUndoCircleOutline } from 'react-icons/io5';
import { imageDeleted } from 'services/thunks/image'; import { imageDeleted } from 'services/thunks/image';
import { createSelector } from '@reduxjs/toolkit'; import { createSelector } from '@reduxjs/toolkit';
import { systemSelector } from 'features/system/store/systemSelectors'; import { systemSelector } from 'features/system/store/systemSelectors';
import { configSelector } from 'features/system/store/configSelectors';
import { lightboxSelector } from 'features/lightbox/store/lightboxSelectors'; import { lightboxSelector } from 'features/lightbox/store/lightboxSelectors';
import { activeTabNameSelector } from 'features/ui/store/uiSelectors'; import { activeTabNameSelector } from 'features/ui/store/uiSelectors';
import { isEqual } from 'lodash-es'; import { isEqual } from 'lodash-es';
import { useFeatureStatus } from 'features/system/hooks/useFeatureStatus';
import { useParameters } from 'features/parameters/hooks/useParameters';
export const selector = createSelector( export const selector = createSelector(
[ [gallerySelector, systemSelector, lightboxSelector, activeTabNameSelector],
gallerySelector, (gallery, system, lightbox, activeTabName) => {
systemSelector,
configSelector,
lightboxSelector,
activeTabNameSelector,
],
(gallery, system, config, lightbox, activeTabName) => {
const { const {
galleryImageObjectFit, galleryImageObjectFit,
galleryImageMinimumWidth, galleryImageMinimumWidth,
@ -71,7 +43,6 @@ export const selector = createSelector(
} = gallery; } = gallery;
const { isLightboxOpen } = lightbox; const { isLightboxOpen } = lightbox;
const { disabledFeatures } = config;
const { isConnected, isProcessing, shouldConfirmOnDelete } = system; const { isConnected, isProcessing, shouldConfirmOnDelete } = system;
return { return {
@ -82,7 +53,6 @@ export const selector = createSelector(
shouldUseSingleGalleryColumn, shouldUseSingleGalleryColumn,
activeTabName, activeTabName,
isLightboxOpen, isLightboxOpen,
disabledFeatures,
}; };
}, },
{ {
@ -113,14 +83,15 @@ const HoverableImage = memo((props: HoverableImageProps) => {
galleryImageMinimumWidth, galleryImageMinimumWidth,
canDeleteImage, canDeleteImage,
shouldUseSingleGalleryColumn, shouldUseSingleGalleryColumn,
disabledFeatures,
shouldConfirmOnDelete, shouldConfirmOnDelete,
} = useAppSelector(selector); } = useAppSelector(selector);
const { const {
isOpen: isDeleteDialogOpen, isOpen: isDeleteDialogOpen,
onOpen: onDeleteDialogOpen, onOpen: onDeleteDialogOpen,
onClose: onDeleteDialogClose, onClose: onDeleteDialogClose,
} = useDisclosure(); } = useDisclosure();
const { image, isSelected } = props; const { image, isSelected } = props;
const { url, thumbnail, name, metadata } = image; const { url, thumbnail, name, metadata } = image;
const { getUrl } = useGetUrl(); const { getUrl } = useGetUrl();
@ -130,53 +101,62 @@ const HoverableImage = memo((props: HoverableImageProps) => {
const toast = useToast(); const toast = useToast();
const { direction } = useTheme(); const { direction } = useTheme();
const { t } = useTranslation(); const { t } = useTranslation();
const setBothPrompts = useSetBothPrompts(); const { isFeatureEnabled: isLightboxEnabled } = useFeatureStatus('lightbox');
const { recallSeed, recallPrompt, sendToImageToImage, recallInitialImage } =
useParameters();
const handleMouseOver = () => setIsHovered(true); const handleMouseOver = () => setIsHovered(true);
const handleMouseOut = () => setIsHovered(false); const handleMouseOut = () => setIsHovered(false);
const handleInitiateDelete = () => { // Immediately deletes an image
const handleDelete = useCallback(() => {
if (canDeleteImage && image) {
dispatch(imageDeleted({ imageType: image.type, imageName: image.name }));
}
}, [dispatch, image, canDeleteImage]);
// Opens the alert dialog to check if user is sure they want to delete
const handleInitiateDelete = useCallback(() => {
if (shouldConfirmOnDelete) { if (shouldConfirmOnDelete) {
onDeleteDialogOpen(); onDeleteDialogOpen();
} else { } else {
handleDelete(); handleDelete();
} }
}; }, [handleDelete, onDeleteDialogOpen, shouldConfirmOnDelete]);
const handleDelete = () => { const handleSelectImage = useCallback(() => {
if (canDeleteImage && image) { dispatch(imageSelected(image.name));
dispatch(imageDeleted({ imageType: image.type, imageName: image.name })); }, [image, dispatch]);
}
};
const handleUsePrompt = () => { const handleDragStart = useCallback(
if (typeof image.metadata?.invokeai?.node?.prompt === 'string') { (e: DragEvent<HTMLDivElement>) => {
setBothPrompts(image.metadata?.invokeai?.node?.prompt); e.dataTransfer.setData('invokeai/imageName', image.name);
} e.dataTransfer.setData('invokeai/imageType', image.type);
toast({ e.dataTransfer.effectAllowed = 'move';
title: t('toast.promptSet'), },
status: 'success', [image]
duration: 2500, );
isClosable: true,
});
};
const handleUseSeed = () => { // Recall parameters handlers
typeof image.metadata.invokeai?.node?.seed === 'number' && const handleRecallPrompt = useCallback(() => {
dispatch(setSeed(image.metadata.invokeai?.node?.seed)); recallPrompt(image.metadata?.invokeai?.node?.prompt);
toast({ }, [image, recallPrompt]);
title: t('toast.seedSet'),
status: 'success',
duration: 2500,
isClosable: true,
});
};
const handleSendToImageToImage = () => { const handleRecallSeed = useCallback(() => {
dispatch(initialImageSelected(image.name)); recallSeed(image.metadata.invokeai?.node?.seed);
}; }, [image, recallSeed]);
const handleSendToImageToImage = useCallback(() => {
sendToImageToImage(image);
}, [image, sendToImageToImage]);
const handleRecallInitialImage = useCallback(() => {
recallInitialImage(image.metadata.invokeai?.node?.image);
}, [image, recallInitialImage]);
/**
* TODO: the rest of these
*/
const handleSendToCanvas = () => { const handleSendToCanvas = () => {
// dispatch(setInitialCanvasImage(image)); // dispatch(setInitialCanvasImage(image));
@ -205,41 +185,6 @@ const HoverableImage = memo((props: HoverableImageProps) => {
// }); // });
}; };
const handleUseInitialImage = async () => {
// if (metadata.invokeai?.node?.image?.init_image_path) {
// const response = await fetch(
// metadata.invokeai?.node?.image?.init_image_path
// );
// if (response.ok) {
// dispatch(setAllImageToImageParameters(metadata?.invokeai?.node));
// toast({
// title: t('toast.initialImageSet'),
// status: 'success',
// duration: 2500,
// isClosable: true,
// });
// return;
// }
// }
// toast({
// title: t('toast.initialImageNotSet'),
// description: t('toast.initialImageNotSetDesc'),
// status: 'error',
// duration: 2500,
// isClosable: true,
// });
};
const handleSelectImage = () => {
dispatch(imageSelected(image.name));
};
const handleDragStart = (e: DragEvent<HTMLDivElement>) => {
e.dataTransfer.setData('invokeai/imageName', image.name);
e.dataTransfer.setData('invokeai/imageType', image.type);
e.dataTransfer.effectAllowed = 'move';
};
const handleLightBox = () => { const handleLightBox = () => {
// dispatch(setCurrentImage(image)); // dispatch(setCurrentImage(image));
// dispatch(setIsLightboxOpen(true)); // dispatch(setIsLightboxOpen(true));
@ -254,21 +199,21 @@ const HoverableImage = memo((props: HoverableImageProps) => {
<ContextMenu<HTMLDivElement> <ContextMenu<HTMLDivElement>
menuProps={{ size: 'sm', isLazy: true }} menuProps={{ size: 'sm', isLazy: true }}
renderMenu={() => ( renderMenu={() => (
<MenuList> <MenuList sx={{ visibility: 'visible !important' }}>
<MenuItem <MenuItem
icon={<ExternalLinkIcon />} icon={<ExternalLinkIcon />}
onClickCapture={handleOpenInNewTab} onClickCapture={handleOpenInNewTab}
> >
{t('common.openInNewTab')} {t('common.openInNewTab')}
</MenuItem> </MenuItem>
{!disabledFeatures.includes('lightbox') && ( {isLightboxEnabled && (
<MenuItem icon={<FaExpand />} onClickCapture={handleLightBox}> <MenuItem icon={<FaExpand />} onClickCapture={handleLightBox}>
{t('parameters.openInViewer')} {t('parameters.openInViewer')}
</MenuItem> </MenuItem>
)} )}
<MenuItem <MenuItem
icon={<IoArrowUndoCircleOutline />} icon={<IoArrowUndoCircleOutline />}
onClickCapture={handleUsePrompt} onClickCapture={handleRecallPrompt}
isDisabled={image?.metadata?.invokeai?.node?.prompt === undefined} isDisabled={image?.metadata?.invokeai?.node?.prompt === undefined}
> >
{t('parameters.usePrompt')} {t('parameters.usePrompt')}
@ -276,14 +221,14 @@ const HoverableImage = memo((props: HoverableImageProps) => {
<MenuItem <MenuItem
icon={<IoArrowUndoCircleOutline />} icon={<IoArrowUndoCircleOutline />}
onClickCapture={handleUseSeed} onClickCapture={handleRecallSeed}
isDisabled={image?.metadata?.invokeai?.node?.seed === undefined} isDisabled={image?.metadata?.invokeai?.node?.seed === undefined}
> >
{t('parameters.useSeed')} {t('parameters.useSeed')}
</MenuItem> </MenuItem>
<MenuItem <MenuItem
icon={<IoArrowUndoCircleOutline />} icon={<IoArrowUndoCircleOutline />}
onClickCapture={handleUseInitialImage} onClickCapture={handleRecallInitialImage}
isDisabled={image?.metadata?.invokeai?.node?.type !== 'img2img'} isDisabled={image?.metadata?.invokeai?.node?.type !== 'img2img'}
> >
{t('parameters.useInitImg')} {t('parameters.useInitImg')}

View File

@ -1,5 +1,5 @@
import { ButtonGroup, Flex, Grid, Icon, Image, Text } from '@chakra-ui/react'; import { ButtonGroup, Flex, Grid, Icon, Image, Text } from '@chakra-ui/react';
import { requestImages } from 'app/socketio/actions'; // import { requestImages } from 'app/socketio/actions';
import { useAppDispatch, useAppSelector } from 'app/store/storeHooks'; import { useAppDispatch, useAppSelector } from 'app/store/storeHooks';
import IAIButton from 'common/components/IAIButton'; import IAIButton from 'common/components/IAIButton';
import IAICheckbox from 'common/components/IAICheckbox'; import IAICheckbox from 'common/components/IAICheckbox';

View File

@ -1,6 +1,5 @@
import { Box } from '@chakra-ui/react'; import { Box } from '@chakra-ui/react';
import { readinessSelector } from 'app/selectors/readinessSelector'; import { readinessSelector } from 'app/selectors/readinessSelector';
import { generateImage } from 'app/socketio/actions';
import { useAppDispatch, useAppSelector } from 'app/store/storeHooks'; import { useAppDispatch, useAppSelector } from 'app/store/storeHooks';
import IAIButton, { IAIButtonProps } from 'common/components/IAIButton'; import IAIButton, { IAIButtonProps } from 'common/components/IAIButton';
import IAIIconButton, { import IAIIconButton, {
@ -8,10 +7,11 @@ import IAIIconButton, {
} from 'common/components/IAIIconButton'; } from 'common/components/IAIIconButton';
import { clampSymmetrySteps } from 'features/parameters/store/generationSlice'; import { clampSymmetrySteps } from 'features/parameters/store/generationSlice';
import { activeTabNameSelector } from 'features/ui/store/uiSelectors'; import { activeTabNameSelector } from 'features/ui/store/uiSelectors';
import { useCallback } from 'react';
import { useHotkeys } from 'react-hotkeys-hook'; import { useHotkeys } from 'react-hotkeys-hook';
import { useTranslation } from 'react-i18next'; import { useTranslation } from 'react-i18next';
import { FaPlay } from 'react-icons/fa'; import { FaPlay } from 'react-icons/fa';
import { generateGraphBuilt, sessionCreated } from 'services/thunks/session'; import { generateGraphBuilt } from 'services/thunks/session';
interface InvokeButton interface InvokeButton
extends Omit<IAIButtonProps | IAIIconButtonProps, 'aria-label'> { extends Omit<IAIButtonProps | IAIIconButtonProps, 'aria-label'> {
@ -24,19 +24,16 @@ export default function InvokeButton(props: InvokeButton) {
const { isReady } = useAppSelector(readinessSelector); const { isReady } = useAppSelector(readinessSelector);
const activeTabName = useAppSelector(activeTabNameSelector); const activeTabName = useAppSelector(activeTabNameSelector);
const handleClickGenerate = () => { const handleInvoke = useCallback(() => {
// dispatch(generateImage(activeTabName)); dispatch(clampSymmetrySteps());
dispatch(generateGraphBuilt()); dispatch(generateGraphBuilt());
}; }, [dispatch]);
const { t } = useTranslation(); const { t } = useTranslation();
useHotkeys( useHotkeys(
['ctrl+enter', 'meta+enter'], ['ctrl+enter', 'meta+enter'],
() => { handleInvoke,
dispatch(clampSymmetrySteps());
dispatch(generateImage(activeTabName));
},
{ {
enabled: () => isReady, enabled: () => isReady,
preventDefault: true, preventDefault: true,
@ -53,7 +50,7 @@ export default function InvokeButton(props: InvokeButton) {
type="submit" type="submit"
icon={<FaPlay />} icon={<FaPlay />}
isDisabled={!isReady} isDisabled={!isReady}
onClick={handleClickGenerate} onClick={handleInvoke}
flexGrow={1} flexGrow={1}
w="100%" w="100%"
tooltip={t('parameters.invoke')} tooltip={t('parameters.invoke')}
@ -66,7 +63,7 @@ export default function InvokeButton(props: InvokeButton) {
aria-label={t('parameters.invoke')} aria-label={t('parameters.invoke')}
type="submit" type="submit"
isDisabled={!isReady} isDisabled={!isReady}
onClick={handleClickGenerate} onClick={handleInvoke}
flexGrow={1} flexGrow={1}
w="100%" w="100%"
colorScheme="accent" colorScheme="accent"

View File

@ -1,5 +1,4 @@
import { Box, FormControl, Textarea } from '@chakra-ui/react'; import { Box, FormControl, Textarea } from '@chakra-ui/react';
import { generateImage } from 'app/socketio/actions';
import { RootState } from 'app/store/store'; import { RootState } from 'app/store/store';
import { useAppDispatch, useAppSelector } from 'app/store/storeHooks'; import { useAppDispatch, useAppSelector } from 'app/store/storeHooks';
import { ChangeEvent, KeyboardEvent, useRef } from 'react'; import { ChangeEvent, KeyboardEvent, useRef } from 'react';
@ -8,6 +7,7 @@ import { createSelector } from '@reduxjs/toolkit';
import { readinessSelector } from 'app/selectors/readinessSelector'; import { readinessSelector } from 'app/selectors/readinessSelector';
import { import {
GenerationState, GenerationState,
clampSymmetrySteps,
setPrompt, setPrompt,
} from 'features/parameters/store/generationSlice'; } from 'features/parameters/store/generationSlice';
import { activeTabNameSelector } from 'features/ui/store/uiSelectors'; import { activeTabNameSelector } from 'features/ui/store/uiSelectors';
@ -15,6 +15,7 @@ import { activeTabNameSelector } from 'features/ui/store/uiSelectors';
import { isEqual } from 'lodash-es'; import { isEqual } from 'lodash-es';
import { useHotkeys } from 'react-hotkeys-hook'; import { useHotkeys } from 'react-hotkeys-hook';
import { useTranslation } from 'react-i18next'; import { useTranslation } from 'react-i18next';
import { generateGraphBuilt } from 'services/thunks/session';
const promptInputSelector = createSelector( const promptInputSelector = createSelector(
[(state: RootState) => state.generation, activeTabNameSelector], [(state: RootState) => state.generation, activeTabNameSelector],
@ -36,7 +37,7 @@ const promptInputSelector = createSelector(
*/ */
const PromptInput = () => { const PromptInput = () => {
const dispatch = useAppDispatch(); const dispatch = useAppDispatch();
const { prompt, activeTabName } = useAppSelector(promptInputSelector); const { prompt } = useAppSelector(promptInputSelector);
const { isReady } = useAppSelector(readinessSelector); const { isReady } = useAppSelector(readinessSelector);
const promptRef = useRef<HTMLTextAreaElement>(null); const promptRef = useRef<HTMLTextAreaElement>(null);
@ -58,7 +59,8 @@ const PromptInput = () => {
const handleKeyDown = (e: KeyboardEvent<HTMLTextAreaElement>) => { const handleKeyDown = (e: KeyboardEvent<HTMLTextAreaElement>) => {
if (e.key === 'Enter' && e.shiftKey === false && isReady) { if (e.key === 'Enter' && e.shiftKey === false && isReady) {
e.preventDefault(); e.preventDefault();
dispatch(generateImage(activeTabName)); dispatch(clampSymmetrySteps());
dispatch(generateGraphBuilt());
} }
}; };

View File

@ -0,0 +1,130 @@
import { useToast } from '@chakra-ui/react';
import { useAppDispatch } from 'app/store/storeHooks';
import { isFinite, isString } from 'lodash-es';
import { useCallback } from 'react';
import { useTranslation } from 'react-i18next';
import useSetBothPrompts from './usePrompt';
import { initialImageSelected, setSeed } from '../store/generationSlice';
import { isImage, isImageField } from 'services/types/guards';
import { NUMPY_RAND_MAX } from 'app/constants';
export const useParameters = () => {
const dispatch = useAppDispatch();
const toast = useToast();
const { t } = useTranslation();
const setBothPrompts = useSetBothPrompts();
/**
* Sets prompt with toast
*/
const recallPrompt = useCallback(
(prompt: unknown) => {
if (!isString(prompt)) {
toast({
title: t('toast.promptNotSet'),
description: t('toast.promptNotSetDesc'),
status: 'warning',
duration: 2500,
isClosable: true,
});
return;
}
setBothPrompts(prompt);
toast({
title: t('toast.promptSet'),
status: 'info',
duration: 2500,
isClosable: true,
});
},
[t, toast, setBothPrompts]
);
/**
* Sets seed with toast
*/
const recallSeed = useCallback(
(seed: unknown) => {
const s = Number(seed);
if (!isFinite(s) || (isFinite(s) && !(s >= 0 && s <= NUMPY_RAND_MAX))) {
toast({
title: t('toast.seedNotSet'),
description: t('toast.seedNotSetDesc'),
status: 'error',
duration: 2500,
isClosable: true,
});
return;
}
dispatch(setSeed(s));
toast({
title: t('toast.seedSet'),
status: 'success',
duration: 2500,
isClosable: true,
});
},
[t, toast, dispatch]
);
/**
* Sets initial image with toast
*/
const recallInitialImage = useCallback(
async (image: unknown) => {
if (!isImageField(image)) {
toast({
title: t('toast.initialImageNotSet'),
description: t('toast.initialImageNotSetDesc'),
status: 'error',
duration: 2500,
isClosable: true,
});
return;
}
dispatch(
initialImageSelected({ name: image.image_name, type: image.image_type })
);
toast({
title: t('toast.initialImageSet'),
status: 'success',
duration: 2500,
isClosable: true,
});
},
[t, toast, dispatch]
);
/**
* Sets image as initial image with toast
*/
const sendToImageToImage = useCallback(
(image: unknown) => {
if (!isImage(image)) {
toast({
title: t('toast.imageNotLoaded'),
description: t('toast.imageNotLoadedDesc'),
status: 'error',
duration: 2500,
isClosable: true,
});
return;
}
dispatch(initialImageSelected({ name: image.name, type: image.type }));
toast({
title: t('toast.sentToImageToImage'),
status: 'success',
duration: 2500,
isClosable: true,
});
},
[t, toast, dispatch]
);
return { recallPrompt, recallSeed, recallInitialImage, sendToImageToImage };
};

View File

@ -1,4 +1,4 @@
import { emptyTempFolder } from 'app/socketio/actions'; // import { emptyTempFolder } from 'app/socketio/actions';
import { useAppDispatch, useAppSelector } from 'app/store/storeHooks'; import { useAppDispatch, useAppSelector } from 'app/store/storeHooks';
import IAIAlertDialog from 'common/components/IAIAlertDialog'; import IAIAlertDialog from 'common/components/IAIAlertDialog';
import IAIButton from 'common/components/IAIButton'; import IAIButton from 'common/components/IAIButton';

View File

@ -17,7 +17,7 @@ import React from 'react';
import SearchModels from './SearchModels'; import SearchModels from './SearchModels';
import { addNewModel } from 'app/socketio/actions'; // import { addNewModel } from 'app/socketio/actions';
import { useAppDispatch, useAppSelector } from 'app/store/storeHooks'; import { useAppDispatch, useAppSelector } from 'app/store/storeHooks';

View File

@ -8,7 +8,7 @@ import {
VStack, VStack,
} from '@chakra-ui/react'; } from '@chakra-ui/react';
import { InvokeDiffusersModelConfigProps } from 'app/types/invokeai'; import { InvokeDiffusersModelConfigProps } from 'app/types/invokeai';
import { addNewModel } from 'app/socketio/actions'; // import { addNewModel } from 'app/socketio/actions';
import { useAppDispatch, useAppSelector } from 'app/store/storeHooks'; import { useAppDispatch, useAppSelector } from 'app/store/storeHooks';
import IAIButton from 'common/components/IAIButton'; import IAIButton from 'common/components/IAIButton';
import IAIInput from 'common/components/IAIInput'; import IAIInput from 'common/components/IAIInput';

View File

@ -17,7 +17,7 @@ import {
VStack, VStack,
} from '@chakra-ui/react'; } from '@chakra-ui/react';
import { addNewModel } from 'app/socketio/actions'; // import { addNewModel } from 'app/socketio/actions';
import { Field, Formik } from 'formik'; import { Field, Formik } from 'formik';
import { useTranslation } from 'react-i18next'; import { useTranslation } from 'react-i18next';

View File

@ -9,7 +9,7 @@ import { systemSelector } from 'features/system/store/systemSelectors';
import { Flex, FormControl, FormLabel, Text, VStack } from '@chakra-ui/react'; import { Flex, FormControl, FormLabel, Text, VStack } from '@chakra-ui/react';
import { addNewModel } from 'app/socketio/actions'; // import { addNewModel } from 'app/socketio/actions';
import { Field, Formik } from 'formik'; import { Field, Formik } from 'formik';
import { useTranslation } from 'react-i18next'; import { useTranslation } from 'react-i18next';

View File

@ -13,7 +13,7 @@ import {
Tooltip, Tooltip,
useDisclosure, useDisclosure,
} from '@chakra-ui/react'; } from '@chakra-ui/react';
import { mergeDiffusersModels } from 'app/socketio/actions'; // import { mergeDiffusersModels } from 'app/socketio/actions';
import { RootState } from 'app/store/store'; import { RootState } from 'app/store/store';
import { useAppDispatch, useAppSelector } from 'app/store/storeHooks'; import { useAppDispatch, useAppSelector } from 'app/store/storeHooks';
import IAIButton from 'common/components/IAIButton'; import IAIButton from 'common/components/IAIButton';

View File

@ -7,7 +7,7 @@ import {
UnorderedList, UnorderedList,
Tooltip, Tooltip,
} from '@chakra-ui/react'; } from '@chakra-ui/react';
import { convertToDiffusers } from 'app/socketio/actions'; // import { convertToDiffusers } from 'app/socketio/actions';
import { RootState } from 'app/store/store'; import { RootState } from 'app/store/store';
import { useAppDispatch, useAppSelector } from 'app/store/storeHooks'; import { useAppDispatch, useAppSelector } from 'app/store/storeHooks';
import IAIAlertDialog from 'common/components/IAIAlertDialog'; import IAIAlertDialog from 'common/components/IAIAlertDialog';

View File

@ -1,7 +1,7 @@
import { DeleteIcon, EditIcon } from '@chakra-ui/icons'; import { DeleteIcon, EditIcon } from '@chakra-ui/icons';
import { Box, Button, Flex, Spacer, Text, Tooltip } from '@chakra-ui/react'; import { Box, Button, Flex, Spacer, Text, Tooltip } from '@chakra-ui/react';
import { ModelStatus } from 'app/types/invokeai'; import { ModelStatus } from 'app/types/invokeai';
import { deleteModel, requestModelChange } from 'app/socketio/actions'; // import { deleteModel, requestModelChange } from 'app/socketio/actions';
import { RootState } from 'app/store/store'; import { RootState } from 'app/store/store';
import { useAppDispatch, useAppSelector } from 'app/store/storeHooks'; import { useAppDispatch, useAppSelector } from 'app/store/storeHooks';
import IAIAlertDialog from 'common/components/IAIAlertDialog'; import IAIAlertDialog from 'common/components/IAIAlertDialog';

View File

@ -20,7 +20,7 @@ import { useTranslation } from 'react-i18next';
import { FaSearch, FaTrash } from 'react-icons/fa'; import { FaSearch, FaTrash } from 'react-icons/fa';
import { addNewModel, searchForModels } from 'app/socketio/actions'; // import { addNewModel, searchForModels } from 'app/socketio/actions';
import { import {
setFoundModels, setFoundModels,
setSearchFolder, setSearchFolder,

View File

@ -1,7 +1,6 @@
import { import {
ChakraProps, ChakraProps,
Flex, Flex,
Grid,
Heading, Heading,
Modal, Modal,
ModalBody, ModalBody,
@ -14,22 +13,16 @@ import {
useDisclosure, useDisclosure,
} from '@chakra-ui/react'; } from '@chakra-ui/react';
import { createSelector } from '@reduxjs/toolkit'; import { createSelector } from '@reduxjs/toolkit';
import { IN_PROGRESS_IMAGE_TYPES } from 'app/constants';
import { RootState } from 'app/store/store';
import { useAppDispatch, useAppSelector } from 'app/store/storeHooks'; import { useAppDispatch, useAppSelector } from 'app/store/storeHooks';
import IAIButton from 'common/components/IAIButton'; import IAIButton from 'common/components/IAIButton';
import IAINumberInput from 'common/components/IAINumberInput';
import IAISelect from 'common/components/IAISelect'; import IAISelect from 'common/components/IAISelect';
import IAISwitch from 'common/components/IAISwitch'; import IAISwitch from 'common/components/IAISwitch';
import { systemSelector } from 'features/system/store/systemSelectors'; import { systemSelector } from 'features/system/store/systemSelectors';
import { import {
consoleLogLevelChanged, consoleLogLevelChanged,
InProgressImageType,
setEnableImageDebugging, setEnableImageDebugging,
setSaveIntermediatesInterval,
setShouldConfirmOnDelete, setShouldConfirmOnDelete,
setShouldDisplayGuides, setShouldDisplayGuides,
setShouldDisplayInProgressType,
shouldLogToConsoleChanged, shouldLogToConsoleChanged,
SystemState, SystemState,
} from 'features/system/store/systemSlice'; } from 'features/system/store/systemSlice';
@ -39,23 +32,19 @@ import {
setShouldUseSliders, setShouldUseSliders,
} from 'features/ui/store/uiSlice'; } from 'features/ui/store/uiSlice';
import { UIState } from 'features/ui/store/uiTypes'; import { UIState } from 'features/ui/store/uiTypes';
import { isEqual, map } from 'lodash-es'; import { isEqual } from 'lodash-es';
import { persistor } from 'app/store/persistor'; import { persistor } from 'app/store/persistor';
import { ChangeEvent, cloneElement, ReactElement, useCallback } from 'react'; import { ChangeEvent, cloneElement, ReactElement, useCallback } from 'react';
import { useTranslation } from 'react-i18next'; import { useTranslation } from 'react-i18next';
import { InvokeLogLevel, VALID_LOG_LEVELS } from 'app/logging/useLogger'; import { VALID_LOG_LEVELS } from 'app/logging/useLogger';
import { LogLevelName } from 'roarr'; import { LogLevelName } from 'roarr';
import { F } from 'ts-toolbelt';
const selector = createSelector( const selector = createSelector(
[systemSelector, uiSelector], [systemSelector, uiSelector],
(system: SystemState, ui: UIState) => { (system: SystemState, ui: UIState) => {
const { const {
shouldDisplayInProgressType,
shouldConfirmOnDelete, shouldConfirmOnDelete,
shouldDisplayGuides, shouldDisplayGuides,
model_list,
saveIntermediatesInterval,
enableImageDebugging, enableImageDebugging,
consoleLogLevel, consoleLogLevel,
shouldLogToConsole, shouldLogToConsole,
@ -64,11 +53,8 @@ const selector = createSelector(
const { shouldUseCanvasBetaLayout, shouldUseSliders } = ui; const { shouldUseCanvasBetaLayout, shouldUseSliders } = ui;
return { return {
shouldDisplayInProgressType,
shouldConfirmOnDelete, shouldConfirmOnDelete,
shouldDisplayGuides, shouldDisplayGuides,
models: map(model_list, (_model, key) => key),
saveIntermediatesInterval,
enableImageDebugging, enableImageDebugging,
shouldUseCanvasBetaLayout, shouldUseCanvasBetaLayout,
shouldUseSliders, shouldUseSliders,
@ -104,8 +90,6 @@ const SettingsModal = ({ children }: SettingsModalProps) => {
const dispatch = useAppDispatch(); const dispatch = useAppDispatch();
const { t } = useTranslation(); const { t } = useTranslation();
const steps = useAppSelector((state: RootState) => state.generation.steps);
const { const {
isOpen: isSettingsModalOpen, isOpen: isSettingsModalOpen,
onOpen: onSettingsModalOpen, onOpen: onSettingsModalOpen,
@ -119,10 +103,8 @@ const SettingsModal = ({ children }: SettingsModalProps) => {
} = useDisclosure(); } = useDisclosure();
const { const {
shouldDisplayInProgressType,
shouldConfirmOnDelete, shouldConfirmOnDelete,
shouldDisplayGuides, shouldDisplayGuides,
saveIntermediatesInterval,
enableImageDebugging, enableImageDebugging,
shouldUseCanvasBetaLayout, shouldUseCanvasBetaLayout,
shouldUseSliders, shouldUseSliders,
@ -134,18 +116,12 @@ const SettingsModal = ({ children }: SettingsModalProps) => {
* Resets localstorage, then opens a secondary modal informing user to * Resets localstorage, then opens a secondary modal informing user to
* refresh their browser. * refresh their browser.
* */ * */
const handleClickResetWebUI = () => { const handleClickResetWebUI = useCallback(() => {
persistor.purge().then(() => { persistor.purge().then(() => {
onSettingsModalClose(); onSettingsModalClose();
onRefreshModalOpen(); onRefreshModalOpen();
}); });
}; }, [onSettingsModalClose, onRefreshModalOpen]);
const handleChangeIntermediateSteps = (value: number) => {
if (value > steps) value = steps;
if (value < 1) value = 1;
dispatch(setSaveIntermediatesInterval(value));
};
const handleLogLevelChanged = useCallback( const handleLogLevelChanged = useCallback(
(e: ChangeEvent<HTMLSelectElement>) => { (e: ChangeEvent<HTMLSelectElement>) => {
@ -182,32 +158,6 @@ const SettingsModal = ({ children }: SettingsModalProps) => {
<Flex sx={modalSectionStyles}> <Flex sx={modalSectionStyles}>
<Heading size="sm">{t('settings.general')}</Heading> <Heading size="sm">{t('settings.general')}</Heading>
<IAISelect
horizontal
spaceEvenly
label={t('settings.displayInProgress')}
validValues={IN_PROGRESS_IMAGE_TYPES}
value={shouldDisplayInProgressType}
onChange={(e: ChangeEvent<HTMLSelectElement>) =>
dispatch(
setShouldDisplayInProgressType(
e.target.value as InProgressImageType
)
)
}
/>
{shouldDisplayInProgressType === 'full-res' && (
<IAINumberInput
label={t('settings.saveSteps')}
min={1}
max={steps}
step={1}
onChange={handleChangeIntermediateSteps}
value={saveIntermediatesInterval}
width="auto"
textAlign="center"
/>
)}
<IAISwitch <IAISwitch
label={t('settings.confirmOnDelete')} label={t('settings.confirmOnDelete')}
isChecked={shouldConfirmOnDelete} isChecked={shouldConfirmOnDelete}

View File

@ -1,22 +1,19 @@
import { Text, Tooltip } from '@chakra-ui/react'; import { Text } from '@chakra-ui/react';
import { createSelector } from '@reduxjs/toolkit'; import { createSelector } from '@reduxjs/toolkit';
import { useAppDispatch, useAppSelector } from 'app/store/storeHooks'; import { useAppDispatch, useAppSelector } from 'app/store/storeHooks';
import { errorSeen, SystemState } from 'features/system/store/systemSlice';
import { isEqual } from 'lodash-es'; import { isEqual } from 'lodash-es';
import { useTranslation } from 'react-i18next'; import { useTranslation } from 'react-i18next';
import { systemSelector } from '../store/systemSelectors'; import { systemSelector } from '../store/systemSelectors';
const statusIndicatorSelector = createSelector( const statusIndicatorSelector = createSelector(
systemSelector, systemSelector,
(system: SystemState) => { (system) => {
return { return {
isConnected: system.isConnected, isConnected: system.isConnected,
isProcessing: system.isProcessing, isProcessing: system.isProcessing,
currentIteration: system.currentIteration, currentIteration: system.currentIteration,
totalIterations: system.totalIterations, totalIterations: system.totalIterations,
currentStatus: system.currentStatus, currentStatus: system.currentStatus,
hasError: system.hasError,
wasErrorSeen: system.wasErrorSeen,
}; };
}, },
{ {
@ -31,15 +28,12 @@ const StatusIndicator = () => {
currentIteration, currentIteration,
totalIterations, totalIterations,
currentStatus, currentStatus,
hasError,
wasErrorSeen,
} = useAppSelector(statusIndicatorSelector); } = useAppSelector(statusIndicatorSelector);
const dispatch = useAppDispatch();
const { t } = useTranslation(); const { t } = useTranslation();
let statusIdentifier; let statusIdentifier;
if (isConnected && !hasError) { if (isConnected) {
statusIdentifier = 'ok'; statusIdentifier = 'ok';
} else { } else {
statusIdentifier = 'error'; statusIdentifier = 'error';
@ -60,25 +54,8 @@ const StatusIndicator = () => {
} }
} }
const tooltipLabel =
hasError && !wasErrorSeen
? 'Click to clear, check logs for details'
: undefined;
const statusIndicatorCursor =
hasError && !wasErrorSeen ? 'pointer' : 'initial';
const handleClickStatusIndicator = () => {
if (hasError || !wasErrorSeen) {
dispatch(errorSeen());
}
};
return ( return (
<Tooltip label={tooltipLabel}>
<Text <Text
cursor={statusIndicatorCursor}
onClick={handleClickStatusIndicator}
sx={{ sx={{
fontSize: 'sm', fontSize: 'sm',
fontWeight: '600', fontWeight: '600',
@ -87,7 +64,6 @@ const StatusIndicator = () => {
> >
{t(statusMessage as keyof typeof t)} {t(statusMessage as keyof typeof t)}
</Text> </Text>
</Tooltip>
); );
}; };

View File

@ -23,33 +23,14 @@ import { parsedOpenAPISchema } from 'features/nodes/store/nodesSlice';
import { LogLevelName } from 'roarr'; import { LogLevelName } from 'roarr';
import { InvokeLogLevel } from 'app/logging/useLogger'; import { InvokeLogLevel } from 'app/logging/useLogger';
export type LogLevel = 'info' | 'warning' | 'error';
export interface LogEntry {
timestamp: string;
level: LogLevel;
message: string;
}
export interface Log {
[index: number]: LogEntry;
}
export type InProgressImageType = 'none' | 'full-res' | 'latents';
export type CancelType = 'immediate' | 'scheduled'; export type CancelType = 'immediate' | 'scheduled';
export interface SystemState export interface SystemState {
extends InvokeAI.SystemStatus,
InvokeAI.SystemConfig {
shouldDisplayInProgressType: InProgressImageType;
shouldShowLogViewer: boolean;
isGFPGANAvailable: boolean; isGFPGANAvailable: boolean;
isESRGANAvailable: boolean; isESRGANAvailable: boolean;
isConnected: boolean; isConnected: boolean;
socketId: string; isProcessing: boolean;
shouldConfirmOnDelete: boolean; shouldConfirmOnDelete: boolean;
openAccordions: ExpandedIndex;
currentStep: number; currentStep: number;
totalSteps: number; totalSteps: number;
currentIteration: number; currentIteration: number;
@ -57,18 +38,12 @@ export interface SystemState
currentStatus: string; currentStatus: string;
currentStatusHasSteps: boolean; currentStatusHasSteps: boolean;
shouldDisplayGuides: boolean; shouldDisplayGuides: boolean;
wasErrorSeen: boolean;
isCancelable: boolean; isCancelable: boolean;
saveIntermediatesInterval: number;
enableImageDebugging: boolean; enableImageDebugging: boolean;
toastQueue: UseToastOptions[]; toastQueue: UseToastOptions[];
searchFolder: string | null; searchFolder: string | null;
foundModels: InvokeAI.FoundModel[] | null; foundModels: InvokeAI.FoundModel[] | null;
openModel: string | null; openModel: string | null;
cancelOptions: {
cancelType: CancelType;
cancelAfter: number | null;
};
/** /**
* The current progress image * The current progress image
*/ */
@ -107,14 +82,10 @@ export interface SystemState
const initialSystemState: SystemState = { const initialSystemState: SystemState = {
isConnected: false, isConnected: false,
isProcessing: false, isProcessing: false,
shouldShowLogViewer: false,
shouldDisplayInProgressType: 'latents',
shouldDisplayGuides: true, shouldDisplayGuides: true,
isGFPGANAvailable: true, isGFPGANAvailable: true,
isESRGANAvailable: true, isESRGANAvailable: true,
socketId: '',
shouldConfirmOnDelete: true, shouldConfirmOnDelete: true,
openAccordions: [0],
currentStep: 0, currentStep: 0,
totalSteps: 0, totalSteps: 0,
currentIteration: 0, currentIteration: 0,
@ -123,26 +94,12 @@ const initialSystemState: SystemState = {
? i18n.t('common.statusDisconnected') ? i18n.t('common.statusDisconnected')
: 'Disconnected', : 'Disconnected',
currentStatusHasSteps: false, currentStatusHasSteps: false,
model: '',
model_id: '',
model_hash: '',
app_id: '',
app_version: '',
model_list: {},
infill_methods: [],
hasError: false,
wasErrorSeen: true,
isCancelable: true, isCancelable: true,
saveIntermediatesInterval: 5,
enableImageDebugging: false, enableImageDebugging: false,
toastQueue: [], toastQueue: [],
searchFolder: null, searchFolder: null,
foundModels: null, foundModels: null,
openModel: null, openModel: null,
cancelOptions: {
cancelType: 'immediate',
cancelAfter: null,
},
progressImage: null, progressImage: null,
sessionId: null, sessionId: null,
cancelType: 'immediate', cancelType: 'immediate',
@ -158,23 +115,13 @@ export const systemSlice = createSlice({
name: 'system', name: 'system',
initialState: initialSystemState, initialState: initialSystemState,
reducers: { reducers: {
setShouldDisplayInProgressType: (
state,
action: PayloadAction<InProgressImageType>
) => {
state.shouldDisplayInProgressType = action.payload;
},
setIsProcessing: (state, action: PayloadAction<boolean>) => { setIsProcessing: (state, action: PayloadAction<boolean>) => {
state.isProcessing = action.payload; state.isProcessing = action.payload;
}, },
setCurrentStatus: (state, action: PayloadAction<string>) => { setCurrentStatus: (state, action: PayloadAction<string>) => {
state.currentStatus = action.payload; state.currentStatus = action.payload;
}, },
setSystemStatus: (state, action: PayloadAction<InvokeAI.SystemStatus>) => {
return { ...state, ...action.payload };
},
errorOccurred: (state) => { errorOccurred: (state) => {
state.hasError = true;
state.isProcessing = false; state.isProcessing = false;
state.isCancelable = true; state.isCancelable = true;
state.currentStep = 0; state.currentStep = 0;
@ -183,17 +130,6 @@ export const systemSlice = createSlice({
state.totalIterations = 0; state.totalIterations = 0;
state.currentStatusHasSteps = false; state.currentStatusHasSteps = false;
state.currentStatus = i18n.t('common.statusError'); state.currentStatus = i18n.t('common.statusError');
state.wasErrorSeen = false;
},
errorSeen: (state) => {
state.hasError = false;
state.wasErrorSeen = true;
state.currentStatus = state.isConnected
? i18n.t('common.statusConnected')
: i18n.t('common.statusDisconnected');
},
setShouldShowLogViewer: (state, action: PayloadAction<boolean>) => {
state.shouldShowLogViewer = action.payload;
}, },
setIsConnected: (state, action: PayloadAction<boolean>) => { setIsConnected: (state, action: PayloadAction<boolean>) => {
state.isConnected = action.payload; state.isConnected = action.payload;
@ -204,23 +140,10 @@ export const systemSlice = createSlice({
state.currentIteration = 0; state.currentIteration = 0;
state.totalIterations = 0; state.totalIterations = 0;
state.currentStatusHasSteps = false; state.currentStatusHasSteps = false;
state.hasError = false;
},
setSocketId: (state, action: PayloadAction<string>) => {
state.socketId = action.payload;
}, },
setShouldConfirmOnDelete: (state, action: PayloadAction<boolean>) => { setShouldConfirmOnDelete: (state, action: PayloadAction<boolean>) => {
state.shouldConfirmOnDelete = action.payload; state.shouldConfirmOnDelete = action.payload;
}, },
setOpenAccordions: (state, action: PayloadAction<ExpandedIndex>) => {
state.openAccordions = action.payload;
},
setSystemConfig: (state, action: PayloadAction<InvokeAI.SystemConfig>) => {
return {
...state,
...action.payload,
};
},
setShouldDisplayGuides: (state, action: PayloadAction<boolean>) => { setShouldDisplayGuides: (state, action: PayloadAction<boolean>) => {
state.shouldDisplayGuides = action.payload; state.shouldDisplayGuides = action.payload;
}, },
@ -244,12 +167,6 @@ export const systemSlice = createSlice({
state.currentStatusHasSteps = false; state.currentStatusHasSteps = false;
state.currentStatus = i18n.t('common.statusPreparing'); state.currentStatus = i18n.t('common.statusPreparing');
}, },
setModelList: (
state,
action: PayloadAction<InvokeAI.ModelList | Record<string, never>>
) => {
state.model_list = action.payload;
},
setIsCancelable: (state, action: PayloadAction<boolean>) => { setIsCancelable: (state, action: PayloadAction<boolean>) => {
state.isCancelable = action.payload; state.isCancelable = action.payload;
}, },
@ -271,9 +188,6 @@ export const systemSlice = createSlice({
state.isProcessing = true; state.isProcessing = true;
state.currentStatusHasSteps = false; state.currentStatusHasSteps = false;
}, },
setSaveIntermediatesInterval: (state, action: PayloadAction<number>) => {
state.saveIntermediatesInterval = action.payload;
},
setEnableImageDebugging: (state, action: PayloadAction<boolean>) => { setEnableImageDebugging: (state, action: PayloadAction<boolean>) => {
state.enableImageDebugging = action.payload; state.enableImageDebugging = action.payload;
}, },
@ -300,12 +214,6 @@ export const systemSlice = createSlice({
setOpenModel: (state, action: PayloadAction<string | null>) => { setOpenModel: (state, action: PayloadAction<string | null>) => {
state.openModel = action.payload; state.openModel = action.payload;
}, },
setCancelType: (state, action: PayloadAction<CancelType>) => {
state.cancelOptions.cancelType = action.payload;
},
setCancelAfter: (state, action: PayloadAction<number | null>) => {
state.cancelOptions.cancelAfter = action.payload;
},
/** /**
* A cancel was scheduled * A cancel was scheduled
*/ */
@ -420,7 +328,6 @@ export const systemSlice = createSlice({
builder.addCase(invocationError, (state, action) => { builder.addCase(invocationError, (state, action) => {
const { data, timestamp } = action.payload; const { data, timestamp } = action.payload;
state.wasErrorSeen = true;
state.progressImage = null; state.progressImage = null;
state.isProcessing = false; state.isProcessing = false;
@ -479,26 +386,17 @@ export const systemSlice = createSlice({
}); });
export const { export const {
setShouldDisplayInProgressType,
setIsProcessing, setIsProcessing,
setShouldShowLogViewer,
setIsConnected, setIsConnected,
setSocketId,
setShouldConfirmOnDelete, setShouldConfirmOnDelete,
setOpenAccordions,
setSystemStatus,
setCurrentStatus, setCurrentStatus,
setSystemConfig,
setShouldDisplayGuides, setShouldDisplayGuides,
processingCanceled, processingCanceled,
errorOccurred, errorOccurred,
errorSeen,
setModelList,
setIsCancelable, setIsCancelable,
modelChangeRequested, modelChangeRequested,
modelConvertRequested, modelConvertRequested,
modelMergingRequested, modelMergingRequested,
setSaveIntermediatesInterval,
setEnableImageDebugging, setEnableImageDebugging,
generationRequested, generationRequested,
addToast, addToast,
@ -507,8 +405,6 @@ export const {
setSearchFolder, setSearchFolder,
setFoundModels, setFoundModels,
setOpenModel, setOpenModel,
setCancelType,
setCancelAfter,
cancelScheduled, cancelScheduled,
scheduledCancelAborted, scheduledCancelAborted,
cancelTypeChanged, cancelTypeChanged,

View File

@ -1,3 +1,5 @@
import { Image } from 'app/types/invokeai';
import { get, isObject, isString } from 'lodash-es';
import { import {
GraphExecutionState, GraphExecutionState,
GraphInvocationOutput, GraphInvocationOutput,
@ -6,6 +8,8 @@ import {
PromptOutput, PromptOutput,
IterateInvocationOutput, IterateInvocationOutput,
CollectInvocationOutput, CollectInvocationOutput,
ImageType,
ImageField,
} from 'services/api'; } from 'services/api';
export const isImageOutput = ( export const isImageOutput = (
@ -31,3 +35,16 @@ export const isIterateOutput = (
export const isCollectOutput = ( export const isCollectOutput = (
output: GraphExecutionState['results'][string] output: GraphExecutionState['results'][string]
): output is CollectInvocationOutput => output.type === 'collect_output'; ): output is CollectInvocationOutput => output.type === 'collect_output';
export const isImageType = (t: unknown): t is ImageType =>
isString(t) && ['results', 'uploads', 'intermediates'].includes(t);
export const isImage = (image: unknown): image is Image =>
isObject(image) &&
isString(get(image, 'name')) &&
isImageType(get(image, 'type'));
export const isImageField = (imageField: unknown): imageField is ImageField =>
isObject(imageField) &&
isString(get(imageField, 'image_name')) &&
isImageType(get(imageField, 'image_type'));