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](aa4b56baf2)

- Dropping/uploading an image on control adapter enables it (controlnet
& ip adapter)
- The image components are always enabled to allow this
This commit is contained in:
blessedcoolant 2023-09-21 10:25:04 +05:30 committed by GitHub
commit 19e487b5ee
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
9 changed files with 72 additions and 40 deletions

View File

@ -4,7 +4,9 @@ import { parseify } from 'common/util/serialize';
import { setInitialCanvasImage } from 'features/canvas/store/canvasSlice';
import {
controlNetImageChanged,
controlNetIsEnabledChanged,
ipAdapterImageChanged,
isIPAdapterEnabledChanged,
} from 'features/controlNet/store/controlNetSlice';
import {
TypesafeDraggableData,
@ -99,6 +101,12 @@ export const addImageDroppedListener = () => {
controlNetId,
})
);
dispatch(
controlNetIsEnabledChanged({
controlNetId,
isEnabled: true,
})
);
return;
}
@ -111,6 +119,7 @@ export const addImageDroppedListener = () => {
activeData.payload.imageDTO
) {
dispatch(ipAdapterImageChanged(activeData.payload.imageDTO));
dispatch(isIPAdapterEnabledChanged(true));
return;
}

View File

@ -3,7 +3,9 @@ import { logger } from 'app/logging/logger';
import { setInitialCanvasImage } from 'features/canvas/store/canvasSlice';
import {
controlNetImageChanged,
controlNetIsEnabledChanged,
ipAdapterImageChanged,
isIPAdapterEnabledChanged,
} from 'features/controlNet/store/controlNetSlice';
import { fieldImageValueChanged } from 'features/nodes/store/nodesSlice';
import { initialImageChanged } from 'features/parameters/store/generationSlice';
@ -87,6 +89,12 @@ export const addImageUploadedFulfilledListener = () => {
if (postUploadAction?.type === 'SET_CONTROLNET_IMAGE') {
const { controlNetId } = postUploadAction;
dispatch(
controlNetIsEnabledChanged({
controlNetId,
isEnabled: true,
})
);
dispatch(
controlNetImageChanged({
controlNetId,
@ -104,6 +112,7 @@ export const addImageUploadedFulfilledListener = () => {
if (postUploadAction?.type === 'SET_IP_ADAPTER_IMAGE') {
dispatch(ipAdapterImageChanged(imageDTO));
dispatch(isIPAdapterEnabledChanged(true));
dispatch(
addToast({
...DEFAULT_UPLOADED_TOAST,

View File

@ -1,12 +1,12 @@
import { Box, Flex } from '@chakra-ui/react';
import { useAppDispatch, useAppSelector } from 'app/store/storeHooks';
import { memo, useCallback } from 'react';
import { ChangeEvent, memo, useCallback } from 'react';
import { FaCopy, FaTrash } from 'react-icons/fa';
import {
ControlNetConfig,
controlNetDuplicated,
controlNetRemoved,
controlNetToggled,
controlNetIsEnabledChanged,
} from '../store/controlNetSlice';
import ParamControlNetModel from './parameters/ParamControlNetModel';
import ParamControlNetWeight from './parameters/ParamControlNetWeight';
@ -77,9 +77,17 @@ const ControlNet = (props: ControlNetProps) => {
);
}, [controlNetId, dispatch]);
const handleToggleIsEnabled = useCallback(() => {
dispatch(controlNetToggled({ controlNetId }));
}, [controlNetId, dispatch]);
const handleToggleIsEnabled = useCallback(
(e: ChangeEvent<HTMLInputElement>) => {
dispatch(
controlNetIsEnabledChanged({
controlNetId,
isEnabled: e.target.checked,
})
);
},
[controlNetId, dispatch]
);
return (
<Flex
@ -106,8 +114,8 @@ const ControlNet = (props: ControlNetProps) => {
sx={{
w: 'full',
minW: 0,
opacity: isEnabled ? 1 : 0.5,
pointerEvents: isEnabled ? 'auto' : 'none',
// opacity: isEnabled ? 1 : 0.5,
// pointerEvents: isEnabled ? 'auto' : 'none',
transitionProperty: 'common',
transitionDuration: '0.1s',
}}

View File

@ -13,6 +13,7 @@ import {
import { setHeight, setWidth } from 'features/parameters/store/generationSlice';
import { activeTabNameSelector } from 'features/ui/store/uiSelectors';
import { memo, useCallback, useMemo, useState } from 'react';
import { useTranslation } from 'react-i18next';
import { FaRulerVertical, FaSave, FaUndo } from 'react-icons/fa';
import {
useAddImageToBoardMutation,
@ -26,7 +27,6 @@ import {
ControlNetConfig,
controlNetImageChanged,
} from '../store/controlNetSlice';
import { useTranslation } from 'react-i18next';
type Props = {
controlNet: ControlNetConfig;
@ -52,7 +52,6 @@ const ControlNetImagePreview = ({ isSmall, controlNet }: Props) => {
controlImage: controlImageName,
processedControlImage: processedControlImageName,
processorType,
isEnabled,
controlNetId,
} = controlNet;
@ -172,15 +171,13 @@ const ControlNetImagePreview = ({ isSmall, controlNet }: Props) => {
h: isSmall ? 28 : 366, // magic no touch
alignItems: 'center',
justifyContent: 'center',
pointerEvents: isEnabled ? 'auto' : 'none',
opacity: isEnabled ? 1 : 0.5,
}}
>
<IAIDndImage
draggableData={draggableData}
droppableData={droppableData}
imageDTO={controlImage}
isDropDisabled={shouldShowProcessedImage || !isEnabled}
isDropDisabled={shouldShowProcessedImage}
postUploadAction={postUploadAction}
/>
@ -202,7 +199,6 @@ const ControlNetImagePreview = ({ isSmall, controlNet }: Props) => {
droppableData={droppableData}
imageDTO={processedControlImage}
isUploadDisabled={true}
isDropDisabled={!isEnabled}
/>
</Box>

View File

@ -3,8 +3,8 @@ 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 { isIPAdapterEnabledChanged } from 'features/controlNet/store/controlNetSlice';
import { ChangeEvent, memo, useCallback } from 'react';
import { useTranslation } from 'react-i18next';
const selector = createSelector(
@ -22,9 +22,12 @@ const ParamIPAdapterFeatureToggle = () => {
const dispatch = useAppDispatch();
const { t } = useTranslation();
const handleChange = useCallback(() => {
dispatch(isIPAdapterEnableToggled());
}, [dispatch]);
const handleChange = useCallback(
(e: ChangeEvent<HTMLInputElement>) => {
dispatch(isIPAdapterEnabledChanged(e.target.checked));
},
[dispatch]
);
return (
<IAISwitch

View File

@ -1,7 +1,9 @@
import { Flex } from '@chakra-ui/react';
import { createSelector } from '@reduxjs/toolkit';
import { skipToken } from '@reduxjs/toolkit/dist/query';
import { RootState } from 'app/store/store';
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 IAIDndImageIcon from 'common/components/IAIDndImageIcon';
import { IAINoContentFallback } from 'common/components/IAIImageFallback';
@ -16,15 +18,17 @@ import { FaUndo } from 'react-icons/fa';
import { useGetImageDTOQuery } from 'services/api/endpoints/images';
import { PostUploadAction } from 'services/api/types';
const selector = createSelector(
stateSelector,
({ controlNet }) => {
const { ipAdapterInfo } = controlNet;
return { ipAdapterInfo };
},
defaultSelectorOptions
);
const ParamIPAdapterImage = () => {
const ipAdapterInfo = useAppSelector(
(state: RootState) => state.controlNet.ipAdapterInfo
);
const isIPAdapterEnabled = useAppSelector(
(state: RootState) => state.controlNet.isIPAdapterEnabled
);
const { ipAdapterInfo } = useAppSelector(selector);
const dispatch = useAppDispatch();
const { t } = useTranslation();
@ -71,8 +75,6 @@ const ParamIPAdapterImage = () => {
droppableData={droppableData}
draggableData={draggableData}
postUploadAction={postUploadAction}
isUploadDisabled={!isIPAdapterEnabled}
isDropDisabled={!isIPAdapterEnabled}
dropLabel={t('toast.setIPAdapterImage')}
noContentFallback={
<IAINoContentFallback

View File

@ -11,6 +11,9 @@ import { useTranslation } from 'react-i18next';
import { useGetIPAdapterModelsQuery } from 'services/api/endpoints/models';
const ParamIPAdapterModelSelect = () => {
const isEnabled = useAppSelector(
(state: RootState) => state.controlNet.isIPAdapterEnabled
);
const ipAdapterModel = useAppSelector(
(state: RootState) => state.controlNet.ipAdapterInfo.model
);
@ -90,6 +93,7 @@ const ParamIPAdapterModelSelect = () => {
data={data}
onChange={handleValueChanged}
sx={{ width: '100%' }}
disabled={!isEnabled}
/>
);
};

View File

@ -146,16 +146,16 @@ export const controlNetSlice = createSlice({
const { controlNetId } = action.payload;
delete state.controlNets[controlNetId];
},
controlNetToggled: (
controlNetIsEnabledChanged: (
state,
action: PayloadAction<{ controlNetId: string }>
action: PayloadAction<{ controlNetId: string; isEnabled: boolean }>
) => {
const { controlNetId } = action.payload;
const { controlNetId, isEnabled } = action.payload;
const cn = state.controlNets[controlNetId];
if (!cn) {
return;
}
cn.isEnabled = !cn.isEnabled;
cn.isEnabled = isEnabled;
},
controlNetImageChanged: (
state,
@ -377,8 +377,8 @@ export const controlNetSlice = createSlice({
controlNetReset: () => {
return { ...initialControlNetState };
},
isIPAdapterEnableToggled: (state) => {
state.isIPAdapterEnabled = !state.isIPAdapterEnabled;
isIPAdapterEnabledChanged: (state, action: PayloadAction<boolean>) => {
state.isIPAdapterEnabled = action.payload;
},
ipAdapterImageChanged: (state, action: PayloadAction<ImageDTO | null>) => {
state.ipAdapterInfo.adapterImage = action.payload;
@ -450,7 +450,7 @@ export const {
controlNetRemoved,
controlNetImageChanged,
controlNetProcessedImageChanged,
controlNetToggled,
controlNetIsEnabledChanged,
controlNetModelChanged,
controlNetWeightChanged,
controlNetBeginStepPctChanged,
@ -461,7 +461,7 @@ export const {
controlNetProcessorTypeChanged,
controlNetReset,
controlNetAutoConfigToggled,
isIPAdapterEnableToggled,
isIPAdapterEnabledChanged,
ipAdapterImageChanged,
ipAdapterWeightChanged,
ipAdapterModelChanged,

View File

@ -23,17 +23,18 @@ import { v4 as uuidv4 } from 'uuid';
const selector = createSelector(
[stateSelector],
({ controlNet }) => {
const { controlNets, isEnabled, isIPAdapterEnabled } = controlNet;
const { controlNets, isEnabled, isIPAdapterEnabled, ipAdapterInfo } =
controlNet;
const validControlNets = getValidControlNets(controlNets);
const isIPAdapterValid = ipAdapterInfo.model && ipAdapterInfo.adapterImage;
let activeLabel = undefined;
if (isEnabled && validControlNets.length > 0) {
activeLabel = `${validControlNets.length} ControlNet`;
}
if (isIPAdapterEnabled) {
if (isIPAdapterEnabled && isIPAdapterValid) {
if (activeLabel) {
activeLabel = `${activeLabel}, IP Adapter`;
} else {