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');