mirror of
https://github.com/invoke-ai/InvokeAI.git
synced 2026-02-13 13:04:57 -05:00
refactor(ui): canvas flow (wip)
This commit is contained in:
@@ -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':
|
||||
|
||||
@@ -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({
|
||||
|
||||
@@ -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">
|
||||
|
||||
@@ -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]);
|
||||
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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
|
||||
);
|
||||
|
||||
@@ -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));
|
||||
},
|
||||
});
|
||||
|
||||
|
||||
@@ -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: [],
|
||||
};
|
||||
|
||||
@@ -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);
|
||||
});
|
||||
|
||||
@@ -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));
|
||||
},
|
||||
});
|
||||
|
||||
|
||||
@@ -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 />;
|
||||
}
|
||||
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
|
||||
@@ -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);
|
||||
});
|
||||
},
|
||||
|
||||
@@ -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'),
|
||||
|
||||
@@ -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'),
|
||||
|
||||
@@ -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'),
|
||||
|
||||
@@ -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({
|
||||
|
||||
@@ -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),
|
||||
|
||||
@@ -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),
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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' },
|
||||
|
||||
@@ -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),
|
||||
|
||||
@@ -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),
|
||||
|
||||
@@ -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),
|
||||
|
||||
@@ -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>
|
||||
);
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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';
|
||||
@@ -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);
|
||||
});
|
||||
},
|
||||
});
|
||||
|
||||
|
||||
@@ -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';
|
||||
});
|
||||
},
|
||||
|
||||
@@ -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']) => {
|
||||
|
||||
@@ -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;
|
||||
|
||||
Reference in New Issue
Block a user