mirror of
https://github.com/invoke-ai/InvokeAI
synced 2024-08-30 20:32:17 +00:00
feat(ui): wip canvas migration, createListenerMiddleware
This commit is contained in:
parent
a75148cb16
commit
6ab5d28cf3
@ -118,6 +118,7 @@
|
||||
"@types/node": "^18.16.2",
|
||||
"@types/react": "^18.2.0",
|
||||
"@types/react-dom": "^18.2.1",
|
||||
"@types/react-redux": "^7.1.25",
|
||||
"@types/react-transition-group": "^4.4.5",
|
||||
"@types/uuid": "^9.0.0",
|
||||
"@typescript-eslint/eslint-plugin": "^5.59.1",
|
||||
|
@ -550,7 +550,7 @@
|
||||
"imageCopied": "Image Copied",
|
||||
"imageLinkCopied": "Image Link Copied",
|
||||
"imageNotLoaded": "No Image Loaded",
|
||||
"imageNotLoadedDesc": "No image found to send to image to image module",
|
||||
"imageNotLoadedDesc": "Could not find image",
|
||||
"imageSavedToGallery": "Image Saved to Gallery",
|
||||
"canvasMerged": "Canvas Merged",
|
||||
"sentToImageToImage": "Sent To Image To Image",
|
||||
|
@ -0,0 +1,59 @@
|
||||
import {
|
||||
createListenerMiddleware,
|
||||
addListener,
|
||||
ListenerEffect,
|
||||
AnyAction,
|
||||
} from '@reduxjs/toolkit';
|
||||
import type { TypedStartListening, TypedAddListener } from '@reduxjs/toolkit';
|
||||
|
||||
import type { RootState, AppDispatch } from '../../store';
|
||||
import { initialImageSelected } from 'features/parameters/store/actions';
|
||||
import { initialImageListener } from './listeners/initialImageListener';
|
||||
import {
|
||||
imageResultReceivedListener,
|
||||
imageResultReceivedPrediate,
|
||||
} from './listeners/invocationCompleteListener';
|
||||
import { imageUploaded } from 'services/thunks/image';
|
||||
import { imageUploadedListener } from './listeners/imageUploadedListener';
|
||||
|
||||
export const listenerMiddleware = createListenerMiddleware();
|
||||
|
||||
export type AppStartListening = TypedStartListening<RootState, AppDispatch>;
|
||||
|
||||
export const startAppListening =
|
||||
listenerMiddleware.startListening as AppStartListening;
|
||||
|
||||
export const addAppListener = addListener as TypedAddListener<
|
||||
RootState,
|
||||
AppDispatch
|
||||
>;
|
||||
|
||||
export type AppListenerEffect = ListenerEffect<
|
||||
AnyAction,
|
||||
RootState,
|
||||
AppDispatch
|
||||
>;
|
||||
|
||||
/**
|
||||
* Initial image selected
|
||||
*/
|
||||
startAppListening({
|
||||
actionCreator: initialImageSelected,
|
||||
effect: initialImageListener,
|
||||
});
|
||||
|
||||
/**
|
||||
* Image Result received
|
||||
*/
|
||||
startAppListening({
|
||||
predicate: imageResultReceivedPrediate,
|
||||
effect: imageResultReceivedListener,
|
||||
});
|
||||
|
||||
/**
|
||||
* Image Uploaded
|
||||
*/
|
||||
startAppListening({
|
||||
actionCreator: imageUploaded.fulfilled,
|
||||
effect: imageUploadedListener,
|
||||
});
|
@ -0,0 +1,19 @@
|
||||
import { deserializeImageResponse } from 'services/util/deserializeImageResponse';
|
||||
import { AppListenerEffect } from '..';
|
||||
import { uploadAdded } from 'features/gallery/store/uploadsSlice';
|
||||
import { imageSelected } from 'features/gallery/store/gallerySlice';
|
||||
|
||||
export const imageUploadedListener: AppListenerEffect = (
|
||||
action,
|
||||
{ dispatch, getState }
|
||||
) => {
|
||||
const { response } = action.payload;
|
||||
const state = getState();
|
||||
const image = deserializeImageResponse(response);
|
||||
|
||||
dispatch(uploadAdded(image));
|
||||
|
||||
if (state.gallery.shouldAutoSwitchToNewImages) {
|
||||
dispatch(imageSelected(image));
|
||||
}
|
||||
};
|
@ -0,0 +1,53 @@
|
||||
import { initialImageChanged } from 'features/parameters/store/generationSlice';
|
||||
import { Image, isInvokeAIImage } from 'app/types/invokeai';
|
||||
import { selectResultsById } from 'features/gallery/store/resultsSlice';
|
||||
import { selectUploadsById } from 'features/gallery/store/uploadsSlice';
|
||||
import { makeToast } from 'features/system/hooks/useToastWatcher';
|
||||
import { t } from 'i18next';
|
||||
import { addToast } from 'features/system/store/systemSlice';
|
||||
import { AnyAction, ListenerEffect } from '@reduxjs/toolkit';
|
||||
import { AppDispatch, RootState } from 'app/store/store';
|
||||
|
||||
export const initialImageListener: ListenerEffect<
|
||||
AnyAction,
|
||||
RootState,
|
||||
AppDispatch
|
||||
> = (action, { getState, dispatch }) => {
|
||||
if (!action.payload) {
|
||||
dispatch(
|
||||
addToast(
|
||||
makeToast({ title: t('toast.imageNotLoadedDesc'), status: 'error' })
|
||||
)
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
if (isInvokeAIImage(action.payload)) {
|
||||
dispatch(initialImageChanged(action.payload));
|
||||
dispatch(addToast(makeToast(t('toast.sentToImageToImage'))));
|
||||
return;
|
||||
}
|
||||
|
||||
const { name, type } = action.payload;
|
||||
|
||||
let image: Image | undefined;
|
||||
const state = getState();
|
||||
|
||||
if (type === 'results') {
|
||||
image = selectResultsById(state, name);
|
||||
} else if (type === 'uploads') {
|
||||
image = selectUploadsById(state, name);
|
||||
}
|
||||
|
||||
if (!image) {
|
||||
dispatch(
|
||||
addToast(
|
||||
makeToast({ title: t('toast.imageNotLoadedDesc'), status: 'error' })
|
||||
)
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
dispatch(initialImageChanged(image));
|
||||
dispatch(addToast(makeToast(t('toast.sentToImageToImage'))));
|
||||
};
|
@ -0,0 +1,75 @@
|
||||
import { AnyListenerPredicate } from '@reduxjs/toolkit';
|
||||
import { invocationComplete } from 'services/events/actions';
|
||||
import { isImageOutput } from 'services/types/guards';
|
||||
import { RootState } from 'app/store/store';
|
||||
import {
|
||||
buildImageUrls,
|
||||
extractTimestampFromImageName,
|
||||
} from 'services/util/deserializeImageField';
|
||||
import { Image } from 'app/types/invokeai';
|
||||
import { resultAdded } from 'features/gallery/store/resultsSlice';
|
||||
import { imageReceived, thumbnailReceived } from 'services/thunks/image';
|
||||
import { AppListenerEffect } from '..';
|
||||
|
||||
export const imageResultReceivedPrediate: AnyListenerPredicate<RootState> = (
|
||||
action,
|
||||
_currentState,
|
||||
_originalState
|
||||
) => {
|
||||
if (
|
||||
invocationComplete.match(action) &&
|
||||
isImageOutput(action.payload.data.result)
|
||||
) {
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
};
|
||||
|
||||
export const imageResultReceivedListener: AppListenerEffect = (
|
||||
action,
|
||||
{ getState, dispatch }
|
||||
) => {
|
||||
const { data, shouldFetchImages } = action.payload;
|
||||
const { result, node, graph_execution_state_id } = data;
|
||||
|
||||
if (isImageOutput(result)) {
|
||||
const name = result.image.image_name;
|
||||
const type = result.image.image_type;
|
||||
const state = getState();
|
||||
|
||||
// if we need to refetch, set URLs to placeholder for now
|
||||
const { url, thumbnail } = shouldFetchImages
|
||||
? { url: '', thumbnail: '' }
|
||||
: buildImageUrls(type, name);
|
||||
|
||||
const timestamp = extractTimestampFromImageName(name);
|
||||
|
||||
const image: Image = {
|
||||
name,
|
||||
type,
|
||||
url,
|
||||
thumbnail,
|
||||
metadata: {
|
||||
created: timestamp,
|
||||
width: result.width,
|
||||
height: result.height,
|
||||
invokeai: {
|
||||
session_id: graph_execution_state_id,
|
||||
...(node ? { node } : {}),
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
dispatch(resultAdded(image));
|
||||
|
||||
if (state.config.shouldFetchImages) {
|
||||
dispatch(imageReceived({ imageName: name, imageType: type }));
|
||||
dispatch(
|
||||
thumbnailReceived({
|
||||
thumbnailName: name,
|
||||
thumbnailType: type,
|
||||
})
|
||||
);
|
||||
}
|
||||
}
|
||||
};
|
@ -1,4 +1,9 @@
|
||||
import { combineReducers, configureStore } from '@reduxjs/toolkit';
|
||||
import {
|
||||
AnyAction,
|
||||
ThunkDispatch,
|
||||
combineReducers,
|
||||
configureStore,
|
||||
} from '@reduxjs/toolkit';
|
||||
|
||||
import { persistReducer } from 'redux-persist';
|
||||
import storage from 'redux-persist/lib/storage'; // defaults to localStorage for web
|
||||
@ -30,6 +35,7 @@ import { systemDenylist } from 'features/system/store/systemPersistDenylist';
|
||||
import { uiDenylist } from 'features/ui/store/uiPersistDenylist';
|
||||
import { resultsDenylist } from 'features/gallery/store/resultsPersistDenylist';
|
||||
import { uploadsDenylist } from 'features/gallery/store/uploadsPersistDenylist';
|
||||
import { listenerMiddleware } from './middleware/listenerMiddleware';
|
||||
|
||||
/**
|
||||
* redux-persist provides an easy and reliable way to persist state across reloads.
|
||||
@ -101,7 +107,9 @@ export const store = configureStore({
|
||||
getDefaultMiddleware({
|
||||
immutableCheck: false,
|
||||
serializableCheck: false,
|
||||
}).concat(dynamicMiddlewares),
|
||||
})
|
||||
.concat(dynamicMiddlewares)
|
||||
.prepend(listenerMiddleware.middleware),
|
||||
devTools: {
|
||||
// Uncommenting these very rapidly called actions makes the redux dev tools output much more readable
|
||||
actionsDenylist: [
|
||||
@ -120,4 +128,5 @@ export const store = configureStore({
|
||||
|
||||
export type AppGetState = typeof store.getState;
|
||||
export type RootState = ReturnType<typeof store.getState>;
|
||||
export type AppThunkDispatch = ThunkDispatch<RootState, any, AnyAction>;
|
||||
export type AppDispatch = typeof store.dispatch;
|
||||
|
@ -1,6 +1,6 @@
|
||||
import { TypedUseSelectorHook, useDispatch, useSelector } from 'react-redux';
|
||||
import { AppDispatch, RootState } from 'app/store/store';
|
||||
import { AppDispatch, AppThunkDispatch, RootState } from 'app/store/store';
|
||||
|
||||
// Use throughout your app instead of plain `useDispatch` and `useSelector`
|
||||
export const useAppDispatch: () => AppDispatch = useDispatch;
|
||||
export const useAppDispatch = () => useDispatch<AppThunkDispatch>();
|
||||
export const useAppSelector: TypedUseSelectorHook<RootState> = useSelector;
|
||||
|
@ -13,6 +13,7 @@
|
||||
*/
|
||||
|
||||
import { GalleryCategory } from 'features/gallery/store/gallerySlice';
|
||||
import { SelectedImage } from 'features/parameters/store/actions';
|
||||
import { FacetoolType } from 'features/parameters/store/postprocessingSlice';
|
||||
import { InvokeTabName } from 'features/ui/store/tabMap';
|
||||
import { IRect } from 'konva/lib/types';
|
||||
@ -126,6 +127,14 @@ export type Image = {
|
||||
metadata: ImageResponseMetadata;
|
||||
};
|
||||
|
||||
export const isInvokeAIImage = (obj: Image | SelectedImage): obj is Image => {
|
||||
if ('url' in obj && 'thumbnail' in obj) {
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
};
|
||||
|
||||
/**
|
||||
* Types related to the system status.
|
||||
*/
|
||||
|
@ -29,7 +29,6 @@ import {
|
||||
isCanvasBaseImage,
|
||||
isCanvasMaskLine,
|
||||
} from './canvasTypes';
|
||||
import { invocationComplete } from 'services/events/actions';
|
||||
|
||||
export const initialLayerState: CanvasLayerState = {
|
||||
objects: [],
|
||||
@ -816,11 +815,6 @@ export const canvasSlice = createSlice({
|
||||
state.isTransformingBoundingBox = false;
|
||||
},
|
||||
},
|
||||
extraReducers(builder) {
|
||||
builder.addCase(invocationComplete, (state, action) => {
|
||||
//
|
||||
});
|
||||
},
|
||||
});
|
||||
|
||||
export const {
|
||||
|
@ -22,7 +22,7 @@ import { setIsLightboxOpen } from 'features/lightbox/store/lightboxSlice';
|
||||
import FaceRestoreSettings from 'features/parameters/components/AdvancedParameters/FaceRestore/FaceRestoreSettings';
|
||||
import UpscaleSettings from 'features/parameters/components/AdvancedParameters/Upscale/UpscaleSettings';
|
||||
import {
|
||||
initialImageSelected,
|
||||
initialImageChanged,
|
||||
setAllParameters,
|
||||
// setInitialImage,
|
||||
setSeed,
|
||||
@ -68,6 +68,7 @@ import { useGetUrl } from 'common/util/getUrl';
|
||||
import { useFeatureStatus } from 'features/system/hooks/useFeatureStatus';
|
||||
import { imageDeleted } from 'services/thunks/image';
|
||||
import { useParameters } from 'features/parameters/hooks/useParameters';
|
||||
import { initialImageSelected } from 'features/parameters/store/actions';
|
||||
|
||||
const currentImageButtonsSelector = createSelector(
|
||||
[
|
||||
@ -264,8 +265,8 @@ const CurrentImageButtons = (props: CurrentImageButtonsProps) => {
|
||||
useHotkeys('p', handleUsePrompt, [image]);
|
||||
|
||||
const handleSendToImageToImage = useCallback(() => {
|
||||
sendToImageToImage(image);
|
||||
}, [image, sendToImageToImage]);
|
||||
dispatch(initialImageSelected(image));
|
||||
}, [dispatch, image]);
|
||||
|
||||
useHotkeys('shift+i', handleSendToImageToImage, [image]);
|
||||
|
||||
|
@ -11,7 +11,7 @@ import CurrentImageFallback from './CurrentImageFallback';
|
||||
import ImageMetadataViewer from './ImageMetaDataViewer/ImageMetadataViewer';
|
||||
import NextPrevImageButtons from './NextPrevImageButtons';
|
||||
import CurrentImageHidden from './CurrentImageHidden';
|
||||
import { memo } from 'react';
|
||||
import { DragEvent, memo, useCallback } from 'react';
|
||||
|
||||
export const imagesSelector = createSelector(
|
||||
[uiSelector, selectedImageSelector, systemSelector],
|
||||
@ -36,6 +36,18 @@ const CurrentImagePreview = () => {
|
||||
useAppSelector(imagesSelector);
|
||||
const { getUrl } = useGetUrl();
|
||||
|
||||
const handleDragStart = useCallback(
|
||||
(e: DragEvent<HTMLDivElement>) => {
|
||||
if (!image) {
|
||||
return;
|
||||
}
|
||||
e.dataTransfer.setData('invokeai/imageName', image.name);
|
||||
e.dataTransfer.setData('invokeai/imageType', image.type);
|
||||
e.dataTransfer.effectAllowed = 'move';
|
||||
},
|
||||
[image]
|
||||
);
|
||||
|
||||
return (
|
||||
<Flex
|
||||
sx={{
|
||||
@ -48,6 +60,7 @@ const CurrentImagePreview = () => {
|
||||
>
|
||||
{image && (
|
||||
<Image
|
||||
onDragStart={handleDragStart}
|
||||
src={shouldHidePreview ? undefined : getUrl(image.url)}
|
||||
width={image.metadata.width}
|
||||
height={image.metadata.height}
|
||||
|
@ -134,7 +134,6 @@ const HoverableImage = memo((props: HoverableImageProps) => {
|
||||
|
||||
const handleDragStart = useCallback(
|
||||
(e: DragEvent<HTMLDivElement>) => {
|
||||
console.log('dragging');
|
||||
e.dataTransfer.setData('invokeai/imageName', image.name);
|
||||
e.dataTransfer.setData('invokeai/imageType', image.type);
|
||||
e.dataTransfer.effectAllowed = 'move';
|
||||
|
@ -4,7 +4,7 @@ import { invocationComplete } from 'services/events/actions';
|
||||
import { isImageOutput } from 'services/types/guards';
|
||||
import { deserializeImageResponse } from 'services/util/deserializeImageResponse';
|
||||
import { imageUploaded } from 'services/thunks/image';
|
||||
import { SelectedImage } from 'features/parameters/store/generationSlice';
|
||||
import { Image } from 'app/types/invokeai';
|
||||
|
||||
type GalleryImageObjectFitType = 'contain' | 'cover';
|
||||
|
||||
@ -12,7 +12,7 @@ export interface GalleryState {
|
||||
/**
|
||||
* The selected image
|
||||
*/
|
||||
selectedImage?: SelectedImage;
|
||||
selectedImage?: Image;
|
||||
galleryImageMinimumWidth: number;
|
||||
galleryImageObjectFit: GalleryImageObjectFitType;
|
||||
shouldAutoSwitchToNewImages: boolean;
|
||||
@ -22,7 +22,6 @@ export interface GalleryState {
|
||||
}
|
||||
|
||||
const initialState: GalleryState = {
|
||||
selectedImage: undefined,
|
||||
galleryImageMinimumWidth: 64,
|
||||
galleryImageObjectFit: 'cover',
|
||||
shouldAutoSwitchToNewImages: true,
|
||||
@ -35,10 +34,7 @@ export const gallerySlice = createSlice({
|
||||
name: 'gallery',
|
||||
initialState,
|
||||
reducers: {
|
||||
imageSelected: (
|
||||
state,
|
||||
action: PayloadAction<SelectedImage | undefined>
|
||||
) => {
|
||||
imageSelected: (state, action: PayloadAction<Image | undefined>) => {
|
||||
state.selectedImage = action.payload;
|
||||
// TODO: if the user selects an image, disable the auto switch?
|
||||
// state.shouldAutoSwitchToNewImages = false;
|
||||
@ -84,16 +80,6 @@ export const gallerySlice = createSlice({
|
||||
};
|
||||
}
|
||||
});
|
||||
|
||||
/**
|
||||
* Upload Image - FULFILLED
|
||||
*/
|
||||
builder.addCase(imageUploaded.fulfilled, (state, action) => {
|
||||
const { response } = action.payload;
|
||||
|
||||
const uploadedImage = deserializeImageResponse(response);
|
||||
state.selectedImage = { name: uploadedImage.name, type: 'uploads' };
|
||||
});
|
||||
},
|
||||
});
|
||||
|
||||
|
@ -73,43 +73,43 @@ const resultsSlice = createSlice({
|
||||
state.isLoading = false;
|
||||
});
|
||||
|
||||
/**
|
||||
* Invocation Complete
|
||||
*/
|
||||
builder.addCase(invocationComplete, (state, action) => {
|
||||
const { data, shouldFetchImages } = action.payload;
|
||||
const { result, node, graph_execution_state_id } = data;
|
||||
// /**
|
||||
// * Invocation Complete
|
||||
// */
|
||||
// builder.addCase(invocationComplete, (state, action) => {
|
||||
// const { data, shouldFetchImages } = action.payload;
|
||||
// const { result, node, graph_execution_state_id } = data;
|
||||
|
||||
if (isImageOutput(result)) {
|
||||
const name = result.image.image_name;
|
||||
const type = result.image.image_type;
|
||||
// if (isImageOutput(result)) {
|
||||
// const name = result.image.image_name;
|
||||
// const type = result.image.image_type;
|
||||
|
||||
// if we need to refetch, set URLs to placeholder for now
|
||||
const { url, thumbnail } = shouldFetchImages
|
||||
? { url: '', thumbnail: '' }
|
||||
: buildImageUrls(type, name);
|
||||
// // if we need to refetch, set URLs to placeholder for now
|
||||
// const { url, thumbnail } = shouldFetchImages
|
||||
// ? { url: '', thumbnail: '' }
|
||||
// : buildImageUrls(type, name);
|
||||
|
||||
const timestamp = extractTimestampFromImageName(name);
|
||||
// const timestamp = extractTimestampFromImageName(name);
|
||||
|
||||
const image: Image = {
|
||||
name,
|
||||
type,
|
||||
url,
|
||||
thumbnail,
|
||||
metadata: {
|
||||
created: timestamp,
|
||||
width: result.width,
|
||||
height: result.height,
|
||||
invokeai: {
|
||||
session_id: graph_execution_state_id,
|
||||
...(node ? { node } : {}),
|
||||
},
|
||||
},
|
||||
};
|
||||
// const image: Image = {
|
||||
// name,
|
||||
// type,
|
||||
// url,
|
||||
// thumbnail,
|
||||
// metadata: {
|
||||
// created: timestamp,
|
||||
// width: result.width,
|
||||
// height: result.height,
|
||||
// invokeai: {
|
||||
// session_id: graph_execution_state_id,
|
||||
// ...(node ? { node } : {}),
|
||||
// },
|
||||
// },
|
||||
// };
|
||||
|
||||
resultsAdapter.setOne(state, image);
|
||||
}
|
||||
});
|
||||
// resultsAdapter.setOne(state, image);
|
||||
// }
|
||||
// });
|
||||
|
||||
/**
|
||||
* Image Received - FULFILLED
|
||||
|
@ -35,7 +35,7 @@ const uploadsSlice = createSlice({
|
||||
name: 'uploads',
|
||||
initialState: initialUploadsState,
|
||||
reducers: {
|
||||
uploadAdded: uploadsAdapter.addOne,
|
||||
uploadAdded: uploadsAdapter.upsertOne,
|
||||
},
|
||||
extraReducers: (builder) => {
|
||||
/**
|
||||
@ -61,17 +61,6 @@ const uploadsSlice = createSlice({
|
||||
state.isLoading = false;
|
||||
});
|
||||
|
||||
/**
|
||||
* Upload Image - FULFILLED
|
||||
*/
|
||||
builder.addCase(imageUploaded.fulfilled, (state, action) => {
|
||||
const { location, response } = action.payload;
|
||||
|
||||
const uploadedImage = deserializeImageResponse(response);
|
||||
|
||||
uploadsAdapter.setOne(state, uploadedImage);
|
||||
});
|
||||
|
||||
/**
|
||||
* Delete Image - FULFILLED
|
||||
*/
|
||||
|
@ -5,9 +5,9 @@ import { useAppDispatch, useAppSelector } from 'app/store/storeHooks';
|
||||
import SelectImagePlaceholder from 'common/components/SelectImagePlaceholder';
|
||||
import { useGetUrl } from 'common/util/getUrl';
|
||||
import useGetImageByNameAndType from 'features/gallery/hooks/useGetImageByName';
|
||||
import {
|
||||
import generationSlice, {
|
||||
clearInitialImage,
|
||||
initialImageSelected,
|
||||
initialImageChanged,
|
||||
} from 'features/parameters/store/generationSlice';
|
||||
import { addToast } from 'features/system/store/systemSlice';
|
||||
import { isEqual } from 'lodash-es';
|
||||
@ -15,23 +15,26 @@ import { DragEvent, useCallback, useState } from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { ImageType } from 'services/api';
|
||||
import ImageToImageOverlay from 'common/components/ImageToImageOverlay';
|
||||
import { initialImageSelector } from 'features/parameters/store/generationSelectors';
|
||||
import {
|
||||
generationSelector,
|
||||
initialImageSelector,
|
||||
} from 'features/parameters/store/generationSelectors';
|
||||
import { initialImageSelected } from 'features/parameters/store/actions';
|
||||
|
||||
const selector = createSelector(
|
||||
[initialImageSelector],
|
||||
(initialImage) => {
|
||||
[generationSelector],
|
||||
(generation) => {
|
||||
const { initialImage, isImageToImageEnabled } = generation;
|
||||
return {
|
||||
initialImage,
|
||||
isImageToImageEnabled,
|
||||
};
|
||||
},
|
||||
{ memoizeOptions: { resultEqualityCheck: isEqual } }
|
||||
);
|
||||
|
||||
const InitialImagePreview = () => {
|
||||
const isImageToImageEnabled = useAppSelector(
|
||||
(state: RootState) => state.generation.isImageToImageEnabled
|
||||
);
|
||||
const { initialImage } = useAppSelector(selector);
|
||||
const { initialImage, isImageToImageEnabled } = useAppSelector(selector);
|
||||
const { getUrl } = useGetUrl();
|
||||
const dispatch = useAppDispatch();
|
||||
const { t } = useTranslation();
|
||||
@ -55,22 +58,13 @@ const InitialImagePreview = () => {
|
||||
const handleDrop = useCallback(
|
||||
(e: DragEvent<HTMLDivElement>) => {
|
||||
setIsLoaded(false);
|
||||
|
||||
const name = e.dataTransfer.getData('invokeai/imageName');
|
||||
const type = e.dataTransfer.getData('invokeai/imageType') as ImageType;
|
||||
|
||||
if (!name || !type) {
|
||||
return;
|
||||
}
|
||||
|
||||
const image = getImageByNameAndType(name, type);
|
||||
|
||||
if (!image) {
|
||||
return;
|
||||
}
|
||||
|
||||
dispatch(initialImageSelected({ name, type }));
|
||||
},
|
||||
[getImageByNameAndType, dispatch]
|
||||
[dispatch]
|
||||
);
|
||||
|
||||
return (
|
||||
|
@ -4,9 +4,11 @@ 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 { initialImageChanged, setSeed } from '../store/generationSlice';
|
||||
import { isImage, isImageField } from 'services/types/guards';
|
||||
import { NUMPY_RAND_MAX } from 'app/constants';
|
||||
import { initialImageSelected } from '../store/actions';
|
||||
import { Image } from 'app/types/invokeai';
|
||||
|
||||
export const useParameters = () => {
|
||||
const dispatch = useAppDispatch();
|
||||
@ -86,7 +88,7 @@ export const useParameters = () => {
|
||||
}
|
||||
|
||||
dispatch(
|
||||
initialImageSelected({ name: image.image_name, type: image.image_type })
|
||||
initialImageChanged({ name: image.image_name, type: image.image_type })
|
||||
);
|
||||
toast({
|
||||
title: t('toast.initialImageSet'),
|
||||
@ -102,27 +104,10 @@ export const useParameters = () => {
|
||||
* 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: 'warning',
|
||||
duration: 2500,
|
||||
isClosable: true,
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
(image: Image) => {
|
||||
dispatch(initialImageSelected({ name: image.name, type: image.type }));
|
||||
toast({
|
||||
title: t('toast.sentToImageToImage'),
|
||||
status: 'info',
|
||||
duration: 2500,
|
||||
isClosable: true,
|
||||
});
|
||||
},
|
||||
[t, toast, dispatch]
|
||||
[dispatch]
|
||||
);
|
||||
|
||||
return { recallPrompt, recallSeed, recallInitialImage, sendToImageToImage };
|
||||
|
@ -0,0 +1,12 @@
|
||||
import { createAction } from '@reduxjs/toolkit';
|
||||
import { Image } from 'app/types/invokeai';
|
||||
import { ImageType } from 'services/api';
|
||||
|
||||
export type SelectedImage = {
|
||||
name: string;
|
||||
type: ImageType;
|
||||
};
|
||||
|
||||
export const initialImageSelected = createAction<
|
||||
Image | SelectedImage | undefined
|
||||
>('generation/initialImageSelected');
|
@ -7,17 +7,12 @@ import { seedWeightsToString } from 'common/util/seedWeightPairs';
|
||||
import { clamp } from 'lodash-es';
|
||||
import { ImageField, ImageType } from 'services/api';
|
||||
|
||||
export type SelectedImage = {
|
||||
name: string;
|
||||
type: ImageType;
|
||||
};
|
||||
|
||||
export interface GenerationState {
|
||||
cfgScale: number;
|
||||
height: number;
|
||||
img2imgStrength: number;
|
||||
infillMethod: string;
|
||||
initialImage?: SelectedImage; // can be an Image or url
|
||||
initialImage?: InvokeAI.Image; // can be an Image or url
|
||||
iterations: number;
|
||||
maskPath: string;
|
||||
perlin: number;
|
||||
@ -351,7 +346,7 @@ export const generationSlice = createSlice({
|
||||
setVerticalSymmetrySteps: (state, action: PayloadAction<number>) => {
|
||||
state.verticalSymmetrySteps = action.payload;
|
||||
},
|
||||
initialImageSelected: (state, action: PayloadAction<SelectedImage>) => {
|
||||
initialImageChanged: (state, action: PayloadAction<InvokeAI.Image>) => {
|
||||
state.initialImage = action.payload;
|
||||
state.isImageToImageEnabled = true;
|
||||
},
|
||||
@ -399,7 +394,7 @@ export const {
|
||||
setShouldUseSymmetry,
|
||||
setHorizontalSymmetrySteps,
|
||||
setVerticalSymmetrySteps,
|
||||
initialImageSelected,
|
||||
initialImageChanged,
|
||||
isImageToImageEnabledChanged,
|
||||
} = generationSlice.actions;
|
||||
|
||||
|
@ -15,7 +15,7 @@ import {
|
||||
} from 'services/events/actions';
|
||||
|
||||
import { ProgressImage } from 'services/events/types';
|
||||
import { initialImageSelected } from 'features/parameters/store/generationSlice';
|
||||
import { initialImageChanged } from 'features/parameters/store/generationSlice';
|
||||
import { makeToast } from '../hooks/useToastWatcher';
|
||||
import { sessionCanceled, sessionInvoked } from 'services/thunks/session';
|
||||
import { receivedModels } from 'services/thunks/model';
|
||||
@ -434,13 +434,6 @@ export const systemSlice = createSlice({
|
||||
state.statusTranslationKey = 'common.statusConnected';
|
||||
});
|
||||
|
||||
/**
|
||||
* Initial Image Selected
|
||||
*/
|
||||
builder.addCase(initialImageSelected, (state) => {
|
||||
state.toastQueue.push(makeToast(t('toast.sentToImageToImage')));
|
||||
});
|
||||
|
||||
/**
|
||||
* Received available models from the backend
|
||||
*/
|
||||
|
@ -1,7 +1,7 @@
|
||||
import { Box, BoxProps, Grid, GridItem } from '@chakra-ui/react';
|
||||
import { createSelector } from '@reduxjs/toolkit';
|
||||
import { useAppDispatch, useAppSelector } from 'app/store/storeHooks';
|
||||
import { initialImageSelected } from 'features/parameters/store/generationSlice';
|
||||
import { initialImageChanged } from 'features/parameters/store/generationSlice';
|
||||
import {
|
||||
activeTabNameSelector,
|
||||
uiSelector,
|
||||
|
@ -3,7 +3,7 @@
|
||||
/* eslint-disable */
|
||||
|
||||
/**
|
||||
* Outputs an image from a base 64 data URL.
|
||||
* Outputs an image from a data URL.
|
||||
*/
|
||||
export type DataURLToImageInvocation = {
|
||||
/**
|
||||
|
@ -2,7 +2,7 @@
|
||||
/* tslint:disable */
|
||||
/* eslint-disable */
|
||||
export const $DataURLToImageInvocation = {
|
||||
description: `Outputs an image from a base 64 data URL.`,
|
||||
description: `Outputs an image from a data URL.`,
|
||||
properties: {
|
||||
id: {
|
||||
type: 'string',
|
||||
|
@ -1,4 +1,4 @@
|
||||
import { createAction } from '@reduxjs/toolkit';
|
||||
import { AnyAction, createAction } from '@reduxjs/toolkit';
|
||||
import {
|
||||
GeneratorProgressEvent,
|
||||
GraphExecutionStateCompleteEvent,
|
||||
|
@ -5,20 +5,14 @@ import {
|
||||
ClientToServerEvents,
|
||||
ServerToClientEvents,
|
||||
} from 'services/events/types';
|
||||
import {
|
||||
invocationComplete,
|
||||
socketSubscribed,
|
||||
socketUnsubscribed,
|
||||
} from './actions';
|
||||
import { AppDispatch, RootState } from 'app/store/store';
|
||||
import { socketSubscribed, socketUnsubscribed } from './actions';
|
||||
import { AppThunkDispatch, RootState } from 'app/store/store';
|
||||
import { getTimestamp } from 'common/util/getTimestamp';
|
||||
import {
|
||||
sessionInvoked,
|
||||
isFulfilledSessionCreatedAction,
|
||||
} from 'services/thunks/session';
|
||||
import { OpenAPI } from 'services/api';
|
||||
import { isImageOutput } from 'services/types/guards';
|
||||
import { imageReceived, thumbnailReceived } from 'services/thunks/image';
|
||||
import { setEventListeners } from 'services/events/util/setEventListeners';
|
||||
import { log } from 'app/logging/useLogger';
|
||||
|
||||
@ -56,13 +50,15 @@ export const socketMiddleware = () => {
|
||||
);
|
||||
|
||||
const middleware: Middleware =
|
||||
(store: MiddlewareAPI<AppDispatch, RootState>) => (next) => (action) => {
|
||||
const { dispatch, getState } = store;
|
||||
(storeApi: MiddlewareAPI<AppThunkDispatch, RootState>) =>
|
||||
(next) =>
|
||||
(action) => {
|
||||
const { dispatch, getState } = storeApi;
|
||||
|
||||
// Set listeners for `connect` and `disconnect` events once
|
||||
// Must happen in middleware to get access to `dispatch`
|
||||
if (!areListenersSet) {
|
||||
setEventListeners({ store, socket, log: socketioLog });
|
||||
setEventListeners({ storeApi, socket, log: socketioLog });
|
||||
|
||||
areListenersSet = true;
|
||||
|
||||
@ -107,26 +103,6 @@ export const socketMiddleware = () => {
|
||||
dispatch(sessionInvoked({ sessionId }));
|
||||
}
|
||||
|
||||
if (invocationComplete.match(action)) {
|
||||
const { config } = getState();
|
||||
|
||||
if (config.shouldFetchImages) {
|
||||
const { result } = action.payload.data;
|
||||
if (isImageOutput(result)) {
|
||||
const imageName = result.image.image_name;
|
||||
const imageType = result.image.image_type;
|
||||
|
||||
dispatch(imageReceived({ imageName, imageType }));
|
||||
dispatch(
|
||||
thumbnailReceived({
|
||||
thumbnailName: imageName,
|
||||
thumbnailType: imageType,
|
||||
})
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
next(action);
|
||||
};
|
||||
|
||||
|
@ -27,13 +27,13 @@ import { addToast } from '../../../features/system/store/systemSlice';
|
||||
|
||||
type SetEventListenersArg = {
|
||||
socket: Socket<ServerToClientEvents, ClientToServerEvents>;
|
||||
store: MiddlewareAPI<AppDispatch, RootState>;
|
||||
storeApi: MiddlewareAPI<AppDispatch, RootState>;
|
||||
log: Logger<JsonObject>;
|
||||
};
|
||||
|
||||
export const setEventListeners = (arg: SetEventListenersArg) => {
|
||||
const { socket, store, log } = arg;
|
||||
const { dispatch, getState } = store;
|
||||
const { socket, storeApi, log } = arg;
|
||||
const { dispatch, getState } = storeApi;
|
||||
|
||||
/**
|
||||
* Connect
|
||||
|
@ -20,7 +20,12 @@
|
||||
"*": ["./src/*"]
|
||||
}
|
||||
},
|
||||
"include": ["src/**/*.ts", "src/**/*.tsx", "*.d.ts"],
|
||||
"include": [
|
||||
"src/**/*.ts",
|
||||
"src/**/*.tsx",
|
||||
"*.d.ts",
|
||||
"src/app/store/middleware/listenerMiddleware"
|
||||
],
|
||||
"exclude": ["src/services/fixtures/*", "node_modules", "dist"],
|
||||
"references": [{ "path": "./tsconfig.node.json" }]
|
||||
}
|
||||
|
@ -1836,7 +1836,7 @@
|
||||
resolved "https://registry.yarnpkg.com/@types/geojson/-/geojson-7946.0.10.tgz#6dfbf5ea17142f7f9a043809f1cd4c448cb68249"
|
||||
integrity sha512-Nmh0K3iWQJzniTuPRcJn5hxXkfB1T1pgB89SBig5PlJQU5yocazeu4jATJlaA0GYFKWMqDdvYemoSnF2pXgLVA==
|
||||
|
||||
"@types/hoist-non-react-statics@^3.3.1":
|
||||
"@types/hoist-non-react-statics@^3.3.0", "@types/hoist-non-react-statics@^3.3.1":
|
||||
version "3.3.1"
|
||||
resolved "https://registry.yarnpkg.com/@types/hoist-non-react-statics/-/hoist-non-react-statics-3.3.1.tgz#1124aafe5118cb591977aeb1ceaaed1070eb039f"
|
||||
integrity sha512-iMIqiko6ooLrTh1joXodJK5X9xeEALT1kM5G3ZLhD3hszxBdIEd5C75U834D9mLcINgD4OyZf5uQXjkuYydWvA==
|
||||
@ -1907,6 +1907,16 @@
|
||||
dependencies:
|
||||
"@types/react" "*"
|
||||
|
||||
"@types/react-redux@^7.1.25":
|
||||
version "7.1.25"
|
||||
resolved "https://registry.yarnpkg.com/@types/react-redux/-/react-redux-7.1.25.tgz#de841631205b24f9dfb4967dd4a7901e048f9a88"
|
||||
integrity sha512-bAGh4e+w5D8dajd6InASVIyCo4pZLJ66oLb80F9OBLO1gKESbZcRCJpTT6uLXX+HAB57zw1WTdwJdAsewuTweg==
|
||||
dependencies:
|
||||
"@types/hoist-non-react-statics" "^3.3.0"
|
||||
"@types/react" "*"
|
||||
hoist-non-react-statics "^3.3.0"
|
||||
redux "^4.0.0"
|
||||
|
||||
"@types/react-transition-group@^4.4.5":
|
||||
version "4.4.5"
|
||||
resolved "https://registry.yarnpkg.com/@types/react-transition-group/-/react-transition-group-4.4.5.tgz#aae20dcf773c5aa275d5b9f7cdbca638abc5e416"
|
||||
@ -5687,7 +5697,7 @@ redux-thunk@^2.4.2:
|
||||
resolved "https://registry.yarnpkg.com/redux-thunk/-/redux-thunk-2.4.2.tgz#b9d05d11994b99f7a91ea223e8b04cf0afa5ef3b"
|
||||
integrity sha512-+P3TjtnP0k/FEjcBL5FZpoovtvrTNT/UXd4/sluaSyrURlSlhLSzEdfsTBW7WsKB6yPvgd7q/iZPICFjW4o57Q==
|
||||
|
||||
redux@^4.2.1:
|
||||
redux@^4.0.0, redux@^4.2.1:
|
||||
version "4.2.1"
|
||||
resolved "https://registry.yarnpkg.com/redux/-/redux-4.2.1.tgz#c08f4306826c49b5e9dc901dee0452ea8fce6197"
|
||||
integrity sha512-LAUYz4lc+Do8/g7aeRa8JkyDErK6ekstQaqWQrNRW//MY1TvCEpMtpTWvlQ+FPbWCx+Xixu/6SHt5N0HR+SB4w==
|
||||
|
Loading…
Reference in New Issue
Block a user