diff --git a/invokeai/frontend/web/package.json b/invokeai/frontend/web/package.json
index 0b448b73428..2d90d21312f 100644
--- a/invokeai/frontend/web/package.json
+++ b/invokeai/frontend/web/package.json
@@ -58,7 +58,7 @@
"@dnd-kit/sortable": "^8.0.0",
"@dnd-kit/utilities": "^3.2.2",
"@fontsource-variable/inter": "^5.0.20",
- "@invoke-ai/ui-library": "^0.0.34",
+ "@invoke-ai/ui-library": "^0.0.36",
"@nanostores/react": "^0.7.3",
"@reduxjs/toolkit": "2.2.3",
"@roarr/browser-log-writer": "^1.3.0",
diff --git a/invokeai/frontend/web/pnpm-lock.yaml b/invokeai/frontend/web/pnpm-lock.yaml
index e6030ac08fa..1c8c30464d9 100644
--- a/invokeai/frontend/web/pnpm-lock.yaml
+++ b/invokeai/frontend/web/pnpm-lock.yaml
@@ -24,8 +24,8 @@ dependencies:
specifier: ^5.0.20
version: 5.0.20
'@invoke-ai/ui-library':
- specifier: ^0.0.34
- version: 0.0.34(@chakra-ui/form-control@2.2.0)(@chakra-ui/icon@3.2.0)(@chakra-ui/media-query@3.3.0)(@chakra-ui/menu@2.2.1)(@chakra-ui/spinner@2.1.0)(@chakra-ui/system@2.6.2)(@fontsource-variable/inter@5.0.20)(@types/react@18.3.3)(i18next@23.12.2)(react-dom@18.3.1)(react@18.3.1)
+ specifier: ^0.0.36
+ version: 0.0.36(@chakra-ui/form-control@2.2.0)(@chakra-ui/icon@3.2.0)(@chakra-ui/media-query@3.3.0)(@chakra-ui/menu@2.2.1)(@chakra-ui/spinner@2.1.0)(@chakra-ui/system@2.6.2)(@fontsource-variable/inter@5.0.20)(@types/react@18.3.3)(i18next@23.12.2)(react-dom@18.3.1)(react@18.3.1)
'@nanostores/react':
specifier: ^0.7.3
version: 0.7.3(nanostores@0.11.2)(react@18.3.1)
@@ -3574,8 +3574,8 @@ packages:
prettier: 3.3.3
dev: true
- /@invoke-ai/ui-library@0.0.34(@chakra-ui/form-control@2.2.0)(@chakra-ui/icon@3.2.0)(@chakra-ui/media-query@3.3.0)(@chakra-ui/menu@2.2.1)(@chakra-ui/spinner@2.1.0)(@chakra-ui/system@2.6.2)(@fontsource-variable/inter@5.0.20)(@types/react@18.3.3)(i18next@23.12.2)(react-dom@18.3.1)(react@18.3.1):
- resolution: {integrity: sha512-iDSjFQV2U4LfQ8+UdZ9Uy6J1iKKTSsXM0uhkWrwcIghbgN5QwY3ABVLhqJrSWVTwp7puEDhe/lRQ9QhTZBkVzw==}
+ /@invoke-ai/ui-library@0.0.36(@chakra-ui/form-control@2.2.0)(@chakra-ui/icon@3.2.0)(@chakra-ui/media-query@3.3.0)(@chakra-ui/menu@2.2.1)(@chakra-ui/spinner@2.1.0)(@chakra-ui/system@2.6.2)(@fontsource-variable/inter@5.0.20)(@types/react@18.3.3)(i18next@23.12.2)(react-dom@18.3.1)(react@18.3.1):
+ resolution: {integrity: sha512-gF/LSzUYFxDmKiUADvl7lsVEbyOQurWVxeCRD4279lBo/RjVcGDwkflLtP8FN//Wv1CqZqn9UnOrJHpUZWl1Rg==}
peerDependencies:
'@fontsource-variable/inter': ^5.0.16
react: ^18.2.0
diff --git a/invokeai/frontend/web/public/locales/en.json b/invokeai/frontend/web/public/locales/en.json
index d6183458c46..f932e2f8a71 100644
--- a/invokeai/frontend/web/public/locales/en.json
+++ b/invokeai/frontend/web/public/locales/en.json
@@ -439,7 +439,9 @@
"compareHelp2": "Press M to cycle through comparison modes.",
"compareHelp3": "Press C to swap the compared images.",
"compareHelp4": "Press Z or Esc to exit.",
- "toggleMiniViewer": "Toggle Mini Viewer"
+ "toggleMiniViewer": "Toggle Mini Viewer",
+ "openViewer": "Open Viewer",
+ "closeViewer": "Close Viewer"
},
"hotkeys": {
"searchHotkeys": "Search Hotkeys",
@@ -1723,9 +1725,11 @@
"sendingToCanvas": "Sending to Canvas",
"sendingToGallery": "Sending to Gallery",
"sendToGallery": "Send To Gallery",
- "sendToGalleryDesc": "Generations will be sent to the gallery.",
+ "sendToGalleryDesc": "Pressing Invoke generates and saves a unique image to your gallery.",
"sendToCanvas": "Send To Canvas",
- "sendToCanvasDesc": "Generations will be staged onto the canvas.",
+ "sendToCanvasDesc": "Pressing Invoke stages your work in progress on the canvas.",
+ "viewProgressInViewer": "View progress and outputs in the Image Viewer.",
+ "viewProgressOnCanvas": "View progress and stage outputs on the Canvas.",
"rasterLayer_withCount_one": "$t(controlLayers.rasterLayer)",
"controlLayer_withCount_one": "$t(controlLayers.controlLayer)",
"inpaintMask_withCount_one": "$t(controlLayers.inpaintMask)",
@@ -2031,6 +2035,7 @@
"events": "Events",
"queue": "Queue",
"metadata": "Metadata"
- }
+ },
+ "showSendingToAlerts": "Alert When Sending to Different View"
}
}
diff --git a/invokeai/frontend/web/src/common/hooks/useGlobalHotkeys.ts b/invokeai/frontend/web/src/common/hooks/useGlobalHotkeys.ts
index 9a40d55841a..634e2ead39c 100644
--- a/invokeai/frontend/web/src/common/hooks/useGlobalHotkeys.ts
+++ b/invokeai/frontend/web/src/common/hooks/useGlobalHotkeys.ts
@@ -114,13 +114,4 @@ export const useGlobalHotkeys = () => {
},
[dispatch, isModelManagerEnabled]
);
-
- useHotkeys(
- isModelManagerEnabled ? '6' : '5',
- () => {
- dispatch(setActiveTab('gallery'));
- setScopes([]);
- },
- [dispatch, isModelManagerEnabled]
- );
};
diff --git a/invokeai/frontend/web/src/features/controlLayers/components/CanvasDropArea.tsx b/invokeai/frontend/web/src/features/controlLayers/components/CanvasDropArea.tsx
index 499cbcba7d5..15ecd5a956e 100644
--- a/invokeai/frontend/web/src/features/controlLayers/components/CanvasDropArea.tsx
+++ b/invokeai/frontend/web/src/features/controlLayers/components/CanvasDropArea.tsx
@@ -1,6 +1,7 @@
import { Flex } from '@invoke-ai/ui-library';
import IAIDroppable from 'common/components/IAIDroppable';
import type { AddControlLayerFromImageDropData, AddRasterLayerFromImageDropData } from 'features/dnd/types';
+import { useImageViewer } from 'features/gallery/components/ImageViewer/useImageViewer';
import { memo } from 'react';
const addRasterLayerFromImageDropData: AddRasterLayerFromImageDropData = {
@@ -14,6 +15,12 @@ const addControlLayerFromImageDropData: AddControlLayerFromImageDropData = {
};
export const CanvasDropArea = memo(() => {
+ const imageViewer = useImageViewer();
+
+ if (imageViewer.isOpen) {
+ return null;
+ }
+
return (
<>
diff --git a/invokeai/frontend/web/src/features/controlLayers/components/CanvasPanelContent.tsx b/invokeai/frontend/web/src/features/controlLayers/components/CanvasLayersPanelContent.tsx
similarity index 89%
rename from invokeai/frontend/web/src/features/controlLayers/components/CanvasPanelContent.tsx
rename to invokeai/frontend/web/src/features/controlLayers/components/CanvasLayersPanelContent.tsx
index 87971e31f0e..1a4bf4d2734 100644
--- a/invokeai/frontend/web/src/features/controlLayers/components/CanvasPanelContent.tsx
+++ b/invokeai/frontend/web/src/features/controlLayers/components/CanvasLayersPanelContent.tsx
@@ -7,7 +7,7 @@ import { CanvasManagerProviderGate } from 'features/controlLayers/contexts/Canva
import { selectHasEntities } from 'features/controlLayers/store/selectors';
import { memo } from 'react';
-export const CanvasPanelContent = memo(() => {
+export const CanvasLayersPanelContent = memo(() => {
const hasEntities = useAppSelector(selectHasEntities);
return (
@@ -22,4 +22,4 @@ export const CanvasPanelContent = memo(() => {
);
});
-CanvasPanelContent.displayName = 'CanvasPanelContent';
+CanvasLayersPanelContent.displayName = 'CanvasLayersPanelContent';
diff --git a/invokeai/frontend/web/src/features/controlLayers/components/CanvasTabContent.tsx b/invokeai/frontend/web/src/features/controlLayers/components/CanvasMainPanelContent.tsx
similarity index 88%
rename from invokeai/frontend/web/src/features/controlLayers/components/CanvasTabContent.tsx
rename to invokeai/frontend/web/src/features/controlLayers/components/CanvasMainPanelContent.tsx
index c228c743e9d..4b2c548885b 100644
--- a/invokeai/frontend/web/src/features/controlLayers/components/CanvasTabContent.tsx
+++ b/invokeai/frontend/web/src/features/controlLayers/components/CanvasMainPanelContent.tsx
@@ -6,6 +6,7 @@ import { CanvasDropArea } from 'features/controlLayers/components/CanvasDropArea
import { Filter } from 'features/controlLayers/components/Filters/Filter';
import { CanvasHUD } from 'features/controlLayers/components/HUD/CanvasHUD';
import { CanvasSelectedEntityStatusAlert } from 'features/controlLayers/components/HUD/CanvasSelectedEntityStatusAlert';
+import { SendingToGalleryAlert } from 'features/controlLayers/components/HUD/CanvasSendingToGalleryAlert';
import { InvokeCanvasComponent } from 'features/controlLayers/components/InvokeCanvasComponent';
import { StagingAreaIsStagingGate } from 'features/controlLayers/components/StagingArea/StagingAreaIsStagingGate';
import { StagingAreaToolbar } from 'features/controlLayers/components/StagingArea/StagingAreaToolbar';
@@ -14,9 +15,10 @@ import { Transform } from 'features/controlLayers/components/Transform/Transform
import { CanvasManagerProviderGate } from 'features/controlLayers/contexts/CanvasManagerProviderGate';
import { TRANSPARENCY_CHECKERBOARD_PATTERN_DATAURL } from 'features/controlLayers/konva/patterns/transparency-checkerboard-pattern';
import { selectDynamicGrid, selectShowHUD } from 'features/controlLayers/store/canvasSettingsSlice';
+import { GatedImageViewer } from 'features/gallery/components/ImageViewer/ImageViewer';
import { memo, useCallback, useRef } from 'react';
-export const CanvasTabContent = memo(() => {
+export const CanvasMainPanelContent = memo(() => {
const ref = useRef(null);
const dynamicGrid = useAppSelector(selectDynamicGrid);
const showHUD = useAppSelector(selectShowHUD);
@@ -76,8 +78,9 @@ export const CanvasTabContent = memo(() => {
)}
-
+
+
@@ -97,8 +100,9 @@ export const CanvasTabContent = memo(() => {
+
);
});
-CanvasTabContent.displayName = 'CanvasTabContent';
+CanvasMainPanelContent.displayName = 'CanvasMainPanelContent';
diff --git a/invokeai/frontend/web/src/features/controlLayers/components/CanvasRightPanel.tsx b/invokeai/frontend/web/src/features/controlLayers/components/CanvasRightPanel.tsx
index 40bd0add243..af67dc5320c 100644
--- a/invokeai/frontend/web/src/features/controlLayers/components/CanvasRightPanel.tsx
+++ b/invokeai/frontend/web/src/features/controlLayers/components/CanvasRightPanel.tsx
@@ -1,30 +1,47 @@
import { useDndContext } from '@dnd-kit/core';
-import { Box, Spacer, Tab, TabList, TabPanel, TabPanels, Tabs } from '@invoke-ai/ui-library';
+import { Box, Button, Spacer, Tab, TabList, TabPanel, TabPanels, Tabs } from '@invoke-ai/ui-library';
+import { useStore } from '@nanostores/react';
import { useAppSelector } from 'app/store/storeHooks';
import { useScopeOnFocus } from 'common/hooks/interactionScopes';
-import { CanvasPanelContent } from 'features/controlLayers/components/CanvasPanelContent';
-import { CanvasSendToToggle } from 'features/controlLayers/components/CanvasSendToToggle';
-import { selectSendToCanvas } from 'features/controlLayers/store/canvasSettingsSlice';
+import { CanvasLayersPanelContent } from 'features/controlLayers/components/CanvasLayersPanelContent';
import { selectEntityCountActive } from 'features/controlLayers/store/selectors';
import GalleryPanelContent from 'features/gallery/components/GalleryPanelContent';
-import { memo, useCallback, useMemo, useRef, useState } from 'react';
+import { useImageViewer } from 'features/gallery/components/ImageViewer/useImageViewer';
+import { atom } from 'nanostores';
+import { memo, useCallback, useMemo, useRef } from 'react';
+import { useHotkeys } from 'react-hotkeys-hook';
import { useTranslation } from 'react-i18next';
-export const CanvasRightPanelContent = memo(() => {
+const $tabIndex = atom(0);
+export const setRightPanelTabToLayers = () => $tabIndex.set(0);
+export const setRightPanelTabToGallery = () => $tabIndex.set(1);
+
+export const CanvasRightPanel = memo(() => {
+ const { t } = useTranslation();
const ref = useRef(null);
- const [tab, setTab] = useState(0);
+ const tabIndex = useStore($tabIndex);
useScopeOnFocus('gallery', ref);
+ const imageViewer = useImageViewer();
+ const onClickViewerToggleButton = useCallback(() => {
+ if ($tabIndex.get() !== 1) {
+ $tabIndex.set(1);
+ }
+ imageViewer.toggle();
+ }, [imageViewer]);
+ useHotkeys('z', imageViewer.toggle);
return (
-
+
-
+
-
+
-
+
@@ -34,30 +51,29 @@ export const CanvasRightPanelContent = memo(() => {
);
});
-CanvasRightPanelContent.displayName = 'CanvasRightPanelContent';
+CanvasRightPanel.displayName = 'CanvasRightPanel';
-const PanelTabs = memo(({ setTab }: { setTab: (val: number) => void }) => {
+const PanelTabs = memo(() => {
const { t } = useTranslation();
const activeEntityCount = useAppSelector(selectEntityCountActive);
- const sendToCanvas = useAppSelector(selectSendToCanvas);
const tabTimeout = useRef(null);
const dndCtx = useDndContext();
const onOnMouseOverLayersTab = useCallback(() => {
tabTimeout.current = window.setTimeout(() => {
if (dndCtx.active) {
- setTab(0);
+ setRightPanelTabToLayers();
}
}, 300);
- }, [dndCtx.active, setTab]);
+ }, [dndCtx.active]);
const onOnMouseOverGalleryTab = useCallback(() => {
tabTimeout.current = window.setTimeout(() => {
if (dndCtx.active) {
- setTab(1);
+ setRightPanelTabToGallery();
}
}, 300);
- }, [dndCtx.active, setTab]);
+ }, [dndCtx.active]);
const onMouseOut = useCallback(() => {
if (tabTimeout.current) {
@@ -78,15 +94,9 @@ const PanelTabs = memo(({ setTab }: { setTab: (val: number) => void }) => {
{layersTabLabel}
- {sendToCanvas && (
-
- )}
{t('gallery.gallery')}
- {!sendToCanvas && (
-
- )}
>
);
diff --git a/invokeai/frontend/web/src/features/controlLayers/components/CanvasSendToToggle.tsx b/invokeai/frontend/web/src/features/controlLayers/components/CanvasSendToToggle.tsx
deleted file mode 100644
index 8e44f44a7f4..00000000000
--- a/invokeai/frontend/web/src/features/controlLayers/components/CanvasSendToToggle.tsx
+++ /dev/null
@@ -1,77 +0,0 @@
-import {
- Button,
- Flex,
- Icon,
- Popover,
- PopoverArrow,
- PopoverBody,
- PopoverContent,
- PopoverTrigger,
- Text,
-} from '@invoke-ai/ui-library';
-import { useAppDispatch, useAppSelector } from 'app/store/storeHooks';
-import { selectSendToCanvas, settingsSendToCanvasChanged } from 'features/controlLayers/store/canvasSettingsSlice';
-import { memo, useCallback } from 'react';
-import { useTranslation } from 'react-i18next';
-import { PiCaretDownBold, PiCheckBold } from 'react-icons/pi';
-
-export const CanvasSendToToggle = memo(() => {
- const { t } = useTranslation();
- const sendToCanvas = useAppSelector(selectSendToCanvas);
- const dispatch = useAppDispatch();
-
- const enableSendToCanvas = useCallback(() => {
- dispatch(settingsSendToCanvasChanged(true));
- }, [dispatch]);
-
- const disableSendToCanvas = useCallback(() => {
- dispatch(settingsSendToCanvasChanged(false));
- }, [dispatch]);
-
- return (
-
-
- }
- >
- {sendToCanvas ? t('controlLayers.sendingToCanvas') : t('controlLayers.sendingToGallery')}
-
-
-
-
-
-
-
-
-
-
-
-
- );
-});
-
-CanvasSendToToggle.displayName = 'CanvasSendToToggle';
diff --git a/invokeai/frontend/web/src/features/controlLayers/components/HUD/CanvasSelectedEntityStatusAlert.tsx b/invokeai/frontend/web/src/features/controlLayers/components/HUD/CanvasSelectedEntityStatusAlert.tsx
index 9042ca3dce8..d510556f898 100644
--- a/invokeai/frontend/web/src/features/controlLayers/components/HUD/CanvasSelectedEntityStatusAlert.tsx
+++ b/invokeai/frontend/web/src/features/controlLayers/components/HUD/CanvasSelectedEntityStatusAlert.tsx
@@ -1,8 +1,8 @@
-import { Box, Flex, Icon, Text } from '@invoke-ai/ui-library';
+import type { AlertStatus } from '@invoke-ai/ui-library';
+import { Alert, AlertDescription, AlertIcon, AlertTitle } from '@invoke-ai/ui-library';
import { useStore } from '@nanostores/react';
import { createSelector } from '@reduxjs/toolkit';
import { useAppSelector } from 'app/store/storeHooks';
-import type { Property } from 'csstype';
import { useEntityAdapterSafe } from 'features/controlLayers/contexts/EntityAdapterContext';
import { useEntityTitle } from 'features/controlLayers/hooks/useEntityTitle';
import { useEntityTypeIsHidden } from 'features/controlLayers/hooks/useEntityTypeIsHidden';
@@ -16,7 +16,6 @@ import type { CanvasEntityIdentifier } from 'features/controlLayers/store/types'
import { atom } from 'nanostores';
import { memo, useMemo } from 'react';
import { useTranslation } from 'react-i18next';
-import { PiWarningCircleFill } from 'react-icons/pi';
type ContentProps = {
entityIdentifier: CanvasEntityIdentifier;
@@ -25,9 +24,10 @@ type ContentProps = {
const $isFilteringFallback = atom(false);
-type EntityStatus = {
- value: string;
- color?: Property.Color;
+type AlertData = {
+ status: AlertStatus;
+ title: string;
+ description: string;
};
const CanvasSelectedEntityStatusAlertContent = memo(({ entityIdentifier, adapter }: ContentProps) => {
@@ -47,73 +47,60 @@ const CanvasSelectedEntityStatusAlertContent = memo(({ entityIdentifier, adapter
const isFiltering = useStore(adapter.filterer?.$isFiltering ?? $isFilteringFallback);
const isTransforming = useStore(adapter.transformer.$isTransforming);
- const status = useMemo(() => {
+ const alert = useMemo(() => {
if (isFiltering) {
return {
- value: t('controlLayers.HUD.entityStatus.isFiltering'),
- color: 'invokeBlue.300',
+ status: 'info',
+ title,
+ description: t('controlLayers.HUD.entityStatus.isFiltering'),
};
}
if (isTransforming) {
return {
- value: t('controlLayers.HUD.entityStatus.isTransforming'),
- color: 'invokeBlue.300',
+ status: 'info',
+ title,
+ description: t('controlLayers.HUD.entityStatus.isTransforming'),
};
}
if (isHidden) {
return {
- value: t('controlLayers.HUD.entityStatus.isHidden'),
- color: 'invokePurple.300',
+ status: 'warning',
+ title,
+ description: t('controlLayers.HUD.entityStatus.isHidden'),
};
}
if (isLocked) {
return {
- value: t('controlLayers.HUD.entityStatus.isLocked'),
- color: 'invokeRed.300',
+ status: 'warning',
+ title,
+ description: t('controlLayers.HUD.entityStatus.isLocked'),
};
}
if (!isEnabled) {
return {
- value: t('controlLayers.HUD.entityStatus.isDisabled'),
- color: 'invokeRed.300',
+ status: 'warning',
+ title,
+ description: t('controlLayers.HUD.entityStatus.isDisabled'),
};
}
return null;
- }, [isFiltering, isTransforming, isHidden, isLocked, isEnabled, t]);
+ }, [isFiltering, isTransforming, isHidden, isLocked, isEnabled, title, t]);
- if (!status) {
+ if (!alert) {
return null;
}
return (
-
-
-
-
-
-
- {title}
- {' '}
- {status.value}
-
-
-
+
+
+ {alert.title}
+ {alert.description}.
+
);
});
diff --git a/invokeai/frontend/web/src/features/controlLayers/components/HUD/CanvasSendingToGalleryAlert.tsx b/invokeai/frontend/web/src/features/controlLayers/components/HUD/CanvasSendingToGalleryAlert.tsx
new file mode 100644
index 00000000000..d38b60c49eb
--- /dev/null
+++ b/invokeai/frontend/web/src/features/controlLayers/components/HUD/CanvasSendingToGalleryAlert.tsx
@@ -0,0 +1,161 @@
+import {
+ Alert,
+ AlertDescription,
+ AlertIcon,
+ AlertTitle,
+ Button,
+ Flex,
+ Icon,
+ IconButton,
+ Spacer,
+} from '@invoke-ai/ui-library';
+import { useAppDispatch, useAppSelector } from 'app/store/storeHooks';
+import { useBoolean } from 'common/hooks/useBoolean';
+import {
+ setRightPanelTabToGallery,
+ setRightPanelTabToLayers,
+} from 'features/controlLayers/components/CanvasRightPanel';
+import { useImageViewer } from 'features/gallery/components/ImageViewer/useImageViewer';
+import { useCurrentDestination } from 'features/queue/hooks/useCurrentDestination';
+import { selectShowSendingToAlerts, showSendingToAlertsChanged } from 'features/system/store/systemSlice';
+import { setActiveTab } from 'features/ui/store/uiSlice';
+import { AnimatePresence, motion } from 'framer-motion';
+import type { PropsWithChildren, ReactNode } from 'react';
+import { useCallback, useMemo } from 'react';
+import { Trans, useTranslation } from 'react-i18next';
+import { PiXBold } from 'react-icons/pi';
+
+const ActivateImageViewerButton = (props: PropsWithChildren) => {
+ const imageViewer = useImageViewer();
+ const onClick = useCallback(() => {
+ imageViewer.open();
+ setRightPanelTabToGallery();
+ }, [imageViewer]);
+ return (
+
+ );
+};
+
+export const SendingToGalleryAlert = () => {
+ const { t } = useTranslation();
+ const destination = useCurrentDestination();
+ const isVisible = useMemo(() => {
+ if (!destination) {
+ return false;
+ }
+
+ if (destination === 'canvas') {
+ return false;
+ }
+
+ return true;
+ }, [destination]);
+
+ return (
+ }} />
+ }
+ isVisible={isVisible}
+ />
+ );
+};
+
+const ActivateCanvasButton = (props: PropsWithChildren) => {
+ const dispatch = useAppDispatch();
+ const imageViewer = useImageViewer();
+ const onClick = useCallback(() => {
+ dispatch(setActiveTab('generation'));
+ setRightPanelTabToLayers();
+ imageViewer.close();
+ }, [dispatch, imageViewer]);
+ return (
+
+ );
+};
+
+export const SendingToCanvasAlert = () => {
+ const { t } = useTranslation();
+ const destination = useCurrentDestination();
+ const isVisible = useMemo(() => {
+ if (!destination) {
+ return false;
+ }
+
+ if (destination !== 'canvas') {
+ return false;
+ }
+
+ return true;
+ }, [destination]);
+
+ return (
+ }} />
+ }
+ isVisible={isVisible}
+ />
+ );
+};
+
+const AlertWrapper = ({
+ title,
+ description,
+ isVisible,
+}: {
+ title: ReactNode;
+ description: ReactNode;
+ isVisible: boolean;
+}) => {
+ const { t } = useTranslation();
+ const dispatch = useAppDispatch();
+ const showSendToAlerts = useAppSelector(selectShowSendingToAlerts);
+ const isHovered = useBoolean(false);
+ const onClickDontShowMeThese = useCallback(() => {
+ dispatch(showSendingToAlertsChanged(false));
+ isHovered.setFalse();
+ }, [dispatch, isHovered]);
+
+ return (
+
+ {(isVisible || isHovered.isTrue) && showSendToAlerts && (
+
+
+
+
+ {title}
+
+ }
+ tooltip={t('common.dontShowMeThese')}
+ aria-label={t('common.dontShowMeThese')}
+ right={-1}
+ top={-2}
+ onClick={onClickDontShowMeThese}
+ minW="auto"
+ />
+
+ {description}
+
+
+ )}
+
+ );
+};
diff --git a/invokeai/frontend/web/src/features/gallery/components/Gallery.tsx b/invokeai/frontend/web/src/features/gallery/components/Gallery.tsx
index fdbe7de50fc..0ef57047ed4 100644
--- a/invokeai/frontend/web/src/features/gallery/components/Gallery.tsx
+++ b/invokeai/frontend/web/src/features/gallery/components/Gallery.tsx
@@ -14,14 +14,12 @@ import {
import { createSelector } from '@reduxjs/toolkit';
import { useAppDispatch, useAppSelector } from 'app/store/storeHooks';
import { useGallerySearchTerm } from 'features/gallery/components/ImageGrid/useGallerySearchTerm';
-import CurrentImageButtons from 'features/gallery/components/ImageViewer/CurrentImageButtons';
-import CurrentImagePreview from 'features/gallery/components/ImageViewer/CurrentImagePreview';
-import { selectIsMiniViewerOpen, selectSelectedBoardId } from 'features/gallery/store/gallerySelectors';
-import { galleryViewChanged, isMiniViewerOpenToggled, selectGallerySlice } from 'features/gallery/store/gallerySlice';
+import { selectSelectedBoardId } from 'features/gallery/store/gallerySelectors';
+import { galleryViewChanged, selectGallerySlice } from 'features/gallery/store/gallerySlice';
import type { CSSProperties } from 'react';
import { useCallback } from 'react';
import { useTranslation } from 'react-i18next';
-import { PiEyeBold, PiEyeClosedBold, PiMagnifyingGlassBold } from 'react-icons/pi';
+import { PiMagnifyingGlassBold } from 'react-icons/pi';
import { useBoardName } from 'services/api/hooks/useBoardName';
import GalleryImageGrid from './ImageGrid/GalleryImageGrid';
@@ -52,11 +50,6 @@ export const Gallery = () => {
const initialSearchTerm = useAppSelector(selectSearchTerm);
const searchDisclosure = useDisclosure({ defaultIsOpen: initialSearchTerm.length > 0 });
const [searchTerm, onChangeSearchTerm, onResetSearchTerm] = useGallerySearchTerm();
- const isMiniViewerOpen = useAppSelector(selectIsMiniViewerOpen);
-
- const toggleMiniViewer = useCallback(() => {
- dispatch(isMiniViewerOpenToggled());
- }, [dispatch]);
const handleClickImages = useCallback(() => {
dispatch(galleryViewChanged('images'));
}, [dispatch]);
@@ -87,27 +80,15 @@ export const Gallery = () => {
{t('gallery.assets')}
-
- : }
- colorScheme={isMiniViewerOpen ? 'invokeBlue' : 'base'}
- />
- }
- />
-
+ }
+ />
@@ -120,21 +101,6 @@ export const Gallery = () => {
/>
-
-
-
-
-
-
-
-
diff --git a/invokeai/frontend/web/src/features/gallery/components/ImageContextMenu/ImageMenuItemOpenInViewer.tsx b/invokeai/frontend/web/src/features/gallery/components/ImageContextMenu/ImageMenuItemOpenInViewer.tsx
index 6f4962a3c7f..0ce467d4a47 100644
--- a/invokeai/frontend/web/src/features/gallery/components/ImageContextMenu/ImageMenuItemOpenInViewer.tsx
+++ b/invokeai/frontend/web/src/features/gallery/components/ImageContextMenu/ImageMenuItemOpenInViewer.tsx
@@ -1,25 +1,25 @@
import { MenuItem } from '@invoke-ai/ui-library';
import { useAppDispatch } from 'app/store/storeHooks';
+import { useImageViewer } from 'features/gallery/components/ImageViewer/useImageViewer';
import { useImageDTOContext } from 'features/gallery/contexts/ImageDTOContext';
import { imageSelected, imageToCompareChanged } from 'features/gallery/store/gallerySlice';
-import { setActiveTab } from 'features/ui/store/uiSlice';
import { memo, useCallback } from 'react';
import { useTranslation } from 'react-i18next';
-import { PiEyeBold } from 'react-icons/pi';
+import { PiArrowsOutBold } from 'react-icons/pi';
export const ImageMenuItemOpenInViewer = memo(() => {
const { t } = useTranslation();
const dispatch = useAppDispatch();
const imageDTO = useImageDTOContext();
-
+ const imageViewer = useImageViewer();
const onClick = useCallback(() => {
dispatch(imageToCompareChanged(null));
dispatch(imageSelected(imageDTO));
- dispatch(setActiveTab('gallery'));
- }, [dispatch, imageDTO]);
+ imageViewer.open();
+ }, [dispatch, imageDTO, imageViewer]);
return (
- } onClick={onClick}>
+ } onClick={onClick}>
{t('gallery.openInViewer')}
);
diff --git a/invokeai/frontend/web/src/features/gallery/components/ImageGrid/GalleryImage.tsx b/invokeai/frontend/web/src/features/gallery/components/ImageGrid/GalleryImage.tsx
index 7dca9ecac2f..87915e4928b 100644
--- a/invokeai/frontend/web/src/features/gallery/components/ImageGrid/GalleryImage.tsx
+++ b/invokeai/frontend/web/src/features/gallery/components/ImageGrid/GalleryImage.tsx
@@ -10,11 +10,11 @@ import IAIFillSkeleton from 'common/components/IAIFillSkeleton';
import { imagesToDeleteSelected } from 'features/deleteImageModal/store/slice';
import type { GallerySelectionDraggableData, ImageDraggableData, TypesafeDraggableData } from 'features/dnd/types';
import { getGalleryImageDataTestId } from 'features/gallery/components/ImageGrid/getGalleryImageDataTestId';
+import { useImageViewer } from 'features/gallery/components/ImageViewer/useImageViewer';
import { useMultiselect } from 'features/gallery/hooks/useMultiselect';
import { useScrollIntoView } from 'features/gallery/hooks/useScrollIntoView';
import { selectSelectedBoardId } from 'features/gallery/store/gallerySelectors';
import { imageToCompareChanged, selectGallerySlice } from 'features/gallery/store/gallerySlice';
-import { setActiveTab } from 'features/ui/store/uiSlice';
import type { MouseEvent, MouseEventHandler } from 'react';
import { memo, useCallback, useMemo, useState } from 'react';
import { useTranslation } from 'react-i18next';
@@ -112,10 +112,11 @@ const GalleryImage = ({ index, imageDTO }: HoverableImageProps) => {
setIsHovered(true);
}, []);
+ const imageViewer = useImageViewer();
const onDoubleClick = useCallback(() => {
- dispatch(setActiveTab('gallery'));
+ imageViewer.open();
dispatch(imageToCompareChanged(null));
- }, [dispatch]);
+ }, [dispatch, imageViewer]);
const handleMouseOut = useCallback(() => {
setIsHovered(false);
diff --git a/invokeai/frontend/web/src/features/gallery/components/ImageGrid/GallerySelectionCountTag.tsx b/invokeai/frontend/web/src/features/gallery/components/ImageGrid/GallerySelectionCountTag.tsx
index bd7faf86e03..cc6872ab7eb 100644
--- a/invokeai/frontend/web/src/features/gallery/components/ImageGrid/GallerySelectionCountTag.tsx
+++ b/invokeai/frontend/web/src/features/gallery/components/ImageGrid/GallerySelectionCountTag.tsx
@@ -22,8 +22,11 @@ export const GallerySelectionCountTag = () => {
const isSelectAllEnabled = useStore($isSelectAllEnabled);
const onClearSelection = useCallback(() => {
- dispatch(selectionChanged([]));
- }, [dispatch]);
+ const firstImage = selection[0];
+ if (firstImage) {
+ dispatch(selectionChanged([firstImage]));
+ }
+ }, [dispatch, selection]);
const onSelectPage = useCallback(() => {
dispatch(selectionChanged([...selection, ...imageDTOs]));
diff --git a/invokeai/frontend/web/src/features/gallery/components/ImageViewer/CompareToolbar.tsx b/invokeai/frontend/web/src/features/gallery/components/ImageViewer/CompareToolbar.tsx
index 637d282e171..e2f2944c314 100644
--- a/invokeai/frontend/web/src/features/gallery/components/ImageViewer/CompareToolbar.tsx
+++ b/invokeai/frontend/web/src/features/gallery/components/ImageViewer/CompareToolbar.tsx
@@ -21,7 +21,7 @@ import {
import { memo, useCallback } from 'react';
import { useHotkeys } from 'react-hotkeys-hook';
import { Trans, useTranslation } from 'react-i18next';
-import { PiArrowsOutBold, PiQuestion, PiSwapBold, PiXBold } from 'react-icons/pi';
+import { PiArrowsOutBold, PiQuestion, PiSwapBold } from 'react-icons/pi';
export const CompareToolbar = memo(() => {
const { t } = useTranslation();
@@ -104,15 +104,17 @@ export const CompareToolbar = memo(() => {
}>
-
+
- }
+
+ >
+ {t('gallery.exitCompare')}
+
diff --git a/invokeai/frontend/web/src/features/gallery/components/ImageViewer/CurrentImagePreview.tsx b/invokeai/frontend/web/src/features/gallery/components/ImageViewer/CurrentImagePreview.tsx
index 193ebec361a..60c4db26e86 100644
--- a/invokeai/frontend/web/src/features/gallery/components/ImageViewer/CurrentImagePreview.tsx
+++ b/invokeai/frontend/web/src/features/gallery/components/ImageViewer/CurrentImagePreview.tsx
@@ -15,7 +15,7 @@ import { memo, useCallback, useMemo, useRef, useState } from 'react';
import { useTranslation } from 'react-i18next';
import { PiImageBold } from 'react-icons/pi';
import { useGetImageDTOQuery } from 'services/api/endpoints/images';
-import { $hasProgress } from 'services/events/setEventListeners';
+import { $hasProgress, $isProgressFromCanvas } from 'services/events/setEventListeners';
import ProgressImage from './ProgressImage';
@@ -24,6 +24,7 @@ const CurrentImagePreview = () => {
const shouldShowImageDetails = useAppSelector(selectShouldShowImageDetails);
const imageName = useAppSelector(selectLastSelectedImageName);
const hasDenoiseProgress = useStore($hasProgress);
+ const isProgressFromCanvas = useStore($isProgressFromCanvas);
const shouldShowProgressInViewer = useAppSelector(selectShouldShowProgressInViewer);
const { currentData: imageDTO } = useGetImageDTOQuery(imageName ?? skipToken);
@@ -61,7 +62,7 @@ const CurrentImagePreview = () => {
justifyContent="center"
position="relative"
>
- {hasDenoiseProgress && shouldShowProgressInViewer ? (
+ {hasDenoiseProgress && !isProgressFromCanvas && shouldShowProgressInViewer ? (
) : (
{
+import { useImageViewer } from './useImageViewer';
+
+type Props = {
+ closeButton?: ReactNode;
+};
+
+export const ImageViewer = memo(({ closeButton }: Props) => {
+ useAssertSingleton('ImageViewer');
const hasImageToCompare = useAppSelector(selectHasImageToCompare);
const [containerRef, containerDims] = useMeasure();
const ref = useRef(null);
useScopeOnFocus('imageViewer', ref);
useScopeOnMount('imageViewer');
+ useEffect(() => {
+ ref?.current?.focus();
+ }, []);
+
return (
{
justifyContent="center"
>
{hasImageToCompare && }
- {!hasImageToCompare && }
+ {!hasImageToCompare && }
{!hasImageToCompare && }
{hasImageToCompare && }
+
+
+
);
});
ImageViewer.displayName = 'ImageViewer';
+
+export const GatedImageViewer = memo(() => {
+ const imageViewer = useImageViewer();
+
+ if (!imageViewer.isOpen) {
+ return null;
+ }
+
+ return } />;
+});
+
+GatedImageViewer.displayName = 'GatedImageViewer';
+
+const ImageViewerCloseButton = memo(() => {
+ const { t } = useTranslation();
+ const imageViewer = useImageViewer();
+ useAssertSingleton('ImageViewerCloseButton');
+ useHotkeys('esc', imageViewer.close);
+ return (
+
+ );
+});
+
+ImageViewerCloseButton.displayName = 'ImageViewerCloseButton';
+
+const GatedImageViewerCloseButton = memo(() => {
+ const imageViewer = useImageViewer();
+
+ if (!imageViewer.isOpen) {
+ return null;
+ }
+
+ return ;
+});
+
+GatedImageViewerCloseButton.displayName = 'GatedImageViewerCloseButton';
diff --git a/invokeai/frontend/web/src/features/gallery/components/ImageViewer/ProgressImage.tsx b/invokeai/frontend/web/src/features/gallery/components/ImageViewer/ProgressImage.tsx
index 6626592294d..2b8013f22ea 100644
--- a/invokeai/frontend/web/src/features/gallery/components/ImageViewer/ProgressImage.tsx
+++ b/invokeai/frontend/web/src/features/gallery/components/ImageViewer/ProgressImage.tsx
@@ -5,7 +5,7 @@ import { createSelector } from '@reduxjs/toolkit';
import { useAppSelector } from 'app/store/storeHooks';
import { selectSystemSlice } from 'features/system/store/systemSlice';
import { memo, useMemo } from 'react';
-import { $progressImage } from 'services/events/setEventListeners';
+import { $isProgressFromCanvas, $progressImage } from 'services/events/setEventListeners';
const selectShouldAntialiasProgressImage = createSelector(
selectSystemSlice,
@@ -14,6 +14,7 @@ const selectShouldAntialiasProgressImage = createSelector(
const CurrentImagePreview = () => {
const progressImage = useStore($progressImage);
+ const isProgressFromCanvas = useStore($isProgressFromCanvas);
const shouldAntialiasProgressImage = useAppSelector(selectShouldAntialiasProgressImage);
const sx = useMemo(
@@ -23,7 +24,7 @@ const CurrentImagePreview = () => {
[shouldAntialiasProgressImage]
);
- if (!progressImage) {
+ if (!progressImage || isProgressFromCanvas) {
return null;
}
diff --git a/invokeai/frontend/web/src/features/gallery/components/ImageViewer/ViewerToolbar.tsx b/invokeai/frontend/web/src/features/gallery/components/ImageViewer/ViewerToolbar.tsx
index 5a66e34039a..690087c6d0e 100644
--- a/invokeai/frontend/web/src/features/gallery/components/ImageViewer/ViewerToolbar.tsx
+++ b/invokeai/frontend/web/src/features/gallery/components/ImageViewer/ViewerToolbar.tsx
@@ -1,11 +1,16 @@
import { Flex } from '@invoke-ai/ui-library';
import { ToggleMetadataViewerButton } from 'features/gallery/components/ImageViewer/ToggleMetadataViewerButton';
import { ToggleProgressButton } from 'features/gallery/components/ImageViewer/ToggleProgressButton';
+import type { ReactNode } from 'react';
import { memo } from 'react';
import CurrentImageButtons from './CurrentImageButtons';
-export const ViewerToolbar = memo(() => {
+type Props = {
+ closeButton?: ReactNode;
+};
+
+export const ViewerToolbar = memo(({ closeButton }: Props) => {
return (
@@ -18,7 +23,9 @@ export const ViewerToolbar = memo(() => {
-
+
+ {closeButton}
+
);
diff --git a/invokeai/frontend/web/src/features/gallery/components/ImageViewer/useImageViewer.ts b/invokeai/frontend/web/src/features/gallery/components/ImageViewer/useImageViewer.ts
new file mode 100644
index 00000000000..732a156e978
--- /dev/null
+++ b/invokeai/frontend/web/src/features/gallery/components/ImageViewer/useImageViewer.ts
@@ -0,0 +1,14 @@
+import { buildUseBoolean } from 'common/hooks/useBoolean';
+import { useMemo } from 'react';
+
+const [useImageViewerState, $imageViewerState] = buildUseBoolean(true);
+
+export const useImageViewer = () => {
+ const imageViewerState = useImageViewerState();
+ const isOpen = useMemo(() => imageViewerState.isTrue, [imageViewerState]);
+ const open = useMemo(() => imageViewerState.setTrue, [imageViewerState]);
+ const close = useMemo(() => imageViewerState.setFalse, [imageViewerState]);
+ const toggle = useMemo(() => imageViewerState.toggle, [imageViewerState]);
+
+ return { isOpen, open, close, toggle, $state: $imageViewerState };
+};
diff --git a/invokeai/frontend/web/src/features/gallery/store/gallerySelectors.ts b/invokeai/frontend/web/src/features/gallery/store/gallerySelectors.ts
index 8f1e8668754..c7b4daa92c9 100644
--- a/invokeai/frontend/web/src/features/gallery/store/gallerySelectors.ts
+++ b/invokeai/frontend/web/src/features/gallery/store/gallerySelectors.ts
@@ -57,4 +57,3 @@ export const selectImageToCompare = createSelector(selectGallerySlice, (gallery)
export const selectHasImageToCompare = createSelector(selectImageToCompare, (imageToCompare) =>
Boolean(imageToCompare)
);
-export const selectIsMiniViewerOpen = createSelector(selectGallerySlice, (gallery) => gallery.isMiniViewerOpen);
diff --git a/invokeai/frontend/web/src/features/gallery/store/gallerySlice.ts b/invokeai/frontend/web/src/features/gallery/store/gallerySlice.ts
index 69e8d6ac4c2..a9f380d7eb7 100644
--- a/invokeai/frontend/web/src/features/gallery/store/gallerySlice.ts
+++ b/invokeai/frontend/web/src/features/gallery/store/gallerySlice.ts
@@ -25,7 +25,6 @@ const initialGalleryState: GalleryState = {
comparisonMode: 'slider',
comparisonFit: 'fill',
shouldShowArchivedBoards: false,
- isMiniViewerOpen: false,
};
export const gallerySlice = createSlice({
@@ -88,9 +87,6 @@ export const gallerySlice = createSlice({
alwaysShowImageSizeBadgeChanged: (state, action: PayloadAction) => {
state.alwaysShowImageSizeBadge = action.payload;
},
- isMiniViewerOpenToggled: (state) => {
- state.isMiniViewerOpen = !state.isMiniViewerOpen;
- },
comparedImagesSwapped: (state) => {
if (state.imageToCompare) {
const oldSelection = state.selection;
@@ -146,7 +142,6 @@ export const {
starredFirstChanged,
shouldShowArchivedBoardsChanged,
searchTermChanged,
- isMiniViewerOpenToggled,
} = gallerySlice.actions;
export const selectGallerySlice = (state: RootState) => state.gallery;
diff --git a/invokeai/frontend/web/src/features/gallery/store/types.ts b/invokeai/frontend/web/src/features/gallery/store/types.ts
index 4ded8c494e4..48bbc8b7be1 100644
--- a/invokeai/frontend/web/src/features/gallery/store/types.ts
+++ b/invokeai/frontend/web/src/features/gallery/store/types.ts
@@ -28,5 +28,4 @@ export type GalleryState = {
comparisonMode: ComparisonMode;
comparisonFit: ComparisonFit;
shouldShowArchivedBoards: boolean;
- isMiniViewerOpen: boolean;
};
diff --git a/invokeai/frontend/web/src/features/queue/components/InvokeQueueBackButton.tsx b/invokeai/frontend/web/src/features/queue/components/InvokeQueueBackButton.tsx
index 1d58c878cf1..5b29f4b6ee9 100644
--- a/invokeai/frontend/web/src/features/queue/components/InvokeQueueBackButton.tsx
+++ b/invokeai/frontend/web/src/features/queue/components/InvokeQueueBackButton.tsx
@@ -15,7 +15,7 @@ export const InvokeQueueBackButton = memo(() => {
const isLoadingDynamicPrompts = useAppSelector(selectDynamicPromptsIsLoading);
return (
-
+
diff --git a/invokeai/frontend/web/src/features/ui/store/uiSlice.ts b/invokeai/frontend/web/src/features/ui/store/uiSlice.ts
index 1bd2ed064f0..ac1c5a52e3e 100644
--- a/invokeai/frontend/web/src/features/ui/store/uiSlice.ts
+++ b/invokeai/frontend/web/src/features/ui/store/uiSlice.ts
@@ -79,7 +79,7 @@ const TABS_WITH_LEFT_PANEL: TabName[] = ['generation', 'upscaling', 'workflows']
export const $isLeftPanelOpen = atom(true);
export const selectWithLeftPanel = createSelector(selectUiSlice, (ui) => TABS_WITH_LEFT_PANEL.includes(ui.activeTab));
-const TABS_WITH_RIGHT_PANEL: TabName[] = ['generation', 'upscaling', 'workflows', 'gallery'] as const;
+const TABS_WITH_RIGHT_PANEL: TabName[] = ['generation', 'upscaling', 'workflows'] as const;
export const RIGHT_PANEL_MIN_SIZE_PX = 390;
export const RIGHT_PANEL_MIN_SIZE_PCT = 20;
export const $isRightPanelOpen = atom(true);
diff --git a/invokeai/frontend/web/src/features/ui/store/uiTypes.ts b/invokeai/frontend/web/src/features/ui/store/uiTypes.ts
index 7306a85b324..3e6eb5a0624 100644
--- a/invokeai/frontend/web/src/features/ui/store/uiTypes.ts
+++ b/invokeai/frontend/web/src/features/ui/store/uiTypes.ts
@@ -1,4 +1,4 @@
-export type TabName = 'generation' | 'upscaling' | 'workflows' | 'models' | 'queue' | 'gallery';
+export type TabName = 'generation' | 'upscaling' | 'workflows' | 'models' | 'queue';
export interface UIState {
/**
diff --git a/invokeai/frontend/web/src/services/api/endpoints/queue.ts b/invokeai/frontend/web/src/services/api/endpoints/queue.ts
index 911154377e0..377671c745d 100644
--- a/invokeai/frontend/web/src/services/api/endpoints/queue.ts
+++ b/invokeai/frontend/web/src/services/api/endpoints/queue.ts
@@ -368,6 +368,7 @@ export const {
useListQueueItemsQuery,
useCancelQueueItemMutation,
useGetBatchStatusQuery,
+ useGetCurrentQueueItemQuery,
} = queueApi;
export const selectQueueStatus = queueApi.endpoints.getQueueStatus.select();
diff --git a/invokeai/frontend/web/src/services/events/onInvocationComplete.ts b/invokeai/frontend/web/src/services/events/onInvocationComplete.tsx
similarity index 93%
rename from invokeai/frontend/web/src/services/events/onInvocationComplete.ts
rename to invokeai/frontend/web/src/services/events/onInvocationComplete.tsx
index ec74bb79dce..32720911d5d 100644
--- a/invokeai/frontend/web/src/services/events/onInvocationComplete.ts
+++ b/invokeai/frontend/web/src/services/events/onInvocationComplete.tsx
@@ -108,12 +108,12 @@ export const buildOnInvocationComplete = (
const imageDTO = await getResultImageDTO(data);
- if (imageDTO) {
+ if (imageDTO && !imageDTO.is_intermediate) {
addImageToGallery(imageDTO);
}
};
- const handleOriginCanvas = async (data: S['InvocationCompleteEvent']) => {
+ const handleOriginGeneration = async (data: S['InvocationCompleteEvent']) => {
const imageDTO = await getResultImageDTO(data);
if (!imageDTO) {
@@ -128,19 +128,19 @@ export const buildOnInvocationComplete = (
} else if (data.result.type === 'image_output') {
dispatch(stagingAreaImageStaged({ stagingAreaImage: { imageDTO, offsetX: 0, offsetY: 0 } }));
}
+ addImageToGallery(imageDTO);
}
- } else {
+ } else if (!imageDTO.is_intermediate) {
// session.mode === 'generate'
setLastCanvasProgressEvent(null);
+ addImageToGallery(imageDTO);
}
-
- addImageToGallery(imageDTO);
};
const handleOriginOther = async (data: S['InvocationCompleteEvent']) => {
const imageDTO = await getResultImageDTO(data);
- if (imageDTO) {
+ if (imageDTO && !imageDTO.is_intermediate) {
addImageToGallery(imageDTO);
}
};
@@ -155,7 +155,7 @@ export const buildOnInvocationComplete = (
if (data.origin === 'workflows') {
await handleOriginWorkflows(data);
} else if (data.origin === 'generation') {
- await handleOriginCanvas(data);
+ await handleOriginGeneration(data);
} else {
await handleOriginOther(data);
}
diff --git a/invokeai/frontend/web/src/services/events/setEventListeners.tsx b/invokeai/frontend/web/src/services/events/setEventListeners.tsx
index 010d018ddc0..f99365da967 100644
--- a/invokeai/frontend/web/src/services/events/setEventListeners.tsx
+++ b/invokeai/frontend/web/src/services/events/setEventListeners.tsx
@@ -40,6 +40,7 @@ const nodeTypeDenylist = ['load_image', 'image'];
export const $lastProgressEvent = atom(null);
export const $hasProgress = computed($lastProgressEvent, (val) => Boolean(val));
export const $progressImage = computed($lastProgressEvent, (val) => val?.progress_image ?? null);
+export const $isProgressFromCanvas = computed($lastProgressEvent, (val) => val?.destination === 'canvas');
export const setEventListeners = ({ socket, dispatch, getState, setIsConnected }: SetEventListenersArg) => {
socket.on('connect', () => {