mirror of
https://github.com/invoke-ai/InvokeAI
synced 2024-08-30 20:32:17 +00:00
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:
commit
19e487b5ee
@ -4,7 +4,9 @@ import { parseify } from 'common/util/serialize';
|
|||||||
import { setInitialCanvasImage } from 'features/canvas/store/canvasSlice';
|
import { setInitialCanvasImage } from 'features/canvas/store/canvasSlice';
|
||||||
import {
|
import {
|
||||||
controlNetImageChanged,
|
controlNetImageChanged,
|
||||||
|
controlNetIsEnabledChanged,
|
||||||
ipAdapterImageChanged,
|
ipAdapterImageChanged,
|
||||||
|
isIPAdapterEnabledChanged,
|
||||||
} from 'features/controlNet/store/controlNetSlice';
|
} from 'features/controlNet/store/controlNetSlice';
|
||||||
import {
|
import {
|
||||||
TypesafeDraggableData,
|
TypesafeDraggableData,
|
||||||
@ -99,6 +101,12 @@ export const addImageDroppedListener = () => {
|
|||||||
controlNetId,
|
controlNetId,
|
||||||
})
|
})
|
||||||
);
|
);
|
||||||
|
dispatch(
|
||||||
|
controlNetIsEnabledChanged({
|
||||||
|
controlNetId,
|
||||||
|
isEnabled: true,
|
||||||
|
})
|
||||||
|
);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -111,6 +119,7 @@ export const addImageDroppedListener = () => {
|
|||||||
activeData.payload.imageDTO
|
activeData.payload.imageDTO
|
||||||
) {
|
) {
|
||||||
dispatch(ipAdapterImageChanged(activeData.payload.imageDTO));
|
dispatch(ipAdapterImageChanged(activeData.payload.imageDTO));
|
||||||
|
dispatch(isIPAdapterEnabledChanged(true));
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -3,7 +3,9 @@ import { logger } from 'app/logging/logger';
|
|||||||
import { setInitialCanvasImage } from 'features/canvas/store/canvasSlice';
|
import { setInitialCanvasImage } from 'features/canvas/store/canvasSlice';
|
||||||
import {
|
import {
|
||||||
controlNetImageChanged,
|
controlNetImageChanged,
|
||||||
|
controlNetIsEnabledChanged,
|
||||||
ipAdapterImageChanged,
|
ipAdapterImageChanged,
|
||||||
|
isIPAdapterEnabledChanged,
|
||||||
} from 'features/controlNet/store/controlNetSlice';
|
} from 'features/controlNet/store/controlNetSlice';
|
||||||
import { fieldImageValueChanged } from 'features/nodes/store/nodesSlice';
|
import { fieldImageValueChanged } from 'features/nodes/store/nodesSlice';
|
||||||
import { initialImageChanged } from 'features/parameters/store/generationSlice';
|
import { initialImageChanged } from 'features/parameters/store/generationSlice';
|
||||||
@ -87,6 +89,12 @@ export const addImageUploadedFulfilledListener = () => {
|
|||||||
|
|
||||||
if (postUploadAction?.type === 'SET_CONTROLNET_IMAGE') {
|
if (postUploadAction?.type === 'SET_CONTROLNET_IMAGE') {
|
||||||
const { controlNetId } = postUploadAction;
|
const { controlNetId } = postUploadAction;
|
||||||
|
dispatch(
|
||||||
|
controlNetIsEnabledChanged({
|
||||||
|
controlNetId,
|
||||||
|
isEnabled: true,
|
||||||
|
})
|
||||||
|
);
|
||||||
dispatch(
|
dispatch(
|
||||||
controlNetImageChanged({
|
controlNetImageChanged({
|
||||||
controlNetId,
|
controlNetId,
|
||||||
@ -104,6 +112,7 @@ export const addImageUploadedFulfilledListener = () => {
|
|||||||
|
|
||||||
if (postUploadAction?.type === 'SET_IP_ADAPTER_IMAGE') {
|
if (postUploadAction?.type === 'SET_IP_ADAPTER_IMAGE') {
|
||||||
dispatch(ipAdapterImageChanged(imageDTO));
|
dispatch(ipAdapterImageChanged(imageDTO));
|
||||||
|
dispatch(isIPAdapterEnabledChanged(true));
|
||||||
dispatch(
|
dispatch(
|
||||||
addToast({
|
addToast({
|
||||||
...DEFAULT_UPLOADED_TOAST,
|
...DEFAULT_UPLOADED_TOAST,
|
||||||
|
@ -1,12 +1,12 @@
|
|||||||
import { Box, Flex } from '@chakra-ui/react';
|
import { Box, Flex } from '@chakra-ui/react';
|
||||||
import { useAppDispatch, useAppSelector } from 'app/store/storeHooks';
|
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 { FaCopy, FaTrash } from 'react-icons/fa';
|
||||||
import {
|
import {
|
||||||
ControlNetConfig,
|
ControlNetConfig,
|
||||||
controlNetDuplicated,
|
controlNetDuplicated,
|
||||||
controlNetRemoved,
|
controlNetRemoved,
|
||||||
controlNetToggled,
|
controlNetIsEnabledChanged,
|
||||||
} from '../store/controlNetSlice';
|
} from '../store/controlNetSlice';
|
||||||
import ParamControlNetModel from './parameters/ParamControlNetModel';
|
import ParamControlNetModel from './parameters/ParamControlNetModel';
|
||||||
import ParamControlNetWeight from './parameters/ParamControlNetWeight';
|
import ParamControlNetWeight from './parameters/ParamControlNetWeight';
|
||||||
@ -77,9 +77,17 @@ const ControlNet = (props: ControlNetProps) => {
|
|||||||
);
|
);
|
||||||
}, [controlNetId, dispatch]);
|
}, [controlNetId, dispatch]);
|
||||||
|
|
||||||
const handleToggleIsEnabled = useCallback(() => {
|
const handleToggleIsEnabled = useCallback(
|
||||||
dispatch(controlNetToggled({ controlNetId }));
|
(e: ChangeEvent<HTMLInputElement>) => {
|
||||||
}, [controlNetId, dispatch]);
|
dispatch(
|
||||||
|
controlNetIsEnabledChanged({
|
||||||
|
controlNetId,
|
||||||
|
isEnabled: e.target.checked,
|
||||||
|
})
|
||||||
|
);
|
||||||
|
},
|
||||||
|
[controlNetId, dispatch]
|
||||||
|
);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Flex
|
<Flex
|
||||||
@ -106,8 +114,8 @@ const ControlNet = (props: ControlNetProps) => {
|
|||||||
sx={{
|
sx={{
|
||||||
w: 'full',
|
w: 'full',
|
||||||
minW: 0,
|
minW: 0,
|
||||||
opacity: isEnabled ? 1 : 0.5,
|
// opacity: isEnabled ? 1 : 0.5,
|
||||||
pointerEvents: isEnabled ? 'auto' : 'none',
|
// pointerEvents: isEnabled ? 'auto' : 'none',
|
||||||
transitionProperty: 'common',
|
transitionProperty: 'common',
|
||||||
transitionDuration: '0.1s',
|
transitionDuration: '0.1s',
|
||||||
}}
|
}}
|
||||||
|
@ -13,6 +13,7 @@ import {
|
|||||||
import { setHeight, setWidth } from 'features/parameters/store/generationSlice';
|
import { setHeight, setWidth } from 'features/parameters/store/generationSlice';
|
||||||
import { activeTabNameSelector } from 'features/ui/store/uiSelectors';
|
import { activeTabNameSelector } from 'features/ui/store/uiSelectors';
|
||||||
import { memo, useCallback, useMemo, useState } from 'react';
|
import { memo, useCallback, useMemo, useState } from 'react';
|
||||||
|
import { useTranslation } from 'react-i18next';
|
||||||
import { FaRulerVertical, FaSave, FaUndo } from 'react-icons/fa';
|
import { FaRulerVertical, FaSave, FaUndo } from 'react-icons/fa';
|
||||||
import {
|
import {
|
||||||
useAddImageToBoardMutation,
|
useAddImageToBoardMutation,
|
||||||
@ -26,7 +27,6 @@ import {
|
|||||||
ControlNetConfig,
|
ControlNetConfig,
|
||||||
controlNetImageChanged,
|
controlNetImageChanged,
|
||||||
} from '../store/controlNetSlice';
|
} from '../store/controlNetSlice';
|
||||||
import { useTranslation } from 'react-i18next';
|
|
||||||
|
|
||||||
type Props = {
|
type Props = {
|
||||||
controlNet: ControlNetConfig;
|
controlNet: ControlNetConfig;
|
||||||
@ -52,7 +52,6 @@ const ControlNetImagePreview = ({ isSmall, controlNet }: Props) => {
|
|||||||
controlImage: controlImageName,
|
controlImage: controlImageName,
|
||||||
processedControlImage: processedControlImageName,
|
processedControlImage: processedControlImageName,
|
||||||
processorType,
|
processorType,
|
||||||
isEnabled,
|
|
||||||
controlNetId,
|
controlNetId,
|
||||||
} = controlNet;
|
} = controlNet;
|
||||||
|
|
||||||
@ -172,15 +171,13 @@ const ControlNetImagePreview = ({ isSmall, controlNet }: Props) => {
|
|||||||
h: isSmall ? 28 : 366, // magic no touch
|
h: isSmall ? 28 : 366, // magic no touch
|
||||||
alignItems: 'center',
|
alignItems: 'center',
|
||||||
justifyContent: 'center',
|
justifyContent: 'center',
|
||||||
pointerEvents: isEnabled ? 'auto' : 'none',
|
|
||||||
opacity: isEnabled ? 1 : 0.5,
|
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<IAIDndImage
|
<IAIDndImage
|
||||||
draggableData={draggableData}
|
draggableData={draggableData}
|
||||||
droppableData={droppableData}
|
droppableData={droppableData}
|
||||||
imageDTO={controlImage}
|
imageDTO={controlImage}
|
||||||
isDropDisabled={shouldShowProcessedImage || !isEnabled}
|
isDropDisabled={shouldShowProcessedImage}
|
||||||
postUploadAction={postUploadAction}
|
postUploadAction={postUploadAction}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
@ -202,7 +199,6 @@ const ControlNetImagePreview = ({ isSmall, controlNet }: Props) => {
|
|||||||
droppableData={droppableData}
|
droppableData={droppableData}
|
||||||
imageDTO={processedControlImage}
|
imageDTO={processedControlImage}
|
||||||
isUploadDisabled={true}
|
isUploadDisabled={true}
|
||||||
isDropDisabled={!isEnabled}
|
|
||||||
/>
|
/>
|
||||||
</Box>
|
</Box>
|
||||||
|
|
||||||
|
@ -3,8 +3,8 @@ import { stateSelector } from 'app/store/store';
|
|||||||
import { useAppDispatch, useAppSelector } from 'app/store/storeHooks';
|
import { useAppDispatch, useAppSelector } from 'app/store/storeHooks';
|
||||||
import { defaultSelectorOptions } from 'app/store/util/defaultMemoizeOptions';
|
import { defaultSelectorOptions } from 'app/store/util/defaultMemoizeOptions';
|
||||||
import IAISwitch from 'common/components/IAISwitch';
|
import IAISwitch from 'common/components/IAISwitch';
|
||||||
import { isIPAdapterEnableToggled } from 'features/controlNet/store/controlNetSlice';
|
import { isIPAdapterEnabledChanged } from 'features/controlNet/store/controlNetSlice';
|
||||||
import { memo, useCallback } from 'react';
|
import { ChangeEvent, memo, useCallback } from 'react';
|
||||||
import { useTranslation } from 'react-i18next';
|
import { useTranslation } from 'react-i18next';
|
||||||
|
|
||||||
const selector = createSelector(
|
const selector = createSelector(
|
||||||
@ -22,9 +22,12 @@ const ParamIPAdapterFeatureToggle = () => {
|
|||||||
const dispatch = useAppDispatch();
|
const dispatch = useAppDispatch();
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
|
|
||||||
const handleChange = useCallback(() => {
|
const handleChange = useCallback(
|
||||||
dispatch(isIPAdapterEnableToggled());
|
(e: ChangeEvent<HTMLInputElement>) => {
|
||||||
}, [dispatch]);
|
dispatch(isIPAdapterEnabledChanged(e.target.checked));
|
||||||
|
},
|
||||||
|
[dispatch]
|
||||||
|
);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<IAISwitch
|
<IAISwitch
|
||||||
|
@ -1,7 +1,9 @@
|
|||||||
import { Flex } from '@chakra-ui/react';
|
import { Flex } from '@chakra-ui/react';
|
||||||
|
import { createSelector } from '@reduxjs/toolkit';
|
||||||
import { skipToken } from '@reduxjs/toolkit/dist/query';
|
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 { useAppDispatch, useAppSelector } from 'app/store/storeHooks';
|
||||||
|
import { defaultSelectorOptions } from 'app/store/util/defaultMemoizeOptions';
|
||||||
import IAIDndImage from 'common/components/IAIDndImage';
|
import IAIDndImage from 'common/components/IAIDndImage';
|
||||||
import IAIDndImageIcon from 'common/components/IAIDndImageIcon';
|
import IAIDndImageIcon from 'common/components/IAIDndImageIcon';
|
||||||
import { IAINoContentFallback } from 'common/components/IAIImageFallback';
|
import { IAINoContentFallback } from 'common/components/IAIImageFallback';
|
||||||
@ -16,15 +18,17 @@ import { FaUndo } from 'react-icons/fa';
|
|||||||
import { useGetImageDTOQuery } from 'services/api/endpoints/images';
|
import { useGetImageDTOQuery } from 'services/api/endpoints/images';
|
||||||
import { PostUploadAction } from 'services/api/types';
|
import { PostUploadAction } from 'services/api/types';
|
||||||
|
|
||||||
|
const selector = createSelector(
|
||||||
|
stateSelector,
|
||||||
|
({ controlNet }) => {
|
||||||
|
const { ipAdapterInfo } = controlNet;
|
||||||
|
return { ipAdapterInfo };
|
||||||
|
},
|
||||||
|
defaultSelectorOptions
|
||||||
|
);
|
||||||
|
|
||||||
const ParamIPAdapterImage = () => {
|
const ParamIPAdapterImage = () => {
|
||||||
const ipAdapterInfo = useAppSelector(
|
const { ipAdapterInfo } = useAppSelector(selector);
|
||||||
(state: RootState) => state.controlNet.ipAdapterInfo
|
|
||||||
);
|
|
||||||
|
|
||||||
const isIPAdapterEnabled = useAppSelector(
|
|
||||||
(state: RootState) => state.controlNet.isIPAdapterEnabled
|
|
||||||
);
|
|
||||||
|
|
||||||
const dispatch = useAppDispatch();
|
const dispatch = useAppDispatch();
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
|
|
||||||
@ -71,8 +75,6 @@ const ParamIPAdapterImage = () => {
|
|||||||
droppableData={droppableData}
|
droppableData={droppableData}
|
||||||
draggableData={draggableData}
|
draggableData={draggableData}
|
||||||
postUploadAction={postUploadAction}
|
postUploadAction={postUploadAction}
|
||||||
isUploadDisabled={!isIPAdapterEnabled}
|
|
||||||
isDropDisabled={!isIPAdapterEnabled}
|
|
||||||
dropLabel={t('toast.setIPAdapterImage')}
|
dropLabel={t('toast.setIPAdapterImage')}
|
||||||
noContentFallback={
|
noContentFallback={
|
||||||
<IAINoContentFallback
|
<IAINoContentFallback
|
||||||
|
@ -11,6 +11,9 @@ import { useTranslation } from 'react-i18next';
|
|||||||
import { useGetIPAdapterModelsQuery } from 'services/api/endpoints/models';
|
import { useGetIPAdapterModelsQuery } from 'services/api/endpoints/models';
|
||||||
|
|
||||||
const ParamIPAdapterModelSelect = () => {
|
const ParamIPAdapterModelSelect = () => {
|
||||||
|
const isEnabled = useAppSelector(
|
||||||
|
(state: RootState) => state.controlNet.isIPAdapterEnabled
|
||||||
|
);
|
||||||
const ipAdapterModel = useAppSelector(
|
const ipAdapterModel = useAppSelector(
|
||||||
(state: RootState) => state.controlNet.ipAdapterInfo.model
|
(state: RootState) => state.controlNet.ipAdapterInfo.model
|
||||||
);
|
);
|
||||||
@ -90,6 +93,7 @@ const ParamIPAdapterModelSelect = () => {
|
|||||||
data={data}
|
data={data}
|
||||||
onChange={handleValueChanged}
|
onChange={handleValueChanged}
|
||||||
sx={{ width: '100%' }}
|
sx={{ width: '100%' }}
|
||||||
|
disabled={!isEnabled}
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
@ -146,16 +146,16 @@ export const controlNetSlice = createSlice({
|
|||||||
const { controlNetId } = action.payload;
|
const { controlNetId } = action.payload;
|
||||||
delete state.controlNets[controlNetId];
|
delete state.controlNets[controlNetId];
|
||||||
},
|
},
|
||||||
controlNetToggled: (
|
controlNetIsEnabledChanged: (
|
||||||
state,
|
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];
|
const cn = state.controlNets[controlNetId];
|
||||||
if (!cn) {
|
if (!cn) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
cn.isEnabled = !cn.isEnabled;
|
cn.isEnabled = isEnabled;
|
||||||
},
|
},
|
||||||
controlNetImageChanged: (
|
controlNetImageChanged: (
|
||||||
state,
|
state,
|
||||||
@ -377,8 +377,8 @@ export const controlNetSlice = createSlice({
|
|||||||
controlNetReset: () => {
|
controlNetReset: () => {
|
||||||
return { ...initialControlNetState };
|
return { ...initialControlNetState };
|
||||||
},
|
},
|
||||||
isIPAdapterEnableToggled: (state) => {
|
isIPAdapterEnabledChanged: (state, action: PayloadAction<boolean>) => {
|
||||||
state.isIPAdapterEnabled = !state.isIPAdapterEnabled;
|
state.isIPAdapterEnabled = action.payload;
|
||||||
},
|
},
|
||||||
ipAdapterImageChanged: (state, action: PayloadAction<ImageDTO | null>) => {
|
ipAdapterImageChanged: (state, action: PayloadAction<ImageDTO | null>) => {
|
||||||
state.ipAdapterInfo.adapterImage = action.payload;
|
state.ipAdapterInfo.adapterImage = action.payload;
|
||||||
@ -450,7 +450,7 @@ export const {
|
|||||||
controlNetRemoved,
|
controlNetRemoved,
|
||||||
controlNetImageChanged,
|
controlNetImageChanged,
|
||||||
controlNetProcessedImageChanged,
|
controlNetProcessedImageChanged,
|
||||||
controlNetToggled,
|
controlNetIsEnabledChanged,
|
||||||
controlNetModelChanged,
|
controlNetModelChanged,
|
||||||
controlNetWeightChanged,
|
controlNetWeightChanged,
|
||||||
controlNetBeginStepPctChanged,
|
controlNetBeginStepPctChanged,
|
||||||
@ -461,7 +461,7 @@ export const {
|
|||||||
controlNetProcessorTypeChanged,
|
controlNetProcessorTypeChanged,
|
||||||
controlNetReset,
|
controlNetReset,
|
||||||
controlNetAutoConfigToggled,
|
controlNetAutoConfigToggled,
|
||||||
isIPAdapterEnableToggled,
|
isIPAdapterEnabledChanged,
|
||||||
ipAdapterImageChanged,
|
ipAdapterImageChanged,
|
||||||
ipAdapterWeightChanged,
|
ipAdapterWeightChanged,
|
||||||
ipAdapterModelChanged,
|
ipAdapterModelChanged,
|
||||||
|
@ -23,17 +23,18 @@ import { v4 as uuidv4 } from 'uuid';
|
|||||||
const selector = createSelector(
|
const selector = createSelector(
|
||||||
[stateSelector],
|
[stateSelector],
|
||||||
({ controlNet }) => {
|
({ controlNet }) => {
|
||||||
const { controlNets, isEnabled, isIPAdapterEnabled } = controlNet;
|
const { controlNets, isEnabled, isIPAdapterEnabled, ipAdapterInfo } =
|
||||||
|
controlNet;
|
||||||
|
|
||||||
const validControlNets = getValidControlNets(controlNets);
|
const validControlNets = getValidControlNets(controlNets);
|
||||||
|
const isIPAdapterValid = ipAdapterInfo.model && ipAdapterInfo.adapterImage;
|
||||||
let activeLabel = undefined;
|
let activeLabel = undefined;
|
||||||
|
|
||||||
if (isEnabled && validControlNets.length > 0) {
|
if (isEnabled && validControlNets.length > 0) {
|
||||||
activeLabel = `${validControlNets.length} ControlNet`;
|
activeLabel = `${validControlNets.length} ControlNet`;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (isIPAdapterEnabled) {
|
if (isIPAdapterEnabled && isIPAdapterValid) {
|
||||||
if (activeLabel) {
|
if (activeLabel) {
|
||||||
activeLabel = `${activeLabel}, IP Adapter`;
|
activeLabel = `${activeLabel}, IP Adapter`;
|
||||||
} else {
|
} else {
|
||||||
|
Loading…
Reference in New Issue
Block a user