mirror of
https://github.com/invoke-ai/InvokeAI
synced 2024-08-30 20:32:17 +00:00
feat(ui): more work on controlnet mini
This commit is contained in:
parent
b6b3b9f99c
commit
a0dde66b5d
@ -48,7 +48,7 @@ const IAIDndImage = (props: IAIDndImageProps) => {
|
|||||||
isDragDisabled = false,
|
isDragDisabled = false,
|
||||||
fallback = <IAIImageFallback />,
|
fallback = <IAIImageFallback />,
|
||||||
payloadImage,
|
payloadImage,
|
||||||
minSize = 36,
|
minSize = 24,
|
||||||
} = props;
|
} = props;
|
||||||
const dndId = useRef(uuidv4());
|
const dndId = useRef(uuidv4());
|
||||||
const { getUrl } = useGetUrl();
|
const { getUrl } = useGetUrl();
|
||||||
|
@ -1,8 +1,8 @@
|
|||||||
import { Checkbox, CheckboxProps, Text } from '@chakra-ui/react';
|
import { Checkbox, CheckboxProps, Text } from '@chakra-ui/react';
|
||||||
import { memo, ReactNode } from 'react';
|
import { memo, ReactElement } from 'react';
|
||||||
|
|
||||||
type IAISimpleCheckboxProps = CheckboxProps & {
|
type IAISimpleCheckboxProps = CheckboxProps & {
|
||||||
label: string | ReactNode;
|
label: string | ReactElement;
|
||||||
};
|
};
|
||||||
|
|
||||||
const IAISimpleCheckbox = (props: IAISimpleCheckboxProps) => {
|
const IAISimpleCheckbox = (props: IAISimpleCheckboxProps) => {
|
||||||
|
@ -58,11 +58,7 @@ const ControlNet = (props: ControlNetProps) => {
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<Flex sx={{ flexDir: 'column', gap: 3 }}>
|
<Flex sx={{ flexDir: 'column', gap: 3 }}>
|
||||||
<ControlNetImagePreview
|
<ControlNetImagePreview controlNet={props.controlNet} />
|
||||||
controlNetId={controlNetId}
|
|
||||||
controlImage={controlImage}
|
|
||||||
processedControlImage={processedControlImage}
|
|
||||||
/>
|
|
||||||
<ParamControlNetModel controlNetId={controlNetId} model={model} />
|
<ParamControlNetModel controlNetId={controlNetId} model={model} />
|
||||||
<Tabs
|
<Tabs
|
||||||
isFitted
|
isFitted
|
||||||
|
@ -1,6 +1,7 @@
|
|||||||
import { memo, useCallback, useState } from 'react';
|
import { memo, useCallback, useRef, useState } from 'react';
|
||||||
import { ImageDTO } from 'services/api';
|
import { ImageDTO } from 'services/api';
|
||||||
import {
|
import {
|
||||||
|
ControlNet,
|
||||||
controlNetImageChanged,
|
controlNetImageChanged,
|
||||||
controlNetSelector,
|
controlNetSelector,
|
||||||
} from '../store/controlNetSlice';
|
} from '../store/controlNetSlice';
|
||||||
@ -11,6 +12,7 @@ import { createSelector } from '@reduxjs/toolkit';
|
|||||||
import { defaultSelectorOptions } from 'app/store/util/defaultMemoizeOptions';
|
import { defaultSelectorOptions } from 'app/store/util/defaultMemoizeOptions';
|
||||||
import { AnimatePresence, motion } from 'framer-motion';
|
import { AnimatePresence, motion } from 'framer-motion';
|
||||||
import { IAIImageFallback } from 'common/components/IAIImageFallback';
|
import { IAIImageFallback } from 'common/components/IAIImageFallback';
|
||||||
|
import { useHoverDirty } from 'react-use';
|
||||||
|
|
||||||
const selector = createSelector(
|
const selector = createSelector(
|
||||||
controlNetSelector,
|
controlNetSelector,
|
||||||
@ -22,18 +24,21 @@ const selector = createSelector(
|
|||||||
);
|
);
|
||||||
|
|
||||||
type Props = {
|
type Props = {
|
||||||
controlNetId: string;
|
controlNet: ControlNet;
|
||||||
controlImage: ImageDTO | null;
|
|
||||||
processedControlImage: ImageDTO | null;
|
|
||||||
};
|
};
|
||||||
|
|
||||||
const ControlNetImagePreview = (props: Props) => {
|
const ControlNetImagePreview = (props: Props) => {
|
||||||
const { controlNetId, controlImage, processedControlImage } = props;
|
const {
|
||||||
|
controlNetId,
|
||||||
|
controlImage,
|
||||||
|
processedControlImage,
|
||||||
|
isControlImageProcessed,
|
||||||
|
} = props.controlNet;
|
||||||
const dispatch = useAppDispatch();
|
const dispatch = useAppDispatch();
|
||||||
const { isProcessingControlImage } = useAppSelector(selector);
|
const { isProcessingControlImage } = useAppSelector(selector);
|
||||||
|
const containerRef = useRef<HTMLDivElement>(null);
|
||||||
|
|
||||||
const [shouldShowProcessedImage, setShouldShowProcessedImage] =
|
const isMouseOverImage = useHoverDirty(containerRef);
|
||||||
useState(true);
|
|
||||||
|
|
||||||
const handleControlImageChanged = useCallback(
|
const handleControlImageChanged = useCallback(
|
||||||
(controlImage: ImageDTO) => {
|
(controlImage: ImageDTO) => {
|
||||||
@ -46,12 +51,15 @@ const ControlNetImagePreview = (props: Props) => {
|
|||||||
Number(controlImage?.width) > Number(processedControlImage?.width) ||
|
Number(controlImage?.width) > Number(processedControlImage?.width) ||
|
||||||
Number(controlImage?.height) > Number(processedControlImage?.height);
|
Number(controlImage?.height) > Number(processedControlImage?.height);
|
||||||
|
|
||||||
|
const shouldShowProcessedImage =
|
||||||
|
controlImage &&
|
||||||
|
processedControlImage &&
|
||||||
|
!isMouseOverImage &&
|
||||||
|
!isProcessingControlImage &&
|
||||||
|
!isControlImageProcessed;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Box
|
<Box ref={containerRef} sx={{ position: 'relative', w: 'full', h: 'full' }}>
|
||||||
sx={{ position: 'relative', h: 'inherit' }}
|
|
||||||
onMouseOver={() => setShouldShowProcessedImage(false)}
|
|
||||||
onMouseOut={() => setShouldShowProcessedImage(true)}
|
|
||||||
>
|
|
||||||
<IAIDndImage
|
<IAIDndImage
|
||||||
image={controlImage}
|
image={controlImage}
|
||||||
onDrop={handleControlImageChanged}
|
onDrop={handleControlImageChanged}
|
||||||
|
@ -1,29 +1,26 @@
|
|||||||
import { memo, useCallback } from 'react';
|
import { memo, useCallback } from 'react';
|
||||||
import {
|
import {
|
||||||
ControlNet,
|
ControlNet,
|
||||||
controlNetProcessedImageChanged,
|
controlNetAdded,
|
||||||
controlNetRemoved,
|
controlNetRemoved,
|
||||||
|
controlNetToggled,
|
||||||
|
isControlNetImageProcessedToggled,
|
||||||
} from '../store/controlNetSlice';
|
} from '../store/controlNetSlice';
|
||||||
import { useAppDispatch } from 'app/store/storeHooks';
|
import { useAppDispatch } from 'app/store/storeHooks';
|
||||||
import ParamControlNetModel from './parameters/ParamControlNetModel';
|
import ParamControlNetModel from './parameters/ParamControlNetModel';
|
||||||
import ParamControlNetWeight from './parameters/ParamControlNetWeight';
|
import ParamControlNetWeight from './parameters/ParamControlNetWeight';
|
||||||
import {
|
import {
|
||||||
Box,
|
Checkbox,
|
||||||
Flex,
|
Flex,
|
||||||
Tab,
|
FormControl,
|
||||||
TabList,
|
FormLabel,
|
||||||
TabPanel,
|
HStack,
|
||||||
TabPanels,
|
|
||||||
Tabs,
|
|
||||||
Text,
|
|
||||||
} from '@chakra-ui/react';
|
} from '@chakra-ui/react';
|
||||||
import IAIButton from 'common/components/IAIButton';
|
import { FaCopy, FaTrash } from 'react-icons/fa';
|
||||||
import { FaUndo } from 'react-icons/fa';
|
|
||||||
import ParamControlNetProcessorSelect from './parameters/ParamControlNetProcessorSelect';
|
|
||||||
import ControlNetProcessorComponent from './ControlNetProcessorComponent';
|
|
||||||
import ControlNetPreprocessButton from './ControlNetPreprocessButton';
|
|
||||||
import ParamControlNetBeginEnd from './parameters/ParamControlNetBeginEnd';
|
import ParamControlNetBeginEnd from './parameters/ParamControlNetBeginEnd';
|
||||||
import ControlNetImagePreview from './ControlNetImagePreview';
|
import ControlNetImagePreview from './ControlNetImagePreview';
|
||||||
|
import IAIIconButton from 'common/components/IAIIconButton';
|
||||||
|
import { v4 as uuidv4 } from 'uuid';
|
||||||
|
|
||||||
type ControlNetProps = {
|
type ControlNetProps = {
|
||||||
controlNet: ControlNet;
|
controlNet: ControlNet;
|
||||||
@ -43,56 +40,111 @@ const ControlNet = (props: ControlNetProps) => {
|
|||||||
processorNode,
|
processorNode,
|
||||||
} = props.controlNet;
|
} = props.controlNet;
|
||||||
const dispatch = useAppDispatch();
|
const dispatch = useAppDispatch();
|
||||||
const handleReset = useCallback(() => {
|
|
||||||
dispatch(
|
const handleDelete = useCallback(() => {
|
||||||
controlNetProcessedImageChanged({
|
dispatch(controlNetRemoved(controlNetId));
|
||||||
controlNetId,
|
|
||||||
processedControlImage: null,
|
|
||||||
})
|
|
||||||
);
|
|
||||||
}, [controlNetId, dispatch]);
|
}, [controlNetId, dispatch]);
|
||||||
|
|
||||||
const handleControlNetRemoved = useCallback(() => {
|
const handleDuplicate = useCallback(() => {
|
||||||
dispatch(controlNetRemoved(controlNetId));
|
dispatch(
|
||||||
|
controlNetAdded({ controlNetId: uuidv4(), controlNet: props.controlNet })
|
||||||
|
);
|
||||||
|
}, [dispatch, props.controlNet]);
|
||||||
|
|
||||||
|
const handleToggleIsEnabled = useCallback(() => {
|
||||||
|
dispatch(controlNetToggled(controlNetId));
|
||||||
|
}, [controlNetId, dispatch]);
|
||||||
|
|
||||||
|
const handleToggleIsPreprocessed = useCallback(() => {
|
||||||
|
dispatch(isControlNetImageProcessedToggled(controlNetId));
|
||||||
}, [controlNetId, dispatch]);
|
}, [controlNetId, dispatch]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Flex
|
<Flex
|
||||||
sx={{
|
sx={{
|
||||||
gap: 4,
|
flexDir: 'column',
|
||||||
p: 2,
|
gap: 2,
|
||||||
paddingInlineEnd: 4,
|
|
||||||
bg: 'base.850',
|
|
||||||
borderRadius: 'base',
|
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
|
<HStack>
|
||||||
|
<ParamControlNetModel controlNetId={controlNetId} model={model} />
|
||||||
|
<IAIIconButton
|
||||||
|
size="sm"
|
||||||
|
tooltip="Duplicate ControlNet"
|
||||||
|
aria-label="Duplicate ControlNet"
|
||||||
|
onClick={handleDuplicate}
|
||||||
|
icon={<FaCopy />}
|
||||||
|
/>
|
||||||
|
<IAIIconButton
|
||||||
|
size="sm"
|
||||||
|
tooltip="Delete ControlNet"
|
||||||
|
aria-label="Delete ControlNet"
|
||||||
|
colorScheme="error"
|
||||||
|
onClick={handleDelete}
|
||||||
|
icon={<FaTrash />}
|
||||||
|
/>
|
||||||
|
</HStack>
|
||||||
<Flex
|
<Flex
|
||||||
sx={{
|
sx={{
|
||||||
alignItems: 'center',
|
gap: 4,
|
||||||
justifyContent: 'center',
|
paddingInlineEnd: 2,
|
||||||
h: 36,
|
|
||||||
w: 36,
|
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<ControlNetImagePreview
|
<Flex
|
||||||
controlNetId={controlNetId}
|
sx={{
|
||||||
controlImage={controlImage}
|
alignItems: 'center',
|
||||||
processedControlImage={processedControlImage}
|
justifyContent: 'center',
|
||||||
/>
|
h: 32,
|
||||||
</Flex>
|
w: 32,
|
||||||
<Flex sx={{ flexDir: 'column', gap: 2, w: 'full', h: 'full' }}>
|
aspectRatio: '1/1',
|
||||||
<ParamControlNetModel controlNetId={controlNetId} model={model} />
|
}}
|
||||||
<ParamControlNetWeight
|
>
|
||||||
controlNetId={controlNetId}
|
<ControlNetImagePreview controlNet={props.controlNet} />
|
||||||
weight={weight}
|
</Flex>
|
||||||
mini
|
<Flex
|
||||||
/>
|
sx={{
|
||||||
<ParamControlNetBeginEnd
|
flexDir: 'column',
|
||||||
controlNetId={controlNetId}
|
gap: 2,
|
||||||
beginStepPct={beginStepPct}
|
w: 'full',
|
||||||
endStepPct={endStepPct}
|
justifyContent: 'space-between',
|
||||||
mini
|
}}
|
||||||
/>
|
>
|
||||||
|
<ParamControlNetWeight
|
||||||
|
controlNetId={controlNetId}
|
||||||
|
weight={weight}
|
||||||
|
mini
|
||||||
|
/>
|
||||||
|
<ParamControlNetBeginEnd
|
||||||
|
controlNetId={controlNetId}
|
||||||
|
beginStepPct={beginStepPct}
|
||||||
|
endStepPct={endStepPct}
|
||||||
|
mini
|
||||||
|
/>
|
||||||
|
<Flex
|
||||||
|
sx={{
|
||||||
|
justifyContent: 'space-between',
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<FormControl>
|
||||||
|
<HStack>
|
||||||
|
<Checkbox
|
||||||
|
isChecked={isEnabled}
|
||||||
|
onChange={handleToggleIsEnabled}
|
||||||
|
/>
|
||||||
|
<FormLabel>Enabled</FormLabel>
|
||||||
|
</HStack>
|
||||||
|
</FormControl>
|
||||||
|
<FormControl>
|
||||||
|
<HStack>
|
||||||
|
<Checkbox
|
||||||
|
isChecked={isControlImageProcessed}
|
||||||
|
onChange={handleToggleIsPreprocessed}
|
||||||
|
/>
|
||||||
|
<FormLabel>Preprocessed</FormLabel>
|
||||||
|
</HStack>
|
||||||
|
</FormControl>
|
||||||
|
</Flex>
|
||||||
|
</Flex>
|
||||||
</Flex>
|
</Flex>
|
||||||
</Flex>
|
</Flex>
|
||||||
);
|
);
|
||||||
|
@ -50,7 +50,7 @@ const ParamControlNetBeginEnd = (props: Props) => {
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<FormControl>
|
<FormControl>
|
||||||
<FormLabel>Begin & End Step %</FormLabel>
|
<FormLabel>Begin / End Step Percentage</FormLabel>
|
||||||
<HStack w="100%" gap={2} alignItems="center">
|
<HStack w="100%" gap={2} alignItems="center">
|
||||||
<RangeSlider
|
<RangeSlider
|
||||||
aria-label={['Begin Step %', 'End Step %']}
|
aria-label={['Begin Step %', 'End Step %']}
|
||||||
@ -72,7 +72,6 @@ const ParamControlNetBeginEnd = (props: Props) => {
|
|||||||
</Tooltip>
|
</Tooltip>
|
||||||
{!mini && (
|
{!mini && (
|
||||||
<>
|
<>
|
||||||
{' '}
|
|
||||||
<RangeSliderMark
|
<RangeSliderMark
|
||||||
value={0}
|
value={0}
|
||||||
sx={{
|
sx={{
|
||||||
|
@ -72,11 +72,11 @@ export const controlNetSlice = createSlice({
|
|||||||
},
|
},
|
||||||
controlNetAdded: (
|
controlNetAdded: (
|
||||||
state,
|
state,
|
||||||
action: PayloadAction<{ controlNetId: string }>
|
action: PayloadAction<{ controlNetId: string; controlNet?: ControlNet }>
|
||||||
) => {
|
) => {
|
||||||
const { controlNetId } = action.payload;
|
const { controlNetId, controlNet } = action.payload;
|
||||||
state.controlNets[controlNetId] = {
|
state.controlNets[controlNetId] = {
|
||||||
...initialControlNet,
|
...(controlNet ?? initialControlNet),
|
||||||
controlNetId,
|
controlNetId,
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
@ -116,11 +116,9 @@ export const controlNetSlice = createSlice({
|
|||||||
},
|
},
|
||||||
isControlNetImageProcessedToggled: (
|
isControlNetImageProcessedToggled: (
|
||||||
state,
|
state,
|
||||||
action: PayloadAction<{
|
action: PayloadAction<string>
|
||||||
controlNetId: string;
|
|
||||||
}>
|
|
||||||
) => {
|
) => {
|
||||||
const { controlNetId } = action.payload;
|
const controlNetId = action.payload;
|
||||||
state.controlNets[controlNetId].isControlImageProcessed =
|
state.controlNets[controlNetId].isControlImageProcessed =
|
||||||
!state.controlNets[controlNetId].isControlImageProcessed;
|
!state.controlNets[controlNetId].isControlImageProcessed;
|
||||||
},
|
},
|
||||||
|
@ -346,6 +346,11 @@ export const buildTextToImageGraph = (state: RootState): Graph => {
|
|||||||
weight,
|
weight,
|
||||||
} = controlNet;
|
} = controlNet;
|
||||||
|
|
||||||
|
if (!isEnabled) {
|
||||||
|
// Skip disabled ControlNets
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
const controlNetNode: ControlNetInvocation = {
|
const controlNetNode: ControlNetInvocation = {
|
||||||
id: `control_net_${controlNetId}`,
|
id: `control_net_${controlNetId}`,
|
||||||
type: 'controlnet',
|
type: 'controlnet',
|
||||||
@ -355,14 +360,14 @@ export const buildTextToImageGraph = (state: RootState): Graph => {
|
|||||||
control_weight: weight,
|
control_weight: weight,
|
||||||
};
|
};
|
||||||
|
|
||||||
if (processedControlImage) {
|
if (processedControlImage && !isControlImageProcessed) {
|
||||||
// We've already processed the image in the app, so we can just use the processed image
|
// We've already processed the image in the app, so we can just use the processed image
|
||||||
const { image_name, image_origin } = processedControlImage;
|
const { image_name, image_origin } = processedControlImage;
|
||||||
controlNetNode.image = {
|
controlNetNode.image = {
|
||||||
image_name,
|
image_name,
|
||||||
image_origin,
|
image_origin,
|
||||||
};
|
};
|
||||||
} else if (controlImage) {
|
} else if (controlImage && isControlImageProcessed) {
|
||||||
// The control image is preprocessed
|
// The control image is preprocessed
|
||||||
const { image_name, image_origin } = controlImage;
|
const { image_name, image_origin } = controlImage;
|
||||||
controlNetNode.image = {
|
controlNetNode.image = {
|
||||||
@ -370,9 +375,10 @@ export const buildTextToImageGraph = (state: RootState): Graph => {
|
|||||||
image_origin,
|
image_origin,
|
||||||
};
|
};
|
||||||
} else {
|
} else {
|
||||||
// The control image is not processed, so we need to add a preprocess node
|
// Skip ControlNets without an unprocessed image - should never happen if everything is working correctly
|
||||||
// TODO: Add preprocess node
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
graph.nodes[controlNetNode.id] = controlNetNode;
|
graph.nodes[controlNetNode.id] = controlNetNode;
|
||||||
|
|
||||||
if (size(controlNets) > 1) {
|
if (size(controlNets) > 1) {
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
import {
|
import {
|
||||||
|
Divider,
|
||||||
Flex,
|
Flex,
|
||||||
Spacer,
|
|
||||||
Tab,
|
Tab,
|
||||||
TabList,
|
TabList,
|
||||||
TabPanel,
|
TabPanel,
|
||||||
@ -9,7 +9,7 @@ import {
|
|||||||
} from '@chakra-ui/react';
|
} from '@chakra-ui/react';
|
||||||
import { useTranslation } from 'react-i18next';
|
import { useTranslation } from 'react-i18next';
|
||||||
import IAICollapse from 'common/components/IAICollapse';
|
import IAICollapse from 'common/components/IAICollapse';
|
||||||
import { memo, useCallback } from 'react';
|
import { Fragment, memo, useCallback } from 'react';
|
||||||
import IAIIconButton from 'common/components/IAIIconButton';
|
import IAIIconButton from 'common/components/IAIIconButton';
|
||||||
import { FaPlus } from 'react-icons/fa';
|
import { FaPlus } from 'react-icons/fa';
|
||||||
import ControlNet from 'features/controlNet/components/ControlNet';
|
import ControlNet from 'features/controlNet/components/ControlNet';
|
||||||
@ -21,11 +21,11 @@ import {
|
|||||||
isControlNetEnabledToggled,
|
isControlNetEnabledToggled,
|
||||||
} from 'features/controlNet/store/controlNetSlice';
|
} from 'features/controlNet/store/controlNetSlice';
|
||||||
import { defaultSelectorOptions } from 'app/store/util/defaultMemoizeOptions';
|
import { defaultSelectorOptions } from 'app/store/util/defaultMemoizeOptions';
|
||||||
import { map, startCase } from 'lodash-es';
|
import { map } from 'lodash-es';
|
||||||
import { v4 as uuidv4 } from 'uuid';
|
import { v4 as uuidv4 } from 'uuid';
|
||||||
import { CloseIcon } from '@chakra-ui/icons';
|
|
||||||
import ControlNetMini from 'features/controlNet/components/ControlNetMini';
|
import ControlNetMini from 'features/controlNet/components/ControlNetMini';
|
||||||
import { useFeatureStatus } from 'features/system/hooks/useFeatureStatus';
|
import { useFeatureStatus } from 'features/system/hooks/useFeatureStatus';
|
||||||
|
import IAIButton from 'common/components/IAIButton';
|
||||||
|
|
||||||
const selector = createSelector(
|
const selector = createSelector(
|
||||||
controlNetSelector,
|
controlNetSelector,
|
||||||
@ -56,11 +56,26 @@ const ParamControlNetCollapse = () => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<IAICollapse
|
||||||
{controlNetsArray.map((c) => (
|
label={'ControlNet'}
|
||||||
<ControlNetMini key={c.controlNetId} controlNet={c} />
|
isOpen={isEnabled}
|
||||||
))}
|
onToggle={handleClickControlNetToggle}
|
||||||
</>
|
withSwitch
|
||||||
|
>
|
||||||
|
{controlNetsArray.length === 0 && (
|
||||||
|
<IAIButton onClick={handleClickedAddControlNet}>
|
||||||
|
Add ControlNet
|
||||||
|
</IAIButton>
|
||||||
|
)}
|
||||||
|
<Flex sx={{ flexDir: 'column', gap: 4 }}>
|
||||||
|
{controlNetsArray.map((c, i) => (
|
||||||
|
<Fragment key={c.controlNetId}>
|
||||||
|
{i > 0 && <Divider />}
|
||||||
|
<ControlNetMini controlNet={c} />
|
||||||
|
</Fragment>
|
||||||
|
))}
|
||||||
|
</Flex>
|
||||||
|
</IAICollapse>
|
||||||
);
|
);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
Loading…
Reference in New Issue
Block a user