diff --git a/invokeai/frontend/web/src/features/controlLayers/components/IPAdapter/IPAdapterSettings.tsx b/invokeai/frontend/web/src/features/controlLayers/components/IPAdapter/IPAdapterSettings.tsx
index 50f7da129d..0d92c1cf70 100644
--- a/invokeai/frontend/web/src/features/controlLayers/components/IPAdapter/IPAdapterSettings.tsx
+++ b/invokeai/frontend/web/src/features/controlLayers/components/IPAdapter/IPAdapterSettings.tsx
@@ -5,6 +5,7 @@ import { BeginEndStepPct } from 'features/controlLayers/components/common/BeginE
import { CanvasEntitySettingsWrapper } from 'features/controlLayers/components/common/CanvasEntitySettingsWrapper';
import { Weight } from 'features/controlLayers/components/common/Weight';
import { IPAdapterMethod } from 'features/controlLayers/components/IPAdapter/IPAdapterMethod';
+import { IPAdapterSettingsEmptyState } from 'features/controlLayers/components/IPAdapter/IPAdapterSettingsEmptyState';
import { useEntityIdentifierContext } from 'features/controlLayers/contexts/EntityIdentifierContext';
import { usePullBboxIntoGlobalReferenceImage } from 'features/controlLayers/hooks/saveCanvasHooks';
import { useCanvasIsBusy } from 'features/controlLayers/hooks/useCanvasIsBusy';
@@ -17,7 +18,7 @@ import {
referenceImageIPAdapterWeightChanged,
} from 'features/controlLayers/store/canvasSlice';
import { selectIsFLUX } from 'features/controlLayers/store/paramsSlice';
-import { selectCanvasSlice, selectEntityOrThrow } from 'features/controlLayers/store/selectors';
+import { selectCanvasSlice, selectEntity, selectEntityOrThrow } from 'features/controlLayers/store/selectors';
import type { CanvasEntityIdentifier, CLIPVisionModelV2, IPMethodV2 } from 'features/controlLayers/store/types';
import type { SetGlobalReferenceImageDndTargetData } from 'features/dnd/dnd';
import { setGlobalReferenceImageDndTarget } from 'features/dnd/dnd';
@@ -35,7 +36,7 @@ const buildSelectIPAdapter = (entityIdentifier: CanvasEntityIdentifier<'referenc
(canvas) => selectEntityOrThrow(canvas, entityIdentifier, 'IPAdapterSettings').ipAdapter
);
-export const IPAdapterSettings = memo(() => {
+const IPAdapterSettingsContent = memo(() => {
const { t } = useTranslation();
const dispatch = useAppDispatch();
const entityIdentifier = useEntityIdentifierContext('reference_image');
@@ -134,4 +135,25 @@ export const IPAdapterSettings = memo(() => {
);
});
+IPAdapterSettingsContent.displayName = 'IPAdapterSettingsContent';
+
+const buildSelectIPAdapterHasImage = (entityIdentifier: CanvasEntityIdentifier<'reference_image'>) =>
+ createSelector(selectCanvasSlice, (canvas) => {
+ const referenceImage = selectEntity(canvas, entityIdentifier);
+ return !!referenceImage && referenceImage.ipAdapter.image !== null;
+ });
+
+export const IPAdapterSettings = memo(() => {
+ const entityIdentifier = useEntityIdentifierContext('reference_image');
+
+ const selectIPAdapterHasImage = useMemo(() => buildSelectIPAdapterHasImage(entityIdentifier), [entityIdentifier]);
+ const hasImage = useAppSelector(selectIPAdapterHasImage);
+
+ if (!hasImage) {
+ return ;
+ }
+
+ return ;
+});
+
IPAdapterSettings.displayName = 'IPAdapterSettings';
diff --git a/invokeai/frontend/web/src/features/controlLayers/components/IPAdapter/IPAdapterSettingsEmptyState.tsx b/invokeai/frontend/web/src/features/controlLayers/components/IPAdapter/IPAdapterSettingsEmptyState.tsx
new file mode 100644
index 0000000000..4928cf7cb7
--- /dev/null
+++ b/invokeai/frontend/web/src/features/controlLayers/components/IPAdapter/IPAdapterSettingsEmptyState.tsx
@@ -0,0 +1,64 @@
+import { Button, Flex, Text } from '@invoke-ai/ui-library';
+import { useAppDispatch } from 'app/store/storeHooks';
+import { useImageUploadButton } from 'common/hooks/useImageUploadButton';
+import { useEntityIdentifierContext } from 'features/controlLayers/contexts/EntityIdentifierContext';
+import { useCanvasIsBusy } from 'features/controlLayers/hooks/useCanvasIsBusy';
+import type { SetGlobalReferenceImageDndTargetData } from 'features/dnd/dnd';
+import { setGlobalReferenceImageDndTarget } from 'features/dnd/dnd';
+import { DndDropTarget } from 'features/dnd/DndDropTarget';
+import { setGlobalReferenceImage } from 'features/imageActions/actions';
+import { activeTabCanvasRightPanelChanged } from 'features/ui/store/uiSlice';
+import { memo, useCallback, useMemo } from 'react';
+import { Trans, useTranslation } from 'react-i18next';
+import type { ImageDTO } from 'services/api/types';
+
+export const IPAdapterSettingsEmptyState = memo(() => {
+ const { t } = useTranslation();
+ const entityIdentifier = useEntityIdentifierContext('reference_image');
+ const dispatch = useAppDispatch();
+ const isBusy = useCanvasIsBusy();
+ const onUpload = useCallback(
+ (imageDTO: ImageDTO) => {
+ setGlobalReferenceImage({ imageDTO, entityIdentifier, dispatch });
+ },
+ [dispatch, entityIdentifier]
+ );
+ const uploadApi = useImageUploadButton({ onUpload, allowMultiple: false });
+ const onClickGalleryButton = useCallback(() => {
+ dispatch(activeTabCanvasRightPanelChanged('gallery'));
+ }, [dispatch]);
+
+ const dndTargetData = useMemo(
+ () => setGlobalReferenceImageDndTarget.getData({ entityIdentifier }),
+ [entityIdentifier]
+ );
+
+ const components = useMemo(
+ () => ({
+ UploadButton: (
+
+ ),
+ GalleryButton: (
+
+ ),
+ }),
+ [isBusy, onClickGalleryButton, uploadApi]
+ );
+
+ return (
+
+
+
+
+
+
+
+ );
+});
+
+IPAdapterSettingsEmptyState.displayName = 'IPAdapterSettingsEmptyState';