mirror of
https://github.com/invoke-ai/InvokeAI.git
synced 2026-02-12 17:55:02 -05:00
experiment(ui): add generate tab
This commit is contained in:
@@ -2,8 +2,9 @@ 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 { rasterLayerAdded } from 'features/controlLayers/store/canvasSlice';
|
||||
import { canvasSessionTypeChanged } from 'features/controlLayers/store/canvasStagingAreaSlice';
|
||||
import { paramsReset } from 'features/controlLayers/store/paramsSlice';
|
||||
import type { CanvasRasterLayerState } from 'features/controlLayers/store/types';
|
||||
import { imageDTOToImageObject } from 'features/controlLayers/store/util';
|
||||
import { $imageViewer } from 'features/gallery/components/ImageViewer/useImageViewer';
|
||||
@@ -90,9 +91,8 @@ export const useStudioInitAction = (action?: StudioInitAction) => {
|
||||
const overrides: Partial<CanvasRasterLayerState> = {
|
||||
objects: [imageObject],
|
||||
};
|
||||
store.dispatch(canvasSessionTypeChanged({ type: 'advanced' }));
|
||||
store.dispatch(canvasReset());
|
||||
store.dispatch(rasterLayerAdded({ overrides, isSelected: true }));
|
||||
store.dispatch(setActiveTab('canvas'));
|
||||
store.dispatch(sentImageToCanvas());
|
||||
$imageViewer.set(false);
|
||||
toast({
|
||||
@@ -116,9 +116,9 @@ export const useStudioInitAction = (action?: StudioInitAction) => {
|
||||
return;
|
||||
}
|
||||
const metadata = getImageMetadataResult.value;
|
||||
store.dispatch(canvasReset());
|
||||
// This shows a toast
|
||||
await parseAndRecallAllMetadata(metadata, true);
|
||||
store.dispatch(setActiveTab('canvas'));
|
||||
},
|
||||
[store, t]
|
||||
);
|
||||
@@ -162,15 +162,13 @@ 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(canvasSessionTypeChanged({ type: 'simple' }));
|
||||
store.dispatch(setActiveTab('canvas'));
|
||||
store.dispatch(paramsReset());
|
||||
store.dispatch(activeTabCanvasRightPanelChanged('gallery'));
|
||||
$imageViewer.set(true);
|
||||
break;
|
||||
case 'canvas':
|
||||
// Go to the canvas tab, close the image viewer, and disable send-to-gallery mode
|
||||
store.dispatch(canvasSessionTypeChanged({ type: 'advanced' }));
|
||||
store.dispatch(setActiveTab('canvas'));
|
||||
store.dispatch(canvasReset());
|
||||
$imageViewer.set(false);
|
||||
break;
|
||||
case 'workflows':
|
||||
|
||||
@@ -6,8 +6,10 @@ import { extractMessageFromAssertionError } from 'common/util/extractMessageFrom
|
||||
import { withResult, withResultAsync } from 'common/util/result';
|
||||
import { parseify } from 'common/util/serialize';
|
||||
import {
|
||||
canvasSessionGenerationStarted,
|
||||
canvasSessionIdCreated,
|
||||
generateSessionIdCreated,
|
||||
selectCanvasSessionId,
|
||||
selectGenerateSessionId,
|
||||
} from 'features/controlLayers/store/canvasStagingAreaSlice';
|
||||
import { $canvasManager } from 'features/controlLayers/store/ephemeral';
|
||||
import { prepareLinearUIBatch } from 'features/nodes/util/graph/buildLinearBatchConfig';
|
||||
@@ -22,6 +24,7 @@ import { buildSD3Graph } from 'features/nodes/util/graph/generation/buildSD3Grap
|
||||
import { buildSDXLGraph } from 'features/nodes/util/graph/generation/buildSDXLGraph';
|
||||
import { UnsupportedGenerationModeError } from 'features/nodes/util/graph/types';
|
||||
import { toast } from 'features/toast/toast';
|
||||
import { selectActiveTab } from 'features/ui/store/uiSelectors';
|
||||
import { serializeError } from 'serialize-error';
|
||||
import { enqueueMutationFixedCacheKeyOptions, queueApi } from 'services/api/endpoints/queue';
|
||||
import { assert, AssertionError } from 'tsafe';
|
||||
@@ -36,12 +39,27 @@ export const addEnqueueRequestedLinear = (startAppListening: AppStartListening)
|
||||
effect: async (action, { getState, dispatch }) => {
|
||||
log.debug('Enqueue requested');
|
||||
|
||||
if (!selectCanvasSessionId(getState())) {
|
||||
dispatch(canvasSessionGenerationStarted());
|
||||
const tab = selectActiveTab(getState());
|
||||
let sessionId = null;
|
||||
if (tab === 'generate') {
|
||||
sessionId = selectGenerateSessionId(getState());
|
||||
if (!sessionId) {
|
||||
dispatch(generateSessionIdCreated());
|
||||
sessionId = selectGenerateSessionId(getState());
|
||||
}
|
||||
} else if (tab === 'canvas') {
|
||||
sessionId = selectCanvasSessionId(getState());
|
||||
if (!sessionId) {
|
||||
dispatch(canvasSessionIdCreated());
|
||||
sessionId = selectCanvasSessionId(getState());
|
||||
}
|
||||
} else {
|
||||
log.warn(`Enqueue requested in unsupported tab ${tab}`);
|
||||
return;
|
||||
}
|
||||
|
||||
const state = getState();
|
||||
const destination = state.canvasSession.id;
|
||||
const destination = sessionId;
|
||||
assert(destination !== null);
|
||||
|
||||
const { prepend } = action.payload;
|
||||
|
||||
@@ -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 { canvasSessionTypeChanged } from 'features/controlLayers/store/canvasStagingAreaSlice';
|
||||
import { canvasSessionReset, generateSessionReset } from 'features/controlLayers/store/canvasStagingAreaSlice';
|
||||
import {
|
||||
selectSystemShouldConfirmOnNewSession,
|
||||
shouldConfirmOnNewSessionToggled,
|
||||
@@ -20,7 +20,7 @@ export const useNewGallerySession = () => {
|
||||
const newSessionDialog = useNewGallerySessionDialog();
|
||||
|
||||
const newGallerySessionImmediate = useCallback(() => {
|
||||
dispatch(canvasSessionTypeChanged({ type: 'simple' }));
|
||||
dispatch(generateSessionReset());
|
||||
dispatch(activeTabCanvasRightPanelChanged('gallery'));
|
||||
}, [dispatch]);
|
||||
|
||||
@@ -41,7 +41,7 @@ export const useNewCanvasSession = () => {
|
||||
const newSessionDialog = useNewCanvasSessionDialog();
|
||||
|
||||
const newCanvasSessionImmediate = useCallback(() => {
|
||||
dispatch(canvasSessionTypeChanged({ type: 'advanced' }));
|
||||
dispatch(canvasSessionReset());
|
||||
dispatch(activeTabCanvasRightPanelChanged('layers'));
|
||||
}, [dispatch]);
|
||||
|
||||
|
||||
@@ -6,13 +6,22 @@ import { InitialStateAddAStyleReference } from 'features/controlLayers/component
|
||||
import { InitialStateEditImageCard } from 'features/controlLayers/components/SimpleSession/InitialStateEditImageCard';
|
||||
import { InitialStateGenerateFromText } from 'features/controlLayers/components/SimpleSession/InitialStateGenerateFromText';
|
||||
import { InitialStateUseALayoutImageCard } from 'features/controlLayers/components/SimpleSession/InitialStateUseALayoutImageCard';
|
||||
import { canvasSessionTypeChanged } from 'features/controlLayers/store/canvasStagingAreaSlice';
|
||||
import { toast } from 'features/toast/toast';
|
||||
import { setActiveTab } from 'features/ui/store/uiSlice';
|
||||
import { memo, useCallback } from 'react';
|
||||
|
||||
export const InitialState = memo(() => {
|
||||
const dispatch = useAppDispatch();
|
||||
const newCanvasSession = useCallback(() => {
|
||||
dispatch(canvasSessionTypeChanged({ type: 'advanced' }));
|
||||
dispatch(setActiveTab('canvas'));
|
||||
toast({
|
||||
title: 'Switched to Canvas',
|
||||
description: 'You are in advanced mode yadda yadda.',
|
||||
status: 'info',
|
||||
position: 'top',
|
||||
// isClosable: false,
|
||||
duration: 5000,
|
||||
});
|
||||
}, [dispatch]);
|
||||
|
||||
return (
|
||||
|
||||
@@ -1,28 +1,31 @@
|
||||
import type { GridItemProps } from '@invoke-ai/ui-library';
|
||||
import { Button, GridItem } from '@invoke-ai/ui-library';
|
||||
import { Button, forwardRef, GridItem } from '@invoke-ai/ui-library';
|
||||
import { memo } from 'react';
|
||||
|
||||
export const InitialStateButtonGridItem = memo(({ children, ...rest }: GridItemProps) => {
|
||||
return (
|
||||
<GridItem
|
||||
as={Button}
|
||||
variant="outline"
|
||||
display="flex"
|
||||
position="relative"
|
||||
flexDir="column"
|
||||
alignItems="center"
|
||||
borderWidth={1}
|
||||
borderRadius="base"
|
||||
p={2}
|
||||
pt={6}
|
||||
gap={2}
|
||||
w="full"
|
||||
h="full"
|
||||
{...rest}
|
||||
>
|
||||
{children}
|
||||
</GridItem>
|
||||
);
|
||||
});
|
||||
export const InitialStateButtonGridItem = memo(
|
||||
forwardRef(({ children, ...rest }: GridItemProps, ref) => {
|
||||
return (
|
||||
<GridItem
|
||||
ref={ref}
|
||||
as={Button}
|
||||
variant="outline"
|
||||
display="flex"
|
||||
position="relative"
|
||||
flexDir="column"
|
||||
alignItems="center"
|
||||
borderWidth={1}
|
||||
borderRadius="base"
|
||||
p={2}
|
||||
pt={6}
|
||||
gap={2}
|
||||
w="full"
|
||||
h="full"
|
||||
{...rest}
|
||||
>
|
||||
{children}
|
||||
</GridItem>
|
||||
);
|
||||
})
|
||||
);
|
||||
|
||||
InitialStateButtonGridItem.displayName = 'InitialStateButtonGridItem';
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
/* eslint-disable i18next/no-literal-string */
|
||||
import { Flex, Heading, Spacer } from '@invoke-ai/ui-library';
|
||||
import { StartOverButton } from 'features/controlLayers/components/StartOverButton';
|
||||
import { memo } from 'react';
|
||||
|
||||
export const StagingAreaHeader = memo(() => {
|
||||
@@ -8,7 +7,6 @@ export const StagingAreaHeader = memo(() => {
|
||||
<Flex gap={2} w="full" alignItems="center" px={2}>
|
||||
<Heading size="sm">Review Session</Heading>
|
||||
<Spacer />
|
||||
<StartOverButton />
|
||||
</Flex>
|
||||
);
|
||||
});
|
||||
|
||||
@@ -5,7 +5,7 @@ import { useIsRegionFocused } from 'common/hooks/focus';
|
||||
import { useCanvasSessionContext } from 'features/controlLayers/components/SimpleSession/context';
|
||||
import { useCanvasManager } from 'features/controlLayers/contexts/CanvasManagerProviderGate';
|
||||
import { rasterLayerAdded } from 'features/controlLayers/store/canvasSlice';
|
||||
import { canvasSessionGenerationFinished } from 'features/controlLayers/store/canvasStagingAreaSlice';
|
||||
import { canvasSessionReset } from 'features/controlLayers/store/canvasStagingAreaSlice';
|
||||
import { selectBboxRect, selectSelectedEntityIdentifier } from 'features/controlLayers/store/selectors';
|
||||
import type { CanvasRasterLayerState } from 'features/controlLayers/store/types';
|
||||
import { imageNameToImageObject } from 'features/controlLayers/store/util';
|
||||
@@ -40,7 +40,7 @@ export const StagingAreaToolbarAcceptButton = memo(() => {
|
||||
};
|
||||
|
||||
dispatch(rasterLayerAdded({ overrides, isSelected: selectedEntityIdentifier?.type === 'raster_layer' }));
|
||||
dispatch(canvasSessionGenerationFinished());
|
||||
dispatch(canvasSessionReset());
|
||||
deleteQueueItemsByDestination.trigger(ctx.session.id);
|
||||
}, [
|
||||
selectedItemImageDTO,
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import { IconButton } from '@invoke-ai/ui-library';
|
||||
import { useAppDispatch } from 'app/store/storeHooks';
|
||||
import { useCanvasSessionContext } from 'features/controlLayers/components/SimpleSession/context';
|
||||
import { canvasSessionGenerationFinished } from 'features/controlLayers/store/canvasStagingAreaSlice';
|
||||
import { canvasSessionReset, generateSessionReset } from 'features/controlLayers/store/canvasStagingAreaSlice';
|
||||
import { useDeleteQueueItemsByDestination } from 'features/queue/hooks/useDeleteQueueItemsByDestination';
|
||||
import { memo, useCallback } from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
@@ -15,8 +15,13 @@ export const StagingAreaToolbarDiscardAllButton = memo(({ isDisabled }: { isDisa
|
||||
|
||||
const discardAll = useCallback(() => {
|
||||
deleteQueueItemsByDestination.trigger(ctx.session.id);
|
||||
dispatch(canvasSessionGenerationFinished());
|
||||
}, [deleteQueueItemsByDestination, ctx.session.id, dispatch]);
|
||||
if (ctx.session.type === 'advanced') {
|
||||
dispatch(canvasSessionReset());
|
||||
} else {
|
||||
// ctx.session.type === 'simple'
|
||||
dispatch(generateSessionReset());
|
||||
}
|
||||
}, [deleteQueueItemsByDestination, ctx.session.id, ctx.session.type, dispatch]);
|
||||
|
||||
return (
|
||||
<IconButton
|
||||
|
||||
@@ -2,7 +2,7 @@ import { IconButton } from '@invoke-ai/ui-library';
|
||||
import { useStore } from '@nanostores/react';
|
||||
import { useAppDispatch } from 'app/store/storeHooks';
|
||||
import { useCanvasSessionContext } from 'features/controlLayers/components/SimpleSession/context';
|
||||
import { canvasSessionGenerationFinished } from 'features/controlLayers/store/canvasStagingAreaSlice';
|
||||
import { canvasSessionReset, generateSessionReset } from 'features/controlLayers/store/canvasStagingAreaSlice';
|
||||
import { useDeleteQueueItem } from 'features/queue/hooks/useDeleteQueueItem';
|
||||
import { memo, useCallback } from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
@@ -23,9 +23,14 @@ export const StagingAreaToolbarDiscardSelectedButton = memo(({ isDisabled }: { i
|
||||
await deleteQueueItem.trigger(selectedItemId);
|
||||
const itemCount = ctx.$itemCount.get();
|
||||
if (itemCount <= 1) {
|
||||
dispatch(canvasSessionGenerationFinished());
|
||||
if (ctx.session.type === 'advanced') {
|
||||
dispatch(canvasSessionReset());
|
||||
} else {
|
||||
// ctx.session.type === 'simple'
|
||||
dispatch(generateSessionReset());
|
||||
}
|
||||
}
|
||||
}, [selectedItemId, ctx.$itemCount, deleteQueueItem, dispatch]);
|
||||
}, [selectedItemId, deleteQueueItem, ctx.$itemCount, ctx.session.type, dispatch]);
|
||||
|
||||
return (
|
||||
<IconButton
|
||||
|
||||
@@ -1,15 +1,16 @@
|
||||
/* eslint-disable i18next/no-literal-string */
|
||||
import { Button } from '@invoke-ai/ui-library';
|
||||
import { useAppDispatch } from 'app/store/storeHooks';
|
||||
import { canvasSessionTypeChanged } from 'features/controlLayers/store/canvasStagingAreaSlice';
|
||||
import { $simpleId } from 'features/ui/components/MainPanelContent';
|
||||
import { memo, useCallback } from 'react';
|
||||
|
||||
export const StartOverButton = memo(() => {
|
||||
const dispatch = useAppDispatch();
|
||||
|
||||
const startOver = useCallback(() => {
|
||||
dispatch(canvasSessionTypeChanged({ type: 'simple' }));
|
||||
}, [dispatch]);
|
||||
// dispatch(canvasSessionTypeChanged({ type: 'simple' }));
|
||||
$simpleId.set(null);
|
||||
}, []);
|
||||
|
||||
return (
|
||||
<Button size="sm" variant="link" alignSelf="stretch" onClick={startOver} px={2}>
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
/* eslint-disable i18next/no-literal-string */
|
||||
import { Divider, Flex, Heading } from '@invoke-ai/ui-library';
|
||||
import { CanvasSettingsPopover } from 'features/controlLayers/components/Settings/CanvasSettingsPopover';
|
||||
import { StartOverButton } from 'features/controlLayers/components/StartOverButton';
|
||||
import { ToolColorPicker } from 'features/controlLayers/components/Tool/ToolFillColorPicker';
|
||||
import { ToolSettings } from 'features/controlLayers/components/Tool/ToolSettings';
|
||||
import { CanvasToolbarFitBboxToLayersButton } from 'features/controlLayers/components/Toolbar/CanvasToolbarFitBboxToLayersButton';
|
||||
@@ -51,7 +50,6 @@ export const CanvasToolbar = memo(() => {
|
||||
<CanvasSettingsPopover />
|
||||
</Flex>
|
||||
<Divider orientation="vertical" />
|
||||
<StartOverButton />
|
||||
</Flex>
|
||||
);
|
||||
});
|
||||
|
||||
@@ -6,7 +6,6 @@ import { deepClone } from 'common/util/deepClone';
|
||||
import { roundDownToMultiple, roundToMultiple } from 'common/util/roundDownToMultiple';
|
||||
import { getPrefixedId } from 'features/controlLayers/konva/util';
|
||||
import { canvasReset } from 'features/controlLayers/store/actions';
|
||||
import { canvasSessionTypeChanged } from 'features/controlLayers/store/canvasStagingAreaSlice';
|
||||
import { modelChanged } from 'features/controlLayers/store/paramsSlice';
|
||||
import {
|
||||
selectAllEntities,
|
||||
@@ -1595,7 +1594,6 @@ export const canvasSlice = createSlice({
|
||||
syncScaledSize(state);
|
||||
}
|
||||
});
|
||||
builder.addCase(canvasSessionTypeChanged, (state) => resetState(state));
|
||||
},
|
||||
});
|
||||
|
||||
|
||||
@@ -5,13 +5,13 @@ import { getPrefixedId } from 'features/controlLayers/konva/util';
|
||||
import { canvasReset } from 'features/controlLayers/store/actions';
|
||||
|
||||
type CanvasStagingAreaState = {
|
||||
type: 'simple' | 'advanced';
|
||||
id: string | null;
|
||||
generateSessionId: string | null;
|
||||
canvasSessionId: string | null;
|
||||
};
|
||||
|
||||
const INITIAL_STATE: CanvasStagingAreaState = {
|
||||
type: 'simple',
|
||||
id: null,
|
||||
generateSessionId: null,
|
||||
canvasSessionId: null,
|
||||
};
|
||||
|
||||
const getInitialState = (): CanvasStagingAreaState => deepClone(INITIAL_STATE);
|
||||
@@ -20,30 +20,39 @@ export const canvasSessionSlice = createSlice({
|
||||
name: 'canvasSession',
|
||||
initialState: getInitialState(),
|
||||
reducers: {
|
||||
canvasSessionTypeChanged: (state, action: PayloadAction<{ type: CanvasStagingAreaState['type'] }>) => {
|
||||
const { type } = action.payload;
|
||||
state.type = type;
|
||||
state.id = null;
|
||||
},
|
||||
canvasSessionGenerationStarted: {
|
||||
generateSessionIdCreated: {
|
||||
reducer: (state, action: PayloadAction<{ id: string }>) => {
|
||||
const { id } = action.payload;
|
||||
state.id = id;
|
||||
state.generateSessionId = id;
|
||||
},
|
||||
prepare: () => ({
|
||||
payload: { id: getPrefixedId('generate') },
|
||||
}),
|
||||
},
|
||||
generateSessionReset: (state) => {
|
||||
state.generateSessionId = null;
|
||||
},
|
||||
canvasSessionIdCreated: {
|
||||
reducer: (state, action: PayloadAction<{ id: string }>) => {
|
||||
const { id } = action.payload;
|
||||
state.canvasSessionId = id;
|
||||
},
|
||||
prepare: () => ({
|
||||
payload: { id: getPrefixedId('canvas') },
|
||||
}),
|
||||
},
|
||||
canvasSessionGenerationFinished: (state) => {
|
||||
state.id = null;
|
||||
canvasSessionReset: (state) => {
|
||||
state.canvasSessionId = null;
|
||||
},
|
||||
},
|
||||
extraReducers(builder) {
|
||||
builder.addCase(canvasReset, () => getInitialState());
|
||||
builder.addCase(canvasReset, (state) => {
|
||||
state.canvasSessionId = null;
|
||||
});
|
||||
},
|
||||
});
|
||||
|
||||
export const { canvasSessionTypeChanged, canvasSessionGenerationStarted, canvasSessionGenerationFinished } =
|
||||
export const { generateSessionIdCreated, generateSessionReset, canvasSessionIdCreated, canvasSessionReset } =
|
||||
canvasSessionSlice.actions;
|
||||
|
||||
/* eslint-disable-next-line @typescript-eslint/no-explicit-any */
|
||||
@@ -60,6 +69,9 @@ export const canvasStagingAreaPersistConfig: PersistConfig<CanvasStagingAreaStat
|
||||
|
||||
export const selectCanvasSessionSlice = (s: RootState) => s[canvasSessionSlice.name];
|
||||
|
||||
export const selectIsStaging = createSelector(selectCanvasSessionSlice, ({ id }) => id !== null);
|
||||
export const selectCanvasSessionType = createSelector(selectCanvasSessionSlice, ({ type }) => type);
|
||||
export const selectCanvasSessionId = createSelector(selectCanvasSessionSlice, ({ id }) => id);
|
||||
export const selectCanvasSessionId = createSelector(selectCanvasSessionSlice, ({ canvasSessionId }) => canvasSessionId);
|
||||
export const selectGenerateSessionId = createSelector(
|
||||
selectCanvasSessionSlice,
|
||||
({ generateSessionId }) => generateSessionId
|
||||
);
|
||||
export const selectIsStaging = createSelector(selectCanvasSessionId, (canvasSessionId) => canvasSessionId !== null);
|
||||
|
||||
@@ -1,7 +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 { canvasSessionTypeChanged } from 'features/controlLayers/store/canvasStagingAreaSlice';
|
||||
import { paramsReset } from 'features/controlLayers/store/paramsSlice';
|
||||
import type { LoRA } from 'features/controlLayers/store/types';
|
||||
import { zModelIdentifierField } from 'features/nodes/types/common';
|
||||
import type { LoRAModelConfig } from 'services/api/types';
|
||||
@@ -64,7 +64,7 @@ export const lorasSlice = createSlice({
|
||||
},
|
||||
},
|
||||
extraReducers(builder) {
|
||||
builder.addCase(canvasSessionTypeChanged, () => {
|
||||
builder.addCase(paramsReset, () => {
|
||||
// When a new session is requested, clear all LoRAs
|
||||
return deepClone(initialState);
|
||||
});
|
||||
|
||||
@@ -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 { canvasSessionTypeChanged } from 'features/controlLayers/store/canvasStagingAreaSlice';
|
||||
import { paramsReset } from 'features/controlLayers/store/paramsSlice';
|
||||
import type { ParameterHRFMethod, ParameterStrength } from 'features/parameters/types/parameterSchemas';
|
||||
|
||||
interface HRFState {
|
||||
@@ -34,7 +34,7 @@ export const hrfSlice = createSlice({
|
||||
},
|
||||
},
|
||||
extraReducers(builder) {
|
||||
builder.addCase(canvasSessionTypeChanged, () => {
|
||||
builder.addCase(paramsReset, () => {
|
||||
return deepClone(initialHRFState);
|
||||
});
|
||||
},
|
||||
|
||||
@@ -6,6 +6,7 @@ import {
|
||||
} from 'features/controlLayers/hooks/addLayerHooks';
|
||||
import { CanvasEntityTransformer } from 'features/controlLayers/konva/CanvasEntity/CanvasEntityTransformer';
|
||||
import { getPrefixedId } from 'features/controlLayers/konva/util';
|
||||
import { canvasReset } from 'features/controlLayers/store/actions';
|
||||
import {
|
||||
bboxChangedFromCanvas,
|
||||
canvasClearHistory,
|
||||
@@ -16,7 +17,6 @@ import {
|
||||
rgAdded,
|
||||
rgRefImageImageChanged,
|
||||
} from 'features/controlLayers/store/canvasSlice';
|
||||
import { canvasSessionTypeChanged } from 'features/controlLayers/store/canvasStagingAreaSlice';
|
||||
import { refImageAdded, refImageImageChanged } from 'features/controlLayers/store/refImagesSlice';
|
||||
import { selectBboxModelBase, selectBboxRect } from 'features/controlLayers/store/selectors';
|
||||
import type {
|
||||
@@ -185,7 +185,7 @@ export const newCanvasFromImage = async (arg: {
|
||||
objects: [imageObject],
|
||||
} satisfies Partial<CanvasRasterLayerState>;
|
||||
addFitOnLayerInitCallback(overrides.id);
|
||||
dispatch(canvasSessionTypeChanged({ type: 'advanced' }));
|
||||
dispatch(canvasReset());
|
||||
// The `bboxChangedFromCanvas` reducer does no validation! Careful!
|
||||
dispatch(bboxChangedFromCanvas({ x: 0, y: 0, width, height }));
|
||||
dispatch(rasterLayerAdded({ overrides, isSelected: true }));
|
||||
@@ -202,7 +202,7 @@ export const newCanvasFromImage = async (arg: {
|
||||
controlAdapter: deepClone(initialControlNet),
|
||||
} satisfies Partial<CanvasControlLayerState>;
|
||||
addFitOnLayerInitCallback(overrides.id);
|
||||
dispatch(canvasSessionTypeChanged({ type: 'advanced' }));
|
||||
dispatch(canvasReset());
|
||||
// The `bboxChangedFromCanvas` reducer does no validation! Careful!
|
||||
dispatch(bboxChangedFromCanvas({ x: 0, y: 0, width, height }));
|
||||
dispatch(controlLayerAdded({ overrides, isSelected: true }));
|
||||
@@ -218,7 +218,7 @@ export const newCanvasFromImage = async (arg: {
|
||||
objects: [imageObject],
|
||||
} satisfies Partial<CanvasInpaintMaskState>;
|
||||
addFitOnLayerInitCallback(overrides.id);
|
||||
dispatch(canvasSessionTypeChanged({ type: 'advanced' }));
|
||||
dispatch(canvasReset());
|
||||
// The `bboxChangedFromCanvas` reducer does no validation! Careful!
|
||||
dispatch(bboxChangedFromCanvas({ x: 0, y: 0, width, height }));
|
||||
dispatch(inpaintMaskAdded({ overrides, isSelected: true }));
|
||||
@@ -234,7 +234,7 @@ export const newCanvasFromImage = async (arg: {
|
||||
objects: [imageObject],
|
||||
} satisfies Partial<CanvasRegionalGuidanceState>;
|
||||
addFitOnLayerInitCallback(overrides.id);
|
||||
dispatch(canvasSessionTypeChanged({ type: 'advanced' }));
|
||||
dispatch(canvasReset());
|
||||
// The `bboxChangedFromCanvas` reducer does no validation! Careful!
|
||||
dispatch(bboxChangedFromCanvas({ x: 0, y: 0, width, height }));
|
||||
dispatch(rgAdded({ overrides, isSelected: true }));
|
||||
@@ -247,7 +247,7 @@ export const newCanvasFromImage = async (arg: {
|
||||
case 'reference_image': {
|
||||
const config = deepClone(getDefaultRefImageConfig(getState));
|
||||
config.image = imageDTOToImageWithDims(imageDTO);
|
||||
dispatch(canvasSessionTypeChanged({ type: 'advanced' }));
|
||||
dispatch(canvasReset());
|
||||
dispatch(refImageAdded({ overrides: { config } }));
|
||||
if (withInpaintMask) {
|
||||
dispatch(inpaintMaskAdded({ isSelected: true, isBookmarked: true }));
|
||||
@@ -259,7 +259,7 @@ export const newCanvasFromImage = async (arg: {
|
||||
const config = getDefaultRegionalGuidanceRefImageConfig(getState);
|
||||
config.image = imageDTOToImageWithDims(imageDTO);
|
||||
const referenceImages = [{ id: getPrefixedId('regional_guidance_reference_image'), config }];
|
||||
dispatch(canvasSessionTypeChanged({ type: 'advanced' }));
|
||||
dispatch(canvasReset());
|
||||
dispatch(rgAdded({ overrides: { referenceImages }, isSelected: true }));
|
||||
if (withInpaintMask) {
|
||||
dispatch(inpaintMaskAdded({ isSelected: true, isBookmarked: true }));
|
||||
|
||||
@@ -1,13 +1,13 @@
|
||||
import { createSelector } from '@reduxjs/toolkit';
|
||||
import type { RootState } from 'app/store/store';
|
||||
import { getPrefixedId } from 'features/controlLayers/konva/util';
|
||||
import { selectCanvasSessionType } from 'features/controlLayers/store/canvasStagingAreaSlice';
|
||||
import { selectParamsSlice } from 'features/controlLayers/store/paramsSlice';
|
||||
import type { CanvasState, ParamsState } from 'features/controlLayers/store/types';
|
||||
import type { BoardField } from 'features/nodes/types/common';
|
||||
import type { Graph } from 'features/nodes/util/graph/generation/Graph';
|
||||
import { buildPresetModifiedPrompt } from 'features/stylePresets/hooks/usePresetModifiedPrompts';
|
||||
import { selectStylePresetSlice } from 'features/stylePresets/store/stylePresetSlice';
|
||||
import { selectActiveTab } from 'features/ui/store/uiSelectors';
|
||||
import { pick } from 'lodash-es';
|
||||
import { selectListStylePresetsRequestState } from 'services/api/endpoints/stylePresets';
|
||||
import type { Invocation, S } from 'services/api/types';
|
||||
@@ -36,9 +36,9 @@ export const getBoardField = (state: RootState): BoardField | undefined => {
|
||||
export const selectCanvasOutputFields = (state: RootState) => {
|
||||
// Advanced session means working on canvas - images are not saved to gallery or added to a board.
|
||||
// Simple session means working in YOLO mode - images are saved to gallery & board.
|
||||
const type = selectCanvasSessionType(state);
|
||||
const is_intermediate = type === 'advanced';
|
||||
const board = type === 'advanced' ? undefined : getBoardField(state);
|
||||
const tab = selectActiveTab(state);
|
||||
const is_intermediate = tab === 'canvas';
|
||||
const board = tab === 'canvas' ? undefined : getBoardField(state);
|
||||
|
||||
return {
|
||||
is_intermediate,
|
||||
|
||||
@@ -44,7 +44,7 @@ export const useInvoke = () => {
|
||||
return;
|
||||
}
|
||||
|
||||
if (tabName === 'canvas') {
|
||||
if (tabName === 'canvas' || tabName === 'generate') {
|
||||
dispatch(enqueueRequestedCanvas({ prepend }));
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -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 { canvasSessionGenerationStarted } from 'features/controlLayers/store/canvasStagingAreaSlice';
|
||||
import { paramsReset } from 'features/controlLayers/store/paramsSlice';
|
||||
import { atom } from 'nanostores';
|
||||
import { stylePresetsApi } from 'services/api/endpoints/stylePresets';
|
||||
|
||||
@@ -29,7 +29,7 @@ export const stylePresetSlice = createSlice({
|
||||
},
|
||||
},
|
||||
extraReducers(builder) {
|
||||
builder.addCase(canvasSessionGenerationStarted, () => {
|
||||
builder.addCase(paramsReset, () => {
|
||||
return deepClone(initialState);
|
||||
});
|
||||
builder.addMatcher(stylePresetsApi.endpoints.deleteStylePreset.matchFulfilled, (state, action) => {
|
||||
|
||||
@@ -2,7 +2,6 @@ import { ButtonGroup, Flex, Icon, IconButton, spinAnimation, Tooltip, useShiftMo
|
||||
import { useAppSelector } from 'app/store/storeHooks';
|
||||
import { ToolChooser } from 'features/controlLayers/components/Tool/ToolChooser';
|
||||
import { CanvasManagerProviderGate } from 'features/controlLayers/contexts/CanvasManagerProviderGate';
|
||||
import { selectCanvasSessionType } from 'features/controlLayers/store/canvasStagingAreaSlice';
|
||||
import { useDeleteAllExceptCurrentQueueItemDialog } from 'features/queue/components/DeleteAllExceptCurrentQueueItemConfirmationAlertDialog';
|
||||
import { InvokeButtonTooltip } from 'features/queue/components/InvokeButtonTooltip/InvokeButtonTooltip';
|
||||
import { useDeleteCurrentQueueItem } from 'features/queue/hooks/useDeleteCurrentQueueItem';
|
||||
@@ -22,11 +21,10 @@ import { useGetQueueStatusQuery } from 'services/api/endpoints/queue';
|
||||
|
||||
export const FloatingLeftPanelButtons = memo((props: { onToggle: () => void }) => {
|
||||
const tab = useAppSelector(selectActiveTab);
|
||||
const type = useAppSelector(selectCanvasSessionType);
|
||||
|
||||
return (
|
||||
<Flex pos="absolute" transform="translate(0, -50%)" top="50%" insetInlineStart={2} direction="column" gap={2}>
|
||||
{tab === 'canvas' && type === 'advanced' && (
|
||||
{tab === 'canvas' && (
|
||||
<CanvasManagerProviderGate>
|
||||
<ToolChooser />
|
||||
</CanvasManagerProviderGate>
|
||||
|
||||
@@ -15,6 +15,7 @@ export const LeftPanelContent = memo(() => {
|
||||
<Flex flexDir="column" w="full" h="full" gap={2}>
|
||||
<QueueControls />
|
||||
<Box position="relative" w="full" h="full">
|
||||
{tab === 'generate' && <ParametersPanelTextToImage />}
|
||||
{tab === 'canvas' && <ParametersPanelTextToImage />}
|
||||
{tab === 'upscaling' && <ParametersPanelUpscale />}
|
||||
{tab === 'workflows' && <WorkflowsTabLeftPanel />}
|
||||
|
||||
@@ -1,19 +1,30 @@
|
||||
import { useAppSelector } from 'app/store/storeHooks';
|
||||
import { CanvasMainPanelContent } from 'features/controlLayers/components/CanvasMainPanelContent';
|
||||
import { AdvancedSession } from 'features/controlLayers/components/AdvancedSession/AdvancedSession';
|
||||
import { SimpleSession } from 'features/controlLayers/components/SimpleSession/SimpleSession';
|
||||
import { selectCanvasSessionId, selectGenerateSessionId } from 'features/controlLayers/store/canvasStagingAreaSlice';
|
||||
import { ImageViewer } from 'features/gallery/components/ImageViewer/ImageViewer';
|
||||
import ModelManagerTab from 'features/ui/components/tabs/ModelManagerTab';
|
||||
import QueueTab from 'features/ui/components/tabs/QueueTab';
|
||||
import { WorkflowsMainPanel } from 'features/ui/components/tabs/WorkflowsTabContent';
|
||||
import { selectActiveTab } from 'features/ui/store/uiSelectors';
|
||||
import { atom } from 'nanostores';
|
||||
import { memo } from 'react';
|
||||
import type { Equals } from 'tsafe';
|
||||
import { assert } from 'tsafe';
|
||||
|
||||
export const $simpleId = atom<string | null>(null);
|
||||
export const $advancedId = atom<string | null>(null);
|
||||
|
||||
export const MainPanelContent = memo(() => {
|
||||
const tab = useAppSelector(selectActiveTab);
|
||||
const generateId = useAppSelector(selectGenerateSessionId);
|
||||
const canvasId = useAppSelector(selectCanvasSessionId);
|
||||
|
||||
if (tab === 'generate') {
|
||||
return <SimpleSession id={generateId} />;
|
||||
}
|
||||
if (tab === 'canvas') {
|
||||
return <CanvasMainPanelContent />;
|
||||
return <AdvancedSession id={canvasId} />;
|
||||
}
|
||||
if (tab === 'upscaling') {
|
||||
return <ImageViewer />;
|
||||
|
||||
@@ -4,7 +4,6 @@ import { useAppSelector } from 'app/store/storeHooks';
|
||||
import { FocusRegionWrapper } from 'common/components/FocusRegionWrapper';
|
||||
import { CanvasLayersPanelContent } from 'features/controlLayers/components/CanvasLayersPanelContent';
|
||||
import { CanvasManagerProviderGate } from 'features/controlLayers/contexts/CanvasManagerProviderGate';
|
||||
import { selectCanvasSessionType } from 'features/controlLayers/store/canvasStagingAreaSlice';
|
||||
import { BoardsListPanelContent } from 'features/gallery/components/BoardsListPanelContent';
|
||||
import { Gallery } from 'features/gallery/components/Gallery';
|
||||
import { GalleryTopBar } from 'features/gallery/components/GalleryTopBar';
|
||||
@@ -29,7 +28,6 @@ export const RightPanelContent = memo(() => {
|
||||
const boardSearchText = useAppSelector(selectBoardSearchText);
|
||||
const boardSearchDisclosure = useDisclosure({ defaultIsOpen: !!boardSearchText.length });
|
||||
const imperativePanelGroupRef = useRef<ImperativePanelGroupHandle>(null);
|
||||
const type = useAppSelector(selectCanvasSessionType);
|
||||
const tab = useAppSelector(selectActiveTab);
|
||||
|
||||
const boardsListPanelOptions = useMemo<UsePanelOptions>(
|
||||
@@ -79,7 +77,7 @@ export const RightPanelContent = memo(() => {
|
||||
<Panel order={1} id="gallery-wrapper-panel" collapsible {...galleryPanel.panelProps}>
|
||||
<Gallery />
|
||||
</Panel>
|
||||
{tab === 'canvas' && type === 'advanced' && (
|
||||
{tab === 'canvas' && (
|
||||
<>
|
||||
<HorizontalResizeHandle id="gallery-panel-to-layers-handle" {...galleryPanel.resizeHandleProps} />
|
||||
<Panel order={2} id="canvas-layers-panel" collapsible {...canvasLayersPanel.panelProps}>
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
import type { SystemStyleObject } from '@invoke-ai/ui-library';
|
||||
import { IconButton, Tooltip } from '@invoke-ai/ui-library';
|
||||
import { useAppDispatch, useAppSelector } from 'app/store/storeHooks';
|
||||
import { selectActiveTab } from 'features/ui/store/uiSelectors';
|
||||
@@ -5,6 +6,12 @@ import { setActiveTab } from 'features/ui/store/uiSlice';
|
||||
import type { TabName } from 'features/ui/store/uiTypes';
|
||||
import { forwardRef, memo, type ReactElement, useCallback } from 'react';
|
||||
|
||||
const sx: SystemStyleObject = {
|
||||
'&[data-selected=true]': {
|
||||
svg: { fill: 'invokeYellow.300' },
|
||||
},
|
||||
};
|
||||
|
||||
export const TabButton = memo(
|
||||
forwardRef(({ tab, icon, label }: { tab: TabName; icon: ReactElement; label: string }, ref) => {
|
||||
const dispatch = useAppDispatch();
|
||||
@@ -26,6 +33,7 @@ export const TabButton = memo(
|
||||
data-selected={activeTabName === tab}
|
||||
aria-label={label}
|
||||
data-testid={label}
|
||||
sx={sx}
|
||||
/>
|
||||
</Tooltip>
|
||||
);
|
||||
|
||||
@@ -8,7 +8,7 @@ import { VideosModalButton } from 'features/system/components/VideosModal/Videos
|
||||
import { TabMountGate } from 'features/ui/components/TabMountGate';
|
||||
import { memo } from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { PiBoundingBoxBold, PiCubeBold, PiFlowArrowBold, PiFrameCornersBold, PiQueueBold } from 'react-icons/pi';
|
||||
import { PiBoundingBoxBold, PiCubeBold, PiFlowArrowBold, PiFrameCornersBold, PiQueueBold, PiTextAaBold } from 'react-icons/pi';
|
||||
|
||||
import { Notifications } from './Notifications';
|
||||
import { TabButton } from './TabButton';
|
||||
@@ -21,6 +21,9 @@ export const VerticalNavBar = memo(() => {
|
||||
<Flex flexDir="column" alignItems="center" py={2} gap={4} minW={0}>
|
||||
<InvokeAILogoComponent />
|
||||
<Flex gap={4} pt={6} h="full" flexDir="column">
|
||||
<TabMountGate tab="generate">
|
||||
<TabButton tab="generate" icon={<PiTextAaBold />} label="Generate" />
|
||||
</TabMountGate>
|
||||
<TabMountGate tab="canvas">
|
||||
<TabButton tab="canvas" icon={<PiBoundingBoxBold />} label={t('ui.tabs.canvas')} />
|
||||
</TabMountGate>
|
||||
|
||||
@@ -1,7 +1,8 @@
|
||||
import type { PayloadAction } from '@reduxjs/toolkit';
|
||||
import { createSelector, createSlice } from '@reduxjs/toolkit';
|
||||
import type { PersistConfig, RootState } from 'app/store/store';
|
||||
import { canvasSessionTypeChanged } from 'features/controlLayers/store/canvasStagingAreaSlice';
|
||||
import { canvasReset } from 'features/controlLayers/store/actions';
|
||||
import { canvasSessionReset, generateSessionReset } 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,9 +57,18 @@ export const uiSlice = createSlice({
|
||||
builder.addCase(workflowLoaded, (state) => {
|
||||
state.activeTab = 'workflows';
|
||||
});
|
||||
builder.addCase(canvasSessionTypeChanged, (state) => {
|
||||
builder.addCase(canvasReset, (state) => {
|
||||
state.activeTab = 'canvas';
|
||||
});
|
||||
builder.addCase(canvasSessionReset, (state) => {
|
||||
state.activeTab = 'canvas';
|
||||
});
|
||||
builder.addCase(generateSessionReset, (state) => {
|
||||
state.activeTab = 'generate';
|
||||
});
|
||||
// builder.addCase(canvasSessionTypeChanged, (state) => {
|
||||
// state.activeTab = 'canvas';
|
||||
// });
|
||||
},
|
||||
});
|
||||
|
||||
@@ -98,12 +108,12 @@ export const uiPersistConfig: PersistConfig<UIState> = {
|
||||
persistDenylist: ['shouldShowImageDetails'],
|
||||
};
|
||||
|
||||
const TABS_WITH_LEFT_PANEL: TabName[] = ['canvas', 'upscaling', 'workflows'] as const;
|
||||
const TABS_WITH_LEFT_PANEL: TabName[] = ['canvas', 'upscaling', 'workflows', 'generate'] as const;
|
||||
export const LEFT_PANEL_MIN_SIZE_PX = 400;
|
||||
export const $isLeftPanelOpen = atom(true);
|
||||
export const selectWithLeftPanel = createSelector(selectUiSlice, (ui) => TABS_WITH_LEFT_PANEL.includes(ui.activeTab));
|
||||
|
||||
const TABS_WITH_RIGHT_PANEL: TabName[] = ['canvas', 'upscaling', 'workflows'] as const;
|
||||
const TABS_WITH_RIGHT_PANEL: TabName[] = ['canvas', 'upscaling', 'workflows', 'generate'] as const;
|
||||
export const RIGHT_PANEL_MIN_SIZE_PX = 390;
|
||||
export const $isRightPanelOpen = atom(true);
|
||||
export const selectWithRightPanel = createSelector(selectUiSlice, (ui) => TABS_WITH_RIGHT_PANEL.includes(ui.activeTab));
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import type { Dimensions } from 'features/controlLayers/store/types';
|
||||
|
||||
export type TabName = 'canvas' | 'upscaling' | 'workflows' | 'models' | 'queue';
|
||||
export type TabName = 'generate' | 'canvas' | 'upscaling' | 'workflows' | 'models' | 'queue';
|
||||
export type CanvasRightPanelTabName = 'layers' | 'gallery';
|
||||
|
||||
export interface UIState {
|
||||
|
||||
Reference in New Issue
Block a user