tidy(ui): excise img2img tab

This commit is contained in:
psychedelicious
2024-05-02 23:38:29 +10:00
committed by Kent Keirsey
parent 7d58908e32
commit a6ac184211
41 changed files with 25 additions and 1184 deletions

View File

@ -32,7 +32,6 @@ import { addImagesStarredListener } from 'app/store/middleware/listenerMiddlewar
import { addImagesUnstarredListener } from 'app/store/middleware/listenerMiddleware/listeners/imagesUnstarred'; import { addImagesUnstarredListener } from 'app/store/middleware/listenerMiddleware/listeners/imagesUnstarred';
import { addImageToDeleteSelectedListener } from 'app/store/middleware/listenerMiddleware/listeners/imageToDeleteSelected'; import { addImageToDeleteSelectedListener } from 'app/store/middleware/listenerMiddleware/listeners/imageToDeleteSelected';
import { addImageUploadedFulfilledListener } from 'app/store/middleware/listenerMiddleware/listeners/imageUploaded'; import { addImageUploadedFulfilledListener } from 'app/store/middleware/listenerMiddleware/listeners/imageUploaded';
import { addInitialImageSelectedListener } from 'app/store/middleware/listenerMiddleware/listeners/initialImageSelected';
import { addModelSelectedListener } from 'app/store/middleware/listenerMiddleware/listeners/modelSelected'; import { addModelSelectedListener } from 'app/store/middleware/listenerMiddleware/listeners/modelSelected';
import { addModelsLoadedListener } from 'app/store/middleware/listenerMiddleware/listeners/modelsLoaded'; import { addModelsLoadedListener } from 'app/store/middleware/listenerMiddleware/listeners/modelsLoaded';
import { addDynamicPromptsListener } from 'app/store/middleware/listenerMiddleware/listeners/promptChanged'; import { addDynamicPromptsListener } from 'app/store/middleware/listenerMiddleware/listeners/promptChanged';
@ -73,9 +72,6 @@ const startAppListening = listenerMiddleware.startListening as AppStartListening
// Image uploaded // Image uploaded
addImageUploadedFulfilledListener(startAppListening); addImageUploadedFulfilledListener(startAppListening);
// Image selected
addInitialImageSelectedListener(startAppListening);
// Image deleted // Image deleted
addRequestedSingleImageDeletionListener(startAppListening); addRequestedSingleImageDeletionListener(startAppListening);
addDeleteBoardAndImagesFulfilledListener(startAppListening); addDeleteBoardAndImagesFulfilledListener(startAppListening);

View File

@ -3,7 +3,6 @@ import { resetCanvas } from 'features/canvas/store/canvasSlice';
import { controlAdaptersReset } from 'features/controlAdapters/store/controlAdaptersSlice'; import { controlAdaptersReset } from 'features/controlAdapters/store/controlAdaptersSlice';
import { getImageUsage } from 'features/deleteImageModal/store/selectors'; import { getImageUsage } from 'features/deleteImageModal/store/selectors';
import { nodeEditorReset } from 'features/nodes/store/nodesSlice'; import { nodeEditorReset } from 'features/nodes/store/nodesSlice';
import { clearInitialImage } from 'features/parameters/store/generationSlice';
import { imagesApi } from 'services/api/endpoints/images'; import { imagesApi } from 'services/api/endpoints/images';
export const addDeleteBoardAndImagesFulfilledListener = (startAppListening: AppStartListening) => { export const addDeleteBoardAndImagesFulfilledListener = (startAppListening: AppStartListening) => {
@ -14,7 +13,6 @@ export const addDeleteBoardAndImagesFulfilledListener = (startAppListening: AppS
// Remove all deleted images from the UI // Remove all deleted images from the UI
let wasInitialImageReset = false;
let wasCanvasReset = false; let wasCanvasReset = false;
let wasNodeEditorReset = false; let wasNodeEditorReset = false;
let wereControlAdaptersReset = false; let wereControlAdaptersReset = false;
@ -23,11 +21,6 @@ export const addDeleteBoardAndImagesFulfilledListener = (startAppListening: AppS
deleted_images.forEach((image_name) => { deleted_images.forEach((image_name) => {
const imageUsage = getImageUsage(generation, canvas, nodes, controlAdapters, image_name); const imageUsage = getImageUsage(generation, canvas, nodes, controlAdapters, image_name);
if (imageUsage.isInitialImage && !wasInitialImageReset) {
dispatch(clearInitialImage());
wasInitialImageReset = true;
}
if (imageUsage.isCanvasImage && !wasCanvasReset) { if (imageUsage.isCanvasImage && !wasCanvasReset) {
dispatch(resetCanvas()); dispatch(resetCanvas());
wasCanvasReset = true; wasCanvasReset = true;

View File

@ -1,8 +1,6 @@
import { enqueueRequested } from 'app/store/actions'; import { enqueueRequested } from 'app/store/actions';
import type { AppStartListening } from 'app/store/middleware/listenerMiddleware'; import type { AppStartListening } from 'app/store/middleware/listenerMiddleware';
import { prepareLinearUIBatch } from 'features/nodes/util/graph/buildLinearBatchConfig'; import { prepareLinearUIBatch } from 'features/nodes/util/graph/buildLinearBatchConfig';
import { buildLinearImageToImageGraph } from 'features/nodes/util/graph/buildLinearImageToImageGraph';
import { buildLinearSDXLImageToImageGraph } from 'features/nodes/util/graph/buildLinearSDXLImageToImageGraph';
import { buildLinearSDXLTextToImageGraph } from 'features/nodes/util/graph/buildLinearSDXLTextToImageGraph'; import { buildLinearSDXLTextToImageGraph } from 'features/nodes/util/graph/buildLinearSDXLTextToImageGraph';
import { buildLinearTextToImageGraph } from 'features/nodes/util/graph/buildLinearTextToImageGraph'; import { buildLinearTextToImageGraph } from 'features/nodes/util/graph/buildLinearTextToImageGraph';
import { queueApi } from 'services/api/endpoints/queue'; import { queueApi } from 'services/api/endpoints/queue';
@ -10,7 +8,7 @@ import { queueApi } from 'services/api/endpoints/queue';
export const addEnqueueRequestedLinear = (startAppListening: AppStartListening) => { export const addEnqueueRequestedLinear = (startAppListening: AppStartListening) => {
startAppListening({ startAppListening({
predicate: (action): action is ReturnType<typeof enqueueRequested> => predicate: (action): action is ReturnType<typeof enqueueRequested> =>
enqueueRequested.match(action) && (action.payload.tabName === 'txt2img' || action.payload.tabName === 'img2img'), enqueueRequested.match(action) && action.payload.tabName === 'txt2img',
effect: async (action, { getState, dispatch }) => { effect: async (action, { getState, dispatch }) => {
const state = getState(); const state = getState();
const model = state.generation.model; const model = state.generation.model;
@ -19,17 +17,9 @@ export const addEnqueueRequestedLinear = (startAppListening: AppStartListening)
let graph; let graph;
if (model && model.base === 'sdxl') { if (model && model.base === 'sdxl') {
if (action.payload.tabName === 'txt2img') {
graph = await buildLinearSDXLTextToImageGraph(state); graph = await buildLinearSDXLTextToImageGraph(state);
} else { } else {
graph = await buildLinearSDXLImageToImageGraph(state);
}
} else {
if (action.payload.tabName === 'txt2img') {
graph = await buildLinearTextToImageGraph(state); graph = await buildLinearTextToImageGraph(state);
} else {
graph = await buildLinearImageToImageGraph(state);
}
} }
const batchConfig = prepareLinearUIBatch(state, graph, prepend); const batchConfig = prepareLinearUIBatch(state, graph, prepend);

View File

@ -14,7 +14,6 @@ import { imageSelected } from 'features/gallery/store/gallerySlice';
import { fieldImageValueChanged } from 'features/nodes/store/nodesSlice'; import { fieldImageValueChanged } from 'features/nodes/store/nodesSlice';
import { isImageFieldInputInstance } from 'features/nodes/types/field'; import { isImageFieldInputInstance } from 'features/nodes/types/field';
import { isInvocationNode } from 'features/nodes/types/invocation'; import { isInvocationNode } from 'features/nodes/types/invocation';
import { clearInitialImage } from 'features/parameters/store/generationSlice';
import { clamp, forEach } from 'lodash-es'; import { clamp, forEach } from 'lodash-es';
import { api } from 'services/api'; import { api } from 'services/api';
import { imagesApi } from 'services/api/endpoints/images'; import { imagesApi } from 'services/api/endpoints/images';
@ -73,11 +72,6 @@ export const addRequestedSingleImageDeletionListener = (startAppListening: AppSt
} }
imageDTOs.forEach((imageDTO) => { imageDTOs.forEach((imageDTO) => {
// reset init image if we deleted it
if (getState().generation.initialImage?.imageName === imageDTO.image_name) {
dispatch(clearInitialImage());
}
// reset control adapters that use the deleted images // reset control adapters that use the deleted images
forEach(selectControlAdapterAll(getState().controlAdapters), (ca) => { forEach(selectControlAdapterAll(getState().controlAdapters), (ca) => {
if ( if (
@ -168,11 +162,6 @@ export const addRequestedSingleImageDeletionListener = (startAppListening: AppSt
} }
imageDTOs.forEach((imageDTO) => { imageDTOs.forEach((imageDTO) => {
// reset init image if we deleted it
if (getState().generation.initialImage?.imageName === imageDTO.image_name) {
dispatch(clearInitialImage());
}
// reset control adapters that use the deleted images // reset control adapters that use the deleted images
forEach(selectControlAdapterAll(getState().controlAdapters), (ca) => { forEach(selectControlAdapterAll(getState().controlAdapters), (ca) => {
if ( if (

View File

@ -16,7 +16,7 @@ import {
import type { TypesafeDraggableData, TypesafeDroppableData } from 'features/dnd/types'; import type { TypesafeDraggableData, TypesafeDroppableData } from 'features/dnd/types';
import { imageSelected } from 'features/gallery/store/gallerySlice'; import { imageSelected } from 'features/gallery/store/gallerySlice';
import { fieldImageValueChanged } from 'features/nodes/store/nodesSlice'; import { fieldImageValueChanged } from 'features/nodes/store/nodesSlice';
import { initialImageChanged, selectOptimalDimension } from 'features/parameters/store/generationSlice'; import { selectOptimalDimension } from 'features/parameters/store/generationSlice';
import { imagesApi } from 'services/api/endpoints/images'; import { imagesApi } from 'services/api/endpoints/images';
export const dndDropped = createAction<{ export const dndDropped = createAction<{
@ -53,18 +53,6 @@ export const addImageDroppedListener = (startAppListening: AppStartListening) =>
return; return;
} }
/**
* Image dropped on initial image
*/
if (
overData.actionType === 'SET_INITIAL_IMAGE' &&
activeData.payloadType === 'IMAGE_DTO' &&
activeData.payload.imageDTO
) {
dispatch(initialImageChanged(activeData.payload.imageDTO));
return;
}
/** /**
* Image dropped on ControlNet * Image dropped on ControlNet
*/ */

View File

@ -14,7 +14,6 @@ export const addImageToDeleteSelectedListener = (startAppListening: AppStartList
const isImageInUse = const isImageInUse =
imagesUsage.some((i) => i.isCanvasImage) || imagesUsage.some((i) => i.isCanvasImage) ||
imagesUsage.some((i) => i.isInitialImage) ||
imagesUsage.some((i) => i.isControlImage) || imagesUsage.some((i) => i.isControlImage) ||
imagesUsage.some((i) => i.isNodesImage); imagesUsage.some((i) => i.isNodesImage);

View File

@ -13,7 +13,7 @@ import {
rgLayerIPAdapterImageChanged, rgLayerIPAdapterImageChanged,
} from 'features/controlLayers/store/controlLayersSlice'; } from 'features/controlLayers/store/controlLayersSlice';
import { fieldImageValueChanged } from 'features/nodes/store/nodesSlice'; import { fieldImageValueChanged } from 'features/nodes/store/nodesSlice';
import { initialImageChanged, selectOptimalDimension } from 'features/parameters/store/generationSlice'; import { selectOptimalDimension } from 'features/parameters/store/generationSlice';
import { addToast } from 'features/system/store/systemSlice'; import { addToast } from 'features/system/store/systemSlice';
import { t } from 'i18next'; import { t } from 'i18next';
import { omit } from 'lodash-es'; import { omit } from 'lodash-es';
@ -158,17 +158,6 @@ export const addImageUploadedFulfilledListener = (startAppListening: AppStartLis
); );
} }
if (postUploadAction?.type === 'SET_INITIAL_IMAGE') {
dispatch(initialImageChanged(imageDTO));
dispatch(
addToast({
...DEFAULT_UPLOADED_TOAST,
description: t('toast.setInitialImage'),
})
);
return;
}
if (postUploadAction?.type === 'SET_NODES_IMAGE') { if (postUploadAction?.type === 'SET_NODES_IMAGE') {
const { nodeId, fieldName } = postUploadAction; const { nodeId, fieldName } = postUploadAction;
dispatch(fieldImageValueChanged({ nodeId, fieldName, value: imageDTO })); dispatch(fieldImageValueChanged({ nodeId, fieldName, value: imageDTO }));

View File

@ -1,21 +0,0 @@
import type { AppStartListening } from 'app/store/middleware/listenerMiddleware';
import { initialImageSelected } from 'features/parameters/store/actions';
import { initialImageChanged } from 'features/parameters/store/generationSlice';
import { addToast } from 'features/system/store/systemSlice';
import { makeToast } from 'features/system/util/makeToast';
import { t } from 'i18next';
export const addInitialImageSelectedListener = (startAppListening: AppStartListening) => {
startAppListening({
actionCreator: initialImageSelected,
effect: (action, { dispatch }) => {
if (!action.payload) {
dispatch(addToast(makeToast({ title: t('toast.imageNotLoadedDesc'), status: 'error' })));
return;
}
dispatch(initialImageChanged(action.payload));
dispatch(addToast(makeToast(t('toast.sentToImageToImage'))));
},
});
};

View File

@ -21,10 +21,6 @@ const selectPostUploadAction = createMemoizedSelector(activeTabNameSelector, (ac
postUploadAction = { type: 'SET_CANVAS_INITIAL_IMAGE' }; postUploadAction = { type: 'SET_CANVAS_INITIAL_IMAGE' };
} }
if (activeTabName === 'img2img') {
postUploadAction = { type: 'SET_INITIAL_IMAGE' };
}
return postUploadAction; return postUploadAction;
}); });

View File

@ -75,21 +75,13 @@ export const useGlobalHotkeys = () => {
useHotkeys( useHotkeys(
'2', '2',
() => { () => {
dispatch(setActiveTab('img2img')); dispatch(setActiveTab('unifiedCanvas'));
}, },
[dispatch] [dispatch]
); );
useHotkeys( useHotkeys(
'3', '3',
() => {
dispatch(setActiveTab('unifiedCanvas'));
},
[dispatch]
);
useHotkeys(
'4',
() => { () => {
dispatch(setActiveTab('nodes')); dispatch(setActiveTab('nodes'));
}, },
@ -97,7 +89,7 @@ export const useGlobalHotkeys = () => {
); );
useHotkeys( useHotkeys(
'5', '4',
() => { () => {
if (isModelManagerEnabled) { if (isModelManagerEnabled) {
dispatch(setActiveTab('modelManager')); dispatch(setActiveTab('modelManager'));
@ -107,7 +99,7 @@ export const useGlobalHotkeys = () => {
); );
useHotkeys( useHotkeys(
isModelManagerEnabled ? '6' : '5', isModelManagerEnabled ? '5' : '4',
() => { () => {
dispatch(setActiveTab('queue')); dispatch(setActiveTab('queue'));
}, },

View File

@ -28,7 +28,7 @@ const selector = createMemoizedSelector(
activeTabNameSelector, activeTabNameSelector,
], ],
(controlAdapters, generation, system, nodes, dynamicPrompts, controlLayers, activeTabName) => { (controlAdapters, generation, system, nodes, dynamicPrompts, controlLayers, activeTabName) => {
const { initialImage, model } = generation; const { model } = generation;
const { positivePrompt } = controlLayers.present; const { positivePrompt } = controlLayers.present;
const { isConnected } = system; const { isConnected } = system;
@ -40,10 +40,6 @@ const selector = createMemoizedSelector(
reasons.push(i18n.t('parameters.invoke.systemDisconnected')); reasons.push(i18n.t('parameters.invoke.systemDisconnected'));
} }
if (activeTabName === 'img2img' && !initialImage) {
reasons.push(i18n.t('parameters.invoke.noInitialImageSelected'));
}
if (activeTabName === 'nodes') { if (activeTabName === 'nodes') {
if (nodes.shouldValidateGraph) { if (nodes.shouldValidateGraph) {
if (!nodes.nodes.length) { if (!nodes.nodes.length) {

View File

@ -627,7 +627,7 @@ export const controlLayersSlice = createSlice({
reducer: (state, action: PayloadAction<{ layerId: string; imageDTO: ImageDTO | null }>) => { reducer: (state, action: PayloadAction<{ layerId: string; imageDTO: ImageDTO | null }>) => {
const { layerId, imageDTO } = action.payload; const { layerId, imageDTO } = action.payload;
// Highlander! There can be only one! // Highlander! There can be only one!
assert(!state.layers.find(isInitialImageLayer)); state.layers = state.layers.filter((l) => isInitialImageLayer(l));
const layer: InitialImageLayer = { const layer: InitialImageLayer = {
id: layerId, id: layerId,
type: 'initial_image_layer', type: 'initial_image_layer',

View File

@ -38,7 +38,6 @@ const selectImageUsages = createMemoizedSelector(
); );
const imageUsageSummary: ImageUsage = { const imageUsageSummary: ImageUsage = {
isInitialImage: some(allImageUsage, (i) => i.isInitialImage),
isCanvasImage: some(allImageUsage, (i) => i.isCanvasImage), isCanvasImage: some(allImageUsage, (i) => i.isCanvasImage),
isNodesImage: some(allImageUsage, (i) => i.isNodesImage), isNodesImage: some(allImageUsage, (i) => i.isNodesImage),
isControlImage: some(allImageUsage, (i) => i.isControlImage), isControlImage: some(allImageUsage, (i) => i.isControlImage),

View File

@ -29,7 +29,6 @@ const ImageUsageMessage = (props: Props) => {
<> <>
<Text>{topMessage}</Text> <Text>{topMessage}</Text>
<UnorderedList paddingInlineStart={6}> <UnorderedList paddingInlineStart={6}>
{imageUsage.isInitialImage && <ListItem>{t('common.img2img')}</ListItem>}
{imageUsage.isCanvasImage && <ListItem>{t('common.unifiedCanvas')}</ListItem>} {imageUsage.isCanvasImage && <ListItem>{t('common.unifiedCanvas')}</ListItem>}
{imageUsage.isControlImage && <ListItem>{t('common.controlNet')}</ListItem>} {imageUsage.isControlImage && <ListItem>{t('common.controlNet')}</ListItem>}
{imageUsage.isNodesImage && <ListItem>{t('common.nodeEditor')}</ListItem>} {imageUsage.isNodesImage && <ListItem>{t('common.nodeEditor')}</ListItem>}

View File

@ -25,8 +25,6 @@ export const getImageUsage = (
controlAdapters: ControlAdaptersState, controlAdapters: ControlAdaptersState,
image_name: string image_name: string
) => { ) => {
const isInitialImage = generation.initialImage?.imageName === image_name;
const isCanvasImage = canvas.layerState.objects.some((obj) => obj.kind === 'image' && obj.imageName === image_name); const isCanvasImage = canvas.layerState.objects.some((obj) => obj.kind === 'image' && obj.imageName === image_name);
const isNodesImage = nodes.nodes.filter(isInvocationNode).some((node) => { const isNodesImage = nodes.nodes.filter(isInvocationNode).some((node) => {
@ -41,7 +39,6 @@ export const getImageUsage = (
); );
const imageUsage: ImageUsage = { const imageUsage: ImageUsage = {
isInitialImage,
isCanvasImage, isCanvasImage,
isNodesImage, isNodesImage,
isControlImage, isControlImage,

View File

@ -6,7 +6,6 @@ export type DeleteImageState = {
}; };
export type ImageUsage = { export type ImageUsage = {
isInitialImage: boolean;
isCanvasImage: boolean; isCanvasImage: boolean;
isNodesImage: boolean; isNodesImage: boolean;
isControlImage: boolean; isControlImage: boolean;

View File

@ -22,10 +22,6 @@ type CurrentImageDropData = BaseDropData & {
actionType: 'SET_CURRENT_IMAGE'; actionType: 'SET_CURRENT_IMAGE';
}; };
type InitialImageDropData = BaseDropData & {
actionType: 'SET_INITIAL_IMAGE';
};
type ControlAdapterDropData = BaseDropData & { type ControlAdapterDropData = BaseDropData & {
actionType: 'SET_CONTROL_ADAPTER_IMAGE'; actionType: 'SET_CONTROL_ADAPTER_IMAGE';
context: { context: {
@ -85,7 +81,6 @@ export type RemoveFromBoardDropData = BaseDropData & {
export type TypesafeDroppableData = export type TypesafeDroppableData =
| CurrentImageDropData | CurrentImageDropData
| InitialImageDropData
| ControlAdapterDropData | ControlAdapterDropData
| CanvasInitialImageDropData | CanvasInitialImageDropData
| NodesImageDropData | NodesImageDropData

View File

@ -15,8 +15,6 @@ export const isValidDrop = (overData: TypesafeDroppableData | undefined, active:
switch (actionType) { switch (actionType) {
case 'SET_CURRENT_IMAGE': case 'SET_CURRENT_IMAGE':
return payloadType === 'IMAGE_DTO'; return payloadType === 'IMAGE_DTO';
case 'SET_INITIAL_IMAGE':
return payloadType === 'IMAGE_DTO';
case 'SET_CONTROL_ADAPTER_IMAGE': case 'SET_CONTROL_ADAPTER_IMAGE':
return payloadType === 'IMAGE_DTO'; return payloadType === 'IMAGE_DTO';
case 'SET_CA_LAYER_IMAGE': case 'SET_CA_LAYER_IMAGE':

View File

@ -50,7 +50,6 @@ const DeleteBoardModal = (props: Props) => {
); );
const imageUsageSummary: ImageUsage = { const imageUsageSummary: ImageUsage = {
isInitialImage: some(allImageUsage, (i) => i.isInitialImage),
isCanvasImage: some(allImageUsage, (i) => i.isCanvasImage), isCanvasImage: some(allImageUsage, (i) => i.isCanvasImage),
isNodesImage: some(allImageUsage, (i) => i.isNodesImage), isNodesImage: some(allImageUsage, (i) => i.isNodesImage),
isControlImage: some(allImageUsage, (i) => i.isControlImage), isControlImage: some(allImageUsage, (i) => i.isControlImage),

View File

@ -4,6 +4,7 @@ import { skipToken } from '@reduxjs/toolkit/query';
import { useAppToaster } from 'app/components/Toaster'; import { useAppToaster } from 'app/components/Toaster';
import { upscaleRequested } from 'app/store/middleware/listenerMiddleware/listeners/upscaleRequested'; import { upscaleRequested } from 'app/store/middleware/listenerMiddleware/listeners/upscaleRequested';
import { useAppDispatch, useAppSelector } from 'app/store/storeHooks'; import { useAppDispatch, useAppSelector } from 'app/store/storeHooks';
import { iiLayerAdded } from 'features/controlLayers/store/controlLayersSlice';
import { DeleteImageButton } from 'features/deleteImageModal/components/DeleteImageButton'; import { DeleteImageButton } from 'features/deleteImageModal/components/DeleteImageButton';
import { imagesToDeleteSelected } from 'features/deleteImageModal/store/slice'; import { imagesToDeleteSelected } from 'features/deleteImageModal/store/slice';
import SingleSelectionMenuItems from 'features/gallery/components/ImageContextMenu/SingleSelectionMenuItems'; import SingleSelectionMenuItems from 'features/gallery/components/ImageContextMenu/SingleSelectionMenuItems';
@ -13,7 +14,6 @@ import { selectLastSelectedImage } from 'features/gallery/store/gallerySelectors
import { selectGallerySlice } from 'features/gallery/store/gallerySlice'; import { selectGallerySlice } from 'features/gallery/store/gallerySlice';
import { parseAndRecallImageDimensions } from 'features/metadata/util/handlers'; import { parseAndRecallImageDimensions } from 'features/metadata/util/handlers';
import ParamUpscalePopover from 'features/parameters/components/Upscale/ParamUpscaleSettings'; import ParamUpscalePopover from 'features/parameters/components/Upscale/ParamUpscaleSettings';
import { initialImageSelected } from 'features/parameters/store/actions';
import { useIsQueueMutationInProgress } from 'features/queue/hooks/useIsQueueMutationInProgress'; import { useIsQueueMutationInProgress } from 'features/queue/hooks/useIsQueueMutationInProgress';
import { useFeatureStatus } from 'features/system/hooks/useFeatureStatus'; import { useFeatureStatus } from 'features/system/hooks/useFeatureStatus';
import { selectSystemSlice } from 'features/system/store/systemSlice'; import { selectSystemSlice } from 'features/system/store/systemSlice';
@ -86,8 +86,11 @@ const CurrentImageButtons = () => {
useHotkeys('d', handleUseSize, [handleUseSize]); useHotkeys('d', handleUseSize, [handleUseSize]);
const handleSendToImageToImage = useCallback(() => { const handleSendToImageToImage = useCallback(() => {
if (!imageDTO) {
return;
}
dispatch(sentImageToImg2Img()); dispatch(sentImageToImg2Img());
dispatch(initialImageSelected(imageDTO)); dispatch(iiLayerAdded(imageDTO));
}, [dispatch, imageDTO]); }, [dispatch, imageDTO]);
useHotkeys('shift+i', handleSendToImageToImage, [imageDTO]); useHotkeys('shift+i', handleSendToImageToImage, [imageDTO]);

View File

@ -7,10 +7,10 @@ import { useCopyImageToClipboard } from 'common/hooks/useCopyImageToClipboard';
import { useDownloadImage } from 'common/hooks/useDownloadImage'; import { useDownloadImage } from 'common/hooks/useDownloadImage';
import { setInitialCanvasImage } from 'features/canvas/store/canvasSlice'; import { setInitialCanvasImage } from 'features/canvas/store/canvasSlice';
import { imagesToChangeSelected, isModalOpenChanged } from 'features/changeBoardModal/store/slice'; import { imagesToChangeSelected, isModalOpenChanged } from 'features/changeBoardModal/store/slice';
import { iiLayerAdded } from 'features/controlLayers/store/controlLayersSlice';
import { imagesToDeleteSelected } from 'features/deleteImageModal/store/slice'; import { imagesToDeleteSelected } from 'features/deleteImageModal/store/slice';
import { useImageActions } from 'features/gallery/hooks/useImageActions'; import { useImageActions } from 'features/gallery/hooks/useImageActions';
import { sentImageToCanvas, sentImageToImg2Img } from 'features/gallery/store/actions'; import { sentImageToCanvas, sentImageToImg2Img } from 'features/gallery/store/actions';
import { initialImageSelected } from 'features/parameters/store/actions';
import { selectOptimalDimension } from 'features/parameters/store/generationSlice'; import { selectOptimalDimension } from 'features/parameters/store/generationSlice';
import { useFeatureStatus } from 'features/system/hooks/useFeatureStatus'; import { useFeatureStatus } from 'features/system/hooks/useFeatureStatus';
import { setActiveTab } from 'features/ui/store/uiSlice'; import { setActiveTab } from 'features/ui/store/uiSlice';
@ -72,7 +72,7 @@ const SingleSelectionMenuItems = (props: SingleSelectionMenuItemsProps) => {
const handleSendToImageToImage = useCallback(() => { const handleSendToImageToImage = useCallback(() => {
dispatch(sentImageToImg2Img()); dispatch(sentImageToImg2Img());
dispatch(initialImageSelected(imageDTO)); dispatch(iiLayerAdded(imageDTO));
}, [dispatch, imageDTO]); }, [dispatch, imageDTO]);
const handleSendToCanvas = useCallback(() => { const handleSendToCanvas = useCallback(() => {

View File

@ -12,7 +12,6 @@ import { PiArrowLeftBold } from 'react-icons/pi';
const TAB_NAME_TO_TKEY: Record<InvokeTabName, string> = { const TAB_NAME_TO_TKEY: Record<InvokeTabName, string> = {
txt2img: 'common.txt2img', txt2img: 'common.txt2img',
img2img: 'common.img2img',
unifiedCanvas: 'common.unifiedCanvas', unifiedCanvas: 'common.unifiedCanvas',
nodes: 'common.nodes', nodes: 'common.nodes',
modelManager: 'modelManager.modelManager', modelManager: 'modelManager.modelManager',

View File

@ -10,6 +10,7 @@ import {
caLayerControlNetsDeleted, caLayerControlNetsDeleted,
caLayerT2IAdaptersDeleted, caLayerT2IAdaptersDeleted,
heightChanged, heightChanged,
iiLayerAdded,
ipaLayerAdded, ipaLayerAdded,
ipaLayersDeleted, ipaLayersDeleted,
negativePrompt2Changed, negativePrompt2Changed,
@ -32,7 +33,6 @@ import type {
} from 'features/metadata/types'; } from 'features/metadata/types';
import { modelSelected } from 'features/parameters/store/actions'; import { modelSelected } from 'features/parameters/store/actions';
import { import {
initialImageChanged,
setCfgRescaleMultiplier, setCfgRescaleMultiplier,
setCfgScale, setCfgScale,
setImg2imgStrength, setImg2imgStrength,
@ -107,7 +107,7 @@ const recallScheduler: MetadataRecallFunc<ParameterScheduler> = (scheduler) => {
}; };
const recallInitialImage: MetadataRecallFunc<ImageDTO> = async (imageDTO) => { const recallInitialImage: MetadataRecallFunc<ImageDTO> = async (imageDTO) => {
getStore().dispatch(initialImageChanged(imageDTO)); getStore().dispatch(iiLayerAdded(imageDTO));
}; };
const setSizeOptions = { updateAspectRatio: true, clamp: true }; const setSizeOptions = { updateAspectRatio: true, clamp: true };

View File

@ -9,8 +9,6 @@ import {
SDXL_CANVAS_TEXT_TO_IMAGE_GRAPH, SDXL_CANVAS_TEXT_TO_IMAGE_GRAPH,
SDXL_CONTROL_LAYERS_GRAPH, SDXL_CONTROL_LAYERS_GRAPH,
SDXL_DENOISE_LATENTS, SDXL_DENOISE_LATENTS,
SDXL_IMAGE_TO_IMAGE_GRAPH,
SDXL_TEXT_TO_IMAGE_GRAPH,
SEAMLESS, SEAMLESS,
VAE_LOADER, VAE_LOADER,
} from './constants'; } from './constants';
@ -56,8 +54,6 @@ export const addSeamlessToLinearGraph = (
if ( if (
graph.id === SDXL_CONTROL_LAYERS_GRAPH || graph.id === SDXL_CONTROL_LAYERS_GRAPH ||
graph.id === SDXL_TEXT_TO_IMAGE_GRAPH ||
graph.id === SDXL_IMAGE_TO_IMAGE_GRAPH ||
graph.id === SDXL_CANVAS_TEXT_TO_IMAGE_GRAPH || graph.id === SDXL_CANVAS_TEXT_TO_IMAGE_GRAPH ||
graph.id === SDXL_CANVAS_IMAGE_TO_IMAGE_GRAPH || graph.id === SDXL_CANVAS_IMAGE_TO_IMAGE_GRAPH ||
graph.id === SDXL_CANVAS_INPAINT_GRAPH || graph.id === SDXL_CANVAS_INPAINT_GRAPH ||

View File

@ -8,7 +8,6 @@ import {
CANVAS_OUTPUT, CANVAS_OUTPUT,
CANVAS_TEXT_TO_IMAGE_GRAPH, CANVAS_TEXT_TO_IMAGE_GRAPH,
CONTROL_LAYERS_GRAPH, CONTROL_LAYERS_GRAPH,
IMAGE_TO_IMAGE_GRAPH,
IMAGE_TO_LATENTS, IMAGE_TO_LATENTS,
INPAINT_CREATE_MASK, INPAINT_CREATE_MASK,
INPAINT_IMAGE, INPAINT_IMAGE,
@ -19,9 +18,7 @@ import {
SDXL_CANVAS_OUTPAINT_GRAPH, SDXL_CANVAS_OUTPAINT_GRAPH,
SDXL_CANVAS_TEXT_TO_IMAGE_GRAPH, SDXL_CANVAS_TEXT_TO_IMAGE_GRAPH,
SDXL_CONTROL_LAYERS_GRAPH, SDXL_CONTROL_LAYERS_GRAPH,
SDXL_IMAGE_TO_IMAGE_GRAPH,
SDXL_REFINER_SEAMLESS, SDXL_REFINER_SEAMLESS,
SDXL_TEXT_TO_IMAGE_GRAPH,
SEAMLESS, SEAMLESS,
VAE_LOADER, VAE_LOADER,
} from './constants'; } from './constants';
@ -52,13 +49,7 @@ export const addVAEToGraph = async (
}; };
} }
if ( if (graph.id === CONTROL_LAYERS_GRAPH || graph.id === SDXL_CONTROL_LAYERS_GRAPH) {
graph.id === CONTROL_LAYERS_GRAPH ||
graph.id === SDXL_CONTROL_LAYERS_GRAPH ||
graph.id === IMAGE_TO_IMAGE_GRAPH ||
graph.id === SDXL_TEXT_TO_IMAGE_GRAPH ||
graph.id === SDXL_IMAGE_TO_IMAGE_GRAPH
) {
graph.edges.push({ graph.edges.push({
source: { source: {
node_id: isSeamlessEnabled node_id: isSeamlessEnabled
@ -104,8 +95,6 @@ export const addVAEToGraph = async (
if ( if (
(graph.id === CONTROL_LAYERS_GRAPH || (graph.id === CONTROL_LAYERS_GRAPH ||
graph.id === SDXL_CONTROL_LAYERS_GRAPH || graph.id === SDXL_CONTROL_LAYERS_GRAPH ||
graph.id === IMAGE_TO_IMAGE_GRAPH ||
graph.id === SDXL_IMAGE_TO_IMAGE_GRAPH ||
graph.id === CANVAS_IMAGE_TO_IMAGE_GRAPH || graph.id === CANVAS_IMAGE_TO_IMAGE_GRAPH ||
graph.id === SDXL_CANVAS_IMAGE_TO_IMAGE_GRAPH) && graph.id === SDXL_CANVAS_IMAGE_TO_IMAGE_GRAPH) &&
Boolean(graph.nodes[IMAGE_TO_LATENTS]) Boolean(graph.nodes[IMAGE_TO_LATENTS])

View File

@ -1,369 +0,0 @@
import { logger } from 'app/logging/logger';
import type { RootState } from 'app/store/store';
import { fetchModelConfigWithTypeGuard } from 'features/metadata/util/modelFetchingHelpers';
import { getBoardField, getIsIntermediate } from 'features/nodes/util/graph/graphBuilderUtils';
import {
type ImageResizeInvocation,
type ImageToLatentsInvocation,
isNonRefinerMainModelConfig,
type NonNullableGraph,
} from 'services/api/types';
import { addControlNetToLinearGraph } from './addControlNetToLinearGraph';
import { addIPAdapterToLinearGraph } from './addIPAdapterToLinearGraph';
import { addLoRAsToGraph } from './addLoRAsToGraph';
import { addNSFWCheckerToGraph } from './addNSFWCheckerToGraph';
import { addSeamlessToLinearGraph } from './addSeamlessToLinearGraph';
import { addT2IAdaptersToLinearGraph } from './addT2IAdapterToLinearGraph';
import { addVAEToGraph } from './addVAEToGraph';
import { addWatermarkerToGraph } from './addWatermarkerToGraph';
import {
CLIP_SKIP,
DENOISE_LATENTS,
IMAGE_TO_IMAGE_GRAPH,
IMAGE_TO_LATENTS,
LATENTS_TO_IMAGE,
MAIN_MODEL_LOADER,
NEGATIVE_CONDITIONING,
NOISE,
POSITIVE_CONDITIONING,
RESIZE,
SEAMLESS,
} from './constants';
import { addCoreMetadataNode, getModelMetadataField } from './metadata';
/**
* Builds the Image to Image tab graph.
*/
export const buildLinearImageToImageGraph = async (state: RootState): Promise<NonNullableGraph> => {
const log = logger('nodes');
const {
model,
cfgScale: cfg_scale,
cfgRescaleMultiplier: cfg_rescale_multiplier,
scheduler,
seed,
steps,
initialImage,
img2imgStrength: strength,
shouldFitToWidthHeight,
clipSkip,
shouldUseCpuNoise,
vaePrecision,
seamlessXAxis,
seamlessYAxis,
} = state.generation;
const { positivePrompt, negativePrompt } = state.controlLayers.present;
const { width, height } = state.controlLayers.present.size;
/**
* The easiest way to build linear graphs is to do it in the node editor, then copy and paste the
* full graph here as a template. Then use the parameters from app state and set friendlier node
* ids.
*
* The only thing we need extra logic for is handling randomized seed, control net, and for img2img,
* the `fit` param. These are added to the graph at the end.
*/
if (!initialImage) {
log.error('No initial image found in state');
throw new Error('No initial image found in state');
}
if (!model) {
log.error('No model found in state');
throw new Error('No model found in state');
}
const fp32 = vaePrecision === 'fp32';
const is_intermediate = true;
let modelLoaderNodeId = MAIN_MODEL_LOADER;
const use_cpu = shouldUseCpuNoise;
// copy-pasted graph from node editor, filled in with state values & friendly node ids
const graph: NonNullableGraph = {
id: IMAGE_TO_IMAGE_GRAPH,
nodes: {
[modelLoaderNodeId]: {
type: 'main_model_loader',
id: modelLoaderNodeId,
model,
is_intermediate,
},
[CLIP_SKIP]: {
type: 'clip_skip',
id: CLIP_SKIP,
skipped_layers: clipSkip,
is_intermediate,
},
[POSITIVE_CONDITIONING]: {
type: 'compel',
id: POSITIVE_CONDITIONING,
prompt: positivePrompt,
is_intermediate,
},
[NEGATIVE_CONDITIONING]: {
type: 'compel',
id: NEGATIVE_CONDITIONING,
prompt: negativePrompt,
is_intermediate,
},
[NOISE]: {
type: 'noise',
id: NOISE,
use_cpu,
seed,
is_intermediate,
},
[LATENTS_TO_IMAGE]: {
type: 'l2i',
id: LATENTS_TO_IMAGE,
fp32,
is_intermediate: getIsIntermediate(state),
board: getBoardField(state),
},
[DENOISE_LATENTS]: {
type: 'denoise_latents',
id: DENOISE_LATENTS,
cfg_scale,
cfg_rescale_multiplier,
scheduler,
steps,
denoising_start: 1 - strength,
denoising_end: 1,
is_intermediate,
},
[IMAGE_TO_LATENTS]: {
type: 'i2l',
id: IMAGE_TO_LATENTS,
// must be set manually later, bc `fit` parameter may require a resize node inserted
// image: {
// image_name: initialImage.image_name,
// },
fp32,
is_intermediate,
use_cache: false,
},
},
edges: [
// Connect Model Loader to UNet and CLIP Skip
{
source: {
node_id: modelLoaderNodeId,
field: 'unet',
},
destination: {
node_id: DENOISE_LATENTS,
field: 'unet',
},
},
{
source: {
node_id: modelLoaderNodeId,
field: 'clip',
},
destination: {
node_id: CLIP_SKIP,
field: 'clip',
},
},
// Connect CLIP Skip to Conditioning
{
source: {
node_id: CLIP_SKIP,
field: 'clip',
},
destination: {
node_id: POSITIVE_CONDITIONING,
field: 'clip',
},
},
{
source: {
node_id: CLIP_SKIP,
field: 'clip',
},
destination: {
node_id: NEGATIVE_CONDITIONING,
field: 'clip',
},
},
// Connect everything to Denoise Latents
{
source: {
node_id: POSITIVE_CONDITIONING,
field: 'conditioning',
},
destination: {
node_id: DENOISE_LATENTS,
field: 'positive_conditioning',
},
},
{
source: {
node_id: NEGATIVE_CONDITIONING,
field: 'conditioning',
},
destination: {
node_id: DENOISE_LATENTS,
field: 'negative_conditioning',
},
},
{
source: {
node_id: NOISE,
field: 'noise',
},
destination: {
node_id: DENOISE_LATENTS,
field: 'noise',
},
},
{
source: {
node_id: IMAGE_TO_LATENTS,
field: 'latents',
},
destination: {
node_id: DENOISE_LATENTS,
field: 'latents',
},
},
// Decode denoised latents to image
{
source: {
node_id: DENOISE_LATENTS,
field: 'latents',
},
destination: {
node_id: LATENTS_TO_IMAGE,
field: 'latents',
},
},
],
};
// handle `fit`
if (shouldFitToWidthHeight && (initialImage.width !== width || initialImage.height !== height)) {
// The init image needs to be resized to the specified width and height before being passed to `IMAGE_TO_LATENTS`
// Create a resize node, explicitly setting its image
const resizeNode: ImageResizeInvocation = {
id: RESIZE,
type: 'img_resize',
image: {
image_name: initialImage.imageName,
},
is_intermediate: true,
width,
height,
};
graph.nodes[RESIZE] = resizeNode;
// The `RESIZE` node then passes its image to `IMAGE_TO_LATENTS`
graph.edges.push({
source: { node_id: RESIZE, field: 'image' },
destination: {
node_id: IMAGE_TO_LATENTS,
field: 'image',
},
});
// The `RESIZE` node also passes its width and height to `NOISE`
graph.edges.push({
source: { node_id: RESIZE, field: 'width' },
destination: {
node_id: NOISE,
field: 'width',
},
});
graph.edges.push({
source: { node_id: RESIZE, field: 'height' },
destination: {
node_id: NOISE,
field: 'height',
},
});
} else {
// We are not resizing, so we need to set the image on the `IMAGE_TO_LATENTS` node explicitly
(graph.nodes[IMAGE_TO_LATENTS] as ImageToLatentsInvocation).image = {
image_name: initialImage.imageName,
};
// Pass the image's dimensions to the `NOISE` node
graph.edges.push({
source: { node_id: IMAGE_TO_LATENTS, field: 'width' },
destination: {
node_id: NOISE,
field: 'width',
},
});
graph.edges.push({
source: { node_id: IMAGE_TO_LATENTS, field: 'height' },
destination: {
node_id: NOISE,
field: 'height',
},
});
}
const modelConfig = await fetchModelConfigWithTypeGuard(model.key, isNonRefinerMainModelConfig);
addCoreMetadataNode(
graph,
{
generation_mode: 'img2img',
cfg_scale,
cfg_rescale_multiplier,
height,
width,
positive_prompt: positivePrompt,
negative_prompt: negativePrompt,
model: getModelMetadataField(modelConfig),
seed,
steps,
rand_device: use_cpu ? 'cpu' : 'cuda',
scheduler,
clip_skip: clipSkip,
strength,
init_image: initialImage.imageName,
},
LATENTS_TO_IMAGE
);
// Add Seamless To Graph
if (seamlessXAxis || seamlessYAxis) {
addSeamlessToLinearGraph(state, graph, modelLoaderNodeId);
modelLoaderNodeId = SEAMLESS;
}
// optionally add custom VAE
await addVAEToGraph(state, graph, modelLoaderNodeId);
// add LoRA support
await addLoRAsToGraph(state, graph, DENOISE_LATENTS, modelLoaderNodeId);
// add controlnet, mutating `graph`
await addControlNetToLinearGraph(state, graph, DENOISE_LATENTS);
// Add IP Adapter
await addIPAdapterToLinearGraph(state, graph, DENOISE_LATENTS);
await addT2IAdaptersToLinearGraph(state, graph, DENOISE_LATENTS);
// NSFW & watermark - must be last thing added to graph
if (state.system.shouldUseNSFWChecker) {
// must add before watermarker!
addNSFWCheckerToGraph(state, graph);
}
if (state.system.shouldUseWatermarker) {
// must add after nsfw checker!
addWatermarkerToGraph(state, graph);
}
return graph;
};

View File

@ -1,390 +0,0 @@
import { logger } from 'app/logging/logger';
import type { RootState } from 'app/store/store';
import { fetchModelConfigWithTypeGuard } from 'features/metadata/util/modelFetchingHelpers';
import {
type ImageResizeInvocation,
type ImageToLatentsInvocation,
isNonRefinerMainModelConfig,
type NonNullableGraph,
} from 'services/api/types';
import { addControlNetToLinearGraph } from './addControlNetToLinearGraph';
import { addIPAdapterToLinearGraph } from './addIPAdapterToLinearGraph';
import { addNSFWCheckerToGraph } from './addNSFWCheckerToGraph';
import { addSDXLLoRAsToGraph } from './addSDXLLoRAstoGraph';
import { addSDXLRefinerToGraph } from './addSDXLRefinerToGraph';
import { addSeamlessToLinearGraph } from './addSeamlessToLinearGraph';
import { addT2IAdaptersToLinearGraph } from './addT2IAdapterToLinearGraph';
import { addVAEToGraph } from './addVAEToGraph';
import { addWatermarkerToGraph } from './addWatermarkerToGraph';
import {
IMAGE_TO_LATENTS,
LATENTS_TO_IMAGE,
NEGATIVE_CONDITIONING,
NOISE,
POSITIVE_CONDITIONING,
RESIZE,
SDXL_DENOISE_LATENTS,
SDXL_IMAGE_TO_IMAGE_GRAPH,
SDXL_MODEL_LOADER,
SDXL_REFINER_SEAMLESS,
SEAMLESS,
} from './constants';
import { getBoardField, getIsIntermediate, getSDXLStylePrompts } from './graphBuilderUtils';
import { addCoreMetadataNode, getModelMetadataField } from './metadata';
/**
* Builds the Image to Image tab graph.
*/
export const buildLinearSDXLImageToImageGraph = async (state: RootState): Promise<NonNullableGraph> => {
const log = logger('nodes');
const {
model,
cfgScale: cfg_scale,
cfgRescaleMultiplier: cfg_rescale_multiplier,
scheduler,
seed,
steps,
initialImage,
shouldFitToWidthHeight,
shouldUseCpuNoise,
vaePrecision,
seamlessXAxis,
seamlessYAxis,
img2imgStrength: strength,
} = state.generation;
const { positivePrompt, negativePrompt } = state.controlLayers.present;
const { width, height } = state.controlLayers.present.size;
const { refinerModel, refinerStart } = state.sdxl;
/**
* The easiest way to build linear graphs is to do it in the node editor, then copy and paste the
* full graph here as a template. Then use the parameters from app state and set friendlier node
* ids.
*
* The only thing we need extra logic for is handling randomized seed, control net, and for img2img,
* the `fit` param. These are added to the graph at the end.
*/
if (!initialImage) {
log.error('No initial image found in state');
throw new Error('No initial image found in state');
}
if (!model) {
log.error('No model found in state');
throw new Error('No model found in state');
}
const fp32 = vaePrecision === 'fp32';
const is_intermediate = true;
// Model Loader ID
let modelLoaderNodeId = SDXL_MODEL_LOADER;
const use_cpu = shouldUseCpuNoise;
// Construct Style Prompt
const { positiveStylePrompt, negativeStylePrompt } = getSDXLStylePrompts(state);
// copy-pasted graph from node editor, filled in with state values & friendly node ids
const graph: NonNullableGraph = {
id: SDXL_IMAGE_TO_IMAGE_GRAPH,
nodes: {
[modelLoaderNodeId]: {
type: 'sdxl_model_loader',
id: modelLoaderNodeId,
model,
is_intermediate,
},
[POSITIVE_CONDITIONING]: {
type: 'sdxl_compel_prompt',
id: POSITIVE_CONDITIONING,
prompt: positivePrompt,
style: positiveStylePrompt,
is_intermediate,
},
[NEGATIVE_CONDITIONING]: {
type: 'sdxl_compel_prompt',
id: NEGATIVE_CONDITIONING,
prompt: negativePrompt,
style: negativeStylePrompt,
is_intermediate,
},
[NOISE]: {
type: 'noise',
id: NOISE,
use_cpu,
seed,
is_intermediate,
},
[LATENTS_TO_IMAGE]: {
type: 'l2i',
id: LATENTS_TO_IMAGE,
fp32,
is_intermediate: getIsIntermediate(state),
board: getBoardField(state),
},
[SDXL_DENOISE_LATENTS]: {
type: 'denoise_latents',
id: SDXL_DENOISE_LATENTS,
cfg_scale,
cfg_rescale_multiplier,
scheduler,
steps,
denoising_start: refinerModel ? Math.min(refinerStart, 1 - strength) : 1 - strength,
denoising_end: refinerModel ? refinerStart : 1,
is_intermediate,
},
[IMAGE_TO_LATENTS]: {
type: 'i2l',
id: IMAGE_TO_LATENTS,
// must be set manually later, bc `fit` parameter may require a resize node inserted
// image: {
// image_name: initialImage.image_name,
// },
fp32,
is_intermediate,
use_cache: false,
},
},
edges: [
// Connect Model Loader to UNet, CLIP & VAE
{
source: {
node_id: modelLoaderNodeId,
field: 'unet',
},
destination: {
node_id: SDXL_DENOISE_LATENTS,
field: 'unet',
},
},
{
source: {
node_id: modelLoaderNodeId,
field: 'clip',
},
destination: {
node_id: POSITIVE_CONDITIONING,
field: 'clip',
},
},
{
source: {
node_id: modelLoaderNodeId,
field: 'clip2',
},
destination: {
node_id: POSITIVE_CONDITIONING,
field: 'clip2',
},
},
{
source: {
node_id: modelLoaderNodeId,
field: 'clip',
},
destination: {
node_id: NEGATIVE_CONDITIONING,
field: 'clip',
},
},
{
source: {
node_id: modelLoaderNodeId,
field: 'clip2',
},
destination: {
node_id: NEGATIVE_CONDITIONING,
field: 'clip2',
},
},
// Connect everything to Denoise Latents
{
source: {
node_id: POSITIVE_CONDITIONING,
field: 'conditioning',
},
destination: {
node_id: SDXL_DENOISE_LATENTS,
field: 'positive_conditioning',
},
},
{
source: {
node_id: NEGATIVE_CONDITIONING,
field: 'conditioning',
},
destination: {
node_id: SDXL_DENOISE_LATENTS,
field: 'negative_conditioning',
},
},
{
source: {
node_id: NOISE,
field: 'noise',
},
destination: {
node_id: SDXL_DENOISE_LATENTS,
field: 'noise',
},
},
{
source: {
node_id: IMAGE_TO_LATENTS,
field: 'latents',
},
destination: {
node_id: SDXL_DENOISE_LATENTS,
field: 'latents',
},
},
// Decode Denoised Latents To Image
{
source: {
node_id: SDXL_DENOISE_LATENTS,
field: 'latents',
},
destination: {
node_id: LATENTS_TO_IMAGE,
field: 'latents',
},
},
],
};
// handle `fit`
if (shouldFitToWidthHeight && (initialImage.width !== width || initialImage.height !== height)) {
// The init image needs to be resized to the specified width and height before being passed to `IMAGE_TO_LATENTS`
// Create a resize node, explicitly setting its image
const resizeNode: ImageResizeInvocation = {
id: RESIZE,
type: 'img_resize',
image: {
image_name: initialImage.imageName,
},
is_intermediate: true,
width,
height,
};
graph.nodes[RESIZE] = resizeNode;
// The `RESIZE` node then passes its image to `IMAGE_TO_LATENTS`
graph.edges.push({
source: { node_id: RESIZE, field: 'image' },
destination: {
node_id: IMAGE_TO_LATENTS,
field: 'image',
},
});
// The `RESIZE` node also passes its width and height to `NOISE`
graph.edges.push({
source: { node_id: RESIZE, field: 'width' },
destination: {
node_id: NOISE,
field: 'width',
},
});
graph.edges.push({
source: { node_id: RESIZE, field: 'height' },
destination: {
node_id: NOISE,
field: 'height',
},
});
} else {
// We are not resizing, so we need to set the image on the `IMAGE_TO_LATENTS` node explicitly
(graph.nodes[IMAGE_TO_LATENTS] as ImageToLatentsInvocation).image = {
image_name: initialImage.imageName,
};
// Pass the image's dimensions to the `NOISE` node
graph.edges.push({
source: { node_id: IMAGE_TO_LATENTS, field: 'width' },
destination: {
node_id: NOISE,
field: 'width',
},
});
graph.edges.push({
source: { node_id: IMAGE_TO_LATENTS, field: 'height' },
destination: {
node_id: NOISE,
field: 'height',
},
});
}
const modelConfig = await fetchModelConfigWithTypeGuard(model.key, isNonRefinerMainModelConfig);
addCoreMetadataNode(
graph,
{
generation_mode: 'sdxl_img2img',
cfg_scale,
cfg_rescale_multiplier,
height,
width,
positive_prompt: positivePrompt,
negative_prompt: negativePrompt,
model: getModelMetadataField(modelConfig),
seed,
steps,
rand_device: use_cpu ? 'cpu' : 'cuda',
scheduler,
strength,
init_image: initialImage.imageName,
positive_style_prompt: positiveStylePrompt,
negative_style_prompt: negativeStylePrompt,
},
LATENTS_TO_IMAGE
);
// Add Seamless To Graph
if (seamlessXAxis || seamlessYAxis) {
addSeamlessToLinearGraph(state, graph, modelLoaderNodeId);
modelLoaderNodeId = SEAMLESS;
}
// Add Refiner if enabled
if (refinerModel) {
await addSDXLRefinerToGraph(state, graph, SDXL_DENOISE_LATENTS);
if (seamlessXAxis || seamlessYAxis) {
modelLoaderNodeId = SDXL_REFINER_SEAMLESS;
}
}
// optionally add custom VAE
await addVAEToGraph(state, graph, modelLoaderNodeId);
// Add LoRA Support
await addSDXLLoRAsToGraph(state, graph, SDXL_DENOISE_LATENTS, modelLoaderNodeId);
// add controlnet, mutating `graph`
await addControlNetToLinearGraph(state, graph, SDXL_DENOISE_LATENTS);
// Add IP Adapter
await addIPAdapterToLinearGraph(state, graph, SDXL_DENOISE_LATENTS);
await addT2IAdaptersToLinearGraph(state, graph, SDXL_DENOISE_LATENTS);
// NSFW & watermark - must be last thing added to graph
if (state.system.shouldUseNSFWChecker) {
// must add before watermarker!
addNSFWCheckerToGraph(state, graph);
}
if (state.system.shouldUseWatermarker) {
// must add after nsfw checker!
addWatermarkerToGraph(state, graph);
}
return graph;
};

View File

@ -57,14 +57,10 @@ export const NEGATIVE_CONDITIONING_COLLECT = 'negative_conditioning_collect';
// friendly graph ids // friendly graph ids
export const CONTROL_LAYERS_GRAPH = 'control_layers_graph'; export const CONTROL_LAYERS_GRAPH = 'control_layers_graph';
export const SDXL_CONTROL_LAYERS_GRAPH = 'sdxl_control_layers_graph'; export const SDXL_CONTROL_LAYERS_GRAPH = 'sdxl_control_layers_graph';
export const TEXT_TO_IMAGE_GRAPH = 'text_to_image_graph';
export const IMAGE_TO_IMAGE_GRAPH = 'image_to_image_graph';
export const CANVAS_TEXT_TO_IMAGE_GRAPH = 'canvas_text_to_image_graph'; export const CANVAS_TEXT_TO_IMAGE_GRAPH = 'canvas_text_to_image_graph';
export const CANVAS_IMAGE_TO_IMAGE_GRAPH = 'canvas_image_to_image_graph'; export const CANVAS_IMAGE_TO_IMAGE_GRAPH = 'canvas_image_to_image_graph';
export const CANVAS_INPAINT_GRAPH = 'canvas_inpaint_graph'; export const CANVAS_INPAINT_GRAPH = 'canvas_inpaint_graph';
export const CANVAS_OUTPAINT_GRAPH = 'canvas_outpaint_graph'; export const CANVAS_OUTPAINT_GRAPH = 'canvas_outpaint_graph';
export const SDXL_TEXT_TO_IMAGE_GRAPH = 'sdxl_text_to_image_graph';
export const SDXL_IMAGE_TO_IMAGE_GRAPH = 'sxdl_image_to_image_graph';
export const SDXL_CANVAS_TEXT_TO_IMAGE_GRAPH = 'sdxl_canvas_text_to_image_graph'; export const SDXL_CANVAS_TEXT_TO_IMAGE_GRAPH = 'sdxl_canvas_text_to_image_graph';
export const SDXL_CANVAS_IMAGE_TO_IMAGE_GRAPH = 'sdxl_canvas_image_to_image_graph'; export const SDXL_CANVAS_IMAGE_TO_IMAGE_GRAPH = 'sdxl_canvas_image_to_image_graph';
export const SDXL_CANVAS_INPAINT_GRAPH = 'sdxl_canvas_inpaint_graph'; export const SDXL_CANVAS_INPAINT_GRAPH = 'sdxl_canvas_inpaint_graph';

View File

@ -1,34 +0,0 @@
import { FormControl, FormLabel, Switch } from '@invoke-ai/ui-library';
import type { RootState } from 'app/store/store';
import { useAppDispatch, useAppSelector } from 'app/store/storeHooks';
import { InformationalPopover } from 'common/components/InformationalPopover/InformationalPopover';
import { setShouldFitToWidthHeight } from 'features/parameters/store/generationSlice';
import type { ChangeEvent } from 'react';
import { memo, useCallback } from 'react';
import { useTranslation } from 'react-i18next';
const ImageToImageFit = () => {
const dispatch = useAppDispatch();
const shouldFitToWidthHeight = useAppSelector((state: RootState) => state.generation.shouldFitToWidthHeight);
const handleChangeFit = useCallback(
(e: ChangeEvent<HTMLInputElement>) => {
dispatch(setShouldFitToWidthHeight(e.target.checked));
},
[dispatch]
);
const { t } = useTranslation();
return (
<FormControl w="full">
<InformationalPopover feature="imageFit">
<FormLabel flexGrow={1}>{t('parameters.imageFit')}</FormLabel>
</InformationalPopover>
<Switch isChecked={shouldFitToWidthHeight} onChange={handleChangeFit} />
</FormControl>
);
};
export default memo(ImageToImageFit);

View File

@ -1,61 +0,0 @@
import { skipToken } from '@reduxjs/toolkit/query';
import { createMemoizedSelector } from 'app/store/createMemoizedSelector';
import { useAppDispatch, useAppSelector } from 'app/store/storeHooks';
import IAIDndImage from 'common/components/IAIDndImage';
import { IAINoContentFallback } from 'common/components/IAIImageFallback';
import type { TypesafeDraggableData, TypesafeDroppableData } from 'features/dnd/types';
import { clearInitialImage, selectGenerationSlice } from 'features/parameters/store/generationSlice';
import { memo, useEffect, useMemo } from 'react';
import { useTranslation } from 'react-i18next';
import { useGetImageDTOQuery } from 'services/api/endpoints/images';
const selectInitialImage = createMemoizedSelector(selectGenerationSlice, (generation) => generation.initialImage);
const InitialImage = () => {
const { t } = useTranslation();
const dispatch = useAppDispatch();
const initialImage = useAppSelector(selectInitialImage);
const isConnected = useAppSelector((s) => s.system.isConnected);
const { currentData: imageDTO, isError } = useGetImageDTOQuery(initialImage?.imageName ?? skipToken);
const draggableData = useMemo<TypesafeDraggableData | undefined>(() => {
if (imageDTO) {
return {
id: 'initial-image',
payloadType: 'IMAGE_DTO',
payload: { imageDTO },
};
}
}, [imageDTO]);
const droppableData = useMemo<TypesafeDroppableData | undefined>(
() => ({
id: 'initial-image',
actionType: 'SET_INITIAL_IMAGE',
}),
[]
);
useEffect(() => {
if (isError && isConnected) {
// The image doesn't exist, reset init image
dispatch(clearInitialImage());
}
}, [dispatch, isConnected, isError]);
return (
<IAIDndImage
imageDTO={imageDTO}
droppableData={droppableData}
draggableData={draggableData}
isUploadDisabled={true}
fitContainer
dropLabel={t('toast.setInitialImage')}
noContentFallback={<IAINoContentFallback label={t('parameters.invoke.noInitialImageSelected')} />}
dataTestId="initial-image"
/>
);
};
export default memo(InitialImage);

View File

@ -1,87 +0,0 @@
import { Flex, IconButton, Spacer, Text } from '@invoke-ai/ui-library';
import { createMemoizedSelector } from 'app/store/createMemoizedSelector';
import { useAppDispatch, useAppSelector } from 'app/store/storeHooks';
import { useImageUploadButton } from 'common/hooks/useImageUploadButton';
import { parseAndRecallImageDimensions } from 'features/metadata/util/handlers';
import { clearInitialImage, selectGenerationSlice } from 'features/parameters/store/generationSlice';
import { memo, useCallback } from 'react';
import { useHotkeys } from 'react-hotkeys-hook';
import { useTranslation } from 'react-i18next';
import { PiArrowCounterClockwiseBold, PiRulerBold, PiUploadSimpleBold } from 'react-icons/pi';
import type { PostUploadAction } from 'services/api/types';
import InitialImage from './InitialImage';
const selectInitialImage = createMemoizedSelector(selectGenerationSlice, (generation) => generation.initialImage);
const postUploadAction: PostUploadAction = {
type: 'SET_INITIAL_IMAGE',
};
const InitialImageDisplay = () => {
const { t } = useTranslation();
const initialImage = useAppSelector(selectInitialImage);
const dispatch = useAppDispatch();
const { getUploadButtonProps, getUploadInputProps } = useImageUploadButton({
postUploadAction,
});
const handleReset = useCallback(() => {
dispatch(clearInitialImage());
}, [dispatch]);
const handleUseSizeInitialImage = useCallback(() => {
if (initialImage) {
parseAndRecallImageDimensions(initialImage);
}
}, [initialImage]);
useHotkeys('shift+d', handleUseSizeInitialImage, [initialImage]);
return (
<Flex
layerStyle="first"
position="relative"
flexDirection="column"
height="full"
width="full"
alignItems="center"
justifyContent="center"
borderRadius="base"
p={2}
gap={4}
>
<Flex w="full" flexWrap="wrap" justifyContent="center" alignItems="center" gap={2}>
<Text ps={2} fontWeight="semibold" userSelect="none" color="base.200">
{t('metadata.initImage')}
</Text>
<Spacer />
<IconButton
tooltip={t('toast.uploadInitialImage')}
aria-label={t('toast.uploadInitialImage')}
icon={<PiUploadSimpleBold />}
{...getUploadButtonProps()}
/>
<IconButton
tooltip={`${t('parameters.useSize')} (Shift+D)`}
aria-label={`${t('parameters.useSize')} (Shift+D)`}
icon={<PiRulerBold />}
onClick={handleUseSizeInitialImage}
isDisabled={!initialImage}
/>
<IconButton
tooltip={t('toast.resetInitialImage')}
aria-label={t('toast.resetInitialImage')}
icon={<PiArrowCounterClockwiseBold />}
onClick={handleReset}
isDisabled={!initialImage}
/>
</Flex>
<InitialImage />
<input {...getUploadInputProps()} />
</Flex>
);
};
export default memo(InitialImageDisplay);

View File

@ -2,8 +2,8 @@ import { skipToken } from '@reduxjs/toolkit/query';
import { useAppToaster } from 'app/components/Toaster'; import { useAppToaster } from 'app/components/Toaster';
import { useAppDispatch, useAppSelector } from 'app/store/storeHooks'; import { useAppDispatch, useAppSelector } from 'app/store/storeHooks';
import { setInitialCanvasImage } from 'features/canvas/store/canvasSlice'; import { setInitialCanvasImage } from 'features/canvas/store/canvasSlice';
import { iiLayerAdded } from 'features/controlLayers/store/controlLayersSlice';
import { parseAndRecallAllMetadata } from 'features/metadata/util/handlers'; import { parseAndRecallAllMetadata } from 'features/metadata/util/handlers';
import { initialImageSelected } from 'features/parameters/store/actions';
import { selectOptimalDimension } from 'features/parameters/store/generationSlice'; import { selectOptimalDimension } from 'features/parameters/store/generationSlice';
import { setActiveTab } from 'features/ui/store/uiSlice'; import { setActiveTab } from 'features/ui/store/uiSlice';
import { t } from 'i18next'; import { t } from 'i18next';
@ -37,7 +37,7 @@ export const usePreselectedImage = (selectedImage?: {
const handleSendToImg2Img = useCallback(() => { const handleSendToImg2Img = useCallback(() => {
if (selectedImageDto) { if (selectedImageDto) {
dispatch(initialImageSelected(selectedImageDto)); dispatch(iiLayerAdded(selectedImageDto));
} }
}, [dispatch, selectedImageDto]); }, [dispatch, selectedImageDto]);

View File

@ -1,8 +1,5 @@
import { createAction } from '@reduxjs/toolkit'; import { createAction } from '@reduxjs/toolkit';
import type { ParameterModel } from 'features/parameters/types/parameterSchemas'; import type { ParameterModel } from 'features/parameters/types/parameterSchemas';
import type { ImageDTO } from 'services/api/types';
export const initialImageSelected = createAction<ImageDTO | undefined>('generation/initialImageSelected');
export const modelSelected = createAction<ParameterModel>('generation/modelSelected'); export const modelSelected = createAction<ParameterModel>('generation/modelSelected');

View File

@ -16,7 +16,6 @@ import { getOptimalDimension } from 'features/parameters/util/optimalDimension';
import { configChanged } from 'features/system/store/configSlice'; import { configChanged } from 'features/system/store/configSlice';
import { clamp } from 'lodash-es'; import { clamp } from 'lodash-es';
import type { RgbaColor } from 'react-colorful'; import type { RgbaColor } from 'react-colorful';
import type { ImageDTO } from 'services/api/types';
import type { GenerationState } from './types'; import type { GenerationState } from './types';
@ -34,7 +33,6 @@ const initialGenerationState: GenerationState = {
canvasCoherenceMinDenoise: 0, canvasCoherenceMinDenoise: 0,
canvasCoherenceEdgeSize: 16, canvasCoherenceEdgeSize: 16,
seed: 0, seed: 0,
shouldFitToWidthHeight: true,
shouldRandomizeSeed: true, shouldRandomizeSeed: true,
steps: 50, steps: 50,
model: null, model: null,
@ -86,15 +84,9 @@ export const generationSlice = createSlice({
setSeamlessYAxis: (state, action: PayloadAction<boolean>) => { setSeamlessYAxis: (state, action: PayloadAction<boolean>) => {
state.seamlessYAxis = action.payload; state.seamlessYAxis = action.payload;
}, },
setShouldFitToWidthHeight: (state, action: PayloadAction<boolean>) => {
state.shouldFitToWidthHeight = action.payload;
},
setShouldRandomizeSeed: (state, action: PayloadAction<boolean>) => { setShouldRandomizeSeed: (state, action: PayloadAction<boolean>) => {
state.shouldRandomizeSeed = action.payload; state.shouldRandomizeSeed = action.payload;
}, },
clearInitialImage: (state) => {
state.initialImage = undefined;
},
setMaskBlur: (state, action: PayloadAction<number>) => { setMaskBlur: (state, action: PayloadAction<number>) => {
state.maskBlur = action.payload; state.maskBlur = action.payload;
}, },
@ -107,10 +99,6 @@ export const generationSlice = createSlice({
setCanvasCoherenceMinDenoise: (state, action: PayloadAction<number>) => { setCanvasCoherenceMinDenoise: (state, action: PayloadAction<number>) => {
state.canvasCoherenceMinDenoise = action.payload; state.canvasCoherenceMinDenoise = action.payload;
}, },
initialImageChanged: (state, action: PayloadAction<ImageDTO>) => {
const { image_name, width, height } = action.payload;
state.initialImage = { imageName: image_name, width, height };
},
modelChanged: { modelChanged: {
reducer: ( reducer: (
state, state,
@ -195,7 +183,6 @@ export const generationSlice = createSlice({
}); });
export const { export const {
clearInitialImage,
setCfgScale, setCfgScale,
setCfgRescaleMultiplier, setCfgRescaleMultiplier,
setImg2imgStrength, setImg2imgStrength,
@ -207,10 +194,8 @@ export const {
setCanvasCoherenceEdgeSize, setCanvasCoherenceEdgeSize,
setCanvasCoherenceMinDenoise, setCanvasCoherenceMinDenoise,
setSeed, setSeed,
setShouldFitToWidthHeight,
setShouldRandomizeSeed, setShouldRandomizeSeed,
setSteps, setSteps,
initialImageChanged,
modelChanged, modelChanged,
vaeSelected, vaeSelected,
setSeamlessXAxis, setSeamlessXAxis,

View File

@ -20,7 +20,6 @@ export interface GenerationState {
cfgRescaleMultiplier: ParameterCFGRescaleMultiplier; cfgRescaleMultiplier: ParameterCFGRescaleMultiplier;
img2imgStrength: ParameterStrength; img2imgStrength: ParameterStrength;
infillMethod: string; infillMethod: string;
initialImage?: { imageName: string; width: number; height: number };
iterations: number; iterations: number;
scheduler: ParameterScheduler; scheduler: ParameterScheduler;
maskBlur: number; maskBlur: number;
@ -29,7 +28,6 @@ export interface GenerationState {
canvasCoherenceMinDenoise: ParameterStrength; canvasCoherenceMinDenoise: ParameterStrength;
canvasCoherenceEdgeSize: number; canvasCoherenceEdgeSize: number;
seed: ParameterSeed; seed: ParameterSeed;
shouldFitToWidthHeight: boolean;
shouldRandomizeSeed: boolean; shouldRandomizeSeed: boolean;
steps: ParameterSteps; steps: ParameterSteps;
model: ParameterModel | null; model: ParameterModel | null;

View File

@ -9,7 +9,6 @@ import { selectHrfSlice } from 'features/hrf/store/hrfSlice';
import ParamScaleBeforeProcessing from 'features/parameters/components/Canvas/InfillAndScaling/ParamScaleBeforeProcessing'; import ParamScaleBeforeProcessing from 'features/parameters/components/Canvas/InfillAndScaling/ParamScaleBeforeProcessing';
import ParamScaledHeight from 'features/parameters/components/Canvas/InfillAndScaling/ParamScaledHeight'; import ParamScaledHeight from 'features/parameters/components/Canvas/InfillAndScaling/ParamScaledHeight';
import ParamScaledWidth from 'features/parameters/components/Canvas/InfillAndScaling/ParamScaledWidth'; import ParamScaledWidth from 'features/parameters/components/Canvas/InfillAndScaling/ParamScaledWidth';
import ImageToImageFit from 'features/parameters/components/ImageToImage/ImageToImageFit';
import ImageToImageStrength from 'features/parameters/components/ImageToImage/ImageToImageStrength'; import ImageToImageStrength from 'features/parameters/components/ImageToImage/ImageToImageStrength';
import { ParamSeedNumberInput } from 'features/parameters/components/Seed/ParamSeedNumberInput'; import { ParamSeedNumberInput } from 'features/parameters/components/Seed/ParamSeedNumberInput';
import { ParamSeedRandomize } from 'features/parameters/components/Seed/ParamSeedRandomize'; import { ParamSeedRandomize } from 'features/parameters/components/Seed/ParamSeedRandomize';
@ -94,8 +93,7 @@ export const ImageSettingsAccordion = memo(() => {
<ParamSeedShuffle /> <ParamSeedShuffle />
<ParamSeedRandomize /> <ParamSeedRandomize />
</Flex> </Flex>
{(activeTabName === 'img2img' || activeTabName === 'unifiedCanvas') && <ImageToImageStrength />} {activeTabName === 'unifiedCanvas' && <ImageToImageStrength />}
{activeTabName === 'img2img' && <ImageToImageFit />}
{activeTabName === 'txt2img' && !isSDXL && <HrfSettings />} {activeTabName === 'txt2img' && !isSDXL && <HrfSettings />}
{activeTabName === 'unifiedCanvas' && ( {activeTabName === 'unifiedCanvas' && (
<> <>

View File

@ -12,7 +12,6 @@ import { selectConfigSlice } from 'features/system/store/configSlice';
import FloatingGalleryButton from 'features/ui/components/FloatingGalleryButton'; import FloatingGalleryButton from 'features/ui/components/FloatingGalleryButton';
import FloatingParametersPanelButtons from 'features/ui/components/FloatingParametersPanelButtons'; import FloatingParametersPanelButtons from 'features/ui/components/FloatingParametersPanelButtons';
import ParametersPanelTextToImage from 'features/ui/components/ParametersPanelTextToImage'; import ParametersPanelTextToImage from 'features/ui/components/ParametersPanelTextToImage';
import ImageToImageTab from 'features/ui/components/tabs/ImageToImageTab';
import ModelManagerTab from 'features/ui/components/tabs/ModelManagerTab'; import ModelManagerTab from 'features/ui/components/tabs/ModelManagerTab';
import NodesTab from 'features/ui/components/tabs/NodesTab'; import NodesTab from 'features/ui/components/tabs/NodesTab';
import QueueTab from 'features/ui/components/tabs/QueueTab'; import QueueTab from 'features/ui/components/tabs/QueueTab';
@ -30,7 +29,7 @@ import { memo, useCallback, useMemo, useRef } from 'react';
import { useHotkeys } from 'react-hotkeys-hook'; import { useHotkeys } from 'react-hotkeys-hook';
import { useTranslation } from 'react-i18next'; import { useTranslation } from 'react-i18next';
import { PiFlowArrowBold } from 'react-icons/pi'; import { PiFlowArrowBold } from 'react-icons/pi';
import { RiBox2Line, RiBrushLine, RiImage2Line, RiInputMethodLine, RiPlayList2Fill } from 'react-icons/ri'; import { RiBox2Line, RiBrushLine, RiInputMethodLine, RiPlayList2Fill } from 'react-icons/ri';
import type { ImperativePanelGroupHandle } from 'react-resizable-panels'; import type { ImperativePanelGroupHandle } from 'react-resizable-panels';
import { Panel, PanelGroup } from 'react-resizable-panels'; import { Panel, PanelGroup } from 'react-resizable-panels';
@ -51,12 +50,6 @@ const TAB_DATA: Record<InvokeTabName, TabData> = {
icon: <RiInputMethodLine />, icon: <RiInputMethodLine />,
content: <TextToImageTab />, content: <TextToImageTab />,
}, },
img2img: {
id: 'img2img',
translationKey: 'common.img2img',
icon: <RiImage2Line />,
content: <ImageToImageTab />,
},
unifiedCanvas: { unifiedCanvas: {
id: 'unifiedCanvas', id: 'unifiedCanvas',
translationKey: 'common.unifiedCanvas', translationKey: 'common.unifiedCanvas',

View File

@ -1,56 +0,0 @@
import { Box, Flex } from '@invoke-ai/ui-library';
import CurrentImageDisplay from 'features/gallery/components/CurrentImage/CurrentImageDisplay';
import InitialImageDisplay from 'features/parameters/components/ImageToImage/InitialImageDisplay';
import ResizeHandle from 'features/ui/components/tabs/ResizeHandle';
import { usePanelStorage } from 'features/ui/hooks/usePanelStorage';
import type { CSSProperties } from 'react';
import { memo, useCallback, useRef } from 'react';
import type { ImperativePanelGroupHandle } from 'react-resizable-panels';
import { Panel, PanelGroup } from 'react-resizable-panels';
const panelGroupStyles: CSSProperties = {
height: '100%',
width: '100%',
};
const panelStyles: CSSProperties = {
position: 'relative',
};
const ImageToImageTab = () => {
const panelGroupRef = useRef<ImperativePanelGroupHandle>(null);
const handleDoubleClickHandle = useCallback(() => {
if (!panelGroupRef.current) {
return;
}
panelGroupRef.current.setLayout([50, 50]);
}, []);
const panelStorage = usePanelStorage();
return (
<Box position="relative" w="full" h="full">
<PanelGroup
ref={panelGroupRef}
autoSaveId="imageTab.content"
direction="horizontal"
style={panelGroupStyles}
storage={panelStorage}
>
<Panel id="imageTab.content.initImage" order={0} defaultSize={50} minSize={25} style={panelStyles}>
<InitialImageDisplay />
</Panel>
<ResizeHandle orientation="vertical" onDoubleClick={handleDoubleClickHandle} />
<Panel id="imageTab.content.selectedImage" order={1} defaultSize={50} minSize={25}>
<Box layerStyle="first" position="relative" w="full" h="full" p={2} borderRadius="base">
<Flex w="full" h="full">
<CurrentImageDisplay />
</Flex>
</Box>
</Panel>
</PanelGroup>
</Box>
);
};
export default memo(ImageToImageTab);

View File

@ -1,3 +1,3 @@
export const TAB_NUMBER_MAP = ['txt2img', 'img2img', 'unifiedCanvas', 'nodes', 'modelManager', 'queue'] as const; export const TAB_NUMBER_MAP = ['txt2img', 'unifiedCanvas', 'nodes', 'modelManager', 'queue'] as const;
export type InvokeTabName = (typeof TAB_NUMBER_MAP)[number]; export type InvokeTabName = (typeof TAB_NUMBER_MAP)[number];

View File

@ -2,7 +2,6 @@ import type { PayloadAction } from '@reduxjs/toolkit';
import { createSlice } from '@reduxjs/toolkit'; import { createSlice } from '@reduxjs/toolkit';
import type { PersistConfig, RootState } from 'app/store/store'; import type { PersistConfig, RootState } from 'app/store/store';
import { workflowLoadRequested } from 'features/nodes/store/actions'; import { workflowLoadRequested } from 'features/nodes/store/actions';
import { initialImageChanged } from 'features/parameters/store/generationSlice';
import type { InvokeTabName } from './tabMap'; import type { InvokeTabName } from './tabMap';
import type { UIState } from './uiTypes'; import type { UIState } from './uiTypes';
@ -43,9 +42,6 @@ export const uiSlice = createSlice({
}, },
}, },
extraReducers(builder) { extraReducers(builder) {
builder.addCase(initialImageChanged, (state) => {
state.activeTab = 'img2img';
});
builder.addCase(workflowLoadRequested, (state) => { builder.addCase(workflowLoadRequested, (state) => {
state.activeTab = 'nodes'; state.activeTab = 'nodes';
}); });

View File

@ -198,10 +198,6 @@ export type IILayerImagePostUploadAction = {
layerId: string; layerId: string;
}; };
type InitialImageAction = {
type: 'SET_INITIAL_IMAGE';
};
type NodesAction = { type NodesAction = {
type: 'SET_NODES_IMAGE'; type: 'SET_NODES_IMAGE';
nodeId: string; nodeId: string;
@ -223,7 +219,6 @@ type AddToBatchAction = {
export type PostUploadAction = export type PostUploadAction =
| ControlAdapterAction | ControlAdapterAction
| InitialImageAction
| NodesAction | NodesAction
| CanvasInitialImageAction | CanvasInitialImageAction
| ToastAction | ToastAction