mirror of
https://github.com/invoke-ai/InvokeAI
synced 2024-08-30 20:32:17 +00:00
feat(ui): clear features if image used by them is deleted
This handles the case when an image is deleted but is still in use in as eg an init image on canvas, or a control image. If we just delete the image, canvas/controlnet/etc may break (the image would just fail to load). When an image is deleted, the app checks to see if it is in use in: - Image to Image - ControlNet - Unified Canvas - Node Editor The delete dialog will always open if the image is in use anywhere, and the user is advised that deleting the image will reset the feature(s). Even if the user has ticked the box to not confirm on delete, the dialog will still show if the image is in use somewhere.
This commit is contained in:
@ -21,7 +21,7 @@ import { ReactNode, memo, useCallback, useEffect, useState } from 'react';
|
|||||||
import { APP_HEIGHT, APP_WIDTH } from 'theme/util/constants';
|
import { APP_HEIGHT, APP_WIDTH } from 'theme/util/constants';
|
||||||
import GlobalHotkeys from './GlobalHotkeys';
|
import GlobalHotkeys from './GlobalHotkeys';
|
||||||
import Toaster from './Toaster';
|
import Toaster from './Toaster';
|
||||||
import DeleteModal from 'features/gallery/components/DeleteModal';
|
import DeleteImageModal from 'features/gallery/components/DeleteImageModal';
|
||||||
|
|
||||||
const DEFAULT_CONFIG = {};
|
const DEFAULT_CONFIG = {};
|
||||||
|
|
||||||
@ -134,7 +134,7 @@ const App = ({
|
|||||||
<FloatingGalleryButton />
|
<FloatingGalleryButton />
|
||||||
</Portal>
|
</Portal>
|
||||||
</Grid>
|
</Grid>
|
||||||
<DeleteModal />
|
<DeleteImageModal />
|
||||||
<Toaster />
|
<Toaster />
|
||||||
<GlobalHotkeys />
|
<GlobalHotkeys />
|
||||||
</>
|
</>
|
||||||
|
@ -7,6 +7,12 @@ import { systemSelector } from 'features/system/store/systemSelectors';
|
|||||||
import { PropsWithChildren, createContext, useCallback, useState } from 'react';
|
import { PropsWithChildren, createContext, useCallback, useState } from 'react';
|
||||||
import { ImageDTO } from 'services/api';
|
import { ImageDTO } from 'services/api';
|
||||||
|
|
||||||
|
import { useImageUsage } from 'common/hooks/useImageUsage';
|
||||||
|
import { resetCanvas } from 'features/canvas/store/canvasSlice';
|
||||||
|
import { controlNetReset } from 'features/controlNet/store/controlNetSlice';
|
||||||
|
import { clearInitialImage } from 'features/parameters/store/generationSlice';
|
||||||
|
import { nodeEditorReset } from 'features/nodes/store/nodesSlice';
|
||||||
|
|
||||||
type DeleteImageContextValue = {
|
type DeleteImageContextValue = {
|
||||||
/**
|
/**
|
||||||
* Whether the delete image dialog is open.
|
* Whether the delete image dialog is open.
|
||||||
@ -16,16 +22,20 @@ type DeleteImageContextValue = {
|
|||||||
* Closes the delete image dialog.
|
* Closes the delete image dialog.
|
||||||
*/
|
*/
|
||||||
onClose: () => void;
|
onClose: () => void;
|
||||||
|
/**
|
||||||
|
* Opens the delete image dialog and handles all deletion-related checks.
|
||||||
|
*/
|
||||||
|
onDelete: (image?: ImageDTO) => void;
|
||||||
|
/**
|
||||||
|
* The image pending deletion
|
||||||
|
*/
|
||||||
|
image?: ImageDTO;
|
||||||
/**
|
/**
|
||||||
* Immediately deletes an image.
|
* Immediately deletes an image.
|
||||||
*
|
*
|
||||||
* You probably don't want to use this - use `onDelete` instead.
|
* You probably don't want to use this - use `onDelete` instead.
|
||||||
*/
|
*/
|
||||||
onImmediatelyDelete: () => void;
|
onImmediatelyDelete: () => void;
|
||||||
/**
|
|
||||||
* Opens the delete image dialog and handles all deletion-related checks.
|
|
||||||
*/
|
|
||||||
onDelete: (image?: ImageDTO) => void;
|
|
||||||
};
|
};
|
||||||
|
|
||||||
export const DeleteImageContext = createContext<DeleteImageContextValue>({
|
export const DeleteImageContext = createContext<DeleteImageContextValue>({
|
||||||
@ -43,8 +53,6 @@ const selector = createSelector(
|
|||||||
return {
|
return {
|
||||||
canDeleteImage: isConnected && !isProcessing,
|
canDeleteImage: isConnected && !isProcessing,
|
||||||
shouldConfirmOnDelete,
|
shouldConfirmOnDelete,
|
||||||
isProcessing,
|
|
||||||
isConnected,
|
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
defaultSelectorOptions
|
defaultSelectorOptions
|
||||||
@ -57,6 +65,35 @@ export const DeleteImageContextProvider = (props: Props) => {
|
|||||||
const [imageToDelete, setImageToDelete] = useState<ImageDTO>();
|
const [imageToDelete, setImageToDelete] = useState<ImageDTO>();
|
||||||
const dispatch = useAppDispatch();
|
const dispatch = useAppDispatch();
|
||||||
const { isOpen, onOpen, onClose } = useDisclosure();
|
const { isOpen, onOpen, onClose } = useDisclosure();
|
||||||
|
const imageUsage = useImageUsage(imageToDelete?.image_name);
|
||||||
|
|
||||||
|
const handleActualDeletion = useCallback(
|
||||||
|
(image: ImageDTO) => {
|
||||||
|
dispatch(requestedImageDeletion(image));
|
||||||
|
|
||||||
|
if (imageUsage.isCanvasImage) {
|
||||||
|
dispatch(resetCanvas());
|
||||||
|
}
|
||||||
|
|
||||||
|
if (imageUsage.isControlNetImage) {
|
||||||
|
dispatch(controlNetReset());
|
||||||
|
}
|
||||||
|
|
||||||
|
if (imageUsage.isInitialImage) {
|
||||||
|
dispatch(clearInitialImage());
|
||||||
|
}
|
||||||
|
|
||||||
|
if (imageUsage.isControlNetImage) {
|
||||||
|
dispatch(nodeEditorReset());
|
||||||
|
}
|
||||||
|
},
|
||||||
|
[
|
||||||
|
dispatch,
|
||||||
|
imageUsage.isCanvasImage,
|
||||||
|
imageUsage.isControlNetImage,
|
||||||
|
imageUsage.isInitialImage,
|
||||||
|
]
|
||||||
|
);
|
||||||
|
|
||||||
const closeAndClearImageToDelete = useCallback(() => {
|
const closeAndClearImageToDelete = useCallback(() => {
|
||||||
setImageToDelete(undefined);
|
setImageToDelete(undefined);
|
||||||
@ -65,20 +102,25 @@ export const DeleteImageContextProvider = (props: Props) => {
|
|||||||
|
|
||||||
const onImmediatelyDelete = useCallback(() => {
|
const onImmediatelyDelete = useCallback(() => {
|
||||||
if (canDeleteImage && imageToDelete) {
|
if (canDeleteImage && imageToDelete) {
|
||||||
dispatch(requestedImageDeletion(imageToDelete));
|
handleActualDeletion(imageToDelete);
|
||||||
}
|
}
|
||||||
closeAndClearImageToDelete();
|
closeAndClearImageToDelete();
|
||||||
}, [canDeleteImage, imageToDelete, closeAndClearImageToDelete, dispatch]);
|
}, [
|
||||||
|
canDeleteImage,
|
||||||
|
imageToDelete,
|
||||||
|
closeAndClearImageToDelete,
|
||||||
|
handleActualDeletion,
|
||||||
|
]);
|
||||||
|
|
||||||
const handleDelete = useCallback(
|
const handleGatedDeletion = useCallback(
|
||||||
(image: ImageDTO) => {
|
(image: ImageDTO) => {
|
||||||
if (shouldConfirmOnDelete) {
|
if (shouldConfirmOnDelete || imageUsage) {
|
||||||
onOpen();
|
onOpen();
|
||||||
} else {
|
} else {
|
||||||
dispatch(requestedImageDeletion(image));
|
handleActualDeletion(image);
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
[shouldConfirmOnDelete, onOpen, dispatch]
|
[shouldConfirmOnDelete, imageUsage, onOpen, handleActualDeletion]
|
||||||
);
|
);
|
||||||
|
|
||||||
const onDelete = useCallback(
|
const onDelete = useCallback(
|
||||||
@ -87,15 +129,16 @@ export const DeleteImageContextProvider = (props: Props) => {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
setImageToDelete(image);
|
setImageToDelete(image);
|
||||||
handleDelete(image);
|
handleGatedDeletion(image);
|
||||||
},
|
},
|
||||||
[handleDelete]
|
[handleGatedDeletion]
|
||||||
);
|
);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<DeleteImageContext.Provider
|
<DeleteImageContext.Provider
|
||||||
value={{
|
value={{
|
||||||
isOpen,
|
isOpen,
|
||||||
|
image: imageToDelete,
|
||||||
onClose: closeAndClearImageToDelete,
|
onClose: closeAndClearImageToDelete,
|
||||||
onDelete,
|
onDelete,
|
||||||
onImmediatelyDelete,
|
onImmediatelyDelete,
|
||||||
|
@ -1,4 +1,5 @@
|
|||||||
import { createSelector } from '@reduxjs/toolkit';
|
import { createSelector } from '@reduxjs/toolkit';
|
||||||
|
import { RootState } from 'app/store/store';
|
||||||
import { useAppSelector } from 'app/store/storeHooks';
|
import { useAppSelector } from 'app/store/storeHooks';
|
||||||
import { defaultSelectorOptions } from 'app/store/util/defaultMemoizeOptions';
|
import { defaultSelectorOptions } from 'app/store/util/defaultMemoizeOptions';
|
||||||
import { canvasSelector } from 'features/canvas/store/canvasSelectors';
|
import { canvasSelector } from 'features/canvas/store/canvasSelectors';
|
||||||
@ -7,13 +8,20 @@ import { nodesSelecter } from 'features/nodes/store/nodesSlice';
|
|||||||
import { generationSelector } from 'features/parameters/store/generationSelectors';
|
import { generationSelector } from 'features/parameters/store/generationSelectors';
|
||||||
import { some } from 'lodash-es';
|
import { some } from 'lodash-es';
|
||||||
|
|
||||||
const selectIsImageInUse = createSelector(
|
export type ImageUsage = {
|
||||||
|
isInitialImage: boolean;
|
||||||
|
isCanvasImage: boolean;
|
||||||
|
isNodesImage: boolean;
|
||||||
|
isControlNetImage: boolean;
|
||||||
|
};
|
||||||
|
|
||||||
|
const selectImageUsage = createSelector(
|
||||||
[
|
[
|
||||||
generationSelector,
|
generationSelector,
|
||||||
canvasSelector,
|
canvasSelector,
|
||||||
nodesSelecter,
|
nodesSelecter,
|
||||||
controlNetSelector,
|
controlNetSelector,
|
||||||
(state, image_name) => image_name,
|
(state: RootState, image_name?: string) => image_name,
|
||||||
],
|
],
|
||||||
(generation, canvas, nodes, controlNet, image_name) => {
|
(generation, canvas, nodes, controlNet, image_name) => {
|
||||||
const isInitialImage = generation.initialImage?.image_name === image_name;
|
const isInitialImage = generation.initialImage?.image_name === image_name;
|
||||||
@ -37,18 +45,22 @@ const selectIsImageInUse = createSelector(
|
|||||||
c.processedControlImage?.image_name === image_name
|
c.processedControlImage?.image_name === image_name
|
||||||
);
|
);
|
||||||
|
|
||||||
return {
|
const imageUsage: ImageUsage = {
|
||||||
isInitialImage,
|
isInitialImage,
|
||||||
isCanvasImage,
|
isCanvasImage,
|
||||||
isNodesImage,
|
isNodesImage,
|
||||||
isControlNetImage,
|
isControlNetImage,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
return imageUsage;
|
||||||
},
|
},
|
||||||
defaultSelectorOptions
|
defaultSelectorOptions
|
||||||
);
|
);
|
||||||
|
|
||||||
export const useGetIsImageInUse = (image_name?: string) => {
|
export const useImageUsage = (image_name?: string) => {
|
||||||
const a = useAppSelector((state) => selectIsImageInUse(state, image_name));
|
const imageUsage = useAppSelector((state) =>
|
||||||
|
selectImageUsage(state, image_name)
|
||||||
|
);
|
||||||
|
|
||||||
return a;
|
return imageUsage;
|
||||||
};
|
};
|
@ -187,6 +187,9 @@ export const controlNetSlice = createSlice({
|
|||||||
processorType
|
processorType
|
||||||
].default as RequiredControlNetProcessorNode;
|
].default as RequiredControlNetProcessorNode;
|
||||||
},
|
},
|
||||||
|
controlNetReset: () => {
|
||||||
|
return { ...initialControlNetState };
|
||||||
|
},
|
||||||
},
|
},
|
||||||
extraReducers: (builder) => {
|
extraReducers: (builder) => {
|
||||||
builder.addCase(controlNetImageProcessed, (state, action) => {
|
builder.addCase(controlNetImageProcessed, (state, action) => {
|
||||||
@ -243,6 +246,7 @@ export const {
|
|||||||
controlNetEndStepPctChanged,
|
controlNetEndStepPctChanged,
|
||||||
controlNetProcessorParamsChanged,
|
controlNetProcessorParamsChanged,
|
||||||
controlNetProcessorTypeChanged,
|
controlNetProcessorTypeChanged,
|
||||||
|
controlNetReset,
|
||||||
} = controlNetSlice.actions;
|
} = controlNetSlice.actions;
|
||||||
|
|
||||||
export default controlNetSlice.reducer;
|
export default controlNetSlice.reducer;
|
||||||
|
@ -50,7 +50,7 @@ import UpscaleSettings from 'features/parameters/components/Parameters/Upscale/U
|
|||||||
import { useAppToaster } from 'app/components/Toaster';
|
import { useAppToaster } from 'app/components/Toaster';
|
||||||
import { setInitialCanvasImage } from 'features/canvas/store/canvasSlice';
|
import { setInitialCanvasImage } from 'features/canvas/store/canvasSlice';
|
||||||
import { DeleteImageContext } from 'app/contexts/DeleteImageContext';
|
import { DeleteImageContext } from 'app/contexts/DeleteImageContext';
|
||||||
import { DeleteImageButton } from './DeleteModal';
|
import { DeleteImageButton } from './DeleteImageModal';
|
||||||
|
|
||||||
const currentImageButtonsSelector = createSelector(
|
const currentImageButtonsSelector = createSelector(
|
||||||
[
|
[
|
||||||
|
@ -15,7 +15,6 @@ import { imageSelected } from '../store/gallerySlice';
|
|||||||
import IAIDndImage from 'common/components/IAIDndImage';
|
import IAIDndImage from 'common/components/IAIDndImage';
|
||||||
import { ImageDTO } from 'services/api';
|
import { ImageDTO } from 'services/api';
|
||||||
import { IAIImageFallback } from 'common/components/IAIImageFallback';
|
import { IAIImageFallback } from 'common/components/IAIImageFallback';
|
||||||
import { useGetIsImageInUse } from 'common/hooks/useGetIsImageInUse';
|
|
||||||
|
|
||||||
export const imagesSelector = createSelector(
|
export const imagesSelector = createSelector(
|
||||||
[uiSelector, gallerySelector, systemSelector],
|
[uiSelector, gallerySelector, systemSelector],
|
||||||
@ -55,8 +54,6 @@ const CurrentImagePreview = () => {
|
|||||||
const toaster = useAppToaster();
|
const toaster = useAppToaster();
|
||||||
const dispatch = useAppDispatch();
|
const dispatch = useAppDispatch();
|
||||||
|
|
||||||
const isImageInUse = useGetIsImageInUse(image?.image_name);
|
|
||||||
console.log(isImageInUse);
|
|
||||||
const handleError = useCallback(() => {
|
const handleError = useCallback(() => {
|
||||||
dispatch(imageSelected());
|
dispatch(imageSelected());
|
||||||
if (shouldFetchImages) {
|
if (shouldFetchImages) {
|
||||||
|
@ -5,19 +5,24 @@ import {
|
|||||||
AlertDialogFooter,
|
AlertDialogFooter,
|
||||||
AlertDialogHeader,
|
AlertDialogHeader,
|
||||||
AlertDialogOverlay,
|
AlertDialogOverlay,
|
||||||
|
Divider,
|
||||||
Flex,
|
Flex,
|
||||||
|
ListItem,
|
||||||
Text,
|
Text,
|
||||||
|
UnorderedList,
|
||||||
} from '@chakra-ui/react';
|
} from '@chakra-ui/react';
|
||||||
import { createSelector } from '@reduxjs/toolkit';
|
import { createSelector } from '@reduxjs/toolkit';
|
||||||
import { DeleteImageContext } from 'app/contexts/DeleteImageContext';
|
import { DeleteImageContext } from 'app/contexts/DeleteImageContext';
|
||||||
import { useAppDispatch, useAppSelector } from 'app/store/storeHooks';
|
import { useAppDispatch, useAppSelector } from 'app/store/storeHooks';
|
||||||
|
import { defaultSelectorOptions } from 'app/store/util/defaultMemoizeOptions';
|
||||||
import IAIButton from 'common/components/IAIButton';
|
import IAIButton from 'common/components/IAIButton';
|
||||||
import IAIIconButton from 'common/components/IAIIconButton';
|
import IAIIconButton from 'common/components/IAIIconButton';
|
||||||
import IAISwitch from 'common/components/IAISwitch';
|
import IAISwitch from 'common/components/IAISwitch';
|
||||||
|
import { ImageUsage, useImageUsage } from 'common/hooks/useImageUsage';
|
||||||
import { configSelector } from 'features/system/store/configSelectors';
|
import { configSelector } from 'features/system/store/configSelectors';
|
||||||
import { systemSelector } from 'features/system/store/systemSelectors';
|
import { systemSelector } from 'features/system/store/systemSelectors';
|
||||||
import { setShouldConfirmOnDelete } from 'features/system/store/systemSlice';
|
import { setShouldConfirmOnDelete } from 'features/system/store/systemSlice';
|
||||||
import { isEqual } from 'lodash-es';
|
import { some } from 'lodash-es';
|
||||||
|
|
||||||
import { ChangeEvent, memo, useCallback, useContext, useRef } from 'react';
|
import { ChangeEvent, memo, useCallback, useContext, useRef } from 'react';
|
||||||
import { useTranslation } from 'react-i18next';
|
import { useTranslation } from 'react-i18next';
|
||||||
@ -28,31 +33,56 @@ const selector = createSelector(
|
|||||||
(system, config) => {
|
(system, config) => {
|
||||||
const { shouldConfirmOnDelete } = system;
|
const { shouldConfirmOnDelete } = system;
|
||||||
const { canRestoreDeletedImagesFromBin } = config;
|
const { canRestoreDeletedImagesFromBin } = config;
|
||||||
return { shouldConfirmOnDelete, canRestoreDeletedImagesFromBin };
|
|
||||||
|
return {
|
||||||
|
shouldConfirmOnDelete,
|
||||||
|
canRestoreDeletedImagesFromBin,
|
||||||
|
};
|
||||||
},
|
},
|
||||||
{
|
defaultSelectorOptions
|
||||||
memoizeOptions: {
|
|
||||||
resultEqualityCheck: isEqual,
|
|
||||||
},
|
|
||||||
}
|
|
||||||
);
|
);
|
||||||
|
|
||||||
|
const ImageInUseMessage = (props: { imageUsage: ImageUsage }) => {
|
||||||
|
const { imageUsage } = props;
|
||||||
|
|
||||||
|
if (!some(imageUsage)) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<Text>This image is currently in use in the following features:</Text>
|
||||||
|
<UnorderedList sx={{ paddingInlineStart: 6 }}>
|
||||||
|
{imageUsage.isInitialImage && <ListItem>Image to Image</ListItem>}
|
||||||
|
{imageUsage.isCanvasImage && <ListItem>Unified Canvas</ListItem>}
|
||||||
|
{imageUsage.isControlNetImage && <ListItem>ControlNet</ListItem>}
|
||||||
|
{imageUsage.isNodesImage && <ListItem>Node Editor</ListItem>}
|
||||||
|
</UnorderedList>
|
||||||
|
<Text>
|
||||||
|
If you delete this image, those features will immediately be reset.
|
||||||
|
</Text>
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
const DeleteImageModal = () => {
|
const DeleteImageModal = () => {
|
||||||
const dispatch = useAppDispatch();
|
const dispatch = useAppDispatch();
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
|
|
||||||
|
const { isOpen, onClose, onImmediatelyDelete, image } =
|
||||||
|
useContext(DeleteImageContext);
|
||||||
|
|
||||||
const { shouldConfirmOnDelete, canRestoreDeletedImagesFromBin } =
|
const { shouldConfirmOnDelete, canRestoreDeletedImagesFromBin } =
|
||||||
useAppSelector(selector);
|
useAppSelector(selector);
|
||||||
|
|
||||||
|
const imageUsage = useImageUsage(image?.image_name);
|
||||||
|
|
||||||
const handleChangeShouldConfirmOnDelete = useCallback(
|
const handleChangeShouldConfirmOnDelete = useCallback(
|
||||||
(e: ChangeEvent<HTMLInputElement>) =>
|
(e: ChangeEvent<HTMLInputElement>) =>
|
||||||
dispatch(setShouldConfirmOnDelete(!e.target.checked)),
|
dispatch(setShouldConfirmOnDelete(!e.target.checked)),
|
||||||
[dispatch]
|
[dispatch]
|
||||||
);
|
);
|
||||||
|
|
||||||
const { isOpen, onClose, onImmediatelyDelete } =
|
|
||||||
useContext(DeleteImageContext);
|
|
||||||
|
|
||||||
const cancelRef = useRef<HTMLButtonElement>(null);
|
const cancelRef = useRef<HTMLButtonElement>(null);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
@ -69,15 +99,15 @@ const DeleteImageModal = () => {
|
|||||||
</AlertDialogHeader>
|
</AlertDialogHeader>
|
||||||
|
|
||||||
<AlertDialogBody>
|
<AlertDialogBody>
|
||||||
<Flex direction="column" gap={5}>
|
<Flex direction="column" gap={3}>
|
||||||
<Flex direction="column" gap={2}>
|
<ImageInUseMessage imageUsage={imageUsage} />
|
||||||
<Text>{t('common.areYouSure')}</Text>
|
<Divider />
|
||||||
<Text>
|
<Text>
|
||||||
{canRestoreDeletedImagesFromBin
|
{canRestoreDeletedImagesFromBin
|
||||||
? t('gallery.deleteImageBin')
|
? t('gallery.deleteImageBin')
|
||||||
: t('gallery.deleteImagePermanent')}
|
: t('gallery.deleteImagePermanent')}
|
||||||
</Text>
|
</Text>
|
||||||
</Flex>
|
<Text>{t('common.areYouSure')}</Text>
|
||||||
<IAISwitch
|
<IAISwitch
|
||||||
label={t('common.dontAskMeAgain')}
|
label={t('common.dontAskMeAgain')}
|
||||||
isChecked={!shouldConfirmOnDelete}
|
isChecked={!shouldConfirmOnDelete}
|
@ -93,6 +93,9 @@ const nodesSlice = createSlice({
|
|||||||
console.error(err);
|
console.error(err);
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
nodeEditorReset: () => {
|
||||||
|
return { ...initialNodesState };
|
||||||
|
},
|
||||||
},
|
},
|
||||||
extraReducers(builder) {
|
extraReducers(builder) {
|
||||||
builder.addCase(receivedOpenAPISchema.fulfilled, (state, action) => {
|
builder.addCase(receivedOpenAPISchema.fulfilled, (state, action) => {
|
||||||
@ -127,6 +130,7 @@ export const {
|
|||||||
connectionEnded,
|
connectionEnded,
|
||||||
shouldShowGraphOverlayChanged,
|
shouldShowGraphOverlayChanged,
|
||||||
parsedOpenAPISchema,
|
parsedOpenAPISchema,
|
||||||
|
nodeEditorReset,
|
||||||
} = nodesSlice.actions;
|
} = nodesSlice.actions;
|
||||||
|
|
||||||
export default nodesSlice.reducer;
|
export default nodesSlice.reducer;
|
||||||
|
@ -14,7 +14,6 @@ import { useAppToaster } from 'app/components/Toaster';
|
|||||||
import IAIDndImage from 'common/components/IAIDndImage';
|
import IAIDndImage from 'common/components/IAIDndImage';
|
||||||
import { ImageDTO } from 'services/api';
|
import { ImageDTO } from 'services/api';
|
||||||
import { IAIImageFallback } from 'common/components/IAIImageFallback';
|
import { IAIImageFallback } from 'common/components/IAIImageFallback';
|
||||||
import { useGetIsImageInUse } from 'common/hooks/useGetIsImageInUse';
|
|
||||||
|
|
||||||
const selector = createSelector(
|
const selector = createSelector(
|
||||||
[generationSelector],
|
[generationSelector],
|
||||||
|
Reference in New Issue
Block a user