diff --git a/invokeai/frontend/web/public/locales/en.json b/invokeai/frontend/web/public/locales/en.json index d20fae0f1f..881e531701 100644 --- a/invokeai/frontend/web/public/locales/en.json +++ b/invokeai/frontend/web/public/locales/en.json @@ -49,6 +49,7 @@ "close": "Close", "communityLabel": "Community", "controlNet": "Controlnet", + "ipAdapter": "IP Adapter", "darkMode": "Dark Mode", "discordLabel": "Discord", "dontAskMeAgain": "Don't ask me again", diff --git a/invokeai/frontend/web/src/app/store/middleware/listenerMiddleware/listeners/boardAndImagesDeleted.ts b/invokeai/frontend/web/src/app/store/middleware/listenerMiddleware/listeners/boardAndImagesDeleted.ts index d4a36d64dc..69b136ca60 100644 --- a/invokeai/frontend/web/src/app/store/middleware/listenerMiddleware/listeners/boardAndImagesDeleted.ts +++ b/invokeai/frontend/web/src/app/store/middleware/listenerMiddleware/listeners/boardAndImagesDeleted.ts @@ -1,5 +1,8 @@ import { resetCanvas } from 'features/canvas/store/canvasSlice'; -import { controlNetReset } from 'features/controlNet/store/controlNetSlice'; +import { + controlNetReset, + ipAdapterStateReset, +} from 'features/controlNet/store/controlNetSlice'; import { getImageUsage } from 'features/deleteImageModal/store/selectors'; import { nodeEditorReset } from 'features/nodes/store/nodesSlice'; import { clearInitialImage } from 'features/parameters/store/generationSlice'; @@ -18,6 +21,7 @@ export const addDeleteBoardAndImagesFulfilledListener = () => { let wasCanvasReset = false; let wasNodeEditorReset = false; let wasControlNetReset = false; + let wasIPAdapterReset = false; const state = getState(); deleted_images.forEach((image_name) => { @@ -42,6 +46,11 @@ export const addDeleteBoardAndImagesFulfilledListener = () => { dispatch(controlNetReset()); wasControlNetReset = true; } + + if (imageUsage.isIPAdapterImage && !wasIPAdapterReset) { + dispatch(ipAdapterStateReset()); + wasIPAdapterReset = true; + } }); }, }); diff --git a/invokeai/frontend/web/src/app/store/middleware/listenerMiddleware/listeners/imageDeleted.ts b/invokeai/frontend/web/src/app/store/middleware/listenerMiddleware/listeners/imageDeleted.ts index 770c9fc11b..7c51b44aa2 100644 --- a/invokeai/frontend/web/src/app/store/middleware/listenerMiddleware/listeners/imageDeleted.ts +++ b/invokeai/frontend/web/src/app/store/middleware/listenerMiddleware/listeners/imageDeleted.ts @@ -3,6 +3,7 @@ import { resetCanvas } from 'features/canvas/store/canvasSlice'; import { controlNetImageChanged, controlNetProcessedImageChanged, + ipAdapterImageChanged, } from 'features/controlNet/store/controlNetSlice'; import { imageDeletionConfirmed } from 'features/deleteImageModal/store/actions'; import { isModalOpenChanged } from 'features/deleteImageModal/store/slice'; @@ -110,6 +111,14 @@ export const addRequestedSingleImageDeletionListener = () => { } }); + // Remove IP Adapter Set Image if image is deleted. + if ( + getState().controlNet.ipAdapterInfo.adapterImage?.image_name === + imageDTO.image_name + ) { + dispatch(ipAdapterImageChanged(null)); + } + // reset nodes that use the deleted images getState().nodes.nodes.forEach((node) => { if (!isInvocationNode(node)) { @@ -227,6 +236,14 @@ export const addRequestedMultipleImageDeletionListener = () => { } }); + // Remove IP Adapter Set Image if image is deleted. + if ( + getState().controlNet.ipAdapterInfo.adapterImage?.image_name === + imageDTO.image_name + ) { + dispatch(ipAdapterImageChanged(null)); + } + // reset nodes that use the deleted images getState().nodes.nodes.forEach((node) => { if (!isInvocationNode(node)) { diff --git a/invokeai/frontend/web/src/app/store/middleware/listenerMiddleware/listeners/imageToDeleteSelected.ts b/invokeai/frontend/web/src/app/store/middleware/listenerMiddleware/listeners/imageToDeleteSelected.ts index 88a4e773d5..61b4379d5d 100644 --- a/invokeai/frontend/web/src/app/store/middleware/listenerMiddleware/listeners/imageToDeleteSelected.ts +++ b/invokeai/frontend/web/src/app/store/middleware/listenerMiddleware/listeners/imageToDeleteSelected.ts @@ -19,6 +19,7 @@ export const addImageToDeleteSelectedListener = () => { imagesUsage.some((i) => i.isCanvasImage) || imagesUsage.some((i) => i.isInitialImage) || imagesUsage.some((i) => i.isControlNetImage) || + imagesUsage.some((i) => i.isIPAdapterImage) || imagesUsage.some((i) => i.isNodesImage); if (shouldConfirmOnDelete || isImageInUse) { diff --git a/invokeai/frontend/web/src/features/controlNet/store/controlNetSlice.ts b/invokeai/frontend/web/src/features/controlNet/store/controlNetSlice.ts index 30a990c20d..60b326226b 100644 --- a/invokeai/frontend/web/src/features/controlNet/store/controlNetSlice.ts +++ b/invokeai/frontend/web/src/features/controlNet/store/controlNetSlice.ts @@ -386,6 +386,14 @@ export const controlNetSlice = createSlice({ ) => { state.ipAdapterInfo.model = action.payload; }, + ipAdapterStateReset: (state) => { + state.isIPAdapterEnabled = false; + state.ipAdapterInfo = { + adapterImage: null, + model: null, + weight: 1, + }; + }, }, extraReducers: (builder) => { builder.addCase(controlNetImageProcessed, (state, action) => { @@ -449,6 +457,7 @@ export const { ipAdapterImageChanged, ipAdapterWeightChanged, ipAdapterModelChanged, + ipAdapterStateReset, } = controlNetSlice.actions; export default controlNetSlice.reducer; diff --git a/invokeai/frontend/web/src/features/deleteImageModal/components/DeleteImageModal.tsx b/invokeai/frontend/web/src/features/deleteImageModal/components/DeleteImageModal.tsx index 0d8ecfbae6..78bd71e802 100644 --- a/invokeai/frontend/web/src/features/deleteImageModal/components/DeleteImageModal.tsx +++ b/invokeai/frontend/web/src/features/deleteImageModal/components/DeleteImageModal.tsx @@ -10,20 +10,20 @@ import { Text, } from '@chakra-ui/react'; import { createSelector } from '@reduxjs/toolkit'; +import { stateSelector } from 'app/store/store'; import { useAppDispatch, useAppSelector } from 'app/store/storeHooks'; import { defaultSelectorOptions } from 'app/store/util/defaultMemoizeOptions'; import IAIButton from 'common/components/IAIButton'; import IAISwitch from 'common/components/IAISwitch'; import { setShouldConfirmOnDelete } from 'features/system/store/systemSlice'; -import { stateSelector } from 'app/store/store'; import { some } from 'lodash-es'; import { ChangeEvent, memo, useCallback, useRef } from 'react'; import { useTranslation } from 'react-i18next'; import { imageDeletionConfirmed } from '../store/actions'; import { getImageUsage, selectImageUsage } from '../store/selectors'; import { imageDeletionCanceled, isModalOpenChanged } from '../store/slice'; -import ImageUsageMessage from './ImageUsageMessage'; import { ImageUsage } from '../store/types'; +import ImageUsageMessage from './ImageUsageMessage'; const selector = createSelector( [stateSelector, selectImageUsage], @@ -42,6 +42,7 @@ const selector = createSelector( isCanvasImage: some(allImageUsage, (i) => i.isCanvasImage), isNodesImage: some(allImageUsage, (i) => i.isNodesImage), isControlNetImage: some(allImageUsage, (i) => i.isControlNetImage), + isIPAdapterImage: some(allImageUsage, (i) => i.isIPAdapterImage), }; return { diff --git a/invokeai/frontend/web/src/features/deleteImageModal/components/ImageUsageMessage.tsx b/invokeai/frontend/web/src/features/deleteImageModal/components/ImageUsageMessage.tsx index de1782b439..6844e13a32 100644 --- a/invokeai/frontend/web/src/features/deleteImageModal/components/ImageUsageMessage.tsx +++ b/invokeai/frontend/web/src/features/deleteImageModal/components/ImageUsageMessage.tsx @@ -1,8 +1,8 @@ import { ListItem, Text, UnorderedList } from '@chakra-ui/react'; import { some } from 'lodash-es'; import { memo } from 'react'; -import { ImageUsage } from '../store/types'; import { useTranslation } from 'react-i18next'; +import { ImageUsage } from '../store/types'; type Props = { imageUsage?: ImageUsage; @@ -38,6 +38,9 @@ const ImageUsageMessage = (props: Props) => { {imageUsage.isControlNetImage && ( {t('common.controlNet')} )} + {imageUsage.isIPAdapterImage && ( + {t('common.ipAdapter')} + )} {imageUsage.isNodesImage && ( {t('common.nodeEditor')} )} diff --git a/invokeai/frontend/web/src/features/deleteImageModal/store/selectors.ts b/invokeai/frontend/web/src/features/deleteImageModal/store/selectors.ts index 37be06bad6..151e975634 100644 --- a/invokeai/frontend/web/src/features/deleteImageModal/store/selectors.ts +++ b/invokeai/frontend/web/src/features/deleteImageModal/store/selectors.ts @@ -1,9 +1,9 @@ import { createSelector } from '@reduxjs/toolkit'; import { RootState } from 'app/store/store'; import { defaultSelectorOptions } from 'app/store/util/defaultMemoizeOptions'; +import { isInvocationNode } from 'features/nodes/types/types'; import { some } from 'lodash-es'; import { ImageUsage } from './types'; -import { isInvocationNode } from 'features/nodes/types/types'; export const getImageUsage = (state: RootState, image_name: string) => { const { generation, canvas, nodes, controlNet } = state; @@ -27,11 +27,15 @@ export const getImageUsage = (state: RootState, image_name: string) => { c.controlImage === image_name || c.processedControlImage === image_name ); + const isIPAdapterImage = + controlNet.ipAdapterInfo.adapterImage?.image_name === image_name; + const imageUsage: ImageUsage = { isInitialImage, isCanvasImage, isNodesImage, isControlNetImage, + isIPAdapterImage, }; return imageUsage; diff --git a/invokeai/frontend/web/src/features/deleteImageModal/store/types.ts b/invokeai/frontend/web/src/features/deleteImageModal/store/types.ts index 2beaa8ca2e..fd13462866 100644 --- a/invokeai/frontend/web/src/features/deleteImageModal/store/types.ts +++ b/invokeai/frontend/web/src/features/deleteImageModal/store/types.ts @@ -10,4 +10,5 @@ export type ImageUsage = { isCanvasImage: boolean; isNodesImage: boolean; isControlNetImage: boolean; + isIPAdapterImage: boolean; }; diff --git a/invokeai/frontend/web/src/features/gallery/components/Boards/DeleteBoardModal.tsx b/invokeai/frontend/web/src/features/gallery/components/Boards/DeleteBoardModal.tsx index 2c54f06cec..f83c08c923 100644 --- a/invokeai/frontend/web/src/features/gallery/components/Boards/DeleteBoardModal.tsx +++ b/invokeai/frontend/web/src/features/gallery/components/Boards/DeleteBoardModal.tsx @@ -53,6 +53,7 @@ const DeleteBoardModal = (props: Props) => { isCanvasImage: some(allImageUsage, (i) => i.isCanvasImage), isNodesImage: some(allImageUsage, (i) => i.isNodesImage), isControlNetImage: some(allImageUsage, (i) => i.isControlNetImage), + isIPAdapterImage: some(allImageUsage, (i) => i.isIPAdapterImage), }; return { imageUsageSummary }; }),