feat(ui): enhance autoprocessing

The processor is automatically selected when model is changed.

But if the user manually changes the processor, processor settings, or disables the new `Auto configure processor` switch, auto processing is disabled.

The user can enable auto configure by turning the switch back on.

When auto configure is enabled, a small dot is overlaid on the expand button to remind the user that the system is not auto configuring the processor for them.

If auto configure is enabled, the processor settings are reset to the default for the selected model.
This commit is contained in:
psychedelicious 2023-06-07 16:59:23 +10:00
parent 844058c0a5
commit 0a8390356f
5 changed files with 110 additions and 30 deletions

View File

@ -1,9 +1,11 @@
import { AnyAction } from '@reduxjs/toolkit'; import { AnyListenerPredicate } from '@reduxjs/toolkit';
import { startAppListening } from '..'; import { startAppListening } from '..';
import { log } from 'app/logging/useLogger'; import { log } from 'app/logging/useLogger';
import { controlNetImageProcessed } from 'features/controlNet/store/actions'; import { controlNetImageProcessed } from 'features/controlNet/store/actions';
import { import {
controlNetAutoConfigToggled,
controlNetImageChanged, controlNetImageChanged,
controlNetModelChanged,
controlNetProcessorParamsChanged, controlNetProcessorParamsChanged,
controlNetProcessorTypeChanged, controlNetProcessorTypeChanged,
} from 'features/controlNet/store/controlNetSlice'; } from 'features/controlNet/store/controlNetSlice';
@ -11,19 +13,26 @@ import { RootState } from 'app/store/store';
const moduleLog = log.child({ namespace: 'controlNet' }); const moduleLog = log.child({ namespace: 'controlNet' });
const predicate = (action: AnyAction, state: RootState) => { const predicate: AnyListenerPredicate<RootState> = (action, state) => {
const isActionMatched = const isActionMatched =
controlNetProcessorParamsChanged.match(action) || controlNetProcessorParamsChanged.match(action) ||
controlNetModelChanged.match(action) ||
controlNetImageChanged.match(action) || controlNetImageChanged.match(action) ||
controlNetProcessorTypeChanged.match(action); controlNetProcessorTypeChanged.match(action) ||
controlNetAutoConfigToggled.match(action);
if (!isActionMatched) { if (!isActionMatched) {
return false; return false;
} }
const { controlImage, processorType } = const { controlImage, processorType, shouldAutoConfig } =
state.controlNet.controlNets[action.payload.controlNetId]; state.controlNet.controlNets[action.payload.controlNetId];
if (controlNetModelChanged.match(action) && !shouldAutoConfig) {
// do not process if the action is a model change but the processor settings are dirty
return false;
}
const isProcessorSelected = processorType !== 'none'; const isProcessorSelected = processorType !== 'none';
const isBusy = state.system.isProcessing; const isBusy = state.system.isProcessing;
@ -49,7 +58,10 @@ export const addControlNetAutoProcessListener = () => {
// Cancel any in-progress instances of this listener // Cancel any in-progress instances of this listener
cancelActiveListeners(); cancelActiveListeners();
moduleLog.trace(
{ data: action.payload },
'ControlNet auto-process triggered'
);
// Delay before starting actual work // Delay before starting actual work
await delay(300); await delay(300);

View File

@ -8,22 +8,8 @@ import {
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 { Flex, Box, ChakraProps } from '@chakra-ui/react';
Checkbox, import { FaCopy, FaTrash } from 'react-icons/fa';
Flex,
FormControl,
FormLabel,
HStack,
TabList,
TabPanels,
Tabs,
Tab,
TabPanel,
Box,
VStack,
ChakraProps,
} from '@chakra-ui/react';
import { FaCopy, FaPlus, FaTrash, FaWrench } from 'react-icons/fa';
import ParamControlNetBeginEnd from './parameters/ParamControlNetBeginEnd'; import ParamControlNetBeginEnd from './parameters/ParamControlNetBeginEnd';
import ControlNetImagePreview from './ControlNetImagePreview'; import ControlNetImagePreview from './ControlNetImagePreview';
@ -32,10 +18,9 @@ import { v4 as uuidv4 } from 'uuid';
import { useToggle } from 'react-use'; import { useToggle } from 'react-use';
import ParamControlNetProcessorSelect from './parameters/ParamControlNetProcessorSelect'; import ParamControlNetProcessorSelect from './parameters/ParamControlNetProcessorSelect';
import ControlNetProcessorComponent from './ControlNetProcessorComponent'; import ControlNetProcessorComponent from './ControlNetProcessorComponent';
import ControlNetPreprocessButton from './ControlNetPreprocessButton';
import IAIButton from 'common/components/IAIButton';
import IAISwitch from 'common/components/IAISwitch'; import IAISwitch from 'common/components/IAISwitch';
import { ChevronDownIcon, ChevronUpIcon } from '@chakra-ui/icons'; import { ChevronUpIcon } from '@chakra-ui/icons';
import ParamControlNetShouldAutoConfig from './ParamControlNetShouldAutoConfig';
const expandedControlImageSx: ChakraProps['sx'] = { maxH: 96 }; const expandedControlImageSx: ChakraProps['sx'] = { maxH: 96 };
@ -55,6 +40,7 @@ const ControlNet = (props: ControlNetProps) => {
processedControlImage, processedControlImage,
processorNode, processorNode,
processorType, processorType,
shouldAutoConfig,
} = props.controlNet; } = props.controlNet;
const dispatch = useAppDispatch(); const dispatch = useAppDispatch();
const [isExpanded, toggleIsExpanded] = useToggle(false); const [isExpanded, toggleIsExpanded] = useToggle(false);
@ -81,6 +67,7 @@ const ControlNet = (props: ControlNetProps) => {
p: 3, p: 3,
bg: 'base.850', bg: 'base.850',
borderRadius: 'base', borderRadius: 'base',
position: 'relative',
}} }}
> >
<Flex sx={{ gap: 2 }}> <Flex sx={{ gap: 2 }}>
@ -119,7 +106,7 @@ const ControlNet = (props: ControlNetProps) => {
/> />
<IAIIconButton <IAIIconButton
size="sm" size="sm"
aria-label="Expand" aria-label="Show All Options"
onClick={toggleIsExpanded} onClick={toggleIsExpanded}
variant="link" variant="link"
icon={ icon={
@ -134,6 +121,19 @@ const ControlNet = (props: ControlNetProps) => {
/> />
} }
/> />
{!shouldAutoConfig && (
<Box
sx={{
position: 'absolute',
w: 1.5,
h: 1.5,
borderRadius: 'full',
bg: 'error.200',
top: 4,
insetInlineEnd: 4,
}}
/>
)}
</Flex> </Flex>
{isEnabled && ( {isEnabled && (
<> <>
@ -192,6 +192,10 @@ const ControlNet = (props: ControlNetProps) => {
controlNetId={controlNetId} controlNetId={controlNetId}
processorNode={processorNode} processorNode={processorNode}
/> />
<ParamControlNetShouldAutoConfig
controlNetId={controlNetId}
shouldAutoConfig={shouldAutoConfig}
/>
</> </>
)} )}
</> </>

View File

@ -0,0 +1,29 @@
import { useAppDispatch } from 'app/store/storeHooks';
import IAISwitch from 'common/components/IAISwitch';
import { controlNetAutoConfigToggled } from 'features/controlNet/store/controlNetSlice';
import { memo, useCallback } from 'react';
type Props = {
controlNetId: string;
shouldAutoConfig: boolean;
};
const ParamControlNetShouldAutoConfig = (props: Props) => {
const { controlNetId, shouldAutoConfig } = props;
const dispatch = useAppDispatch();
const handleShouldAutoConfigChanged = useCallback(() => {
dispatch(controlNetAutoConfigToggled({ controlNetId }));
}, [controlNetId, dispatch]);
return (
<IAISwitch
label="Auto configure processor"
aria-label="Auto configure processor"
isChecked={shouldAutoConfig}
onChange={handleShouldAutoConfigChanged}
/>
);
};
export default memo(ParamControlNetShouldAutoConfig);

View File

@ -7,12 +7,12 @@ import {
import { controlNetModelChanged } from 'features/controlNet/store/controlNetSlice'; import { controlNetModelChanged } from 'features/controlNet/store/controlNetSlice';
import { memo, useCallback } from 'react'; import { memo, useCallback } from 'react';
type ParamIsControlNetModelProps = { type ParamControlNetModelProps = {
controlNetId: string; controlNetId: string;
model: ControlNetModel; model: ControlNetModel;
}; };
const ParamIsControlNetModel = (props: ParamIsControlNetModelProps) => { const ParamControlNetModel = (props: ParamControlNetModelProps) => {
const { controlNetId, model } = props; const { controlNetId, model } = props;
const dispatch = useAppDispatch(); const dispatch = useAppDispatch();
@ -38,4 +38,4 @@ const ParamIsControlNetModel = (props: ParamIsControlNetModelProps) => {
); );
}; };
export default memo(ParamIsControlNetModel); export default memo(ParamControlNetModel);

View File

@ -1,4 +1,4 @@
import { PayloadAction, isAnyOf } from '@reduxjs/toolkit'; import { PayloadAction } from '@reduxjs/toolkit';
import { createSlice } from '@reduxjs/toolkit'; import { createSlice } from '@reduxjs/toolkit';
import { RootState } from 'app/store/store'; import { RootState } from 'app/store/store';
import { ImageDTO } from 'services/api'; import { ImageDTO } from 'services/api';
@ -30,6 +30,7 @@ export const initialControlNet: Omit<ControlNetConfig, 'controlNetId'> = {
processorType: 'canny_image_processor', processorType: 'canny_image_processor',
processorNode: CONTROLNET_PROCESSORS.canny_image_processor processorNode: CONTROLNET_PROCESSORS.canny_image_processor
.default as RequiredCannyImageProcessorInvocation, .default as RequiredCannyImageProcessorInvocation,
shouldAutoConfig: false,
}; };
export type ControlNetConfig = { export type ControlNetConfig = {
@ -43,6 +44,7 @@ export type ControlNetConfig = {
processedControlImage: ImageDTO | null; processedControlImage: ImageDTO | null;
processorType: ControlNetProcessorType; processorType: ControlNetProcessorType;
processorNode: RequiredControlNetProcessorNode; processorNode: RequiredControlNetProcessorNode;
shouldAutoConfig: boolean;
}; };
export type ControlNetState = { export type ControlNetState = {
@ -140,8 +142,9 @@ export const controlNetSlice = createSlice({
) => { ) => {
const { controlNetId, model } = action.payload; const { controlNetId, model } = action.payload;
state.controlNets[controlNetId].model = model; state.controlNets[controlNetId].model = model;
state.controlNets[controlNetId].processedControlImage = null;
if (!state.controlNets[controlNetId].controlImage) { if (state.controlNets[controlNetId].shouldAutoConfig) {
const processorType = CONTROLNET_MODEL_MAP[model]; const processorType = CONTROLNET_MODEL_MAP[model];
if (processorType) { if (processorType) {
state.controlNets[controlNetId].processorType = processorType; state.controlNets[controlNetId].processorType = processorType;
@ -192,6 +195,7 @@ export const controlNetSlice = createSlice({
...processorNode, ...processorNode,
...changes, ...changes,
}; };
state.controlNets[controlNetId].shouldAutoConfig = false;
}, },
controlNetProcessorTypeChanged: ( controlNetProcessorTypeChanged: (
state, state,
@ -201,10 +205,40 @@ export const controlNetSlice = createSlice({
}> }>
) => { ) => {
const { controlNetId, processorType } = action.payload; const { controlNetId, processorType } = action.payload;
state.controlNets[controlNetId].processedControlImage = null;
state.controlNets[controlNetId].processorType = processorType; state.controlNets[controlNetId].processorType = processorType;
state.controlNets[controlNetId].processorNode = CONTROLNET_PROCESSORS[ state.controlNets[controlNetId].processorNode = CONTROLNET_PROCESSORS[
processorType processorType
].default as RequiredControlNetProcessorNode; ].default as RequiredControlNetProcessorNode;
state.controlNets[controlNetId].shouldAutoConfig = false;
},
controlNetAutoConfigToggled: (
state,
action: PayloadAction<{
controlNetId: string;
}>
) => {
const { controlNetId } = action.payload;
const newShouldAutoConfig =
!state.controlNets[controlNetId].shouldAutoConfig;
if (newShouldAutoConfig) {
// manage the processor for the user
const processorType =
CONTROLNET_MODEL_MAP[state.controlNets[controlNetId].model];
if (processorType) {
state.controlNets[controlNetId].processorType = processorType;
state.controlNets[controlNetId].processorNode = CONTROLNET_PROCESSORS[
processorType
].default as RequiredControlNetProcessorNode;
} else {
state.controlNets[controlNetId].processorType = 'none';
state.controlNets[controlNetId].processorNode = CONTROLNET_PROCESSORS
.none.default as RequiredControlNetProcessorNode;
}
}
state.controlNets[controlNetId].shouldAutoConfig = newShouldAutoConfig;
}, },
controlNetReset: () => { controlNetReset: () => {
return { ...initialControlNetState }; return { ...initialControlNetState };
@ -274,6 +308,7 @@ export const {
controlNetProcessorParamsChanged, controlNetProcessorParamsChanged,
controlNetProcessorTypeChanged, controlNetProcessorTypeChanged,
controlNetReset, controlNetReset,
controlNetAutoConfigToggled,
} = controlNetSlice.actions; } = controlNetSlice.actions;
export default controlNetSlice.reducer; export default controlNetSlice.reducer;