diff --git a/invokeai/frontend/web/src/common/components/IAIDndImage.tsx b/invokeai/frontend/web/src/common/components/IAIDndImage.tsx index a6667e73be..1451f82677 100644 --- a/invokeai/frontend/web/src/common/components/IAIDndImage.tsx +++ b/invokeai/frontend/web/src/common/components/IAIDndImage.tsx @@ -48,7 +48,7 @@ const IAIDndImage = (props: IAIDndImageProps) => { isDragDisabled = false, fallback = , payloadImage, - minSize = 36, + minSize = 24, } = props; const dndId = useRef(uuidv4()); const { getUrl } = useGetUrl(); diff --git a/invokeai/frontend/web/src/common/components/IAISimpleCheckbox.tsx b/invokeai/frontend/web/src/common/components/IAISimpleCheckbox.tsx index 4d21d3d3d0..2d28b5b72e 100644 --- a/invokeai/frontend/web/src/common/components/IAISimpleCheckbox.tsx +++ b/invokeai/frontend/web/src/common/components/IAISimpleCheckbox.tsx @@ -1,8 +1,8 @@ import { Checkbox, CheckboxProps, Text } from '@chakra-ui/react'; -import { memo, ReactNode } from 'react'; +import { memo, ReactElement } from 'react'; type IAISimpleCheckboxProps = CheckboxProps & { - label: string | ReactNode; + label: string | ReactElement; }; const IAISimpleCheckbox = (props: IAISimpleCheckboxProps) => { diff --git a/invokeai/frontend/web/src/features/controlNet/components/ControlNet.tsx b/invokeai/frontend/web/src/features/controlNet/components/ControlNet.tsx index 803444d96b..2b86ca0e4d 100644 --- a/invokeai/frontend/web/src/features/controlNet/components/ControlNet.tsx +++ b/invokeai/frontend/web/src/features/controlNet/components/ControlNet.tsx @@ -58,11 +58,7 @@ const ControlNet = (props: ControlNetProps) => { return ( - + { - const { controlNetId, controlImage, processedControlImage } = props; + const { + controlNetId, + controlImage, + processedControlImage, + isControlImageProcessed, + } = props.controlNet; const dispatch = useAppDispatch(); const { isProcessingControlImage } = useAppSelector(selector); + const containerRef = useRef(null); - const [shouldShowProcessedImage, setShouldShowProcessedImage] = - useState(true); + const isMouseOverImage = useHoverDirty(containerRef); const handleControlImageChanged = useCallback( (controlImage: ImageDTO) => { @@ -46,12 +51,15 @@ const ControlNetImagePreview = (props: Props) => { Number(controlImage?.width) > Number(processedControlImage?.width) || Number(controlImage?.height) > Number(processedControlImage?.height); + const shouldShowProcessedImage = + controlImage && + processedControlImage && + !isMouseOverImage && + !isProcessingControlImage && + !isControlImageProcessed; + return ( - setShouldShowProcessedImage(false)} - onMouseOut={() => setShouldShowProcessedImage(true)} - > + { processorNode, } = props.controlNet; const dispatch = useAppDispatch(); - const handleReset = useCallback(() => { - dispatch( - controlNetProcessedImageChanged({ - controlNetId, - processedControlImage: null, - }) - ); + + const handleDelete = useCallback(() => { + dispatch(controlNetRemoved(controlNetId)); }, [controlNetId, dispatch]); - const handleControlNetRemoved = useCallback(() => { - dispatch(controlNetRemoved(controlNetId)); + const handleDuplicate = useCallback(() => { + dispatch( + controlNetAdded({ controlNetId: uuidv4(), controlNet: props.controlNet }) + ); + }, [dispatch, props.controlNet]); + + const handleToggleIsEnabled = useCallback(() => { + dispatch(controlNetToggled(controlNetId)); + }, [controlNetId, dispatch]); + + const handleToggleIsPreprocessed = useCallback(() => { + dispatch(isControlNetImageProcessedToggled(controlNetId)); }, [controlNetId, dispatch]); return ( + + + } + /> + } + /> + - - - - - - + + + + + + + + + + + Enabled + + + + + + Preprocessed + + + + ); diff --git a/invokeai/frontend/web/src/features/controlNet/components/parameters/ParamControlNetBeginEnd.tsx b/invokeai/frontend/web/src/features/controlNet/components/parameters/ParamControlNetBeginEnd.tsx index e258d4cf29..bb2f151193 100644 --- a/invokeai/frontend/web/src/features/controlNet/components/parameters/ParamControlNetBeginEnd.tsx +++ b/invokeai/frontend/web/src/features/controlNet/components/parameters/ParamControlNetBeginEnd.tsx @@ -50,7 +50,7 @@ const ParamControlNetBeginEnd = (props: Props) => { return ( - Begin & End Step % + Begin / End Step Percentage { {!mini && ( <> - {' '} + action: PayloadAction<{ controlNetId: string; controlNet?: ControlNet }> ) => { - const { controlNetId } = action.payload; + const { controlNetId, controlNet } = action.payload; state.controlNets[controlNetId] = { - ...initialControlNet, + ...(controlNet ?? initialControlNet), controlNetId, }; }, @@ -116,11 +116,9 @@ export const controlNetSlice = createSlice({ }, isControlNetImageProcessedToggled: ( state, - action: PayloadAction<{ - controlNetId: string; - }> + action: PayloadAction ) => { - const { controlNetId } = action.payload; + const controlNetId = action.payload; state.controlNets[controlNetId].isControlImageProcessed = !state.controlNets[controlNetId].isControlImageProcessed; }, diff --git a/invokeai/frontend/web/src/features/nodes/util/graphBuilders/buildTextToImageGraph.ts b/invokeai/frontend/web/src/features/nodes/util/graphBuilders/buildTextToImageGraph.ts index 9975d446a3..161f857bd7 100644 --- a/invokeai/frontend/web/src/features/nodes/util/graphBuilders/buildTextToImageGraph.ts +++ b/invokeai/frontend/web/src/features/nodes/util/graphBuilders/buildTextToImageGraph.ts @@ -346,6 +346,11 @@ export const buildTextToImageGraph = (state: RootState): Graph => { weight, } = controlNet; + if (!isEnabled) { + // Skip disabled ControlNets + return; + } + const controlNetNode: ControlNetInvocation = { id: `control_net_${controlNetId}`, type: 'controlnet', @@ -355,14 +360,14 @@ export const buildTextToImageGraph = (state: RootState): Graph => { control_weight: weight, }; - if (processedControlImage) { + if (processedControlImage && !isControlImageProcessed) { // We've already processed the image in the app, so we can just use the processed image const { image_name, image_origin } = processedControlImage; controlNetNode.image = { image_name, image_origin, }; - } else if (controlImage) { + } else if (controlImage && isControlImageProcessed) { // The control image is preprocessed const { image_name, image_origin } = controlImage; controlNetNode.image = { @@ -370,9 +375,10 @@ export const buildTextToImageGraph = (state: RootState): Graph => { image_origin, }; } else { - // The control image is not processed, so we need to add a preprocess node - // TODO: Add preprocess node + // Skip ControlNets without an unprocessed image - should never happen if everything is working correctly + return; } + graph.nodes[controlNetNode.id] = controlNetNode; if (size(controlNets) > 1) { diff --git a/invokeai/frontend/web/src/features/parameters/components/Parameters/ControlNet/ParamControlNetCollapse.tsx b/invokeai/frontend/web/src/features/parameters/components/Parameters/ControlNet/ParamControlNetCollapse.tsx index 2359e5123c..6369ab56de 100644 --- a/invokeai/frontend/web/src/features/parameters/components/Parameters/ControlNet/ParamControlNetCollapse.tsx +++ b/invokeai/frontend/web/src/features/parameters/components/Parameters/ControlNet/ParamControlNetCollapse.tsx @@ -1,6 +1,6 @@ import { + Divider, Flex, - Spacer, Tab, TabList, TabPanel, @@ -9,7 +9,7 @@ import { } from '@chakra-ui/react'; import { useTranslation } from 'react-i18next'; import IAICollapse from 'common/components/IAICollapse'; -import { memo, useCallback } from 'react'; +import { Fragment, memo, useCallback } from 'react'; import IAIIconButton from 'common/components/IAIIconButton'; import { FaPlus } from 'react-icons/fa'; import ControlNet from 'features/controlNet/components/ControlNet'; @@ -21,11 +21,11 @@ import { isControlNetEnabledToggled, } from 'features/controlNet/store/controlNetSlice'; import { defaultSelectorOptions } from 'app/store/util/defaultMemoizeOptions'; -import { map, startCase } from 'lodash-es'; +import { map } from 'lodash-es'; import { v4 as uuidv4 } from 'uuid'; -import { CloseIcon } from '@chakra-ui/icons'; import ControlNetMini from 'features/controlNet/components/ControlNetMini'; import { useFeatureStatus } from 'features/system/hooks/useFeatureStatus'; +import IAIButton from 'common/components/IAIButton'; const selector = createSelector( controlNetSelector, @@ -56,11 +56,26 @@ const ParamControlNetCollapse = () => { } return ( - <> - {controlNetsArray.map((c) => ( - - ))} - + + {controlNetsArray.length === 0 && ( + + Add ControlNet + + )} + + {controlNetsArray.map((c, i) => ( + + {i > 0 && } + + + ))} + + ); return (