diff --git a/invokeai/frontend/web/public/locales/en.json b/invokeai/frontend/web/public/locales/en.json
index ff512cb48e..694a006778 100644
--- a/invokeai/frontend/web/public/locales/en.json
+++ b/invokeai/frontend/web/public/locales/en.json
@@ -164,10 +164,10 @@
"alpha": "Alpha",
"selected": "Selected",
"tab": "Tab",
- "viewing": "Viewing",
- "viewingDesc": "Review images in a large gallery view",
- "editing": "Editing",
- "editingDesc": "Edit on the Control Layers canvas",
+ "view": "View",
+ "viewDesc": "Review images in a large gallery view",
+ "edit": "Edit",
+ "editDesc": "Edit on the Canvas",
"comparing": "Comparing",
"comparingDesc": "Comparing two images",
"enabled": "Enabled",
@@ -328,9 +328,13 @@
"completedIn": "Completed in",
"batch": "Batch",
"origin": "Origin",
- "originCanvas": "Canvas",
- "originWorkflows": "Workflows",
- "originOther": "Other",
+ "destination": "Destination",
+ "upscaling": "Upscaling",
+ "canvas": "Canvas",
+ "generation": "Generation",
+ "workflows": "Workflows",
+ "other": "Other",
+ "gallery": "Gallery",
"batchFieldValues": "Batch Field Values",
"item": "Item",
"session": "Session",
@@ -1695,6 +1699,10 @@
"inpaintMask": "Inpaint Mask",
"regionalGuidance": "Regional Guidance",
"ipAdapter": "IP Adapter",
+ "sendToGallery": "Send To Gallery",
+ "sendToGalleryDesc": "Generations will be sent to the gallery.",
+ "sendToCanvas": "Send To Canvas",
+ "sendToCanvasDesc": "Generations will be staged onto the canvas.",
"rasterLayer_withCount_one": "$t(controlLayers.rasterLayer)",
"controlLayer_withCount_one": "$t(controlLayers.controlLayer)",
"inpaintMask_withCount_one": "$t(controlLayers.inpaintMask)",
diff --git a/invokeai/frontend/web/src/app/store/middleware/listenerMiddleware/listeners/enqueueRequestedLinear.ts b/invokeai/frontend/web/src/app/store/middleware/listenerMiddleware/listeners/enqueueRequestedLinear.ts
index 89777f5081..afd9489bc5 100644
--- a/invokeai/frontend/web/src/app/store/middleware/listenerMiddleware/listeners/enqueueRequestedLinear.ts
+++ b/invokeai/frontend/web/src/app/store/middleware/listenerMiddleware/listeners/enqueueRequestedLinear.ts
@@ -31,7 +31,7 @@ export const addEnqueueRequestedLinear = (startAppListening: AppStartListening)
let didStartStaging = false;
- if (!state.canvasSession.isStaging && state.canvasSession.mode === 'compose') {
+ if (!state.canvasSession.isStaging && state.canvasSession.sendToCanvas) {
dispatch(sessionStartedStaging());
didStartStaging = true;
}
@@ -70,7 +70,11 @@ export const addEnqueueRequestedLinear = (startAppListening: AppStartListening)
const { g, noise, posCond } = buildGraphResult.value;
- const prepareBatchResult = withResult(() => prepareLinearUIBatch(state, g, prepend, noise, posCond));
+ const destination = state.canvasSession.sendToCanvas ? 'canvas' : 'gallery';
+
+ const prepareBatchResult = withResult(() =>
+ prepareLinearUIBatch(state, g, prepend, noise, posCond, 'generation', destination)
+ );
if (isErr(prepareBatchResult)) {
log.error({ error: serializeError(prepareBatchResult.error) }, 'Failed to prepare batch');
diff --git a/invokeai/frontend/web/src/app/store/middleware/listenerMiddleware/listeners/enqueueRequestedNodes.ts b/invokeai/frontend/web/src/app/store/middleware/listenerMiddleware/listeners/enqueueRequestedNodes.ts
index 847c64f3c3..42cd591e0c 100644
--- a/invokeai/frontend/web/src/app/store/middleware/listenerMiddleware/listeners/enqueueRequestedNodes.ts
+++ b/invokeai/frontend/web/src/app/store/middleware/listenerMiddleware/listeners/enqueueRequestedNodes.ts
@@ -32,6 +32,7 @@ export const addEnqueueRequestedNodes = (startAppListening: AppStartListening) =
workflow: builtWorkflow,
runs: state.params.iterations,
origin: 'workflows',
+ destination: 'gallery',
},
prepend: action.payload.prepend,
};
diff --git a/invokeai/frontend/web/src/app/store/middleware/listenerMiddleware/listeners/enqueueRequestedUpscale.ts b/invokeai/frontend/web/src/app/store/middleware/listenerMiddleware/listeners/enqueueRequestedUpscale.ts
index 959a1bf5ae..cbfaac6227 100644
--- a/invokeai/frontend/web/src/app/store/middleware/listenerMiddleware/listeners/enqueueRequestedUpscale.ts
+++ b/invokeai/frontend/web/src/app/store/middleware/listenerMiddleware/listeners/enqueueRequestedUpscale.ts
@@ -16,7 +16,7 @@ export const addEnqueueRequestedUpscale = (startAppListening: AppStartListening)
const { g, noise, posCond } = await buildMultidiffusionUpscaleGraph(state);
- const batchConfig = prepareLinearUIBatch(state, g, prepend, noise, posCond);
+ const batchConfig = prepareLinearUIBatch(state, g, prepend, noise, posCond, 'upscaling', 'gallery');
const req = dispatch(
queueApi.endpoints.enqueueBatch.initiate(batchConfig, {
diff --git a/invokeai/frontend/web/src/common/components/IconSwitch.tsx b/invokeai/frontend/web/src/common/components/IconSwitch.tsx
new file mode 100644
index 0000000000..43cf6d2f36
--- /dev/null
+++ b/invokeai/frontend/web/src/common/components/IconSwitch.tsx
@@ -0,0 +1,104 @@
+import type { SystemStyleObject } from '@invoke-ai/ui-library';
+import { Box, Flex, IconButton, Tooltip, useToken } from '@invoke-ai/ui-library';
+import type { ReactElement, ReactNode } from 'react';
+import { memo, useCallback, useMemo } from 'react';
+
+type IconSwitchProps = {
+ isChecked: boolean;
+ onChange: (checked: boolean) => void;
+ iconChecked: ReactElement;
+ tooltipChecked?: ReactNode;
+ iconUnchecked: ReactElement;
+ tooltipUnchecked?: ReactNode;
+ ariaLabel: string;
+};
+
+const getSx = (padding: string | number): SystemStyleObject => ({
+ transition: 'left 0.1s ease-in-out, transform 0.1s ease-in-out',
+ '&[data-checked="true"]': {
+ left: `calc(100% - ${padding})`,
+ transform: 'translateX(-100%)',
+ },
+ '&[data-checked="false"]': {
+ left: padding,
+ transform: 'translateX(0)',
+ },
+});
+
+export const IconSwitch = memo(
+ ({
+ isChecked,
+ onChange,
+ iconChecked,
+ tooltipChecked,
+ iconUnchecked,
+ tooltipUnchecked,
+ ariaLabel,
+ }: IconSwitchProps) => {
+ const onUncheck = useCallback(() => {
+ onChange(false);
+ }, [onChange]);
+ const onCheck = useCallback(() => {
+ onChange(true);
+ }, [onChange]);
+
+ const gap = useToken('space', 1.5);
+ const sx = useMemo(() => getSx(gap), [gap]);
+
+ return (
+
+
+
+
+
+
+
+
+
+ );
+ }
+);
+
+IconSwitch.displayName = 'IconSwitch';
diff --git a/invokeai/frontend/web/src/features/controlLayers/components/CanvasModeSwitcher.tsx b/invokeai/frontend/web/src/features/controlLayers/components/CanvasModeSwitcher.tsx
deleted file mode 100644
index e052c1a214..0000000000
--- a/invokeai/frontend/web/src/features/controlLayers/components/CanvasModeSwitcher.tsx
+++ /dev/null
@@ -1,29 +0,0 @@
-import { Button, ButtonGroup } from '@invoke-ai/ui-library';
-import { createSelector } from '@reduxjs/toolkit';
-import { useAppDispatch, useAppSelector } from 'app/store/storeHooks';
-import { selectCanvasSessionSlice, sessionModeChanged } from 'features/controlLayers/store/canvasSessionSlice';
-import { memo, useCallback } from 'react';
-import { useTranslation } from 'react-i18next';
-
-const selectCanvasMode = createSelector(selectCanvasSessionSlice, (canvasSession) => canvasSession.mode);
-
-export const CanvasModeSwitcher = memo(() => {
- const { t } = useTranslation();
- const dispatch = useAppDispatch();
- const mode = useAppSelector(selectCanvasMode);
- const onClickGenerate = useCallback(() => dispatch(sessionModeChanged({ mode: 'generate' })), [dispatch]);
- const onClickCompose = useCallback(() => dispatch(sessionModeChanged({ mode: 'compose' })), [dispatch]);
-
- return (
-
-
-
-
- );
-});
-
-CanvasModeSwitcher.displayName = 'CanvasModeSwitcher';
diff --git a/invokeai/frontend/web/src/features/controlLayers/components/CanvasSendToToggle.tsx b/invokeai/frontend/web/src/features/controlLayers/components/CanvasSendToToggle.tsx
new file mode 100644
index 0000000000..4d572beccb
--- /dev/null
+++ b/invokeai/frontend/web/src/features/controlLayers/components/CanvasSendToToggle.tsx
@@ -0,0 +1,59 @@
+import { Flex, Text } from '@invoke-ai/ui-library';
+import { useAppDispatch, useAppSelector } from 'app/store/storeHooks';
+import { IconSwitch } from 'common/components/IconSwitch';
+import { selectIsComposing, sessionSendToCanvasChanged } from 'features/controlLayers/store/canvasSessionSlice';
+import { memo, useCallback } from 'react';
+import { useTranslation } from 'react-i18next';
+import { PiImageBold, PiPaintBrushBold } from 'react-icons/pi';
+
+const TooltipSendToGallery = memo(() => {
+ const { t } = useTranslation();
+
+ return (
+
+ {t('controlLayers.sendToGallery')}
+ {t('controlLayers.sendToGalleryDesc')}
+
+ );
+});
+
+TooltipSendToGallery.displayName = 'TooltipSendToGallery';
+
+const TooltipSendToCanvas = memo(() => {
+ const { t } = useTranslation();
+
+ return (
+
+ {t('controlLayers.sendToCanvas')}
+ {t('controlLayers.sendToCanvasDesc')}
+
+ );
+});
+
+TooltipSendToCanvas.displayName = 'TooltipSendToCanvas';
+
+export const CanvasSendToToggle = memo(() => {
+ const dispatch = useAppDispatch();
+ const isComposing = useAppSelector(selectIsComposing);
+
+ const onChange = useCallback(
+ (isChecked: boolean) => {
+ dispatch(sessionSendToCanvasChanged(isChecked));
+ },
+ [dispatch]
+ );
+
+ return (
+ }
+ tooltipUnchecked={}
+ iconChecked={}
+ tooltipChecked={}
+ ariaLabel="Toggle canvas mode"
+ />
+ );
+});
+
+CanvasSendToToggle.displayName = 'CanvasSendToToggle';
diff --git a/invokeai/frontend/web/src/features/controlLayers/components/ControlLayersToolbar.tsx b/invokeai/frontend/web/src/features/controlLayers/components/ControlLayersToolbar.tsx
index 56133a5749..19ffcc25d7 100644
--- a/invokeai/frontend/web/src/features/controlLayers/components/ControlLayersToolbar.tsx
+++ b/invokeai/frontend/web/src/features/controlLayers/components/ControlLayersToolbar.tsx
@@ -1,6 +1,5 @@
/* eslint-disable i18next/no-literal-string */
import { Flex, Spacer } from '@invoke-ai/ui-library';
-import { CanvasModeSwitcher } from 'features/controlLayers/components/CanvasModeSwitcher';
import { CanvasResetViewButton } from 'features/controlLayers/components/CanvasResetViewButton';
import { CanvasScale } from 'features/controlLayers/components/CanvasScale';
import { CanvasSettingsPopover } from 'features/controlLayers/components/Settings/CanvasSettingsPopover';
@@ -28,7 +27,6 @@ export const ControlLayersToolbar = memo(() => {
-
diff --git a/invokeai/frontend/web/src/features/controlLayers/store/canvasSessionSlice.ts b/invokeai/frontend/web/src/features/controlLayers/store/canvasSessionSlice.ts
index 6947ffd2df..e115b6800b 100644
--- a/invokeai/frontend/web/src/features/controlLayers/store/canvasSessionSlice.ts
+++ b/invokeai/frontend/web/src/features/controlLayers/store/canvasSessionSlice.ts
@@ -1,17 +1,17 @@
import { createAction, createSelector, createSlice, type PayloadAction } from '@reduxjs/toolkit';
import type { PersistConfig, RootState } from 'app/store/store';
import { canvasSlice } from 'features/controlLayers/store/canvasSlice';
-import type { SessionMode, StagingAreaImage } from 'features/controlLayers/store/types';
+import type { StagingAreaImage } from 'features/controlLayers/store/types';
export type CanvasSessionState = {
- mode: SessionMode;
+ sendToCanvas: boolean;
isStaging: boolean;
stagedImages: StagingAreaImage[];
selectedStagedImageIndex: number;
};
const initialState: CanvasSessionState = {
- mode: 'generate',
+ sendToCanvas: false,
isStaging: false,
stagedImages: [],
selectedStagedImageIndex: 0,
@@ -27,6 +27,7 @@ export const canvasSessionSlice = createSlice({
},
sessionImageStaged: (state, action: PayloadAction<{ stagingAreaImage: StagingAreaImage }>) => {
const { stagingAreaImage } = action.payload;
+ state.isStaging = true;
state.stagedImages.push(stagingAreaImage);
state.selectedStagedImageIndex = state.stagedImages.length - 1;
},
@@ -50,9 +51,8 @@ export const canvasSessionSlice = createSlice({
state.stagedImages = [];
state.selectedStagedImageIndex = 0;
},
- sessionModeChanged: (state, action: PayloadAction<{ mode: SessionMode }>) => {
- const { mode } = action.payload;
- state.mode = mode;
+ sessionSendToCanvasChanged: (state, action: PayloadAction) => {
+ state.sendToCanvas = action.payload;
},
},
});
@@ -64,7 +64,7 @@ export const {
sessionStagingAreaReset,
sessionNextStagedImageSelected,
sessionPrevStagedImageSelected,
- sessionModeChanged,
+ sessionSendToCanvasChanged,
} = canvasSessionSlice.actions;
/* eslint-disable-next-line @typescript-eslint/no-explicit-any */
@@ -85,3 +85,7 @@ export const sessionStagingAreaImageAccepted = createAction<{ index: number }>(
export const selectCanvasSessionSlice = (s: RootState) => s.canvasSession;
export const selectIsStaging = createSelector(selectCanvasSessionSlice, (canvasSession) => canvasSession.isStaging);
+export const selectIsComposing = createSelector(
+ selectCanvasSessionSlice,
+ (canvasSession) => canvasSession.sendToCanvas
+);
diff --git a/invokeai/frontend/web/src/features/gallery/components/ImageViewer/ViewerToggleMenu.tsx b/invokeai/frontend/web/src/features/gallery/components/ImageViewer/ViewerToggleMenu.tsx
index c37a460b12..71b94db445 100644
--- a/invokeai/frontend/web/src/features/gallery/components/ImageViewer/ViewerToggleMenu.tsx
+++ b/invokeai/frontend/web/src/features/gallery/components/ImageViewer/ViewerToggleMenu.tsx
@@ -1,57 +1,60 @@
-import { ButtonGroup, Flex, IconButton, Text, Tooltip } from '@invoke-ai/ui-library';
+import { Flex, Text } from '@invoke-ai/ui-library';
+import { IconSwitch } from 'common/components/IconSwitch';
import { useImageViewer } from 'features/gallery/components/ImageViewer/useImageViewer';
-import { memo } from 'react';
+import { memo, useCallback } from 'react';
import { useHotkeys } from 'react-hotkeys-hook';
import { useTranslation } from 'react-i18next';
import { PiEyeBold, PiPencilBold } from 'react-icons/pi';
-export const ViewerToggle = memo(() => {
+const TooltipEdit = memo(() => {
const { t } = useTranslation();
+
+ return (
+
+ {t('common.edit')}
+ {t('common.editDesc')}
+
+ );
+});
+TooltipEdit.displayName = 'TooltipEdit';
+
+const TooltipView = memo(() => {
+ const { t } = useTranslation();
+
+ return (
+
+ {t('common.view')}
+ {t('common.viewDesc')}
+
+ );
+});
+TooltipView.displayName = 'TooltipView';
+
+export const ViewerToggle = memo(() => {
const imageViewer = useImageViewer();
useHotkeys('z', imageViewer.onToggle, [imageViewer]);
useHotkeys('esc', imageViewer.onClose, [imageViewer]);
+ const onChange = useCallback(
+ (isChecked: boolean) => {
+ if (isChecked) {
+ imageViewer.onClose();
+ } else {
+ imageViewer.onOpen();
+ }
+ },
+ [imageViewer]
+ );
return (
-
-
-
- {t('common.viewing')}
- {t('common.viewingDesc')}
-
- }
- >
- }
- onClick={imageViewer.onOpen}
- variant={imageViewer.isOpen ? 'solid' : 'outline'}
- colorScheme={imageViewer.isOpen ? 'invokeBlue' : 'base'}
- aria-label={t('common.viewing')}
- w={12}
- />
-
-
- {t('common.editing')}
- {t('common.editingDesc')}
-
- }
- >
- }
- onClick={imageViewer.onClose}
- variant={!imageViewer.isOpen ? 'solid' : 'outline'}
- colorScheme={!imageViewer.isOpen ? 'invokeBlue' : 'base'}
- aria-label={t('common.editing')}
- w={12}
- />
-
-
-
+ }
+ tooltipUnchecked={}
+ iconChecked={}
+ tooltipChecked={}
+ ariaLabel="Toggle viewer"
+ />
);
});
diff --git a/invokeai/frontend/web/src/features/nodes/components/sidePanel/NodeEditorPanelGroup.tsx b/invokeai/frontend/web/src/features/nodes/components/sidePanel/NodeEditorPanelGroup.tsx
index 0fd1bb6ea7..6b7f019f89 100644
--- a/invokeai/frontend/web/src/features/nodes/components/sidePanel/NodeEditorPanelGroup.tsx
+++ b/invokeai/frontend/web/src/features/nodes/components/sidePanel/NodeEditorPanelGroup.tsx
@@ -3,7 +3,6 @@ import 'reactflow/dist/style.css';
import { Flex } from '@invoke-ai/ui-library';
import { useAppSelector } from 'app/store/storeHooks';
import { selectWorkflowMode } from 'features/nodes/store/workflowSlice';
-import QueueControls from 'features/queue/components/QueueControls';
import ResizeHandle from 'features/ui/components/tabs/ResizeHandle';
import { usePanelStorage } from 'features/ui/hooks/usePanelStorage';
import WorkflowLibraryButton from 'features/workflowLibrary/components/WorkflowLibraryButton';
@@ -34,7 +33,6 @@ const NodeEditorPanelGroup = () => {
return (
-
diff --git a/invokeai/frontend/web/src/features/nodes/util/graph/buildLinearBatchConfig.ts b/invokeai/frontend/web/src/features/nodes/util/graph/buildLinearBatchConfig.ts
index 4262298951..6253dc30dc 100644
--- a/invokeai/frontend/web/src/features/nodes/util/graph/buildLinearBatchConfig.ts
+++ b/invokeai/frontend/web/src/features/nodes/util/graph/buildLinearBatchConfig.ts
@@ -10,7 +10,9 @@ export const prepareLinearUIBatch = (
g: Graph,
prepend: boolean,
noise: Invocation<'noise'>,
- posCond: Invocation<'compel' | 'sdxl_compel_prompt'>
+ posCond: Invocation<'compel' | 'sdxl_compel_prompt'>,
+ origin: 'generation' | 'workflows' | 'upscaling',
+ destination: 'canvas' | 'gallery'
): BatchConfig => {
const { iterations, model, shouldRandomizeSeed, seed, shouldConcatPrompts } = state.params;
const { prompts, seedBehaviour } = state.dynamicPrompts;
@@ -103,7 +105,8 @@ export const prepareLinearUIBatch = (
graph: g.getGraph(),
runs: 1,
data,
- origin: 'canvas',
+ origin,
+ destination,
},
};
diff --git a/invokeai/frontend/web/src/features/nodes/util/graph/generation/addInpaint.ts b/invokeai/frontend/web/src/features/nodes/util/graph/generation/addInpaint.ts
index f9fa00c5d6..c69820bd49 100644
--- a/invokeai/frontend/web/src/features/nodes/util/graph/generation/addInpaint.ts
+++ b/invokeai/frontend/web/src/features/nodes/util/graph/generation/addInpaint.ts
@@ -29,7 +29,7 @@ export const addInpaint = async (
const canvas = selectCanvasSlice(state);
const { bbox } = canvas;
- const { mode } = canvasSession;
+ const { sendToCanvas: isComposing } = canvasSession;
const initialImage = await manager.compositor.getCompositeRasterLayerImageDTO(bbox.rect);
const maskImage = await manager.compositor.getCompositeInpaintMaskImageDTO(bbox.rect);
@@ -99,7 +99,7 @@ export const addInpaint = async (
g.addEdge(resizeImageToOriginalSize, 'image', canvasPasteBack, 'generated_image');
g.addEdge(resizeMaskToOriginalSize, 'image', canvasPasteBack, 'mask');
- if (mode === 'generate') {
+ if (!isComposing) {
canvasPasteBack.source_image = { image_name: initialImage.image_name };
}
@@ -143,7 +143,7 @@ export const addInpaint = async (
g.addEdge(l2i, 'image', canvasPasteBack, 'generated_image');
- if (mode === 'generate') {
+ if (!isComposing) {
canvasPasteBack.source_image = { image_name: initialImage.image_name };
}
diff --git a/invokeai/frontend/web/src/features/nodes/util/graph/generation/addOutpaint.ts b/invokeai/frontend/web/src/features/nodes/util/graph/generation/addOutpaint.ts
index ecbc09f916..80cdf5d53d 100644
--- a/invokeai/frontend/web/src/features/nodes/util/graph/generation/addOutpaint.ts
+++ b/invokeai/frontend/web/src/features/nodes/util/graph/generation/addOutpaint.ts
@@ -30,7 +30,7 @@ export const addOutpaint = async (
const canvas = selectCanvasSlice(state);
const { bbox } = canvas;
- const { mode } = canvasSession;
+ const { sendToCanvas: isComposing } = canvasSession;
const initialImage = await manager.compositor.getCompositeRasterLayerImageDTO(bbox.rect);
const maskImage = await manager.compositor.getCompositeInpaintMaskImageDTO(bbox.rect);
@@ -123,7 +123,7 @@ export const addOutpaint = async (
g.addEdge(resizeOutputImageToOriginalSize, 'image', canvasPasteBack, 'generated_image');
g.addEdge(resizeOutputMaskToOriginalSize, 'image', canvasPasteBack, 'mask');
- if (mode === 'generate') {
+ if (!isComposing) {
canvasPasteBack.source_image = { image_name: initialImage.image_name };
}
@@ -173,7 +173,7 @@ export const addOutpaint = async (
g.addEdge(createGradientMask, 'expanded_mask_area', canvasPasteBack, 'mask');
g.addEdge(l2i, 'image', canvasPasteBack, 'generated_image');
- if (mode === 'generate') {
+ if (!isComposing) {
canvasPasteBack.source_image = { image_name: initialImage.image_name };
}
diff --git a/invokeai/frontend/web/src/features/nodes/util/graph/generation/buildSD1Graph.ts b/invokeai/frontend/web/src/features/nodes/util/graph/generation/buildSD1Graph.ts
index 112fb20a60..693b194e52 100644
--- a/invokeai/frontend/web/src/features/nodes/util/graph/generation/buildSD1Graph.ts
+++ b/invokeai/frontend/web/src/features/nodes/util/graph/generation/buildSD1Graph.ts
@@ -282,7 +282,7 @@ export const buildSD1Graph = async (
canvasOutput = addWatermarker(g, canvasOutput);
}
- const shouldSaveToGallery = canvasSession.mode === 'generate' || canvasSettings.autoSave;
+ const shouldSaveToGallery = !canvasSession.sendToCanvas || canvasSettings.autoSave;
g.updateNode(canvasOutput, {
id: getPrefixedId('canvas_output'),
diff --git a/invokeai/frontend/web/src/features/nodes/util/graph/generation/buildSDXLGraph.ts b/invokeai/frontend/web/src/features/nodes/util/graph/generation/buildSDXLGraph.ts
index ddf8da0dcd..3fbf2f2f56 100644
--- a/invokeai/frontend/web/src/features/nodes/util/graph/generation/buildSDXLGraph.ts
+++ b/invokeai/frontend/web/src/features/nodes/util/graph/generation/buildSDXLGraph.ts
@@ -285,7 +285,7 @@ export const buildSDXLGraph = async (
canvasOutput = addWatermarker(g, canvasOutput);
}
- const shouldSaveToGallery = canvasSession.mode === 'generate' || canvasSettings.autoSave;
+ const shouldSaveToGallery = !canvasSession.sendToCanvas || canvasSettings.autoSave;
g.updateNode(canvasOutput, {
id: getPrefixedId('canvas_output'),
diff --git a/invokeai/frontend/web/src/features/queue/components/ClearQueueIconButton.tsx b/invokeai/frontend/web/src/features/queue/components/ClearQueueIconButton.tsx
index 39a84c0216..66d445e7c3 100644
--- a/invokeai/frontend/web/src/features/queue/components/ClearQueueIconButton.tsx
+++ b/invokeai/frontend/web/src/features/queue/components/ClearQueueIconButton.tsx
@@ -1,67 +1,39 @@
-import type { IconButtonProps } from '@invoke-ai/ui-library';
import { IconButton, useShiftModifier } from '@invoke-ai/ui-library';
-import { useClearQueueConfirmationAlertDialog } from 'features/queue/components/ClearQueueConfirmationAlertDialog';
+import { QueueCountBadge } from 'features/queue/components/QueueCountBadge';
import { useCancelCurrentQueueItem } from 'features/queue/hooks/useCancelCurrentQueueItem';
import { useClearQueue } from 'features/queue/hooks/useClearQueue';
-import { memo } from 'react';
+import { memo, useRef } from 'react';
import { useTranslation } from 'react-i18next';
import { PiTrashSimpleBold, PiXBold } from 'react-icons/pi';
-type ClearQueueButtonProps = Omit;
-
-export const ClearAllQueueIconButton = memo((props: ClearQueueButtonProps) => {
+export const ClearQueueIconButton = memo((_) => {
+ const ref = useRef(null);
const { t } = useTranslation();
- const dialogState = useClearQueueConfirmationAlertDialog();
- const { isLoading, isDisabled } = useClearQueue();
+ const clearQueue = useClearQueue();
+ const cancelCurrentQueueItem = useCancelCurrentQueueItem();
- return (
- }
- colorScheme="error"
- onClick={dialogState.setTrue}
- data-testid={t('queue.clear')}
- {...props}
- />
- );
-});
-
-ClearAllQueueIconButton.displayName = 'ClearAllQueueIconButton';
-
-const ClearSingleQueueItemIconButton = memo((props: ClearQueueButtonProps) => {
- const { t } = useTranslation();
- const { cancelQueueItem, isLoading, isDisabled } = useCancelCurrentQueueItem();
-
- return (
- }
- colorScheme="error"
- onClick={cancelQueueItem}
- data-testid={t('queue.cancel')}
- {...props}
- />
- );
-});
-
-ClearSingleQueueItemIconButton.displayName = 'ClearSingleQueueItemIconButton';
-
-export const ClearQueueIconButton = memo((props: ClearQueueButtonProps) => {
// Show the single item clear button when shift is pressed
// Otherwise show the clear queue button
const shift = useShiftModifier();
- if (shift) {
- return ;
- }
-
- return ;
+ return (
+ <>
+ : }
+ colorScheme="error"
+ onClick={shift ? clearQueue.openDialog : cancelCurrentQueueItem.cancelQueueItem}
+ data-testid={shift ? t('queue.clear') : t('queue.cancel')}
+ />
+ {/* The badge is dynamically positioned, needs a ref to the target element */}
+
+ >
+ );
});
ClearQueueIconButton.displayName = 'ClearQueueIconButton';
diff --git a/invokeai/frontend/web/src/features/queue/components/InvokeQueueBackButton.tsx b/invokeai/frontend/web/src/features/queue/components/InvokeQueueBackButton.tsx
index 08950671a6..68356c2837 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/queue/components/QueueCountBadge.tsx b/invokeai/frontend/web/src/features/queue/components/QueueCountBadge.tsx
new file mode 100644
index 0000000000..ea7a05b8a9
--- /dev/null
+++ b/invokeai/frontend/web/src/features/queue/components/QueueCountBadge.tsx
@@ -0,0 +1,77 @@
+import { Badge, Portal } from '@invoke-ai/ui-library';
+import { useStore } from '@nanostores/react';
+import { $isParametersPanelOpen } from 'features/ui/store/uiSlice';
+import type { RefObject } from 'react';
+import { memo, useEffect, useState } from 'react';
+import { useGetQueueStatusQuery } from 'services/api/endpoints/queue';
+
+type Props = {
+ targetRef: RefObject;
+};
+
+export const QueueCountBadge = memo(({ targetRef }: Props) => {
+ const [badgePos, setBadgePos] = useState<{ x: string; y: string } | null>(null);
+ const isParametersPanelOpen = useStore($isParametersPanelOpen);
+ const { queueSize } = useGetQueueStatusQuery(undefined, {
+ selectFromResult: (res) => ({
+ queueSize: res.data ? res.data.queue.pending + res.data.queue.in_progress : 0,
+ }),
+ });
+
+ useEffect(() => {
+ if (!targetRef.current) {
+ return;
+ }
+
+ const target = targetRef.current;
+ const parent = target.parentElement;
+
+ if (!parent) {
+ return;
+ }
+
+ const cb = () => {
+ if (!$isParametersPanelOpen.get()) {
+ return;
+ }
+ const { x, y } = target.getBoundingClientRect();
+ setBadgePos({ x: `${x - 7}px`, y: `${y - 5}px` });
+ };
+
+ const resizeObserver = new ResizeObserver(cb);
+ resizeObserver.observe(parent);
+ cb();
+
+ return () => {
+ resizeObserver.disconnect();
+ };
+ }, [targetRef]);
+
+ if (queueSize === 0) {
+ return null;
+ }
+ if (!badgePos) {
+ return null;
+ }
+ if (!isParametersPanelOpen) {
+ return null;
+ }
+
+ return (
+
+
+ {queueSize}
+
+
+ );
+});
+
+QueueCountBadge.displayName = 'QueueCountBadge';
diff --git a/invokeai/frontend/web/src/features/queue/components/QueueList/QueueItemComponent.tsx b/invokeai/frontend/web/src/features/queue/components/QueueList/QueueItemComponent.tsx
index f15e724527..a4a1b41a9d 100644
--- a/invokeai/frontend/web/src/features/queue/components/QueueList/QueueItemComponent.tsx
+++ b/invokeai/frontend/web/src/features/queue/components/QueueList/QueueItemComponent.tsx
@@ -1,6 +1,7 @@
import type { ChakraProps, CollapseProps } from '@invoke-ai/ui-library';
import { ButtonGroup, Collapse, Flex, IconButton, Text } from '@invoke-ai/ui-library';
import QueueStatusBadge from 'features/queue/components/common/QueueStatusBadge';
+import { useDestinationText } from 'features/queue/components/QueueList/useDestinationText';
import { useOriginText } from 'features/queue/components/QueueList/useOriginText';
import { useCancelQueueItem } from 'features/queue/hooks/useCancelQueueItem';
import { getSecondsFromTimestamps } from 'features/queue/util/getSecondsFromTimestamps';
@@ -52,6 +53,7 @@ const QueueItemComponent = ({ index, item, context }: InnerItemProps) => {
const isCanceled = useMemo(() => ['canceled', 'completed', 'failed'].includes(item.status), [item.status]);
const originText = useOriginText(item.origin);
+ const destinationText = useDestinationText(item.destination);
const icon = useMemo(() => , []);
return (
@@ -76,6 +78,11 @@ const QueueItemComponent = ({ index, item, context }: InnerItemProps) => {
{originText}
+
+
+ {destinationText}
+
+
{executionTime || '-'}
diff --git a/invokeai/frontend/web/src/features/queue/components/QueueList/QueueItemDetail.tsx b/invokeai/frontend/web/src/features/queue/components/QueueList/QueueItemDetail.tsx
index 23d304cc84..ee05039f5a 100644
--- a/invokeai/frontend/web/src/features/queue/components/QueueList/QueueItemDetail.tsx
+++ b/invokeai/frontend/web/src/features/queue/components/QueueList/QueueItemDetail.tsx
@@ -1,5 +1,6 @@
import { Button, ButtonGroup, Flex, Heading, Spinner, Text } from '@invoke-ai/ui-library';
import DataViewer from 'features/gallery/components/ImageMetadataViewer/DataViewer';
+import { useDestinationText } from 'features/queue/components/QueueList/useDestinationText';
import { useOriginText } from 'features/queue/components/QueueList/useOriginText';
import { useCancelBatch } from 'features/queue/hooks/useCancelBatch';
import { useCancelQueueItem } from 'features/queue/hooks/useCancelQueueItem';
@@ -17,7 +18,7 @@ type Props = {
};
const QueueItemComponent = ({ queueItemDTO }: Props) => {
- const { session_id, batch_id, item_id, origin } = queueItemDTO;
+ const { session_id, batch_id, item_id, origin, destination } = queueItemDTO;
const { t } = useTranslation();
const { cancelBatch, isLoading: isLoadingCancelBatch, isCanceled } = useCancelBatch(batch_id);
@@ -26,6 +27,7 @@ const QueueItemComponent = ({ queueItemDTO }: Props) => {
const { data: queueItem } = useGetQueueItemQuery(item_id);
const originText = useOriginText(origin);
+ const destinationText = useDestinationText(destination);
const statusAndTiming = useMemo(() => {
if (!queueItem) {
@@ -54,6 +56,7 @@ const QueueItemComponent = ({ queueItemDTO }: Props) => {
>
+
diff --git a/invokeai/frontend/web/src/features/queue/components/QueueList/QueueListHeader.tsx b/invokeai/frontend/web/src/features/queue/components/QueueList/QueueListHeader.tsx
index acfa57d710..1f0dc5eaf0 100644
--- a/invokeai/frontend/web/src/features/queue/components/QueueList/QueueListHeader.tsx
+++ b/invokeai/frontend/web/src/features/queue/components/QueueList/QueueListHeader.tsx
@@ -25,6 +25,9 @@ const QueueListHeader = () => {
{t('queue.origin')}
+
+ {t('queue.destination')}
+
{t('queue.time')}
diff --git a/invokeai/frontend/web/src/features/queue/components/QueueList/constants.ts b/invokeai/frontend/web/src/features/queue/components/QueueList/constants.ts
index c4c48d177d..1f66622857 100644
--- a/invokeai/frontend/web/src/features/queue/components/QueueList/constants.ts
+++ b/invokeai/frontend/web/src/features/queue/components/QueueList/constants.ts
@@ -4,7 +4,8 @@ export const COLUMN_WIDTHS = {
statusDot: 2,
time: '4rem',
origin: '5rem',
+ destination: '6rem',
batchId: '5rem',
fieldValues: 'auto',
actions: 'auto',
-};
+} as const;
diff --git a/invokeai/frontend/web/src/features/queue/components/QueueList/useDestinationText.ts b/invokeai/frontend/web/src/features/queue/components/QueueList/useDestinationText.ts
new file mode 100644
index 0000000000..e72d1d709c
--- /dev/null
+++ b/invokeai/frontend/web/src/features/queue/components/QueueList/useDestinationText.ts
@@ -0,0 +1,16 @@
+import { useTranslation } from 'react-i18next';
+import type { SessionQueueItemDTO } from 'services/api/types';
+
+export const useDestinationText = (destination: SessionQueueItemDTO['destination']) => {
+ const { t } = useTranslation();
+
+ if (destination === 'canvas') {
+ return t('queue.canvas');
+ }
+
+ if (destination === 'gallery') {
+ return t('queue.gallery');
+ }
+
+ return t('queue.other');
+};
diff --git a/invokeai/frontend/web/src/features/queue/components/QueueList/useOriginText.ts b/invokeai/frontend/web/src/features/queue/components/QueueList/useOriginText.ts
index e4f2b34f4b..8a818a0bc8 100644
--- a/invokeai/frontend/web/src/features/queue/components/QueueList/useOriginText.ts
+++ b/invokeai/frontend/web/src/features/queue/components/QueueList/useOriginText.ts
@@ -4,13 +4,13 @@ import type { SessionQueueItemDTO } from 'services/api/types';
export const useOriginText = (origin: SessionQueueItemDTO['origin']) => {
const { t } = useTranslation();
- if (origin === 'canvas') {
- return t('queue.originCanvas');
+ if (origin === 'generation') {
+ return t('queue.generation');
}
if (origin === 'workflows') {
- return t('queue.originWorkflows');
+ return t('queue.workflows');
}
- return t('queue.originOther');
+ return t('queue.other');
};
diff --git a/invokeai/frontend/web/src/features/queue/hooks/useClearQueue.ts b/invokeai/frontend/web/src/features/queue/hooks/useClearQueue.ts
index bb80f7aa10..24a4d39d95 100644
--- a/invokeai/frontend/web/src/features/queue/hooks/useClearQueue.ts
+++ b/invokeai/frontend/web/src/features/queue/hooks/useClearQueue.ts
@@ -1,6 +1,7 @@
import { useStore } from '@nanostores/react';
import { $isConnected } from 'app/hooks/useSocketIO';
import { useAppDispatch } from 'app/store/storeHooks';
+import { useClearQueueConfirmationAlertDialog } from 'features/queue/components/ClearQueueConfirmationAlertDialog';
import { listCursorChanged, listPriorityChanged } from 'features/queue/store/queueSlice';
import { toast } from 'features/toast/toast';
import { useCallback, useMemo } from 'react';
@@ -10,6 +11,7 @@ import { useClearQueueMutation, useGetQueueStatusQuery } from 'services/api/endp
export const useClearQueue = () => {
const { t } = useTranslation();
const dispatch = useAppDispatch();
+ const dialog = useClearQueueConfirmationAlertDialog();
const { data: queueStatus } = useGetQueueStatusQuery();
const isConnected = useStore($isConnected);
const [trigger, { isLoading }] = useClearQueueMutation({
@@ -41,5 +43,5 @@ export const useClearQueue = () => {
const isDisabled = useMemo(() => !isConnected || !queueStatus?.queue.total, [isConnected, queueStatus?.queue.total]);
- return { clearQueue, isLoading, queueStatus, isDisabled };
+ return { clearQueue, openDialog: dialog.setTrue, isLoading, queueStatus, isDisabled };
};
diff --git a/invokeai/frontend/web/src/features/ui/components/AppContent.tsx b/invokeai/frontend/web/src/features/ui/components/AppContent.tsx
index d0f366130e..2018b071ad 100644
--- a/invokeai/frontend/web/src/features/ui/components/AppContent.tsx
+++ b/invokeai/frontend/web/src/features/ui/components/AppContent.tsx
@@ -1,4 +1,4 @@
-import { Flex } from '@invoke-ai/ui-library';
+import { Box, Flex } from '@invoke-ai/ui-library';
import { createSelector } from '@reduxjs/toolkit';
import { useAppSelector } from 'app/store/storeHooks';
import { useScopeOnFocus } from 'common/hooks/interactionScopes';
@@ -7,6 +7,7 @@ import GalleryPanelContent from 'features/gallery/components/GalleryPanelContent
import { ImageViewer } from 'features/gallery/components/ImageViewer/ImageViewer';
import { useIsImageViewerOpen } from 'features/gallery/components/ImageViewer/useImageViewer';
import NodeEditorPanelGroup from 'features/nodes/components/sidePanel/NodeEditorPanelGroup';
+import QueueControls from 'features/queue/components/QueueControls';
import FloatingGalleryButton from 'features/ui/components/FloatingGalleryButton';
import FloatingParametersPanelButtons from 'features/ui/components/FloatingParametersPanelButtons';
import ParametersPanelTextToImage from 'features/ui/components/ParametersPanels/ParametersPanelTextToImage';
@@ -89,8 +90,14 @@ export const AppContent = memo(() => {
const galleryPanel = usePanel(galleryPanelUsePanelOptions);
- useHotkeys('g', galleryPanel.toggle, [galleryPanel.toggle]);
- useHotkeys(['t', 'o'], optionsPanel.toggle, [optionsPanel.toggle]);
+ useHotkeys('g', galleryPanel.toggle, { enabled: shouldShowGalleryPanel }, [
+ galleryPanel.toggle,
+ shouldShowGalleryPanel,
+ ]);
+ useHotkeys(['t', 'o'], optionsPanel.toggle, { enabled: shouldShowOptionsPanel }, [
+ optionsPanel.toggle,
+ shouldShowOptionsPanel,
+ ]);
useHotkeys(
'shift+r',
() => {
@@ -133,21 +140,26 @@ export const AppContent = memo(() => {
storage={panelStorage}
>
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/invokeai/frontend/web/src/features/ui/components/FloatingParametersPanelButtons.tsx b/invokeai/frontend/web/src/features/ui/components/FloatingParametersPanelButtons.tsx
index 34bcb13b03..e1d2f252c2 100644
--- a/invokeai/frontend/web/src/features/ui/components/FloatingParametersPanelButtons.tsx
+++ b/invokeai/frontend/web/src/features/ui/components/FloatingParametersPanelButtons.tsx
@@ -1,13 +1,13 @@
import type { SystemStyleObject } from '@invoke-ai/ui-library';
import { ButtonGroup, Flex, Icon, IconButton, Portal, spinAnimation } from '@invoke-ai/ui-library';
import CancelCurrentQueueItemIconButton from 'features/queue/components/CancelCurrentQueueItemIconButton';
-import { ClearAllQueueIconButton } from 'features/queue/components/ClearQueueIconButton';
import { QueueButtonTooltip } from 'features/queue/components/QueueButtonTooltip';
+import { useClearQueue } from 'features/queue/hooks/useClearQueue';
import { useQueueBack } from 'features/queue/hooks/useQueueBack';
import type { UsePanelReturn } from 'features/ui/hooks/usePanel';
import { memo, useMemo } from 'react';
import { useTranslation } from 'react-i18next';
-import { PiCircleNotchBold, PiSlidersHorizontalBold } from 'react-icons/pi';
+import { PiCircleNotchBold, PiSlidersHorizontalBold, PiTrashSimpleBold } from 'react-icons/pi';
import { RiSparklingFill } from 'react-icons/ri';
import { useGetQueueStatusQuery } from 'services/api/endpoints/queue';
@@ -23,6 +23,7 @@ type Props = {
const FloatingSidePanelButtons = (props: Props) => {
const { t } = useTranslation();
const { queueBack, isLoading, isDisabled } = useQueueBack();
+ const clearQueue = useClearQueue();
const { data: queueStatus } = useGetQueueStatusQuery();
const queueButtonIcon = useMemo(() => {
@@ -71,7 +72,17 @@ const FloatingSidePanelButtons = (props: Props) => {
-
+ }
+ colorScheme="error"
+ onClick={clearQueue.openDialog}
+ data-testid={t('queue.clear')}
+ sx={floatingButtonStyles}
+ />
);
diff --git a/invokeai/frontend/web/src/features/ui/components/ParametersPanels/ParametersPanelTextToImage.tsx b/invokeai/frontend/web/src/features/ui/components/ParametersPanels/ParametersPanelTextToImage.tsx
index 52d8cdbde9..b27183f7c0 100644
--- a/invokeai/frontend/web/src/features/ui/components/ParametersPanels/ParametersPanelTextToImage.tsx
+++ b/invokeai/frontend/web/src/features/ui/components/ParametersPanels/ParametersPanelTextToImage.tsx
@@ -8,7 +8,6 @@ import { selectIsSDXL } from 'features/controlLayers/store/paramsSlice';
import { selectEntityCount } from 'features/controlLayers/store/selectors';
import { isImageViewerOpenChanged } from 'features/gallery/store/gallerySlice';
import { Prompts } from 'features/parameters/components/Prompts/Prompts';
-import QueueControls from 'features/queue/components/QueueControls';
import { AdvancedSettingsAccordion } from 'features/settingsAccordions/components/AdvancedSettingsAccordion/AdvancedSettingsAccordion';
import { CompositingSettingsAccordion } from 'features/settingsAccordions/components/CompositingSettingsAccordion/CompositingSettingsAccordion';
import { GenerationSettingsAccordion } from 'features/settingsAccordions/components/GenerationSettingsAccordion/GenerationSettingsAccordion';
@@ -64,7 +63,6 @@ const ParametersPanelTextToImage = () => {
return (
-
diff --git a/invokeai/frontend/web/src/features/ui/components/ParametersPanels/ParametersPanelUpscale.tsx b/invokeai/frontend/web/src/features/ui/components/ParametersPanels/ParametersPanelUpscale.tsx
index 1e9d7ec328..8dd44bbd93 100644
--- a/invokeai/frontend/web/src/features/ui/components/ParametersPanels/ParametersPanelUpscale.tsx
+++ b/invokeai/frontend/web/src/features/ui/components/ParametersPanels/ParametersPanelUpscale.tsx
@@ -2,7 +2,6 @@ import { Box, Flex } from '@invoke-ai/ui-library';
import { useStore } from '@nanostores/react';
import { overlayScrollbarsParams } from 'common/components/OverlayScrollbars/constants';
import { Prompts } from 'features/parameters/components/Prompts/Prompts';
-import QueueControls from 'features/queue/components/QueueControls';
import { AdvancedSettingsAccordion } from 'features/settingsAccordions/components/AdvancedSettingsAccordion/AdvancedSettingsAccordion';
import { GenerationSettingsAccordion } from 'features/settingsAccordions/components/GenerationSettingsAccordion/GenerationSettingsAccordion';
import { UpscaleSettingsAccordion } from 'features/settingsAccordions/components/UpscaleSettingsAccordion/UpscaleSettingsAccordion';
@@ -23,7 +22,6 @@ const ParametersPanelUpscale = () => {
return (
-
diff --git a/invokeai/frontend/web/src/features/ui/components/TabButton.tsx b/invokeai/frontend/web/src/features/ui/components/TabButton.tsx
index fa3d5732d7..d59f51f3ed 100644
--- a/invokeai/frontend/web/src/features/ui/components/TabButton.tsx
+++ b/invokeai/frontend/web/src/features/ui/components/TabButton.tsx
@@ -3,30 +3,33 @@ import { useAppDispatch, useAppSelector } from 'app/store/storeHooks';
import { selectActiveTab } from 'features/ui/store/uiSelectors';
import { setActiveTab } from 'features/ui/store/uiSlice';
import type { TabName } from 'features/ui/store/uiTypes';
-import { memo, type ReactElement, useCallback } from 'react';
+import { forwardRef, memo, type ReactElement, useCallback } from 'react';
-export const TabButton = memo(({ tab, icon, label }: { tab: TabName; icon: ReactElement; label: string }) => {
- const dispatch = useAppDispatch();
- const activeTabName = useAppSelector(selectActiveTab);
- const onClick = useCallback(() => {
- dispatch(setActiveTab(tab));
- }, [dispatch, tab]);
+export const TabButton = memo(
+ forwardRef(({ tab, icon, label }: { tab: TabName; icon: ReactElement; label: string }, ref) => {
+ const dispatch = useAppDispatch();
+ const activeTabName = useAppSelector(selectActiveTab);
+ const onClick = useCallback(() => {
+ dispatch(setActiveTab(tab));
+ }, [dispatch, tab]);
- return (
-
-
-
- );
-});
+ return (
+
+
+
+ );
+ })
+);
TabButton.displayName = 'TabButton';
diff --git a/invokeai/frontend/web/src/features/ui/components/tabs/ModelManagerTab.tsx b/invokeai/frontend/web/src/features/ui/components/tabs/ModelManagerTab.tsx
index d80f36bd91..8f17795afc 100644
--- a/invokeai/frontend/web/src/features/ui/components/tabs/ModelManagerTab.tsx
+++ b/invokeai/frontend/web/src/features/ui/components/tabs/ModelManagerTab.tsx
@@ -5,7 +5,7 @@ import { memo } from 'react';
const ModelManagerTab = () => {
return (
-
+
diff --git a/invokeai/frontend/web/src/features/ui/components/tabs/QueueTab.tsx b/invokeai/frontend/web/src/features/ui/components/tabs/QueueTab.tsx
index 96a09f5aa4..1d6d8b6181 100644
--- a/invokeai/frontend/web/src/features/ui/components/tabs/QueueTab.tsx
+++ b/invokeai/frontend/web/src/features/ui/components/tabs/QueueTab.tsx
@@ -4,7 +4,7 @@ import { memo } from 'react';
const QueueTab = () => {
return (
-
+
);
diff --git a/invokeai/frontend/web/src/services/events/onInvocationComplete.ts b/invokeai/frontend/web/src/services/events/onInvocationComplete.ts
index 09cd840bd1..6fcf808aab 100644
--- a/invokeai/frontend/web/src/services/events/onInvocationComplete.ts
+++ b/invokeai/frontend/web/src/services/events/onInvocationComplete.ts
@@ -13,7 +13,7 @@ import { getCategories, getListImagesUrl } from 'services/api/util';
const log = logger('events');
-const isCanvasOutput = (data: S['InvocationCompleteEvent']) => {
+const isCanvasOutputNode = (data: S['InvocationCompleteEvent']) => {
return data.invocation_source_id.split(':')[0] === 'canvas_output';
};
@@ -114,25 +114,19 @@ export const buildOnInvocationComplete = (
};
const handleOriginCanvas = async (data: S['InvocationCompleteEvent']) => {
- const session = getState().canvasSession;
-
const imageDTO = await getResultImageDTO(data);
if (!imageDTO) {
return;
}
- if (session.mode === 'compose') {
- if (session.isStaging && isCanvasOutput(data)) {
+ if (data.destination === 'canvas') {
+ if (isCanvasOutputNode(data)) {
if (data.result.type === 'canvas_v2_mask_and_crop_output') {
const { offset_x, offset_y } = data.result;
- if (session.isStaging) {
- dispatch(sessionImageStaged({ stagingAreaImage: { imageDTO, offsetX: offset_x, offsetY: offset_y } }));
- }
+ dispatch(sessionImageStaged({ stagingAreaImage: { imageDTO, offsetX: offset_x, offsetY: offset_y } }));
} else if (data.result.type === 'image_output') {
- if (session.isStaging) {
- dispatch(sessionImageStaged({ stagingAreaImage: { imageDTO, offsetX: 0, offsetY: 0 } }));
- }
+ dispatch(sessionImageStaged({ stagingAreaImage: { imageDTO, offsetX: 0, offsetY: 0 } }));
}
}
} else {
diff --git a/invokeai/frontend/web/src/services/events/setEventListeners.tsx b/invokeai/frontend/web/src/services/events/setEventListeners.tsx
index 9af12891c7..71114442f0 100644
--- a/invokeai/frontend/web/src/services/events/setEventListeners.tsx
+++ b/invokeai/frontend/web/src/services/events/setEventListeners.tsx
@@ -96,7 +96,7 @@ export const setEventListeners = ({ socket, dispatch, getState, setIsConnected }
});
socket.on('invocation_denoise_progress', (data) => {
- const { invocation_source_id, invocation, step, total_steps, progress_image, origin, percentage, session_id } =
+ const { invocation_source_id, invocation, step, total_steps, progress_image, origin, destination, percentage, session_id } =
data;
if (cancellations.has(session_id)) {
@@ -122,7 +122,7 @@ export const setEventListeners = ({ socket, dispatch, getState, setIsConnected }
}
}
- if (origin === 'canvas') {
+ if (origin === 'canvas' && destination === 'canvas') {
$lastCanvasProgressEvent.set(data);
}
});