diff --git a/invokeai/frontend/web/src/features/controlNet/components/ControlNet.tsx b/invokeai/frontend/web/src/features/controlNet/components/ControlNet.tsx
index cd3cafa12a..8f90797fd9 100644
--- a/invokeai/frontend/web/src/features/controlNet/components/ControlNet.tsx
+++ b/invokeai/frontend/web/src/features/controlNet/components/ControlNet.tsx
@@ -1,10 +1,9 @@
-import { Box, ChakraProps, Flex, useColorMode } from '@chakra-ui/react';
-import { useAppDispatch } from 'app/store/storeHooks';
+import { Box, Flex, useColorMode } from '@chakra-ui/react';
+import { useAppDispatch, useAppSelector } from 'app/store/storeHooks';
import { memo, useCallback } from 'react';
import { FaCopy, FaTrash } from 'react-icons/fa';
import {
- ControlNetConfig,
- controlNetAdded,
+ controlNetDuplicated,
controlNetRemoved,
controlNetToggled,
} from '../store/controlNetSlice';
@@ -12,9 +11,13 @@ import ParamControlNetModel from './parameters/ParamControlNetModel';
import ParamControlNetWeight from './parameters/ParamControlNetWeight';
import { ChevronUpIcon } from '@chakra-ui/icons';
+import { createSelector } from '@reduxjs/toolkit';
+import { stateSelector } from 'app/store/store';
+import { defaultSelectorOptions } from 'app/store/util/defaultMemoizeOptions';
import IAIIconButton from 'common/components/IAIIconButton';
import IAISwitch from 'common/components/IAISwitch';
import { useToggle } from 'react-use';
+import { mode } from 'theme/util/mode';
import { v4 as uuidv4 } from 'uuid';
import ControlNetImagePreview from './ControlNetImagePreview';
import ControlNetProcessorComponent from './ControlNetProcessorComponent';
@@ -22,30 +25,28 @@ import ParamControlNetShouldAutoConfig from './ParamControlNetShouldAutoConfig';
import ParamControlNetBeginEnd from './parameters/ParamControlNetBeginEnd';
import ParamControlNetControlMode from './parameters/ParamControlNetControlMode';
import ParamControlNetProcessorSelect from './parameters/ParamControlNetProcessorSelect';
-import { mode } from 'theme/util/mode';
-
-const expandedControlImageSx: ChakraProps['sx'] = { maxH: 96 };
type ControlNetProps = {
- controlNet: ControlNetConfig;
+ controlNetId: string;
};
const ControlNet = (props: ControlNetProps) => {
- const {
- controlNetId,
- isEnabled,
- model,
- weight,
- beginStepPct,
- endStepPct,
- controlMode,
- controlImage,
- processedControlImage,
- processorNode,
- processorType,
- shouldAutoConfig,
- } = props.controlNet;
+ const { controlNetId } = props;
const dispatch = useAppDispatch();
+
+ const selector = createSelector(
+ stateSelector,
+ ({ controlNet }) => {
+ const { isEnabled, shouldAutoConfig } =
+ controlNet.controlNets[controlNetId];
+
+ return { isEnabled, shouldAutoConfig };
+ },
+ defaultSelectorOptions
+ );
+
+ const { isEnabled, shouldAutoConfig } = useAppSelector(selector);
+
const [isExpanded, toggleIsExpanded] = useToggle(false);
const { colorMode } = useColorMode();
const handleDelete = useCallback(() => {
@@ -54,9 +55,12 @@ const ControlNet = (props: ControlNetProps) => {
const handleDuplicate = useCallback(() => {
dispatch(
- controlNetAdded({ controlNetId: uuidv4(), controlNet: props.controlNet })
+ controlNetDuplicated({
+ sourceControlNetId: controlNetId,
+ newControlNetId: uuidv4(),
+ })
);
- }, [dispatch, props.controlNet]);
+ }, [controlNetId, dispatch]);
const handleToggleIsEnabled = useCallback(() => {
dispatch(controlNetToggled({ controlNetId }));
@@ -140,14 +144,8 @@ const ControlNet = (props: ControlNetProps) => {
)}
-
-
+
+
{isEnabled && (
<>
@@ -166,13 +164,10 @@ const ControlNet = (props: ControlNetProps) => {
>
@@ -187,30 +182,24 @@ const ControlNet = (props: ControlNetProps) => {
}}
>
)}
-
+
{isExpanded && (
<>
-
+
>
)}
>
diff --git a/invokeai/frontend/web/src/features/controlNet/components/ControlNetImagePreview.tsx b/invokeai/frontend/web/src/features/controlNet/components/ControlNetImagePreview.tsx
index 1321f7b0c0..f82e39d34e 100644
--- a/invokeai/frontend/web/src/features/controlNet/components/ControlNetImagePreview.tsx
+++ b/invokeai/frontend/web/src/features/controlNet/components/ControlNetImagePreview.tsx
@@ -5,42 +5,51 @@ import {
TypesafeDraggableData,
TypesafeDroppableData,
} from 'app/components/ImageDnd/typesafeDnd';
+import { stateSelector } from 'app/store/store';
import { useAppDispatch, useAppSelector } from 'app/store/storeHooks';
import { defaultSelectorOptions } from 'app/store/util/defaultMemoizeOptions';
import IAIDndImage from 'common/components/IAIDndImage';
import { memo, useCallback, useMemo, useState } from 'react';
import { useGetImageDTOQuery } from 'services/api/endpoints/images';
import { PostUploadAction } from 'services/api/thunks/image';
-import {
- ControlNetConfig,
- controlNetImageChanged,
- controlNetSelector,
-} from '../store/controlNetSlice';
-
-const selector = createSelector(
- controlNetSelector,
- (controlNet) => {
- const { pendingControlImages } = controlNet;
- return { pendingControlImages };
- },
- defaultSelectorOptions
-);
+import { controlNetImageChanged } from '../store/controlNetSlice';
type Props = {
- controlNet: ControlNetConfig;
+ controlNetId: string;
height: SystemStyleObject['h'];
};
const ControlNetImagePreview = (props: Props) => {
- const { height } = props;
- const {
- controlNetId,
- controlImage: controlImageName,
- processedControlImage: processedControlImageName,
- processorType,
- } = props.controlNet;
+ const { height, controlNetId } = props;
const dispatch = useAppDispatch();
- const { pendingControlImages } = useAppSelector(selector);
+
+ const selector = useMemo(
+ () =>
+ createSelector(
+ stateSelector,
+ ({ controlNet }) => {
+ const { pendingControlImages } = controlNet;
+ const { controlImage, processedControlImage, processorType } =
+ controlNet.controlNets[controlNetId];
+
+ return {
+ controlImageName: controlImage,
+ processedControlImageName: processedControlImage,
+ processorType,
+ pendingControlImages,
+ };
+ },
+ defaultSelectorOptions
+ ),
+ [controlNetId]
+ );
+
+ const {
+ controlImageName,
+ processedControlImageName,
+ processorType,
+ pendingControlImages,
+ } = useAppSelector(selector);
const [isMouseOverImage, setIsMouseOverImage] = useState(false);
diff --git a/invokeai/frontend/web/src/features/controlNet/components/ControlNetProcessorComponent.tsx b/invokeai/frontend/web/src/features/controlNet/components/ControlNetProcessorComponent.tsx
index 4649f89b35..863e42632c 100644
--- a/invokeai/frontend/web/src/features/controlNet/components/ControlNetProcessorComponent.tsx
+++ b/invokeai/frontend/web/src/features/controlNet/components/ControlNetProcessorComponent.tsx
@@ -1,10 +1,13 @@
-import { memo } from 'react';
-import { RequiredControlNetProcessorNode } from '../store/types';
+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 { memo, useMemo } from 'react';
import CannyProcessor from './processors/CannyProcessor';
-import HedProcessor from './processors/HedProcessor';
-import LineartProcessor from './processors/LineartProcessor';
-import LineartAnimeProcessor from './processors/LineartAnimeProcessor';
import ContentShuffleProcessor from './processors/ContentShuffleProcessor';
+import HedProcessor from './processors/HedProcessor';
+import LineartAnimeProcessor from './processors/LineartAnimeProcessor';
+import LineartProcessor from './processors/LineartProcessor';
import MediapipeFaceProcessor from './processors/MediapipeFaceProcessor';
import MidasDepthProcessor from './processors/MidasDepthProcessor';
import MlsdImageProcessor from './processors/MlsdImageProcessor';
@@ -15,11 +18,23 @@ import ZoeDepthProcessor from './processors/ZoeDepthProcessor';
export type ControlNetProcessorProps = {
controlNetId: string;
- processorNode: RequiredControlNetProcessorNode;
};
const ControlNetProcessorComponent = (props: ControlNetProcessorProps) => {
- const { controlNetId, processorNode } = props;
+ const { controlNetId } = props;
+
+ const selector = useMemo(
+ () =>
+ createSelector(
+ stateSelector,
+ ({ controlNet }) => controlNet.controlNets[controlNetId]?.processorNode,
+ defaultSelectorOptions
+ ),
+ [controlNetId]
+ );
+
+ const processorNode = useAppSelector(selector);
+
if (processorNode.type === 'canny_image_processor') {
return (
{
- const { controlNetId, shouldAutoConfig } = props;
+ const { controlNetId } = props;
const dispatch = useAppDispatch();
- const isReady = useIsReadyToInvoke();
+ const selector = useMemo(
+ () =>
+ createSelector(
+ stateSelector,
+ ({ controlNet }) =>
+ controlNet.controlNets[controlNetId]?.shouldAutoConfig,
+ defaultSelectorOptions
+ ),
+ [controlNetId]
+ );
+
+ const shouldAutoConfig = useAppSelector(selector);
+ const isBusy = useAppSelector(selectIsBusy);
+
const handleShouldAutoConfigChanged = useCallback(() => {
dispatch(controlNetAutoConfigToggled({ controlNetId }));
}, [controlNetId, dispatch]);
@@ -23,7 +38,7 @@ const ParamControlNetShouldAutoConfig = (props: Props) => {
aria-label="Auto configure processor"
isChecked={shouldAutoConfig}
onChange={handleShouldAutoConfigChanged}
- isDisabled={!isReady}
+ isDisabled={isBusy}
/>
);
};
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 7d0c53fe40..c08ecf1bb2 100644
--- a/invokeai/frontend/web/src/features/controlNet/components/parameters/ParamControlNetBeginEnd.tsx
+++ b/invokeai/frontend/web/src/features/controlNet/components/parameters/ParamControlNetBeginEnd.tsx
@@ -10,13 +10,15 @@ import {
RangeSliderTrack,
Tooltip,
} from '@chakra-ui/react';
-import { useAppDispatch } from 'app/store/storeHooks';
+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 {
controlNetBeginStepPctChanged,
controlNetEndStepPctChanged,
} from 'features/controlNet/store/controlNetSlice';
-import { memo, useCallback } from 'react';
-import { useTranslation } from 'react-i18next';
+import { memo, useCallback, useMemo } from 'react';
const SLIDER_MARK_STYLES: ChakraProps['sx'] = {
mt: 1.5,
@@ -27,17 +29,30 @@ const SLIDER_MARK_STYLES: ChakraProps['sx'] = {
type Props = {
controlNetId: string;
- beginStepPct: number;
- endStepPct: number;
mini?: boolean;
};
const formatPct = (v: number) => `${Math.round(v * 100)}%`;
const ParamControlNetBeginEnd = (props: Props) => {
- const { controlNetId, beginStepPct, mini = false, endStepPct } = props;
+ const { controlNetId, mini = false } = props;
const dispatch = useAppDispatch();
- const { t } = useTranslation();
+
+ const selector = useMemo(
+ () =>
+ createSelector(
+ stateSelector,
+ ({ controlNet }) => {
+ const { beginStepPct, endStepPct } =
+ controlNet.controlNets[controlNetId];
+ return { beginStepPct, endStepPct };
+ },
+ defaultSelectorOptions
+ ),
+ [controlNetId]
+ );
+
+ const { beginStepPct, endStepPct } = useAppSelector(selector);
const handleStepPctChanged = useCallback(
(v: number[]) => {
diff --git a/invokeai/frontend/web/src/features/controlNet/components/parameters/ParamControlNetControlMode.tsx b/invokeai/frontend/web/src/features/controlNet/components/parameters/ParamControlNetControlMode.tsx
index b8737004fd..07b58384e1 100644
--- a/invokeai/frontend/web/src/features/controlNet/components/parameters/ParamControlNetControlMode.tsx
+++ b/invokeai/frontend/web/src/features/controlNet/components/parameters/ParamControlNetControlMode.tsx
@@ -1,15 +1,17 @@
-import { useAppDispatch } from 'app/store/storeHooks';
+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 IAIMantineSelect from 'common/components/IAIMantineSelect';
import {
ControlModes,
controlNetControlModeChanged,
} from 'features/controlNet/store/controlNetSlice';
-import { useCallback } from 'react';
+import { useCallback, useMemo } from 'react';
import { useTranslation } from 'react-i18next';
type ParamControlNetControlModeProps = {
controlNetId: string;
- controlMode: string;
};
const CONTROL_MODE_DATA = [
@@ -22,8 +24,19 @@ const CONTROL_MODE_DATA = [
export default function ParamControlNetControlMode(
props: ParamControlNetControlModeProps
) {
- const { controlNetId, controlMode = false } = props;
+ const { controlNetId } = props;
const dispatch = useAppDispatch();
+ const selector = useMemo(
+ () =>
+ createSelector(
+ stateSelector,
+ ({ controlNet }) => controlNet.controlNets[controlNetId]?.controlMode,
+ defaultSelectorOptions
+ ),
+ [controlNetId]
+ );
+
+ const controlMode = useAppSelector(selector);
const { t } = useTranslation();
diff --git a/invokeai/frontend/web/src/features/controlNet/components/parameters/ParamControlNetProcessorSelect.tsx b/invokeai/frontend/web/src/features/controlNet/components/parameters/ParamControlNetProcessorSelect.tsx
index 5d091be1ef..a57de5d70e 100644
--- a/invokeai/frontend/web/src/features/controlNet/components/parameters/ParamControlNetProcessorSelect.tsx
+++ b/invokeai/frontend/web/src/features/controlNet/components/parameters/ParamControlNetProcessorSelect.tsx
@@ -1,24 +1,21 @@
import { useAppDispatch, useAppSelector } from 'app/store/storeHooks';
import { createSelector } from '@reduxjs/toolkit';
+import { stateSelector } from 'app/store/store';
import { defaultSelectorOptions } from 'app/store/util/defaultMemoizeOptions';
import IAIMantineSearchableSelect, {
IAISelectDataType,
} from 'common/components/IAIMantineSearchableSelect';
-import { useIsReadyToInvoke } from 'common/hooks/useIsReadyToInvoke';
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 { memo, useCallback, useMemo } from 'react';
import { CONTROLNET_PROCESSORS } from '../../store/constants';
import { controlNetProcessorTypeChanged } from '../../store/controlNetSlice';
-import {
- ControlNetProcessorNode,
- ControlNetProcessorType,
-} from '../../store/types';
+import { ControlNetProcessorType } from '../../store/types';
type ParamControlNetProcessorSelectProps = {
controlNetId: string;
- processorNode: ControlNetProcessorNode;
};
const selector = createSelector(
@@ -54,10 +51,22 @@ const selector = createSelector(
const ParamControlNetProcessorSelect = (
props: ParamControlNetProcessorSelectProps
) => {
- const { controlNetId, processorNode } = props;
const dispatch = useAppDispatch();
- const isReady = useIsReadyToInvoke();
+ const { controlNetId } = props;
+ const processorNodeSelector = useMemo(
+ () =>
+ createSelector(
+ stateSelector,
+ ({ controlNet }) => ({
+ processorNode: controlNet.controlNets[controlNetId]?.processorNode,
+ }),
+ defaultSelectorOptions
+ ),
+ [controlNetId]
+ );
+ const isBusy = useAppSelector(selectIsBusy);
const controlNetProcessors = useAppSelector(selector);
+ const { processorNode } = useAppSelector(processorNodeSelector);
const handleProcessorTypeChanged = useCallback(
(v: string | null) => {
@@ -77,7 +86,7 @@ const ParamControlNetProcessorSelect = (
value={processorNode.type ?? 'canny_image_processor'}
data={controlNetProcessors}
onChange={handleProcessorTypeChanged}
- disabled={!isReady}
+ disabled={isBusy}
/>
);
};
diff --git a/invokeai/frontend/web/src/features/controlNet/components/parameters/ParamControlNetWeight.tsx b/invokeai/frontend/web/src/features/controlNet/components/parameters/ParamControlNetWeight.tsx
index c2b77125d0..d0973f3d81 100644
--- a/invokeai/frontend/web/src/features/controlNet/components/parameters/ParamControlNetWeight.tsx
+++ b/invokeai/frontend/web/src/features/controlNet/components/parameters/ParamControlNetWeight.tsx
@@ -1,18 +1,30 @@
-import { useAppDispatch } from 'app/store/storeHooks';
+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 IAISlider from 'common/components/IAISlider';
import { controlNetWeightChanged } from 'features/controlNet/store/controlNetSlice';
-import { memo, useCallback } from 'react';
+import { memo, useCallback, useMemo } from 'react';
type ParamControlNetWeightProps = {
controlNetId: string;
- weight: number;
mini?: boolean;
};
const ParamControlNetWeight = (props: ParamControlNetWeightProps) => {
- const { controlNetId, weight, mini = false } = props;
+ const { controlNetId, mini = false } = props;
const dispatch = useAppDispatch();
+ const selector = useMemo(
+ () =>
+ createSelector(
+ stateSelector,
+ ({ controlNet }) => controlNet.controlNets[controlNetId]?.weight,
+ defaultSelectorOptions
+ ),
+ [controlNetId]
+ );
+ const weight = useAppSelector(selector);
const handleWeightChanged = useCallback(
(weight: number) => {
dispatch(controlNetWeightChanged({ controlNetId, weight }));
diff --git a/invokeai/frontend/web/src/features/controlNet/store/controlNetSlice.ts b/invokeai/frontend/web/src/features/controlNet/store/controlNetSlice.ts
index c735a47510..8e6f96add3 100644
--- a/invokeai/frontend/web/src/features/controlNet/store/controlNetSlice.ts
+++ b/invokeai/frontend/web/src/features/controlNet/store/controlNetSlice.ts
@@ -1,7 +1,7 @@
import { PayloadAction, createSlice } from '@reduxjs/toolkit';
import { RootState } from 'app/store/store';
import { ControlNetModelParam } from 'features/parameters/types/parameterSchemas';
-import { forEach } from 'lodash-es';
+import { cloneDeep, forEach } from 'lodash-es';
import { imageDeleted } from 'services/api/thunks/image';
import { isAnySessionRejected } from 'services/api/thunks/session';
import { appSocketInvocationError } from 'services/events/actions';
@@ -84,6 +84,19 @@ export const controlNetSlice = createSlice({
controlNetId,
};
},
+ controlNetDuplicated: (
+ state,
+ action: PayloadAction<{
+ sourceControlNetId: string;
+ newControlNetId: string;
+ }>
+ ) => {
+ const { sourceControlNetId, newControlNetId } = action.payload;
+
+ const newControlnet = cloneDeep(state.controlNets[sourceControlNetId]);
+ newControlnet.controlNetId = newControlNetId;
+ state.controlNets[newControlNetId] = newControlnet;
+ },
controlNetAddedFromImage: (
state,
action: PayloadAction<{ controlNetId: string; controlImage: string }>
@@ -315,6 +328,7 @@ export const controlNetSlice = createSlice({
export const {
isControlNetEnabledToggled,
controlNetAdded,
+ controlNetDuplicated,
controlNetAddedFromImage,
controlNetRemoved,
controlNetImageChanged,
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 59bf7542eb..201cf860c9 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
@@ -55,7 +55,7 @@ const ParamControlNetCollapse = () => {
{controlNetsArray.map((c, i) => (
{i > 0 && }
-
+
))}