diff --git a/invokeai/frontend/web/src/app/store/middleware/listenerMiddleware/listeners/controlNetAutoProcess.ts b/invokeai/frontend/web/src/app/store/middleware/listenerMiddleware/listeners/controlNetAutoProcess.ts index 9f98b8f25e..dd2fb6f469 100644 --- a/invokeai/frontend/web/src/app/store/middleware/listenerMiddleware/listeners/controlNetAutoProcess.ts +++ b/invokeai/frontend/web/src/app/store/middleware/listenerMiddleware/listeners/controlNetAutoProcess.ts @@ -1,9 +1,11 @@ -import { AnyAction } from '@reduxjs/toolkit'; +import { AnyListenerPredicate } from '@reduxjs/toolkit'; import { startAppListening } from '..'; import { log } from 'app/logging/useLogger'; import { controlNetImageProcessed } from 'features/controlNet/store/actions'; import { + controlNetAutoConfigToggled, controlNetImageChanged, + controlNetModelChanged, controlNetProcessorParamsChanged, controlNetProcessorTypeChanged, } from 'features/controlNet/store/controlNetSlice'; @@ -11,19 +13,26 @@ import { RootState } from 'app/store/store'; const moduleLog = log.child({ namespace: 'controlNet' }); -const predicate = (action: AnyAction, state: RootState) => { +const predicate: AnyListenerPredicate = (action, state) => { const isActionMatched = controlNetProcessorParamsChanged.match(action) || + controlNetModelChanged.match(action) || controlNetImageChanged.match(action) || - controlNetProcessorTypeChanged.match(action); + controlNetProcessorTypeChanged.match(action) || + controlNetAutoConfigToggled.match(action); if (!isActionMatched) { return false; } - const { controlImage, processorType } = + const { controlImage, processorType, shouldAutoConfig } = state.controlNet.controlNets[action.payload.controlNetId]; + if (controlNetModelChanged.match(action) && !shouldAutoConfig) { + // do not process if the action is a model change but the processor settings are dirty + return false; + } + const isProcessorSelected = processorType !== 'none'; const isBusy = state.system.isProcessing; @@ -49,7 +58,10 @@ export const addControlNetAutoProcessListener = () => { // Cancel any in-progress instances of this listener cancelActiveListeners(); - + moduleLog.trace( + { data: action.payload }, + 'ControlNet auto-process triggered' + ); // Delay before starting actual work await delay(300); diff --git a/invokeai/frontend/web/src/features/controlNet/components/ControlNet.tsx b/invokeai/frontend/web/src/features/controlNet/components/ControlNet.tsx index ac212cc71d..5001a9ba24 100644 --- a/invokeai/frontend/web/src/features/controlNet/components/ControlNet.tsx +++ b/invokeai/frontend/web/src/features/controlNet/components/ControlNet.tsx @@ -8,22 +8,8 @@ import { import { useAppDispatch } from 'app/store/storeHooks'; import ParamControlNetModel from './parameters/ParamControlNetModel'; import ParamControlNetWeight from './parameters/ParamControlNetWeight'; -import { - Checkbox, - Flex, - FormControl, - FormLabel, - HStack, - TabList, - TabPanels, - Tabs, - Tab, - TabPanel, - Box, - VStack, - ChakraProps, -} from '@chakra-ui/react'; -import { FaCopy, FaPlus, FaTrash, FaWrench } from 'react-icons/fa'; +import { Flex, Box, ChakraProps } from '@chakra-ui/react'; +import { FaCopy, FaTrash } from 'react-icons/fa'; import ParamControlNetBeginEnd from './parameters/ParamControlNetBeginEnd'; import ControlNetImagePreview from './ControlNetImagePreview'; @@ -32,10 +18,9 @@ import { v4 as uuidv4 } from 'uuid'; import { useToggle } from 'react-use'; import ParamControlNetProcessorSelect from './parameters/ParamControlNetProcessorSelect'; import ControlNetProcessorComponent from './ControlNetProcessorComponent'; -import ControlNetPreprocessButton from './ControlNetPreprocessButton'; -import IAIButton from 'common/components/IAIButton'; import IAISwitch from 'common/components/IAISwitch'; -import { ChevronDownIcon, ChevronUpIcon } from '@chakra-ui/icons'; +import { ChevronUpIcon } from '@chakra-ui/icons'; +import ParamControlNetShouldAutoConfig from './ParamControlNetShouldAutoConfig'; const expandedControlImageSx: ChakraProps['sx'] = { maxH: 96 }; @@ -55,6 +40,7 @@ const ControlNet = (props: ControlNetProps) => { processedControlImage, processorNode, processorType, + shouldAutoConfig, } = props.controlNet; const dispatch = useAppDispatch(); const [isExpanded, toggleIsExpanded] = useToggle(false); @@ -81,6 +67,7 @@ const ControlNet = (props: ControlNetProps) => { p: 3, bg: 'base.850', borderRadius: 'base', + position: 'relative', }} > @@ -119,7 +106,7 @@ const ControlNet = (props: ControlNetProps) => { /> { /> } /> + {!shouldAutoConfig && ( + + )} {isEnabled && ( <> @@ -192,6 +192,10 @@ const ControlNet = (props: ControlNetProps) => { controlNetId={controlNetId} processorNode={processorNode} /> + )} diff --git a/invokeai/frontend/web/src/features/controlNet/components/ParamControlNetShouldAutoConfig.tsx b/invokeai/frontend/web/src/features/controlNet/components/ParamControlNetShouldAutoConfig.tsx new file mode 100644 index 0000000000..c60d0ee95b --- /dev/null +++ b/invokeai/frontend/web/src/features/controlNet/components/ParamControlNetShouldAutoConfig.tsx @@ -0,0 +1,29 @@ +import { useAppDispatch } from 'app/store/storeHooks'; +import IAISwitch from 'common/components/IAISwitch'; +import { controlNetAutoConfigToggled } from 'features/controlNet/store/controlNetSlice'; +import { memo, useCallback } from 'react'; + +type Props = { + controlNetId: string; + shouldAutoConfig: boolean; +}; + +const ParamControlNetShouldAutoConfig = (props: Props) => { + const { controlNetId, shouldAutoConfig } = props; + const dispatch = useAppDispatch(); + + const handleShouldAutoConfigChanged = useCallback(() => { + dispatch(controlNetAutoConfigToggled({ controlNetId })); + }, [controlNetId, dispatch]); + + return ( + + ); +}; + +export default memo(ParamControlNetShouldAutoConfig); diff --git a/invokeai/frontend/web/src/features/controlNet/components/parameters/ParamControlNetModel.tsx b/invokeai/frontend/web/src/features/controlNet/components/parameters/ParamControlNetModel.tsx index 113b1148f4..187d296a4f 100644 --- a/invokeai/frontend/web/src/features/controlNet/components/parameters/ParamControlNetModel.tsx +++ b/invokeai/frontend/web/src/features/controlNet/components/parameters/ParamControlNetModel.tsx @@ -7,12 +7,12 @@ import { import { controlNetModelChanged } from 'features/controlNet/store/controlNetSlice'; import { memo, useCallback } from 'react'; -type ParamIsControlNetModelProps = { +type ParamControlNetModelProps = { controlNetId: string; model: ControlNetModel; }; -const ParamIsControlNetModel = (props: ParamIsControlNetModelProps) => { +const ParamControlNetModel = (props: ParamControlNetModelProps) => { const { controlNetId, model } = props; const dispatch = useAppDispatch(); @@ -38,4 +38,4 @@ const ParamIsControlNetModel = (props: ParamIsControlNetModelProps) => { ); }; -export default memo(ParamIsControlNetModel); +export default memo(ParamControlNetModel); diff --git a/invokeai/frontend/web/src/features/controlNet/store/controlNetSlice.ts b/invokeai/frontend/web/src/features/controlNet/store/controlNetSlice.ts index 7a10b607b4..c4badef395 100644 --- a/invokeai/frontend/web/src/features/controlNet/store/controlNetSlice.ts +++ b/invokeai/frontend/web/src/features/controlNet/store/controlNetSlice.ts @@ -1,4 +1,4 @@ -import { PayloadAction, isAnyOf } from '@reduxjs/toolkit'; +import { PayloadAction } from '@reduxjs/toolkit'; import { createSlice } from '@reduxjs/toolkit'; import { RootState } from 'app/store/store'; import { ImageDTO } from 'services/api'; @@ -30,6 +30,7 @@ export const initialControlNet: Omit = { processorType: 'canny_image_processor', processorNode: CONTROLNET_PROCESSORS.canny_image_processor .default as RequiredCannyImageProcessorInvocation, + shouldAutoConfig: false, }; export type ControlNetConfig = { @@ -43,6 +44,7 @@ export type ControlNetConfig = { processedControlImage: ImageDTO | null; processorType: ControlNetProcessorType; processorNode: RequiredControlNetProcessorNode; + shouldAutoConfig: boolean; }; export type ControlNetState = { @@ -140,8 +142,9 @@ export const controlNetSlice = createSlice({ ) => { const { controlNetId, model } = action.payload; state.controlNets[controlNetId].model = model; + state.controlNets[controlNetId].processedControlImage = null; - if (!state.controlNets[controlNetId].controlImage) { + if (state.controlNets[controlNetId].shouldAutoConfig) { const processorType = CONTROLNET_MODEL_MAP[model]; if (processorType) { state.controlNets[controlNetId].processorType = processorType; @@ -192,6 +195,7 @@ export const controlNetSlice = createSlice({ ...processorNode, ...changes, }; + state.controlNets[controlNetId].shouldAutoConfig = false; }, controlNetProcessorTypeChanged: ( state, @@ -201,10 +205,40 @@ export const controlNetSlice = createSlice({ }> ) => { const { controlNetId, processorType } = action.payload; + state.controlNets[controlNetId].processedControlImage = null; state.controlNets[controlNetId].processorType = processorType; state.controlNets[controlNetId].processorNode = CONTROLNET_PROCESSORS[ processorType ].default as RequiredControlNetProcessorNode; + state.controlNets[controlNetId].shouldAutoConfig = false; + }, + controlNetAutoConfigToggled: ( + state, + action: PayloadAction<{ + controlNetId: string; + }> + ) => { + const { controlNetId } = action.payload; + const newShouldAutoConfig = + !state.controlNets[controlNetId].shouldAutoConfig; + + if (newShouldAutoConfig) { + // manage the processor for the user + const processorType = + CONTROLNET_MODEL_MAP[state.controlNets[controlNetId].model]; + if (processorType) { + state.controlNets[controlNetId].processorType = processorType; + state.controlNets[controlNetId].processorNode = CONTROLNET_PROCESSORS[ + processorType + ].default as RequiredControlNetProcessorNode; + } else { + state.controlNets[controlNetId].processorType = 'none'; + state.controlNets[controlNetId].processorNode = CONTROLNET_PROCESSORS + .none.default as RequiredControlNetProcessorNode; + } + } + + state.controlNets[controlNetId].shouldAutoConfig = newShouldAutoConfig; }, controlNetReset: () => { return { ...initialControlNetState }; @@ -274,6 +308,7 @@ export const { controlNetProcessorParamsChanged, controlNetProcessorTypeChanged, controlNetReset, + controlNetAutoConfigToggled, } = controlNetSlice.actions; export default controlNetSlice.reducer;