Skip to content

Commit 19e487b

Browse files
feat(ui): enable control adapters on image drop (#4627)
## What type of PR is this? (check all applicable) - [ ] Refactor - [x] Feature - [ ] Bug Fix - [ ] Optimization - [ ] Documentation Update - [ ] Community Node Submission ## Have you discussed this change with the InvokeAI team? - [x] Yes - [ ] No, because: ## Description [feat(ui): enable control adapters on image drop](aa4b56b) - Dropping/uploading an image on control adapter enables it (controlnet & ip adapter) - The image components are always enabled to allow this
2 parents d3a2be6 + aa4b56b commit 19e487b

File tree

9 files changed

+72
-40
lines changed

9 files changed

+72
-40
lines changed

invokeai/frontend/web/src/app/store/middleware/listenerMiddleware/listeners/imageDropped.ts

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,9 @@ import { parseify } from 'common/util/serialize';
44
import { setInitialCanvasImage } from 'features/canvas/store/canvasSlice';
55
import {
66
controlNetImageChanged,
7+
controlNetIsEnabledChanged,
78
ipAdapterImageChanged,
9+
isIPAdapterEnabledChanged,
810
} from 'features/controlNet/store/controlNetSlice';
911
import {
1012
TypesafeDraggableData,
@@ -99,6 +101,12 @@ export const addImageDroppedListener = () => {
99101
controlNetId,
100102
})
101103
);
104+
dispatch(
105+
controlNetIsEnabledChanged({
106+
controlNetId,
107+
isEnabled: true,
108+
})
109+
);
102110
return;
103111
}
104112

@@ -111,6 +119,7 @@ export const addImageDroppedListener = () => {
111119
activeData.payload.imageDTO
112120
) {
113121
dispatch(ipAdapterImageChanged(activeData.payload.imageDTO));
122+
dispatch(isIPAdapterEnabledChanged(true));
114123
return;
115124
}
116125

invokeai/frontend/web/src/app/store/middleware/listenerMiddleware/listeners/imageUploaded.ts

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,9 @@ import { logger } from 'app/logging/logger';
33
import { setInitialCanvasImage } from 'features/canvas/store/canvasSlice';
44
import {
55
controlNetImageChanged,
6+
controlNetIsEnabledChanged,
67
ipAdapterImageChanged,
8+
isIPAdapterEnabledChanged,
79
} from 'features/controlNet/store/controlNetSlice';
810
import { fieldImageValueChanged } from 'features/nodes/store/nodesSlice';
911
import { initialImageChanged } from 'features/parameters/store/generationSlice';
@@ -87,6 +89,12 @@ export const addImageUploadedFulfilledListener = () => {
8789

8890
if (postUploadAction?.type === 'SET_CONTROLNET_IMAGE') {
8991
const { controlNetId } = postUploadAction;
92+
dispatch(
93+
controlNetIsEnabledChanged({
94+
controlNetId,
95+
isEnabled: true,
96+
})
97+
);
9098
dispatch(
9199
controlNetImageChanged({
92100
controlNetId,
@@ -104,6 +112,7 @@ export const addImageUploadedFulfilledListener = () => {
104112

105113
if (postUploadAction?.type === 'SET_IP_ADAPTER_IMAGE') {
106114
dispatch(ipAdapterImageChanged(imageDTO));
115+
dispatch(isIPAdapterEnabledChanged(true));
107116
dispatch(
108117
addToast({
109118
...DEFAULT_UPLOADED_TOAST,

invokeai/frontend/web/src/features/controlNet/components/ControlNet.tsx

Lines changed: 15 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,12 @@
11
import { Box, Flex } from '@chakra-ui/react';
22
import { useAppDispatch, useAppSelector } from 'app/store/storeHooks';
3-
import { memo, useCallback } from 'react';
3+
import { ChangeEvent, memo, useCallback } from 'react';
44
import { FaCopy, FaTrash } from 'react-icons/fa';
55
import {
66
ControlNetConfig,
77
controlNetDuplicated,
88
controlNetRemoved,
9-
controlNetToggled,
9+
controlNetIsEnabledChanged,
1010
} from '../store/controlNetSlice';
1111
import ParamControlNetModel from './parameters/ParamControlNetModel';
1212
import ParamControlNetWeight from './parameters/ParamControlNetWeight';
@@ -77,9 +77,17 @@ const ControlNet = (props: ControlNetProps) => {
7777
);
7878
}, [controlNetId, dispatch]);
7979

80-
const handleToggleIsEnabled = useCallback(() => {
81-
dispatch(controlNetToggled({ controlNetId }));
82-
}, [controlNetId, dispatch]);
80+
const handleToggleIsEnabled = useCallback(
81+
(e: ChangeEvent<HTMLInputElement>) => {
82+
dispatch(
83+
controlNetIsEnabledChanged({
84+
controlNetId,
85+
isEnabled: e.target.checked,
86+
})
87+
);
88+
},
89+
[controlNetId, dispatch]
90+
);
8391

8492
return (
8593
<Flex
@@ -106,8 +114,8 @@ const ControlNet = (props: ControlNetProps) => {
106114
sx={{
107115
w: 'full',
108116
minW: 0,
109-
opacity: isEnabled ? 1 : 0.5,
110-
pointerEvents: isEnabled ? 'auto' : 'none',
117+
// opacity: isEnabled ? 1 : 0.5,
118+
// pointerEvents: isEnabled ? 'auto' : 'none',
111119
transitionProperty: 'common',
112120
transitionDuration: '0.1s',
113121
}}

invokeai/frontend/web/src/features/controlNet/components/ControlNetImagePreview.tsx

Lines changed: 2 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ import {
1313
import { setHeight, setWidth } from 'features/parameters/store/generationSlice';
1414
import { activeTabNameSelector } from 'features/ui/store/uiSelectors';
1515
import { memo, useCallback, useMemo, useState } from 'react';
16+
import { useTranslation } from 'react-i18next';
1617
import { FaRulerVertical, FaSave, FaUndo } from 'react-icons/fa';
1718
import {
1819
useAddImageToBoardMutation,
@@ -26,7 +27,6 @@ import {
2627
ControlNetConfig,
2728
controlNetImageChanged,
2829
} from '../store/controlNetSlice';
29-
import { useTranslation } from 'react-i18next';
3030

3131
type Props = {
3232
controlNet: ControlNetConfig;
@@ -52,7 +52,6 @@ const ControlNetImagePreview = ({ isSmall, controlNet }: Props) => {
5252
controlImage: controlImageName,
5353
processedControlImage: processedControlImageName,
5454
processorType,
55-
isEnabled,
5655
controlNetId,
5756
} = controlNet;
5857

@@ -172,15 +171,13 @@ const ControlNetImagePreview = ({ isSmall, controlNet }: Props) => {
172171
h: isSmall ? 28 : 366, // magic no touch
173172
alignItems: 'center',
174173
justifyContent: 'center',
175-
pointerEvents: isEnabled ? 'auto' : 'none',
176-
opacity: isEnabled ? 1 : 0.5,
177174
}}
178175
>
179176
<IAIDndImage
180177
draggableData={draggableData}
181178
droppableData={droppableData}
182179
imageDTO={controlImage}
183-
isDropDisabled={shouldShowProcessedImage || !isEnabled}
180+
isDropDisabled={shouldShowProcessedImage}
184181
postUploadAction={postUploadAction}
185182
/>
186183

@@ -202,7 +199,6 @@ const ControlNetImagePreview = ({ isSmall, controlNet }: Props) => {
202199
droppableData={droppableData}
203200
imageDTO={processedControlImage}
204201
isUploadDisabled={true}
205-
isDropDisabled={!isEnabled}
206202
/>
207203
</Box>
208204

invokeai/frontend/web/src/features/controlNet/components/ipAdapter/ParamIPAdapterFeatureToggle.tsx

Lines changed: 8 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -3,8 +3,8 @@ import { stateSelector } from 'app/store/store';
33
import { useAppDispatch, useAppSelector } from 'app/store/storeHooks';
44
import { defaultSelectorOptions } from 'app/store/util/defaultMemoizeOptions';
55
import IAISwitch from 'common/components/IAISwitch';
6-
import { isIPAdapterEnableToggled } from 'features/controlNet/store/controlNetSlice';
7-
import { memo, useCallback } from 'react';
6+
import { isIPAdapterEnabledChanged } from 'features/controlNet/store/controlNetSlice';
7+
import { ChangeEvent, memo, useCallback } from 'react';
88
import { useTranslation } from 'react-i18next';
99

1010
const selector = createSelector(
@@ -22,9 +22,12 @@ const ParamIPAdapterFeatureToggle = () => {
2222
const dispatch = useAppDispatch();
2323
const { t } = useTranslation();
2424

25-
const handleChange = useCallback(() => {
26-
dispatch(isIPAdapterEnableToggled());
27-
}, [dispatch]);
25+
const handleChange = useCallback(
26+
(e: ChangeEvent<HTMLInputElement>) => {
27+
dispatch(isIPAdapterEnabledChanged(e.target.checked));
28+
},
29+
[dispatch]
30+
);
2831

2932
return (
3033
<IAISwitch

invokeai/frontend/web/src/features/controlNet/components/ipAdapter/ParamIPAdapterImage.tsx

Lines changed: 13 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,9 @@
11
import { Flex } from '@chakra-ui/react';
2+
import { createSelector } from '@reduxjs/toolkit';
23
import { skipToken } from '@reduxjs/toolkit/dist/query';
3-
import { RootState } from 'app/store/store';
4+
import { stateSelector } from 'app/store/store';
45
import { useAppDispatch, useAppSelector } from 'app/store/storeHooks';
6+
import { defaultSelectorOptions } from 'app/store/util/defaultMemoizeOptions';
57
import IAIDndImage from 'common/components/IAIDndImage';
68
import IAIDndImageIcon from 'common/components/IAIDndImageIcon';
79
import { IAINoContentFallback } from 'common/components/IAIImageFallback';
@@ -16,15 +18,17 @@ import { FaUndo } from 'react-icons/fa';
1618
import { useGetImageDTOQuery } from 'services/api/endpoints/images';
1719
import { PostUploadAction } from 'services/api/types';
1820

19-
const ParamIPAdapterImage = () => {
20-
const ipAdapterInfo = useAppSelector(
21-
(state: RootState) => state.controlNet.ipAdapterInfo
22-
);
23-
24-
const isIPAdapterEnabled = useAppSelector(
25-
(state: RootState) => state.controlNet.isIPAdapterEnabled
26-
);
21+
const selector = createSelector(
22+
stateSelector,
23+
({ controlNet }) => {
24+
const { ipAdapterInfo } = controlNet;
25+
return { ipAdapterInfo };
26+
},
27+
defaultSelectorOptions
28+
);
2729

30+
const ParamIPAdapterImage = () => {
31+
const { ipAdapterInfo } = useAppSelector(selector);
2832
const dispatch = useAppDispatch();
2933
const { t } = useTranslation();
3034

@@ -71,8 +75,6 @@ const ParamIPAdapterImage = () => {
7175
droppableData={droppableData}
7276
draggableData={draggableData}
7377
postUploadAction={postUploadAction}
74-
isUploadDisabled={!isIPAdapterEnabled}
75-
isDropDisabled={!isIPAdapterEnabled}
7678
dropLabel={t('toast.setIPAdapterImage')}
7779
noContentFallback={
7880
<IAINoContentFallback

invokeai/frontend/web/src/features/controlNet/components/ipAdapter/ParamIPAdapterModelSelect.tsx

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,9 @@ import { useTranslation } from 'react-i18next';
1111
import { useGetIPAdapterModelsQuery } from 'services/api/endpoints/models';
1212

1313
const ParamIPAdapterModelSelect = () => {
14+
const isEnabled = useAppSelector(
15+
(state: RootState) => state.controlNet.isIPAdapterEnabled
16+
);
1417
const ipAdapterModel = useAppSelector(
1518
(state: RootState) => state.controlNet.ipAdapterInfo.model
1619
);
@@ -90,6 +93,7 @@ const ParamIPAdapterModelSelect = () => {
9093
data={data}
9194
onChange={handleValueChanged}
9295
sx={{ width: '100%' }}
96+
disabled={!isEnabled}
9397
/>
9498
);
9599
};

invokeai/frontend/web/src/features/controlNet/store/controlNetSlice.ts

Lines changed: 8 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -146,16 +146,16 @@ export const controlNetSlice = createSlice({
146146
const { controlNetId } = action.payload;
147147
delete state.controlNets[controlNetId];
148148
},
149-
controlNetToggled: (
149+
controlNetIsEnabledChanged: (
150150
state,
151-
action: PayloadAction<{ controlNetId: string }>
151+
action: PayloadAction<{ controlNetId: string; isEnabled: boolean }>
152152
) => {
153-
const { controlNetId } = action.payload;
153+
const { controlNetId, isEnabled } = action.payload;
154154
const cn = state.controlNets[controlNetId];
155155
if (!cn) {
156156
return;
157157
}
158-
cn.isEnabled = !cn.isEnabled;
158+
cn.isEnabled = isEnabled;
159159
},
160160
controlNetImageChanged: (
161161
state,
@@ -377,8 +377,8 @@ export const controlNetSlice = createSlice({
377377
controlNetReset: () => {
378378
return { ...initialControlNetState };
379379
},
380-
isIPAdapterEnableToggled: (state) => {
381-
state.isIPAdapterEnabled = !state.isIPAdapterEnabled;
380+
isIPAdapterEnabledChanged: (state, action: PayloadAction<boolean>) => {
381+
state.isIPAdapterEnabled = action.payload;
382382
},
383383
ipAdapterImageChanged: (state, action: PayloadAction<ImageDTO | null>) => {
384384
state.ipAdapterInfo.adapterImage = action.payload;
@@ -450,7 +450,7 @@ export const {
450450
controlNetRemoved,
451451
controlNetImageChanged,
452452
controlNetProcessedImageChanged,
453-
controlNetToggled,
453+
controlNetIsEnabledChanged,
454454
controlNetModelChanged,
455455
controlNetWeightChanged,
456456
controlNetBeginStepPctChanged,
@@ -461,7 +461,7 @@ export const {
461461
controlNetProcessorTypeChanged,
462462
controlNetReset,
463463
controlNetAutoConfigToggled,
464-
isIPAdapterEnableToggled,
464+
isIPAdapterEnabledChanged,
465465
ipAdapterImageChanged,
466466
ipAdapterWeightChanged,
467467
ipAdapterModelChanged,

invokeai/frontend/web/src/features/parameters/components/Parameters/ControlNet/ParamControlNetCollapse.tsx

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -23,17 +23,18 @@ import { v4 as uuidv4 } from 'uuid';
2323
const selector = createSelector(
2424
[stateSelector],
2525
({ controlNet }) => {
26-
const { controlNets, isEnabled, isIPAdapterEnabled } = controlNet;
26+
const { controlNets, isEnabled, isIPAdapterEnabled, ipAdapterInfo } =
27+
controlNet;
2728

2829
const validControlNets = getValidControlNets(controlNets);
29-
30+
const isIPAdapterValid = ipAdapterInfo.model && ipAdapterInfo.adapterImage;
3031
let activeLabel = undefined;
3132

3233
if (isEnabled && validControlNets.length > 0) {
3334
activeLabel = `${validControlNets.length} ControlNet`;
3435
}
3536

36-
if (isIPAdapterEnabled) {
37+
if (isIPAdapterEnabled && isIPAdapterValid) {
3738
if (activeLabel) {
3839
activeLabel = `${activeLabel}, IP Adapter`;
3940
} else {

0 commit comments

Comments
 (0)