From 48e2e7e4a11e33ef58d0d80529646fdde46127fe Mon Sep 17 00:00:00 2001
From: psychedelicious <4822129+psychedelicious@users.noreply.github.com>
Date: Fri, 13 Jun 2025 13:08:03 +1000
Subject: [PATCH] refactor(ui): ref images (WIP)
---
.../components/CanvasAddEntityButtons.tsx | 4 +-
...nvasContextMenuSelectedEntityMenuItems.tsx | 2 +-
.../EntityListGlobalActionBarAddLayerMenu.tsx | 4 +-
.../components/IPAdapter/IPAdapter.tsx | 29 ---
.../{IPAdapter => RefImage}/IPAdapterList.tsx | 4 +-
.../IPAdapterMenuItemPullBbox.tsx | 0
.../IPAdapterMenuItems.tsx | 2 +-
.../IPAdapterMethod.tsx | 0
.../IPAdapterSettings.tsx | 52 +++---
.../RefImage.tsx} | 11 +-
.../RefImageImage.tsx} | 4 +-
.../RefImageModel.tsx} | 4 +-
.../RefImageNoImageState.tsx} | 4 +-
...onalGuidanceAddPromptsIPAdapterButtons.tsx | 12 +-
.../RegionalGuidanceIPAdapterSettings.tsx | 10 +-
...uidanceMenuItemsAddPromptsAndIPAdapter.tsx | 12 +-
.../components/common/CanvasEntityHeader.tsx | 2 +-
...Model.tsx => IPAdapterCLIPVisionModel.tsx} | 4 +-
.../controlLayers/hooks/addLayerHooks.ts | 171 +++++++++---------
.../controlLayers/hooks/saveCanvasHooks.ts | 23 +--
invokeai/frontend/web/src/features/dnd/dnd.ts | 6 +-
.../ImageMenuItemNewLayerFromImageSubMenu.tsx | 22 ---
.../ImageMenuItemUseAsRefImage.tsx | 39 ++++
.../SingleSelectionMenuItems.tsx | 2 +
.../web/src/features/imageActions/actions.ts | 19 +-
.../components/Core/ParamPositivePrompt.tsx | 2 +-
.../src/services/api/hooks/modelsByType.ts | 20 +-
.../services/events/onInvocationComplete.tsx | 4 +-
.../services/events/onModelInstallError.tsx | 4 +-
29 files changed, 235 insertions(+), 237 deletions(-)
delete mode 100644 invokeai/frontend/web/src/features/controlLayers/components/IPAdapter/IPAdapter.tsx
rename invokeai/frontend/web/src/features/controlLayers/components/{IPAdapter => RefImage}/IPAdapterList.tsx (88%)
rename invokeai/frontend/web/src/features/controlLayers/components/{IPAdapter => RefImage}/IPAdapterMenuItemPullBbox.tsx (100%)
rename invokeai/frontend/web/src/features/controlLayers/components/{IPAdapter => RefImage}/IPAdapterMenuItems.tsx (94%)
rename invokeai/frontend/web/src/features/controlLayers/components/{IPAdapter => RefImage}/IPAdapterMethod.tsx (100%)
rename invokeai/frontend/web/src/features/controlLayers/components/{IPAdapter => RefImage}/IPAdapterSettings.tsx (77%)
rename invokeai/frontend/web/src/features/controlLayers/components/{IPAdapter/IPAdapterPreview.tsx => RefImage/RefImage.tsx} (89%)
rename invokeai/frontend/web/src/features/controlLayers/components/{IPAdapter/IPAdapterImagePreview.tsx => RefImage/RefImageImage.tsx} (96%)
rename invokeai/frontend/web/src/features/controlLayers/components/{IPAdapter/GlobalReferenceImageModel.tsx => RefImage/RefImageModel.tsx} (93%)
rename invokeai/frontend/web/src/features/controlLayers/components/{IPAdapter/IPAdapterSettingsEmptyState.tsx => RefImage/RefImageNoImageState.tsx} (95%)
rename invokeai/frontend/web/src/features/controlLayers/components/common/{CLIPVisionModel.tsx => IPAdapterCLIPVisionModel.tsx} (92%)
create mode 100644 invokeai/frontend/web/src/features/gallery/components/ImageContextMenu/ImageMenuItemUseAsRefImage.tsx
diff --git a/invokeai/frontend/web/src/features/controlLayers/components/CanvasAddEntityButtons.tsx b/invokeai/frontend/web/src/features/controlLayers/components/CanvasAddEntityButtons.tsx
index 6027ed14d2..d3475a385c 100644
--- a/invokeai/frontend/web/src/features/controlLayers/components/CanvasAddEntityButtons.tsx
+++ b/invokeai/frontend/web/src/features/controlLayers/components/CanvasAddEntityButtons.tsx
@@ -5,7 +5,7 @@ import {
useAddInpaintMask,
useAddRasterLayer,
useAddRegionalGuidance,
- useAddRegionalReferenceImage,
+ useAddNewRegionalGuidanceWithARefImage,
} from 'features/controlLayers/hooks/addLayerHooks';
import { useIsEntityTypeEnabled } from 'features/controlLayers/hooks/useIsEntityTypeEnabled';
import { memo } from 'react';
@@ -18,7 +18,7 @@ export const CanvasAddEntityButtons = memo(() => {
const addRegionalGuidance = useAddRegionalGuidance();
const addRasterLayer = useAddRasterLayer();
const addControlLayer = useAddControlLayer();
- const addRegionalReferenceImage = useAddRegionalReferenceImage();
+ const addRegionalReferenceImage = useAddNewRegionalGuidanceWithARefImage();
const isRegionalGuidanceEnabled = useIsEntityTypeEnabled('regional_guidance');
const isControlLayerEnabled = useIsEntityTypeEnabled('control_layer');
const isInpaintLayerEnabled = useIsEntityTypeEnabled('inpaint_mask');
diff --git a/invokeai/frontend/web/src/features/controlLayers/components/CanvasContextMenu/CanvasContextMenuSelectedEntityMenuItems.tsx b/invokeai/frontend/web/src/features/controlLayers/components/CanvasContextMenu/CanvasContextMenuSelectedEntityMenuItems.tsx
index 015e1d9238..8d150e0bb7 100644
--- a/invokeai/frontend/web/src/features/controlLayers/components/CanvasContextMenu/CanvasContextMenuSelectedEntityMenuItems.tsx
+++ b/invokeai/frontend/web/src/features/controlLayers/components/CanvasContextMenu/CanvasContextMenuSelectedEntityMenuItems.tsx
@@ -2,7 +2,7 @@ import { MenuGroup } from '@invoke-ai/ui-library';
import { useAppSelector } from 'app/store/storeHooks';
import { ControlLayerMenuItems } from 'features/controlLayers/components/ControlLayer/ControlLayerMenuItems';
import { InpaintMaskMenuItems } from 'features/controlLayers/components/InpaintMask/InpaintMaskMenuItems';
-import { IPAdapterMenuItems } from 'features/controlLayers/components/IPAdapter/IPAdapterMenuItems';
+import { IPAdapterMenuItems } from 'features/controlLayers/components/RefImage/IPAdapterMenuItems';
import { RasterLayerMenuItems } from 'features/controlLayers/components/RasterLayer/RasterLayerMenuItems';
import { RegionalGuidanceMenuItems } from 'features/controlLayers/components/RegionalGuidance/RegionalGuidanceMenuItems';
import { CanvasEntityStateGate } from 'features/controlLayers/contexts/CanvasEntityStateGate';
diff --git a/invokeai/frontend/web/src/features/controlLayers/components/CanvasEntityList/EntityListGlobalActionBarAddLayerMenu.tsx b/invokeai/frontend/web/src/features/controlLayers/components/CanvasEntityList/EntityListGlobalActionBarAddLayerMenu.tsx
index 9ffdc58bf6..04994f200e 100644
--- a/invokeai/frontend/web/src/features/controlLayers/components/CanvasEntityList/EntityListGlobalActionBarAddLayerMenu.tsx
+++ b/invokeai/frontend/web/src/features/controlLayers/components/CanvasEntityList/EntityListGlobalActionBarAddLayerMenu.tsx
@@ -4,7 +4,7 @@ import {
useAddInpaintMask,
useAddRasterLayer,
useAddRegionalGuidance,
- useAddRegionalReferenceImage,
+ useAddNewRegionalGuidanceWithARefImage,
} from 'features/controlLayers/hooks/addLayerHooks';
import { useCanvasIsBusy } from 'features/controlLayers/hooks/useCanvasIsBusy';
import { useIsEntityTypeEnabled } from 'features/controlLayers/hooks/useIsEntityTypeEnabled';
@@ -17,7 +17,7 @@ export const EntityListGlobalActionBarAddLayerMenu = memo(() => {
const isBusy = useCanvasIsBusy();
const addInpaintMask = useAddInpaintMask();
const addRegionalGuidance = useAddRegionalGuidance();
- const addRegionalReferenceImage = useAddRegionalReferenceImage();
+ const addRegionalReferenceImage = useAddNewRegionalGuidanceWithARefImage();
const addRasterLayer = useAddRasterLayer();
const addControlLayer = useAddControlLayer();
const isRegionalGuidanceEnabled = useIsEntityTypeEnabled('regional_guidance');
diff --git a/invokeai/frontend/web/src/features/controlLayers/components/IPAdapter/IPAdapter.tsx b/invokeai/frontend/web/src/features/controlLayers/components/IPAdapter/IPAdapter.tsx
deleted file mode 100644
index a31841a716..0000000000
--- a/invokeai/frontend/web/src/features/controlLayers/components/IPAdapter/IPAdapter.tsx
+++ /dev/null
@@ -1,29 +0,0 @@
-import { Spacer } from '@invoke-ai/ui-library';
-import { CanvasEntityContainer } from 'features/controlLayers/components/CanvasEntityList/CanvasEntityContainer';
-import { CanvasEntityHeader } from 'features/controlLayers/components/common/CanvasEntityHeader';
-import { CanvasEntityHeaderCommonActions } from 'features/controlLayers/components/common/CanvasEntityHeaderCommonActions';
-import { CanvasEntityEditableTitle } from 'features/controlLayers/components/common/CanvasEntityTitleEdit';
-import { IPAdapterSettings } from 'features/controlLayers/components/IPAdapter/IPAdapterSettings';
-import { RefImageIdContext } from 'features/controlLayers/contexts/RefImageIdContext';
-import { memo } from 'react';
-
-type Props = {
- id: string;
-};
-
-export const IPAdapter = memo(({ id }: Props) => {
- return (
-
-
-
-
-
-
-
-
-
-
- );
-});
-
-IPAdapter.displayName = 'IPAdapter';
diff --git a/invokeai/frontend/web/src/features/controlLayers/components/IPAdapter/IPAdapterList.tsx b/invokeai/frontend/web/src/features/controlLayers/components/RefImage/IPAdapterList.tsx
similarity index 88%
rename from invokeai/frontend/web/src/features/controlLayers/components/IPAdapter/IPAdapterList.tsx
rename to invokeai/frontend/web/src/features/controlLayers/components/RefImage/IPAdapterList.tsx
index c803bae13c..6cda069930 100644
--- a/invokeai/frontend/web/src/features/controlLayers/components/IPAdapter/IPAdapterList.tsx
+++ b/invokeai/frontend/web/src/features/controlLayers/components/RefImage/IPAdapterList.tsx
@@ -2,7 +2,7 @@
import type { FlexProps, SystemStyleObject } from '@invoke-ai/ui-library';
import { Flex } from '@invoke-ai/ui-library';
import { useAppSelector } from 'app/store/storeHooks';
-import { RefImagePreview } from 'features/controlLayers/components/IPAdapter/IPAdapterPreview';
+import { RefImage } from 'features/controlLayers/components/RefImage/RefImage';
import { RefImageIdContext } from 'features/controlLayers/contexts/RefImageIdContext';
import { selectRefImageEntityIds } from 'features/controlLayers/store/refImagesSlice';
import { memo } from 'react';
@@ -27,7 +27,7 @@ export const RefImageList = memo((props: FlexProps) => {
{ids.map((id) => (
-
+
))}
diff --git a/invokeai/frontend/web/src/features/controlLayers/components/IPAdapter/IPAdapterMenuItemPullBbox.tsx b/invokeai/frontend/web/src/features/controlLayers/components/RefImage/IPAdapterMenuItemPullBbox.tsx
similarity index 100%
rename from invokeai/frontend/web/src/features/controlLayers/components/IPAdapter/IPAdapterMenuItemPullBbox.tsx
rename to invokeai/frontend/web/src/features/controlLayers/components/RefImage/IPAdapterMenuItemPullBbox.tsx
diff --git a/invokeai/frontend/web/src/features/controlLayers/components/IPAdapter/IPAdapterMenuItems.tsx b/invokeai/frontend/web/src/features/controlLayers/components/RefImage/IPAdapterMenuItems.tsx
similarity index 94%
rename from invokeai/frontend/web/src/features/controlLayers/components/IPAdapter/IPAdapterMenuItems.tsx
rename to invokeai/frontend/web/src/features/controlLayers/components/RefImage/IPAdapterMenuItems.tsx
index f174bdf004..94d30ad5d2 100644
--- a/invokeai/frontend/web/src/features/controlLayers/components/IPAdapter/IPAdapterMenuItems.tsx
+++ b/invokeai/frontend/web/src/features/controlLayers/components/RefImage/IPAdapterMenuItems.tsx
@@ -3,7 +3,7 @@ import { IconMenuItemGroup } from 'common/components/IconMenuItem';
import { CanvasEntityMenuItemsArrange } from 'features/controlLayers/components/common/CanvasEntityMenuItemsArrange';
import { CanvasEntityMenuItemsDelete } from 'features/controlLayers/components/common/CanvasEntityMenuItemsDelete';
import { CanvasEntityMenuItemsDuplicate } from 'features/controlLayers/components/common/CanvasEntityMenuItemsDuplicate';
-import { IPAdapterMenuItemPullBbox } from 'features/controlLayers/components/IPAdapter/IPAdapterMenuItemPullBbox';
+import { IPAdapterMenuItemPullBbox } from 'features/controlLayers/components/RefImage/IPAdapterMenuItemPullBbox';
import { memo } from 'react';
export const IPAdapterMenuItems = memo(() => {
diff --git a/invokeai/frontend/web/src/features/controlLayers/components/IPAdapter/IPAdapterMethod.tsx b/invokeai/frontend/web/src/features/controlLayers/components/RefImage/IPAdapterMethod.tsx
similarity index 100%
rename from invokeai/frontend/web/src/features/controlLayers/components/IPAdapter/IPAdapterMethod.tsx
rename to invokeai/frontend/web/src/features/controlLayers/components/RefImage/IPAdapterMethod.tsx
diff --git a/invokeai/frontend/web/src/features/controlLayers/components/IPAdapter/IPAdapterSettings.tsx b/invokeai/frontend/web/src/features/controlLayers/components/RefImage/IPAdapterSettings.tsx
similarity index 77%
rename from invokeai/frontend/web/src/features/controlLayers/components/IPAdapter/IPAdapterSettings.tsx
rename to invokeai/frontend/web/src/features/controlLayers/components/RefImage/IPAdapterSettings.tsx
index 1e884209b4..8afacb0d2d 100644
--- a/invokeai/frontend/web/src/features/controlLayers/components/IPAdapter/IPAdapterSettings.tsx
+++ b/invokeai/frontend/web/src/features/controlLayers/components/RefImage/IPAdapterSettings.tsx
@@ -2,12 +2,12 @@ import { Flex } from '@invoke-ai/ui-library';
import { createSelector } from '@reduxjs/toolkit';
import { useAppDispatch, useAppSelector } from 'app/store/storeHooks';
import { BeginEndStepPct } from 'features/controlLayers/components/common/BeginEndStepPct';
-import { CLIPVisionModel } from 'features/controlLayers/components/common/CLIPVisionModel';
import { FLUXReduxImageInfluence } from 'features/controlLayers/components/common/FLUXReduxImageInfluence';
+import { IPAdapterCLIPVisionModel } from 'features/controlLayers/components/common/IPAdapterCLIPVisionModel';
import { Weight } from 'features/controlLayers/components/common/Weight';
-import { GlobalReferenceImageModel } from 'features/controlLayers/components/IPAdapter/GlobalReferenceImageModel';
-import { IPAdapterMethod } from 'features/controlLayers/components/IPAdapter/IPAdapterMethod';
-import { IPAdapterSettingsEmptyState } from 'features/controlLayers/components/IPAdapter/IPAdapterSettingsEmptyState';
+import { IPAdapterMethod } from 'features/controlLayers/components/RefImage/IPAdapterMethod';
+import { RefImageModel } from 'features/controlLayers/components/RefImage/RefImageModel';
+import { RefImageNoImageState } from 'features/controlLayers/components/RefImage/RefImageNoImageState';
import { useRefImageIdContext } from 'features/controlLayers/contexts/RefImageIdContext';
import { selectIsFLUX } from 'features/controlLayers/store/paramsSlice';
import {
@@ -22,10 +22,12 @@ import {
selectRefImageEntityOrThrow,
selectRefImagesSlice,
} from 'features/controlLayers/store/refImagesSlice';
-import type {
- CLIPVisionModelV2,
- FLUXReduxImageInfluence as FLUXReduxImageInfluenceType,
- IPMethodV2,
+import {
+ type CLIPVisionModelV2,
+ type FLUXReduxImageInfluence as FLUXReduxImageInfluenceType,
+ type IPMethodV2,
+ isFLUXReduxConfig,
+ isIPAdapterConfig,
} from 'features/controlLayers/store/types';
import type { SetGlobalReferenceImageDndTargetData } from 'features/dnd/dnd';
import { setGlobalReferenceImageDndTarget } from 'features/dnd/dnd';
@@ -33,7 +35,7 @@ import { memo, useCallback, useMemo } from 'react';
import { useTranslation } from 'react-i18next';
import type { ApiModelConfig, FLUXReduxModelConfig, ImageDTO, IPAdapterModelConfig } from 'services/api/types';
-import { IPAdapterImagePreview } from './IPAdapterImagePreview';
+import { RefImageImage } from './RefImageImage';
const buildSelectConfig = (id: string) =>
createSelector(
@@ -45,8 +47,8 @@ const IPAdapterSettingsContent = memo(() => {
const { t } = useTranslation();
const dispatch = useAppDispatch();
const id = useRefImageIdContext();
- const selectIPAdapter = useMemo(() => buildSelectConfig(id), [id]);
- const ipAdapter = useAppSelector(selectIPAdapter);
+ const selectConfig = useMemo(() => buildSelectConfig(id), [id]);
+ const config = useAppSelector(selectConfig);
const onChangeBeginEndStepPct = useCallback(
(beginEndStepPct: [number, number]) => {
@@ -98,8 +100,8 @@ const IPAdapterSettingsContent = memo(() => {
);
const dndTargetData = useMemo(
- () => setGlobalReferenceImageDndTarget.getData({ id }, ipAdapter.image?.image_name),
- [id, ipAdapter.image?.image_name]
+ () => setGlobalReferenceImageDndTarget.getData({ id }, config.image?.image_name),
+ [id, config.image?.image_name]
);
// const pullBboxIntoIPAdapter = usePullBboxIntoGlobalReferenceImage(id);
// const isBusy = useCanvasIsBusy();
@@ -109,9 +111,9 @@ const IPAdapterSettingsContent = memo(() => {
return (
-
- {ipAdapter.type === 'ip_adapter' && (
-
+
+ {isIPAdapterConfig(config) && (
+
)}
{/* {
/> */}
- {ipAdapter.type === 'ip_adapter' && (
+ {isIPAdapterConfig(config) && (
- {!isFLUX && }
-
-
+ {!isFLUX && }
+
+
)}
- {ipAdapter.type === 'flux_redux' && (
+ {isFLUXReduxConfig(config) && (
)}
- {
const hasImage = useAppSelector(selectIPAdapterHasImage);
if (!hasImage) {
- return ;
+ return ;
}
return ;
diff --git a/invokeai/frontend/web/src/features/controlLayers/components/IPAdapter/IPAdapterPreview.tsx b/invokeai/frontend/web/src/features/controlLayers/components/RefImage/RefImage.tsx
similarity index 89%
rename from invokeai/frontend/web/src/features/controlLayers/components/IPAdapter/IPAdapterPreview.tsx
rename to invokeai/frontend/web/src/features/controlLayers/components/RefImage/RefImage.tsx
index 01b85fb5f1..1212fbe3fa 100644
--- a/invokeai/frontend/web/src/features/controlLayers/components/IPAdapter/IPAdapterPreview.tsx
+++ b/invokeai/frontend/web/src/features/controlLayers/components/RefImage/RefImage.tsx
@@ -14,7 +14,7 @@ import { skipToken } from '@reduxjs/toolkit/query';
import { useAppSelector } from 'app/store/storeHooks';
import { useDisclosure } from 'common/hooks/useBoolean';
import { useFilterableOutsideClick } from 'common/hooks/useFilterableOutsideClick';
-import { IPAdapterSettings } from 'features/controlLayers/components/IPAdapter/IPAdapterSettings';
+import { IPAdapterSettings } from 'features/controlLayers/components/RefImage/IPAdapterSettings';
import { useRefImageIdContext } from 'features/controlLayers/contexts/RefImageIdContext';
import { selectRefImageEntityOrThrow, selectRefImagesSlice } from 'features/controlLayers/store/refImagesSlice';
import type { ImageWithDims } from 'features/controlLayers/store/types';
@@ -34,15 +34,12 @@ const sx: SystemStyleObject = {
transitionDuration: '0.2s',
};
-export const RefImagePreview = memo(() => {
+export const RefImage = memo(() => {
const id = useRefImageIdContext();
const ref = useRef(null);
const disclosure = useDisclosure(false);
const selectEntity = useMemo(
- () =>
- createSelector(selectRefImagesSlice, (refImages) =>
- selectRefImageEntityOrThrow(refImages, id, 'RefImagePreview')
- ),
+ () => createSelector(selectRefImagesSlice, (refImages) => selectRefImageEntityOrThrow(refImages, id, 'RefImage')),
[id]
);
const entity = useAppSelector(selectEntity);
@@ -66,7 +63,7 @@ export const RefImagePreview = memo(() => {
);
});
-RefImagePreview.displayName = 'RefImagePreview';
+RefImage.displayName = 'RefImage';
const Thumbnail = memo(({ image }: { image: ImageWithDims | null }) => {
const { data: imageDTO } = useGetImageDTOQuery(image?.image_name ?? skipToken);
diff --git a/invokeai/frontend/web/src/features/controlLayers/components/IPAdapter/IPAdapterImagePreview.tsx b/invokeai/frontend/web/src/features/controlLayers/components/RefImage/RefImageImage.tsx
similarity index 96%
rename from invokeai/frontend/web/src/features/controlLayers/components/IPAdapter/IPAdapterImagePreview.tsx
rename to invokeai/frontend/web/src/features/controlLayers/components/RefImage/RefImageImage.tsx
index 57d1a3f3f4..979b40f5e1 100644
--- a/invokeai/frontend/web/src/features/controlLayers/components/IPAdapter/IPAdapterImagePreview.tsx
+++ b/invokeai/frontend/web/src/features/controlLayers/components/RefImage/RefImageImage.tsx
@@ -21,7 +21,7 @@ type Props;
};
-export const IPAdapterImagePreview = memo(
+export const RefImageImage = memo(
({
image,
onChangeImage,
@@ -77,4 +77,4 @@ export const IPAdapterImagePreview = memo(
}
);
-IPAdapterImagePreview.displayName = 'IPAdapterImagePreview';
+RefImageImage.displayName = 'RefImageImage';
diff --git a/invokeai/frontend/web/src/features/controlLayers/components/IPAdapter/GlobalReferenceImageModel.tsx b/invokeai/frontend/web/src/features/controlLayers/components/RefImage/RefImageModel.tsx
similarity index 93%
rename from invokeai/frontend/web/src/features/controlLayers/components/IPAdapter/GlobalReferenceImageModel.tsx
rename to invokeai/frontend/web/src/features/controlLayers/components/RefImage/RefImageModel.tsx
index a0b6b4ad4e..8e6e0749aa 100644
--- a/invokeai/frontend/web/src/features/controlLayers/components/IPAdapter/GlobalReferenceImageModel.tsx
+++ b/invokeai/frontend/web/src/features/controlLayers/components/RefImage/RefImageModel.tsx
@@ -12,7 +12,7 @@ type Props = {
onChangeModel: (modelConfig: IPAdapterModelConfig | FLUXReduxModelConfig | ApiModelConfig) => void;
};
-export const GlobalReferenceImageModel = memo(({ modelKey, onChangeModel }: Props) => {
+export const RefImageModel = memo(({ modelKey, onChangeModel }: Props) => {
const { t } = useTranslation();
const currentBaseModel = useAppSelector(selectBase);
const [modelConfigs, { isLoading }] = useGlobalReferenceImageModels();
@@ -60,4 +60,4 @@ export const GlobalReferenceImageModel = memo(({ modelKey, onChangeModel }: Prop
);
});
-GlobalReferenceImageModel.displayName = 'GlobalReferenceImageModel';
+RefImageModel.displayName = 'RefImageModel';
diff --git a/invokeai/frontend/web/src/features/controlLayers/components/IPAdapter/IPAdapterSettingsEmptyState.tsx b/invokeai/frontend/web/src/features/controlLayers/components/RefImage/RefImageNoImageState.tsx
similarity index 95%
rename from invokeai/frontend/web/src/features/controlLayers/components/IPAdapter/IPAdapterSettingsEmptyState.tsx
rename to invokeai/frontend/web/src/features/controlLayers/components/RefImage/RefImageNoImageState.tsx
index 3bf6744d99..3a82fd16f9 100644
--- a/invokeai/frontend/web/src/features/controlLayers/components/IPAdapter/IPAdapterSettingsEmptyState.tsx
+++ b/invokeai/frontend/web/src/features/controlLayers/components/RefImage/RefImageNoImageState.tsx
@@ -13,7 +13,7 @@ import { memo, useCallback, useMemo } from 'react';
import { Trans, useTranslation } from 'react-i18next';
import type { ImageDTO } from 'services/api/types';
-export const IPAdapterSettingsEmptyState = memo(() => {
+export const RefImageNoImageState = memo(() => {
const { t } = useTranslation();
const id = useRefImageIdContext();
const dispatch = useAppDispatch();
@@ -66,4 +66,4 @@ export const IPAdapterSettingsEmptyState = memo(() => {
);
});
-IPAdapterSettingsEmptyState.displayName = 'IPAdapterSettingsEmptyState';
+RefImageNoImageState.displayName = 'RefImageNoImageState';
diff --git a/invokeai/frontend/web/src/features/controlLayers/components/RegionalGuidance/RegionalGuidanceAddPromptsIPAdapterButtons.tsx b/invokeai/frontend/web/src/features/controlLayers/components/RegionalGuidance/RegionalGuidanceAddPromptsIPAdapterButtons.tsx
index 059135b507..cb88c281f7 100644
--- a/invokeai/frontend/web/src/features/controlLayers/components/RegionalGuidance/RegionalGuidanceAddPromptsIPAdapterButtons.tsx
+++ b/invokeai/frontend/web/src/features/controlLayers/components/RegionalGuidance/RegionalGuidanceAddPromptsIPAdapterButtons.tsx
@@ -3,9 +3,9 @@ import { useAppSelector } from 'app/store/storeHooks';
import { useEntityIdentifierContext } from 'features/controlLayers/contexts/EntityIdentifierContext';
import {
buildSelectValidRegionalGuidanceActions,
- useAddRegionalGuidanceIPAdapter,
- useAddRegionalGuidanceNegativePrompt,
- useAddRegionalGuidancePositivePrompt,
+ useAddRefImageToExistingRegionalGuidance,
+ useAddNegativePromptToExistingRegionalGuidance,
+ useAddPositivePromptToExistingRegionalGuidance,
} from 'features/controlLayers/hooks/addLayerHooks';
import { useMemo } from 'react';
import { useTranslation } from 'react-i18next';
@@ -14,9 +14,9 @@ import { PiPlusBold } from 'react-icons/pi';
export const RegionalGuidanceAddPromptsIPAdapterButtons = () => {
const entityIdentifier = useEntityIdentifierContext('regional_guidance');
const { t } = useTranslation();
- const addRegionalGuidanceIPAdapter = useAddRegionalGuidanceIPAdapter(entityIdentifier);
- const addRegionalGuidancePositivePrompt = useAddRegionalGuidancePositivePrompt(entityIdentifier);
- const addRegionalGuidanceNegativePrompt = useAddRegionalGuidanceNegativePrompt(entityIdentifier);
+ const addRegionalGuidanceIPAdapter = useAddRefImageToExistingRegionalGuidance(entityIdentifier);
+ const addRegionalGuidancePositivePrompt = useAddPositivePromptToExistingRegionalGuidance(entityIdentifier);
+ const addRegionalGuidanceNegativePrompt = useAddNegativePromptToExistingRegionalGuidance(entityIdentifier);
const selectValidActions = useMemo(
() => buildSelectValidRegionalGuidanceActions(entityIdentifier),
diff --git a/invokeai/frontend/web/src/features/controlLayers/components/RegionalGuidance/RegionalGuidanceIPAdapterSettings.tsx b/invokeai/frontend/web/src/features/controlLayers/components/RegionalGuidance/RegionalGuidanceIPAdapterSettings.tsx
index 4cc434c439..70445b484b 100644
--- a/invokeai/frontend/web/src/features/controlLayers/components/RegionalGuidance/RegionalGuidanceIPAdapterSettings.tsx
+++ b/invokeai/frontend/web/src/features/controlLayers/components/RegionalGuidance/RegionalGuidanceIPAdapterSettings.tsx
@@ -2,11 +2,11 @@ import { Flex, IconButton, Spacer, Text } from '@invoke-ai/ui-library';
import { createSelector } from '@reduxjs/toolkit';
import { useAppDispatch, useAppSelector } from 'app/store/storeHooks';
import { BeginEndStepPct } from 'features/controlLayers/components/common/BeginEndStepPct';
-import { CLIPVisionModel } from 'features/controlLayers/components/common/CLIPVisionModel';
+import { IPAdapterCLIPVisionModel } from 'features/controlLayers/components/common/IPAdapterCLIPVisionModel';
import { FLUXReduxImageInfluence } from 'features/controlLayers/components/common/FLUXReduxImageInfluence';
import { Weight } from 'features/controlLayers/components/common/Weight';
-import { IPAdapterImagePreview } from 'features/controlLayers/components/IPAdapter/IPAdapterImagePreview';
-import { IPAdapterMethod } from 'features/controlLayers/components/IPAdapter/IPAdapterMethod';
+import { RefImageImage } from 'features/controlLayers/components/RefImage/RefImageImage';
+import { IPAdapterMethod } from 'features/controlLayers/components/RefImage/IPAdapterMethod';
import { RegionalGuidanceIPAdapterSettingsEmptyState } from 'features/controlLayers/components/RegionalGuidance/RegionalGuidanceIPAdapterSettingsEmptyState';
import { RegionalReferenceImageModel } from 'features/controlLayers/components/RegionalGuidance/RegionalReferenceImageModel';
import { useEntityIdentifierContext } from 'features/controlLayers/contexts/EntityIdentifierContext';
@@ -142,7 +142,7 @@ const RegionalGuidanceIPAdapterSettingsContent = memo(({ referenceImageId }: Pro
{config.type === 'ip_adapter' && (
-
+
)}
)}
- {
const entityIdentifier = useEntityIdentifierContext('regional_guidance');
const { t } = useTranslation();
const isBusy = useCanvasIsBusy();
- const addRegionalGuidanceIPAdapter = useAddRegionalGuidanceIPAdapter(entityIdentifier);
- const addRegionalGuidancePositivePrompt = useAddRegionalGuidancePositivePrompt(entityIdentifier);
- const addRegionalGuidanceNegativePrompt = useAddRegionalGuidanceNegativePrompt(entityIdentifier);
+ const addRegionalGuidanceIPAdapter = useAddRefImageToExistingRegionalGuidance(entityIdentifier);
+ const addRegionalGuidancePositivePrompt = useAddPositivePromptToExistingRegionalGuidance(entityIdentifier);
+ const addRegionalGuidanceNegativePrompt = useAddNegativePromptToExistingRegionalGuidance(entityIdentifier);
const selectValidActions = useMemo(
() => buildSelectValidRegionalGuidanceActions(entityIdentifier),
[entityIdentifier]
diff --git a/invokeai/frontend/web/src/features/controlLayers/components/common/CanvasEntityHeader.tsx b/invokeai/frontend/web/src/features/controlLayers/components/common/CanvasEntityHeader.tsx
index 89a924ad6c..8c8e07cdc9 100644
--- a/invokeai/frontend/web/src/features/controlLayers/components/common/CanvasEntityHeader.tsx
+++ b/invokeai/frontend/web/src/features/controlLayers/components/common/CanvasEntityHeader.tsx
@@ -2,7 +2,7 @@ import type { FlexProps } from '@invoke-ai/ui-library';
import { ContextMenu, Flex, MenuList } from '@invoke-ai/ui-library';
import { ControlLayerMenuItems } from 'features/controlLayers/components/ControlLayer/ControlLayerMenuItems';
import { InpaintMaskMenuItems } from 'features/controlLayers/components/InpaintMask/InpaintMaskMenuItems';
-import { IPAdapterMenuItems } from 'features/controlLayers/components/IPAdapter/IPAdapterMenuItems';
+import { IPAdapterMenuItems } from 'features/controlLayers/components/RefImage/IPAdapterMenuItems';
import { RasterLayerMenuItems } from 'features/controlLayers/components/RasterLayer/RasterLayerMenuItems';
import { RegionalGuidanceMenuItems } from 'features/controlLayers/components/RegionalGuidance/RegionalGuidanceMenuItems';
import { useEntityIdentifierContext } from 'features/controlLayers/contexts/EntityIdentifierContext';
diff --git a/invokeai/frontend/web/src/features/controlLayers/components/common/CLIPVisionModel.tsx b/invokeai/frontend/web/src/features/controlLayers/components/common/IPAdapterCLIPVisionModel.tsx
similarity index 92%
rename from invokeai/frontend/web/src/features/controlLayers/components/common/CLIPVisionModel.tsx
rename to invokeai/frontend/web/src/features/controlLayers/components/common/IPAdapterCLIPVisionModel.tsx
index 6023fd579f..dd7917d39a 100644
--- a/invokeai/frontend/web/src/features/controlLayers/components/common/CLIPVisionModel.tsx
+++ b/invokeai/frontend/web/src/features/controlLayers/components/common/IPAdapterCLIPVisionModel.tsx
@@ -22,7 +22,7 @@ type Props = {
onChange: (clipVisionModel: CLIPVisionModelV2) => void;
};
-export const CLIPVisionModel = memo(({ model, onChange }: Props) => {
+export const IPAdapterCLIPVisionModel = memo(({ model, onChange }: Props) => {
const { t } = useTranslation();
const _onChangeCLIPVisionModel = useCallback(
@@ -58,4 +58,4 @@ export const CLIPVisionModel = memo(({ model, onChange }: Props) => {
);
});
-CLIPVisionModel.displayName = 'CLIPVisionModel';
+IPAdapterCLIPVisionModel.displayName = 'IPAdapterCLIPVisionModel';
diff --git a/invokeai/frontend/web/src/features/controlLayers/hooks/addLayerHooks.ts b/invokeai/frontend/web/src/features/controlLayers/hooks/addLayerHooks.ts
index 41d871436e..f8860e210c 100644
--- a/invokeai/frontend/web/src/features/controlLayers/hooks/addLayerHooks.ts
+++ b/invokeai/frontend/web/src/features/controlLayers/hooks/addLayerHooks.ts
@@ -1,6 +1,8 @@
import { createSelector } from '@reduxjs/toolkit';
import { createMemoizedSelector } from 'app/store/createMemoizedSelector';
-import { useAppDispatch, useAppSelector } from 'app/store/storeHooks';
+import { useAppStore } from 'app/store/nanostores/store';
+import type { AppGetState } from 'app/store/store';
+import { useAppDispatch } from 'app/store/storeHooks';
import { deepClone } from 'common/util/deepClone';
import { getPrefixedId } from 'features/controlLayers/konva/util';
import {
@@ -20,10 +22,11 @@ import { selectCanvasSlice, selectEntity } from 'features/controlLayers/store/se
import type {
CanvasEntityIdentifier,
CanvasRegionalGuidanceState,
+ ChatGPT4oReferenceImageConfig,
ControlLoRAConfig,
ControlNetConfig,
+ FluxKontextReferenceImageConfig,
IPAdapterConfig,
- RefImageState,
T2IAdapterConfig,
} from 'features/controlLayers/store/types';
import {
@@ -36,13 +39,9 @@ import {
import { zModelIdentifierField } from 'features/nodes/types/common';
import { useCallback } from 'react';
import { modelConfigsAdapterSelectors, selectModelConfigsQuery } from 'services/api/endpoints/models';
-import type {
- ControlLoRAModelConfig,
- ControlNetModelConfig,
- IPAdapterModelConfig,
- T2IAdapterModelConfig,
-} from 'services/api/types';
-import { isControlLayerModelConfig, isIPAdapterModelConfig } from 'services/api/types';
+import { selectIPAdapterModels } from 'services/api/hooks/modelsByType';
+import type { ControlLoRAModelConfig, ControlNetModelConfig, T2IAdapterModelConfig } from 'services/api/types';
+import { isControlLayerModelConfig } from 'services/api/types';
/**
* Selects the default control adapter configuration based on the model configurations and the base.
@@ -73,67 +72,69 @@ export const selectDefaultControlAdapter = createSelector(
}
);
-export const selectDefaultRefImageConfig = createSelector(
- selectMainModelConfig,
- selectModelConfigsQuery,
- selectBase,
- (selectedMainModel, query, base): RefImageState['config'] => {
- if (selectedMainModel?.base === 'chatgpt-4o') {
- const referenceImage = deepClone(initialChatGPT4oReferenceImage);
- referenceImage.model = zModelIdentifierField.parse(selectedMainModel);
- return referenceImage;
- }
+export const getDefaultRefImageConfig = (
+ getState: AppGetState
+): IPAdapterConfig | ChatGPT4oReferenceImageConfig | FluxKontextReferenceImageConfig => {
+ const state = getState();
- if (selectedMainModel?.base === 'flux-kontext') {
- const referenceImage = deepClone(initialFluxKontextReferenceImage);
- referenceImage.model = zModelIdentifierField.parse(selectedMainModel);
- return referenceImage;
- }
+ const mainModelConfig = selectMainModelConfig(state);
+ const ipAdapterModelConfigs = selectIPAdapterModels(state);
- const { data } = query;
- let model: IPAdapterModelConfig | null = null;
- if (data) {
- const modelConfigs = modelConfigsAdapterSelectors.selectAll(data).filter(isIPAdapterModelConfig);
- const compatibleModels = modelConfigs.filter((m) => (base ? m.base === base : true));
- model = compatibleModels[0] ?? modelConfigs[0] ?? null;
- }
- const ipAdapter = deepClone(initialIPAdapter);
- if (model) {
- ipAdapter.model = zModelIdentifierField.parse(model);
- if (model.base === 'flux') {
- ipAdapter.clipVisionModel = 'ViT-L';
- }
- }
- return ipAdapter;
+ const base = mainModelConfig?.base;
+
+ // For ChatGPT-4o, the ref image model is the model itself.
+ if (base === 'chatgpt-4o') {
+ const config = deepClone(initialChatGPT4oReferenceImage);
+ config.model = zModelIdentifierField.parse(mainModelConfig);
+ return config;
}
-);
-/**
- * Selects the default IP adapter configuration based on the model configurations and the base.
- *
- * Be sure to clone the output of this selector before modifying it!
- */
-export const selectDefaultIPAdapter = createSelector(
- selectModelConfigsQuery,
- selectBase,
- (query, base): IPAdapterConfig => {
- const { data } = query;
- let model: IPAdapterModelConfig | null = null;
- if (data) {
- const modelConfigs = modelConfigsAdapterSelectors.selectAll(data).filter(isIPAdapterModelConfig);
- const compatibleModels = modelConfigs.filter((m) => (base ? m.base === base : true));
- model = compatibleModels[0] ?? modelConfigs[0] ?? null;
- }
- const ipAdapter = deepClone(initialIPAdapter);
- if (model) {
- ipAdapter.model = zModelIdentifierField.parse(model);
- if (model.base === 'flux') {
- ipAdapter.clipVisionModel = 'ViT-L';
- }
- }
- return ipAdapter;
+ if (base === 'flux-kontext') {
+ const config = deepClone(initialFluxKontextReferenceImage);
+ config.model = zModelIdentifierField.parse(mainModelConfig);
+ return config;
}
-);
+
+ // Otherwise, find the first compatible IP Adapter model.
+ const modelConfig = ipAdapterModelConfigs.find((m) => m.base === base);
+
+ // Clone the initial IP Adapter config and set the model if available.
+ const config = deepClone(initialIPAdapter);
+
+ if (modelConfig) {
+ config.model = zModelIdentifierField.parse(modelConfig);
+ // FLUX models use a different vision model.
+ if (modelConfig.base === 'flux') {
+ config.clipVisionModel = 'ViT-L';
+ }
+ }
+ return config;
+};
+
+export const getDefaultRegionalGuidanceRefImageConfig = (getState: AppGetState): IPAdapterConfig => {
+ // Regional guidance ref images do not support ChatGPT-4o, so we always return the IP Adapter config.
+ const state = getState();
+
+ const mainModelConfig = selectMainModelConfig(state);
+ const ipAdapterModelConfigs = selectIPAdapterModels(state);
+
+ const base = mainModelConfig?.base;
+
+ // Find the first compatible IP Adapter model.
+ const modelConfig = ipAdapterModelConfigs.find((m) => m.base === base);
+
+ // Clone the initial IP Adapter config and set the model if available.
+ const config = deepClone(initialIPAdapter);
+
+ if (modelConfig) {
+ config.model = zModelIdentifierField.parse(modelConfig);
+ // FLUX models use a different vision model.
+ if (modelConfig.base === 'flux') {
+ config.clipVisionModel = 'ViT-L';
+ }
+ }
+ return config;
+};
export const useAddControlLayer = () => {
const dispatch = useAppDispatch();
@@ -172,44 +173,46 @@ export const useAddRegionalGuidance = () => {
return func;
};
-export const useAddRegionalReferenceImage = () => {
- const dispatch = useAppDispatch();
- const defaultIPAdapter = useAppSelector(selectDefaultIPAdapter);
+export const useAddNewRegionalGuidanceWithARefImage = () => {
+ const { dispatch, getState } = useAppStore();
const func = useCallback(() => {
+ const config = getDefaultRegionalGuidanceRefImageConfig(getState);
const overrides: Partial = {
- referenceImages: [
- { id: getPrefixedId('regional_guidance_reference_image'), config: deepClone(defaultIPAdapter) },
- ],
+ referenceImages: [{ id: getPrefixedId('regional_guidance_reference_image'), config }],
};
dispatch(rgAdded({ isSelected: true, overrides }));
- }, [defaultIPAdapter, dispatch]);
+ }, [dispatch, getState]);
return func;
};
export const useAddGlobalReferenceImage = () => {
- const dispatch = useAppDispatch();
- const defaultRefImage = useAppSelector(selectDefaultRefImageConfig);
+ const { dispatch, getState } = useAppStore();
const func = useCallback(() => {
- const overrides = { config: deepClone(defaultRefImage) };
+ const config = getDefaultRefImageConfig(getState);
+ const overrides = { config };
dispatch(refImageAdded({ isSelected: true, overrides }));
- }, [defaultRefImage, dispatch]);
+ }, [dispatch, getState]);
return func;
};
-export const useAddRegionalGuidanceIPAdapter = (entityIdentifier: CanvasEntityIdentifier<'regional_guidance'>) => {
- const dispatch = useAppDispatch();
- const defaultIPAdapter = useAppSelector(selectDefaultIPAdapter);
+export const useAddRefImageToExistingRegionalGuidance = (
+ entityIdentifier: CanvasEntityIdentifier<'regional_guidance'>
+) => {
+ const { dispatch, getState } = useAppStore();
const func = useCallback(() => {
- dispatch(rgRefImageAdded({ entityIdentifier, overrides: { config: deepClone(defaultIPAdapter) } }));
- }, [defaultIPAdapter, dispatch, entityIdentifier]);
+ const config = getDefaultRegionalGuidanceRefImageConfig(getState);
+ dispatch(rgRefImageAdded({ entityIdentifier, overrides: { config } }));
+ }, [dispatch, entityIdentifier, getState]);
return func;
};
-export const useAddRegionalGuidancePositivePrompt = (entityIdentifier: CanvasEntityIdentifier<'regional_guidance'>) => {
+export const useAddPositivePromptToExistingRegionalGuidance = (
+ entityIdentifier: CanvasEntityIdentifier<'regional_guidance'>
+) => {
const dispatch = useAppDispatch();
const func = useCallback(() => {
dispatch(rgPositivePromptChanged({ entityIdentifier, prompt: '' }));
@@ -218,7 +221,9 @@ export const useAddRegionalGuidancePositivePrompt = (entityIdentifier: CanvasEnt
return func;
};
-export const useAddRegionalGuidanceNegativePrompt = (entityIdentifier: CanvasEntityIdentifier<'regional_guidance'>) => {
+export const useAddNegativePromptToExistingRegionalGuidance = (
+ entityIdentifier: CanvasEntityIdentifier<'regional_guidance'>
+) => {
const dispatch = useAppDispatch();
const runc = useCallback(() => {
dispatch(rgNegativePromptChanged({ entityIdentifier, prompt: '' }));
diff --git a/invokeai/frontend/web/src/features/controlLayers/hooks/saveCanvasHooks.ts b/invokeai/frontend/web/src/features/controlLayers/hooks/saveCanvasHooks.ts
index 0e0d2d433d..395768585f 100644
--- a/invokeai/frontend/web/src/features/controlLayers/hooks/saveCanvasHooks.ts
+++ b/invokeai/frontend/web/src/features/controlLayers/hooks/saveCanvasHooks.ts
@@ -1,9 +1,12 @@
import { logger } from 'app/logging/logger';
-import { useAppDispatch, useAppSelector, useAppStore } from 'app/store/storeHooks';
+import { useAppDispatch, useAppStore } from 'app/store/storeHooks';
import { deepClone } from 'common/util/deepClone';
import { withResultAsync } from 'common/util/result';
import { useCanvasManager } from 'features/controlLayers/contexts/CanvasManagerProviderGate';
-import { selectDefaultIPAdapter, selectDefaultRefImageConfig } from 'features/controlLayers/hooks/addLayerHooks';
+import {
+ getDefaultRefImageConfig,
+ getDefaultRegionalGuidanceRefImageConfig,
+} from 'features/controlLayers/hooks/addLayerHooks';
import { getPrefixedId } from 'features/controlLayers/konva/util';
import {
controlLayerAdded,
@@ -18,7 +21,7 @@ import {
selectPositivePrompt,
selectSeed,
} from 'features/controlLayers/store/paramsSlice';
-import { refImageAdded,refImageImageChanged } from 'features/controlLayers/store/refImagesSlice';
+import { refImageAdded, refImageImageChanged } from 'features/controlLayers/store/refImagesSlice';
import { selectCanvasMetadata } from 'features/controlLayers/store/selectors';
import type {
CanvasControlLayerState,
@@ -167,15 +170,14 @@ export const useSaveBboxToGallery = () => {
export const useNewRegionalReferenceImageFromBbox = () => {
const { t } = useTranslation();
- const dispatch = useAppDispatch();
- const defaultIPAdapter = useAppSelector(selectDefaultIPAdapter);
+ const { dispatch, getState } = useAppStore();
const arg = useMemo(() => {
const onSave = (imageDTO: ImageDTO) => {
const ipAdapter: RegionalGuidanceRefImageState = {
id: getPrefixedId('regional_guidance_reference_image'),
config: {
- ...deepClone(defaultIPAdapter),
+ ...getDefaultRegionalGuidanceRefImageConfig(getState),
image: imageDTOToImageWithDims(imageDTO),
},
};
@@ -193,21 +195,20 @@ export const useNewRegionalReferenceImageFromBbox = () => {
toastOk: t('controlLayers.newRegionalReferenceImageOk'),
toastError: t('controlLayers.newRegionalReferenceImageError'),
};
- }, [defaultIPAdapter, dispatch, t]);
+ }, [dispatch, getState, t]);
const func = useSaveCanvas(arg);
return func;
};
export const useNewGlobalReferenceImageFromBbox = () => {
const { t } = useTranslation();
- const dispatch = useAppDispatch();
- const defaultIPAdapter = useAppSelector(selectDefaultRefImageConfig);
+ const { dispatch, getState } = useAppStore();
const arg = useMemo(() => {
const onSave = (imageDTO: ImageDTO) => {
const overrides: Partial = {
config: {
- ...deepClone(defaultIPAdapter),
+ ...getDefaultRefImageConfig(getState),
image: imageDTOToImageWithDims(imageDTO),
},
};
@@ -221,7 +222,7 @@ export const useNewGlobalReferenceImageFromBbox = () => {
toastOk: t('controlLayers.newGlobalReferenceImageOk'),
toastError: t('controlLayers.newGlobalReferenceImageError'),
};
- }, [defaultIPAdapter, dispatch, t]);
+ }, [dispatch, getState, t]);
const func = useSaveCanvas(arg);
return func;
};
diff --git a/invokeai/frontend/web/src/features/dnd/dnd.ts b/invokeai/frontend/web/src/features/dnd/dnd.ts
index 925a118694..bb401727d3 100644
--- a/invokeai/frontend/web/src/features/dnd/dnd.ts
+++ b/invokeai/frontend/web/src/features/dnd/dnd.ts
@@ -1,5 +1,5 @@
import { logger } from 'app/logging/logger';
-import type { AppDispatch, RootState } from 'app/store/store';
+import type { AppDispatch, AppGetState } from 'app/store/store';
import { getPrefixedId } from 'features/controlLayers/konva/util';
import type { CanvasEntityIdentifier, CanvasEntityType } from 'features/controlLayers/store/types';
import { selectComparisonImages } from 'features/gallery/components/ImageViewer/common';
@@ -114,13 +114,13 @@ type DndTarget = {
sourceData: RecordUnknown;
targetData: TargetData;
dispatch: AppDispatch;
- getState: () => RootState;
+ getState: AppGetState;
}) => boolean;
handler: (arg: {
sourceData: SourceData;
targetData: TargetData;
dispatch: AppDispatch;
- getState: () => RootState;
+ getState: AppGetState;
}) => void;
};
diff --git a/invokeai/frontend/web/src/features/gallery/components/ImageContextMenu/ImageMenuItemNewLayerFromImageSubMenu.tsx b/invokeai/frontend/web/src/features/gallery/components/ImageContextMenu/ImageMenuItemNewLayerFromImageSubMenu.tsx
index 7b3971ba39..4fc9934f07 100644
--- a/invokeai/frontend/web/src/features/gallery/components/ImageContextMenu/ImageMenuItemNewLayerFromImageSubMenu.tsx
+++ b/invokeai/frontend/web/src/features/gallery/components/ImageContextMenu/ImageMenuItemNewLayerFromImageSubMenu.tsx
@@ -3,8 +3,6 @@ import { useAppStore } from 'app/store/nanostores/store';
import { SubMenuButtonContent, useSubMenu } from 'common/hooks/useSubMenu';
import { NewLayerIcon } from 'features/controlLayers/components/common/icons';
import { useCanvasIsBusySafe } from 'features/controlLayers/hooks/useCanvasIsBusy';
-import { refImageAdded } from 'features/controlLayers/store/refImagesSlice';
-import { imageDTOToImageWithDims } from 'features/controlLayers/store/util';
import { useImageViewer } from 'features/gallery/components/ImageViewer/useImageViewer';
import { useImageDTOContext } from 'features/gallery/contexts/ImageDTOContext';
import { sentImageToCanvas } from 'features/gallery/store/actions';
@@ -75,19 +73,6 @@ export const ImageMenuItemNewLayerFromImageSubMenu = memo(() => {
});
}, [imageDTO, imageViewer, store, t]);
- const onClickNewGlobalReferenceImageFromImage = useCallback(() => {
- const { dispatch } = store;
- dispatch(refImageAdded({ overrides: { config: { image: imageDTOToImageWithDims(imageDTO) } } }));
- dispatch(sentImageToCanvas());
- dispatch(setActiveTab('canvas'));
- imageViewer.close();
- toast({
- id: 'SENT_TO_CANVAS',
- title: t('toast.sentToCanvas'),
- status: 'success',
- });
- }, [imageDTO, imageViewer, store, t]);
-
const onClickNewRegionalReferenceImageFromImage = useCallback(() => {
const { dispatch, getState } = store;
createNewCanvasEntityFromImage({ imageDTO, type: 'regional_guidance_with_reference_image', dispatch, getState });
@@ -127,13 +112,6 @@ export const ImageMenuItemNewLayerFromImageSubMenu = memo(() => {
>
{t('controlLayers.referenceImageRegional')}
- }
- onClickCapture={onClickNewGlobalReferenceImageFromImage}
- isDisabled={isBusy}
- >
- {t('controlLayers.referenceImageGlobal')}
-
diff --git a/invokeai/frontend/web/src/features/gallery/components/ImageContextMenu/ImageMenuItemUseAsRefImage.tsx b/invokeai/frontend/web/src/features/gallery/components/ImageContextMenu/ImageMenuItemUseAsRefImage.tsx
new file mode 100644
index 0000000000..344f81ef7f
--- /dev/null
+++ b/invokeai/frontend/web/src/features/gallery/components/ImageContextMenu/ImageMenuItemUseAsRefImage.tsx
@@ -0,0 +1,39 @@
+import { MenuItem } from '@invoke-ai/ui-library';
+import { useAppStore } from 'app/store/nanostores/store';
+import { getDefaultRefImageConfig } from 'features/controlLayers/hooks/addLayerHooks';
+import { refImageAdded } from 'features/controlLayers/store/refImagesSlice';
+import { imageDTOToImageWithDims } from 'features/controlLayers/store/util';
+import { useImageViewer } from 'features/gallery/components/ImageViewer/useImageViewer';
+import { useImageDTOContext } from 'features/gallery/contexts/ImageDTOContext';
+import { toast } from 'features/toast/toast';
+import { memo, useCallback } from 'react';
+import { useTranslation } from 'react-i18next';
+import { PiImageBold } from 'react-icons/pi';
+
+export const ImageMenuItemUseAsRefImage = memo(() => {
+ const { t } = useTranslation();
+ const store = useAppStore();
+ const imageDTO = useImageDTOContext();
+ const imageViewer = useImageViewer();
+
+ const onClickNewGlobalReferenceImageFromImage = useCallback(() => {
+ const { dispatch, getState } = store;
+ const config = getDefaultRefImageConfig(getState);
+ config.image = imageDTOToImageWithDims(imageDTO);
+ dispatch(refImageAdded({ overrides: { config } }));
+ imageViewer.close();
+ toast({
+ id: 'SENT_TO_CANVAS',
+ title: t('toast.sentToCanvas'),
+ status: 'success',
+ });
+ }, [imageDTO, imageViewer, store, t]);
+
+ return (
+ } onClickCapture={onClickNewGlobalReferenceImageFromImage}>
+ Use as Reference Image
+
+ );
+});
+
+ImageMenuItemUseAsRefImage.displayName = 'ImageMenuItemUseAsRefImage';
diff --git a/invokeai/frontend/web/src/features/gallery/components/ImageContextMenu/SingleSelectionMenuItems.tsx b/invokeai/frontend/web/src/features/gallery/components/ImageContextMenu/SingleSelectionMenuItems.tsx
index bac272d6c1..368d265735 100644
--- a/invokeai/frontend/web/src/features/gallery/components/ImageContextMenu/SingleSelectionMenuItems.tsx
+++ b/invokeai/frontend/web/src/features/gallery/components/ImageContextMenu/SingleSelectionMenuItems.tsx
@@ -13,6 +13,7 @@ import { ImageMenuItemOpenInViewer } from 'features/gallery/components/ImageCont
import { ImageMenuItemSelectForCompare } from 'features/gallery/components/ImageContextMenu/ImageMenuItemSelectForCompare';
import { ImageMenuItemSendToUpscale } from 'features/gallery/components/ImageContextMenu/ImageMenuItemSendToUpscale';
import { ImageMenuItemStarUnstar } from 'features/gallery/components/ImageContextMenu/ImageMenuItemStarUnstar';
+import { ImageMenuItemUseAsRefImage } from 'features/gallery/components/ImageContextMenu/ImageMenuItemUseAsRefImage';
import { ImageDTOContextProvider } from 'features/gallery/contexts/ImageDTOContext';
import { memo } from 'react';
import type { ImageDTO } from 'services/api/types';
@@ -37,6 +38,7 @@ const SingleSelectionMenuItems = ({ imageDTO }: SingleSelectionMenuItemsProps) =
+
diff --git a/invokeai/frontend/web/src/features/imageActions/actions.ts b/invokeai/frontend/web/src/features/imageActions/actions.ts
index babbdd35a2..52db1a7c09 100644
--- a/invokeai/frontend/web/src/features/imageActions/actions.ts
+++ b/invokeai/frontend/web/src/features/imageActions/actions.ts
@@ -1,6 +1,9 @@
-import type { AppDispatch, RootState } from 'app/store/store';
+import type { AppDispatch, AppGetState } from 'app/store/store';
import { deepClone } from 'common/util/deepClone';
-import { selectDefaultIPAdapter, selectDefaultRefImageConfig } from 'features/controlLayers/hooks/addLayerHooks';
+import {
+ getDefaultRefImageConfig,
+ getDefaultRegionalGuidanceRefImageConfig,
+} from 'features/controlLayers/hooks/addLayerHooks';
import { CanvasEntityTransformer } from 'features/controlLayers/konva/CanvasEntity/CanvasEntityTransformer';
import { getPrefixedId } from 'features/controlLayers/konva/util';
import {
@@ -77,7 +80,7 @@ export const createNewCanvasEntityFromImage = (arg: {
imageDTO: ImageDTO;
type: CanvasEntityType | 'regional_guidance_with_reference_image';
dispatch: AppDispatch;
- getState: () => RootState;
+ getState: AppGetState;
overrides?: Partial>;
}) => {
const { type, imageDTO, dispatch, getState, overrides: _overrides } = arg;
@@ -112,7 +115,7 @@ export const createNewCanvasEntityFromImage = (arg: {
break;
}
case 'regional_guidance_with_reference_image': {
- const config = deepClone(selectDefaultIPAdapter(getState()));
+ const config = getDefaultRegionalGuidanceRefImageConfig(getState);
config.image = imageDTOToImageWithDims(imageDTO);
const referenceImages = [{ id: getPrefixedId('regional_guidance_reference_image'), config }];
dispatch(rgAdded({ overrides: { referenceImages }, isSelected: true }));
@@ -138,7 +141,7 @@ export const newCanvasFromImage = async (arg: {
withResize?: boolean;
withInpaintMask?: boolean;
dispatch: AppDispatch;
- getState: () => RootState;
+ getState: AppGetState;
}) => {
const { type, imageDTO, withResize = false, withInpaintMask = false, dispatch, getState } = arg;
const state = getState();
@@ -242,7 +245,7 @@ export const newCanvasFromImage = async (arg: {
break;
}
case 'reference_image': {
- const config = deepClone(selectDefaultRefImageConfig(getState()));
+ const config = deepClone(getDefaultRefImageConfig(getState));
config.image = imageDTOToImageWithDims(imageDTO);
dispatch(canvasSessionTypeChanged({ type: 'advanced' }));
dispatch(refImageAdded({ overrides: { config }, isSelected: true }));
@@ -253,7 +256,7 @@ export const newCanvasFromImage = async (arg: {
break;
}
case 'regional_guidance_with_reference_image': {
- const config = deepClone(selectDefaultIPAdapter(getState()));
+ const config = getDefaultRegionalGuidanceRefImageConfig(getState);
config.image = imageDTOToImageWithDims(imageDTO);
const referenceImages = [{ id: getPrefixedId('regional_guidance_reference_image'), config }];
dispatch(canvasSessionTypeChanged({ type: 'advanced' }));
@@ -273,7 +276,7 @@ export const replaceCanvasEntityObjectsWithImage = (arg: {
imageDTO: ImageDTO;
entityIdentifier: CanvasEntityIdentifier;
dispatch: AppDispatch;
- getState: () => RootState;
+ getState: AppGetState;
}) => {
const { imageDTO, entityIdentifier, dispatch, getState } = arg;
const imageObject = imageDTOToImageObject(imageDTO);
diff --git a/invokeai/frontend/web/src/features/parameters/components/Core/ParamPositivePrompt.tsx b/invokeai/frontend/web/src/features/parameters/components/Core/ParamPositivePrompt.tsx
index f6f71927c8..aa65205470 100644
--- a/invokeai/frontend/web/src/features/parameters/components/Core/ParamPositivePrompt.tsx
+++ b/invokeai/frontend/web/src/features/parameters/components/Core/ParamPositivePrompt.tsx
@@ -1,7 +1,7 @@
import { Box, Textarea } from '@invoke-ai/ui-library';
import { useAppDispatch, useAppSelector } from 'app/store/storeHooks';
import { usePersistedTextAreaSize } from 'common/hooks/usePersistedTextareaSize';
-import { RefImageList } from 'features/controlLayers/components/IPAdapter/IPAdapterList';
+import { RefImageList } from 'features/controlLayers/components/RefImage/IPAdapterList';
import { positivePromptChanged, selectBase, selectPositivePrompt } from 'features/controlLayers/store/paramsSlice';
import { ShowDynamicPromptsPreviewButton } from 'features/dynamicPrompts/components/ShowDynamicPromptsPreviewButton';
import { PromptLabel } from 'features/parameters/components/Prompts/PromptLabel';
diff --git a/invokeai/frontend/web/src/services/api/hooks/modelsByType.ts b/invokeai/frontend/web/src/services/api/hooks/modelsByType.ts
index fc2c782003..553d441005 100644
--- a/invokeai/frontend/web/src/services/api/hooks/modelsByType.ts
+++ b/invokeai/frontend/web/src/services/api/hooks/modelsByType.ts
@@ -101,15 +101,15 @@ export const useImagen4Models = buildModelsHook(isImagen4ModelConfig);
export const useChatGPT4oModels = buildModelsHook(isChatGPT4oModelConfig);
export const useFluxKontextModels = buildModelsHook(isFluxKontextModelConfig);
-// const buildModelsSelector =
-// (typeGuard: (config: AnyModelConfig) => config is T): Selector =>
-// (state) => {
-// const result = selectModelConfigsQuery(state);
-// if (!result.data) {
-// return EMPTY_ARRAY;
-// }
-// return modelConfigsAdapterSelectors.selectAll(result.data).filter(typeGuard);
-// };
+const buildModelsSelector =
+ (typeGuard: (config: AnyModelConfig) => config is T): Selector =>
+ (state) => {
+ const result = selectModelConfigsQuery(state);
+ if (!result.data) {
+ return EMPTY_ARRAY;
+ }
+ return modelConfigsAdapterSelectors.selectAll(result.data).filter(typeGuard);
+ };
// export const selectSDMainModels = buildModelsSelector(isNonRefinerNonFluxMainModelConfig);
// export const selectMainModels = buildModelsSelector(isNonRefinerMainModelConfig);
// export const selectNonSDXLMainModels = buildModelsSelector(isNonSDXLMainModelConfig);
@@ -123,7 +123,7 @@ export const useFluxKontextModels = buildModelsHook(isFluxKontextModelConfig);
// export const selectT5EncoderModels = buildModelsSelector(isT5EncoderModelConfig);
// export const selectClipEmbedModels = buildModelsSelector(isClipEmbedModelConfig);
// export const selectSpandrelImageToImageModels = buildModelsSelector(isSpandrelImageToImageModelConfig);
-// export const selectIPAdapterModels = buildModelsSelector(isIPAdapterModelConfig);
+export const selectIPAdapterModels = buildModelsSelector(isIPAdapterModelConfig);
// export const selectEmbeddingModels = buildModelsSelector(isTIModelConfig);
// export const selectVAEModels = buildModelsSelector(isVAEModelConfig);
// export const selectFluxVAEModels = buildModelsSelector(isFluxVAEModelConfig);
diff --git a/invokeai/frontend/web/src/services/events/onInvocationComplete.tsx b/invokeai/frontend/web/src/services/events/onInvocationComplete.tsx
index 8e299202e9..c40d3fb5a0 100644
--- a/invokeai/frontend/web/src/services/events/onInvocationComplete.tsx
+++ b/invokeai/frontend/web/src/services/events/onInvocationComplete.tsx
@@ -1,5 +1,5 @@
import { logger } from 'app/logging/logger';
-import type { AppDispatch, RootState } from 'app/store/store';
+import type { AppDispatch, AppGetState } from 'app/store/store';
import { deepClone } from 'common/util/deepClone';
import { boardIdSelected, galleryViewChanged, imageSelected, offsetChanged } from 'features/gallery/store/gallerySlice';
import { $nodeExecutionStates, upsertExecutionState } from 'features/nodes/hooks/useNodeExecutionState';
@@ -20,7 +20,7 @@ const log = logger('events');
const nodeTypeDenylist = ['load_image', 'image'];
-export const buildOnInvocationComplete = (getState: () => RootState, dispatch: AppDispatch) => {
+export const buildOnInvocationComplete = (getState: AppGetState, dispatch: AppDispatch) => {
const addImagesToGallery = async (data: S['InvocationCompleteEvent']) => {
if (nodeTypeDenylist.includes(data.invocation.type)) {
log.trace(`Skipping denylisted node type (${data.invocation.type})`);
diff --git a/invokeai/frontend/web/src/services/events/onModelInstallError.tsx b/invokeai/frontend/web/src/services/events/onModelInstallError.tsx
index 3ae11de353..36c6762323 100644
--- a/invokeai/frontend/web/src/services/events/onModelInstallError.tsx
+++ b/invokeai/frontend/web/src/services/events/onModelInstallError.tsx
@@ -1,7 +1,7 @@
import { Button, ExternalLink, Spinner, Text } from '@invoke-ai/ui-library';
import { skipToken } from '@reduxjs/toolkit/query';
import { logger } from 'app/logging/logger';
-import type { AppDispatch, RootState } from 'app/store/store';
+import type { AppDispatch, AppGetState } from 'app/store/store';
import { useAppDispatch } from 'app/store/storeHooks';
import { getPrefixedId } from 'features/controlLayers/konva/util';
import { useFeatureStatus } from 'features/system/hooks/useFeatureStatus';
@@ -41,7 +41,7 @@ const getHFTokenStatus = async (dispatch: AppDispatch): Promise RootState, dispatch: AppDispatch) => {
+export const buildOnModelInstallError = (getState: AppGetState, dispatch: AppDispatch) => {
return async (data: S['ModelInstallErrorEvent']) => {
log.error({ data }, 'Model install error');