fixed merge conflicts

This commit is contained in:
Jennifer Player
2023-09-20 10:00:11 -04:00
334 changed files with 13464 additions and 4184 deletions

View File

@ -153,8 +153,8 @@ const IAICanvas = () => {
});
resizeObserver.observe(containerRef.current);
dispatch(canvasResized(containerRef.current.getBoundingClientRect()));
const { width, height } = containerRef.current.getBoundingClientRect();
dispatch(canvasResized({ width, height }));
return () => {
resizeObserver.disconnect();

View File

@ -1,23 +1,24 @@
import { createSelector } from '@reduxjs/toolkit';
import { stateSelector } from 'app/store/store';
import { useAppSelector } from 'app/store/storeHooks';
import { systemSelector } from 'features/system/store/systemSelectors';
import { ImageConfig } from 'konva/lib/shapes/Image';
import { isEqual } from 'lodash-es';
import { memo, useEffect, useState } from 'react';
import { Image as KonvaImage } from 'react-konva';
import { canvasSelector } from '../store/canvasSelectors';
const selector = createSelector(
[systemSelector, canvasSelector],
(system, canvas) => {
const { progressImage, sessionId } = system;
const { sessionId: canvasSessionId, boundingBox } =
canvas.layerState.stagingArea;
[stateSelector],
({ system, canvas }) => {
const { denoiseProgress } = system;
const { boundingBox } = canvas.layerState.stagingArea;
const { sessionIds } = canvas;
return {
boundingBox,
progressImage: sessionId === canvasSessionId ? progressImage : undefined,
progressImage:
denoiseProgress && sessionIds.includes(denoiseProgress.session_id)
? denoiseProgress.progress_image
: undefined,
};
},
{

View File

@ -11,8 +11,9 @@ import {
setShouldShowStagingImage,
setShouldShowStagingOutline,
} from 'features/canvas/store/canvasSlice';
import { isEqual } from 'lodash-es';
import { skipToken } from '@reduxjs/toolkit/dist/query';
import { defaultSelectorOptions } from 'app/store/util/defaultMemoizeOptions';
import { memo, useCallback } from 'react';
import { useHotkeys } from 'react-hotkeys-hook';
import { useTranslation } from 'react-i18next';
@ -25,16 +26,15 @@ import {
FaPlus,
FaSave,
} from 'react-icons/fa';
import { stagingAreaImageSaved } from '../store/actions';
import { useGetImageDTOQuery } from 'services/api/endpoints/images';
import { skipToken } from '@reduxjs/toolkit/dist/query';
import { stagingAreaImageSaved } from '../store/actions';
const selector = createSelector(
[canvasSelector],
(canvas) => {
const {
layerState: {
stagingArea: { images, selectedImageIndex, sessionId },
stagingArea: { images, selectedImageIndex },
},
shouldShowStagingOutline,
shouldShowStagingImage,
@ -47,14 +47,9 @@ const selector = createSelector(
isOnLastImage: selectedImageIndex === images.length - 1,
shouldShowStagingImage,
shouldShowStagingOutline,
sessionId,
};
},
{
memoizeOptions: {
resultEqualityCheck: isEqual,
},
}
defaultSelectorOptions
);
const IAICanvasStagingAreaToolbar = () => {
@ -64,7 +59,6 @@ const IAICanvasStagingAreaToolbar = () => {
isOnLastImage,
currentStagingAreaImage,
shouldShowStagingImage,
sessionId,
} = useAppSelector(selector);
const { t } = useTranslation();
@ -121,8 +115,8 @@ const IAICanvasStagingAreaToolbar = () => {
);
const handleAccept = useCallback(
() => dispatch(commitStagingAreaImage(sessionId)),
[dispatch, sessionId]
() => dispatch(commitStagingAreaImage()),
[dispatch]
);
const { data: imageDTO } = useGetImageDTOQuery(

View File

@ -1,24 +1,23 @@
import { createSelector } from '@reduxjs/toolkit';
import { useAppDispatch, useAppSelector } from 'app/store/storeHooks';
import IAIIconButton from 'common/components/IAIIconButton';
import { canvasSelector } from 'features/canvas/store/canvasSelectors';
import { activeTabNameSelector } from 'features/ui/store/uiSelectors';
import { useHotkeys } from 'react-hotkeys-hook';
import { FaRedo } from 'react-icons/fa';
import { redo } from 'features/canvas/store/canvasSlice';
import { systemSelector } from 'features/system/store/systemSelectors';
import { stateSelector } from 'app/store/store';
import { isEqual } from 'lodash-es';
import { useTranslation } from 'react-i18next';
const canvasRedoSelector = createSelector(
[canvasSelector, activeTabNameSelector, systemSelector],
(canvas, activeTabName, system) => {
[stateSelector, activeTabNameSelector],
({ canvas }, activeTabName) => {
const { futureLayerStates } = canvas;
return {
canRedo: futureLayerStates.length > 0 && !system.isProcessing,
canRedo: futureLayerStates.length > 0,
activeTabName,
};
},

View File

@ -1,14 +1,12 @@
import { ButtonGroup, Flex } from '@chakra-ui/react';
import { createSelector } from '@reduxjs/toolkit';
import { stateSelector } from 'app/store/store';
import { useAppDispatch, useAppSelector } from 'app/store/storeHooks';
import IAIColorPicker from 'common/components/IAIColorPicker';
import IAIIconButton from 'common/components/IAIIconButton';
import IAIPopover from 'common/components/IAIPopover';
import IAISlider from 'common/components/IAISlider';
import {
canvasSelector,
isStagingSelector,
} from 'features/canvas/store/canvasSelectors';
import { isStagingSelector } from 'features/canvas/store/canvasSelectors';
import {
addEraseRect,
addFillRect,
@ -16,7 +14,6 @@ import {
setBrushSize,
setTool,
} from 'features/canvas/store/canvasSlice';
import { systemSelector } from 'features/system/store/systemSelectors';
import { clamp, isEqual } from 'lodash-es';
import { memo } from 'react';
@ -32,15 +29,13 @@ import {
} from 'react-icons/fa';
export const selector = createSelector(
[canvasSelector, isStagingSelector, systemSelector],
(canvas, isStaging, system) => {
const { isProcessing } = system;
[stateSelector, isStagingSelector],
({ canvas }, isStaging) => {
const { tool, brushColor, brushSize } = canvas;
return {
tool,
isStaging,
isProcessing,
brushColor,
brushSize,
};

View File

@ -1,5 +1,6 @@
import { Box, ButtonGroup, Flex } from '@chakra-ui/react';
import { createSelector } from '@reduxjs/toolkit';
import { stateSelector } from 'app/store/store';
import { useAppDispatch, useAppSelector } from 'app/store/storeHooks';
import IAIIconButton from 'common/components/IAIIconButton';
import IAIMantineSelect from 'common/components/IAIMantineSelect';
@ -11,10 +12,7 @@ import {
canvasMerged,
canvasSavedToGallery,
} from 'features/canvas/store/actions';
import {
canvasSelector,
isStagingSelector,
} from 'features/canvas/store/canvasSelectors';
import { isStagingSelector } from 'features/canvas/store/canvasSelectors';
import {
resetCanvas,
resetCanvasView,
@ -27,9 +25,9 @@ import {
LAYER_NAMES_DICT,
} from 'features/canvas/store/canvasTypes';
import { getCanvasBaseLayer } from 'features/canvas/util/konvaInstanceProvider';
import { systemSelector } from 'features/system/store/systemSelectors';
import { useCopyImageToClipboard } from 'features/ui/hooks/useCopyImageToClipboard';
import { isEqual } from 'lodash-es';
import { memo } from 'react';
import { useHotkeys } from 'react-hotkeys-hook';
import { useTranslation } from 'react-i18next';
import {
@ -47,17 +45,14 @@ import IAICanvasRedoButton from './IAICanvasRedoButton';
import IAICanvasSettingsButtonPopover from './IAICanvasSettingsButtonPopover';
import IAICanvasToolChooserOptions from './IAICanvasToolChooserOptions';
import IAICanvasUndoButton from './IAICanvasUndoButton';
import { memo } from 'react';
export const selector = createSelector(
[systemSelector, canvasSelector, isStagingSelector],
(system, canvas, isStaging) => {
const { isProcessing } = system;
[stateSelector, isStagingSelector],
({ canvas }, isStaging) => {
const { tool, shouldCropToBoundingBoxOnSave, layer, isMaskEnabled } =
canvas;
return {
isProcessing,
isStaging,
isMaskEnabled,
tool,
@ -74,8 +69,7 @@ export const selector = createSelector(
const IAICanvasToolbar = () => {
const dispatch = useAppDispatch();
const { isProcessing, isStaging, isMaskEnabled, layer, tool } =
useAppSelector(selector);
const { isStaging, isMaskEnabled, layer, tool } = useAppSelector(selector);
const canvasBaseLayer = getCanvasBaseLayer();
const { t } = useTranslation();
@ -118,7 +112,7 @@ const IAICanvasToolbar = () => {
enabled: () => !isStaging,
preventDefault: true,
},
[canvasBaseLayer, isProcessing]
[canvasBaseLayer]
);
useHotkeys(
@ -130,7 +124,7 @@ const IAICanvasToolbar = () => {
enabled: () => !isStaging,
preventDefault: true,
},
[canvasBaseLayer, isProcessing]
[canvasBaseLayer]
);
useHotkeys(
@ -142,7 +136,7 @@ const IAICanvasToolbar = () => {
enabled: () => !isStaging && isClipboardAPIAvailable,
preventDefault: true,
},
[canvasBaseLayer, isProcessing, isClipboardAPIAvailable]
[canvasBaseLayer, isClipboardAPIAvailable]
);
useHotkeys(
@ -154,7 +148,7 @@ const IAICanvasToolbar = () => {
enabled: () => !isStaging,
preventDefault: true,
},
[canvasBaseLayer, isProcessing]
[canvasBaseLayer]
);
const handleSelectMoveTool = () => dispatch(setTool('move'));

View File

@ -1,24 +1,23 @@
import { createSelector } from '@reduxjs/toolkit';
import { useAppDispatch, useAppSelector } from 'app/store/storeHooks';
import IAIIconButton from 'common/components/IAIIconButton';
import { canvasSelector } from 'features/canvas/store/canvasSelectors';
import { useHotkeys } from 'react-hotkeys-hook';
import { FaUndo } from 'react-icons/fa';
import { undo } from 'features/canvas/store/canvasSlice';
import { systemSelector } from 'features/system/store/systemSelectors';
import { activeTabNameSelector } from 'features/ui/store/uiSelectors';
import { isEqual } from 'lodash-es';
import { useTranslation } from 'react-i18next';
import { stateSelector } from 'app/store/store';
const canvasUndoSelector = createSelector(
[canvasSelector, activeTabNameSelector, systemSelector],
(canvas, activeTabName, system) => {
[stateSelector, activeTabNameSelector],
({ canvas }, activeTabName) => {
const { pastLayerStates } = canvas;
return {
canUndo: pastLayerStates.length > 0 && !system.isProcessing,
canUndo: pastLayerStates.length > 0,
activeTabName,
};
},

View File

@ -1,16 +1,12 @@
import { createSelector } from '@reduxjs/toolkit';
import { RootState } from 'app/store/store';
import { systemSelector } from 'features/system/store/systemSelectors';
import { activeTabNameSelector } from 'features/ui/store/uiSelectors';
import { RootState, stateSelector } from 'app/store/store';
import { CanvasImage, CanvasState, isCanvasBaseImage } from './canvasTypes';
export const canvasSelector = (state: RootState): CanvasState => state.canvas;
export const isStagingSelector = createSelector(
[canvasSelector, activeTabNameSelector, systemSelector],
(canvas, activeTabName, system) =>
canvas.layerState.stagingArea.images.length > 0 ||
(activeTabName === 'unifiedCanvas' && system.isProcessing)
[stateSelector],
({ canvas }) => canvas.layerState.stagingArea.images.length > 0
);
export const initialCanvasImageSelector = (

View File

@ -85,6 +85,8 @@ export const initialCanvasState: CanvasState = {
stageDimensions: { width: 0, height: 0 },
stageScale: 1,
tool: 'brush',
sessionIds: [],
batchIds: [],
};
export const canvasSlice = createSlice({
@ -297,18 +299,26 @@ export const canvasSlice = createSlice({
setIsMoveStageKeyHeld: (state, action: PayloadAction<boolean>) => {
state.isMoveStageKeyHeld = action.payload;
},
canvasSessionIdChanged: (state, action: PayloadAction<string>) => {
state.layerState.stagingArea.sessionId = action.payload;
canvasBatchIdAdded: (state, action: PayloadAction<string>) => {
state.batchIds.push(action.payload);
},
canvasSessionIdAdded: (state, action: PayloadAction<string>) => {
state.sessionIds.push(action.payload);
},
canvasBatchesAndSessionsReset: (state) => {
state.sessionIds = [];
state.batchIds = [];
},
stagingAreaInitialized: (
state,
action: PayloadAction<{ sessionId: string; boundingBox: IRect }>
action: PayloadAction<{
boundingBox: IRect;
}>
) => {
const { sessionId, boundingBox } = action.payload;
const { boundingBox } = action.payload;
state.layerState.stagingArea = {
boundingBox,
sessionId,
images: [],
selectedImageIndex: -1,
};
@ -632,10 +642,7 @@ export const canvasSlice = createSlice({
0
);
},
commitStagingAreaImage: (
state,
_action: PayloadAction<string | undefined>
) => {
commitStagingAreaImage: (state) => {
if (!state.layerState.stagingArea.images.length) {
return;
}
@ -869,9 +876,11 @@ export const {
setScaledBoundingBoxDimensions,
setShouldRestrictStrokesToBox,
stagingAreaInitialized,
canvasSessionIdChanged,
setShouldAntialias,
canvasResized,
canvasBatchIdAdded,
canvasSessionIdAdded,
canvasBatchesAndSessionsReset,
} = canvasSlice.actions;
export default canvasSlice.reducer;

View File

@ -89,7 +89,6 @@ export type CanvasLayerState = {
stagingArea: {
images: CanvasImage[];
selectedImageIndex: number;
sessionId?: string;
boundingBox?: IRect;
};
};
@ -166,6 +165,8 @@ export interface CanvasState {
stageScale: number;
tool: CanvasTool;
generationMode?: GenerationMode;
batchIds: string[];
sessionIds: string[];
}
export type GenerationMode = 'txt2img' | 'img2img' | 'inpaint' | 'outpaint';

View File

@ -18,6 +18,7 @@ import { defaultSelectorOptions } from 'app/store/util/defaultMemoizeOptions';
import IAIIconButton from 'common/components/IAIIconButton';
import IAISwitch from 'common/components/IAISwitch';
import { activeTabNameSelector } from 'features/ui/store/uiSelectors';
import { useTranslation } from 'react-i18next';
import { useToggle } from 'react-use';
import { v4 as uuidv4 } from 'uuid';
import ControlNetImagePreview from './ControlNetImagePreview';
@ -28,7 +29,6 @@ import ParamControlNetBeginEnd from './parameters/ParamControlNetBeginEnd';
import ParamControlNetControlMode from './parameters/ParamControlNetControlMode';
import ParamControlNetProcessorSelect from './parameters/ParamControlNetProcessorSelect';
import ParamControlNetResizeMode from './parameters/ParamControlNetResizeMode';
import { useTranslation } from 'react-i18next';
type ControlNetProps = {
controlNet: ControlNetConfig;

View File

@ -3,7 +3,7 @@ import { memo, useCallback } from 'react';
import { ControlNetConfig } from '../store/controlNetSlice';
import { useAppDispatch } from 'app/store/storeHooks';
import { controlNetImageProcessed } from '../store/actions';
import { useIsReadyToInvoke } from 'common/hooks/useIsReadyToInvoke';
import { useIsReadyToEnqueue } from 'common/hooks/useIsReadyToEnqueue';
type Props = {
controlNet: ControlNetConfig;
@ -12,7 +12,7 @@ type Props = {
const ControlNetPreprocessButton = (props: Props) => {
const { controlNetId, controlImage } = props.controlNet;
const dispatch = useAppDispatch();
const isReady = useIsReadyToInvoke();
const isReady = useIsReadyToEnqueue();
const handleProcess = useCallback(() => {
dispatch(

View File

@ -1,10 +1,9 @@
import { useAppDispatch, useAppSelector } from 'app/store/storeHooks';
import { useAppDispatch } from 'app/store/storeHooks';
import IAISwitch from 'common/components/IAISwitch';
import {
ControlNetConfig,
controlNetAutoConfigToggled,
} from 'features/controlNet/store/controlNetSlice';
import { selectIsBusy } from 'features/system/store/systemSelectors';
import { memo, useCallback } from 'react';
import { useTranslation } from 'react-i18next';
@ -15,7 +14,6 @@ type Props = {
const ParamControlNetShouldAutoConfig = (props: Props) => {
const { controlNetId, isEnabled, shouldAutoConfig } = props.controlNet;
const dispatch = useAppDispatch();
const isBusy = useAppSelector(selectIsBusy);
const { t } = useTranslation();
const handleShouldAutoConfigChanged = useCallback(() => {
@ -28,7 +26,7 @@ const ParamControlNetShouldAutoConfig = (props: Props) => {
aria-label={t('controlnet.autoConfigure')}
isChecked={shouldAutoConfig}
onChange={handleShouldAutoConfigChanged}
isDisabled={isBusy || !isEnabled}
isDisabled={!isEnabled}
/>
);
};

View File

@ -0,0 +1,35 @@
import { Flex } from '@chakra-ui/react';
import { memo } from 'react';
import ParamIPAdapterBeginEnd from './ParamIPAdapterBeginEnd';
import ParamIPAdapterFeatureToggle from './ParamIPAdapterFeatureToggle';
import ParamIPAdapterImage from './ParamIPAdapterImage';
import ParamIPAdapterModelSelect from './ParamIPAdapterModelSelect';
import ParamIPAdapterWeight from './ParamIPAdapterWeight';
const IPAdapterPanel = () => {
return (
<Flex
sx={{
flexDir: 'column',
gap: 3,
paddingInline: 3,
paddingBlock: 2,
paddingBottom: 5,
borderRadius: 'base',
position: 'relative',
bg: 'base.250',
_dark: {
bg: 'base.750',
},
}}
>
<ParamIPAdapterFeatureToggle />
<ParamIPAdapterImage />
<ParamIPAdapterModelSelect />
<ParamIPAdapterWeight />
<ParamIPAdapterBeginEnd />
</Flex>
);
};
export default memo(IPAdapterPanel);

View File

@ -0,0 +1,100 @@
import {
FormControl,
FormLabel,
HStack,
RangeSlider,
RangeSliderFilledTrack,
RangeSliderMark,
RangeSliderThumb,
RangeSliderTrack,
Tooltip,
} from '@chakra-ui/react';
import { RootState } from 'app/store/store';
import { useAppDispatch, useAppSelector } from 'app/store/storeHooks';
import {
ipAdapterBeginStepPctChanged,
ipAdapterEndStepPctChanged,
} from 'features/controlNet/store/controlNetSlice';
import { memo, useCallback } from 'react';
import { useTranslation } from 'react-i18next';
const formatPct = (v: number) => `${Math.round(v * 100)}%`;
const ParamIPAdapterBeginEnd = () => {
const isEnabled = useAppSelector(
(state: RootState) => state.controlNet.isIPAdapterEnabled
);
const beginStepPct = useAppSelector(
(state: RootState) => state.controlNet.ipAdapterInfo.beginStepPct
);
const endStepPct = useAppSelector(
(state: RootState) => state.controlNet.ipAdapterInfo.endStepPct
);
const dispatch = useAppDispatch();
const { t } = useTranslation();
const handleStepPctChanged = useCallback(
(v: number[]) => {
dispatch(ipAdapterBeginStepPctChanged(v[0] as number));
dispatch(ipAdapterEndStepPctChanged(v[1] as number));
},
[dispatch]
);
return (
<FormControl isDisabled={!isEnabled}>
<FormLabel>{t('controlnet.beginEndStepPercent')}</FormLabel>
<HStack w="100%" gap={2} alignItems="center">
<RangeSlider
aria-label={['Begin Step %', 'End Step %!']}
value={[beginStepPct, endStepPct]}
onChange={handleStepPctChanged}
min={0}
max={1}
step={0.01}
minStepsBetweenThumbs={5}
isDisabled={!isEnabled}
>
<RangeSliderTrack>
<RangeSliderFilledTrack />
</RangeSliderTrack>
<Tooltip label={formatPct(beginStepPct)} placement="top" hasArrow>
<RangeSliderThumb index={0} />
</Tooltip>
<Tooltip label={formatPct(endStepPct)} placement="top" hasArrow>
<RangeSliderThumb index={1} />
</Tooltip>
<RangeSliderMark
value={0}
sx={{
insetInlineStart: '0 !important',
insetInlineEnd: 'unset !important',
}}
>
0%
</RangeSliderMark>
<RangeSliderMark
value={0.5}
sx={{
insetInlineStart: '50% !important',
transform: 'translateX(-50%)',
}}
>
50%
</RangeSliderMark>
<RangeSliderMark
value={1}
sx={{
insetInlineStart: 'unset !important',
insetInlineEnd: '0 !important',
}}
>
100%
</RangeSliderMark>
</RangeSlider>
</HStack>
</FormControl>
);
};
export default memo(ParamIPAdapterBeginEnd);

View File

@ -3,39 +3,42 @@ import { stateSelector } from 'app/store/store';
import { useAppDispatch, useAppSelector } from 'app/store/storeHooks';
import { defaultSelectorOptions } from 'app/store/util/defaultMemoizeOptions';
import IAISwitch from 'common/components/IAISwitch';
import { isIPAdapterEnableToggled } from 'features/controlNet/store/controlNetSlice';
import { memo, useCallback } from 'react';
import { isEnabledToggled } from '../store/dynamicPromptsSlice';
import { useTranslation } from 'react-i18next';
import IAIInformationalPopover from 'common/components/IAIInformationalPopover';
const selector = createSelector(
stateSelector,
(state) => {
const { isEnabled } = state.dynamicPrompts;
const { isIPAdapterEnabled } = state.controlNet;
return { isEnabled };
return { isIPAdapterEnabled };
},
defaultSelectorOptions
);
const ParamDynamicPromptsToggle = () => {
const ParamIPAdapterFeatureToggle = () => {
const { isIPAdapterEnabled } = useAppSelector(selector);
const dispatch = useAppDispatch();
const { isEnabled } = useAppSelector(selector);
const { t } = useTranslation();
const handleToggleIsEnabled = useCallback(() => {
dispatch(isEnabledToggled());
const handleChange = useCallback(() => {
dispatch(isIPAdapterEnableToggled());
}, [dispatch]);
return (
<IAIInformationalPopover details="dynamicPromptsToggle">
<IAISwitch
label={t('prompt.enableDynamicPrompts')}
isChecked={isEnabled}
onChange={handleToggleIsEnabled}
label={t('controlnet.enableIPAdapter')}
isChecked={isIPAdapterEnabled}
onChange={handleChange}
formControlProps={{
width: '100%',
}}
/>
</IAIInformationalPopover>
);
};
export default memo(ParamDynamicPromptsToggle);
export default memo(ParamIPAdapterFeatureToggle);

View File

@ -0,0 +1,93 @@
import { Flex } from '@chakra-ui/react';
import { skipToken } from '@reduxjs/toolkit/dist/query';
import { RootState } from 'app/store/store';
import { useAppDispatch, useAppSelector } from 'app/store/storeHooks';
import IAIDndImage from 'common/components/IAIDndImage';
import IAIDndImageIcon from 'common/components/IAIDndImageIcon';
import { IAINoContentFallback } from 'common/components/IAIImageFallback';
import { ipAdapterImageChanged } from 'features/controlNet/store/controlNetSlice';
import {
TypesafeDraggableData,
TypesafeDroppableData,
} from 'features/dnd/types';
import { memo, useMemo } from 'react';
import { useTranslation } from 'react-i18next';
import { FaUndo } from 'react-icons/fa';
import { useGetImageDTOQuery } from 'services/api/endpoints/images';
import { PostUploadAction } from 'services/api/types';
const ParamIPAdapterImage = () => {
const ipAdapterInfo = useAppSelector(
(state: RootState) => state.controlNet.ipAdapterInfo
);
const isIPAdapterEnabled = useAppSelector(
(state: RootState) => state.controlNet.isIPAdapterEnabled
);
const dispatch = useAppDispatch();
const { t } = useTranslation();
const { currentData: imageDTO } = useGetImageDTOQuery(
ipAdapterInfo.adapterImage?.image_name ?? skipToken
);
const draggableData = useMemo<TypesafeDraggableData | undefined>(() => {
if (imageDTO) {
return {
id: 'ip-adapter-image',
payloadType: 'IMAGE_DTO',
payload: { imageDTO },
};
}
}, [imageDTO]);
const droppableData = useMemo<TypesafeDroppableData | undefined>(
() => ({
id: 'ip-adapter-image',
actionType: 'SET_IP_ADAPTER_IMAGE',
}),
[]
);
const postUploadAction = useMemo<PostUploadAction>(
() => ({
type: 'SET_IP_ADAPTER_IMAGE',
}),
[]
);
return (
<Flex
sx={{
position: 'relative',
w: 'full',
alignItems: 'center',
justifyContent: 'center',
}}
>
<IAIDndImage
imageDTO={imageDTO}
droppableData={droppableData}
draggableData={draggableData}
postUploadAction={postUploadAction}
isUploadDisabled={!isIPAdapterEnabled}
isDropDisabled={!isIPAdapterEnabled}
dropLabel={t('toast.setIPAdapterImage')}
noContentFallback={
<IAINoContentFallback
label={t('controlnet.ipAdapterImageFallback')}
/>
}
/>
<IAIDndImageIcon
onClick={() => dispatch(ipAdapterImageChanged(null))}
icon={ipAdapterInfo.adapterImage ? <FaUndo /> : undefined}
tooltip={t('controlnet.resetIPAdapterImage')}
/>
</Flex>
);
};
export default memo(ParamIPAdapterImage);

View File

@ -0,0 +1,97 @@
import { SelectItem } from '@mantine/core';
import { RootState } from 'app/store/store';
import { useAppDispatch, useAppSelector } from 'app/store/storeHooks';
import IAIMantineSelect from 'common/components/IAIMantineSelect';
import { ipAdapterModelChanged } from 'features/controlNet/store/controlNetSlice';
import { MODEL_TYPE_MAP } from 'features/parameters/types/constants';
import { modelIdToIPAdapterModelParam } from 'features/parameters/util/modelIdToIPAdapterModelParams';
import { forEach } from 'lodash-es';
import { memo, useCallback, useMemo } from 'react';
import { useTranslation } from 'react-i18next';
import { useGetIPAdapterModelsQuery } from 'services/api/endpoints/models';
const ParamIPAdapterModelSelect = () => {
const ipAdapterModel = useAppSelector(
(state: RootState) => state.controlNet.ipAdapterInfo.model
);
const model = useAppSelector((state: RootState) => state.generation.model);
const dispatch = useAppDispatch();
const { t } = useTranslation();
const { data: ipAdapterModels } = useGetIPAdapterModelsQuery();
// grab the full model entity from the RTK Query cache
const selectedModel = useMemo(
() =>
ipAdapterModels?.entities[
`${ipAdapterModel?.base_model}/ip_adapter/${ipAdapterModel?.model_name}`
] ?? null,
[
ipAdapterModel?.base_model,
ipAdapterModel?.model_name,
ipAdapterModels?.entities,
]
);
const data = useMemo(() => {
if (!ipAdapterModels) {
return [];
}
const data: SelectItem[] = [];
forEach(ipAdapterModels.entities, (ipAdapterModel, id) => {
if (!ipAdapterModel) {
return;
}
const disabled = model?.base_model !== ipAdapterModel.base_model;
data.push({
value: id,
label: ipAdapterModel.model_name,
group: MODEL_TYPE_MAP[ipAdapterModel.base_model],
disabled,
tooltip: disabled
? `Incompatible base model: ${ipAdapterModel.base_model}`
: undefined,
});
});
return data.sort((a, b) => (a.disabled && !b.disabled ? 1 : -1));
}, [ipAdapterModels, model?.base_model]);
const handleValueChanged = useCallback(
(v: string | null) => {
if (!v) {
return;
}
const newIPAdapterModel = modelIdToIPAdapterModelParam(v);
if (!newIPAdapterModel) {
return;
}
dispatch(ipAdapterModelChanged(newIPAdapterModel));
},
[dispatch]
);
return (
<IAIMantineSelect
label={t('controlnet.ipAdapterModel')}
className="nowheel nodrag"
tooltip={selectedModel?.description}
value={selectedModel?.id ?? null}
placeholder="Pick one"
error={!selectedModel}
data={data}
onChange={handleValueChanged}
sx={{ width: '100%' }}
/>
);
};
export default memo(ParamIPAdapterModelSelect);

View File

@ -0,0 +1,46 @@
import { RootState } from 'app/store/store';
import { useAppDispatch, useAppSelector } from 'app/store/storeHooks';
import IAISlider from 'common/components/IAISlider';
import { ipAdapterWeightChanged } from 'features/controlNet/store/controlNetSlice';
import { memo, useCallback } from 'react';
import { useTranslation } from 'react-i18next';
const ParamIPAdapterWeight = () => {
const isIpAdapterEnabled = useAppSelector(
(state: RootState) => state.controlNet.isIPAdapterEnabled
);
const ipAdapterWeight = useAppSelector(
(state: RootState) => state.controlNet.ipAdapterInfo.weight
);
const dispatch = useAppDispatch();
const { t } = useTranslation();
const handleWeightChanged = useCallback(
(weight: number) => {
dispatch(ipAdapterWeightChanged(weight));
},
[dispatch]
);
const handleWeightReset = useCallback(() => {
dispatch(ipAdapterWeightChanged(1));
}, [dispatch]);
return (
<IAISlider
isDisabled={!isIpAdapterEnabled}
label={t('controlnet.weight')}
value={ipAdapterWeight}
onChange={handleWeightChanged}
min={0}
max={2}
step={0.01}
withSliderMarks
sliderMarks={[0, 1, 2]}
withReset
handleReset={handleWeightReset}
/>
);
};
export default memo(ParamIPAdapterWeight);

View File

@ -11,11 +11,10 @@ import {
} from 'features/controlNet/store/controlNetSlice';
import { MODEL_TYPE_MAP } from 'features/parameters/types/constants';
import { modelIdToControlNetModelParam } from 'features/parameters/util/modelIdToControlNetModelParam';
import { selectIsBusy } from 'features/system/store/systemSelectors';
import { forEach } from 'lodash-es';
import { memo, useCallback, useMemo } from 'react';
import { useGetControlNetModelsQuery } from 'services/api/endpoints/models';
import { useTranslation } from 'react-i18next';
import { useGetControlNetModelsQuery } from 'services/api/endpoints/models';
type ParamControlNetModelProps = {
controlNet: ControlNetConfig;
@ -33,7 +32,6 @@ const selector = createSelector(
const ParamControlNetModel = (props: ParamControlNetModelProps) => {
const { controlNetId, model: controlNetModel, isEnabled } = props.controlNet;
const dispatch = useAppDispatch();
const isBusy = useAppSelector(selectIsBusy);
const { mainModel } = useAppSelector(selector);
const { t } = useTranslation();
@ -110,7 +108,7 @@ const ParamControlNetModel = (props: ParamControlNetModelProps) => {
placeholder={t('controlnet.selectModel')}
value={selectedModel?.id ?? null}
onChange={handleModelChanged}
disabled={isBusy || !isEnabled}
disabled={!isEnabled}
tooltip={selectedModel?.description}
/>
);

View File

@ -6,7 +6,6 @@ import IAIMantineSearchableSelect, {
IAISelectDataType,
} from 'common/components/IAIMantineSearchableSelect';
import { configSelector } from 'features/system/store/configSelectors';
import { selectIsBusy } from 'features/system/store/systemSelectors';
import { map } from 'lodash-es';
import { memo, useCallback } from 'react';
import { CONTROLNET_PROCESSORS } from '../../store/constants';
@ -56,7 +55,6 @@ const ParamControlNetProcessorSelect = (
) => {
const dispatch = useAppDispatch();
const { controlNetId, isEnabled, processorNode } = props.controlNet;
const isBusy = useAppSelector(selectIsBusy);
const controlNetProcessors = useAppSelector(selector);
const { t } = useTranslation();
@ -78,7 +76,7 @@ const ParamControlNetProcessorSelect = (
value={processorNode.type ?? 'canny_image_processor'}
data={controlNetProcessors}
onChange={handleProcessorTypeChanged}
disabled={isBusy || !isEnabled}
disabled={!isEnabled}
/>
);
};

View File

@ -1,12 +1,10 @@
import { useAppSelector } from 'app/store/storeHooks';
import IAISlider from 'common/components/IAISlider';
import { CONTROLNET_PROCESSORS } from 'features/controlNet/store/constants';
import { RequiredCannyImageProcessorInvocation } from 'features/controlNet/store/types';
import { selectIsBusy } from 'features/system/store/systemSelectors';
import { memo, useCallback } from 'react';
import { useTranslation } from 'react-i18next';
import { useProcessorNodeChanged } from '../hooks/useProcessorNodeChanged';
import ProcessorWrapper from './common/ProcessorWrapper';
import { useTranslation } from 'react-i18next';
const DEFAULTS = CONTROLNET_PROCESSORS.canny_image_processor
.default as RequiredCannyImageProcessorInvocation;
@ -20,7 +18,6 @@ type CannyProcessorProps = {
const CannyProcessor = (props: CannyProcessorProps) => {
const { controlNetId, processorNode, isEnabled } = props;
const { low_threshold, high_threshold } = processorNode;
const isBusy = useAppSelector(selectIsBusy);
const processorChanged = useProcessorNodeChanged();
const { t } = useTranslation();
@ -53,7 +50,7 @@ const CannyProcessor = (props: CannyProcessorProps) => {
return (
<ProcessorWrapper>
<IAISlider
isDisabled={isBusy || !isEnabled}
isDisabled={!isEnabled}
label={t('controlnet.lowThreshold')}
value={low_threshold}
onChange={handleLowThresholdChanged}
@ -65,7 +62,7 @@ const CannyProcessor = (props: CannyProcessorProps) => {
withSliderMarks
/>
<IAISlider
isDisabled={isBusy || !isEnabled}
isDisabled={!isEnabled}
label={t('controlnet.highThreshold')}
value={high_threshold}
onChange={handleHighThresholdChanged}

View File

@ -2,11 +2,9 @@ import IAISlider from 'common/components/IAISlider';
import { CONTROLNET_PROCESSORS } from 'features/controlNet/store/constants';
import { RequiredContentShuffleImageProcessorInvocation } from 'features/controlNet/store/types';
import { memo, useCallback } from 'react';
import { useTranslation } from 'react-i18next';
import { useProcessorNodeChanged } from '../hooks/useProcessorNodeChanged';
import ProcessorWrapper from './common/ProcessorWrapper';
import { useAppSelector } from 'app/store/storeHooks';
import { selectIsBusy } from 'features/system/store/systemSelectors';
import { useTranslation } from 'react-i18next';
const DEFAULTS = CONTROLNET_PROCESSORS.content_shuffle_image_processor
.default as RequiredContentShuffleImageProcessorInvocation;
@ -21,7 +19,6 @@ const ContentShuffleProcessor = (props: Props) => {
const { controlNetId, processorNode, isEnabled } = props;
const { image_resolution, detect_resolution, w, h, f } = processorNode;
const processorChanged = useProcessorNodeChanged();
const isBusy = useAppSelector(selectIsBusy);
const { t } = useTranslation();
const handleDetectResolutionChanged = useCallback(
@ -101,7 +98,7 @@ const ContentShuffleProcessor = (props: Props) => {
max={4096}
withInput
withSliderMarks
isDisabled={isBusy || !isEnabled}
isDisabled={!isEnabled}
/>
<IAISlider
label={t('controlnet.imageResolution')}
@ -113,7 +110,7 @@ const ContentShuffleProcessor = (props: Props) => {
max={4096}
withInput
withSliderMarks
isDisabled={isBusy || !isEnabled}
isDisabled={!isEnabled}
/>
<IAISlider
label={t('controlnet.w')}
@ -125,7 +122,7 @@ const ContentShuffleProcessor = (props: Props) => {
max={4096}
withInput
withSliderMarks
isDisabled={isBusy || !isEnabled}
isDisabled={!isEnabled}
/>
<IAISlider
label={t('controlnet.h')}
@ -137,7 +134,7 @@ const ContentShuffleProcessor = (props: Props) => {
max={4096}
withInput
withSliderMarks
isDisabled={isBusy || !isEnabled}
isDisabled={!isEnabled}
/>
<IAISlider
label={t('controlnet.f')}
@ -149,7 +146,7 @@ const ContentShuffleProcessor = (props: Props) => {
max={4096}
withInput
withSliderMarks
isDisabled={isBusy || !isEnabled}
isDisabled={!isEnabled}
/>
</ProcessorWrapper>
);

View File

@ -1,13 +1,11 @@
import { useAppSelector } from 'app/store/storeHooks';
import IAISlider from 'common/components/IAISlider';
import IAISwitch from 'common/components/IAISwitch';
import { CONTROLNET_PROCESSORS } from 'features/controlNet/store/constants';
import { RequiredHedImageProcessorInvocation } from 'features/controlNet/store/types';
import { selectIsBusy } from 'features/system/store/systemSelectors';
import { ChangeEvent, memo, useCallback } from 'react';
import { useTranslation } from 'react-i18next';
import { useProcessorNodeChanged } from '../hooks/useProcessorNodeChanged';
import ProcessorWrapper from './common/ProcessorWrapper';
import { useTranslation } from 'react-i18next';
const DEFAULTS = CONTROLNET_PROCESSORS.hed_image_processor
.default as RequiredHedImageProcessorInvocation;
@ -24,7 +22,6 @@ const HedPreprocessor = (props: HedProcessorProps) => {
processorNode: { detect_resolution, image_resolution, scribble },
isEnabled,
} = props;
const isBusy = useAppSelector(selectIsBusy);
const processorChanged = useProcessorNodeChanged();
const { t } = useTranslation();
@ -73,7 +70,7 @@ const HedPreprocessor = (props: HedProcessorProps) => {
max={4096}
withInput
withSliderMarks
isDisabled={isBusy || !isEnabled}
isDisabled={!isEnabled}
/>
<IAISlider
label={t('controlnet.imageResolution')}
@ -85,13 +82,13 @@ const HedPreprocessor = (props: HedProcessorProps) => {
max={4096}
withInput
withSliderMarks
isDisabled={isBusy || !isEnabled}
isDisabled={!isEnabled}
/>
<IAISwitch
label={t('controlnet.scribble')}
isChecked={scribble}
onChange={handleScribbleChanged}
isDisabled={isBusy || !isEnabled}
isDisabled={!isEnabled}
/>
</ProcessorWrapper>
);

View File

@ -1,12 +1,10 @@
import { useAppSelector } from 'app/store/storeHooks';
import IAISlider from 'common/components/IAISlider';
import { CONTROLNET_PROCESSORS } from 'features/controlNet/store/constants';
import { RequiredLineartAnimeImageProcessorInvocation } from 'features/controlNet/store/types';
import { selectIsBusy } from 'features/system/store/systemSelectors';
import { memo, useCallback } from 'react';
import { useTranslation } from 'react-i18next';
import { useProcessorNodeChanged } from '../hooks/useProcessorNodeChanged';
import ProcessorWrapper from './common/ProcessorWrapper';
import { useTranslation } from 'react-i18next';
const DEFAULTS = CONTROLNET_PROCESSORS.lineart_anime_image_processor
.default as RequiredLineartAnimeImageProcessorInvocation;
@ -21,7 +19,6 @@ const LineartAnimeProcessor = (props: Props) => {
const { controlNetId, processorNode, isEnabled } = props;
const { image_resolution, detect_resolution } = processorNode;
const processorChanged = useProcessorNodeChanged();
const isBusy = useAppSelector(selectIsBusy);
const { t } = useTranslation();
const handleDetectResolutionChanged = useCallback(
@ -62,7 +59,7 @@ const LineartAnimeProcessor = (props: Props) => {
max={4096}
withInput
withSliderMarks
isDisabled={isBusy || !isEnabled}
isDisabled={!isEnabled}
/>
<IAISlider
label={t('controlnet.imageResolution')}
@ -74,7 +71,7 @@ const LineartAnimeProcessor = (props: Props) => {
max={4096}
withInput
withSliderMarks
isDisabled={isBusy || !isEnabled}
isDisabled={!isEnabled}
/>
</ProcessorWrapper>
);

View File

@ -1,13 +1,11 @@
import { useAppSelector } from 'app/store/storeHooks';
import IAISlider from 'common/components/IAISlider';
import IAISwitch from 'common/components/IAISwitch';
import { CONTROLNET_PROCESSORS } from 'features/controlNet/store/constants';
import { RequiredLineartImageProcessorInvocation } from 'features/controlNet/store/types';
import { selectIsBusy } from 'features/system/store/systemSelectors';
import { ChangeEvent, memo, useCallback } from 'react';
import { useTranslation } from 'react-i18next';
import { useProcessorNodeChanged } from '../hooks/useProcessorNodeChanged';
import ProcessorWrapper from './common/ProcessorWrapper';
import { useTranslation } from 'react-i18next';
const DEFAULTS = CONTROLNET_PROCESSORS.lineart_image_processor
.default as RequiredLineartImageProcessorInvocation;
@ -22,7 +20,6 @@ const LineartProcessor = (props: LineartProcessorProps) => {
const { controlNetId, processorNode, isEnabled } = props;
const { image_resolution, detect_resolution, coarse } = processorNode;
const processorChanged = useProcessorNodeChanged();
const isBusy = useAppSelector(selectIsBusy);
const { t } = useTranslation();
const handleDetectResolutionChanged = useCallback(
@ -70,7 +67,7 @@ const LineartProcessor = (props: LineartProcessorProps) => {
max={4096}
withInput
withSliderMarks
isDisabled={isBusy || !isEnabled}
isDisabled={!isEnabled}
/>
<IAISlider
label={t('controlnet.imageResolution')}
@ -82,13 +79,13 @@ const LineartProcessor = (props: LineartProcessorProps) => {
max={4096}
withInput
withSliderMarks
isDisabled={isBusy || !isEnabled}
isDisabled={!isEnabled}
/>
<IAISwitch
label={t('controlnet.coarse')}
isChecked={coarse}
onChange={handleCoarseChanged}
isDisabled={isBusy || !isEnabled}
isDisabled={!isEnabled}
/>
</ProcessorWrapper>
);

View File

@ -1,12 +1,10 @@
import { useAppSelector } from 'app/store/storeHooks';
import IAISlider from 'common/components/IAISlider';
import { CONTROLNET_PROCESSORS } from 'features/controlNet/store/constants';
import { RequiredMediapipeFaceProcessorInvocation } from 'features/controlNet/store/types';
import { selectIsBusy } from 'features/system/store/systemSelectors';
import { memo, useCallback } from 'react';
import { useTranslation } from 'react-i18next';
import { useProcessorNodeChanged } from '../hooks/useProcessorNodeChanged';
import ProcessorWrapper from './common/ProcessorWrapper';
import { useTranslation } from 'react-i18next';
const DEFAULTS = CONTROLNET_PROCESSORS.mediapipe_face_processor
.default as RequiredMediapipeFaceProcessorInvocation;
@ -21,7 +19,6 @@ const MediapipeFaceProcessor = (props: Props) => {
const { controlNetId, processorNode, isEnabled } = props;
const { max_faces, min_confidence } = processorNode;
const processorChanged = useProcessorNodeChanged();
const isBusy = useAppSelector(selectIsBusy);
const { t } = useTranslation();
const handleMaxFacesChanged = useCallback(
@ -58,7 +55,7 @@ const MediapipeFaceProcessor = (props: Props) => {
max={20}
withInput
withSliderMarks
isDisabled={isBusy || !isEnabled}
isDisabled={!isEnabled}
/>
<IAISlider
label={t('controlnet.minConfidence')}
@ -71,7 +68,7 @@ const MediapipeFaceProcessor = (props: Props) => {
step={0.01}
withInput
withSliderMarks
isDisabled={isBusy || !isEnabled}
isDisabled={!isEnabled}
/>
</ProcessorWrapper>
);

View File

@ -1,12 +1,10 @@
import { useAppSelector } from 'app/store/storeHooks';
import IAISlider from 'common/components/IAISlider';
import { CONTROLNET_PROCESSORS } from 'features/controlNet/store/constants';
import { RequiredMidasDepthImageProcessorInvocation } from 'features/controlNet/store/types';
import { selectIsBusy } from 'features/system/store/systemSelectors';
import { memo, useCallback } from 'react';
import { useTranslation } from 'react-i18next';
import { useProcessorNodeChanged } from '../hooks/useProcessorNodeChanged';
import ProcessorWrapper from './common/ProcessorWrapper';
import { useTranslation } from 'react-i18next';
const DEFAULTS = CONTROLNET_PROCESSORS.midas_depth_image_processor
.default as RequiredMidasDepthImageProcessorInvocation;
@ -21,7 +19,6 @@ const MidasDepthProcessor = (props: Props) => {
const { controlNetId, processorNode, isEnabled } = props;
const { a_mult, bg_th } = processorNode;
const processorChanged = useProcessorNodeChanged();
const isBusy = useAppSelector(selectIsBusy);
const { t } = useTranslation();
const handleAMultChanged = useCallback(
@ -59,7 +56,7 @@ const MidasDepthProcessor = (props: Props) => {
step={0.01}
withInput
withSliderMarks
isDisabled={isBusy || !isEnabled}
isDisabled={!isEnabled}
/>
<IAISlider
label={t('controlnet.bgth')}
@ -72,7 +69,7 @@ const MidasDepthProcessor = (props: Props) => {
step={0.01}
withInput
withSliderMarks
isDisabled={isBusy || !isEnabled}
isDisabled={!isEnabled}
/>
</ProcessorWrapper>
);

View File

@ -1,12 +1,10 @@
import { useAppSelector } from 'app/store/storeHooks';
import IAISlider from 'common/components/IAISlider';
import { CONTROLNET_PROCESSORS } from 'features/controlNet/store/constants';
import { RequiredMlsdImageProcessorInvocation } from 'features/controlNet/store/types';
import { selectIsBusy } from 'features/system/store/systemSelectors';
import { memo, useCallback } from 'react';
import { useTranslation } from 'react-i18next';
import { useProcessorNodeChanged } from '../hooks/useProcessorNodeChanged';
import ProcessorWrapper from './common/ProcessorWrapper';
import { useTranslation } from 'react-i18next';
const DEFAULTS = CONTROLNET_PROCESSORS.mlsd_image_processor
.default as RequiredMlsdImageProcessorInvocation;
@ -21,7 +19,6 @@ const MlsdImageProcessor = (props: Props) => {
const { controlNetId, processorNode, isEnabled } = props;
const { image_resolution, detect_resolution, thr_d, thr_v } = processorNode;
const processorChanged = useProcessorNodeChanged();
const isBusy = useAppSelector(selectIsBusy);
const { t } = useTranslation();
const handleDetectResolutionChanged = useCallback(
@ -84,7 +81,7 @@ const MlsdImageProcessor = (props: Props) => {
max={4096}
withInput
withSliderMarks
isDisabled={isBusy || !isEnabled}
isDisabled={!isEnabled}
/>
<IAISlider
label={t('controlnet.imageResolution')}
@ -96,7 +93,7 @@ const MlsdImageProcessor = (props: Props) => {
max={4096}
withInput
withSliderMarks
isDisabled={isBusy || !isEnabled}
isDisabled={!isEnabled}
/>
<IAISlider
label={t('controlnet.w')}
@ -109,7 +106,7 @@ const MlsdImageProcessor = (props: Props) => {
step={0.01}
withInput
withSliderMarks
isDisabled={isBusy || !isEnabled}
isDisabled={!isEnabled}
/>
<IAISlider
label={t('controlnet.h')}
@ -122,7 +119,7 @@ const MlsdImageProcessor = (props: Props) => {
step={0.01}
withInput
withSliderMarks
isDisabled={isBusy || !isEnabled}
isDisabled={!isEnabled}
/>
</ProcessorWrapper>
);

View File

@ -1,12 +1,10 @@
import { useAppSelector } from 'app/store/storeHooks';
import IAISlider from 'common/components/IAISlider';
import { CONTROLNET_PROCESSORS } from 'features/controlNet/store/constants';
import { RequiredNormalbaeImageProcessorInvocation } from 'features/controlNet/store/types';
import { selectIsBusy } from 'features/system/store/systemSelectors';
import { memo, useCallback } from 'react';
import { useTranslation } from 'react-i18next';
import { useProcessorNodeChanged } from '../hooks/useProcessorNodeChanged';
import ProcessorWrapper from './common/ProcessorWrapper';
import { useTranslation } from 'react-i18next';
const DEFAULTS = CONTROLNET_PROCESSORS.normalbae_image_processor
.default as RequiredNormalbaeImageProcessorInvocation;
@ -21,7 +19,6 @@ const NormalBaeProcessor = (props: Props) => {
const { controlNetId, processorNode, isEnabled } = props;
const { image_resolution, detect_resolution } = processorNode;
const processorChanged = useProcessorNodeChanged();
const isBusy = useAppSelector(selectIsBusy);
const { t } = useTranslation();
const handleDetectResolutionChanged = useCallback(
@ -62,7 +59,7 @@ const NormalBaeProcessor = (props: Props) => {
max={4096}
withInput
withSliderMarks
isDisabled={isBusy || !isEnabled}
isDisabled={!isEnabled}
/>
<IAISlider
label={t('controlnet.imageResolution')}
@ -74,7 +71,7 @@ const NormalBaeProcessor = (props: Props) => {
max={4096}
withInput
withSliderMarks
isDisabled={isBusy || !isEnabled}
isDisabled={!isEnabled}
/>
</ProcessorWrapper>
);

View File

@ -1,13 +1,11 @@
import { useAppSelector } from 'app/store/storeHooks';
import IAISlider from 'common/components/IAISlider';
import IAISwitch from 'common/components/IAISwitch';
import { CONTROLNET_PROCESSORS } from 'features/controlNet/store/constants';
import { RequiredOpenposeImageProcessorInvocation } from 'features/controlNet/store/types';
import { selectIsBusy } from 'features/system/store/systemSelectors';
import { ChangeEvent, memo, useCallback } from 'react';
import { useTranslation } from 'react-i18next';
import { useProcessorNodeChanged } from '../hooks/useProcessorNodeChanged';
import ProcessorWrapper from './common/ProcessorWrapper';
import { useTranslation } from 'react-i18next';
const DEFAULTS = CONTROLNET_PROCESSORS.openpose_image_processor
.default as RequiredOpenposeImageProcessorInvocation;
@ -22,7 +20,6 @@ const OpenposeProcessor = (props: Props) => {
const { controlNetId, processorNode, isEnabled } = props;
const { image_resolution, detect_resolution, hand_and_face } = processorNode;
const processorChanged = useProcessorNodeChanged();
const isBusy = useAppSelector(selectIsBusy);
const { t } = useTranslation();
const handleDetectResolutionChanged = useCallback(
@ -70,7 +67,7 @@ const OpenposeProcessor = (props: Props) => {
max={4096}
withInput
withSliderMarks
isDisabled={isBusy || !isEnabled}
isDisabled={!isEnabled}
/>
<IAISlider
label={t('controlnet.imageResolution')}
@ -82,13 +79,13 @@ const OpenposeProcessor = (props: Props) => {
max={4096}
withInput
withSliderMarks
isDisabled={isBusy || !isEnabled}
isDisabled={!isEnabled}
/>
<IAISwitch
label={t('controlnet.handAndFace')}
isChecked={hand_and_face}
onChange={handleHandAndFaceChanged}
isDisabled={isBusy || !isEnabled}
isDisabled={!isEnabled}
/>
</ProcessorWrapper>
);

View File

@ -1,13 +1,11 @@
import { useAppSelector } from 'app/store/storeHooks';
import IAISlider from 'common/components/IAISlider';
import IAISwitch from 'common/components/IAISwitch';
import { CONTROLNET_PROCESSORS } from 'features/controlNet/store/constants';
import { RequiredPidiImageProcessorInvocation } from 'features/controlNet/store/types';
import { selectIsBusy } from 'features/system/store/systemSelectors';
import { ChangeEvent, memo, useCallback } from 'react';
import { useTranslation } from 'react-i18next';
import { useProcessorNodeChanged } from '../hooks/useProcessorNodeChanged';
import ProcessorWrapper from './common/ProcessorWrapper';
import { useTranslation } from 'react-i18next';
const DEFAULTS = CONTROLNET_PROCESSORS.pidi_image_processor
.default as RequiredPidiImageProcessorInvocation;
@ -22,7 +20,6 @@ const PidiProcessor = (props: Props) => {
const { controlNetId, processorNode, isEnabled } = props;
const { image_resolution, detect_resolution, scribble, safe } = processorNode;
const processorChanged = useProcessorNodeChanged();
const isBusy = useAppSelector(selectIsBusy);
const { t } = useTranslation();
const handleDetectResolutionChanged = useCallback(
@ -77,7 +74,7 @@ const PidiProcessor = (props: Props) => {
max={4096}
withInput
withSliderMarks
isDisabled={isBusy || !isEnabled}
isDisabled={!isEnabled}
/>
<IAISlider
label={t('controlnet.imageResolution')}
@ -89,7 +86,7 @@ const PidiProcessor = (props: Props) => {
max={4096}
withInput
withSliderMarks
isDisabled={isBusy || !isEnabled}
isDisabled={!isEnabled}
/>
<IAISwitch
label={t('controlnet.scribble')}
@ -100,7 +97,7 @@ const PidiProcessor = (props: Props) => {
label={t('controlnet.safe')}
isChecked={safe}
onChange={handleSafeChanged}
isDisabled={isBusy || !isEnabled}
isDisabled={!isEnabled}
/>
</ProcessorWrapper>
);

View File

@ -1,9 +1,13 @@
import { PayloadAction, createSlice } from '@reduxjs/toolkit';
import { ControlNetModelParam } from 'features/parameters/types/parameterSchemas';
import {
ControlNetModelParam,
IPAdapterModelParam,
} from 'features/parameters/types/parameterSchemas';
import { cloneDeep, forEach } from 'lodash-es';
import { imagesApi } from 'services/api/endpoints/images';
import { components } from 'services/api/schema';
import { isAnySessionRejected } from 'services/api/thunks/session';
import { ImageDTO } from 'services/api/types';
import { appSocketInvocationError } from 'services/events/actions';
import { controlNetImageProcessed } from './actions';
import {
@ -56,16 +60,36 @@ export type ControlNetConfig = {
shouldAutoConfig: boolean;
};
export type IPAdapterConfig = {
adapterImage: ImageDTO | null;
model: IPAdapterModelParam | null;
weight: number;
beginStepPct: number;
endStepPct: number;
};
export type ControlNetState = {
controlNets: Record<string, ControlNetConfig>;
isEnabled: boolean;
pendingControlImages: string[];
isIPAdapterEnabled: boolean;
ipAdapterInfo: IPAdapterConfig;
};
export const initialIPAdapterState: IPAdapterConfig = {
adapterImage: null,
model: null,
weight: 1,
beginStepPct: 0,
endStepPct: 1,
};
export const initialControlNetState: ControlNetState = {
controlNets: {},
isEnabled: false,
pendingControlImages: [],
isIPAdapterEnabled: false,
ipAdapterInfo: { ...initialIPAdapterState },
};
export const controlNetSlice = createSlice({
@ -353,6 +377,31 @@ export const controlNetSlice = createSlice({
controlNetReset: () => {
return { ...initialControlNetState };
},
isIPAdapterEnableToggled: (state) => {
state.isIPAdapterEnabled = !state.isIPAdapterEnabled;
},
ipAdapterImageChanged: (state, action: PayloadAction<ImageDTO | null>) => {
state.ipAdapterInfo.adapterImage = action.payload;
},
ipAdapterWeightChanged: (state, action: PayloadAction<number>) => {
state.ipAdapterInfo.weight = action.payload;
},
ipAdapterModelChanged: (
state,
action: PayloadAction<IPAdapterModelParam | null>
) => {
state.ipAdapterInfo.model = action.payload;
},
ipAdapterBeginStepPctChanged: (state, action: PayloadAction<number>) => {
state.ipAdapterInfo.beginStepPct = action.payload;
},
ipAdapterEndStepPctChanged: (state, action: PayloadAction<number>) => {
state.ipAdapterInfo.endStepPct = action.payload;
},
ipAdapterStateReset: (state) => {
state.isIPAdapterEnabled = false;
state.ipAdapterInfo = { ...initialIPAdapterState };
},
},
extraReducers: (builder) => {
builder.addCase(controlNetImageProcessed, (state, action) => {
@ -412,6 +461,13 @@ export const {
controlNetProcessorTypeChanged,
controlNetReset,
controlNetAutoConfigToggled,
isIPAdapterEnableToggled,
ipAdapterImageChanged,
ipAdapterWeightChanged,
ipAdapterModelChanged,
ipAdapterBeginStepPctChanged,
ipAdapterEndStepPctChanged,
ipAdapterStateReset,
} = controlNetSlice.actions;
export default controlNetSlice.reducer;

View File

@ -1,20 +1,9 @@
import { IconButtonProps } from '@chakra-ui/react';
import { createSelector } from '@reduxjs/toolkit';
import { stateSelector } from 'app/store/store';
import { useAppSelector } from 'app/store/storeHooks';
import IAIIconButton from 'common/components/IAIIconButton';
import { useTranslation } from 'react-i18next';
import { FaTrash } from 'react-icons/fa';
const deleteImageButtonsSelector = createSelector(
[stateSelector],
({ system }) => {
const { isProcessing, isConnected } = system;
return isConnected && !isProcessing;
}
);
type DeleteImageButtonProps = Omit<IconButtonProps, 'aria-label'> & {
onClick: () => void;
};
@ -22,7 +11,7 @@ type DeleteImageButtonProps = Omit<IconButtonProps, 'aria-label'> & {
export const DeleteImageButton = (props: DeleteImageButtonProps) => {
const { onClick, isDisabled } = props;
const { t } = useTranslation();
const canDeleteImage = useAppSelector(deleteImageButtonsSelector);
const isConnected = useAppSelector((state) => state.system.isConnected);
return (
<IAIIconButton
@ -30,7 +19,7 @@ export const DeleteImageButton = (props: DeleteImageButtonProps) => {
icon={<FaTrash />}
tooltip={`${t('gallery.deleteImage')} (Del)`}
aria-label={`${t('gallery.deleteImage')} (Del)`}
isDisabled={isDisabled || !canDeleteImage}
isDisabled={isDisabled || !isConnected}
colorScheme="error"
/>
);

View File

@ -10,20 +10,20 @@ import {
Text,
} from '@chakra-ui/react';
import { createSelector } from '@reduxjs/toolkit';
import { stateSelector } from 'app/store/store';
import { useAppDispatch, useAppSelector } from 'app/store/storeHooks';
import { defaultSelectorOptions } from 'app/store/util/defaultMemoizeOptions';
import IAIButton from 'common/components/IAIButton';
import IAISwitch from 'common/components/IAISwitch';
import { setShouldConfirmOnDelete } from 'features/system/store/systemSlice';
import { stateSelector } from 'app/store/store';
import { some } from 'lodash-es';
import { ChangeEvent, memo, useCallback, useRef } from 'react';
import { useTranslation } from 'react-i18next';
import { imageDeletionConfirmed } from '../store/actions';
import { getImageUsage, selectImageUsage } from '../store/selectors';
import { imageDeletionCanceled, isModalOpenChanged } from '../store/slice';
import ImageUsageMessage from './ImageUsageMessage';
import { ImageUsage } from '../store/types';
import ImageUsageMessage from './ImageUsageMessage';
const selector = createSelector(
[stateSelector, selectImageUsage],
@ -42,6 +42,7 @@ const selector = createSelector(
isCanvasImage: some(allImageUsage, (i) => i.isCanvasImage),
isNodesImage: some(allImageUsage, (i) => i.isNodesImage),
isControlNetImage: some(allImageUsage, (i) => i.isControlNetImage),
isIPAdapterImage: some(allImageUsage, (i) => i.isIPAdapterImage),
};
return {

View File

@ -1,8 +1,8 @@
import { ListItem, Text, UnorderedList } from '@chakra-ui/react';
import { some } from 'lodash-es';
import { memo } from 'react';
import { ImageUsage } from '../store/types';
import { useTranslation } from 'react-i18next';
import { ImageUsage } from '../store/types';
type Props = {
imageUsage?: ImageUsage;
@ -38,6 +38,9 @@ const ImageUsageMessage = (props: Props) => {
{imageUsage.isControlNetImage && (
<ListItem>{t('common.controlNet')}</ListItem>
)}
{imageUsage.isIPAdapterImage && (
<ListItem>{t('common.ipAdapter')}</ListItem>
)}
{imageUsage.isNodesImage && (
<ListItem>{t('common.nodeEditor')}</ListItem>
)}

View File

@ -1,9 +1,9 @@
import { createSelector } from '@reduxjs/toolkit';
import { RootState } from 'app/store/store';
import { defaultSelectorOptions } from 'app/store/util/defaultMemoizeOptions';
import { isInvocationNode } from 'features/nodes/types/types';
import { some } from 'lodash-es';
import { ImageUsage } from './types';
import { isInvocationNode } from 'features/nodes/types/types';
export const getImageUsage = (state: RootState, image_name: string) => {
const { generation, canvas, nodes, controlNet } = state;
@ -27,11 +27,15 @@ export const getImageUsage = (state: RootState, image_name: string) => {
c.controlImage === image_name || c.processedControlImage === image_name
);
const isIPAdapterImage =
controlNet.ipAdapterInfo.adapterImage?.image_name === image_name;
const imageUsage: ImageUsage = {
isInitialImage,
isCanvasImage,
isNodesImage,
isControlNetImage,
isIPAdapterImage,
};
return imageUsage;

View File

@ -10,4 +10,5 @@ export type ImageUsage = {
isCanvasImage: boolean;
isNodesImage: boolean;
isControlNetImage: boolean;
isIPAdapterImage: boolean;
};

View File

@ -35,6 +35,10 @@ export type ControlNetDropData = BaseDropData & {
};
};
export type IPAdapterImageDropData = BaseDropData & {
actionType: 'SET_IP_ADAPTER_IMAGE';
};
export type CanvasInitialImageDropData = BaseDropData & {
actionType: 'SET_CANVAS_INITIAL_IMAGE';
};
@ -73,6 +77,7 @@ export type TypesafeDroppableData =
| CurrentImageDropData
| InitialImageDropData
| ControlNetDropData
| IPAdapterImageDropData
| CanvasInitialImageDropData
| NodesImageDropData
| AddToBatchDropData

View File

@ -24,6 +24,8 @@ export const isValidDrop = (
return payloadType === 'IMAGE_DTO';
case 'SET_CONTROLNET_IMAGE':
return payloadType === 'IMAGE_DTO';
case 'SET_IP_ADAPTER_IMAGE':
return payloadType === 'IMAGE_DTO';
case 'SET_CANVAS_INITIAL_IMAGE':
return payloadType === 'IMAGE_DTO';
case 'SET_NODES_IMAGE':

View File

@ -2,28 +2,33 @@ import { Flex } from '@chakra-ui/react';
import { createSelector } from '@reduxjs/toolkit';
import { stateSelector } from 'app/store/store';
import { useAppSelector } from 'app/store/storeHooks';
import { defaultSelectorOptions } from 'app/store/util/defaultMemoizeOptions';
import IAICollapse from 'common/components/IAICollapse';
import { memo } from 'react';
import { useFeatureStatus } from '../../system/hooks/useFeatureStatus';
import ParamDynamicPromptsCombinatorial from './ParamDynamicPromptsCombinatorial';
import ParamDynamicPromptsToggle from './ParamDynamicPromptsEnabled';
import ParamDynamicPromptsMaxPrompts from './ParamDynamicPromptsMaxPrompts';
import { memo, useMemo } from 'react';
import { useTranslation } from 'react-i18next';
const selector = createSelector(
stateSelector,
(state) => {
const { isEnabled } = state.dynamicPrompts;
return { activeLabel: isEnabled ? 'Enabled' : undefined };
},
defaultSelectorOptions
);
import { useFeatureStatus } from '../../system/hooks/useFeatureStatus';
import ParamDynamicPromptsMaxPrompts from './ParamDynamicPromptsMaxPrompts';
import ParamDynamicPromptsPreview from './ParamDynamicPromptsPreview';
import ParamDynamicPromptsSeedBehaviour from './ParamDynamicPromptsSeedBehaviour';
const ParamDynamicPromptsCollapse = () => {
const { activeLabel } = useAppSelector(selector);
const { t } = useTranslation();
const selectActiveLabel = useMemo(
() =>
createSelector(stateSelector, ({ dynamicPrompts }) => {
const count = dynamicPrompts.prompts.length;
if (count === 1) {
return t('dynamicPrompts.promptsWithCount_one', {
count,
});
} else {
return t('dynamicPrompts.promptsWithCount_other', {
count,
});
}
}),
[t]
);
const activeLabel = useAppSelector(selectActiveLabel);
const isDynamicPromptingEnabled =
useFeatureStatus('dynamicPrompting').isFeatureEnabled;
@ -33,10 +38,13 @@ const ParamDynamicPromptsCollapse = () => {
}
return (
<IAICollapse label={t('prompt.dynamicPrompts')} activeLabel={activeLabel}>
<IAICollapse
label={t('dynamicPrompts.dynamicPrompts')}
activeLabel={activeLabel}
>
<Flex sx={{ gap: 2, flexDir: 'column' }}>
<ParamDynamicPromptsToggle />
<ParamDynamicPromptsCombinatorial />
<ParamDynamicPromptsSeedBehaviour />
<ParamDynamicPromptsPreview />
<ParamDynamicPromptsMaxPrompts />
</Flex>
</IAICollapse>

View File

@ -11,15 +11,15 @@ import IAIInformationalPopover from 'common/components/IAIInformationalPopover';
const selector = createSelector(
stateSelector,
(state) => {
const { combinatorial, isEnabled } = state.dynamicPrompts;
const { combinatorial } = state.dynamicPrompts;
return { combinatorial, isDisabled: !isEnabled };
return { combinatorial };
},
defaultSelectorOptions
);
const ParamDynamicPromptsCombinatorial = () => {
const { combinatorial, isDisabled } = useAppSelector(selector);
const { combinatorial } = useAppSelector(selector);
const dispatch = useAppDispatch();
const { t } = useTranslation();
@ -30,8 +30,7 @@ const ParamDynamicPromptsCombinatorial = () => {
return (
<IAIInformationalPopover details="dynamicPromptsCombinatorial">
<IAISwitch
isDisabled={isDisabled}
label={t('prompt.combinatorial')}
label={t('dynamicPrompts.combinatorial')}
isChecked={combinatorial}
onChange={handleChange}
/>

View File

@ -13,7 +13,7 @@ import { useTranslation } from 'react-i18next';
const selector = createSelector(
stateSelector,
(state) => {
const { maxPrompts, combinatorial, isEnabled } = state.dynamicPrompts;
const { maxPrompts, combinatorial } = state.dynamicPrompts;
const { min, sliderMax, inputMax } =
state.config.sd.dynamicPrompts.maxPrompts;
@ -22,7 +22,7 @@ const selector = createSelector(
min,
sliderMax,
inputMax,
isDisabled: !isEnabled || !combinatorial,
isDisabled: !combinatorial,
};
},
defaultSelectorOptions
@ -47,7 +47,7 @@ const ParamDynamicPromptsMaxPrompts = () => {
return (
<IAISlider
label={t('prompt.maxPrompts')}
label={t('dynamicPrompts.maxPrompts')}
isDisabled={isDisabled}
min={min}
max={sliderMax}

View File

@ -0,0 +1,100 @@
import {
ChakraProps,
Flex,
FormControl,
FormLabel,
ListItem,
OrderedList,
Spinner,
Text,
} from '@chakra-ui/react';
import { createSelector } from '@reduxjs/toolkit';
import { stateSelector } from 'app/store/store';
import { useAppSelector } from 'app/store/storeHooks';
import { defaultSelectorOptions } from 'app/store/util/defaultMemoizeOptions';
import { IAINoContentFallback } from 'common/components/IAIImageFallback';
import ScrollableContent from 'features/nodes/components/sidePanel/ScrollableContent';
import { memo } from 'react';
import { FaCircleExclamation } from 'react-icons/fa6';
const selector = createSelector(
stateSelector,
(state) => {
const { isLoading, isError, prompts, parsingError } = state.dynamicPrompts;
return {
prompts,
parsingError,
isError,
isLoading,
};
},
defaultSelectorOptions
);
const listItemStyles: ChakraProps['sx'] = {
'&::marker': { color: 'base.500', _dark: { color: 'base.500' } },
};
const ParamDynamicPromptsPreview = () => {
const { prompts, parsingError, isLoading, isError } =
useAppSelector(selector);
if (isError) {
return (
<Flex
w="full"
h="full"
layerStyle="second"
alignItems="center"
justifyContent="center"
p={8}
>
<IAINoContentFallback
icon={FaCircleExclamation}
label="Problem generating prompts"
/>
</Flex>
);
}
return (
<FormControl isInvalid={Boolean(parsingError)}>
<FormLabel whiteSpace="nowrap" overflow="hidden" textOverflow="ellipsis">
Prompts Preview ({prompts.length}){parsingError && ` - ${parsingError}`}
</FormLabel>
<Flex h={64} pos="relative" layerStyle="third" borderRadius="base" p={2}>
<ScrollableContent>
<OrderedList stylePosition="inside" ms={0}>
{prompts.map((prompt, i) => (
<ListItem
fontSize="sm"
key={`${prompt}.${i}`}
sx={listItemStyles}
>
<Text as="span">{prompt}</Text>
</ListItem>
))}
</OrderedList>
</ScrollableContent>
{isLoading && (
<Flex
pos="absolute"
w="full"
h="full"
top={0}
insetInlineStart={0}
layerStyle="second"
opacity={0.7}
alignItems="center"
justifyContent="center"
>
<Spinner />
</Flex>
)}
</Flex>
</FormControl>
);
};
export default memo(ParamDynamicPromptsPreview);

View File

@ -0,0 +1,60 @@
import { useAppDispatch, useAppSelector } from 'app/store/storeHooks';
import IAIMantineSelect from 'common/components/IAIMantineSelect';
import { memo, useCallback, useMemo } from 'react';
import { useTranslation } from 'react-i18next';
import {
SeedBehaviour,
seedBehaviourChanged,
} from '../store/dynamicPromptsSlice';
import IAIMantineSelectItemWithDescription from 'common/components/IAIMantineSelectItemWithDescription';
type Item = {
label: string;
value: SeedBehaviour;
description: string;
};
const ParamDynamicPromptsSeedBehaviour = () => {
const dispatch = useAppDispatch();
const { t } = useTranslation();
const seedBehaviour = useAppSelector(
(state) => state.dynamicPrompts.seedBehaviour
);
const data = useMemo<Item[]>(() => {
return [
{
value: 'PER_ITERATION',
label: t('dynamicPrompts.seedBehaviour.perIterationLabel'),
description: t('dynamicPrompts.seedBehaviour.perIterationDesc'),
},
{
value: 'PER_PROMPT',
label: t('dynamicPrompts.seedBehaviour.perPromptLabel'),
description: t('dynamicPrompts.seedBehaviour.perPromptDesc'),
},
];
}, [t]);
const handleChange = useCallback(
(v: string | null) => {
if (!v) {
return;
}
dispatch(seedBehaviourChanged(v as SeedBehaviour));
},
[dispatch]
);
return (
<IAIMantineSelect
label={t('dynamicPrompts.seedBehaviour.label')}
value={seedBehaviour}
data={data}
itemComponent={IAIMantineSelectItemWithDescription}
onChange={handleChange}
/>
);
};
export default memo(ParamDynamicPromptsSeedBehaviour);

View File

@ -0,0 +1,4 @@
import { initialDynamicPromptsState } from './dynamicPromptsSlice';
export const dynamicPromptsPersistDenylist: (keyof typeof initialDynamicPromptsState)[] =
['prompts'];

View File

@ -1,15 +1,24 @@
import { PayloadAction, createSlice } from '@reduxjs/toolkit';
export type SeedBehaviour = 'PER_ITERATION' | 'PER_PROMPT';
export interface DynamicPromptsState {
isEnabled: boolean;
maxPrompts: number;
combinatorial: boolean;
prompts: string[];
parsingError: string | undefined | null;
isError: boolean;
isLoading: boolean;
seedBehaviour: SeedBehaviour;
}
export const initialDynamicPromptsState: DynamicPromptsState = {
isEnabled: false,
maxPrompts: 100,
combinatorial: true,
prompts: [],
parsingError: undefined,
isError: false,
isLoading: false,
seedBehaviour: 'PER_ITERATION',
};
const initialState: DynamicPromptsState = initialDynamicPromptsState;
@ -27,17 +36,33 @@ export const dynamicPromptsSlice = createSlice({
combinatorialToggled: (state) => {
state.combinatorial = !state.combinatorial;
},
isEnabledToggled: (state) => {
state.isEnabled = !state.isEnabled;
promptsChanged: (state, action: PayloadAction<string[]>) => {
state.prompts = action.payload;
},
parsingErrorChanged: (state, action: PayloadAction<string | undefined>) => {
state.parsingError = action.payload;
},
isErrorChanged: (state, action: PayloadAction<boolean>) => {
state.isError = action.payload;
},
isLoadingChanged: (state, action: PayloadAction<boolean>) => {
state.isLoading = action.payload;
},
seedBehaviourChanged: (state, action: PayloadAction<SeedBehaviour>) => {
state.seedBehaviour = action.payload;
},
},
});
export const {
isEnabledToggled,
maxPromptsChanged,
maxPromptsReset,
combinatorialToggled,
promptsChanged,
parsingErrorChanged,
isErrorChanged,
isLoadingChanged,
seedBehaviourChanged,
} = dynamicPromptsSlice.actions;
export default dynamicPromptsSlice.reducer;

View File

@ -7,19 +7,17 @@ import IAIMantineSearchableSelect from 'common/components/IAIMantineSearchableSe
import IAIMantineSelectItemWithTooltip from 'common/components/IAIMantineSelectItemWithTooltip';
import { autoAddBoardIdChanged } from 'features/gallery/store/gallerySlice';
import { memo, useCallback, useRef } from 'react';
import { useListAllBoardsQuery } from 'services/api/endpoints/boards';
import { useTranslation } from 'react-i18next';
import { useListAllBoardsQuery } from 'services/api/endpoints/boards';
const selector = createSelector(
[stateSelector],
({ gallery, system }) => {
({ gallery }) => {
const { autoAddBoardId, autoAssignBoardOnClick } = gallery;
const { isProcessing } = system;
return {
autoAddBoardId,
autoAssignBoardOnClick,
isProcessing,
};
},
defaultSelectorOptions
@ -28,8 +26,7 @@ const selector = createSelector(
const BoardAutoAddSelect = () => {
const dispatch = useAppDispatch();
const { t } = useTranslation();
const { autoAddBoardId, autoAssignBoardOnClick, isProcessing } =
useAppSelector(selector);
const { autoAddBoardId, autoAssignBoardOnClick } = useAppSelector(selector);
const inputRef = useRef<HTMLInputElement>(null);
const { boards, hasBoards } = useListAllBoardsQuery(undefined, {
selectFromResult: ({ data }) => {
@ -73,7 +70,7 @@ const BoardAutoAddSelect = () => {
data={boards}
nothingFound={t('boards.noMatching')}
itemComponent={IAIMantineSelectItemWithTooltip}
disabled={!hasBoards || autoAssignBoardOnClick || isProcessing}
disabled={!hasBoards || autoAssignBoardOnClick}
filter={(value, item: SelectItem) =>
item.label?.toLowerCase().includes(value.toLowerCase().trim()) ||
item.value.toLowerCase().includes(value.toLowerCase().trim())

View File

@ -2,6 +2,7 @@ import { MenuGroup, MenuItem, MenuList } from '@chakra-ui/react';
import { createSelector } from '@reduxjs/toolkit';
import { stateSelector } from 'app/store/store';
import { useAppDispatch, useAppSelector } from 'app/store/storeHooks';
import { defaultSelectorOptions } from 'app/store/util/defaultMemoizeOptions';
import {
IAIContextMenu,
IAIContextMenuProps,
@ -9,14 +10,13 @@ import {
import { autoAddBoardIdChanged } from 'features/gallery/store/gallerySlice';
import { BoardId } from 'features/gallery/store/types';
import { MouseEvent, memo, useCallback, useMemo } from 'react';
import { useTranslation } from 'react-i18next';
import { FaPlus } from 'react-icons/fa';
import { useBoardName } from 'services/api/hooks/useBoardName';
import { BoardDTO } from 'services/api/types';
import { menuListMotionProps } from 'theme/components/menu';
import GalleryBoardContextMenuItems from './GalleryBoardContextMenuItems';
import NoBoardContextMenuItems from './NoBoardContextMenuItems';
import { defaultSelectorOptions } from 'app/store/util/defaultMemoizeOptions';
import { useTranslation } from 'react-i18next';
type Props = {
board?: BoardDTO;
@ -37,19 +37,17 @@ const BoardContextMenu = ({
() =>
createSelector(
stateSelector,
({ gallery, system }) => {
({ gallery }) => {
const isAutoAdd = gallery.autoAddBoardId === board_id;
const isProcessing = system.isProcessing;
const autoAssignBoardOnClick = gallery.autoAssignBoardOnClick;
return { isAutoAdd, isProcessing, autoAssignBoardOnClick };
return { isAutoAdd, autoAssignBoardOnClick };
},
defaultSelectorOptions
),
[board_id]
);
const { isAutoAdd, isProcessing, autoAssignBoardOnClick } =
useAppSelector(selector);
const { isAutoAdd, autoAssignBoardOnClick } = useAppSelector(selector);
const boardName = useBoardName(board_id);
const handleSetAutoAdd = useCallback(() => {
@ -78,7 +76,7 @@ const BoardContextMenu = ({
<MenuGroup title={boardName}>
<MenuItem
icon={<FaPlus />}
isDisabled={isAutoAdd || isProcessing || autoAssignBoardOnClick}
isDisabled={isAutoAdd || autoAssignBoardOnClick}
onClick={handleSetAutoAdd}
>
{t('boards.menuItemAutoAdd')}

View File

@ -16,6 +16,7 @@ import { useAppDispatch, useAppSelector } from 'app/store/storeHooks';
import { defaultSelectorOptions } from 'app/store/util/defaultMemoizeOptions';
import IAIDroppable from 'common/components/IAIDroppable';
import SelectionOverlay from 'common/components/SelectionOverlay';
import { AddToBoardDropData } from 'features/dnd/types';
import {
autoAddBoardIdChanged,
boardIdSelected,
@ -31,7 +32,6 @@ import { useGetImageDTOQuery } from 'services/api/endpoints/images';
import { BoardDTO } from 'services/api/types';
import AutoAddIcon from '../AutoAddIcon';
import BoardContextMenu from '../BoardContextMenu';
import { AddToBoardDropData } from 'features/dnd/types';
interface GalleryBoardProps {
board: BoardDTO;
@ -49,16 +49,14 @@ const GalleryBoard = ({
() =>
createSelector(
stateSelector,
({ gallery, system }) => {
({ gallery }) => {
const isSelectedForAutoAdd =
board.board_id === gallery.autoAddBoardId;
const autoAssignBoardOnClick = gallery.autoAssignBoardOnClick;
const isProcessing = system.isProcessing;
return {
isSelectedForAutoAdd,
autoAssignBoardOnClick,
isProcessing,
};
},
defaultSelectorOptions
@ -66,7 +64,7 @@ const GalleryBoard = ({
[board.board_id]
);
const { isSelectedForAutoAdd, autoAssignBoardOnClick, isProcessing } =
const { isSelectedForAutoAdd, autoAssignBoardOnClick } =
useAppSelector(selector);
const [isHovered, setIsHovered] = useState(false);
const handleMouseOver = useCallback(() => {
@ -96,10 +94,10 @@ const GalleryBoard = ({
const handleSelectBoard = useCallback(() => {
dispatch(boardIdSelected(board_id));
if (autoAssignBoardOnClick && !isProcessing) {
if (autoAssignBoardOnClick) {
dispatch(autoAddBoardIdChanged(board_id));
}
}, [board_id, autoAssignBoardOnClick, isProcessing, dispatch]);
}, [board_id, autoAssignBoardOnClick, dispatch]);
const [updateBoard, { isLoading: isUpdateBoardLoading }] =
useUpdateBoardMutation();

View File

@ -22,25 +22,23 @@ interface Props {
const selector = createSelector(
stateSelector,
({ gallery, system }) => {
({ gallery }) => {
const { autoAddBoardId, autoAssignBoardOnClick } = gallery;
const { isProcessing } = system;
return { autoAddBoardId, autoAssignBoardOnClick, isProcessing };
return { autoAddBoardId, autoAssignBoardOnClick };
},
defaultSelectorOptions
);
const NoBoardBoard = memo(({ isSelected }: Props) => {
const dispatch = useAppDispatch();
const { autoAddBoardId, autoAssignBoardOnClick, isProcessing } =
useAppSelector(selector);
const { autoAddBoardId, autoAssignBoardOnClick } = useAppSelector(selector);
const boardName = useBoardName('none');
const handleSelectBoard = useCallback(() => {
dispatch(boardIdSelected('none'));
if (autoAssignBoardOnClick && !isProcessing) {
if (autoAssignBoardOnClick) {
dispatch(autoAddBoardIdChanged('none'));
}
}, [dispatch, autoAssignBoardOnClick, isProcessing]);
}, [dispatch, autoAssignBoardOnClick]);
const [isHovered, setIsHovered] = useState(false);
const handleMouseOver = useCallback(() => {

View File

@ -53,6 +53,7 @@ const DeleteBoardModal = (props: Props) => {
isCanvasImage: some(allImageUsage, (i) => i.isCanvasImage),
isNodesImage: some(allImageUsage, (i) => i.isNodesImage),
isControlNetImage: some(allImageUsage, (i) => i.isControlNetImage),
isIPAdapterImage: some(allImageUsage, (i) => i.isIPAdapterImage),
};
return { imageUsageSummary };
}),

View File

@ -21,6 +21,7 @@ import { workflowLoadRequested } from 'features/nodes/store/actions';
import ParamUpscalePopover from 'features/parameters/components/Parameters/Upscale/ParamUpscaleSettings';
import { useRecallParameters } from 'features/parameters/hooks/useRecallParameters';
import { initialImageSelected } from 'features/parameters/store/actions';
import { useIsQueueMutationInProgress } from 'features/queue/hooks/useIsQueueMutationInProgress';
import { useFeatureStatus } from 'features/system/hooks/useFeatureStatus';
import { activeTabNameSelector } from 'features/ui/store/uiSelectors';
import {
@ -36,9 +37,8 @@ import {
FaHourglassHalf,
FaQuoteRight,
FaSeedling,
FaShareAlt,
} from 'react-icons/fa';
import { MdDeviceHub } from 'react-icons/md';
import { FaCircleNodes, FaEllipsis } from 'react-icons/fa6';
import {
useGetImageDTOQuery,
useGetImageMetadataFromFileQuery,
@ -50,8 +50,7 @@ import SingleSelectionMenuItems from '../ImageContextMenu/SingleSelectionMenuIte
const currentImageButtonsSelector = createSelector(
[stateSelector, activeTabNameSelector],
({ gallery, system, ui, config }, activeTabName) => {
const { isProcessing, isConnected, shouldConfirmOnDelete, progressImage } =
system;
const { isConnected, shouldConfirmOnDelete, denoiseProgress } = system;
const {
shouldShowImageDetails,
@ -64,11 +63,10 @@ const currentImageButtonsSelector = createSelector(
const lastSelectedImage = gallery.selection[gallery.selection.length - 1];
return {
canDeleteImage: isConnected && !isProcessing,
shouldConfirmOnDelete,
isProcessing,
isConnected,
shouldDisableToolbarButtons: Boolean(progressImage) || !lastSelectedImage,
shouldDisableToolbarButtons:
Boolean(denoiseProgress?.progress_image) || !lastSelectedImage,
shouldShowImageDetails,
activeTabName,
shouldHidePreview,
@ -89,7 +87,6 @@ type CurrentImageButtonsProps = FlexProps;
const CurrentImageButtons = (props: CurrentImageButtonsProps) => {
const dispatch = useAppDispatch();
const {
isProcessing,
isConnected,
shouldDisableToolbarButtons,
shouldShowImageDetails,
@ -99,7 +96,7 @@ const CurrentImageButtons = (props: CurrentImageButtonsProps) => {
} = useAppSelector(currentImageButtonsSelector);
const isUpscalingEnabled = useFeatureStatus('upscaling').isFeatureEnabled;
const isQueueMutationInProgress = useIsQueueMutationInProgress();
const toaster = useAppToaster();
const { t } = useTranslation();
@ -202,19 +199,10 @@ const CurrentImageButtons = (props: CurrentImageButtonsProps) => {
{
enabled: () =>
Boolean(
isUpscalingEnabled &&
!shouldDisableToolbarButtons &&
isConnected &&
!isProcessing
isUpscalingEnabled && !shouldDisableToolbarButtons && isConnected
),
},
[
isUpscalingEnabled,
imageDTO,
shouldDisableToolbarButtons,
isConnected,
isProcessing,
]
[isUpscalingEnabled, imageDTO, shouldDisableToolbarButtons, isConnected]
);
const handleClickShowImageDetails = useCallback(
@ -266,10 +254,10 @@ const CurrentImageButtons = (props: CurrentImageButtonsProps) => {
<Menu>
<MenuButton
as={IAIIconButton}
aria-label={`${t('parameters.sendTo')}...`}
tooltip={`${t('parameters.sendTo')}...`}
aria-label={t('parameters.imageActions')}
tooltip={t('parameters.imageActions')}
isDisabled={!imageDTO}
icon={<FaShareAlt />}
icon={<FaEllipsis />}
/>
<MenuList motionProps={menuListMotionProps}>
{imageDTO && <SingleSelectionMenuItems imageDTO={imageDTO} />}
@ -280,7 +268,7 @@ const CurrentImageButtons = (props: CurrentImageButtonsProps) => {
<ButtonGroup isAttached={true} isDisabled={shouldDisableToolbarButtons}>
<IAIIconButton
isLoading={isLoading}
icon={<MdDeviceHub />}
icon={<FaCircleNodes />}
tooltip={`${t('nodes.loadWorkflow')} (W)`}
aria-label={`${t('nodes.loadWorkflow')} (W)`}
isDisabled={!workflow}
@ -313,15 +301,12 @@ const CurrentImageButtons = (props: CurrentImageButtonsProps) => {
</ButtonGroup>
{isUpscalingEnabled && (
<ButtonGroup
isAttached={true}
isDisabled={shouldDisableToolbarButtons}
>
<ButtonGroup isAttached={true} isDisabled={isQueueMutationInProgress}>
{isUpscalingEnabled && <ParamUpscalePopover imageDTO={imageDTO} />}
</ButtonGroup>
)}
<ButtonGroup isAttached={true} isDisabled={shouldDisableToolbarButtons}>
<ButtonGroup isAttached={true}>
<IAIIconButton
icon={<FaCode />}
tooltip={`${t('parameters.info')} (I)`}
@ -342,10 +327,7 @@ const CurrentImageButtons = (props: CurrentImageButtonsProps) => {
</ButtonGroup>
<ButtonGroup isAttached={true}>
<DeleteImageButton
onClick={handleDelete}
isDisabled={shouldDisableToolbarButtons}
/>
<DeleteImageButton onClick={handleDelete} />
</ButtonGroup>
</Flex>
</>

View File

@ -29,12 +29,12 @@ export const imagesSelector = createSelector(
shouldHidePreview,
shouldShowProgressInViewer,
} = ui;
const { progressImage, shouldAntialiasProgressImage } = system;
const { denoiseProgress, shouldAntialiasProgressImage } = system;
return {
shouldShowImageDetails,
shouldHidePreview,
imageName: lastSelectedImage?.image_name,
progressImage,
denoiseProgress,
shouldShowProgressInViewer,
shouldAntialiasProgressImage,
};
@ -50,7 +50,7 @@ const CurrentImagePreview = () => {
const {
shouldShowImageDetails,
imageName,
progressImage,
denoiseProgress,
shouldShowProgressInViewer,
shouldAntialiasProgressImage,
} = useAppSelector(imagesSelector);
@ -143,11 +143,11 @@ const CurrentImagePreview = () => {
position: 'relative',
}}
>
{progressImage && shouldShowProgressInViewer ? (
{denoiseProgress?.progress_image && shouldShowProgressInViewer ? (
<Image
src={progressImage.dataURL}
width={progressImage.width}
height={progressImage.height}
src={denoiseProgress.progress_image.dataURL}
width={denoiseProgress.progress_image.width}
height={denoiseProgress.progress_image.height}
draggable={false}
sx={{
objectFit: 'contain',

View File

@ -16,6 +16,7 @@ import { useFeatureStatus } from 'features/system/hooks/useFeatureStatus';
import { useCopyImageToClipboard } from 'features/ui/hooks/useCopyImageToClipboard';
import { setActiveTab } from 'features/ui/store/uiSlice';
import { memo, useCallback } from 'react';
import { flushSync } from 'react-dom';
import { useTranslation } from 'react-i18next';
import {
FaAsterisk,
@ -28,7 +29,8 @@ import {
FaShare,
FaTrash,
} from 'react-icons/fa';
import { MdDeviceHub, MdStar, MdStarBorder } from 'react-icons/md';
import { FaCircleNodes } from 'react-icons/fa6';
import { MdStar, MdStarBorder } from 'react-icons/md';
import {
useGetImageMetadataFromFileQuery,
useStarImagesMutation,
@ -37,7 +39,6 @@ import {
import { ImageDTO } from 'services/api/types';
import { configSelector } from '../../../system/store/configSelectors';
import { sentImageToCanvas, sentImageToImg2Img } from '../../store/actions';
import { flushSync } from 'react-dom';
type SingleSelectionMenuItemsProps = {
imageDTO: ImageDTO;
@ -180,7 +181,7 @@ const SingleSelectionMenuItems = (props: SingleSelectionMenuItemsProps) => {
{t('parameters.downloadImage')}
</MenuItem>
<MenuItem
icon={isLoading ? <SpinnerIcon /> : <MdDeviceHub />}
icon={isLoading ? <SpinnerIcon /> : <FaCircleNodes />}
onClickCapture={handleLoadWorkflow}
isDisabled={isLoading || !workflow}
>

View File

@ -103,7 +103,7 @@ const ImageMetadataActions = (props: Props) => {
)}
{metadata.negative_prompt && (
<ImageMetadataItem
label={t('metadata.NegativePrompt')}
label={t('metadata.negativePrompt')}
labelPosition="top"
value={metadata.negative_prompt}
onClick={handleRecallNegativePrompt}

View File

@ -96,7 +96,7 @@ const ImageMetadataViewer = ({ image }: ImageMetadataViewerProps) => {
{workflow ? (
<DataViewer data={workflow} label={t('metadata.workflow')} />
) : (
<IAINoContentFallback label={t('metadata.noWorkFlow')} />
<IAINoContentFallback label={t('nodes.noWorkflow')} />
)}
</TabPanel>
</TabPanels>

View File

@ -96,7 +96,7 @@ const AddNodePopover = () => {
(nodeType: AnyInvocationType) => {
const invocation = buildInvocation(nodeType);
if (!invocation) {
const errorMessage = t('nodes.unknownInvocation', {
const errorMessage = t('nodes.unknownNode', {
nodeType: nodeType,
});
toaster({

View File

@ -16,7 +16,7 @@ const selector = createSelector(stateSelector, ({ system, gallery }) => {
return {
imageDTO,
progressImage: system.progressImage,
progressImage: system.denoiseProgress?.progress_image,
};
});

View File

@ -27,7 +27,7 @@ const EmbedWorkflowCheckbox = ({ nodeId }: { nodeId: string }) => {
return (
<FormControl as={Flex} sx={{ alignItems: 'center', gap: 2, w: 'auto' }}>
<FormLabel sx={{ fontSize: 'xs', mb: '1px' }}>Embed Workflow</FormLabel>
<FormLabel sx={{ fontSize: 'xs', mb: '1px' }}>Workflow</FormLabel>
<Checkbox
className="nopan"
size="sm"

View File

@ -1,14 +1,13 @@
import { Flex, Grid, GridItem } from '@chakra-ui/react';
import { useAnyOrDirectInputFieldNames } from 'features/nodes/hooks/useAnyOrDirectInputFieldNames';
import { useConnectionInputFieldNames } from 'features/nodes/hooks/useConnectionInputFieldNames';
import { useOutputFieldNames } from 'features/nodes/hooks/useOutputFieldNames';
import { memo } from 'react';
import NodeWrapper from '../common/NodeWrapper';
import InvocationNodeFooter from './InvocationNodeFooter';
import InvocationNodeHeader from './InvocationNodeHeader';
import NodeWrapper from '../common/NodeWrapper';
import OutputField from './fields/OutputField';
import InputField from './fields/InputField';
import { useOutputFieldNames } from 'features/nodes/hooks/useOutputFieldNames';
import { useWithFooter } from 'features/nodes/hooks/useWithFooter';
import { useConnectionInputFieldNames } from 'features/nodes/hooks/useConnectionInputFieldNames';
import { useAnyOrDirectInputFieldNames } from 'features/nodes/hooks/useAnyOrDirectInputFieldNames';
import OutputField from './fields/OutputField';
type Props = {
nodeId: string;
@ -22,7 +21,6 @@ const InvocationNode = ({ nodeId, isOpen, label, type, selected }: Props) => {
const inputConnectionFieldNames = useConnectionInputFieldNames(nodeId);
const inputAnyOrDirectFieldNames = useAnyOrDirectInputFieldNames(nodeId);
const outputFieldNames = useOutputFieldNames(nodeId);
const withFooter = useWithFooter(nodeId);
return (
<NodeWrapper nodeId={nodeId} selected={selected}>
@ -43,7 +41,7 @@ const InvocationNode = ({ nodeId, isOpen, label, type, selected }: Props) => {
h: 'full',
py: 2,
gap: 1,
borderBottomRadius: withFooter ? 0 : 'base',
borderBottomRadius: 0,
}}
>
<Flex sx={{ flexDir: 'column', px: 2, w: 'full', h: 'full' }}>
@ -76,7 +74,7 @@ const InvocationNode = ({ nodeId, isOpen, label, type, selected }: Props) => {
))}
</Flex>
</Flex>
{withFooter && <InvocationNodeFooter nodeId={nodeId} />}
<InvocationNodeFooter nodeId={nodeId} />
</>
)}
</NodeWrapper>

View File

@ -3,12 +3,15 @@ import { DRAG_HANDLE_CLASSNAME } from 'features/nodes/types/constants';
import { memo } from 'react';
import EmbedWorkflowCheckbox from './EmbedWorkflowCheckbox';
import SaveToGalleryCheckbox from './SaveToGalleryCheckbox';
import UseCacheCheckbox from './UseCacheCheckbox';
import { useHasImageOutput } from 'features/nodes/hooks/useHasImageOutput';
type Props = {
nodeId: string;
};
const InvocationNodeFooter = ({ nodeId }: Props) => {
const hasImageOutput = useHasImageOutput(nodeId);
return (
<Flex
className={DRAG_HANDLE_CLASSNAME}
@ -22,8 +25,9 @@ const InvocationNodeFooter = ({ nodeId }: Props) => {
justifyContent: 'space-between',
}}
>
<EmbedWorkflowCheckbox nodeId={nodeId} />
<SaveToGalleryCheckbox nodeId={nodeId} />
{hasImageOutput && <EmbedWorkflowCheckbox nodeId={nodeId} />}
<UseCacheCheckbox nodeId={nodeId} />
{hasImageOutput && <SaveToGalleryCheckbox nodeId={nodeId} />}
</Flex>
);
};

View File

@ -0,0 +1,35 @@
import { Checkbox, Flex, FormControl, FormLabel } from '@chakra-ui/react';
import { useAppDispatch } from 'app/store/storeHooks';
import { useUseCache } from 'features/nodes/hooks/useUseCache';
import { nodeUseCacheChanged } from 'features/nodes/store/nodesSlice';
import { ChangeEvent, memo, useCallback } from 'react';
const UseCacheCheckbox = ({ nodeId }: { nodeId: string }) => {
const dispatch = useAppDispatch();
const useCache = useUseCache(nodeId);
const handleChange = useCallback(
(e: ChangeEvent<HTMLInputElement>) => {
dispatch(
nodeUseCacheChanged({
nodeId,
useCache: e.target.checked,
})
);
},
[dispatch, nodeId]
);
return (
<FormControl as={Flex} sx={{ alignItems: 'center', gap: 2, w: 'auto' }}>
<FormLabel sx={{ fontSize: 'xs', mb: '1px' }}>Use Cache</FormLabel>
<Checkbox
className="nopan"
size="sm"
onChange={handleChange}
isChecked={useCache}
/>
</FormControl>
);
};
export default memo(UseCacheCheckbox);

View File

@ -38,7 +38,7 @@ const EditableFieldTitle = forwardRef((props: Props, ref) => {
const dispatch = useAppDispatch();
const [localTitle, setLocalTitle] = useState(
label || fieldTemplateTitle || t('nodes.unknownFeild')
label || fieldTemplateTitle || t('nodes.unknownField')
);
const handleSubmit = useCallback(

View File

@ -15,6 +15,7 @@ import SDXLMainModelInputField from './inputs/SDXLMainModelInputField';
import SchedulerInputField from './inputs/SchedulerInputField';
import StringInputField from './inputs/StringInputField';
import VaeModelInputField from './inputs/VaeModelInputField';
import IPAdapterModelInputField from './inputs/IPAdapterModelInputField';
type InputFieldProps = {
nodeId: string;
@ -147,6 +148,19 @@ const InputFieldRenderer = ({ nodeId, fieldName }: InputFieldProps) => {
);
}
if (
field?.type === 'IPAdapterModelField' &&
fieldTemplate?.type === 'IPAdapterModelField'
) {
return (
<IPAdapterModelInputField
nodeId={nodeId}
field={field}
fieldTemplate={fieldTemplate}
/>
);
}
if (field?.type === 'ColorField' && fieldTemplate?.type === 'ColorField') {
return (
<ColorInputField

View File

@ -0,0 +1,17 @@
import {
IPAdapterInputFieldTemplate,
IPAdapterInputFieldValue,
FieldComponentProps,
} from 'features/nodes/types/types';
import { memo } from 'react';
const IPAdapterInputFieldComponent = (
_props: FieldComponentProps<
IPAdapterInputFieldValue,
IPAdapterInputFieldTemplate
>
) => {
return null;
};
export default memo(IPAdapterInputFieldComponent);

View File

@ -0,0 +1,100 @@
import { SelectItem } from '@mantine/core';
import { useAppDispatch } from 'app/store/storeHooks';
import IAIMantineSelect from 'common/components/IAIMantineSelect';
import { fieldIPAdapterModelValueChanged } from 'features/nodes/store/nodesSlice';
import {
IPAdapterModelInputFieldTemplate,
IPAdapterModelInputFieldValue,
FieldComponentProps,
} from 'features/nodes/types/types';
import { MODEL_TYPE_MAP } from 'features/parameters/types/constants';
import { modelIdToIPAdapterModelParam } from 'features/parameters/util/modelIdToIPAdapterModelParams';
import { forEach } from 'lodash-es';
import { memo, useCallback, useMemo } from 'react';
import { useGetIPAdapterModelsQuery } from 'services/api/endpoints/models';
const IPAdapterModelInputFieldComponent = (
props: FieldComponentProps<
IPAdapterModelInputFieldValue,
IPAdapterModelInputFieldTemplate
>
) => {
const { nodeId, field } = props;
const ipAdapterModel = field.value;
const dispatch = useAppDispatch();
const { data: ipAdapterModels } = useGetIPAdapterModelsQuery();
// grab the full model entity from the RTK Query cache
const selectedModel = useMemo(
() =>
ipAdapterModels?.entities[
`${ipAdapterModel?.base_model}/ip_adapter/${ipAdapterModel?.model_name}`
] ?? null,
[
ipAdapterModel?.base_model,
ipAdapterModel?.model_name,
ipAdapterModels?.entities,
]
);
const data = useMemo(() => {
if (!ipAdapterModels) {
return [];
}
const data: SelectItem[] = [];
forEach(ipAdapterModels.entities, (model, id) => {
if (!model) {
return;
}
data.push({
value: id,
label: model.model_name,
group: MODEL_TYPE_MAP[model.base_model],
});
});
return data;
}, [ipAdapterModels]);
const handleValueChanged = useCallback(
(v: string | null) => {
if (!v) {
return;
}
const newIPAdapterModel = modelIdToIPAdapterModelParam(v);
if (!newIPAdapterModel) {
return;
}
dispatch(
fieldIPAdapterModelValueChanged({
nodeId,
fieldName: field.name,
value: newIPAdapterModel,
})
);
},
[dispatch, field.name, nodeId]
);
return (
<IAIMantineSelect
className="nowheel nodrag"
tooltip={selectedModel?.description}
value={selectedModel?.id ?? null}
placeholder="Pick one"
error={!selectedModel}
data={data}
onChange={handleValueChanged}
sx={{ width: '100%' }}
/>
);
};
export default memo(IPAdapterModelInputFieldComponent);

View File

@ -114,7 +114,7 @@ const NodeWrapper = (props: NodeWrapperProps) => {
borderRadius: 'md',
pointerEvents: 'none',
transitionProperty: 'common',
transitionDuration: 'normal',
transitionDuration: '0.1s',
opacity: 0.7,
shadow: isInProgress ? inProgressShadow : undefined,
zIndex: -1,

View File

@ -1,15 +0,0 @@
import { Flex } from '@chakra-ui/react';
import CancelButton from 'features/parameters/components/ProcessButtons/CancelButton';
import InvokeButton from 'features/parameters/components/ProcessButtons/InvokeButton';
import { memo } from 'react';
const WorkflowEditorControls = () => {
return (
<Flex layerStyle="first" sx={{ gap: 2, borderRadius: 'base', p: 2 }}>
<InvokeButton />
<CancelButton />
</Flex>
);
};
export default memo(WorkflowEditorControls);

View File

@ -1,5 +1,5 @@
import { Flex } from '@chakra-ui/react';
import ProcessButtons from 'features/parameters/components/ProcessButtons/ProcessButtons';
import QueueControls from 'features/queue/components/QueueControls';
import ResizeHandle from 'features/ui/components/tabs/ResizeHandle';
import { usePanelStorage } from 'features/ui/hooks/usePanelStorage';
import { memo, useCallback, useRef, useState } from 'react';
@ -11,6 +11,7 @@ import {
import 'reactflow/dist/style.css';
import InspectorPanel from './inspector/InspectorPanel';
import WorkflowPanel from './workflow/WorkflowPanel';
import ParamIterations from 'features/parameters/components/Parameters/Core/ParamIterations';
const NodeEditorPanelGroup = () => {
const [isTopPanelCollapsed, setIsTopPanelCollapsed] = useState(false);
@ -26,7 +27,21 @@ const NodeEditorPanelGroup = () => {
return (
<Flex sx={{ flexDir: 'column', gap: 2, height: '100%', width: '100%' }}>
<ProcessButtons />
<QueueControls />
<Flex
layerStyle="first"
sx={{
w: 'full',
position: 'relative',
borderRadius: 'base',
p: 2,
pb: 3,
gap: 2,
flexDir: 'column',
}}
>
<ParamIterations asSlider />
</Flex>
<PanelGroup
ref={panelGroupRef}
id="workflow-panel-group"

View File

@ -1,13 +1,18 @@
import { Box, Flex } from '@chakra-ui/react';
import { Box, Flex, StyleProps } from '@chakra-ui/react';
import { OverlayScrollbarsComponent } from 'overlayscrollbars-react';
import { PropsWithChildren, memo } from 'react';
const ScrollableContent = (props: PropsWithChildren) => {
type Props = PropsWithChildren & {
maxHeight?: StyleProps['maxHeight'];
};
const ScrollableContent = ({ children, maxHeight }: Props) => {
return (
<Flex
sx={{
w: 'full',
h: 'full',
maxHeight,
position: 'relative',
}}
>
@ -35,7 +40,7 @@ const ScrollableContent = (props: PropsWithChildren) => {
},
}}
>
{props.children}
{children}
</OverlayScrollbarsComponent>
</Box>
</Flex>

View File

@ -83,7 +83,7 @@ const InspectorOutputsTab = () => {
/>
))
) : (
<DataViewer data={nes.outputs} label={t('nodes.nodesOutputs')} />
<DataViewer data={nes.outputs} label={t('nodes.nodeOutputs')} />
)}
</Flex>
</ScrollableContent>

View File

@ -38,7 +38,7 @@ const NodeTemplateInspector = () => {
);
}
return <DataViewer data={template} label={t('nodes.NodeTemplate')} />;
return <DataViewer data={template} label={t('nodes.nodeTemplate')} />;
};
export default memo(NodeTemplateInspector);

View File

@ -146,6 +146,7 @@ export const useBuildNodeData = () => {
isIntermediate: true,
inputs,
outputs,
useCache: template.useCache,
},
};

View File

@ -0,0 +1,29 @@
import { createSelector } from '@reduxjs/toolkit';
import { stateSelector } from 'app/store/store';
import { useAppSelector } from 'app/store/storeHooks';
import { defaultSelectorOptions } from 'app/store/util/defaultMemoizeOptions';
import { useMemo } from 'react';
import { isInvocationNode } from '../types/types';
export const useUseCache = (nodeId: string) => {
const selector = useMemo(
() =>
createSelector(
stateSelector,
({ nodes }) => {
const node = nodes.nodes.find((node) => node.id === nodeId);
if (!isInvocationNode(node)) {
return false;
}
// cast to boolean to support older workflows that didn't have useCache
// TODO: handle this better somehow
return node.data.useCache;
},
defaultSelectorOptions
),
[nodeId]
);
const useCache = useAppSelector(selector);
return useCache;
};

View File

@ -7,7 +7,7 @@ import { useMemo } from 'react';
import { FOOTER_FIELDS } from '../types/constants';
import { isInvocationNode } from '../types/types';
export const useWithFooter = (nodeId: string) => {
export const useHasImageOutputs = (nodeId: string) => {
const selector = useMemo(
() =>
createSelector(

View File

@ -25,6 +25,7 @@ import {
appSocketInvocationComplete,
appSocketInvocationError,
appSocketInvocationStarted,
appSocketQueueItemStatusChanged,
} from 'services/events/actions';
import { v4 as uuidv4 } from 'uuid';
import { DRAG_HANDLE_CLASSNAME } from '../types/constants';
@ -41,6 +42,7 @@ import {
IntegerInputFieldValue,
InvocationNodeData,
InvocationTemplate,
IPAdapterModelInputFieldValue,
isInvocationNode,
isNotesNode,
LoRAModelInputFieldValue,
@ -260,6 +262,20 @@ const nodesSlice = createSlice({
}
node.data.embedWorkflow = embedWorkflow;
},
nodeUseCacheChanged: (
state,
action: PayloadAction<{ nodeId: string; useCache: boolean }>
) => {
const { nodeId, useCache } = action.payload;
const nodeIndex = state.nodes.findIndex((n) => n.id === nodeId);
const node = state.nodes?.[nodeIndex];
if (!isInvocationNode(node)) {
return;
}
node.data.useCache = useCache;
},
nodeIsIntermediateChanged: (
state,
action: PayloadAction<{ nodeId: string; isIntermediate: boolean }>
@ -520,6 +536,12 @@ const nodesSlice = createSlice({
) => {
fieldValueReducer(state, action);
},
fieldIPAdapterModelValueChanged: (
state,
action: FieldValueAction<IPAdapterModelInputFieldValue>
) => {
fieldValueReducer(state, action);
},
fieldEnumModelValueChanged: (
state,
action: FieldValueAction<EnumInputFieldValue>
@ -840,6 +862,19 @@ const nodesSlice = createSlice({
}
});
});
builder.addCase(appSocketQueueItemStatusChanged, (state, action) => {
if (
['completed', 'canceled', 'failed'].includes(action.payload.data.status)
) {
forEach(state.nodeExecutionStates, (nes) => {
nes.status = NodeStatus.PENDING;
nes.error = null;
nes.progress = null;
nes.progressImage = null;
// do not reset nes.outputs, this allows a user to inspect the output of a node across batches
});
}
});
},
});
@ -866,6 +901,7 @@ export const {
fieldLoRAModelValueChanged,
fieldEnumModelValueChanged,
fieldControlNetModelValueChanged,
fieldIPAdapterModelValueChanged,
fieldRefinerModelValueChanged,
fieldSchedulerValueChanged,
nodeIsOpenChanged,
@ -904,6 +940,7 @@ export const {
nodeIsIntermediateChanged,
mouseOverNodeChanged,
nodeExclusivelySelected,
nodeUseCacheChanged,
} = nodesSlice.actions;
export default nodesSlice.reducer;

View File

@ -41,6 +41,7 @@ export const POLYMORPHIC_TYPES = [
];
export const MODEL_TYPES = [
'IPAdapterModelField',
'ControlNetModelField',
'LoRAModelField',
'MainModelField',
@ -236,6 +237,16 @@ export const FIELDS: Record<FieldType, FieldUIConfig> = {
description: t('nodes.integerPolymorphicDescription'),
title: t('nodes.integerPolymorphic'),
},
IPAdapterField: {
color: 'green.300',
description: 'IP-Adapter info passed between nodes.',
title: 'IP-Adapter',
},
IPAdapterModelField: {
color: 'teal.500',
description: 'IP-Adapter model',
title: 'IP-Adapter Model',
},
LatentsCollection: {
color: 'pink.500',
description: t('nodes.latentsCollectionDescription'),

View File

@ -1,3 +1,4 @@
import { $store } from 'app/store/nanostores/store';
import {
SchedulerParam,
zBaseModel,
@ -7,7 +8,8 @@ import {
zSDXLRefinerModel,
zScheduler,
} from 'features/parameters/types/parameterSchemas';
import { keyBy } from 'lodash-es';
import i18n from 'i18next';
import { has, keyBy } from 'lodash-es';
import { OpenAPIV3 } from 'openapi-types';
import { RgbaColor } from 'react-colorful';
import { Node } from 'reactflow';
@ -20,7 +22,6 @@ import {
import { O } from 'ts-toolbelt';
import { JsonObject } from 'type-fest';
import { z } from 'zod';
import i18n from 'i18next';
export type NonNullableGraph = O.Required<Graph, 'nodes' | 'edges'>;
@ -57,6 +58,10 @@ export type InvocationTemplate = {
* The invocation's version.
*/
version?: string;
/**
* Whether or not this node should use the cache
*/
useCache: boolean;
};
export type FieldUIConfig = {
@ -94,6 +99,8 @@ export const zFieldType = z.enum([
'integer',
'IntegerCollection',
'IntegerPolymorphic',
'IPAdapterField',
'IPAdapterModelField',
'LatentsCollection',
'LatentsField',
'LatentsPolymorphic',
@ -389,6 +396,25 @@ export type ControlCollectionInputFieldValue = z.infer<
typeof zControlCollectionInputFieldValue
>;
export const zIPAdapterModel = zModelIdentifier;
export type IPAdapterModel = z.infer<typeof zIPAdapterModel>;
export const zIPAdapterField = z.object({
image: zImageField,
ip_adapter_model: zIPAdapterModel,
image_encoder_model: z.string().trim().min(1),
weight: z.number(),
});
export type IPAdapterField = z.infer<typeof zIPAdapterField>;
export const zIPAdapterInputFieldValue = zInputFieldValueBase.extend({
type: z.literal('IPAdapterField'),
value: zIPAdapterField.optional(),
});
export type IPAdapterInputFieldValue = z.infer<
typeof zIPAdapterInputFieldValue
>;
export const zModelType = z.enum([
'onnx',
'main',
@ -538,6 +564,17 @@ export type ControlNetModelInputFieldValue = z.infer<
typeof zControlNetModelInputFieldValue
>;
export const zIPAdapterModelField = zModelIdentifier;
export type IPAdapterModelField = z.infer<typeof zIPAdapterModelField>;
export const zIPAdapterModelInputFieldValue = zInputFieldValueBase.extend({
type: z.literal('IPAdapterModelField'),
value: zIPAdapterModelField.optional(),
});
export type IPAdapterModelInputFieldValue = z.infer<
typeof zIPAdapterModelInputFieldValue
>;
export const zCollectionInputFieldValue = zInputFieldValueBase.extend({
type: z.literal('Collection'),
value: z.array(z.any()).optional(), // TODO: should this field ever have a value?
@ -620,6 +657,8 @@ export const zInputFieldValue = z.discriminatedUnion('type', [
zIntegerCollectionInputFieldValue,
zIntegerPolymorphicInputFieldValue,
zIntegerInputFieldValue,
zIPAdapterInputFieldValue,
zIPAdapterModelInputFieldValue,
zLatentsInputFieldValue,
zLatentsCollectionInputFieldValue,
zLatentsPolymorphicInputFieldValue,
@ -822,6 +861,11 @@ export type ControlPolymorphicInputFieldTemplate = Omit<
type: 'ControlPolymorphic';
};
export type IPAdapterInputFieldTemplate = InputFieldTemplateBase & {
default: undefined;
type: 'IPAdapterField';
};
export type EnumInputFieldTemplate = InputFieldTemplateBase & {
default: string;
type: 'enum';
@ -859,6 +903,11 @@ export type ControlNetModelInputFieldTemplate = InputFieldTemplateBase & {
type: 'ControlNetModelField';
};
export type IPAdapterModelInputFieldTemplate = InputFieldTemplateBase & {
default: string;
type: 'IPAdapterModelField';
};
export type CollectionInputFieldTemplate = InputFieldTemplateBase & {
default: [];
type: 'Collection';
@ -930,6 +979,8 @@ export type InputFieldTemplate =
| IntegerCollectionInputFieldTemplate
| IntegerPolymorphicInputFieldTemplate
| IntegerInputFieldTemplate
| IPAdapterInputFieldTemplate
| IPAdapterModelInputFieldTemplate
| LatentsInputFieldTemplate
| LatentsCollectionInputFieldTemplate
| LatentsPolymorphicInputFieldTemplate
@ -976,6 +1027,9 @@ export type InvocationSchemaExtra = {
type: Omit<OpenAPIV3.SchemaObject, 'default'> & {
default: AnyInvocationType;
};
use_cache: Omit<OpenAPIV3.SchemaObject, 'default'> & {
default: boolean;
};
};
};
@ -1139,9 +1193,37 @@ export const zInvocationNodeData = z.object({
version: zSemVer.optional(),
});
export const zInvocationNodeDataV2 = z.preprocess(
(arg) => {
try {
const data = zInvocationNodeData.parse(arg);
if (!has(data, 'useCache')) {
const nodeTemplates = $store.get()?.getState().nodes.nodeTemplates as
| Record<string, InvocationTemplate>
| undefined;
const template = nodeTemplates?.[data.type];
let useCache = true;
if (template) {
useCache = template.useCache;
}
Object.assign(data, { useCache });
}
return data;
} catch {
return arg;
}
},
zInvocationNodeData.extend({
useCache: z.boolean(),
})
);
// Massage this to get better type safety while developing
export type InvocationNodeData = Omit<
z.infer<typeof zInvocationNodeData>,
z.infer<typeof zInvocationNodeDataV2>,
'type'
> & {
type: AnyInvocationType;
@ -1169,7 +1251,7 @@ const zDimension = z.number().gt(0).nullish();
export const zWorkflowInvocationNode = z.object({
id: z.string().trim().min(1),
type: z.literal('invocation'),
data: zInvocationNodeData,
data: zInvocationNodeDataV2,
width: zDimension,
height: zDimension,
position: zPosition,
@ -1231,6 +1313,8 @@ export type WorkflowWarning = {
data: JsonObject;
};
const CURRENT_WORKFLOW_VERSION = '1.0.0';
export const zWorkflow = z.object({
name: z.string().default(''),
author: z.string().default(''),
@ -1246,7 +1330,7 @@ export const zWorkflow = z.object({
.object({
version: zSemVer,
})
.default({ version: '1.0.0' }),
.default({ version: CURRENT_WORKFLOW_VERSION }),
});
export const zValidatedWorkflow = zWorkflow.transform((workflow) => {

View File

@ -60,6 +60,8 @@ import {
ImageField,
LatentsField,
ConditioningField,
IPAdapterInputFieldTemplate,
IPAdapterModelInputFieldTemplate,
} from '../types/types';
import { ControlField } from 'services/api/types';
@ -435,6 +437,19 @@ const buildControlNetModelInputFieldTemplate = ({
return template;
};
const buildIPAdapterModelInputFieldTemplate = ({
schemaObject,
baseField,
}: BuildInputFieldArg): IPAdapterModelInputFieldTemplate => {
const template: IPAdapterModelInputFieldTemplate = {
...baseField,
type: 'IPAdapterModelField',
default: schemaObject.default ?? undefined,
};
return template;
};
const buildImageInputFieldTemplate = ({
schemaObject,
baseField,
@ -648,6 +663,19 @@ const buildControlCollectionInputFieldTemplate = ({
return template;
};
const buildIPAdapterInputFieldTemplate = ({
schemaObject,
baseField,
}: BuildInputFieldArg): IPAdapterInputFieldTemplate => {
const template: IPAdapterInputFieldTemplate = {
...baseField,
type: 'IPAdapterField',
default: schemaObject.default ?? undefined,
};
return template;
};
const buildEnumInputFieldTemplate = ({
schemaObject,
baseField,
@ -851,6 +879,8 @@ const TEMPLATE_BUILDER_MAP = {
integer: buildIntegerInputFieldTemplate,
IntegerCollection: buildIntegerCollectionInputFieldTemplate,
IntegerPolymorphic: buildIntegerPolymorphicInputFieldTemplate,
IPAdapterField: buildIPAdapterInputFieldTemplate,
IPAdapterModelField: buildIPAdapterModelInputFieldTemplate,
LatentsCollection: buildLatentsCollectionInputFieldTemplate,
LatentsField: buildLatentsInputFieldTemplate,
LatentsPolymorphic: buildLatentsPolymorphicInputFieldTemplate,

View File

@ -28,6 +28,8 @@ const FIELD_VALUE_FALLBACK_MAP = {
integer: 0,
IntegerCollection: [],
IntegerPolymorphic: 0,
IPAdapterField: undefined,
IPAdapterModelField: undefined,
LatentsCollection: [],
LatentsField: undefined,
LatentsPolymorphic: undefined,

View File

@ -1,209 +0,0 @@
import { RootState } from 'app/store/store';
import { NonNullableGraph } from 'features/nodes/types/types';
import { unset } from 'lodash-es';
import {
DynamicPromptInvocation,
IterateInvocation,
MetadataAccumulatorInvocation,
NoiseInvocation,
RandomIntInvocation,
RangeOfSizeInvocation,
} from 'services/api/types';
import {
DYNAMIC_PROMPT,
ITERATE,
METADATA_ACCUMULATOR,
NOISE,
POSITIVE_CONDITIONING,
RANDOM_INT,
RANGE_OF_SIZE,
} from './constants';
export const addDynamicPromptsToGraph = (
state: RootState,
graph: NonNullableGraph
): void => {
const { positivePrompt, iterations, seed, shouldRandomizeSeed } =
state.generation;
const {
combinatorial,
isEnabled: isDynamicPromptsEnabled,
maxPrompts,
} = state.dynamicPrompts;
const metadataAccumulator = graph.nodes[METADATA_ACCUMULATOR] as
| MetadataAccumulatorInvocation
| undefined;
if (isDynamicPromptsEnabled) {
// iteration is handled via dynamic prompts
unset(graph.nodes[POSITIVE_CONDITIONING], 'prompt');
const dynamicPromptNode: DynamicPromptInvocation = {
id: DYNAMIC_PROMPT,
type: 'dynamic_prompt',
is_intermediate: true,
max_prompts: combinatorial ? maxPrompts : iterations,
combinatorial,
prompt: positivePrompt,
};
const iterateNode: IterateInvocation = {
id: ITERATE,
type: 'iterate',
is_intermediate: true,
};
graph.nodes[DYNAMIC_PROMPT] = dynamicPromptNode;
graph.nodes[ITERATE] = iterateNode;
// connect dynamic prompts to compel nodes
graph.edges.push(
{
source: {
node_id: DYNAMIC_PROMPT,
field: 'collection',
},
destination: {
node_id: ITERATE,
field: 'collection',
},
},
{
source: {
node_id: ITERATE,
field: 'item',
},
destination: {
node_id: POSITIVE_CONDITIONING,
field: 'prompt',
},
}
);
// hook up positive prompt to metadata
if (metadataAccumulator) {
graph.edges.push({
source: {
node_id: ITERATE,
field: 'item',
},
destination: {
node_id: METADATA_ACCUMULATOR,
field: 'positive_prompt',
},
});
}
if (shouldRandomizeSeed) {
// Random int node to generate the starting seed
const randomIntNode: RandomIntInvocation = {
id: RANDOM_INT,
type: 'rand_int',
is_intermediate: true,
};
graph.nodes[RANDOM_INT] = randomIntNode;
// Connect random int to the start of the range of size so the range starts on the random first seed
graph.edges.push({
source: { node_id: RANDOM_INT, field: 'value' },
destination: { node_id: NOISE, field: 'seed' },
});
if (metadataAccumulator) {
graph.edges.push({
source: { node_id: RANDOM_INT, field: 'value' },
destination: { node_id: METADATA_ACCUMULATOR, field: 'seed' },
});
}
} else {
// User specified seed, so set the start of the range of size to the seed
(graph.nodes[NOISE] as NoiseInvocation).seed = seed;
// hook up seed to metadata
if (metadataAccumulator) {
metadataAccumulator.seed = seed;
}
}
} else {
// no dynamic prompt - hook up positive prompt
if (metadataAccumulator) {
metadataAccumulator.positive_prompt = positivePrompt;
}
const rangeOfSizeNode: RangeOfSizeInvocation = {
id: RANGE_OF_SIZE,
type: 'range_of_size',
is_intermediate: true,
size: iterations,
step: 1,
};
const iterateNode: IterateInvocation = {
id: ITERATE,
type: 'iterate',
is_intermediate: true,
};
graph.nodes[ITERATE] = iterateNode;
graph.nodes[RANGE_OF_SIZE] = rangeOfSizeNode;
graph.edges.push({
source: {
node_id: RANGE_OF_SIZE,
field: 'collection',
},
destination: {
node_id: ITERATE,
field: 'collection',
},
});
graph.edges.push({
source: {
node_id: ITERATE,
field: 'item',
},
destination: {
node_id: NOISE,
field: 'seed',
},
});
// hook up seed to metadata
if (metadataAccumulator) {
graph.edges.push({
source: {
node_id: ITERATE,
field: 'item',
},
destination: {
node_id: METADATA_ACCUMULATOR,
field: 'seed',
},
});
}
// handle seed
if (shouldRandomizeSeed) {
// Random int node to generate the starting seed
const randomIntNode: RandomIntInvocation = {
id: RANDOM_INT,
type: 'rand_int',
is_intermediate: true,
};
graph.nodes[RANDOM_INT] = randomIntNode;
// Connect random int to the start of the range of size so the range starts on the random first seed
graph.edges.push({
source: { node_id: RANDOM_INT, field: 'value' },
destination: { node_id: RANGE_OF_SIZE, field: 'start' },
});
} else {
// User specified seed, so set the start of the range of size to the seed
rangeOfSizeNode.start = seed;
}
}
};

View File

@ -0,0 +1,59 @@
import { RootState } from 'app/store/store';
import { IPAdapterInvocation } from 'services/api/types';
import { NonNullableGraph } from '../../types/types';
import { IP_ADAPTER } from './constants';
export const addIPAdapterToLinearGraph = (
state: RootState,
graph: NonNullableGraph,
baseNodeId: string
): void => {
const { isIPAdapterEnabled, ipAdapterInfo } = state.controlNet;
// const metadataAccumulator = graph.nodes[METADATA_ACCUMULATOR] as
// | MetadataAccumulatorInvocation
// | undefined;
if (isIPAdapterEnabled && ipAdapterInfo.model) {
const ipAdapterNode: IPAdapterInvocation = {
id: IP_ADAPTER,
type: 'ip_adapter',
is_intermediate: true,
weight: ipAdapterInfo.weight,
ip_adapter_model: {
base_model: ipAdapterInfo.model?.base_model,
model_name: ipAdapterInfo.model?.model_name,
},
begin_step_percent: ipAdapterInfo.beginStepPct,
end_step_percent: ipAdapterInfo.endStepPct,
};
if (ipAdapterInfo.adapterImage) {
ipAdapterNode.image = {
image_name: ipAdapterInfo.adapterImage.image_name,
};
} else {
return;
}
graph.nodes[ipAdapterNode.id] = ipAdapterNode as IPAdapterInvocation;
// if (metadataAccumulator?.ip_adapters) {
// // metadata accumulator only needs the ip_adapter field - not the whole node
// // extract what we need and add to the accumulator
// const ipAdapterField = omit(ipAdapterNode, [
// 'id',
// 'type',
// ]) as IPAdapterField;
// metadataAccumulator.ip_adapters.push(ipAdapterField);
// }
graph.edges.push({
source: { node_id: ipAdapterNode.id, field: 'ip_adapter' },
destination: {
node_id: baseNodeId,
field: 'ip_adapter',
},
});
}
};

View File

@ -1,46 +1,32 @@
import { RootState } from 'app/store/store';
import { NonNullableGraph } from 'features/nodes/types/types';
import { activeTabNameSelector } from 'features/ui/store/uiSelectors';
import {
ImageNSFWBlurInvocation,
LatentsToImageInvocation,
MetadataAccumulatorInvocation,
} from 'services/api/types';
import {
LATENTS_TO_IMAGE,
METADATA_ACCUMULATOR,
NSFW_CHECKER,
} from './constants';
import { LATENTS_TO_IMAGE, NSFW_CHECKER } from './constants';
export const addNSFWCheckerToGraph = (
state: RootState,
graph: NonNullableGraph,
nodeIdToAddTo = LATENTS_TO_IMAGE
): void => {
const activeTabName = activeTabNameSelector(state);
const is_intermediate =
activeTabName === 'unifiedCanvas' ? !state.canvas.shouldAutoSave : false;
const nodeToAddTo = graph.nodes[nodeIdToAddTo] as
| LatentsToImageInvocation
| undefined;
const metadataAccumulator = graph.nodes[METADATA_ACCUMULATOR] as
| MetadataAccumulatorInvocation
| undefined;
if (!nodeToAddTo) {
// something has gone terribly awry
return;
}
nodeToAddTo.is_intermediate = true;
nodeToAddTo.use_cache = true;
const nsfwCheckerNode: ImageNSFWBlurInvocation = {
id: NSFW_CHECKER,
type: 'img_nsfw',
is_intermediate,
is_intermediate: true,
};
graph.nodes[NSFW_CHECKER] = nsfwCheckerNode as ImageNSFWBlurInvocation;
@ -54,17 +40,4 @@ export const addNSFWCheckerToGraph = (
field: 'image',
},
});
if (metadataAccumulator) {
graph.edges.push({
source: {
node_id: METADATA_ACCUMULATOR,
field: 'metadata',
},
destination: {
node_id: NSFW_CHECKER,
field: 'metadata',
},
});
}
};

View File

@ -25,7 +25,7 @@ import {
SDXL_REFINER_POSITIVE_CONDITIONING,
SDXL_REFINER_SEAMLESS,
} from './constants';
import { craftSDXLStylePrompt } from './helpers/craftSDXLStylePrompt';
import { buildSDXLStylePrompts } from './helpers/craftSDXLStylePrompt';
export const addSDXLRefinerToGraph = (
state: RootState,
@ -78,8 +78,8 @@ export const addSDXLRefinerToGraph = (
: SDXL_MODEL_LOADER;
// Construct Style Prompt
const { craftedPositiveStylePrompt, craftedNegativeStylePrompt } =
craftSDXLStylePrompt(state, true);
const { joinedPositiveStylePrompt, joinedNegativeStylePrompt } =
buildSDXLStylePrompts(state, true);
// Unplug SDXL Latents Generation To Latents To Image
graph.edges = graph.edges.filter(
@ -100,13 +100,13 @@ export const addSDXLRefinerToGraph = (
graph.nodes[SDXL_REFINER_POSITIVE_CONDITIONING] = {
type: 'sdxl_refiner_compel_prompt',
id: SDXL_REFINER_POSITIVE_CONDITIONING,
style: craftedPositiveStylePrompt,
style: joinedPositiveStylePrompt,
aesthetic_score: refinerPositiveAestheticScore,
};
graph.nodes[SDXL_REFINER_NEGATIVE_CONDITIONING] = {
type: 'sdxl_refiner_compel_prompt',
id: SDXL_REFINER_NEGATIVE_CONDITIONING,
style: craftedNegativeStylePrompt,
style: joinedNegativeStylePrompt,
aesthetic_score: refinerNegativeAestheticScore,
};
graph.nodes[SDXL_REFINER_DENOISE_LATENTS] = {

View File

@ -0,0 +1,92 @@
import { NonNullableGraph } from 'features/nodes/types/types';
import {
CANVAS_OUTPUT,
LATENTS_TO_IMAGE,
METADATA_ACCUMULATOR,
NSFW_CHECKER,
SAVE_IMAGE,
WATERMARKER,
} from './constants';
import {
MetadataAccumulatorInvocation,
SaveImageInvocation,
} from 'services/api/types';
import { RootState } from 'app/store/store';
import { activeTabNameSelector } from 'features/ui/store/uiSelectors';
/**
* Set the `use_cache` field on the linear/canvas graph's final image output node to False.
*/
export const addSaveImageNode = (
state: RootState,
graph: NonNullableGraph
): void => {
const activeTabName = activeTabNameSelector(state);
const is_intermediate =
activeTabName === 'unifiedCanvas' ? !state.canvas.shouldAutoSave : false;
const saveImageNode: SaveImageInvocation = {
id: SAVE_IMAGE,
type: 'save_image',
is_intermediate,
use_cache: false,
};
graph.nodes[SAVE_IMAGE] = saveImageNode;
const metadataAccumulator = graph.nodes[METADATA_ACCUMULATOR] as
| MetadataAccumulatorInvocation
| undefined;
if (metadataAccumulator) {
graph.edges.push({
source: {
node_id: METADATA_ACCUMULATOR,
field: 'metadata',
},
destination: {
node_id: SAVE_IMAGE,
field: 'metadata',
},
});
}
const destination = {
node_id: SAVE_IMAGE,
field: 'image',
};
if (WATERMARKER in graph.nodes) {
graph.edges.push({
source: {
node_id: WATERMARKER,
field: 'image',
},
destination,
});
} else if (NSFW_CHECKER in graph.nodes) {
graph.edges.push({
source: {
node_id: NSFW_CHECKER,
field: 'image',
},
destination,
});
} else if (CANVAS_OUTPUT in graph.nodes) {
graph.edges.push({
source: {
node_id: CANVAS_OUTPUT,
field: 'image',
},
destination,
});
} else if (LATENTS_TO_IMAGE in graph.nodes) {
graph.edges.push({
source: {
node_id: LATENTS_TO_IMAGE,
field: 'image',
},
destination,
});
}
};

View File

@ -51,6 +51,7 @@ export const addWatermarkerToGraph = (
// no matter the situation, we want the l2i node to be intermediate
nodeToAddTo.is_intermediate = true;
nodeToAddTo.use_cache = true;
if (nsfwCheckerNode) {
// if we are using NSFW checker, we need to "disable" it output by marking it intermediate,

View File

@ -1,7 +1,11 @@
import { NonNullableGraph } from 'features/nodes/types/types';
import { ESRGANModelName } from 'features/parameters/store/postprocessingSlice';
import { Graph, ESRGANInvocation } from 'services/api/types';
import { REALESRGAN as ESRGAN } from './constants';
import {
Graph,
ESRGANInvocation,
SaveImageInvocation,
} from 'services/api/types';
import { REALESRGAN as ESRGAN, SAVE_IMAGE } from './constants';
type Arg = {
image_name: string;
@ -17,15 +21,33 @@ export const buildAdHocUpscaleGraph = ({
type: 'esrgan',
image: { image_name },
model_name: esrganModelName,
is_intermediate: false,
is_intermediate: true,
};
const saveImageNode: SaveImageInvocation = {
id: SAVE_IMAGE,
type: 'save_image',
use_cache: false,
};
const graph: NonNullableGraph = {
id: `adhoc-esrgan-graph`,
nodes: {
[ESRGAN]: realesrganNode,
[SAVE_IMAGE]: saveImageNode,
},
edges: [],
edges: [
{
source: {
node_id: ESRGAN,
field: 'image',
},
destination: {
node_id: SAVE_IMAGE,
field: 'image',
},
},
],
};
return graph;

View File

@ -4,9 +4,10 @@ import { NonNullableGraph } from 'features/nodes/types/types';
import { initialGenerationState } from 'features/parameters/store/generationSlice';
import { ImageDTO, ImageToLatentsInvocation } from 'services/api/types';
import { addControlNetToLinearGraph } from './addControlNetToLinearGraph';
import { addDynamicPromptsToGraph } from './addDynamicPromptsToGraph';
import { addIPAdapterToLinearGraph } from './addIPAdapterToLinearGraph';
import { addLoRAsToGraph } from './addLoRAsToGraph';
import { addNSFWCheckerToGraph } from './addNSFWCheckerToGraph';
import { addSaveImageNode } from './addSaveImageNode';
import { addSeamlessToLinearGraph } from './addSeamlessToLinearGraph';
import { addVAEToGraph } from './addVAEToGraph';
import { addWatermarkerToGraph } from './addWatermarkerToGraph';
@ -40,6 +41,7 @@ export const buildCanvasImageToImageGraph = (
model,
cfgScale: cfg_scale,
scheduler,
seed,
steps,
img2imgStrength: strength,
vaePrecision,
@ -53,14 +55,10 @@ export const buildCanvasImageToImageGraph = (
// The bounding box determines width and height, not the width and height params
const { width, height } = state.canvas.boundingBoxDimensions;
const {
scaledBoundingBoxDimensions,
boundingBoxScaleMethod,
shouldAutoSave,
} = state.canvas;
const { scaledBoundingBoxDimensions, boundingBoxScaleMethod } = state.canvas;
const fp32 = vaePrecision === 'fp32';
const is_intermediate = true;
const isUsingScaledDimensions = ['auto', 'manual'].includes(
boundingBoxScaleMethod
);
@ -92,32 +90,33 @@ export const buildCanvasImageToImageGraph = (
[modelLoaderNodeId]: {
type: 'main_model_loader',
id: modelLoaderNodeId,
is_intermediate: true,
is_intermediate,
model,
},
[CLIP_SKIP]: {
type: 'clip_skip',
id: CLIP_SKIP,
is_intermediate: true,
is_intermediate,
skipped_layers: clipSkip,
},
[POSITIVE_CONDITIONING]: {
type: 'compel',
id: POSITIVE_CONDITIONING,
is_intermediate: true,
is_intermediate,
prompt: positivePrompt,
},
[NEGATIVE_CONDITIONING]: {
type: 'compel',
id: NEGATIVE_CONDITIONING,
is_intermediate: true,
is_intermediate,
prompt: negativePrompt,
},
[NOISE]: {
type: 'noise',
id: NOISE,
is_intermediate: true,
is_intermediate,
use_cpu,
seed,
width: !isUsingScaledDimensions
? width
: scaledBoundingBoxDimensions.width,
@ -128,12 +127,12 @@ export const buildCanvasImageToImageGraph = (
[IMAGE_TO_LATENTS]: {
type: 'i2l',
id: IMAGE_TO_LATENTS,
is_intermediate: true,
is_intermediate,
},
[DENOISE_LATENTS]: {
type: 'denoise_latents',
id: DENOISE_LATENTS,
is_intermediate: true,
is_intermediate,
cfg_scale,
scheduler,
steps,
@ -143,7 +142,7 @@ export const buildCanvasImageToImageGraph = (
[CANVAS_OUTPUT]: {
type: 'l2i',
id: CANVAS_OUTPUT,
is_intermediate: !shouldAutoSave,
is_intermediate,
},
},
edges: [
@ -238,7 +237,7 @@ export const buildCanvasImageToImageGraph = (
graph.nodes[IMG2IMG_RESIZE] = {
id: IMG2IMG_RESIZE,
type: 'img_resize',
is_intermediate: true,
is_intermediate,
image: initialImage,
width: scaledBoundingBoxDimensions.width,
height: scaledBoundingBoxDimensions.height,
@ -246,13 +245,13 @@ export const buildCanvasImageToImageGraph = (
graph.nodes[LATENTS_TO_IMAGE] = {
id: LATENTS_TO_IMAGE,
type: 'l2i',
is_intermediate: true,
is_intermediate,
fp32,
};
graph.nodes[CANVAS_OUTPUT] = {
id: CANVAS_OUTPUT,
type: 'img_resize',
is_intermediate: !shouldAutoSave,
is_intermediate,
width: width,
height: height,
};
@ -293,7 +292,7 @@ export const buildCanvasImageToImageGraph = (
graph.nodes[CANVAS_OUTPUT] = {
type: 'l2i',
id: CANVAS_OUTPUT,
is_intermediate: !shouldAutoSave,
is_intermediate,
fp32,
};
@ -322,10 +321,10 @@ export const buildCanvasImageToImageGraph = (
height: !isUsingScaledDimensions
? height
: scaledBoundingBoxDimensions.height,
positive_prompt: '', // set in addDynamicPromptsToGraph
positive_prompt: positivePrompt,
negative_prompt: negativePrompt,
model,
seed: 0, // set in addDynamicPromptsToGraph
seed,
steps,
rand_device: use_cpu ? 'cpu' : 'cuda',
scheduler,
@ -337,17 +336,6 @@ export const buildCanvasImageToImageGraph = (
init_image: initialImage.image_name,
};
graph.edges.push({
source: {
node_id: METADATA_ACCUMULATOR,
field: 'metadata',
},
destination: {
node_id: CANVAS_OUTPUT,
field: 'metadata',
},
});
// Add Seamless To Graph
if (seamlessXAxis || seamlessYAxis) {
addSeamlessToLinearGraph(state, graph, modelLoaderNodeId);
@ -360,12 +348,12 @@ export const buildCanvasImageToImageGraph = (
// optionally add custom VAE
addVAEToGraph(state, graph, modelLoaderNodeId);
// add dynamic prompts - also sets up core iteration and seed
addDynamicPromptsToGraph(state, graph);
// add controlnet, mutating `graph`
addControlNetToLinearGraph(state, graph, DENOISE_LATENTS);
// Add IP Adapter
addIPAdapterToLinearGraph(state, graph, DENOISE_LATENTS);
// NSFW & watermark - must be last thing added to graph
if (state.system.shouldUseNSFWChecker) {
// must add before watermarker!
@ -377,5 +365,7 @@ export const buildCanvasImageToImageGraph = (
addWatermarkerToGraph(state, graph, CANVAS_OUTPUT);
}
addSaveImageNode(state, graph);
return graph;
};

View File

@ -8,10 +8,9 @@ import {
ImageToLatentsInvocation,
MaskEdgeInvocation,
NoiseInvocation,
RandomIntInvocation,
RangeOfSizeInvocation,
} from 'services/api/types';
import { addControlNetToLinearGraph } from './addControlNetToLinearGraph';
import { addIPAdapterToLinearGraph } from './addIPAdapterToLinearGraph';
import { addLoRAsToGraph } from './addLoRAsToGraph';
import { addNSFWCheckerToGraph } from './addNSFWCheckerToGraph';
import { addSeamlessToLinearGraph } from './addSeamlessToLinearGraph';
@ -31,7 +30,6 @@ import {
INPAINT_IMAGE,
INPAINT_IMAGE_RESIZE_DOWN,
INPAINT_IMAGE_RESIZE_UP,
ITERATE,
LATENTS_TO_IMAGE,
MAIN_MODEL_LOADER,
MASK_BLUR,
@ -40,10 +38,9 @@ import {
NEGATIVE_CONDITIONING,
NOISE,
POSITIVE_CONDITIONING,
RANDOM_INT,
RANGE_OF_SIZE,
SEAMLESS,
} from './constants';
import { addSaveImageNode } from './addSaveImageNode';
/**
* Builds the Canvas tab's Inpaint graph.
@ -62,9 +59,7 @@ export const buildCanvasInpaintGraph = (
scheduler,
steps,
img2imgStrength: strength,
iterations,
seed,
shouldRandomizeSeed,
vaePrecision,
shouldUseNoiseSettings,
shouldUseCpuNoise,
@ -87,12 +82,8 @@ export const buildCanvasInpaintGraph = (
const { width, height } = state.canvas.boundingBoxDimensions;
// We may need to set the inpaint width and height to scale the image
const {
scaledBoundingBoxDimensions,
boundingBoxScaleMethod,
shouldAutoSave,
} = state.canvas;
const { scaledBoundingBoxDimensions, boundingBoxScaleMethod } = state.canvas;
const is_intermediate = true;
const fp32 = vaePrecision === 'fp32';
const isUsingScaledDimensions = ['auto', 'manual'].includes(
@ -111,56 +102,57 @@ export const buildCanvasInpaintGraph = (
[modelLoaderNodeId]: {
type: 'main_model_loader',
id: modelLoaderNodeId,
is_intermediate: true,
is_intermediate,
model,
},
[CLIP_SKIP]: {
type: 'clip_skip',
id: CLIP_SKIP,
is_intermediate: true,
is_intermediate,
skipped_layers: clipSkip,
},
[POSITIVE_CONDITIONING]: {
type: 'compel',
id: POSITIVE_CONDITIONING,
is_intermediate: true,
is_intermediate,
prompt: positivePrompt,
},
[NEGATIVE_CONDITIONING]: {
type: 'compel',
id: NEGATIVE_CONDITIONING,
is_intermediate: true,
is_intermediate,
prompt: negativePrompt,
},
[MASK_BLUR]: {
type: 'img_blur',
id: MASK_BLUR,
is_intermediate: true,
is_intermediate,
radius: maskBlur,
blur_type: maskBlurMethod,
},
[INPAINT_IMAGE]: {
type: 'i2l',
id: INPAINT_IMAGE,
is_intermediate: true,
is_intermediate,
fp32,
},
[NOISE]: {
type: 'noise',
id: NOISE,
use_cpu,
is_intermediate: true,
seed,
is_intermediate,
},
[INPAINT_CREATE_MASK]: {
type: 'create_denoise_mask',
id: INPAINT_CREATE_MASK,
is_intermediate: true,
is_intermediate,
fp32,
},
[DENOISE_LATENTS]: {
type: 'denoise_latents',
id: DENOISE_LATENTS,
is_intermediate: true,
is_intermediate,
steps: steps,
cfg_scale: cfg_scale,
scheduler: scheduler,
@ -169,20 +161,21 @@ export const buildCanvasInpaintGraph = (
},
[CANVAS_COHERENCE_NOISE]: {
type: 'noise',
id: NOISE,
id: CANVAS_COHERENCE_NOISE,
use_cpu,
is_intermediate: true,
seed: seed + 1,
is_intermediate,
},
[CANVAS_COHERENCE_NOISE_INCREMENT]: {
type: 'add',
id: CANVAS_COHERENCE_NOISE_INCREMENT,
b: 1,
is_intermediate: true,
is_intermediate,
},
[CANVAS_COHERENCE_DENOISE_LATENTS]: {
type: 'denoise_latents',
id: CANVAS_COHERENCE_DENOISE_LATENTS,
is_intermediate: true,
is_intermediate,
steps: canvasCoherenceSteps,
cfg_scale: cfg_scale,
scheduler: scheduler,
@ -192,29 +185,15 @@ export const buildCanvasInpaintGraph = (
[LATENTS_TO_IMAGE]: {
type: 'l2i',
id: LATENTS_TO_IMAGE,
is_intermediate: true,
is_intermediate,
fp32,
},
[CANVAS_OUTPUT]: {
type: 'color_correct',
id: CANVAS_OUTPUT,
is_intermediate: !shouldAutoSave,
is_intermediate,
reference: canvasInitImage,
},
[RANGE_OF_SIZE]: {
type: 'range_of_size',
id: RANGE_OF_SIZE,
is_intermediate: true,
// seed - must be connected manually
// start: 0,
size: iterations,
step: 1,
},
[ITERATE]: {
type: 'iterate',
id: ITERATE,
is_intermediate: true,
},
},
edges: [
// Connect Model Loader to CLIP Skip and UNet
@ -321,48 +300,7 @@ export const buildCanvasInpaintGraph = (
field: 'denoise_mask',
},
},
// Iterate
{
source: {
node_id: RANGE_OF_SIZE,
field: 'collection',
},
destination: {
node_id: ITERATE,
field: 'collection',
},
},
{
source: {
node_id: ITERATE,
field: 'item',
},
destination: {
node_id: NOISE,
field: 'seed',
},
},
// Canvas Refine
{
source: {
node_id: ITERATE,
field: 'item',
},
destination: {
node_id: CANVAS_COHERENCE_NOISE_INCREMENT,
field: 'a',
},
},
{
source: {
node_id: CANVAS_COHERENCE_NOISE_INCREMENT,
field: 'value',
},
destination: {
node_id: CANVAS_COHERENCE_NOISE,
field: 'seed',
},
},
{
source: {
node_id: modelLoaderNodeId,
@ -436,7 +374,7 @@ export const buildCanvasInpaintGraph = (
graph.nodes[INPAINT_IMAGE_RESIZE_UP] = {
type: 'img_resize',
id: INPAINT_IMAGE_RESIZE_UP,
is_intermediate: true,
is_intermediate,
width: scaledWidth,
height: scaledHeight,
image: canvasInitImage,
@ -444,7 +382,7 @@ export const buildCanvasInpaintGraph = (
graph.nodes[MASK_RESIZE_UP] = {
type: 'img_resize',
id: MASK_RESIZE_UP,
is_intermediate: true,
is_intermediate,
width: scaledWidth,
height: scaledHeight,
image: canvasMaskImage,
@ -452,14 +390,14 @@ export const buildCanvasInpaintGraph = (
graph.nodes[INPAINT_IMAGE_RESIZE_DOWN] = {
type: 'img_resize',
id: INPAINT_IMAGE_RESIZE_DOWN,
is_intermediate: true,
is_intermediate,
width: width,
height: height,
};
graph.nodes[MASK_RESIZE_DOWN] = {
type: 'img_resize',
id: MASK_RESIZE_DOWN,
is_intermediate: true,
is_intermediate,
width: width,
height: height,
};
@ -597,7 +535,7 @@ export const buildCanvasInpaintGraph = (
graph.nodes[CANVAS_COHERENCE_INPAINT_CREATE_MASK] = {
type: 'create_denoise_mask',
id: CANVAS_COHERENCE_INPAINT_CREATE_MASK,
is_intermediate: true,
is_intermediate,
fp32,
};
@ -650,7 +588,7 @@ export const buildCanvasInpaintGraph = (
graph.nodes[CANVAS_COHERENCE_MASK_EDGE] = {
type: 'mask_edge',
id: CANVAS_COHERENCE_MASK_EDGE,
is_intermediate: true,
is_intermediate,
edge_blur: maskBlur,
edge_size: maskBlur * 2,
low_threshold: 100,
@ -701,26 +639,6 @@ export const buildCanvasInpaintGraph = (
});
}
// Handle Seed
if (shouldRandomizeSeed) {
// Random int node to generate the starting seed
const randomIntNode: RandomIntInvocation = {
id: RANDOM_INT,
type: 'rand_int',
};
graph.nodes[RANDOM_INT] = randomIntNode;
// Connect random int to the start of the range of size so the range starts on the random first seed
graph.edges.push({
source: { node_id: RANDOM_INT, field: 'value' },
destination: { node_id: RANGE_OF_SIZE, field: 'start' },
});
} else {
// User specified seed, so set the start of the range of size to the seed
(graph.nodes[RANGE_OF_SIZE] as RangeOfSizeInvocation).start = seed;
}
// Add Seamless To Graph
if (seamlessXAxis || seamlessYAxis) {
addSeamlessToLinearGraph(state, graph, modelLoaderNodeId);
@ -736,6 +654,9 @@ export const buildCanvasInpaintGraph = (
// add controlnet, mutating `graph`
addControlNetToLinearGraph(state, graph, DENOISE_LATENTS);
// Add IP Adapter
addIPAdapterToLinearGraph(state, graph, DENOISE_LATENTS);
// NSFW & watermark - must be last thing added to graph
if (state.system.shouldUseNSFWChecker) {
// must add before watermarker!
@ -747,5 +668,7 @@ export const buildCanvasInpaintGraph = (
addWatermarkerToGraph(state, graph, CANVAS_OUTPUT);
}
addSaveImageNode(state, graph);
return graph;
};

View File

@ -7,12 +7,12 @@ import {
InfillPatchMatchInvocation,
InfillTileInvocation,
NoiseInvocation,
RandomIntInvocation,
RangeOfSizeInvocation,
} from 'services/api/types';
import { addControlNetToLinearGraph } from './addControlNetToLinearGraph';
import { addIPAdapterToLinearGraph } from './addIPAdapterToLinearGraph';
import { addLoRAsToGraph } from './addLoRAsToGraph';
import { addNSFWCheckerToGraph } from './addNSFWCheckerToGraph';
import { addSaveImageNode } from './addSaveImageNode';
import { addSeamlessToLinearGraph } from './addSeamlessToLinearGraph';
import { addVAEToGraph } from './addVAEToGraph';
import { addWatermarkerToGraph } from './addWatermarkerToGraph';
@ -32,7 +32,6 @@ import {
INPAINT_IMAGE_RESIZE_UP,
INPAINT_INFILL,
INPAINT_INFILL_RESIZE_DOWN,
ITERATE,
LATENTS_TO_IMAGE,
MAIN_MODEL_LOADER,
MASK_COMBINE,
@ -42,8 +41,6 @@ import {
NEGATIVE_CONDITIONING,
NOISE,
POSITIVE_CONDITIONING,
RANDOM_INT,
RANGE_OF_SIZE,
SEAMLESS,
} from './constants';
@ -64,9 +61,7 @@ export const buildCanvasOutpaintGraph = (
scheduler,
steps,
img2imgStrength: strength,
iterations,
seed,
shouldRandomizeSeed,
vaePrecision,
shouldUseNoiseSettings,
shouldUseCpuNoise,
@ -91,14 +86,10 @@ export const buildCanvasOutpaintGraph = (
const { width, height } = state.canvas.boundingBoxDimensions;
// We may need to set the inpaint width and height to scale the image
const {
scaledBoundingBoxDimensions,
boundingBoxScaleMethod,
shouldAutoSave,
} = state.canvas;
const { scaledBoundingBoxDimensions, boundingBoxScaleMethod } = state.canvas;
const fp32 = vaePrecision === 'fp32';
const is_intermediate = true;
const isUsingScaledDimensions = ['auto', 'manual'].includes(
boundingBoxScaleMethod
);
@ -115,61 +106,62 @@ export const buildCanvasOutpaintGraph = (
[modelLoaderNodeId]: {
type: 'main_model_loader',
id: modelLoaderNodeId,
is_intermediate: true,
is_intermediate,
model,
},
[CLIP_SKIP]: {
type: 'clip_skip',
id: CLIP_SKIP,
is_intermediate: true,
is_intermediate,
skipped_layers: clipSkip,
},
[POSITIVE_CONDITIONING]: {
type: 'compel',
id: POSITIVE_CONDITIONING,
is_intermediate: true,
is_intermediate,
prompt: positivePrompt,
},
[NEGATIVE_CONDITIONING]: {
type: 'compel',
id: NEGATIVE_CONDITIONING,
is_intermediate: true,
is_intermediate,
prompt: negativePrompt,
},
[MASK_FROM_ALPHA]: {
type: 'tomask',
id: MASK_FROM_ALPHA,
is_intermediate: true,
is_intermediate,
image: canvasInitImage,
},
[MASK_COMBINE]: {
type: 'mask_combine',
id: MASK_COMBINE,
is_intermediate: true,
is_intermediate,
mask2: canvasMaskImage,
},
[INPAINT_IMAGE]: {
type: 'i2l',
id: INPAINT_IMAGE,
is_intermediate: true,
is_intermediate,
fp32,
},
[NOISE]: {
type: 'noise',
id: NOISE,
use_cpu,
is_intermediate: true,
seed,
is_intermediate,
},
[INPAINT_CREATE_MASK]: {
type: 'create_denoise_mask',
id: INPAINT_CREATE_MASK,
is_intermediate: true,
is_intermediate,
fp32,
},
[DENOISE_LATENTS]: {
type: 'denoise_latents',
id: DENOISE_LATENTS,
is_intermediate: true,
is_intermediate,
steps: steps,
cfg_scale: cfg_scale,
scheduler: scheduler,
@ -178,20 +170,21 @@ export const buildCanvasOutpaintGraph = (
},
[CANVAS_COHERENCE_NOISE]: {
type: 'noise',
id: NOISE,
id: CANVAS_COHERENCE_NOISE,
use_cpu,
is_intermediate: true,
seed: seed + 1,
is_intermediate,
},
[CANVAS_COHERENCE_NOISE_INCREMENT]: {
type: 'add',
id: CANVAS_COHERENCE_NOISE_INCREMENT,
b: 1,
is_intermediate: true,
is_intermediate,
},
[CANVAS_COHERENCE_DENOISE_LATENTS]: {
type: 'denoise_latents',
id: CANVAS_COHERENCE_DENOISE_LATENTS,
is_intermediate: true,
is_intermediate,
steps: canvasCoherenceSteps,
cfg_scale: cfg_scale,
scheduler: scheduler,
@ -201,27 +194,13 @@ export const buildCanvasOutpaintGraph = (
[LATENTS_TO_IMAGE]: {
type: 'l2i',
id: LATENTS_TO_IMAGE,
is_intermediate: true,
is_intermediate,
fp32,
},
[CANVAS_OUTPUT]: {
type: 'color_correct',
id: CANVAS_OUTPUT,
is_intermediate: !shouldAutoSave,
},
[RANGE_OF_SIZE]: {
type: 'range_of_size',
id: RANGE_OF_SIZE,
is_intermediate: true,
// seed - must be connected manually
// start: 0,
size: iterations,
step: 1,
},
[ITERATE]: {
type: 'iterate',
id: ITERATE,
is_intermediate: true,
is_intermediate,
},
},
edges: [
@ -351,48 +330,7 @@ export const buildCanvasOutpaintGraph = (
field: 'denoise_mask',
},
},
// Iterate
{
source: {
node_id: RANGE_OF_SIZE,
field: 'collection',
},
destination: {
node_id: ITERATE,
field: 'collection',
},
},
{
source: {
node_id: ITERATE,
field: 'item',
},
destination: {
node_id: NOISE,
field: 'seed',
},
},
// Canvas Refine
{
source: {
node_id: ITERATE,
field: 'item',
},
destination: {
node_id: CANVAS_COHERENCE_NOISE_INCREMENT,
field: 'a',
},
},
{
source: {
node_id: CANVAS_COHERENCE_NOISE_INCREMENT,
field: 'value',
},
destination: {
node_id: CANVAS_COHERENCE_NOISE,
field: 'seed',
},
},
{
source: {
node_id: modelLoaderNodeId,
@ -472,7 +410,7 @@ export const buildCanvasOutpaintGraph = (
graph.nodes[INPAINT_INFILL] = {
type: 'infill_patchmatch',
id: INPAINT_INFILL,
is_intermediate: true,
is_intermediate,
downscale: infillPatchmatchDownscaleSize,
};
}
@ -481,7 +419,7 @@ export const buildCanvasOutpaintGraph = (
graph.nodes[INPAINT_INFILL] = {
type: 'infill_lama',
id: INPAINT_INFILL,
is_intermediate: true,
is_intermediate,
};
}
@ -489,7 +427,7 @@ export const buildCanvasOutpaintGraph = (
graph.nodes[INPAINT_INFILL] = {
type: 'infill_cv2',
id: INPAINT_INFILL,
is_intermediate: true,
is_intermediate,
};
}
@ -497,7 +435,7 @@ export const buildCanvasOutpaintGraph = (
graph.nodes[INPAINT_INFILL] = {
type: 'infill_tile',
id: INPAINT_INFILL,
is_intermediate: true,
is_intermediate,
tile_size: infillTileSize,
};
}
@ -511,7 +449,7 @@ export const buildCanvasOutpaintGraph = (
graph.nodes[INPAINT_IMAGE_RESIZE_UP] = {
type: 'img_resize',
id: INPAINT_IMAGE_RESIZE_UP,
is_intermediate: true,
is_intermediate,
width: scaledWidth,
height: scaledHeight,
image: canvasInitImage,
@ -519,28 +457,28 @@ export const buildCanvasOutpaintGraph = (
graph.nodes[MASK_RESIZE_UP] = {
type: 'img_resize',
id: MASK_RESIZE_UP,
is_intermediate: true,
is_intermediate,
width: scaledWidth,
height: scaledHeight,
};
graph.nodes[INPAINT_IMAGE_RESIZE_DOWN] = {
type: 'img_resize',
id: INPAINT_IMAGE_RESIZE_DOWN,
is_intermediate: true,
is_intermediate,
width: width,
height: height,
};
graph.nodes[INPAINT_INFILL_RESIZE_DOWN] = {
type: 'img_resize',
id: INPAINT_INFILL_RESIZE_DOWN,
is_intermediate: true,
is_intermediate,
width: width,
height: height,
};
graph.nodes[MASK_RESIZE_DOWN] = {
type: 'img_resize',
id: MASK_RESIZE_DOWN,
is_intermediate: true,
is_intermediate,
width: width,
height: height,
};
@ -699,7 +637,7 @@ export const buildCanvasOutpaintGraph = (
graph.nodes[CANVAS_COHERENCE_INPAINT_CREATE_MASK] = {
type: 'create_denoise_mask',
id: CANVAS_COHERENCE_INPAINT_CREATE_MASK,
is_intermediate: true,
is_intermediate,
fp32,
};
@ -746,7 +684,7 @@ export const buildCanvasOutpaintGraph = (
graph.nodes[CANVAS_COHERENCE_MASK_EDGE] = {
type: 'mask_edge',
id: CANVAS_COHERENCE_MASK_EDGE,
is_intermediate: true,
is_intermediate,
edge_blur: maskBlur,
edge_size: maskBlur * 2,
low_threshold: 100,
@ -803,26 +741,6 @@ export const buildCanvasOutpaintGraph = (
});
}
// Handle Seed
if (shouldRandomizeSeed) {
// Random int node to generate the starting seed
const randomIntNode: RandomIntInvocation = {
id: RANDOM_INT,
type: 'rand_int',
};
graph.nodes[RANDOM_INT] = randomIntNode;
// Connect random int to the start of the range of size so the range starts on the random first seed
graph.edges.push({
source: { node_id: RANDOM_INT, field: 'value' },
destination: { node_id: RANGE_OF_SIZE, field: 'start' },
});
} else {
// User specified seed, so set the start of the range of size to the seed
(graph.nodes[RANGE_OF_SIZE] as RangeOfSizeInvocation).start = seed;
}
// Add Seamless To Graph
if (seamlessXAxis || seamlessYAxis) {
addSeamlessToLinearGraph(state, graph, modelLoaderNodeId);
@ -838,6 +756,9 @@ export const buildCanvasOutpaintGraph = (
// add controlnet, mutating `graph`
addControlNetToLinearGraph(state, graph, DENOISE_LATENTS);
// Add IP Adapter
addIPAdapterToLinearGraph(state, graph, DENOISE_LATENTS);
// NSFW & watermark - must be last thing added to graph
if (state.system.shouldUseNSFWChecker) {
// must add before watermarker!
@ -849,5 +770,7 @@ export const buildCanvasOutpaintGraph = (
addWatermarkerToGraph(state, graph, CANVAS_OUTPUT);
}
addSaveImageNode(state, graph);
return graph;
};

View File

@ -4,10 +4,11 @@ import { NonNullableGraph } from 'features/nodes/types/types';
import { initialGenerationState } from 'features/parameters/store/generationSlice';
import { ImageDTO, ImageToLatentsInvocation } from 'services/api/types';
import { addControlNetToLinearGraph } from './addControlNetToLinearGraph';
import { addDynamicPromptsToGraph } from './addDynamicPromptsToGraph';
import { addIPAdapterToLinearGraph } from './addIPAdapterToLinearGraph';
import { addNSFWCheckerToGraph } from './addNSFWCheckerToGraph';
import { addSDXLLoRAsToGraph } from './addSDXLLoRAstoGraph';
import { addSDXLRefinerToGraph } from './addSDXLRefinerToGraph';
import { addSaveImageNode } from './addSaveImageNode';
import { addSeamlessToLinearGraph } from './addSeamlessToLinearGraph';
import { addVAEToGraph } from './addVAEToGraph';
import { addWatermarkerToGraph } from './addWatermarkerToGraph';
@ -26,7 +27,7 @@ import {
SDXL_REFINER_SEAMLESS,
SEAMLESS,
} from './constants';
import { craftSDXLStylePrompt } from './helpers/craftSDXLStylePrompt';
import { buildSDXLStylePrompts } from './helpers/craftSDXLStylePrompt';
/**
* Builds the Canvas tab's Image to Image graph.
@ -42,6 +43,7 @@ export const buildCanvasSDXLImageToImageGraph = (
model,
cfgScale: cfg_scale,
scheduler,
seed,
steps,
vaePrecision,
clipSkip,
@ -55,20 +57,15 @@ export const buildCanvasSDXLImageToImageGraph = (
shouldUseSDXLRefiner,
refinerStart,
sdxlImg2ImgDenoisingStrength: strength,
shouldConcatSDXLStylePrompt,
} = state.sdxl;
// The bounding box determines width and height, not the width and height params
const { width, height } = state.canvas.boundingBoxDimensions;
const {
scaledBoundingBoxDimensions,
boundingBoxScaleMethod,
shouldAutoSave,
} = state.canvas;
const { scaledBoundingBoxDimensions, boundingBoxScaleMethod } = state.canvas;
const fp32 = vaePrecision === 'fp32';
const is_intermediate = true;
const isUsingScaledDimensions = ['auto', 'manual'].includes(
boundingBoxScaleMethod
);
@ -86,8 +83,8 @@ export const buildCanvasSDXLImageToImageGraph = (
: initialGenerationState.shouldUseCpuNoise;
// Construct Style Prompt
const { craftedPositiveStylePrompt, craftedNegativeStylePrompt } =
craftSDXLStylePrompt(state, shouldConcatSDXLStylePrompt);
const { joinedPositiveStylePrompt, joinedNegativeStylePrompt } =
buildSDXLStylePrompts(state);
/**
* The easiest way to build linear graphs is to do it in the node editor, then copy and paste the
@ -111,19 +108,20 @@ export const buildCanvasSDXLImageToImageGraph = (
type: 'sdxl_compel_prompt',
id: POSITIVE_CONDITIONING,
prompt: positivePrompt,
style: craftedPositiveStylePrompt,
style: joinedPositiveStylePrompt,
},
[NEGATIVE_CONDITIONING]: {
type: 'sdxl_compel_prompt',
id: NEGATIVE_CONDITIONING,
prompt: negativePrompt,
style: craftedNegativeStylePrompt,
style: joinedNegativeStylePrompt,
},
[NOISE]: {
type: 'noise',
id: NOISE,
is_intermediate: true,
is_intermediate,
use_cpu,
seed,
width: !isUsingScaledDimensions
? width
: scaledBoundingBoxDimensions.width,
@ -134,13 +132,13 @@ export const buildCanvasSDXLImageToImageGraph = (
[IMAGE_TO_LATENTS]: {
type: 'i2l',
id: IMAGE_TO_LATENTS,
is_intermediate: true,
is_intermediate,
fp32,
},
[SDXL_DENOISE_LATENTS]: {
type: 'denoise_latents',
id: SDXL_DENOISE_LATENTS,
is_intermediate: true,
is_intermediate,
cfg_scale,
scheduler,
steps,
@ -251,7 +249,7 @@ export const buildCanvasSDXLImageToImageGraph = (
graph.nodes[IMG2IMG_RESIZE] = {
id: IMG2IMG_RESIZE,
type: 'img_resize',
is_intermediate: true,
is_intermediate,
image: initialImage,
width: scaledBoundingBoxDimensions.width,
height: scaledBoundingBoxDimensions.height,
@ -259,13 +257,13 @@ export const buildCanvasSDXLImageToImageGraph = (
graph.nodes[LATENTS_TO_IMAGE] = {
id: LATENTS_TO_IMAGE,
type: 'l2i',
is_intermediate: true,
is_intermediate,
fp32,
};
graph.nodes[CANVAS_OUTPUT] = {
id: CANVAS_OUTPUT,
type: 'img_resize',
is_intermediate: !shouldAutoSave,
is_intermediate,
width: width,
height: height,
};
@ -306,7 +304,7 @@ export const buildCanvasSDXLImageToImageGraph = (
graph.nodes[CANVAS_OUTPUT] = {
type: 'l2i',
id: CANVAS_OUTPUT,
is_intermediate: !shouldAutoSave,
is_intermediate,
fp32,
};
@ -335,10 +333,10 @@ export const buildCanvasSDXLImageToImageGraph = (
height: !isUsingScaledDimensions
? height
: scaledBoundingBoxDimensions.height,
positive_prompt: '', // set in addDynamicPromptsToGraph
positive_prompt: positivePrompt,
negative_prompt: negativePrompt,
model,
seed: 0, // set in addDynamicPromptsToGraph
seed,
steps,
rand_device: use_cpu ? 'cpu' : 'cuda',
scheduler,
@ -386,12 +384,12 @@ export const buildCanvasSDXLImageToImageGraph = (
// add LoRA support
addSDXLLoRAsToGraph(state, graph, SDXL_DENOISE_LATENTS, modelLoaderNodeId);
// add dynamic prompts - also sets up core iteration and seed
addDynamicPromptsToGraph(state, graph);
// add controlnet, mutating `graph`
addControlNetToLinearGraph(state, graph, SDXL_DENOISE_LATENTS);
// Add IP Adapter
addIPAdapterToLinearGraph(state, graph, SDXL_DENOISE_LATENTS);
// NSFW & watermark - must be last thing added to graph
if (state.system.shouldUseNSFWChecker) {
// must add before watermarker!
@ -403,5 +401,7 @@ export const buildCanvasSDXLImageToImageGraph = (
addWatermarkerToGraph(state, graph, CANVAS_OUTPUT);
}
addSaveImageNode(state, graph);
return graph;
};

View File

@ -8,13 +8,13 @@ import {
ImageToLatentsInvocation,
MaskEdgeInvocation,
NoiseInvocation,
RandomIntInvocation,
RangeOfSizeInvocation,
} 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 { addSaveImageNode } from './addSaveImageNode';
import { addSeamlessToLinearGraph } from './addSeamlessToLinearGraph';
import { addVAEToGraph } from './addVAEToGraph';
import { addWatermarkerToGraph } from './addWatermarkerToGraph';
@ -29,7 +29,6 @@ import {
INPAINT_IMAGE,
INPAINT_IMAGE_RESIZE_DOWN,
INPAINT_IMAGE_RESIZE_UP,
ITERATE,
LATENTS_TO_IMAGE,
MASK_BLUR,
MASK_RESIZE_DOWN,
@ -37,15 +36,13 @@ import {
NEGATIVE_CONDITIONING,
NOISE,
POSITIVE_CONDITIONING,
RANDOM_INT,
RANGE_OF_SIZE,
SDXL_CANVAS_INPAINT_GRAPH,
SDXL_DENOISE_LATENTS,
SDXL_MODEL_LOADER,
SDXL_REFINER_SEAMLESS,
SEAMLESS,
} from './constants';
import { craftSDXLStylePrompt } from './helpers/craftSDXLStylePrompt';
import { buildSDXLStylePrompts } from './helpers/craftSDXLStylePrompt';
/**
* Builds the Canvas tab's Inpaint graph.
@ -63,9 +60,7 @@ export const buildCanvasSDXLInpaintGraph = (
cfgScale: cfg_scale,
scheduler,
steps,
iterations,
seed,
shouldRandomizeSeed,
vaePrecision,
shouldUseNoiseSettings,
shouldUseCpuNoise,
@ -82,7 +77,6 @@ export const buildCanvasSDXLInpaintGraph = (
sdxlImg2ImgDenoisingStrength: strength,
shouldUseSDXLRefiner,
refinerStart,
shouldConcatSDXLStylePrompt,
} = state.sdxl;
if (!model) {
@ -94,14 +88,10 @@ export const buildCanvasSDXLInpaintGraph = (
const { width, height } = state.canvas.boundingBoxDimensions;
// We may need to set the inpaint width and height to scale the image
const {
scaledBoundingBoxDimensions,
boundingBoxScaleMethod,
shouldAutoSave,
} = state.canvas;
const { scaledBoundingBoxDimensions, boundingBoxScaleMethod } = state.canvas;
const fp32 = vaePrecision === 'fp32';
const is_intermediate = true;
const isUsingScaledDimensions = ['auto', 'manual'].includes(
boundingBoxScaleMethod
);
@ -113,8 +103,8 @@ export const buildCanvasSDXLInpaintGraph = (
: shouldUseCpuNoise;
// Construct Style Prompt
const { craftedPositiveStylePrompt, craftedNegativeStylePrompt } =
craftSDXLStylePrompt(state, shouldConcatSDXLStylePrompt);
const { joinedPositiveStylePrompt, joinedNegativeStylePrompt } =
buildSDXLStylePrompts(state);
const graph: NonNullableGraph = {
id: SDXL_CANVAS_INPAINT_GRAPH,
@ -128,43 +118,44 @@ export const buildCanvasSDXLInpaintGraph = (
type: 'sdxl_compel_prompt',
id: POSITIVE_CONDITIONING,
prompt: positivePrompt,
style: craftedPositiveStylePrompt,
style: joinedPositiveStylePrompt,
},
[NEGATIVE_CONDITIONING]: {
type: 'sdxl_compel_prompt',
id: NEGATIVE_CONDITIONING,
prompt: negativePrompt,
style: craftedNegativeStylePrompt,
style: joinedNegativeStylePrompt,
},
[MASK_BLUR]: {
type: 'img_blur',
id: MASK_BLUR,
is_intermediate: true,
is_intermediate,
radius: maskBlur,
blur_type: maskBlurMethod,
},
[INPAINT_IMAGE]: {
type: 'i2l',
id: INPAINT_IMAGE,
is_intermediate: true,
is_intermediate,
fp32,
},
[NOISE]: {
type: 'noise',
id: NOISE,
use_cpu,
is_intermediate: true,
seed,
is_intermediate,
},
[INPAINT_CREATE_MASK]: {
type: 'create_denoise_mask',
id: INPAINT_CREATE_MASK,
is_intermediate: true,
is_intermediate,
fp32,
},
[SDXL_DENOISE_LATENTS]: {
type: 'denoise_latents',
id: SDXL_DENOISE_LATENTS,
is_intermediate: true,
is_intermediate,
steps: steps,
cfg_scale: cfg_scale,
scheduler: scheduler,
@ -175,20 +166,21 @@ export const buildCanvasSDXLInpaintGraph = (
},
[CANVAS_COHERENCE_NOISE]: {
type: 'noise',
id: NOISE,
id: CANVAS_COHERENCE_NOISE,
use_cpu,
is_intermediate: true,
seed: seed + 1,
is_intermediate,
},
[CANVAS_COHERENCE_NOISE_INCREMENT]: {
type: 'add',
id: CANVAS_COHERENCE_NOISE_INCREMENT,
b: 1,
is_intermediate: true,
is_intermediate,
},
[CANVAS_COHERENCE_DENOISE_LATENTS]: {
type: 'denoise_latents',
id: CANVAS_COHERENCE_DENOISE_LATENTS,
is_intermediate: true,
is_intermediate,
steps: canvasCoherenceSteps,
cfg_scale: cfg_scale,
scheduler: scheduler,
@ -198,29 +190,15 @@ export const buildCanvasSDXLInpaintGraph = (
[LATENTS_TO_IMAGE]: {
type: 'l2i',
id: LATENTS_TO_IMAGE,
is_intermediate: true,
is_intermediate,
fp32,
},
[CANVAS_OUTPUT]: {
type: 'color_correct',
id: CANVAS_OUTPUT,
is_intermediate: !shouldAutoSave,
is_intermediate,
reference: canvasInitImage,
},
[RANGE_OF_SIZE]: {
type: 'range_of_size',
id: RANGE_OF_SIZE,
is_intermediate: true,
// seed - must be connected manually
// start: 0,
size: iterations,
step: 1,
},
[ITERATE]: {
type: 'iterate',
id: ITERATE,
is_intermediate: true,
},
},
edges: [
// Connect Model Loader to UNet and CLIP
@ -336,48 +314,7 @@ export const buildCanvasSDXLInpaintGraph = (
field: 'denoise_mask',
},
},
// Iterate
{
source: {
node_id: RANGE_OF_SIZE,
field: 'collection',
},
destination: {
node_id: ITERATE,
field: 'collection',
},
},
{
source: {
node_id: ITERATE,
field: 'item',
},
destination: {
node_id: NOISE,
field: 'seed',
},
},
// Canvas Refine
{
source: {
node_id: ITERATE,
field: 'item',
},
destination: {
node_id: CANVAS_COHERENCE_NOISE_INCREMENT,
field: 'a',
},
},
{
source: {
node_id: CANVAS_COHERENCE_NOISE_INCREMENT,
field: 'value',
},
destination: {
node_id: CANVAS_COHERENCE_NOISE,
field: 'seed',
},
},
{
source: {
node_id: modelLoaderNodeId,
@ -451,7 +388,7 @@ export const buildCanvasSDXLInpaintGraph = (
graph.nodes[INPAINT_IMAGE_RESIZE_UP] = {
type: 'img_resize',
id: INPAINT_IMAGE_RESIZE_UP,
is_intermediate: true,
is_intermediate,
width: scaledWidth,
height: scaledHeight,
image: canvasInitImage,
@ -459,7 +396,7 @@ export const buildCanvasSDXLInpaintGraph = (
graph.nodes[MASK_RESIZE_UP] = {
type: 'img_resize',
id: MASK_RESIZE_UP,
is_intermediate: true,
is_intermediate,
width: scaledWidth,
height: scaledHeight,
image: canvasMaskImage,
@ -467,14 +404,14 @@ export const buildCanvasSDXLInpaintGraph = (
graph.nodes[INPAINT_IMAGE_RESIZE_DOWN] = {
type: 'img_resize',
id: INPAINT_IMAGE_RESIZE_DOWN,
is_intermediate: true,
is_intermediate,
width: width,
height: height,
};
graph.nodes[MASK_RESIZE_DOWN] = {
type: 'img_resize',
id: MASK_RESIZE_DOWN,
is_intermediate: true,
is_intermediate,
width: width,
height: height,
};
@ -612,7 +549,7 @@ export const buildCanvasSDXLInpaintGraph = (
graph.nodes[CANVAS_COHERENCE_INPAINT_CREATE_MASK] = {
type: 'create_denoise_mask',
id: CANVAS_COHERENCE_INPAINT_CREATE_MASK,
is_intermediate: true,
is_intermediate,
fp32,
};
@ -665,7 +602,7 @@ export const buildCanvasSDXLInpaintGraph = (
graph.nodes[CANVAS_COHERENCE_MASK_EDGE] = {
type: 'mask_edge',
id: CANVAS_COHERENCE_MASK_EDGE,
is_intermediate: true,
is_intermediate,
edge_blur: maskBlur,
edge_size: maskBlur * 2,
low_threshold: 100,
@ -716,26 +653,6 @@ export const buildCanvasSDXLInpaintGraph = (
});
}
// Handle Seed
if (shouldRandomizeSeed) {
// Random int node to generate the starting seed
const randomIntNode: RandomIntInvocation = {
id: RANDOM_INT,
type: 'rand_int',
};
graph.nodes[RANDOM_INT] = randomIntNode;
// Connect random int to the start of the range of size so the range starts on the random first seed
graph.edges.push({
source: { node_id: RANDOM_INT, field: 'value' },
destination: { node_id: RANGE_OF_SIZE, field: 'start' },
});
} else {
// User specified seed, so set the start of the range of size to the seed
(graph.nodes[RANGE_OF_SIZE] as RangeOfSizeInvocation).start = seed;
}
// Add Seamless To Graph
if (seamlessXAxis || seamlessYAxis) {
addSeamlessToLinearGraph(state, graph, modelLoaderNodeId);
@ -765,6 +682,9 @@ export const buildCanvasSDXLInpaintGraph = (
// add controlnet, mutating `graph`
addControlNetToLinearGraph(state, graph, SDXL_DENOISE_LATENTS);
// Add IP Adapter
addIPAdapterToLinearGraph(state, graph, SDXL_DENOISE_LATENTS);
// NSFW & watermark - must be last thing added to graph
if (state.system.shouldUseNSFWChecker) {
// must add before watermarker!
@ -776,5 +696,7 @@ export const buildCanvasSDXLInpaintGraph = (
addWatermarkerToGraph(state, graph, CANVAS_OUTPUT);
}
addSaveImageNode(state, graph);
return graph;
};

View File

@ -7,13 +7,13 @@ import {
InfillPatchMatchInvocation,
InfillTileInvocation,
NoiseInvocation,
RandomIntInvocation,
RangeOfSizeInvocation,
} 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 { addSaveImageNode } from './addSaveImageNode';
import { addSeamlessToLinearGraph } from './addSeamlessToLinearGraph';
import { addVAEToGraph } from './addVAEToGraph';
import { addWatermarkerToGraph } from './addWatermarkerToGraph';
@ -30,7 +30,6 @@ import {
INPAINT_IMAGE_RESIZE_UP,
INPAINT_INFILL,
INPAINT_INFILL_RESIZE_DOWN,
ITERATE,
LATENTS_TO_IMAGE,
MASK_COMBINE,
MASK_FROM_ALPHA,
@ -39,15 +38,13 @@ import {
NEGATIVE_CONDITIONING,
NOISE,
POSITIVE_CONDITIONING,
RANDOM_INT,
RANGE_OF_SIZE,
SDXL_CANVAS_OUTPAINT_GRAPH,
SDXL_DENOISE_LATENTS,
SDXL_MODEL_LOADER,
SDXL_REFINER_SEAMLESS,
SEAMLESS,
} from './constants';
import { craftSDXLStylePrompt } from './helpers/craftSDXLStylePrompt';
import { buildSDXLStylePrompts } from './helpers/craftSDXLStylePrompt';
/**
* Builds the Canvas tab's Outpaint graph.
@ -65,9 +62,7 @@ export const buildCanvasSDXLOutpaintGraph = (
cfgScale: cfg_scale,
scheduler,
steps,
iterations,
seed,
shouldRandomizeSeed,
vaePrecision,
shouldUseNoiseSettings,
shouldUseCpuNoise,
@ -86,7 +81,6 @@ export const buildCanvasSDXLOutpaintGraph = (
sdxlImg2ImgDenoisingStrength: strength,
shouldUseSDXLRefiner,
refinerStart,
shouldConcatSDXLStylePrompt,
} = state.sdxl;
if (!model) {
@ -98,14 +92,10 @@ export const buildCanvasSDXLOutpaintGraph = (
const { width, height } = state.canvas.boundingBoxDimensions;
// We may need to set the inpaint width and height to scale the image
const {
scaledBoundingBoxDimensions,
boundingBoxScaleMethod,
shouldAutoSave,
} = state.canvas;
const { scaledBoundingBoxDimensions, boundingBoxScaleMethod } = state.canvas;
const fp32 = vaePrecision === 'fp32';
const is_intermediate = true;
const isUsingScaledDimensions = ['auto', 'manual'].includes(
boundingBoxScaleMethod
);
@ -117,8 +107,8 @@ export const buildCanvasSDXLOutpaintGraph = (
: shouldUseCpuNoise;
// Construct Style Prompt
const { craftedPositiveStylePrompt, craftedNegativeStylePrompt } =
craftSDXLStylePrompt(state, shouldConcatSDXLStylePrompt);
const { joinedPositiveStylePrompt, joinedNegativeStylePrompt } =
buildSDXLStylePrompts(state);
const graph: NonNullableGraph = {
id: SDXL_CANVAS_OUTPAINT_GRAPH,
@ -132,48 +122,49 @@ export const buildCanvasSDXLOutpaintGraph = (
type: 'sdxl_compel_prompt',
id: POSITIVE_CONDITIONING,
prompt: positivePrompt,
style: craftedPositiveStylePrompt,
style: joinedPositiveStylePrompt,
},
[NEGATIVE_CONDITIONING]: {
type: 'sdxl_compel_prompt',
id: NEGATIVE_CONDITIONING,
prompt: negativePrompt,
style: craftedNegativeStylePrompt,
style: joinedNegativeStylePrompt,
},
[MASK_FROM_ALPHA]: {
type: 'tomask',
id: MASK_FROM_ALPHA,
is_intermediate: true,
is_intermediate,
image: canvasInitImage,
},
[MASK_COMBINE]: {
type: 'mask_combine',
id: MASK_COMBINE,
is_intermediate: true,
is_intermediate,
mask2: canvasMaskImage,
},
[INPAINT_IMAGE]: {
type: 'i2l',
id: INPAINT_IMAGE,
is_intermediate: true,
is_intermediate,
fp32,
},
[NOISE]: {
type: 'noise',
id: NOISE,
use_cpu,
is_intermediate: true,
seed,
is_intermediate,
},
[INPAINT_CREATE_MASK]: {
type: 'create_denoise_mask',
id: INPAINT_CREATE_MASK,
is_intermediate: true,
is_intermediate,
fp32,
},
[SDXL_DENOISE_LATENTS]: {
type: 'denoise_latents',
id: SDXL_DENOISE_LATENTS,
is_intermediate: true,
is_intermediate,
steps: steps,
cfg_scale: cfg_scale,
scheduler: scheduler,
@ -184,20 +175,21 @@ export const buildCanvasSDXLOutpaintGraph = (
},
[CANVAS_COHERENCE_NOISE]: {
type: 'noise',
id: NOISE,
id: CANVAS_COHERENCE_NOISE,
use_cpu,
is_intermediate: true,
seed: seed + 1,
is_intermediate,
},
[CANVAS_COHERENCE_NOISE_INCREMENT]: {
type: 'add',
id: CANVAS_COHERENCE_NOISE_INCREMENT,
b: 1,
is_intermediate: true,
is_intermediate,
},
[CANVAS_COHERENCE_DENOISE_LATENTS]: {
type: 'denoise_latents',
id: CANVAS_COHERENCE_DENOISE_LATENTS,
is_intermediate: true,
is_intermediate,
steps: canvasCoherenceSteps,
cfg_scale: cfg_scale,
scheduler: scheduler,
@ -207,27 +199,13 @@ export const buildCanvasSDXLOutpaintGraph = (
[LATENTS_TO_IMAGE]: {
type: 'l2i',
id: LATENTS_TO_IMAGE,
is_intermediate: true,
is_intermediate,
fp32,
},
[CANVAS_OUTPUT]: {
type: 'color_correct',
id: CANVAS_OUTPUT,
is_intermediate: !shouldAutoSave,
},
[RANGE_OF_SIZE]: {
type: 'range_of_size',
id: RANGE_OF_SIZE,
is_intermediate: true,
// seed - must be connected manually
// start: 0,
size: iterations,
step: 1,
},
[ITERATE]: {
type: 'iterate',
id: ITERATE,
is_intermediate: true,
is_intermediate,
},
},
edges: [
@ -366,48 +344,7 @@ export const buildCanvasSDXLOutpaintGraph = (
field: 'denoise_mask',
},
},
// Iterate
{
source: {
node_id: RANGE_OF_SIZE,
field: 'collection',
},
destination: {
node_id: ITERATE,
field: 'collection',
},
},
{
source: {
node_id: ITERATE,
field: 'item',
},
destination: {
node_id: NOISE,
field: 'seed',
},
},
// Canvas Refine
{
source: {
node_id: ITERATE,
field: 'item',
},
destination: {
node_id: CANVAS_COHERENCE_NOISE_INCREMENT,
field: 'a',
},
},
{
source: {
node_id: CANVAS_COHERENCE_NOISE_INCREMENT,
field: 'value',
},
destination: {
node_id: CANVAS_COHERENCE_NOISE,
field: 'seed',
},
},
{
source: {
node_id: modelLoaderNodeId,
@ -487,7 +424,7 @@ export const buildCanvasSDXLOutpaintGraph = (
graph.nodes[INPAINT_INFILL] = {
type: 'infill_patchmatch',
id: INPAINT_INFILL,
is_intermediate: true,
is_intermediate,
downscale: infillPatchmatchDownscaleSize,
};
}
@ -496,7 +433,7 @@ export const buildCanvasSDXLOutpaintGraph = (
graph.nodes[INPAINT_INFILL] = {
type: 'infill_lama',
id: INPAINT_INFILL,
is_intermediate: true,
is_intermediate,
};
}
@ -504,7 +441,7 @@ export const buildCanvasSDXLOutpaintGraph = (
graph.nodes[INPAINT_INFILL] = {
type: 'infill_cv2',
id: INPAINT_INFILL,
is_intermediate: true,
is_intermediate,
};
}
@ -512,7 +449,7 @@ export const buildCanvasSDXLOutpaintGraph = (
graph.nodes[INPAINT_INFILL] = {
type: 'infill_tile',
id: INPAINT_INFILL,
is_intermediate: true,
is_intermediate,
tile_size: infillTileSize,
};
}
@ -526,7 +463,7 @@ export const buildCanvasSDXLOutpaintGraph = (
graph.nodes[INPAINT_IMAGE_RESIZE_UP] = {
type: 'img_resize',
id: INPAINT_IMAGE_RESIZE_UP,
is_intermediate: true,
is_intermediate,
width: scaledWidth,
height: scaledHeight,
image: canvasInitImage,
@ -534,28 +471,28 @@ export const buildCanvasSDXLOutpaintGraph = (
graph.nodes[MASK_RESIZE_UP] = {
type: 'img_resize',
id: MASK_RESIZE_UP,
is_intermediate: true,
is_intermediate,
width: scaledWidth,
height: scaledHeight,
};
graph.nodes[INPAINT_IMAGE_RESIZE_DOWN] = {
type: 'img_resize',
id: INPAINT_IMAGE_RESIZE_DOWN,
is_intermediate: true,
is_intermediate,
width: width,
height: height,
};
graph.nodes[INPAINT_INFILL_RESIZE_DOWN] = {
type: 'img_resize',
id: INPAINT_INFILL_RESIZE_DOWN,
is_intermediate: true,
is_intermediate,
width: width,
height: height,
};
graph.nodes[MASK_RESIZE_DOWN] = {
type: 'img_resize',
id: MASK_RESIZE_DOWN,
is_intermediate: true,
is_intermediate,
width: width,
height: height,
};
@ -715,7 +652,7 @@ export const buildCanvasSDXLOutpaintGraph = (
graph.nodes[CANVAS_COHERENCE_INPAINT_CREATE_MASK] = {
type: 'create_denoise_mask',
id: CANVAS_COHERENCE_INPAINT_CREATE_MASK,
is_intermediate: true,
is_intermediate,
fp32,
};
@ -762,7 +699,7 @@ export const buildCanvasSDXLOutpaintGraph = (
graph.nodes[CANVAS_COHERENCE_MASK_EDGE] = {
type: 'mask_edge',
id: CANVAS_COHERENCE_MASK_EDGE,
is_intermediate: true,
is_intermediate,
edge_blur: maskBlur,
edge_size: maskBlur * 2,
low_threshold: 100,
@ -819,26 +756,6 @@ export const buildCanvasSDXLOutpaintGraph = (
});
}
// Handle Seed
if (shouldRandomizeSeed) {
// Random int node to generate the starting seed
const randomIntNode: RandomIntInvocation = {
id: RANDOM_INT,
type: 'rand_int',
};
graph.nodes[RANDOM_INT] = randomIntNode;
// Connect random int to the start of the range of size so the range starts on the random first seed
graph.edges.push({
source: { node_id: RANDOM_INT, field: 'value' },
destination: { node_id: RANGE_OF_SIZE, field: 'start' },
});
} else {
// User specified seed, so set the start of the range of size to the seed
(graph.nodes[RANGE_OF_SIZE] as RangeOfSizeInvocation).start = seed;
}
// Add Seamless To Graph
if (seamlessXAxis || seamlessYAxis) {
addSeamlessToLinearGraph(state, graph, modelLoaderNodeId);
@ -868,6 +785,9 @@ export const buildCanvasSDXLOutpaintGraph = (
// add controlnet, mutating `graph`
addControlNetToLinearGraph(state, graph, SDXL_DENOISE_LATENTS);
// Add IP Adapter
addIPAdapterToLinearGraph(state, graph, SDXL_DENOISE_LATENTS);
// NSFW & watermark - must be last thing added to graph
if (state.system.shouldUseNSFWChecker) {
// must add before watermarker!
@ -879,5 +799,7 @@ export const buildCanvasSDXLOutpaintGraph = (
addWatermarkerToGraph(state, graph, CANVAS_OUTPUT);
}
addSaveImageNode(state, graph);
return graph;
};

View File

@ -7,10 +7,11 @@ import {
ONNXTextToLatentsInvocation,
} from 'services/api/types';
import { addControlNetToLinearGraph } from './addControlNetToLinearGraph';
import { addDynamicPromptsToGraph } from './addDynamicPromptsToGraph';
import { addIPAdapterToLinearGraph } from './addIPAdapterToLinearGraph';
import { addNSFWCheckerToGraph } from './addNSFWCheckerToGraph';
import { addSDXLLoRAsToGraph } from './addSDXLLoRAstoGraph';
import { addSDXLRefinerToGraph } from './addSDXLRefinerToGraph';
import { addSaveImageNode } from './addSaveImageNode';
import { addSeamlessToLinearGraph } from './addSeamlessToLinearGraph';
import { addVAEToGraph } from './addVAEToGraph';
import { addWatermarkerToGraph } from './addWatermarkerToGraph';
@ -28,7 +29,7 @@ import {
SDXL_REFINER_SEAMLESS,
SEAMLESS,
} from './constants';
import { craftSDXLStylePrompt } from './helpers/craftSDXLStylePrompt';
import { buildSDXLStylePrompts } from './helpers/craftSDXLStylePrompt';
/**
* Builds the Canvas tab's Text to Image graph.
@ -43,6 +44,7 @@ export const buildCanvasSDXLTextToImageGraph = (
model,
cfgScale: cfg_scale,
scheduler,
seed,
steps,
vaePrecision,
clipSkip,
@ -55,20 +57,15 @@ export const buildCanvasSDXLTextToImageGraph = (
// The bounding box determines width and height, not the width and height params
const { width, height } = state.canvas.boundingBoxDimensions;
const {
scaledBoundingBoxDimensions,
boundingBoxScaleMethod,
shouldAutoSave,
} = state.canvas;
const { scaledBoundingBoxDimensions, boundingBoxScaleMethod } = state.canvas;
const fp32 = vaePrecision === 'fp32';
const is_intermediate = true;
const isUsingScaledDimensions = ['auto', 'manual'].includes(
boundingBoxScaleMethod
);
const { shouldUseSDXLRefiner, refinerStart, shouldConcatSDXLStylePrompt } =
state.sdxl;
const { shouldUseSDXLRefiner, refinerStart } = state.sdxl;
if (!model) {
log.error('No model found in state');
@ -94,7 +91,7 @@ export const buildCanvasSDXLTextToImageGraph = (
? {
type: 't2l_onnx',
id: SDXL_DENOISE_LATENTS,
is_intermediate: true,
is_intermediate,
cfg_scale,
scheduler,
steps,
@ -102,7 +99,7 @@ export const buildCanvasSDXLTextToImageGraph = (
: {
type: 'denoise_latents',
id: SDXL_DENOISE_LATENTS,
is_intermediate: true,
is_intermediate,
cfg_scale,
scheduler,
steps,
@ -111,8 +108,8 @@ export const buildCanvasSDXLTextToImageGraph = (
};
// Construct Style Prompt
const { craftedPositiveStylePrompt, craftedNegativeStylePrompt } =
craftSDXLStylePrompt(state, shouldConcatSDXLStylePrompt);
const { joinedPositiveStylePrompt, joinedNegativeStylePrompt } =
buildSDXLStylePrompts(state);
/**
* The easiest way to build linear graphs is to do it in the node editor, then copy and paste the
@ -131,27 +128,28 @@ export const buildCanvasSDXLTextToImageGraph = (
[modelLoaderNodeId]: {
type: modelLoaderNodeType,
id: modelLoaderNodeId,
is_intermediate: true,
is_intermediate,
model,
},
[POSITIVE_CONDITIONING]: {
type: isUsingOnnxModel ? 'prompt_onnx' : 'sdxl_compel_prompt',
id: POSITIVE_CONDITIONING,
is_intermediate: true,
is_intermediate,
prompt: positivePrompt,
style: craftedPositiveStylePrompt,
style: joinedPositiveStylePrompt,
},
[NEGATIVE_CONDITIONING]: {
type: isUsingOnnxModel ? 'prompt_onnx' : 'sdxl_compel_prompt',
id: NEGATIVE_CONDITIONING,
is_intermediate: true,
is_intermediate,
prompt: negativePrompt,
style: craftedNegativeStylePrompt,
style: joinedNegativeStylePrompt,
},
[NOISE]: {
type: 'noise',
id: NOISE,
is_intermediate: true,
is_intermediate,
seed,
width: !isUsingScaledDimensions
? width
: scaledBoundingBoxDimensions.width,
@ -253,14 +251,14 @@ export const buildCanvasSDXLTextToImageGraph = (
graph.nodes[LATENTS_TO_IMAGE] = {
id: LATENTS_TO_IMAGE,
type: isUsingOnnxModel ? 'l2i_onnx' : 'l2i',
is_intermediate: true,
is_intermediate,
fp32,
};
graph.nodes[CANVAS_OUTPUT] = {
id: CANVAS_OUTPUT,
type: 'img_resize',
is_intermediate: !shouldAutoSave,
is_intermediate,
width: width,
height: height,
};
@ -291,7 +289,7 @@ export const buildCanvasSDXLTextToImageGraph = (
graph.nodes[CANVAS_OUTPUT] = {
type: isUsingOnnxModel ? 'l2i_onnx' : 'l2i',
id: CANVAS_OUTPUT,
is_intermediate: !shouldAutoSave,
is_intermediate,
fp32,
};
@ -317,10 +315,10 @@ export const buildCanvasSDXLTextToImageGraph = (
height: !isUsingScaledDimensions
? height
: scaledBoundingBoxDimensions.height,
positive_prompt: '', // set in addDynamicPromptsToGraph
positive_prompt: positivePrompt,
negative_prompt: negativePrompt,
model,
seed: 0, // set in addDynamicPromptsToGraph
seed,
steps,
rand_device: use_cpu ? 'cpu' : 'cuda',
scheduler,
@ -366,12 +364,12 @@ export const buildCanvasSDXLTextToImageGraph = (
// optionally add custom VAE
addVAEToGraph(state, graph, modelLoaderNodeId);
// add dynamic prompts - also sets up core iteration and seed
addDynamicPromptsToGraph(state, graph);
// add controlnet, mutating `graph`
addControlNetToLinearGraph(state, graph, SDXL_DENOISE_LATENTS);
// Add IP Adapter
addIPAdapterToLinearGraph(state, graph, SDXL_DENOISE_LATENTS);
// NSFW & watermark - must be last thing added to graph
if (state.system.shouldUseNSFWChecker) {
// must add before watermarker!
@ -383,5 +381,7 @@ export const buildCanvasSDXLTextToImageGraph = (
addWatermarkerToGraph(state, graph, CANVAS_OUTPUT);
}
addSaveImageNode(state, graph);
return graph;
};

View File

@ -7,9 +7,10 @@ import {
ONNXTextToLatentsInvocation,
} from 'services/api/types';
import { addControlNetToLinearGraph } from './addControlNetToLinearGraph';
import { addDynamicPromptsToGraph } from './addDynamicPromptsToGraph';
import { addIPAdapterToLinearGraph } from './addIPAdapterToLinearGraph';
import { addLoRAsToGraph } from './addLoRAsToGraph';
import { addNSFWCheckerToGraph } from './addNSFWCheckerToGraph';
import { addSaveImageNode } from './addSaveImageNode';
import { addSeamlessToLinearGraph } from './addSeamlessToLinearGraph';
import { addVAEToGraph } from './addVAEToGraph';
import { addWatermarkerToGraph } from './addWatermarkerToGraph';
@ -41,6 +42,7 @@ export const buildCanvasTextToImageGraph = (
model,
cfgScale: cfg_scale,
scheduler,
seed,
steps,
vaePrecision,
clipSkip,
@ -53,14 +55,10 @@ export const buildCanvasTextToImageGraph = (
// The bounding box determines width and height, not the width and height params
const { width, height } = state.canvas.boundingBoxDimensions;
const {
scaledBoundingBoxDimensions,
boundingBoxScaleMethod,
shouldAutoSave,
} = state.canvas;
const { scaledBoundingBoxDimensions, boundingBoxScaleMethod } = state.canvas;
const fp32 = vaePrecision === 'fp32';
const is_intermediate = true;
const isUsingScaledDimensions = ['auto', 'manual'].includes(
boundingBoxScaleMethod
);
@ -89,7 +87,7 @@ export const buildCanvasTextToImageGraph = (
? {
type: 't2l_onnx',
id: DENOISE_LATENTS,
is_intermediate: true,
is_intermediate,
cfg_scale,
scheduler,
steps,
@ -97,7 +95,7 @@ export const buildCanvasTextToImageGraph = (
: {
type: 'denoise_latents',
id: DENOISE_LATENTS,
is_intermediate: true,
is_intermediate,
cfg_scale,
scheduler,
steps,
@ -122,31 +120,32 @@ export const buildCanvasTextToImageGraph = (
[modelLoaderNodeId]: {
type: modelLoaderNodeType,
id: modelLoaderNodeId,
is_intermediate: true,
is_intermediate,
model,
},
[CLIP_SKIP]: {
type: 'clip_skip',
id: CLIP_SKIP,
is_intermediate: true,
is_intermediate,
skipped_layers: clipSkip,
},
[POSITIVE_CONDITIONING]: {
type: isUsingOnnxModel ? 'prompt_onnx' : 'compel',
id: POSITIVE_CONDITIONING,
is_intermediate: true,
is_intermediate,
prompt: positivePrompt,
},
[NEGATIVE_CONDITIONING]: {
type: isUsingOnnxModel ? 'prompt_onnx' : 'compel',
id: NEGATIVE_CONDITIONING,
is_intermediate: true,
is_intermediate,
prompt: negativePrompt,
},
[NOISE]: {
type: 'noise',
id: NOISE,
is_intermediate: true,
is_intermediate,
seed,
width: !isUsingScaledDimensions
? width
: scaledBoundingBoxDimensions.width,
@ -239,14 +238,14 @@ export const buildCanvasTextToImageGraph = (
graph.nodes[LATENTS_TO_IMAGE] = {
id: LATENTS_TO_IMAGE,
type: isUsingOnnxModel ? 'l2i_onnx' : 'l2i',
is_intermediate: true,
is_intermediate,
fp32,
};
graph.nodes[CANVAS_OUTPUT] = {
id: CANVAS_OUTPUT,
type: 'img_resize',
is_intermediate: !shouldAutoSave,
is_intermediate,
width: width,
height: height,
};
@ -277,7 +276,7 @@ export const buildCanvasTextToImageGraph = (
graph.nodes[CANVAS_OUTPUT] = {
type: isUsingOnnxModel ? 'l2i_onnx' : 'l2i',
id: CANVAS_OUTPUT,
is_intermediate: !shouldAutoSave,
is_intermediate,
fp32,
};
@ -303,10 +302,10 @@ export const buildCanvasTextToImageGraph = (
height: !isUsingScaledDimensions
? height
: scaledBoundingBoxDimensions.height,
positive_prompt: '', // set in addDynamicPromptsToGraph
positive_prompt: positivePrompt,
negative_prompt: negativePrompt,
model,
seed: 0, // set in addDynamicPromptsToGraph
seed,
steps,
rand_device: use_cpu ? 'cpu' : 'cuda',
scheduler,
@ -339,12 +338,12 @@ export const buildCanvasTextToImageGraph = (
// add LoRA support
addLoRAsToGraph(state, graph, DENOISE_LATENTS, modelLoaderNodeId);
// add dynamic prompts - also sets up core iteration and seed
addDynamicPromptsToGraph(state, graph);
// add controlnet, mutating `graph`
addControlNetToLinearGraph(state, graph, DENOISE_LATENTS);
// Add IP Adapter
addIPAdapterToLinearGraph(state, graph, DENOISE_LATENTS);
// NSFW & watermark - must be last thing added to graph
if (state.system.shouldUseNSFWChecker) {
// must add before watermarker!
@ -356,5 +355,7 @@ export const buildCanvasTextToImageGraph = (
addWatermarkerToGraph(state, graph, CANVAS_OUTPUT);
}
addSaveImageNode(state, graph);
return graph;
};

View File

@ -0,0 +1,185 @@
import { NUMPY_RAND_MAX } from 'app/constants';
import { RootState } from 'app/store/store';
import { generateSeeds } from 'common/util/generateSeeds';
import { NonNullableGraph } from 'features/nodes/types/types';
import { range, unset } from 'lodash-es';
import { components } from 'services/api/schema';
import { Batch, BatchConfig } from 'services/api/types';
import {
CANVAS_COHERENCE_NOISE,
METADATA_ACCUMULATOR,
NOISE,
POSITIVE_CONDITIONING,
} from './constants';
export const prepareLinearUIBatch = (
state: RootState,
graph: NonNullableGraph,
prepend: boolean
): BatchConfig => {
const { iterations, model, shouldRandomizeSeed, seed } = state.generation;
const { shouldConcatSDXLStylePrompt, positiveStylePrompt } = state.sdxl;
const { prompts, seedBehaviour } = state.dynamicPrompts;
const data: Batch['data'] = [];
if (prompts.length === 1) {
unset(graph.nodes[METADATA_ACCUMULATOR], 'seed');
const seeds = generateSeeds({
count: iterations,
start: shouldRandomizeSeed ? undefined : seed,
});
const zipped: components['schemas']['BatchDatum'][] = [];
if (graph.nodes[NOISE]) {
zipped.push({
node_path: NOISE,
field_name: 'seed',
items: seeds,
});
}
if (graph.nodes[METADATA_ACCUMULATOR]) {
zipped.push({
node_path: METADATA_ACCUMULATOR,
field_name: 'seed',
items: seeds,
});
}
if (graph.nodes[CANVAS_COHERENCE_NOISE]) {
zipped.push({
node_path: CANVAS_COHERENCE_NOISE,
field_name: 'seed',
items: seeds.map((seed) => (seed + 1) % NUMPY_RAND_MAX),
});
}
data.push(zipped);
} else {
// prompts.length > 1 aka dynamic prompts
const firstBatchDatumList: components['schemas']['BatchDatum'][] = [];
const secondBatchDatumList: components['schemas']['BatchDatum'][] = [];
// add seeds first to ensure the output order groups the prompts
if (seedBehaviour === 'PER_PROMPT') {
const seeds = generateSeeds({
count: prompts.length * iterations,
start: shouldRandomizeSeed ? undefined : seed,
});
if (graph.nodes[NOISE]) {
firstBatchDatumList.push({
node_path: NOISE,
field_name: 'seed',
items: seeds,
});
}
if (graph.nodes[METADATA_ACCUMULATOR]) {
firstBatchDatumList.push({
node_path: METADATA_ACCUMULATOR,
field_name: 'seed',
items: seeds,
});
}
if (graph.nodes[CANVAS_COHERENCE_NOISE]) {
firstBatchDatumList.push({
node_path: CANVAS_COHERENCE_NOISE,
field_name: 'seed',
items: seeds.map((seed) => (seed + 1) % NUMPY_RAND_MAX),
});
}
} else {
// seedBehaviour = SeedBehaviour.PerRun
const seeds = generateSeeds({
count: iterations,
start: shouldRandomizeSeed ? undefined : seed,
});
if (graph.nodes[NOISE]) {
secondBatchDatumList.push({
node_path: NOISE,
field_name: 'seed',
items: seeds,
});
}
if (graph.nodes[METADATA_ACCUMULATOR]) {
secondBatchDatumList.push({
node_path: METADATA_ACCUMULATOR,
field_name: 'seed',
items: seeds,
});
}
if (graph.nodes[CANVAS_COHERENCE_NOISE]) {
secondBatchDatumList.push({
node_path: CANVAS_COHERENCE_NOISE,
field_name: 'seed',
items: seeds.map((seed) => (seed + 1) % NUMPY_RAND_MAX),
});
}
data.push(secondBatchDatumList);
}
const extendedPrompts =
seedBehaviour === 'PER_PROMPT'
? range(iterations).flatMap(() => prompts)
: prompts;
// zipped batch of prompts
if (graph.nodes[POSITIVE_CONDITIONING]) {
firstBatchDatumList.push({
node_path: POSITIVE_CONDITIONING,
field_name: 'prompt',
items: extendedPrompts,
});
}
if (graph.nodes[METADATA_ACCUMULATOR]) {
firstBatchDatumList.push({
node_path: METADATA_ACCUMULATOR,
field_name: 'positive_prompt',
items: extendedPrompts,
});
}
if (shouldConcatSDXLStylePrompt && model?.base_model === 'sdxl') {
unset(graph.nodes[METADATA_ACCUMULATOR], 'positive_style_prompt');
const stylePrompts = extendedPrompts.map((p) =>
[p, positiveStylePrompt].join(' ')
);
if (graph.nodes[POSITIVE_CONDITIONING]) {
firstBatchDatumList.push({
node_path: POSITIVE_CONDITIONING,
field_name: 'style',
items: stylePrompts,
});
}
if (graph.nodes[METADATA_ACCUMULATOR]) {
firstBatchDatumList.push({
node_path: METADATA_ACCUMULATOR,
field_name: 'positive_style_prompt',
items: stylePrompts,
});
}
}
data.push(firstBatchDatumList);
}
const enqueueBatchArg: BatchConfig = {
prepend,
batch: {
graph,
runs: 1,
data,
},
};
return enqueueBatchArg;
};

View File

@ -7,9 +7,10 @@ import {
ImageToLatentsInvocation,
} from 'services/api/types';
import { addControlNetToLinearGraph } from './addControlNetToLinearGraph';
import { addDynamicPromptsToGraph } from './addDynamicPromptsToGraph';
import { addIPAdapterToLinearGraph } from './addIPAdapterToLinearGraph';
import { addLoRAsToGraph } from './addLoRAsToGraph';
import { addNSFWCheckerToGraph } from './addNSFWCheckerToGraph';
import { addSaveImageNode } from './addSaveImageNode';
import { addSeamlessToLinearGraph } from './addSeamlessToLinearGraph';
import { addVAEToGraph } from './addVAEToGraph';
import { addWatermarkerToGraph } from './addWatermarkerToGraph';
@ -41,6 +42,7 @@ export const buildLinearImageToImageGraph = (
model,
cfgScale: cfg_scale,
scheduler,
seed,
steps,
initialImage,
img2imgStrength: strength,
@ -55,16 +57,6 @@ export const buildLinearImageToImageGraph = (
seamlessYAxis,
} = state.generation;
// TODO: add batch functionality
// const {
// isEnabled: isBatchEnabled,
// imageNames: batchImageNames,
// asInitialImage,
// } = state.batch;
// const shouldBatch =
// isBatchEnabled && batchImageNames.length > 0 && asInitialImage;
/**
* 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
@ -85,6 +77,7 @@ export const buildLinearImageToImageGraph = (
}
const fp32 = vaePrecision === 'fp32';
const is_intermediate = true;
let modelLoaderNodeId = MAIN_MODEL_LOADER;
@ -100,31 +93,38 @@ export const buildLinearImageToImageGraph = (
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,
},
[DENOISE_LATENTS]: {
type: 'denoise_latents',
@ -134,6 +134,7 @@ export const buildLinearImageToImageGraph = (
steps,
denoising_start: 1 - strength,
denoising_end: 1,
is_intermediate,
},
[IMAGE_TO_LATENTS]: {
type: 'i2l',
@ -143,6 +144,7 @@ export const buildLinearImageToImageGraph = (
// image_name: initialImage.image_name,
// },
fp32,
is_intermediate,
},
},
edges: [
@ -320,10 +322,10 @@ export const buildLinearImageToImageGraph = (
cfg_scale,
height,
width,
positive_prompt: '', // set in addDynamicPromptsToGraph
positive_prompt: positivePrompt,
negative_prompt: negativePrompt,
model,
seed: 0, // set in addDynamicPromptsToGraph
seed,
steps,
rand_device: use_cpu ? 'cpu' : 'cuda',
scheduler,
@ -358,12 +360,12 @@ export const buildLinearImageToImageGraph = (
// add LoRA support
addLoRAsToGraph(state, graph, DENOISE_LATENTS, modelLoaderNodeId);
// add dynamic prompts - also sets up core iteration and seed
addDynamicPromptsToGraph(state, graph);
// add controlnet, mutating `graph`
addControlNetToLinearGraph(state, graph, DENOISE_LATENTS);
// Add IP Adapter
addIPAdapterToLinearGraph(state, graph, DENOISE_LATENTS);
// NSFW & watermark - must be last thing added to graph
if (state.system.shouldUseNSFWChecker) {
// must add before watermarker!
@ -375,5 +377,7 @@ export const buildLinearImageToImageGraph = (
addWatermarkerToGraph(state, graph);
}
addSaveImageNode(state, graph);
return graph;
};

View File

@ -7,10 +7,11 @@ import {
ImageToLatentsInvocation,
} from 'services/api/types';
import { addControlNetToLinearGraph } from './addControlNetToLinearGraph';
import { addDynamicPromptsToGraph } from './addDynamicPromptsToGraph';
import { addIPAdapterToLinearGraph } from './addIPAdapterToLinearGraph';
import { addNSFWCheckerToGraph } from './addNSFWCheckerToGraph';
import { addSDXLLoRAsToGraph } from './addSDXLLoRAstoGraph';
import { addSDXLRefinerToGraph } from './addSDXLRefinerToGraph';
import { addSaveImageNode } from './addSaveImageNode';
import { addSeamlessToLinearGraph } from './addSeamlessToLinearGraph';
import { addVAEToGraph } from './addVAEToGraph';
import { addWatermarkerToGraph } from './addWatermarkerToGraph';
@ -28,7 +29,7 @@ import {
SDXL_REFINER_SEAMLESS,
SEAMLESS,
} from './constants';
import { craftSDXLStylePrompt } from './helpers/craftSDXLStylePrompt';
import { buildSDXLStylePrompts } from './helpers/craftSDXLStylePrompt';
/**
* Builds the Image to Image tab graph.
@ -43,6 +44,7 @@ export const buildLinearSDXLImageToImageGraph = (
model,
cfgScale: cfg_scale,
scheduler,
seed,
steps,
initialImage,
shouldFitToWidthHeight,
@ -59,7 +61,6 @@ export const buildLinearSDXLImageToImageGraph = (
const {
positiveStylePrompt,
negativeStylePrompt,
shouldConcatSDXLStylePrompt,
shouldUseSDXLRefiner,
refinerStart,
sdxlImg2ImgDenoisingStrength: strength,
@ -85,6 +86,7 @@ export const buildLinearSDXLImageToImageGraph = (
}
const fp32 = vaePrecision === 'fp32';
const is_intermediate = true;
// Model Loader ID
let modelLoaderNodeId = SDXL_MODEL_LOADER;
@ -94,8 +96,8 @@ export const buildLinearSDXLImageToImageGraph = (
: initialGenerationState.shouldUseCpuNoise;
// Construct Style Prompt
const { craftedPositiveStylePrompt, craftedNegativeStylePrompt } =
craftSDXLStylePrompt(state, shouldConcatSDXLStylePrompt);
const { joinedPositiveStylePrompt, joinedNegativeStylePrompt } =
buildSDXLStylePrompts(state);
// copy-pasted graph from node editor, filled in with state values & friendly node ids
const graph: NonNullableGraph = {
@ -105,28 +107,34 @@ export const buildLinearSDXLImageToImageGraph = (
type: 'sdxl_model_loader',
id: modelLoaderNodeId,
model,
is_intermediate,
},
[POSITIVE_CONDITIONING]: {
type: 'sdxl_compel_prompt',
id: POSITIVE_CONDITIONING,
prompt: positivePrompt,
style: craftedPositiveStylePrompt,
style: joinedPositiveStylePrompt,
is_intermediate,
},
[NEGATIVE_CONDITIONING]: {
type: 'sdxl_compel_prompt',
id: NEGATIVE_CONDITIONING,
prompt: negativePrompt,
style: craftedNegativeStylePrompt,
style: joinedNegativeStylePrompt,
is_intermediate,
},
[NOISE]: {
type: 'noise',
id: NOISE,
use_cpu,
seed,
is_intermediate,
},
[LATENTS_TO_IMAGE]: {
type: 'l2i',
id: LATENTS_TO_IMAGE,
fp32,
is_intermediate,
},
[SDXL_DENOISE_LATENTS]: {
type: 'denoise_latents',
@ -138,6 +146,7 @@ export const buildLinearSDXLImageToImageGraph = (
? Math.min(refinerStart, 1 - strength)
: 1 - strength,
denoising_end: shouldUseSDXLRefiner ? refinerStart : 1,
is_intermediate,
},
[IMAGE_TO_LATENTS]: {
type: 'i2l',
@ -147,6 +156,7 @@ export const buildLinearSDXLImageToImageGraph = (
// image_name: initialImage.image_name,
// },
fp32,
is_intermediate,
},
},
edges: [
@ -333,10 +343,10 @@ export const buildLinearSDXLImageToImageGraph = (
cfg_scale,
height,
width,
positive_prompt: '', // set in addDynamicPromptsToGraph
positive_prompt: positivePrompt,
negative_prompt: negativePrompt,
model,
seed: 0, // set in addDynamicPromptsToGraph
seed,
steps,
rand_device: use_cpu ? 'cpu' : 'cuda',
scheduler,
@ -384,8 +394,8 @@ export const buildLinearSDXLImageToImageGraph = (
// add controlnet, mutating `graph`
addControlNetToLinearGraph(state, graph, SDXL_DENOISE_LATENTS);
// add dynamic prompts - also sets up core iteration and seed
addDynamicPromptsToGraph(state, graph);
// Add IP Adapter
addIPAdapterToLinearGraph(state, graph, SDXL_DENOISE_LATENTS);
// NSFW & watermark - must be last thing added to graph
if (state.system.shouldUseNSFWChecker) {
@ -398,5 +408,7 @@ export const buildLinearSDXLImageToImageGraph = (
addWatermarkerToGraph(state, graph);
}
addSaveImageNode(state, graph);
return graph;
};

View File

@ -3,10 +3,11 @@ import { RootState } from 'app/store/store';
import { NonNullableGraph } from 'features/nodes/types/types';
import { initialGenerationState } from 'features/parameters/store/generationSlice';
import { addControlNetToLinearGraph } from './addControlNetToLinearGraph';
import { addDynamicPromptsToGraph } from './addDynamicPromptsToGraph';
import { addIPAdapterToLinearGraph } from './addIPAdapterToLinearGraph';
import { addNSFWCheckerToGraph } from './addNSFWCheckerToGraph';
import { addSDXLLoRAsToGraph } from './addSDXLLoRAstoGraph';
import { addSDXLRefinerToGraph } from './addSDXLRefinerToGraph';
import { addSaveImageNode } from './addSaveImageNode';
import { addSeamlessToLinearGraph } from './addSeamlessToLinearGraph';
import { addVAEToGraph } from './addVAEToGraph';
import { addWatermarkerToGraph } from './addWatermarkerToGraph';
@ -22,7 +23,7 @@ import {
SDXL_TEXT_TO_IMAGE_GRAPH,
SEAMLESS,
} from './constants';
import { craftSDXLStylePrompt } from './helpers/craftSDXLStylePrompt';
import { buildSDXLStylePrompts } from './helpers/craftSDXLStylePrompt';
export const buildLinearSDXLTextToImageGraph = (
state: RootState
@ -34,6 +35,7 @@ export const buildLinearSDXLTextToImageGraph = (
model,
cfgScale: cfg_scale,
scheduler,
seed,
steps,
width,
height,
@ -49,24 +51,23 @@ export const buildLinearSDXLTextToImageGraph = (
positiveStylePrompt,
negativeStylePrompt,
shouldUseSDXLRefiner,
shouldConcatSDXLStylePrompt,
refinerStart,
} = state.sdxl;
const use_cpu = shouldUseNoiseSettings
? shouldUseCpuNoise
: initialGenerationState.shouldUseCpuNoise;
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;
// Construct Style Prompt
const { craftedPositiveStylePrompt, craftedNegativeStylePrompt } =
craftSDXLStylePrompt(state, shouldConcatSDXLStylePrompt);
const { joinedPositiveStylePrompt, joinedNegativeStylePrompt } =
buildSDXLStylePrompts(state);
// Model Loader ID
let modelLoaderNodeId = SDXL_MODEL_LOADER;
@ -88,25 +89,30 @@ export const buildLinearSDXLTextToImageGraph = (
type: 'sdxl_model_loader',
id: modelLoaderNodeId,
model,
is_intermediate,
},
[POSITIVE_CONDITIONING]: {
type: 'sdxl_compel_prompt',
id: POSITIVE_CONDITIONING,
prompt: positivePrompt,
style: craftedPositiveStylePrompt,
style: joinedPositiveStylePrompt,
is_intermediate,
},
[NEGATIVE_CONDITIONING]: {
type: 'sdxl_compel_prompt',
id: NEGATIVE_CONDITIONING,
prompt: negativePrompt,
style: craftedNegativeStylePrompt,
style: joinedNegativeStylePrompt,
is_intermediate,
},
[NOISE]: {
type: 'noise',
id: NOISE,
seed,
width,
height,
use_cpu,
is_intermediate,
},
[SDXL_DENOISE_LATENTS]: {
type: 'denoise_latents',
@ -116,11 +122,13 @@ export const buildLinearSDXLTextToImageGraph = (
steps,
denoising_start: 0,
denoising_end: shouldUseSDXLRefiner ? refinerStart : 1,
is_intermediate,
},
[LATENTS_TO_IMAGE]: {
type: 'l2i',
id: LATENTS_TO_IMAGE,
fp32,
is_intermediate,
},
},
edges: [
@ -228,10 +236,10 @@ export const buildLinearSDXLTextToImageGraph = (
cfg_scale,
height,
width,
positive_prompt: '', // set in addDynamicPromptsToGraph
positive_prompt: positivePrompt,
negative_prompt: negativePrompt,
model,
seed: 0, // set in addDynamicPromptsToGraph
seed,
steps,
rand_device: use_cpu ? 'cpu' : 'cuda',
scheduler,
@ -277,8 +285,8 @@ export const buildLinearSDXLTextToImageGraph = (
// add controlnet, mutating `graph`
addControlNetToLinearGraph(state, graph, SDXL_DENOISE_LATENTS);
// add dynamic prompts - also sets up core iteration and seed
addDynamicPromptsToGraph(state, graph);
// add IP Adapter
addIPAdapterToLinearGraph(state, graph, SDXL_DENOISE_LATENTS);
// NSFW & watermark - must be last thing added to graph
if (state.system.shouldUseNSFWChecker) {
@ -291,5 +299,7 @@ export const buildLinearSDXLTextToImageGraph = (
addWatermarkerToGraph(state, graph);
}
addSaveImageNode(state, graph);
return graph;
};

Some files were not shown because too many files have changed in this diff Show More