From ef135f99239dbe16d2fb3ffccbb868b5fee86349 Mon Sep 17 00:00:00 2001 From: Cursor Agent Date: Mon, 7 Jul 2025 01:27:09 +0000 Subject: [PATCH] Add option to save all staging images to gallery in canvas mode Co-authored-by: kent --- invokeai/frontend/web/public/locales/en.json | 4 +++ ...vasAlertsSaveAllStagingImagesToGallery.tsx | 23 +++++++++++++++++ .../Settings/CanvasSettingsPopover.tsx | 2 ++ ...sSaveAllStagingImagesToGalleryCheckbox.tsx | 25 +++++++++++++++++++ .../store/canvasSettingsSlice.ts | 12 +++++++++ .../ModelInstallQueue/ModelInstallQueue.tsx | 4 +-- .../nodes/util/graph/graphBuilderUtils.ts | 8 ++++-- .../ui/layouts/CanvasWorkspacePanel.tsx | 2 ++ 8 files changed, 75 insertions(+), 5 deletions(-) create mode 100644 invokeai/frontend/web/src/features/controlLayers/components/CanvasAlerts/CanvasAlertsSaveAllStagingImagesToGallery.tsx create mode 100644 invokeai/frontend/web/src/features/controlLayers/components/Settings/CanvasSettingsSaveAllStagingImagesToGalleryCheckbox.tsx diff --git a/invokeai/frontend/web/public/locales/en.json b/invokeai/frontend/web/public/locales/en.json index af8795dd5e..5179f30002 100644 --- a/invokeai/frontend/web/public/locales/en.json +++ b/invokeai/frontend/web/public/locales/en.json @@ -1962,6 +1962,7 @@ "recalculateRects": "Recalculate Rects", "clipToBbox": "Clip Strokes to Bbox", "outputOnlyMaskedRegions": "Output Only Generated Regions", + "saveAllStagingImagesToGallery": "Save All Staging Images to Gallery", "addLayer": "Add Layer", "duplicate": "Duplicate", "moveToFront": "Move to Front", @@ -2330,6 +2331,9 @@ "label": "Preserve Masked Region", "alert": "Preserving Masked Region" }, + "saveAllStagingImagesToGallery": { + "alert": "Saving All Staging Images to Gallery" + }, "isolatedStagingPreview": "Isolated Staging Preview", "isolatedPreview": "Isolated Preview", "isolatedLayerPreview": "Isolated Layer Preview", diff --git a/invokeai/frontend/web/src/features/controlLayers/components/CanvasAlerts/CanvasAlertsSaveAllStagingImagesToGallery.tsx b/invokeai/frontend/web/src/features/controlLayers/components/CanvasAlerts/CanvasAlertsSaveAllStagingImagesToGallery.tsx new file mode 100644 index 0000000000..63faec7719 --- /dev/null +++ b/invokeai/frontend/web/src/features/controlLayers/components/CanvasAlerts/CanvasAlertsSaveAllStagingImagesToGallery.tsx @@ -0,0 +1,23 @@ +import { Alert, AlertIcon, AlertTitle } from '@invoke-ai/ui-library'; +import { useAppSelector } from 'app/store/storeHooks'; +import { selectSaveAllStagingImagesToGallery } from 'features/controlLayers/store/canvasSettingsSlice'; +import { memo } from 'react'; +import { useTranslation } from 'react-i18next'; + +export const CanvasAlertsSaveAllStagingImagesToGallery = memo(() => { + const { t } = useTranslation(); + const saveAllStagingImagesToGallery = useAppSelector(selectSaveAllStagingImagesToGallery); + + if (!saveAllStagingImagesToGallery) { + return null; + } + + return ( + + + {t('controlLayers.settings.saveAllStagingImagesToGallery.alert')} + + ); +}); + +CanvasAlertsSaveAllStagingImagesToGallery.displayName = 'CanvasAlertsSaveAllStagingImagesToGallery'; diff --git a/invokeai/frontend/web/src/features/controlLayers/components/Settings/CanvasSettingsPopover.tsx b/invokeai/frontend/web/src/features/controlLayers/components/Settings/CanvasSettingsPopover.tsx index df42fb9ded..e5cd083eba 100644 --- a/invokeai/frontend/web/src/features/controlLayers/components/Settings/CanvasSettingsPopover.tsx +++ b/invokeai/frontend/web/src/features/controlLayers/components/Settings/CanvasSettingsPopover.tsx @@ -26,6 +26,7 @@ import { CanvasSettingsPreserveMaskCheckbox } from 'features/controlLayers/compo import { CanvasSettingsPressureSensitivityCheckbox } from 'features/controlLayers/components/Settings/CanvasSettingsPressureSensitivity'; import { CanvasSettingsRecalculateRectsButton } from 'features/controlLayers/components/Settings/CanvasSettingsRecalculateRectsButton'; import { CanvasSettingsRuleOfThirdsSwitch } from 'features/controlLayers/components/Settings/CanvasSettingsRuleOfThirdsGuideSwitch'; +import { CanvasSettingsSaveAllStagingImagesToGalleryCheckbox } from 'features/controlLayers/components/Settings/CanvasSettingsSaveAllStagingImagesToGalleryCheckbox'; import { CanvasSettingsShowHUDSwitch } from 'features/controlLayers/components/Settings/CanvasSettingsShowHUDSwitch'; import { CanvasSettingsShowProgressOnCanvas } from 'features/controlLayers/components/Settings/CanvasSettingsShowProgressOnCanvasSwitch'; import { memo } from 'react'; @@ -61,6 +62,7 @@ export const CanvasSettingsPopover = memo(() => { + diff --git a/invokeai/frontend/web/src/features/controlLayers/components/Settings/CanvasSettingsSaveAllStagingImagesToGalleryCheckbox.tsx b/invokeai/frontend/web/src/features/controlLayers/components/Settings/CanvasSettingsSaveAllStagingImagesToGalleryCheckbox.tsx new file mode 100644 index 0000000000..633056dfbb --- /dev/null +++ b/invokeai/frontend/web/src/features/controlLayers/components/Settings/CanvasSettingsSaveAllStagingImagesToGalleryCheckbox.tsx @@ -0,0 +1,25 @@ +import { Checkbox, FormControl, FormLabel } from '@invoke-ai/ui-library'; +import { useAppDispatch, useAppSelector } from 'app/store/storeHooks'; +import { + selectSaveAllStagingImagesToGallery, + settingsSaveAllStagingImagesToGalleryToggled, +} from 'features/controlLayers/store/canvasSettingsSlice'; +import { memo, useCallback } from 'react'; +import { useTranslation } from 'react-i18next'; + +export const CanvasSettingsSaveAllStagingImagesToGalleryCheckbox = memo(() => { + const { t } = useTranslation(); + const dispatch = useAppDispatch(); + const saveAllStagingImagesToGallery = useAppSelector(selectSaveAllStagingImagesToGallery); + const onChange = useCallback(() => { + dispatch(settingsSaveAllStagingImagesToGalleryToggled()); + }, [dispatch]); + return ( + + {t('controlLayers.saveAllStagingImagesToGallery')} + + + ); +}); + +CanvasSettingsSaveAllStagingImagesToGalleryCheckbox.displayName = 'CanvasSettingsSaveAllStagingImagesToGalleryCheckbox'; diff --git a/invokeai/frontend/web/src/features/controlLayers/store/canvasSettingsSlice.ts b/invokeai/frontend/web/src/features/controlLayers/store/canvasSettingsSlice.ts index 785c9f9099..ac24b0af82 100644 --- a/invokeai/frontend/web/src/features/controlLayers/store/canvasSettingsSlice.ts +++ b/invokeai/frontend/web/src/features/controlLayers/store/canvasSettingsSlice.ts @@ -77,6 +77,10 @@ type CanvasSettingsState = { * Whether to show the rule of thirds composition guide overlay on the canvas. */ ruleOfThirds: boolean; + /** + * Whether to save all staging images to the gallery instead of keeping them as intermediate images. + */ + saveAllStagingImagesToGallery: boolean; }; const initialState: CanvasSettingsState = { @@ -97,6 +101,7 @@ const initialState: CanvasSettingsState = { isolatedLayerPreview: true, pressureSensitivity: true, ruleOfThirds: false, + saveAllStagingImagesToGallery: false, }; export const canvasSettingsSlice = createSlice({ @@ -154,6 +159,9 @@ export const canvasSettingsSlice = createSlice({ settingsRuleOfThirdsToggled: (state) => { state.ruleOfThirds = !state.ruleOfThirds; }, + settingsSaveAllStagingImagesToGalleryToggled: (state) => { + state.saveAllStagingImagesToGallery = !state.saveAllStagingImagesToGallery; + }, }, }); @@ -175,6 +183,7 @@ export const { settingsIsolatedLayerPreviewToggled, settingsPressureSensitivityToggled, settingsRuleOfThirdsToggled, + settingsSaveAllStagingImagesToGalleryToggled, } = canvasSettingsSlice.actions; /* eslint-disable-next-line @typescript-eslint/no-explicit-any */ @@ -209,3 +218,6 @@ export const selectIsolatedStagingPreview = createCanvasSettingsSelector((settin export const selectIsolatedLayerPreview = createCanvasSettingsSelector((settings) => settings.isolatedLayerPreview); export const selectPressureSensitivity = createCanvasSettingsSelector((settings) => settings.pressureSensitivity); export const selectRuleOfThirds = createCanvasSettingsSelector((settings) => settings.ruleOfThirds); +export const selectSaveAllStagingImagesToGallery = createCanvasSettingsSelector( + (settings) => settings.saveAllStagingImagesToGallery +); diff --git a/invokeai/frontend/web/src/features/modelManagerV2/subpanels/AddModelPanel/ModelInstallQueue/ModelInstallQueue.tsx b/invokeai/frontend/web/src/features/modelManagerV2/subpanels/AddModelPanel/ModelInstallQueue/ModelInstallQueue.tsx index f124462e8b..c2443dde6b 100644 --- a/invokeai/frontend/web/src/features/modelManagerV2/subpanels/AddModelPanel/ModelInstallQueue/ModelInstallQueue.tsx +++ b/invokeai/frontend/web/src/features/modelManagerV2/subpanels/AddModelPanel/ModelInstallQueue/ModelInstallQueue.tsx @@ -55,9 +55,7 @@ export const ModelInstallQueue = memo(() => { - {data?.map((model) => ( - - ))} + {data?.map((model) => )} diff --git a/invokeai/frontend/web/src/features/nodes/util/graph/graphBuilderUtils.ts b/invokeai/frontend/web/src/features/nodes/util/graph/graphBuilderUtils.ts index 6549f07d07..c273d16e19 100644 --- a/invokeai/frontend/web/src/features/nodes/util/graph/graphBuilderUtils.ts +++ b/invokeai/frontend/web/src/features/nodes/util/graph/graphBuilderUtils.ts @@ -1,6 +1,7 @@ import { createSelector } from '@reduxjs/toolkit'; import type { RootState } from 'app/store/store'; import { getPrefixedId } from 'features/controlLayers/konva/util'; +import { selectSaveAllStagingImagesToGallery } from 'features/controlLayers/store/canvasSettingsSlice'; import { selectImg2imgStrength, selectMainModelConfig, @@ -44,8 +45,11 @@ export const selectCanvasOutputFields = (state: RootState) => { // Advanced session means working on canvas - images are not saved to gallery or added to a board. // Simple session means working in YOLO mode - images are saved to gallery & board. const tab = selectActiveTab(state); - const is_intermediate = tab === 'canvas'; - const board = tab === 'canvas' ? undefined : getBoardField(state); + const saveAllStagingImagesToGallery = selectSaveAllStagingImagesToGallery(state); + + // If we're on canvas and the save all staging images setting is enabled, save to gallery + const is_intermediate = tab === 'canvas' && !saveAllStagingImagesToGallery; + const board = tab === 'canvas' && !saveAllStagingImagesToGallery ? undefined : getBoardField(state); return { is_intermediate, diff --git a/invokeai/frontend/web/src/features/ui/layouts/CanvasWorkspacePanel.tsx b/invokeai/frontend/web/src/features/ui/layouts/CanvasWorkspacePanel.tsx index e9e4d2d0bf..3ff0ef6c2b 100644 --- a/invokeai/frontend/web/src/features/ui/layouts/CanvasWorkspacePanel.tsx +++ b/invokeai/frontend/web/src/features/ui/layouts/CanvasWorkspacePanel.tsx @@ -2,6 +2,7 @@ import { ContextMenu, Divider, Flex, IconButton, Menu, MenuButton, MenuList } fr import { useAppSelector } from 'app/store/storeHooks'; import { CanvasAlertsInvocationProgress } from 'features/controlLayers/components/CanvasAlerts/CanvasAlertsInvocationProgress'; import { CanvasAlertsPreserveMask } from 'features/controlLayers/components/CanvasAlerts/CanvasAlertsPreserveMask'; +import { CanvasAlertsSaveAllStagingImagesToGallery } from 'features/controlLayers/components/CanvasAlerts/CanvasAlertsSaveAllStagingImagesToGallery'; import { CanvasAlertsSelectedEntityStatus } from 'features/controlLayers/components/CanvasAlerts/CanvasAlertsSelectedEntityStatus'; import { CanvasBusySpinner } from 'features/controlLayers/components/CanvasBusySpinner'; import { CanvasContextMenuGlobalMenuItems } from 'features/controlLayers/components/CanvasContextMenu/CanvasContextMenuGlobalMenuItems'; @@ -88,6 +89,7 @@ export const CanvasWorkspacePanel = memo(() => { {showHUD && } +