refactor(ui): canvas flow (wip)

This commit is contained in:
psychedelicious
2025-05-27 16:20:10 +10:00
parent c0428ee7ef
commit ce5ae83689
32 changed files with 98 additions and 452 deletions

View File

@@ -2,9 +2,8 @@ import { useStore } from '@nanostores/react';
import { useAppStore } from 'app/store/storeHooks';
import { useAssertSingleton } from 'common/hooks/useAssertSingleton';
import { withResultAsync } from 'common/util/result';
import { canvasReset } from 'features/controlLayers/store/actions';
import { settingsSendToCanvasChanged } from 'features/controlLayers/store/canvasSettingsSlice';
import { rasterLayerAdded } from 'features/controlLayers/store/canvasSlice';
import { canvasSessionStarted } from 'features/controlLayers/store/canvasStagingAreaSlice';
import type { CanvasRasterLayerState } from 'features/controlLayers/store/types';
import { imageDTOToImageObject } from 'features/controlLayers/store/util';
import { $imageViewer } from 'features/gallery/components/ImageViewer/useImageViewer';
@@ -91,9 +90,8 @@ export const useStudioInitAction = (action?: StudioInitAction) => {
const overrides: Partial<CanvasRasterLayerState> = {
objects: [imageObject],
};
store.dispatch(canvasReset());
store.dispatch(canvasSessionStarted({ sessionType: 'advanced' }));
store.dispatch(rasterLayerAdded({ overrides, isSelected: true }));
store.dispatch(settingsSendToCanvasChanged(true));
store.dispatch(setActiveTab('canvas'));
store.dispatch(sentImageToCanvas());
$imageViewer.set(false);
@@ -164,15 +162,15 @@ export const useStudioInitAction = (action?: StudioInitAction) => {
switch (destination) {
case 'generation':
// Go to the canvas tab, open the image viewer, and enable send-to-gallery mode
store.dispatch(canvasSessionStarted({ sessionType: 'simple' }));
store.dispatch(setActiveTab('canvas'));
store.dispatch(activeTabCanvasRightPanelChanged('gallery'));
store.dispatch(settingsSendToCanvasChanged(false));
$imageViewer.set(true);
break;
case 'canvas':
// Go to the canvas tab, close the image viewer, and disable send-to-gallery mode
store.dispatch(canvasSessionStarted({ sessionType: 'advanced' }));
store.dispatch(setActiveTab('canvas'));
store.dispatch(settingsSendToCanvasChanged(true));
$imageViewer.set(false);
break;
case 'workflows':

View File

@@ -1,7 +1,7 @@
import { isAnyOf } from '@reduxjs/toolkit';
import { logger } from 'app/logging/logger';
import type { AppStartListening } from 'app/store/middleware/listenerMiddleware';
import { canvasReset, newSessionRequested } from 'features/controlLayers/store/actions';
import { canvasReset } from 'features/controlLayers/store/actions';
import { stagingAreaReset } from 'features/controlLayers/store/canvasStagingAreaSlice';
import { toast } from 'features/toast/toast';
import { t } from 'i18next';
@@ -9,7 +9,7 @@ import { queueApi } from 'services/api/endpoints/queue';
const log = logger('canvas');
const matchCanvasOrStagingAreaReset = isAnyOf(stagingAreaReset, canvasReset, newSessionRequested);
const matchCanvasOrStagingAreaReset = isAnyOf(stagingAreaReset, canvasReset);
export const addStagingListeners = (startAppListening: AppStartListening) => {
startAppListening({

View File

@@ -19,9 +19,10 @@ import { StagingAreaToolbar } from 'features/controlLayers/components/StagingAre
import { CanvasToolbar } from 'features/controlLayers/components/Toolbar/CanvasToolbar';
import { Transform } from 'features/controlLayers/components/Transform/Transform';
import { CanvasManagerProviderGate } from 'features/controlLayers/contexts/CanvasManagerProviderGate';
import { canvasReset, newAdvancedCanvasSessionRequested } from 'features/controlLayers/store/actions';
import { canvasReset } from 'features/controlLayers/store/actions';
import { selectDynamicGrid, selectShowHUD } from 'features/controlLayers/store/canvasSettingsSlice';
import {
canvasSessionStarted,
selectCanvasSessionType,
selectIsStaging,
selectSelectedImage,
@@ -88,7 +89,7 @@ CanvasMainPanelContent.displayName = 'CanvasMainPanelContent';
const NoActiveSession = memo(() => {
const dispatch = useAppDispatch();
const newSesh = useCallback(() => {
dispatch(newAdvancedCanvasSessionRequested());
dispatch(canvasSessionStarted({ sessionType: 'advanced' }));
}, [dispatch]);
return (
<Flex flexDir="column" w="full" h="full" alignItems="center" justifyContent="center">

View File

@@ -2,7 +2,7 @@ import { Checkbox, ConfirmationAlertDialog, Flex, FormControl, FormLabel, Text }
import { useAppDispatch, useAppSelector } from 'app/store/storeHooks';
import { useAssertSingleton } from 'common/hooks/useAssertSingleton';
import { buildUseBoolean } from 'common/hooks/useBoolean';
import { newAdvancedCanvasSessionRequested, newSimpleCanvasSessionRequested } from 'features/controlLayers/store/actions';
import { canvasSessionStarted } from 'features/controlLayers/store/canvasStagingAreaSlice';
import {
selectSystemShouldConfirmOnNewSession,
shouldConfirmOnNewSessionToggled,
@@ -20,7 +20,7 @@ export const useNewGallerySession = () => {
const newSessionDialog = useNewGallerySessionDialog();
const newGallerySessionImmediate = useCallback(() => {
dispatch(newSimpleCanvasSessionRequested());
dispatch(canvasSessionStarted({ sessionType: 'simple' }));
dispatch(activeTabCanvasRightPanelChanged('gallery'));
}, [dispatch]);
@@ -41,7 +41,7 @@ export const useNewCanvasSession = () => {
const newSessionDialog = useNewCanvasSessionDialog();
const newCanvasSessionImmediate = useCallback(() => {
dispatch(newAdvancedCanvasSessionRequested());
dispatch(canvasSessionStarted({ sessionType: 'advanced' }));
dispatch(activeTabCanvasRightPanelChanged('layers'));
}, [dispatch]);

View File

@@ -85,6 +85,8 @@ export class CanvasProgressImageModule extends CanvasModuleBase {
if (data.destination !== 'canvas') {
return;
}
// The staging area module handles _completed_ events. Only care about failed or canceled here.
if (data.status === 'failed' || data.status === 'canceled') {
this.$lastProgressEvent.set(null);
this.$hasActiveGeneration.set(false);

View File

@@ -1,7 +1,4 @@
import { createAction, isAnyOf } from '@reduxjs/toolkit';
import { createAction } from '@reduxjs/toolkit';
// Needed to split this from canvasSlice.ts to avoid circular dependencies
export const canvasReset = createAction('canvas/canvasReset');
export const newSimpleCanvasSessionRequested = createAction('canvas/newSimpleCanvasSessionRequested');
export const newAdvancedCanvasSessionRequested = createAction('canvas/newAdvancedCanvasSessionRequested');
export const newSessionRequested = isAnyOf(newSimpleCanvasSessionRequested, newAdvancedCanvasSessionRequested);

View File

@@ -1,7 +1,6 @@
import type { PayloadAction, Selector } from '@reduxjs/toolkit';
import { createSelector, createSlice } from '@reduxjs/toolkit';
import type { PersistConfig, RootState } from 'app/store/store';
import { newAdvancedCanvasSessionRequested, newSimpleCanvasSessionRequested } from 'features/controlLayers/store/actions';
import type { RgbaColor } from 'features/controlLayers/store/types';
type CanvasSettingsState = {
@@ -34,11 +33,6 @@ type CanvasSettingsState = {
* The color to use when drawing lines or filling shapes.
*/
color: RgbaColor;
/**
* Whether to send generated images to canvas staging area. When disabled, generated images will be sent directly to
* the gallery.
*/
sendToCanvas: boolean;
/**
* Whether to composite inpainted/outpainted regions back onto the source image when saving canvas generations.
*
@@ -89,7 +83,6 @@ const initialState: CanvasSettingsState = {
eraserWidth: 50,
invertScrollForToolWidth: false,
color: { r: 31, g: 160, b: 224, a: 1 }, // invokeBlue.500
sendToCanvas: false,
outputOnlyMaskedRegions: true,
autoProcess: true,
snapToGrid: true,
@@ -126,9 +119,6 @@ export const canvasSettingsSlice = createSlice({
settingsInvertScrollForToolWidthChanged: (state, action: PayloadAction<boolean>) => {
state.invertScrollForToolWidth = action.payload;
},
settingsSendToCanvasChanged: (state, action: PayloadAction<boolean>) => {
state.sendToCanvas = action.payload;
},
settingsOutputOnlyMaskedRegionsToggled: (state) => {
state.outputOnlyMaskedRegions = !state.outputOnlyMaskedRegions;
},
@@ -157,14 +147,6 @@ export const canvasSettingsSlice = createSlice({
state.pressureSensitivity = !state.pressureSensitivity;
},
},
extraReducers(builder) {
builder.addCase(newSimpleCanvasSessionRequested, (state) => {
state.sendToCanvas = false;
});
builder.addCase(newAdvancedCanvasSessionRequested, (state) => {
state.sendToCanvas = true;
});
},
});
export const {
@@ -175,7 +157,6 @@ export const {
settingsEraserWidthChanged,
settingsColorChanged,
settingsInvertScrollForToolWidthChanged,
settingsSendToCanvasChanged,
settingsOutputOnlyMaskedRegionsToggled,
settingsAutoProcessToggled,
settingsSnapToGridToggled,
@@ -212,7 +193,6 @@ export const selectBboxOverlay = createCanvasSettingsSelector((settings) => sett
export const selectShowHUD = createCanvasSettingsSelector((settings) => settings.showHUD);
export const selectAutoProcess = createCanvasSettingsSelector((settings) => settings.autoProcess);
export const selectSnapToGrid = createCanvasSettingsSelector((settings) => settings.snapToGrid);
export const selectSendToCanvas = createCanvasSettingsSelector((canvasSettings) => canvasSettings.sendToCanvas);
export const selectShowProgressOnCanvas = createCanvasSettingsSelector(
(canvasSettings) => canvasSettings.showProgressOnCanvas
);

View File

@@ -5,11 +5,8 @@ import { moveOneToEnd, moveOneToStart, moveToEnd, moveToStart } from 'common/uti
import { deepClone } from 'common/util/deepClone';
import { roundDownToMultiple, roundToMultiple } from 'common/util/roundDownToMultiple';
import { getPrefixedId } from 'features/controlLayers/konva/util';
import {
canvasReset,
newAdvancedCanvasSessionRequested,
newSimpleCanvasSessionRequested,
} from 'features/controlLayers/store/actions';
import { canvasReset } from 'features/controlLayers/store/actions';
import { canvasSessionStarted } from 'features/controlLayers/store/canvasStagingAreaSlice';
import { modelChanged } from 'features/controlLayers/store/paramsSlice';
import {
selectAllEntities,
@@ -1806,13 +1803,7 @@ export const canvasSlice = createSlice({
syncScaledSize(state);
}
});
builder.addCase(newSimpleCanvasSessionRequested, (state) => {
return resetState(state);
});
builder.addCase(newAdvancedCanvasSessionRequested, (state) => {
const newState = resetState(state);
return newState;
});
builder.addCase(canvasSessionStarted, (state) => resetState(state));
},
});

View File

@@ -1,11 +1,7 @@
import { createSelector, createSlice, type PayloadAction } from '@reduxjs/toolkit';
import type { PersistConfig, RootState } from 'app/store/store';
import { deepClone } from 'common/util/deepClone';
import {
canvasReset,
newAdvancedCanvasSessionRequested,
newSimpleCanvasSessionRequested,
} from 'features/controlLayers/store/actions';
import { canvasReset } from 'features/controlLayers/store/actions';
import type { StagingAreaImage } from 'features/controlLayers/store/types';
import { selectCanvasQueueCounts } from 'services/api/endpoints/queue';
@@ -15,15 +11,17 @@ type CanvasStagingAreaState = {
selectedImageIndex: number;
};
const initialState: CanvasStagingAreaState = {
const INITIAL_STATE: CanvasStagingAreaState = {
sessionType: null,
images: [],
selectedImageIndex: 0,
};
const getInitialState = (): CanvasStagingAreaState => deepClone(INITIAL_STATE);
export const canvasSessionSlice = createSlice({
name: 'canvasSession',
initialState,
initialState: getInitialState(),
reducers: {
stagingAreaImageStaged: (state, action: PayloadAction<{ stagingAreaImage: StagingAreaImage }>) => {
const { stagingAreaImage } = action.payload;
@@ -49,25 +47,15 @@ export const canvasSessionSlice = createSlice({
state.images = [];
state.selectedImageIndex = 0;
},
canvasSessionStarted: (state, action: PayloadAction<{ sessionType: CanvasStagingAreaState['sessionType'] }>) => {
canvasSessionStarted: (_, action: PayloadAction<{ sessionType: CanvasStagingAreaState['sessionType'] }>) => {
const { sessionType } = action.payload;
const state = getInitialState();
state.sessionType = sessionType;
state.images = [];
state.selectedImageIndex = 0;
return state;
},
},
extraReducers(builder) {
builder.addCase(canvasReset, () => deepClone(initialState));
builder.addCase(newSimpleCanvasSessionRequested, () => {
const state = deepClone(initialState);
state.sessionType === 'simple';
return state;
});
builder.addCase(newAdvancedCanvasSessionRequested, () => {
const state = deepClone(initialState);
state.sessionType === 'advanced';
return state;
});
builder.addCase(canvasReset, () => getInitialState());
},
});
@@ -88,7 +76,7 @@ const migrate = (state: any): any => {
export const canvasStagingAreaPersistConfig: PersistConfig<CanvasStagingAreaState> = {
name: canvasSessionSlice.name,
initialState,
initialState: getInitialState(),
migrate,
persistDenylist: [],
};

View File

@@ -1,13 +1,12 @@
import { createSelector, createSlice, type PayloadAction } from '@reduxjs/toolkit';
import type { PersistConfig, RootState } from 'app/store/store';
import { deepClone } from 'common/util/deepClone';
import { canvasSessionStarted } from 'features/controlLayers/store/canvasStagingAreaSlice';
import type { LoRA } from 'features/controlLayers/store/types';
import { zModelIdentifierField } from 'features/nodes/types/common';
import type { LoRAModelConfig } from 'services/api/types';
import { v4 as uuidv4 } from 'uuid';
import { newSessionRequested } from './actions';
type LoRAsState = {
loras: LoRA[];
};
@@ -65,7 +64,7 @@ export const lorasSlice = createSlice({
},
},
extraReducers(builder) {
builder.addMatcher(newSessionRequested, () => {
builder.addCase(canvasSessionStarted, () => {
// When a new session is requested, clear all LoRAs
return deepClone(initialState);
});

View File

@@ -1,6 +1,7 @@
import type { PayloadAction, Selector } from '@reduxjs/toolkit';
import { createSelector, createSlice } from '@reduxjs/toolkit';
import type { PersistConfig, RootState } from 'app/store/store';
import { canvasSessionStarted } from 'features/controlLayers/store/canvasStagingAreaSlice';
import type { ParamsState, RgbaColor } from 'features/controlLayers/store/types';
import { getInitialParamsState } from 'features/controlLayers/store/types';
import { CLIP_SKIP_MAP } from 'features/parameters/types/constants';
@@ -24,7 +25,6 @@ import { clamp } from 'lodash-es';
import { modelConfigsAdapterSelectors, selectModelConfigsQuery } from 'services/api/endpoints/models';
import { isNonRefinerMainModelConfig } from 'services/api/types';
import { newSessionRequested } from './actions';
export const paramsSlice = createSlice({
name: 'params',
@@ -189,7 +189,7 @@ export const paramsSlice = createSlice({
paramsReset: (state) => resetState(state),
},
extraReducers(builder) {
builder.addMatcher(newSessionRequested, (state) => resetState(state));
builder.addCase(canvasSessionStarted, (state) => resetState(state));
},
});

View File

@@ -11,7 +11,7 @@ import type { AnimationProps } from 'framer-motion';
import { AnimatePresence, motion } from 'framer-motion';
import { memo, useCallback, useRef, useState } from 'react';
import type { ImageDTO } from 'services/api/types';
import { $hasProgressImage, $isProgressFromCanvas } from 'services/events/stores';
import { $hasProgressImage } from 'services/events/stores';
import { NoContentForViewer } from './NoContentForViewer';
import ProgressImage from './ProgressImage';
@@ -87,10 +87,9 @@ export default memo(CurrentImagePreview);
const ImageContent = memo(({ imageDTO }: { imageDTO?: ImageDTO }) => {
const hasProgressImage = useStore($hasProgressImage);
const isProgressFromCanvas = useStore($isProgressFromCanvas);
const shouldShowProgressInViewer = useAppSelector(selectShouldShowProgressInViewer);
if (hasProgressImage && !isProgressFromCanvas && shouldShowProgressInViewer) {
if (hasProgressImage && shouldShowProgressInViewer) {
return <ProgressImage />;
}

View File

@@ -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 { $isProgressFromCanvas, $progressImage } from 'services/events/stores';
import { $progressImage } from 'services/events/stores';
const selectShouldAntialiasProgressImage = createSelector(
selectSystemSlice,
@@ -14,7 +14,6 @@ const selectShouldAntialiasProgressImage = createSelector(
const CurrentImagePreview = () => {
const progressImage = useStore($progressImage);
const isProgressFromCanvas = useStore($isProgressFromCanvas);
const shouldAntialiasProgressImage = useAppSelector(selectShouldAntialiasProgressImage);
const sx = useMemo<SystemStyleObject>(
@@ -24,7 +23,7 @@ const CurrentImagePreview = () => {
[shouldAntialiasProgressImage]
);
if (!progressImage || isProgressFromCanvas) {
if (!progressImage) {
return null;
}

View File

@@ -2,7 +2,7 @@ import type { PayloadAction } from '@reduxjs/toolkit';
import { createSelector, createSlice } from '@reduxjs/toolkit';
import type { PersistConfig, RootState } from 'app/store/store';
import { deepClone } from 'common/util/deepClone';
import { newSessionRequested } from 'features/controlLayers/store/actions';
import { canvasSessionStarted } from 'features/controlLayers/store/canvasStagingAreaSlice';
import type { ParameterHRFMethod, ParameterStrength } from 'features/parameters/types/parameterSchemas';
interface HRFState {
@@ -34,7 +34,7 @@ export const hrfSlice = createSlice({
},
},
extraReducers(builder) {
builder.addMatcher(newSessionRequested, () => {
builder.addCase(canvasSessionStarted, () => {
return deepClone(initialHRFState);
});
},

View File

@@ -119,9 +119,8 @@ export const addFLUXFill = async ({
});
g.addEdge(maskCombine, 'image', expandMask, 'mask');
// Do the paste back if we are sending to gallery (in which case we want to see the full image), or if we are sending
// to canvas but not outputting only masked regions
if (!canvasSettings.sendToCanvas || !canvasSettings.outputOnlyMaskedRegions) {
// Do the paste back if we are not outputting only masked regions
if (!canvasSettings.outputOnlyMaskedRegions) {
const imageLayerBlend = g.addNode({
type: 'invokeai_img_blend',
id: getPrefixedId('image_layer_blend'),
@@ -178,9 +177,8 @@ export const addFLUXFill = async ({
});
g.addEdge(maskCombine, 'image', expandMask, 'mask');
// Do the paste back if we are sending to gallery (in which case we want to see the full image), or if we are sending
// to canvas but not outputting only masked regions
if (!canvasSettings.sendToCanvas || !canvasSettings.outputOnlyMaskedRegions) {
// Do the paste back if we are not outputting only masked regions
if (!canvasSettings.outputOnlyMaskedRegions) {
const imageLayerBlend = g.addNode({
type: 'invokeai_img_blend',
id: getPrefixedId('image_layer_blend'),

View File

@@ -185,9 +185,8 @@ export const addInpaint = async ({
g.addEdge(expandMask, 'image', resizeMaskToOriginalSize, 'image');
// After denoising, resize the image and mask back to original size
// Do the paste back if we are sending to gallery (in which case we want to see the full image), or if we are sending
// to canvas but not outputting only masked regions
if (!canvasSettings.sendToCanvas || !canvasSettings.outputOnlyMaskedRegions) {
// Do the paste back if we are not outputting only masked regions
if (!canvasSettings.outputOnlyMaskedRegions) {
const imageLayerBlend = g.addNode({
type: 'invokeai_img_blend',
id: getPrefixedId('image_layer_blend'),
@@ -259,9 +258,8 @@ export const addInpaint = async ({
});
g.addEdge(createGradientMask, 'expanded_mask_area', expandMask, 'mask');
// Do the paste back if we are sending to gallery (in which case we want to see the full image), or if we are sending
// to canvas but not outputting only masked regions
if (!canvasSettings.sendToCanvas || !canvasSettings.outputOnlyMaskedRegions) {
// Do the paste back if we are not outputting only masked regions
if (!canvasSettings.outputOnlyMaskedRegions) {
const imageLayerBlend = g.addNode({
type: 'invokeai_img_blend',
id: getPrefixedId('image_layer_blend'),

View File

@@ -207,9 +207,9 @@ export const addOutpaint = async ({
g.addEdge(l2i, 'image', resizeOutputImageToOriginalSize, 'image');
g.addEdge(createGradientMask, 'expanded_mask_area', expandMask, 'mask');
g.addEdge(expandMask, 'image', resizeOutputMaskToOriginalSize, 'image');
// Do the paste back if we are sending to gallery (in which case we want to see the full image), or if we are sending
// to canvas but not outputting only masked regions
if (!canvasSettings.sendToCanvas || !canvasSettings.outputOnlyMaskedRegions) {
// Do the paste back if we are not outputting only masked regions
if (!canvasSettings.outputOnlyMaskedRegions) {
const imageLayerBlend = g.addNode({
type: 'invokeai_img_blend',
id: getPrefixedId('image_layer_blend'),
@@ -295,9 +295,8 @@ export const addOutpaint = async ({
});
g.addEdge(createGradientMask, 'expanded_mask_area', expandMask, 'mask');
// Do the paste back if we are sending to gallery (in which case we want to see the full image), or if we are sending
// to canvas but not outputting only masked regions
if (!canvasSettings.sendToCanvas || !canvasSettings.outputOnlyMaskedRegions) {
// Do the paste back if we are not outputting only masked regions
if (!canvasSettings.outputOnlyMaskedRegions) {
const imageLayerBlend = g.addNode({
type: 'invokeai_img_blend',
id: getPrefixedId('image_layer_blend'),

View File

@@ -2,7 +2,6 @@ import { logger } from 'app/logging/logger';
import type { RootState } from 'app/store/store';
import type { CanvasManager } from 'features/controlLayers/konva/CanvasManager';
import { getPrefixedId } from 'features/controlLayers/konva/util';
import { selectCanvasSettingsSlice } from 'features/controlLayers/store/canvasSettingsSlice';
import { selectMainModelConfig } from 'features/controlLayers/store/paramsSlice';
import { selectCanvasSlice } from 'features/controlLayers/store/selectors';
import { isChatGPT4oAspectRatioID, isChatGPT4oReferenceImageConfig } from 'features/controlLayers/store/types';
@@ -10,11 +9,7 @@ import { getGlobalReferenceImageWarnings } from 'features/controlLayers/store/va
import { type ImageField, zModelIdentifierField } from 'features/nodes/types/common';
import { getGenerationMode } from 'features/nodes/util/graph/generation/getGenerationMode';
import { Graph } from 'features/nodes/util/graph/generation/Graph';
import {
CANVAS_OUTPUT_PREFIX,
getBoardField,
selectPresetModifiedPrompts,
} from 'features/nodes/util/graph/graphBuilderUtils';
import { CANVAS_OUTPUT_PREFIX, selectPresetModifiedPrompts } from 'features/nodes/util/graph/graphBuilderUtils';
import { type GraphBuilderReturn, UnsupportedGenerationModeError } from 'features/nodes/util/graph/types';
import { t } from 'i18next';
import type { Equals } from 'tsafe';
@@ -37,7 +32,6 @@ export const buildChatGPT4oGraph = async (
const model = selectMainModelConfig(state);
const canvas = selectCanvasSlice(state);
const canvasSettings = selectCanvasSettingsSlice(state);
const { bbox } = canvas;
const { positivePrompt } = selectPresetModifiedPrompts(state);
@@ -65,9 +59,6 @@ export const buildChatGPT4oGraph = async (
}
}
const is_intermediate = canvasSettings.sendToCanvas;
const board = canvasSettings.sendToCanvas ? undefined : getBoardField(state);
if (generationMode === 'txt2img') {
const g = new Graph(getPrefixedId('chatgpt_4o_txt2img_graph'));
const gptImage = g.addNode({

View File

@@ -2,7 +2,6 @@ import { logger } from 'app/logging/logger';
import type { RootState } from 'app/store/store';
import type { CanvasManager } from 'features/controlLayers/konva/CanvasManager';
import { getPrefixedId } from 'features/controlLayers/konva/util';
import { selectCanvasSettingsSlice } from 'features/controlLayers/store/canvasSettingsSlice';
import { selectParamsSlice } from 'features/controlLayers/store/paramsSlice';
import { selectCanvasMetadata, selectCanvasSlice } from 'features/controlLayers/store/selectors';
import { fetchModelConfigWithTypeGuard } from 'features/metadata/util/modelFetchingHelpers';
@@ -16,7 +15,6 @@ import { getGenerationMode } from 'features/nodes/util/graph/generation/getGener
import { Graph } from 'features/nodes/util/graph/generation/Graph';
import {
CANVAS_OUTPUT_PREFIX,
getBoardField,
getSizes,
selectPresetModifiedPrompts,
} from 'features/nodes/util/graph/graphBuilderUtils';
@@ -36,7 +34,6 @@ export const buildCogView4Graph = async (
log.debug({ generationMode }, 'Building CogView4 graph');
const params = selectParamsSlice(state);
const canvasSettings = selectCanvasSettingsSlice(state);
const canvas = selectCanvasSlice(state);
const { bbox } = canvas;
@@ -176,13 +173,7 @@ export const buildCogView4Graph = async (
canvasOutput = addWatermarker(g, canvasOutput);
}
// This image will be staged, should not be saved to the gallery or added to a board.
const is_intermediate = canvasSettings.sendToCanvas;
const board = canvasSettings.sendToCanvas ? undefined : getBoardField(state);
if (!canvasSettings.sendToCanvas) {
g.upsertMetadata(selectCanvasMetadata(state));
}
g.upsertMetadata(selectCanvasMetadata(state));
g.updateNode(canvasOutput, {
id: getPrefixedId(CANVAS_OUTPUT_PREFIX),

View File

@@ -2,7 +2,6 @@ import { logger } from 'app/logging/logger';
import type { RootState } from 'app/store/store';
import type { CanvasManager } from 'features/controlLayers/konva/CanvasManager';
import { getPrefixedId } from 'features/controlLayers/konva/util';
import { selectCanvasSettingsSlice } from 'features/controlLayers/store/canvasSettingsSlice';
import { selectMainModelConfig, selectParamsSlice } from 'features/controlLayers/store/paramsSlice';
import { selectCanvasMetadata, selectCanvasSlice } from 'features/controlLayers/store/selectors';
import { addFLUXFill } from 'features/nodes/util/graph/generation/addFLUXFill';
@@ -19,7 +18,6 @@ import { getGenerationMode } from 'features/nodes/util/graph/generation/getGener
import { Graph } from 'features/nodes/util/graph/generation/Graph';
import {
CANVAS_OUTPUT_PREFIX,
getBoardField,
getSizes,
selectPresetModifiedPrompts,
} from 'features/nodes/util/graph/graphBuilderUtils';
@@ -43,7 +41,6 @@ export const buildFLUXGraph = async (state: RootState, manager?: CanvasManager |
log.debug({ generationMode }, 'Building FLUX graph');
const params = selectParamsSlice(state);
const canvasSettings = selectCanvasSettingsSlice(state);
const canvas = selectCanvasSlice(state);
const { bbox } = canvas;
@@ -335,13 +332,7 @@ export const buildFLUXGraph = async (state: RootState, manager?: CanvasManager |
canvasOutput = addWatermarker(g, canvasOutput);
}
// This image will be staged, should not be saved to the gallery or added to a board.
const is_intermediate = canvasSettings.sendToCanvas;
const board = canvasSettings.sendToCanvas ? undefined : getBoardField(state);
if (!canvasSettings.sendToCanvas) {
g.upsertMetadata(selectCanvasMetadata(state));
}
g.upsertMetadata(selectCanvasMetadata(state));
g.updateNode(canvasOutput, {
id: getPrefixedId(CANVAS_OUTPUT_PREFIX),

View File

@@ -2,18 +2,13 @@ import { logger } from 'app/logging/logger';
import type { RootState } from 'app/store/store';
import type { CanvasManager } from 'features/controlLayers/konva/CanvasManager';
import { getPrefixedId } from 'features/controlLayers/konva/util';
import { selectCanvasSettingsSlice } from 'features/controlLayers/store/canvasSettingsSlice';
import { selectMainModelConfig } from 'features/controlLayers/store/paramsSlice';
import { selectCanvasSlice } from 'features/controlLayers/store/selectors';
import { selectCanvasMetadata, selectCanvasSlice } from 'features/controlLayers/store/selectors';
import { isImagenAspectRatioID } from 'features/controlLayers/store/types';
import { zModelIdentifierField } from 'features/nodes/types/common';
import { getGenerationMode } from 'features/nodes/util/graph/generation/getGenerationMode';
import { Graph } from 'features/nodes/util/graph/generation/Graph';
import {
CANVAS_OUTPUT_PREFIX,
getBoardField,
selectPresetModifiedPrompts,
} from 'features/nodes/util/graph/graphBuilderUtils';
import { CANVAS_OUTPUT_PREFIX, selectPresetModifiedPrompts } from 'features/nodes/util/graph/graphBuilderUtils';
import { type GraphBuilderReturn, UnsupportedGenerationModeError } from 'features/nodes/util/graph/types';
import { t } from 'i18next';
import type { Equals } from 'tsafe';
@@ -34,7 +29,6 @@ export const buildImagen3Graph = async (
log.debug({ generationMode }, 'Building Imagen3 graph');
const canvas = selectCanvasSlice(state);
const canvasSettings = selectCanvasSettingsSlice(state);
const { bbox } = canvas;
const { positivePrompt, negativePrompt } = selectPresetModifiedPrompts(state);
@@ -45,9 +39,6 @@ export const buildImagen3Graph = async (
assert(isImagenAspectRatioID(bbox.aspectRatio.id), 'Imagen3 does not support this aspect ratio');
assert(positivePrompt.length > 0, 'Imagen3 requires positive prompt to have at least one character');
const is_intermediate = canvasSettings.sendToCanvas;
const board = canvasSettings.sendToCanvas ? undefined : getBoardField(state);
if (generationMode === 'txt2img') {
const g = new Graph(getPrefixedId('imagen3_txt2img_graph'));
const imagen3 = g.addNode({
@@ -70,6 +61,7 @@ export const buildImagen3Graph = async (
width: bbox.rect.width,
height: bbox.rect.height,
model: Graph.getModelMetadataField(model),
...selectCanvasMetadata(state),
});
return {
g,

View File

@@ -2,18 +2,13 @@ import { logger } from 'app/logging/logger';
import type { RootState } from 'app/store/store';
import type { CanvasManager } from 'features/controlLayers/konva/CanvasManager';
import { getPrefixedId } from 'features/controlLayers/konva/util';
import { selectCanvasSettingsSlice } from 'features/controlLayers/store/canvasSettingsSlice';
import { selectMainModelConfig } from 'features/controlLayers/store/paramsSlice';
import { selectCanvasSlice } from 'features/controlLayers/store/selectors';
import { selectCanvasMetadata, selectCanvasSlice } from 'features/controlLayers/store/selectors';
import { isImagenAspectRatioID } from 'features/controlLayers/store/types';
import { zModelIdentifierField } from 'features/nodes/types/common';
import { getGenerationMode } from 'features/nodes/util/graph/generation/getGenerationMode';
import { Graph } from 'features/nodes/util/graph/generation/Graph';
import {
CANVAS_OUTPUT_PREFIX,
getBoardField,
selectPresetModifiedPrompts,
} from 'features/nodes/util/graph/graphBuilderUtils';
import { CANVAS_OUTPUT_PREFIX, selectPresetModifiedPrompts } from 'features/nodes/util/graph/graphBuilderUtils';
import { type GraphBuilderReturn, UnsupportedGenerationModeError } from 'features/nodes/util/graph/types';
import { t } from 'i18next';
import type { Equals } from 'tsafe';
@@ -34,7 +29,6 @@ export const buildImagen4Graph = async (
log.debug({ generationMode }, 'Building Imagen4 graph');
const canvas = selectCanvasSlice(state);
const canvasSettings = selectCanvasSettingsSlice(state);
const { bbox } = canvas;
const { positivePrompt, negativePrompt } = selectPresetModifiedPrompts(state);
@@ -45,9 +39,6 @@ export const buildImagen4Graph = async (
assert(isImagenAspectRatioID(bbox.aspectRatio.id), 'Imagen4 does not support this aspect ratio');
assert(positivePrompt.length > 0, 'Imagen4 requires positive prompt to have at least one character');
const is_intermediate = canvasSettings.sendToCanvas;
const board = canvasSettings.sendToCanvas ? undefined : getBoardField(state);
if (generationMode === 'txt2img') {
const g = new Graph(getPrefixedId('imagen4_txt2img_graph'));
const imagen4 = g.addNode({
@@ -70,7 +61,9 @@ export const buildImagen4Graph = async (
width: bbox.rect.width,
height: bbox.rect.height,
model: Graph.getModelMetadataField(model),
...selectCanvasMetadata(state),
});
return {
g,
seedFieldIdentifier: { nodeId: imagen4.id, fieldName: 'seed' },

View File

@@ -2,7 +2,6 @@ import { logger } from 'app/logging/logger';
import type { RootState } from 'app/store/store';
import type { CanvasManager } from 'features/controlLayers/konva/CanvasManager';
import { getPrefixedId } from 'features/controlLayers/konva/util';
import { selectCanvasSettingsSlice } from 'features/controlLayers/store/canvasSettingsSlice';
import { selectMainModelConfig, selectParamsSlice } from 'features/controlLayers/store/paramsSlice';
import { selectCanvasMetadata, selectCanvasSlice } from 'features/controlLayers/store/selectors';
import { addControlNets, addT2IAdapters } from 'features/nodes/util/graph/generation/addControlAdapters';
@@ -20,7 +19,6 @@ import { getGenerationMode } from 'features/nodes/util/graph/generation/getGener
import { Graph } from 'features/nodes/util/graph/generation/Graph';
import {
CANVAS_OUTPUT_PREFIX,
getBoardField,
getSizes,
selectPresetModifiedPrompts,
} from 'features/nodes/util/graph/graphBuilderUtils';
@@ -38,7 +36,6 @@ export const buildSD1Graph = async (state: RootState, manager?: CanvasManager |
log.debug({ generationMode }, 'Building SD1/SD2 graph');
const params = selectParamsSlice(state);
const canvasSettings = selectCanvasSettingsSlice(state);
const canvas = selectCanvasSlice(state);
const { bbox } = canvas;
@@ -307,13 +304,7 @@ export const buildSD1Graph = async (state: RootState, manager?: CanvasManager |
canvasOutput = addWatermarker(g, canvasOutput);
}
// This image will be staged, should not be saved to the gallery or added to a board.
const is_intermediate = canvasSettings.sendToCanvas;
const board = canvasSettings.sendToCanvas ? undefined : getBoardField(state);
if (!canvasSettings.sendToCanvas) {
g.upsertMetadata(selectCanvasMetadata(state));
}
g.upsertMetadata(selectCanvasMetadata(state));
g.updateNode(canvasOutput, {
id: getPrefixedId(CANVAS_OUTPUT_PREFIX),

View File

@@ -2,8 +2,7 @@ import { logger } from 'app/logging/logger';
import type { RootState } from 'app/store/store';
import type { CanvasManager } from 'features/controlLayers/konva/CanvasManager';
import { getPrefixedId } from 'features/controlLayers/konva/util';
import { selectCanvasSettingsSlice } from 'features/controlLayers/store/canvasSettingsSlice';
import { selectMainModelConfig,selectParamsSlice } from 'features/controlLayers/store/paramsSlice';
import { selectMainModelConfig, selectParamsSlice } from 'features/controlLayers/store/paramsSlice';
import { selectCanvasMetadata, selectCanvasSlice } from 'features/controlLayers/store/selectors';
import { addImageToImage } from 'features/nodes/util/graph/generation/addImageToImage';
import { addInpaint } from 'features/nodes/util/graph/generation/addInpaint';
@@ -15,7 +14,6 @@ import { getGenerationMode } from 'features/nodes/util/graph/generation/getGener
import { Graph } from 'features/nodes/util/graph/generation/Graph';
import {
CANVAS_OUTPUT_PREFIX,
getBoardField,
getSizes,
selectPresetModifiedPrompts,
} from 'features/nodes/util/graph/graphBuilderUtils';
@@ -35,7 +33,6 @@ export const buildSD3Graph = async (state: RootState, manager?: CanvasManager |
assert(model.base === 'sd-3');
const params = selectParamsSlice(state);
const canvasSettings = selectCanvasSettingsSlice(state);
const canvas = selectCanvasSlice(state);
const { bbox } = canvas;
@@ -197,13 +194,7 @@ export const buildSD3Graph = async (state: RootState, manager?: CanvasManager |
canvasOutput = addWatermarker(g, canvasOutput);
}
// This image will be staged, should not be saved to the gallery or added to a board.
const is_intermediate = canvasSettings.sendToCanvas;
const board = canvasSettings.sendToCanvas ? undefined : getBoardField(state);
if (!canvasSettings.sendToCanvas) {
g.upsertMetadata(selectCanvasMetadata(state));
}
g.upsertMetadata(selectCanvasMetadata(state));
g.updateNode(canvasOutput, {
id: getPrefixedId(CANVAS_OUTPUT_PREFIX),

View File

@@ -2,7 +2,6 @@ import { logger } from 'app/logging/logger';
import type { RootState } from 'app/store/store';
import type { CanvasManager } from 'features/controlLayers/konva/CanvasManager';
import { getPrefixedId } from 'features/controlLayers/konva/util';
import { selectCanvasSettingsSlice } from 'features/controlLayers/store/canvasSettingsSlice';
import { selectMainModelConfig, selectParamsSlice } from 'features/controlLayers/store/paramsSlice';
import { selectCanvasMetadata, selectCanvasSlice } from 'features/controlLayers/store/selectors';
import { addControlNets, addT2IAdapters } from 'features/nodes/util/graph/generation/addControlAdapters';
@@ -20,7 +19,6 @@ import { getGenerationMode } from 'features/nodes/util/graph/generation/getGener
import { Graph } from 'features/nodes/util/graph/generation/Graph';
import {
CANVAS_OUTPUT_PREFIX,
getBoardField,
getSizes,
selectPresetModifiedPrompts,
} from 'features/nodes/util/graph/graphBuilderUtils';
@@ -42,7 +40,6 @@ export const buildSDXLGraph = async (state: RootState, manager?: CanvasManager |
assert(model.base === 'sdxl');
const params = selectParamsSlice(state);
const canvasSettings = selectCanvasSettingsSlice(state);
const canvas = selectCanvasSlice(state);
const { bbox } = canvas;
@@ -313,13 +310,7 @@ export const buildSDXLGraph = async (state: RootState, manager?: CanvasManager |
canvasOutput = addWatermarker(g, canvasOutput);
}
// This image will be staged, should not be saved to the gallery or added to a board.
const is_intermediate = canvasSettings.sendToCanvas;
const board = canvasSettings.sendToCanvas ? undefined : getBoardField(state);
if (!canvasSettings.sendToCanvas) {
g.upsertMetadata(selectCanvasMetadata(state));
}
g.upsertMetadata(selectCanvasMetadata(state));
g.updateNode(canvasOutput, {
id: getPrefixedId(CANVAS_OUTPUT_PREFIX),

View File

@@ -2,7 +2,6 @@ import type { TooltipProps } from '@invoke-ai/ui-library';
import { Divider, Flex, ListItem, Text, Tooltip, UnorderedList } from '@invoke-ai/ui-library';
import { useStore } from '@nanostores/react';
import { useAppDispatch, useAppSelector } from 'app/store/storeHooks';
import { selectSendToCanvas } from 'features/controlLayers/store/canvasSettingsSlice';
import { selectIterations } from 'features/controlLayers/store/paramsSlice';
import { selectDynamicPromptsIsLoading } from 'features/dynamicPrompts/store/dynamicPromptsSlice';
import { selectAutoAddBoardId } from 'features/gallery/store/gallerySelectors';
@@ -237,32 +236,14 @@ StyledDivider.displayName = 'StyledDivider';
const AddingToText = memo(() => {
const { t } = useTranslation();
const sendToCanvas = useAppSelector(selectSendToCanvas);
const autoAddBoardId = useAppSelector(selectAutoAddBoardId);
const autoAddBoardName = useBoardName(autoAddBoardId);
const addingTo = useMemo(() => {
if (sendToCanvas) {
return t('controlLayers.stagingOnCanvas');
}
return t('parameters.invoke.addingImagesTo');
}, [sendToCanvas, t]);
const destination = useMemo(() => {
if (sendToCanvas) {
return t('queue.canvas');
}
if (autoAddBoardName) {
return autoAddBoardName;
}
return t('boards.uncategorized');
}, [autoAddBoardName, sendToCanvas, t]);
return (
<Text fontStyle="oblique 10deg">
{addingTo}{' '}
{t('parameters.invoke.addingImagesTo')}{' '}
<Text as="span" fontWeight="semibold">
{destination}
{autoAddBoardName || t('boards.uncategorized')}
</Text>
</Text>
);

View File

@@ -1,28 +1,17 @@
import { Flex, Spacer } from '@invoke-ai/ui-library';
import { useAppSelector } from 'app/store/storeHooks';
import { CanvasManagerProviderGate } from 'features/controlLayers/contexts/CanvasManagerProviderGate';
import { ClearQueueIconButton } from 'features/queue/components/ClearQueueIconButton';
import { QueueActionsMenuButton } from 'features/queue/components/QueueActionsMenuButton';
import { SendToToggle } from 'features/queue/components/SendToToggle';
import ProgressBar from 'features/system/components/ProgressBar';
import { selectActiveTab } from 'features/ui/store/uiSelectors';
import { memo } from 'react';
import { InvokeButton } from './InvokeQueueBackButton';
const QueueControls = () => {
const tab = useAppSelector(selectActiveTab);
return (
<Flex w="full" position="relative" borderRadius="base" gap={2} flexDir="column">
<Flex gap={2}>
<InvokeButton />
<Spacer />
{tab === 'canvas' && (
<CanvasManagerProviderGate>
<SendToToggle />
</CanvasManagerProviderGate>
)}
<QueueActionsMenuButton />
<ClearQueueIconButton />
</Flex>

View File

@@ -1,205 +0,0 @@
import type { SystemStyleObject } from '@invoke-ai/ui-library';
import {
Box,
Button,
chakra,
Flex,
Icon,
Popover,
PopoverArrow,
PopoverBody,
PopoverContent,
PopoverTrigger,
Portal,
Text,
useCheckbox,
useToken,
} from '@invoke-ai/ui-library';
import { useAppDispatch, useAppSelector } from 'app/store/storeHooks';
import { selectSendToCanvas, settingsSendToCanvasChanged } from 'features/controlLayers/store/canvasSettingsSlice';
import { selectIsStaging } from 'features/controlLayers/store/canvasStagingAreaSlice';
import { $imageViewer } from 'features/gallery/components/ImageViewer/useImageViewer';
import { activeTabCanvasRightPanelChanged, setActiveTab } from 'features/ui/store/uiSlice';
import type { ChangeEvent, PropsWithChildren } from 'react';
import { memo, useCallback, useMemo } from 'react';
import { Trans, useTranslation } from 'react-i18next';
import { PiImageBold, PiPaintBrushBold } from 'react-icons/pi';
const getSx = (padding: string | number): SystemStyleObject => ({
bg: 'base.700',
w: '72px',
cursor: 'pointer',
'&[data-checked]': {
'.thumb': {
left: `calc(100% - ${padding})`,
transform: 'translateX(-100%)',
bg: 'invokeGreen.300',
},
'.unchecked-icon': {
color: 'base.50',
opacity: 0.4,
},
'.checked-icon': {
color: 'base.900',
opacity: 1,
},
},
'&[data-disabled]': {
bg: 'base.700',
'.thumb': {
bg: 'base.500',
},
'.unchecked-icon': {
color: 'base.800',
},
'.checked-icon': {
color: 'base.800',
},
},
'.thumb': {
transition: 'left 0.1s ease-in-out, transform 0.1s ease-in-out',
left: padding,
transform: 'translateX(0)',
bg: 'invokeBlue.400',
shadow: 'md',
},
'.unchecked-icon': {
color: 'base.900',
opacity: 1,
},
'.checked-icon': {
color: 'base.50',
opacity: 0.4,
},
});
export const SendToToggle = memo(() => {
const { t } = useTranslation();
const dispatch = useAppDispatch();
const sendToCanvas = useAppSelector(selectSendToCanvas);
const isStaging = useAppSelector(selectIsStaging);
const gap = useToken('space', 1);
const sx = useMemo(() => getSx(gap), [gap]);
const onChange = useCallback(
(e: ChangeEvent<HTMLInputElement>) => {
dispatch(settingsSendToCanvasChanged(e.target.checked));
},
[dispatch]
);
const { getCheckboxProps, getInputProps, htmlProps } = useCheckbox({
onChange,
isChecked: sendToCanvas,
isDisabled: isStaging,
});
return (
<Popover trigger="hover">
<PopoverTrigger>
<chakra.label {...htmlProps}>
<Flex
position="relative"
borderRadius="base"
alignItems="center"
justifyContent="space-between"
h="full"
p={gap}
gap={gap}
sx={sx}
{...getCheckboxProps()}
>
<input {...getInputProps()} hidden />
<Box className="thumb" position="absolute" borderRadius="base" w={10} top={gap} bottom={gap} />
<Flex w={10} h="full" alignItems="center" justifyContent="center" pos="relative">
<Icon
className="unchecked-icon"
w={6}
h={6}
as={PiImageBold}
aria-label={t('controlLayers.sendToGallery')}
/>
</Flex>
<Flex w={10} h="full" alignItems="center" justifyContent="center" pos="relative">
<Icon
className="checked-icon"
w={6}
h={6}
as={PiPaintBrushBold}
aria-label={t('controlLayers.sendToCanvas')}
/>
</Flex>
</Flex>
</chakra.label>
</PopoverTrigger>
<Portal>
<PopoverContent maxW={296} p={2} bg="base.200" color="base.800">
<PopoverArrow
bg="base.200"
left={sendToCanvas ? '18px !important' : '-18px !important'}
transitionProperty="all"
transitionDuration="0.2s"
/>
<PopoverBody>
<TooltipContent />
</PopoverBody>
</PopoverContent>
</Portal>
</Popover>
);
});
SendToToggle.displayName = 'SendToToggle';
const TooltipContent = memo(() => {
const { t } = useTranslation();
const sendToCanvas = useAppSelector(selectSendToCanvas);
const isStaging = useAppSelector(selectIsStaging);
if (isStaging) {
return (
<Flex flexDir="column">
<Text fontWeight="semibold">{t('controlLayers.sendingToCanvas')}</Text>
<Text fontWeight="normal">
<Trans i18nKey="controlLayers.viewProgressOnCanvas" components={{ Btn: <ActivateCanvasButton /> }} />
</Text>
</Flex>
);
}
return (
<Flex flexDir="column">
<Text fontWeight="semibold">
{sendToCanvas ? t('controlLayers.sendToCanvas') : t('controlLayers.sendToGallery')}
</Text>
<Text fontWeight="normal">
{sendToCanvas ? t('controlLayers.sendToCanvasDesc') : t('controlLayers.sendToGalleryDesc')}
</Text>
</Flex>
);
});
TooltipContent.displayName = 'TooltipContent';
const ActivateCanvasButton = memo((props: PropsWithChildren) => {
const dispatch = useAppDispatch();
const onClick = useCallback(() => {
dispatch(setActiveTab('canvas'));
dispatch(activeTabCanvasRightPanelChanged('layers'));
$imageViewer.set(false);
}, [dispatch]);
return (
<Button
onClick={onClick}
size="sm"
variant="link"
color="base.800"
_hover={{ color: 'base.900', textDecoration: 'underline' }}
>
{props.children}
</Button>
);
});
ActivateCanvasButton.displayName = 'ActivateCanvasButton';

View File

@@ -2,7 +2,7 @@ import type { PayloadAction, Selector } from '@reduxjs/toolkit';
import { createSelector, createSlice } from '@reduxjs/toolkit';
import type { PersistConfig, RootState } from 'app/store/store';
import { deepClone } from 'common/util/deepClone';
import { newSessionRequested } from 'features/controlLayers/store/actions';
import { canvasSessionStarted } from 'features/controlLayers/store/canvasStagingAreaSlice';
import { atom } from 'nanostores';
import { stylePresetsApi } from 'services/api/endpoints/stylePresets';
@@ -29,6 +29,9 @@ export const stylePresetSlice = createSlice({
},
},
extraReducers(builder) {
builder.addCase(canvasSessionStarted, () => {
return deepClone(initialState);
});
builder.addMatcher(stylePresetsApi.endpoints.deleteStylePreset.matchFulfilled, (state, action) => {
if (state.activeStylePresetId === null) {
return;
@@ -47,9 +50,6 @@ export const stylePresetSlice = createSlice({
state.activeStylePresetId = null;
}
});
builder.addMatcher(newSessionRequested, () => {
return deepClone(initialState);
});
},
});

View File

@@ -1,7 +1,7 @@
import type { PayloadAction } from '@reduxjs/toolkit';
import { createSelector, createSlice } from '@reduxjs/toolkit';
import type { PersistConfig, RootState } from 'app/store/store';
import { newSessionRequested } from 'features/controlLayers/store/actions';
import { canvasSessionStarted } from 'features/controlLayers/store/canvasStagingAreaSlice';
import type { Dimensions } from 'features/controlLayers/store/types';
import { workflowLoaded } from 'features/nodes/store/nodesSlice';
import { atom } from 'nanostores';
@@ -56,7 +56,7 @@ export const uiSlice = createSlice({
builder.addCase(workflowLoaded, (state) => {
state.activeTab = 'workflows';
});
builder.addMatcher(newSessionRequested, (state) => {
builder.addCase(canvasSessionStarted, (state) => {
state.activeTab = 'canvas';
});
},

View File

@@ -1,10 +1,12 @@
import { logger } from 'app/logging/logger';
import type { AppDispatch, RootState } from 'app/store/store';
import { deepClone } from 'common/util/deepClone';
import { stagingAreaImageStaged } from 'features/controlLayers/store/canvasStagingAreaSlice';
import { boardIdSelected, galleryViewChanged, imageSelected, offsetChanged } from 'features/gallery/store/gallerySlice';
import { $nodeExecutionStates, upsertExecutionState } from 'features/nodes/hooks/useNodeExecutionState';
import { isImageField, isImageFieldCollection } from 'features/nodes/types/common';
import { zNodeStatus } from 'features/nodes/types/invocation';
import { isCanvasOutputEvent } from 'features/nodes/util/graph/graphBuilderUtils';
import type { ApiTagDescription } from 'services/api';
import { boardsApi } from 'services/api/endpoints/boards';
import { getImageDTOSafe, imagesApi } from 'services/api/endpoints/images';
@@ -163,26 +165,18 @@ export const buildOnInvocationComplete = (getState: () => RootState, dispatch: A
};
const handleOriginCanvas = async (data: S['InvocationCompleteEvent']) => {
const imageDTOs = await getResultImageDTOs(data);
if (!isCanvasOutputEvent(data)) {
return;
}
// We expect only a single image in the canvas output
const imageDTO = imageDTOs[0];
const imageDTO = (await getResultImageDTOs(data))[0];
if (!imageDTO) {
return;
}
if (data.destination === 'canvas') {
// TODO(psyche): Can/should we let canvas handle this itself?
// if (isCanvasOutputEvent(data)) {
// if (data.result.type === 'image_output') {
// dispatch(stagingAreaImageStaged({ stagingAreaImage: { imageDTO, offsetX: 0, offsetY: 0 } }));
// }
// addImagesToGallery(data, [imageDTO]);
// }
} else if (!imageDTO.is_intermediate) {
// Desintaion is gallery
addImagesToGallery(data, [imageDTO]);
}
dispatch(stagingAreaImageStaged({ stagingAreaImage: { imageDTO, offsetX: 0, offsetY: 0 } }));
};
const handleOriginOther = async (data: S['InvocationCompleteEvent']) => {

View File

@@ -8,21 +8,28 @@ export const $socket = atom<AppSocket | null>(null);
export const $socketOptions = map<Partial<ManagerOptions & SocketOptions>>({});
export const $isConnected = atom<boolean>(false);
export const $lastProgressEvent = atom<S['InvocationProgressEvent'] | null>(null);
export const $progressImage = computed($lastProgressEvent, (val) => val?.image ?? null);
export const $canvasProgressImage = computed($lastProgressEvent, (event) => {
$lastProgressEvent.subscribe((event) => {
if (!event) {
return null;
return;
}
if (event.origin !== 'canvas') {
return null;
switch (event.destination) {
case 'workflows':
$lastWorkflowsProgressEvent.set(event);
break;
case 'upscaling':
$lastUpscalingProgressEvent.set(event);
break;
case 'canvas':
$lastCanvasProgressEvent.set(event);
break;
}
if (!event.image) {
return null;
}
return event.image;
});
export const $lastCanvasProgressEvent = atom<S['InvocationProgressEvent'] | null>(null);
export const $lastWorkflowsProgressEvent = atom<S['InvocationProgressEvent'] | null>(null);
export const $lastUpscalingProgressEvent = atom<S['InvocationProgressEvent'] | null>(null);
export const $progressImage = computed($lastProgressEvent, (val) => val?.image ?? null);
export const $hasProgressImage = computed($lastProgressEvent, (val) => Boolean(val?.image));
export const $isProgressFromCanvas = computed($lastProgressEvent, (val) => val?.destination === 'canvas');
export const $invocationProgressMessage = computed($lastProgressEvent, (val) => {
if (!val) {
return null;