feat(ui): more tweaking controlnet ui

This commit is contained in:
psychedelicious 2023-06-01 22:52:25 +10:00
parent 98493ed9e2
commit b17f4c1650
14 changed files with 271 additions and 148 deletions

View File

@ -104,7 +104,8 @@ const IAICustomSelect = (props: IAICustomSelectProps) => {
ref={refs.setFloating} ref={refs.setFloating}
sx={{ sx={{
...floatingStyles, ...floatingStyles,
width: 'max-content', width: 'full',
// width: 'max-content',
top: 0, top: 0,
left: 0, left: 0,
flexDirection: 'column', flexDirection: 'column',
@ -118,6 +119,7 @@ const IAICustomSelect = (props: IAICustomSelectProps) => {
px: 0, px: 0,
h: 'fit-content', h: 'fit-content',
maxH: 64, maxH: 64,
minW: 48,
}} }}
> >
<OverlayScrollbarsComponent> <OverlayScrollbarsComponent>

View File

@ -3,8 +3,10 @@ import { ControlNetProcessorNode } from '../store/types';
import { ImageDTO } from 'services/api'; import { ImageDTO } from 'services/api';
import CannyProcessor from './processors/CannyProcessor'; import CannyProcessor from './processors/CannyProcessor';
import { import {
CONTROLNET_PROCESSORS,
ControlNet, ControlNet,
ControlNetModel, ControlNetModel,
ControlNetProcessor,
controlNetBeginStepPctChanged, controlNetBeginStepPctChanged,
controlNetEndStepPctChanged, controlNetEndStepPctChanged,
controlNetImageChanged, controlNetImageChanged,
@ -23,7 +25,18 @@ import ParamControlNetModel from './parameters/ParamControlNetModel';
import ParamControlNetWeight from './parameters/ParamControlNetWeight'; import ParamControlNetWeight from './parameters/ParamControlNetWeight';
import ParamControlNetBeginStepPct from './parameters/ParamControlNetBeginStepPct'; import ParamControlNetBeginStepPct from './parameters/ParamControlNetBeginStepPct';
import ParamControlNetEndStepPct from './parameters/ParamControlNetEndStepPct'; import ParamControlNetEndStepPct from './parameters/ParamControlNetEndStepPct';
import { Flex, HStack, VStack } from '@chakra-ui/react'; import {
Box,
Flex,
HStack,
Tab,
TabList,
TabPanel,
TabPanels,
Tabs,
VStack,
useDisclosure,
} from '@chakra-ui/react';
import IAISelectableImage from './parameters/IAISelectableImage'; import IAISelectableImage from './parameters/IAISelectableImage';
import IAIButton from 'common/components/IAIButton'; import IAIButton from 'common/components/IAIButton';
import IAIIconButton from 'common/components/IAIIconButton'; import IAIIconButton from 'common/components/IAIIconButton';
@ -31,6 +44,7 @@ import IAISwitch from 'common/components/IAISwitch';
import ParamControlNetIsPreprocessed from './parameters/ParamControlNetIsPreprocessed'; import ParamControlNetIsPreprocessed from './parameters/ParamControlNetIsPreprocessed';
import IAICollapse from 'common/components/IAICollapse'; import IAICollapse from 'common/components/IAICollapse';
import ControlNetProcessorCollapse from './ControlNetProcessorCollapse'; import ControlNetProcessorCollapse from './ControlNetProcessorCollapse';
import IAICustomSelect from 'common/components/IAICustomSelect';
type ControlNetProps = { type ControlNetProps = {
controlNet: ControlNet; controlNet: ControlNet;
@ -50,9 +64,14 @@ const ControlNet = (props: ControlNetProps) => {
} = props.controlNet; } = props.controlNet;
const dispatch = useAppDispatch(); const dispatch = useAppDispatch();
const [processorType, setProcessorType] = useState< const [processorType, setProcessorType] =
ControlNetProcessorNode['type'] useState<ControlNetProcessor>('canny');
>('canny_image_processor');
const handleProcessorTypeChanged = (type: string | null | undefined) => {
setProcessorType(type as ControlNetProcessor);
};
const { isOpen, onToggle } = useDisclosure();
const handleControlImageChanged = useCallback( const handleControlImageChanged = useCallback(
(controlImage: ImageDTO) => { (controlImage: ImageDTO) => {
@ -82,34 +101,82 @@ const ControlNet = (props: ControlNetProps) => {
); );
return ( return (
<Flex sx={{ flexDir: 'column', gap: 3, pb: 4 }}> <Flex sx={{ flexDir: 'column', gap: 3 }}>
<IAIButton onClick={handleControlNetRemoved}>Remove ControlNet</IAIButton>
<IAISelectableImage <IAISelectableImage
image={processedControlImage || controlImage} image={processedControlImage || controlImage}
onChange={handleControlImageChanged} onChange={handleControlImageChanged}
onReset={handleControlImageReset} onReset={handleControlImageReset}
resetIconSize="sm" resetIconSize="sm"
/> />
<ControlNetProcessorCollapse
controlNetId={controlNetId}
image={controlImage}
/>
<ParamControlNetModel controlNetId={controlNetId} model={model} /> <ParamControlNetModel controlNetId={controlNetId} model={model} />
<ParamControlNetIsEnabled <Tabs
controlNetId={controlNetId} isFitted
isEnabled={isEnabled} orientation="horizontal"
/> variant="enclosed"
<ParamControlNetWeight controlNetId={controlNetId} weight={weight} /> size="sm"
<ParamControlNetBeginStepPct colorScheme="accent"
controlNetId={controlNetId} >
beginStepPct={beginStepPct} <TabList>
/> <Tab
<ParamControlNetEndStepPct sx={{ 'button&': { _selected: { borderBottomColor: 'base.800' } } }}
controlNetId={controlNetId} >
endStepPct={endStepPct} Model Config
/> </Tab>
<Tab
sx={{ 'button&': { _selected: { borderBottomColor: 'base.800' } } }}
>
Preprocess
</Tab>
</TabList>
<TabPanels sx={{ pt: 2 }}>
<TabPanel sx={{ p: 0 }}>
<ParamControlNetWeight
controlNetId={controlNetId}
weight={weight}
/>
<ParamControlNetBeginStepPct
controlNetId={controlNetId}
beginStepPct={beginStepPct}
/>
<ParamControlNetEndStepPct
controlNetId={controlNetId}
endStepPct={endStepPct}
/>
</TabPanel>
<TabPanel sx={{ p: 0 }}>
<IAICustomSelect
label="Processor"
items={CONTROLNET_PROCESSORS}
selectedItem={processorType}
setSelectedItem={handleProcessorTypeChanged}
/>
<ProcessorComponent
controlNetId={controlNetId}
controlImage={controlImage}
processedControlImage={processedControlImage}
type={processorType}
/>
</TabPanel>
</TabPanels>
</Tabs>
<IAIButton onClick={handleControlNetRemoved}>Remove ControlNet</IAIButton>
</Flex> </Flex>
); );
}; };
export default memo(ControlNet); export default memo(ControlNet);
export type ControlNetProcessorProps = {
controlNetId: string;
controlImage: ImageDTO | null;
processedControlImage: ImageDTO | null;
type: ControlNetProcessor;
};
const ProcessorComponent = (props: ControlNetProcessorProps) => {
const { type } = props;
if (type === 'canny') {
return <CannyProcessor {...props} />;
}
return null;
};

View File

@ -1,67 +1,76 @@
import { useDisclosure } from '@chakra-ui/react'; // import { Collapse, Flex, useDisclosure } from '@chakra-ui/react';
import IAICollapse from 'common/components/IAICollapse'; // import { memo, useState } from 'react';
import { memo, useState } from 'react'; // import CannyProcessor from './processors/CannyProcessor';
import CannyProcessor from './processors/CannyProcessor'; // import { ImageDTO } from 'services/api';
import { ImageDTO } from 'services/api'; // import IAICustomSelect from 'common/components/IAICustomSelect';
import IAICustomSelect from 'common/components/IAICustomSelect'; // import {
import { // CONTROLNET_PROCESSORS,
CONTROLNET_PROCESSORS, // ControlNetProcessor,
ControlNetProcessor, // } from '../store/controlNetSlice';
} from '../store/controlNetSlice'; // import IAISwitch from 'common/components/IAISwitch';
export type ControlNetProcessorProps = { // export type ControlNetProcessorProps = {
controlNetId: string; // controlNetId: string;
image: ImageDTO; // controlImage: ImageDTO | null;
type: ControlNetProcessor; // processedControlImage: ImageDTO | null;
}; // type: ControlNetProcessor;
// };
const ProcessorComponent = (props: ControlNetProcessorProps) => { // const ProcessorComponent = (props: ControlNetProcessorProps) => {
const { type } = props; // const { type } = props;
if (type === 'canny') { // if (type === 'canny') {
return <CannyProcessor {...props} />; // return <CannyProcessor {...props} />;
} // }
return null; // return null;
}; // };
type ControlNetProcessorCollapseProps = { // type ControlNetProcessorCollapseProps = {
controlNetId: string; // isOpen: boolean;
image: ImageDTO | null; // controlNetId: string;
}; // controlImage: ImageDTO | null;
// processedControlImage: ImageDTO | null;
// };
// const ControlNetProcessorCollapse = (
// props: ControlNetProcessorCollapseProps
// ) => {
// const { isOpen, controlImage, controlNetId, processedControlImage } = props;
const ControlNetProcessorCollapse = ( // const [processorType, setProcessorType] =
props: ControlNetProcessorCollapseProps // useState<ControlNetProcessor>('canny');
) => {
const { image, controlNetId } = props;
const { isOpen, onToggle } = useDisclosure();
const [processorType, setProcessorType] = // const handleProcessorTypeChanged = (type: string | null | undefined) => {
useState<ControlNetProcessor>('canny'); // setProcessorType(type as ControlNetProcessor);
// };
const handleProcessorTypeChanged = (type: string | null | undefined) => { // return (
setProcessorType(type as ControlNetProcessor); // <Flex
}; // sx={{
// gap: 2,
// p: 4,
// mt: 2,
// bg: 'base.850',
// borderRadius: 'base',
// flexDirection: 'column',
// }}
// >
// <IAICustomSelect
// label="Processor"
// items={CONTROLNET_PROCESSORS}
// selectedItem={processorType}
// setSelectedItem={handleProcessorTypeChanged}
// />
// {controlImage && (
// <ProcessorComponent
// controlNetId={controlNetId}
// controlImage={controlImage}
// processedControlImage={processedControlImage}
// type={processorType}
// />
// )}
// </Flex>
// );
// };
return ( // export default memo(ControlNetProcessorCollapse);
<IAICollapse
isOpen={Boolean(isOpen && image)}
onToggle={onToggle}
label="Process Image"
withSwitch
>
<IAICustomSelect
items={CONTROLNET_PROCESSORS}
selectedItem={processorType}
setSelectedItem={handleProcessorTypeChanged}
/>
{image && (
<ProcessorComponent
controlNetId={controlNetId}
image={image}
type={processorType}
/>
)}
</IAICollapse>
);
};
export default memo(ControlNetProcessorCollapse); export default {};

View File

@ -64,6 +64,7 @@ const IAISelectableImage = (props: IAISelectableImageProps) => {
fallbackStrategy="beforeLoadOrError" fallbackStrategy="beforeLoadOrError"
fallback={<ImageFallback />} fallback={<ImageFallback />}
onError={onError} onError={onError}
draggable={false}
sx={{ sx={{
borderRadius: 'base', borderRadius: 'base',
}} }}
@ -80,6 +81,7 @@ const IAISelectableImage = (props: IAISelectableImageProps) => {
> >
<IAIIconButton <IAIIconButton
size={resetIconSize} size={resetIconSize}
tooltip="Reset Image"
aria-label="Reset Image" aria-label="Reset Image"
icon={<FaTimes />} icon={<FaTimes />}
onClick={onReset} onClick={onReset}

View File

@ -1,5 +1,5 @@
import { useAppDispatch } from 'app/store/storeHooks'; import { useAppDispatch } from 'app/store/storeHooks';
import IAIFullCheckbox from 'common/components/IAIFullCheckbox'; import IAISwitch from 'common/components/IAISwitch';
import { controlNetToggled } from 'features/controlNet/store/controlNetSlice'; import { controlNetToggled } from 'features/controlNet/store/controlNetSlice';
import { memo, useCallback } from 'react'; import { memo, useCallback } from 'react';
@ -17,7 +17,7 @@ const ParamControlNetIsEnabled = (props: ParamControlNetIsEnabledProps) => {
}, [dispatch, controlNetId]); }, [dispatch, controlNetId]);
return ( return (
<IAIFullCheckbox <IAISwitch
label="Enabled" label="Enabled"
isChecked={isEnabled} isChecked={isEnabled}
onChange={handleIsEnabledChanged} onChange={handleIsEnabledChanged}

View File

@ -27,7 +27,6 @@ const ParamIsControlNetModel = (props: ParamIsControlNetModelProps) => {
return ( return (
<IAICustomSelect <IAICustomSelect
label="Model"
items={CONTROLNET_MODELS} items={CONTROLNET_MODELS}
selectedItem={model} selectedItem={model}
setSelectedItem={handleModelChanged} setSelectedItem={handleModelChanged}

View File

@ -1,23 +1,22 @@
import { Flex } from '@chakra-ui/react'; import { Flex } from '@chakra-ui/react';
import IAISlider from 'common/components/IAISlider'; import IAISlider from 'common/components/IAISlider';
import { memo, useCallback, useState } from 'react';
import ControlNetProcessButton from './common/ControlNetProcessButton';
import { useAppDispatch } from 'app/store/storeHooks'; import { useAppDispatch } from 'app/store/storeHooks';
import { controlNetImageProcessed } from 'features/controlNet/store/actions'; import { controlNetImageProcessed } from 'features/controlNet/store/actions';
import ControlNetResetProcessedImageButton from './common/ControlNetResetProcessedImageButton';
import { ControlNetProcessorProps } from '../ControlNetProcessorCollapse';
import { controlNetProcessedImageChanged } from 'features/controlNet/store/controlNetSlice'; import { controlNetProcessedImageChanged } from 'features/controlNet/store/controlNetSlice';
import ControlNetProcessorButtons from './common/ControlNetProcessorButtons';
import { memo, useCallback, useState } from 'react';
import { ControlNetProcessorProps } from '../ControlNet';
export const CANNY_PROCESSOR = 'canny_image_processor'; export const CANNY_PROCESSOR = 'canny_image_processor';
const CannyProcessor = (props: ControlNetProcessorProps) => { const CannyProcessor = (props: ControlNetProcessorProps) => {
const { controlNetId, image, type } = props; const { controlNetId, controlImage, processedControlImage, type } = props;
const dispatch = useAppDispatch(); const dispatch = useAppDispatch();
const [lowThreshold, setLowThreshold] = useState(100); const [lowThreshold, setLowThreshold] = useState(100);
const [highThreshold, setHighThreshold] = useState(200); const [highThreshold, setHighThreshold] = useState(200);
const handleProcess = useCallback(() => { const handleProcess = useCallback(() => {
if (!image) { if (!controlImage) {
return; return;
} }
@ -28,15 +27,15 @@ const CannyProcessor = (props: ControlNetProcessorProps) => {
id: CANNY_PROCESSOR, id: CANNY_PROCESSOR,
type: 'canny_image_processor', type: 'canny_image_processor',
image: { image: {
image_name: image.image_name, image_name: controlImage.image_name,
image_origin: image.image_origin, image_origin: controlImage.image_origin,
}, },
low_threshold: lowThreshold, low_threshold: lowThreshold,
high_threshold: highThreshold, high_threshold: highThreshold,
}, },
}) })
); );
}, [controlNetId, dispatch, highThreshold, image, lowThreshold]); }, [controlNetId, dispatch, highThreshold, controlImage, lowThreshold]);
const handleReset = useCallback(() => { const handleReset = useCallback(() => {
dispatch( dispatch(
@ -65,10 +64,12 @@ const CannyProcessor = (props: ControlNetProcessorProps) => {
max={255} max={255}
withInput withInput
/> />
<Flex sx={{ gap: 4 }}> <ControlNetProcessorButtons
<ControlNetProcessButton onClick={handleProcess} /> handleProcess={handleProcess}
<ControlNetResetProcessedImageButton onClick={handleReset} /> isProcessDisabled={Boolean(!controlImage)}
</Flex> handleReset={handleReset}
isResetDisabled={Boolean(!processedControlImage)}
/>
</Flex> </Flex>
); );
}; };

View File

@ -1,13 +0,0 @@
import IAIButton from 'common/components/IAIButton';
import { memo } from 'react';
type ControlNetProcessButtonProps = {
onClick: () => void;
};
const ControlNetProcessButton = (props: ControlNetProcessButtonProps) => {
const { onClick } = props;
return <IAIButton onClick={onClick}>Process Control Image</IAIButton>;
};
export default memo(ControlNetProcessButton);

View File

@ -0,0 +1,44 @@
import { Flex } from '@chakra-ui/react';
import { memo } from 'react';
import { FaUndo } from 'react-icons/fa';
import IAIButton from 'common/components/IAIButton';
type ControlNetProcessorButtonsProps = {
handleProcess: () => void;
isProcessDisabled: boolean;
handleReset: () => void;
isResetDisabled: boolean;
};
const ControlNetProcessorButtons = (props: ControlNetProcessorButtonsProps) => {
const { handleProcess, isProcessDisabled, handleReset, isResetDisabled } =
props;
return (
<Flex
sx={{
gap: 4,
w: 'full',
alignItems: 'center',
justifyContent: 'stretch',
}}
>
<IAIButton
size="sm"
onClick={handleProcess}
isDisabled={isProcessDisabled}
>
Preprocess
</IAIButton>
<IAIButton
size="sm"
leftIcon={<FaUndo />}
onClick={handleReset}
isDisabled={isResetDisabled}
>
Reset Processing
</IAIButton>
</Flex>
);
};
export default memo(ControlNetProcessorButtons);

View File

@ -1,20 +0,0 @@
import IAIButton from 'common/components/IAIButton';
import { memo } from 'react';
import { FaUnderline, FaUndo } from 'react-icons/fa';
type ControlNetResetProcessedImageButtonProps = {
onClick: () => void;
};
const ControlNetResetProcessedImageButton = (
props: ControlNetResetProcessedImageButtonProps
) => {
const { onClick } = props;
return (
<IAIButton leftIcon={<FaUndo />} onClick={onClick}>
Reset Processing
</IAIButton>
);
};
export default memo(ControlNetResetProcessedImageButton);

View File

@ -50,7 +50,10 @@ export const gallerySlice = createSlice({
}, },
extraReducers: (builder) => { extraReducers: (builder) => {
builder.addCase(imageUpserted, (state, action) => { builder.addCase(imageUpserted, (state, action) => {
if (state.shouldAutoSwitchToNewImages) { if (
state.shouldAutoSwitchToNewImages &&
action.payload.image_category === 'general'
) {
state.selectedImage = action.payload; state.selectedImage = action.payload;
} }
}); });

View File

@ -19,6 +19,7 @@ const NOISE = 'noise';
const RANDOM_INT = 'rand_int'; const RANDOM_INT = 'rand_int';
const RANGE_OF_SIZE = 'range_of_size'; const RANGE_OF_SIZE = 'range_of_size';
const ITERATE = 'iterate'; const ITERATE = 'iterate';
const CONTROL_NET = 'control_net';
/** /**
* Builds the Text to Image tab graph. * Builds the Text to Image tab graph.

View File

@ -1,4 +1,12 @@
import { Flex, useDisclosure } from '@chakra-ui/react'; import {
Flex,
Spacer,
Tab,
TabList,
TabPanel,
TabPanels,
Tabs,
} 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 { memo, useCallback } from 'react';
@ -13,22 +21,23 @@ 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 } from 'lodash-es'; import { map, startCase } from 'lodash-es';
import { v4 as uuidv4 } from 'uuid'; import { v4 as uuidv4 } from 'uuid';
import { CloseIcon } from '@chakra-ui/icons';
const selector = createSelector( const selector = createSelector(
controlNetSelector, controlNetSelector,
(controlNet) => { (controlNet) => {
const { controlNets, isEnabled } = controlNet; const { controlNets, isEnabled } = controlNet;
return { controlNets, isEnabled }; return { controlNetsArray: map(controlNets), isEnabled };
}, },
defaultSelectorOptions defaultSelectorOptions
); );
const ParamControlNetCollapse = () => { const ParamControlNetCollapse = () => {
const { t } = useTranslation(); const { t } = useTranslation();
const { controlNets, isEnabled } = useAppSelector(selector); const { controlNetsArray, isEnabled } = useAppSelector(selector);
const dispatch = useAppDispatch(); const dispatch = useAppDispatch();
const handleClickControlNetToggle = useCallback(() => { const handleClickControlNetToggle = useCallback(() => {
@ -46,17 +55,35 @@ const ParamControlNetCollapse = () => {
onToggle={handleClickControlNetToggle} onToggle={handleClickControlNetToggle}
withSwitch withSwitch
> >
<Flex sx={{ alignItems: 'flex-end' }}> <Tabs
<IAIIconButton isFitted
size="sm" orientation="horizontal"
aria-label="Add ControlNet" variant="line"
onClick={handleClickedAddControlNet} size="sm"
icon={<FaPlus />} colorScheme="accent"
/> >
</Flex> <TabList alignItems="center" borderBottomColor="base.800" pb={4}>
{map(controlNets, (c) => ( {controlNetsArray.map((c, i) => (
<ControlNet key={c.controlNetId} controlNet={c} /> <Tab key={`tab_${c.controlNetId}`} borderTopRadius="base">
))} {i + 1}
</Tab>
))}
<IAIIconButton
marginInlineStart={2}
size="sm"
aria-label="Add ControlNet"
onClick={handleClickedAddControlNet}
icon={<FaPlus />}
/>
</TabList>
<TabPanels>
{controlNetsArray.map((c) => (
<TabPanel key={`tabPanel_${c.controlNetId}`} sx={{ p: 0 }}>
<ControlNet controlNet={c} />
</TabPanel>
))}
</TabPanels>
</Tabs>
</IAICollapse> </IAICollapse>
); );
}; };

View File

@ -26,6 +26,7 @@ const invokeAITablist = defineStyle((_props) => ({
padding: 2, padding: 2,
borderRadius: 'base', borderRadius: 'base',
_selected: { _selected: {
borderBottomColor: 'base.800',
bg: 'accent.700', bg: 'accent.700',
color: 'accent.100', color: 'accent.100',
_hover: { _hover: {