refactor(ui): rip out old control adapter implementation

This commit is contained in:
psychedelicious 2024-06-15 20:34:28 +10:00
parent 8864ad1b50
commit 5fc7a03669
55 changed files with 30 additions and 4270 deletions

View File

@ -4,15 +4,15 @@ import type { AppStartListening } from 'app/store/middleware/listenerMiddleware'
import type { AppDispatch } from 'app/store/store';
import { parseify } from 'common/util/serialize';
import {
controlAdapterImageChanged,
controlAdapterModelChanged,
controlAdapterProcessedImageChanged,
controlAdapterProcessorConfigChanged,
controlAdapterProcessorPendingBatchIdChanged,
controlAdapterRecalled,
caImageChanged,
caModelChanged,
caProcessedImageChanged,
caProcessorConfigChanged,
caProcessorPendingBatchIdChanged,
caRecalled,
} from 'features/controlLayers/store/canvasV2Slice';
import { isControlAdapterLayer } from 'features/controlLayers/store/types';
import { CA_PROCESSOR_DATA } from 'features/controlLayers/util/controlAdapters';
import { selectCA } from 'features/controlLayers/store/controlAdaptersReducers';
import { CA_PROCESSOR_DATA } from 'features/controlLayers/store/types';
import { toast } from 'features/toast/toast';
import { t } from 'i18next';
import { isEqual } from 'lodash-es';
@ -22,13 +22,7 @@ import type { BatchConfig } from 'services/api/types';
import { socketInvocationComplete } from 'services/events/actions';
import { assert } from 'tsafe';
const matcher = isAnyOf(
controlAdapterImageChanged,
controlAdapterProcessedImageChanged,
controlAdapterProcessorConfigChanged,
controlAdapterModelChanged,
controlAdapterRecalled
);
const matcher = isAnyOf(caImageChanged, caProcessedImageChanged, caProcessorConfigChanged, caModelChanged, caRecalled);
const DEBOUNCE_MS = 300;
const log = logger('session');
@ -36,7 +30,7 @@ const log = logger('session');
/**
* Simple helper to cancel a batch and reset the pending batch ID
*/
const cancelProcessorBatch = async (dispatch: AppDispatch, layerId: string, batchId: string) => {
const cancelProcessorBatch = async (dispatch: AppDispatch, id: string, batchId: string) => {
const req = dispatch(queueApi.endpoints.cancelByBatchIds.initiate({ batch_ids: [batchId] }));
log.trace({ batchId }, 'Cancelling existing preprocessor batch');
try {
@ -46,7 +40,7 @@ const cancelProcessorBatch = async (dispatch: AppDispatch, layerId: string, batc
} finally {
req.reset();
// Always reset the pending batch ID - the cancel req could fail if the batch doesn't exist
dispatch(controlAdapterProcessorPendingBatchIdChanged({ layerId, batchId: null }));
dispatch(caProcessorPendingBatchIdChanged({ id, batchId: null }));
}
};
@ -54,7 +48,7 @@ export const addControlAdapterPreprocessor = (startAppListening: AppStartListeni
startAppListening({
matcher,
effect: async (action, { dispatch, getState, getOriginalState, cancelActiveListeners, delay, take, signal }) => {
const layerId = controlAdapterRecalled.match(action) ? action.payload.id : action.payload.layerId;
const id = caRecalled.match(action) ? action.payload.data.id : action.payload.id;
const state = getState();
const originalState = getOriginalState();
@ -65,22 +59,20 @@ export const addControlAdapterPreprocessor = (startAppListening: AppStartListeni
// Delay before starting actual work
await delay(DEBOUNCE_MS);
const layer = state.canvasV2.layers.filter(isControlAdapterLayer).find((l) => l.id === layerId);
const ca = selectCA(state.canvasV2, id);
if (!layer) {
if (!ca) {
return;
}
// We should only process if the processor settings or image have changed
const originalLayer = originalState.canvasV2.layers
.filter(isControlAdapterLayer)
.find((l) => l.id === layerId);
const originalImage = originalLayer?.controlAdapter.image;
const originalConfig = originalLayer?.controlAdapter.processorConfig;
const originalCA = selectCA(originalState.canvasV2, id);
const originalImage = originalCA?.image;
const originalConfig = originalCA?.processorConfig;
const image = layer.controlAdapter.image;
const processedImage = layer.controlAdapter.processedImage;
const config = layer.controlAdapter.processorConfig;
const image = ca.image;
const processedImage = ca.processedImage;
const config = ca.processorConfig;
if (isEqual(config, originalConfig) && isEqual(image, originalImage) && processedImage) {
// Neither config nor image have changed, we can bail
@ -91,15 +83,15 @@ export const addControlAdapterPreprocessor = (startAppListening: AppStartListeni
// - If we have no image, we have nothing to process
// - If we have no processor config, we have nothing to process
// Clear the processed image and bail
dispatch(controlAdapterProcessedImageChanged({ layerId, imageDTO: null }));
dispatch(caProcessedImageChanged({ id, imageDTO: null }));
return;
}
// At this point, the user has stopped fiddling with the processor settings and there is a processor selected.
// If there is a pending processor batch, cancel it.
if (layer.controlAdapter.processorPendingBatchId) {
cancelProcessorBatch(dispatch, layerId, layer.controlAdapter.processorPendingBatchId);
if (ca.processorPendingBatchId) {
cancelProcessorBatch(dispatch, id, ca.processorPendingBatchId);
}
// TODO(psyche): I can't get TS to be happy, it thinkgs `config` is `never` but it should be inferred from the generic... I'll just cast it for now
@ -132,7 +124,7 @@ export const addControlAdapterPreprocessor = (startAppListening: AppStartListeni
const enqueueResult = await req.unwrap();
// TODO(psyche): Update the pydantic models, pretty sure we will _always_ have a batch_id here, but the model says it's optional
assert(enqueueResult.batch.batch_id, 'Batch ID not returned from queue');
dispatch(controlAdapterProcessorPendingBatchIdChanged({ layerId, batchId: enqueueResult.batch.batch_id }));
dispatch(caProcessorPendingBatchIdChanged({ id, batchId: enqueueResult.batch.batch_id }));
log.debug({ enqueueResult: parseify(enqueueResult) }, t('queue.graphQueued'));
// Wait for the processor node to complete
@ -154,17 +146,15 @@ export const addControlAdapterPreprocessor = (startAppListening: AppStartListeni
assert(imageDTO, "Failed to fetch processor output's image DTO");
// Whew! We made it. Update the layer with the processed image
log.debug({ layerId, imageDTO }, 'ControlNet image processed');
dispatch(controlAdapterProcessedImageChanged({ layerId, imageDTO }));
dispatch(controlAdapterProcessorPendingBatchIdChanged({ layerId, batchId: null }));
log.debug({ id, imageDTO }, 'ControlNet image processed');
dispatch(caProcessedImageChanged({ id, imageDTO }));
dispatch(caProcessorPendingBatchIdChanged({ id, batchId: null }));
} catch (error) {
if (signal.aborted) {
// The listener was canceled - we need to cancel the pending processor batch, if there is one (could have changed by now).
const pendingBatchId = getState()
.canvasV2.layers.filter(isControlAdapterLayer)
.find((l) => l.id === layerId)?.controlAdapter.processorPendingBatchId;
const pendingBatchId = selectCA(getState().canvasV2, id)?.processorPendingBatchId;
if (pendingBatchId) {
cancelProcessorBatch(dispatch, layerId, pendingBatchId);
cancelProcessorBatch(dispatch, id, pendingBatchId);
}
log.trace('Control Adapter preprocessor cancelled');
} else {
@ -174,7 +164,7 @@ export const addControlAdapterPreprocessor = (startAppListening: AppStartListeni
if (error instanceof Object) {
if ('data' in error && 'status' in error) {
if (error.status === 403) {
dispatch(controlAdapterImageChanged({ layerId, imageDTO: null }));
dispatch(caImageChanged({ id, imageDTO: null }));
return;
}
}

View File

@ -1,85 +0,0 @@
import type { AnyListenerPredicate } from '@reduxjs/toolkit';
import { logger } from 'app/logging/logger';
import type { AppStartListening } from 'app/store/middleware/listenerMiddleware';
import type { RootState } from 'app/store/store';
import { controlAdapterImageProcessed } from 'features/controlAdapters/store/actions';
import {
controlAdapterAutoConfigToggled,
controlAdapterImageChanged,
controlAdapterModelChanged,
controlAdapterProcessorParamsChanged,
controlAdapterProcessortTypeChanged,
selectControlAdapterById,
} from 'features/controlAdapters/store/controlAdaptersSlice';
import { isControlNetOrT2IAdapter } from 'features/controlAdapters/store/types';
type AnyControlAdapterParamChangeAction =
| ReturnType<typeof controlAdapterProcessorParamsChanged>
| ReturnType<typeof controlAdapterModelChanged>
| ReturnType<typeof controlAdapterImageChanged>
| ReturnType<typeof controlAdapterProcessortTypeChanged>
| ReturnType<typeof controlAdapterAutoConfigToggled>;
const predicate: AnyListenerPredicate<RootState> = (action, state, prevState) => {
const isActionMatched =
controlAdapterProcessorParamsChanged.match(action) ||
controlAdapterModelChanged.match(action) ||
controlAdapterImageChanged.match(action) ||
controlAdapterProcessortTypeChanged.match(action) ||
controlAdapterAutoConfigToggled.match(action);
if (!isActionMatched) {
return false;
}
const { id } = action.payload;
const prevCA = selectControlAdapterById(prevState.controlAdapters, id);
const ca = selectControlAdapterById(state.controlAdapters, id);
if (!prevCA || !isControlNetOrT2IAdapter(prevCA) || !ca || !isControlNetOrT2IAdapter(ca)) {
return false;
}
if (controlAdapterAutoConfigToggled.match(action)) {
// do not process if the user just disabled auto-config
if (prevCA.shouldAutoConfig === true) {
return false;
}
}
const { controlImage, processorType, shouldAutoConfig } = ca;
if (controlAdapterModelChanged.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 hasControlImage = Boolean(controlImage);
return isProcessorSelected && hasControlImage;
};
const DEBOUNCE_MS = 300;
/**
* Listener that automatically processes a ControlNet image when its processor parameters are changed.
*
* The network request is debounced.
*/
export const addControlNetAutoProcessListener = (startAppListening: AppStartListening) => {
startAppListening({
predicate,
effect: async (action, { dispatch, cancelActiveListeners, delay }) => {
const log = logger('session');
const { id } = (action as AnyControlAdapterParamChangeAction).payload;
// Cancel any in-progress instances of this listener
cancelActiveListeners();
log.trace('ControlNet auto-process triggered');
// Delay before starting actual work
await delay(DEBOUNCE_MS);
dispatch(controlAdapterImageProcessed({ id }));
},
});
};

View File

@ -1,118 +0,0 @@
import { logger } from 'app/logging/logger';
import type { AppStartListening } from 'app/store/middleware/listenerMiddleware';
import { parseify } from 'common/util/serialize';
import { controlAdapterImageProcessed } from 'features/controlAdapters/store/actions';
import {
controlAdapterImageChanged,
controlAdapterProcessedImageChanged,
pendingControlImagesCleared,
selectControlAdapterById,
} from 'features/controlAdapters/store/controlAdaptersSlice';
import { isControlNetOrT2IAdapter } from 'features/controlAdapters/store/types';
import { toast } from 'features/toast/toast';
import { t } from 'i18next';
import { imagesApi } from 'services/api/endpoints/images';
import { queueApi } from 'services/api/endpoints/queue';
import type { BatchConfig, ImageDTO } from 'services/api/types';
import { socketInvocationComplete } from 'services/events/actions';
export const addControlNetImageProcessedListener = (startAppListening: AppStartListening) => {
startAppListening({
actionCreator: controlAdapterImageProcessed,
effect: async (action, { dispatch, getState, take }) => {
const log = logger('session');
const { id } = action.payload;
const ca = selectControlAdapterById(getState().controlAdapters, id);
if (!ca?.controlImage || !isControlNetOrT2IAdapter(ca)) {
log.error('Unable to process ControlNet image');
return;
}
if (ca.processorType === 'none' || ca.processorNode.type === 'none') {
return;
}
// ControlNet one-off procressing graph is just the processor node, no edges.
// Also we need to grab the image.
const nodeId = ca.processorNode.id;
const enqueueBatchArg: BatchConfig = {
prepend: true,
batch: {
graph: {
nodes: {
[ca.processorNode.id]: {
...ca.processorNode,
is_intermediate: true,
use_cache: false,
image: { image_name: ca.controlImage },
},
},
edges: [],
},
runs: 1,
},
};
try {
const req = dispatch(
queueApi.endpoints.enqueueBatch.initiate(enqueueBatchArg, {
fixedCacheKey: 'enqueueBatch',
})
);
const enqueueResult = await req.unwrap();
req.reset();
log.debug({ enqueueResult: parseify(enqueueResult) }, t('queue.graphQueued'));
const [invocationCompleteAction] = await take(
(action): action is ReturnType<typeof socketInvocationComplete> =>
socketInvocationComplete.match(action) &&
action.payload.data.batch_id === enqueueResult.batch.batch_id &&
action.payload.data.invocation_source_id === nodeId
);
// We still have to check the output type
if (invocationCompleteAction.payload.data.result.type === 'image_output') {
const { image_name } = invocationCompleteAction.payload.data.result.image;
// Wait for the ImageDTO to be received
const [{ payload }] = await take(
(action) =>
imagesApi.endpoints.getImageDTO.matchFulfilled(action) && action.payload.image_name === image_name
);
const processedControlImage = payload as ImageDTO;
log.debug({ controlNetId: action.payload, processedControlImage }, 'ControlNet image processed');
// Update the processed image in the store
dispatch(
controlAdapterProcessedImageChanged({
id,
processedControlImage: processedControlImage.image_name,
})
);
}
} catch (error) {
log.error({ enqueueBatchArg: parseify(enqueueBatchArg) }, t('queue.graphFailedToQueue'));
if (error instanceof Object) {
if ('data' in error && 'status' in error) {
if (error.status === 403) {
dispatch(pendingControlImagesCleared());
dispatch(controlAdapterImageChanged({ id, controlImage: null }));
return;
}
}
}
toast({
id: 'GRAPH_QUEUE_FAILED',
title: t('queue.graphFailedToQueue'),
status: 'error',
});
}
},
});
};

View File

@ -1,144 +0,0 @@
import { Box, Flex, FormControl, FormLabel, Icon, IconButton, Switch } from '@invoke-ai/ui-library';
import { useAppDispatch, useAppSelector } from 'app/store/storeHooks';
import ParamControlAdapterModel from 'features/controlAdapters/components/parameters/ParamControlAdapterModel';
import { useControlAdapterIsEnabled } from 'features/controlAdapters/hooks/useControlAdapterIsEnabled';
import { useControlAdapterType } from 'features/controlAdapters/hooks/useControlAdapterType';
import {
controlAdapterDuplicated,
controlAdapterIsEnabledChanged,
controlAdapterRemoved,
} from 'features/controlAdapters/store/controlAdaptersSlice';
import { activeTabNameSelector } from 'features/ui/store/uiSelectors';
import type { ChangeEvent } from 'react';
import { memo, useCallback } from 'react';
import { useTranslation } from 'react-i18next';
import { PiCaretUpBold, PiCopyBold, PiTrashSimpleBold } from 'react-icons/pi';
import { useToggle } from 'react-use';
import ControlAdapterImagePreview from './ControlAdapterImagePreview';
import ControlAdapterProcessorComponent from './ControlAdapterProcessorComponent';
import ControlAdapterShouldAutoConfig from './ControlAdapterShouldAutoConfig';
import ControlNetCanvasImageImports from './imports/ControlNetCanvasImageImports';
import { ParamControlAdapterBeginEnd } from './parameters/ParamControlAdapterBeginEnd';
import ParamControlAdapterControlMode from './parameters/ParamControlAdapterControlMode';
import ParamControlAdapterIPMethod from './parameters/ParamControlAdapterIPMethod';
import ParamControlAdapterProcessorSelect from './parameters/ParamControlAdapterProcessorSelect';
import ParamControlAdapterResizeMode from './parameters/ParamControlAdapterResizeMode';
import ParamControlAdapterWeight from './parameters/ParamControlAdapterWeight';
const ControlAdapterConfig = (props: { id: string; number: number }) => {
const { id, number } = props;
const controlAdapterType = useControlAdapterType(id);
const dispatch = useAppDispatch();
const { t } = useTranslation();
const activeTabName = useAppSelector(activeTabNameSelector);
const isEnabled = useControlAdapterIsEnabled(id);
const [isExpanded, toggleIsExpanded] = useToggle(false);
const handleDelete = useCallback(() => {
dispatch(controlAdapterRemoved({ id }));
}, [id, dispatch]);
const handleDuplicate = useCallback(() => {
dispatch(controlAdapterDuplicated(id));
}, [id, dispatch]);
const handleToggleIsEnabled = useCallback(
(e: ChangeEvent<HTMLInputElement>) => {
dispatch(
controlAdapterIsEnabledChanged({
id,
isEnabled: e.target.checked,
})
);
},
[id, dispatch]
);
if (!controlAdapterType) {
return null;
}
return (
<Flex flexDir="column" gap={4} p={4} borderRadius="base" position="relative" bg="base.750">
<Flex gap={2} alignItems="center" justifyContent="space-between">
<FormControl>
<FormLabel flexGrow={1}>{t(`controlnet.${controlAdapterType}`, { number })}</FormLabel>
<Switch
aria-label={t('controlnet.toggleControlNet')}
isChecked={isEnabled}
onChange={handleToggleIsEnabled}
/>
</FormControl>
</Flex>
<Flex gap={4} alignItems="center">
<Box minW={0} w="full" transitionProperty="common" transitionDuration="0.1s">
<ParamControlAdapterModel id={id} />
</Box>
{activeTabName === 'canvas' && <ControlNetCanvasImageImports id={id} />}
<IconButton
size="sm"
tooltip={t('controlnet.duplicate')}
aria-label={t('controlnet.duplicate')}
onClick={handleDuplicate}
icon={<PiCopyBold />}
/>
<IconButton
size="sm"
tooltip={t('controlnet.delete')}
aria-label={t('controlnet.delete')}
colorScheme="error"
onClick={handleDelete}
icon={<PiTrashSimpleBold />}
/>
<IconButton
size="sm"
tooltip={isExpanded ? t('controlnet.hideAdvanced') : t('controlnet.showAdvanced')}
aria-label={isExpanded ? t('controlnet.hideAdvanced') : t('controlnet.showAdvanced')}
onClick={toggleIsExpanded}
variant="ghost"
icon={
<Icon
boxSize={4}
as={PiCaretUpBold}
transform={isExpanded ? 'rotate(0deg)' : 'rotate(180deg)'}
transitionProperty="common"
transitionDuration="normal"
/>
}
/>
</Flex>
<Flex w="full" flexDir="column" gap={4}>
<Flex gap={8} w="full" alignItems="center">
<Flex flexDir="column" gap={4} h={controlAdapterType === 'ip_adapter' ? 40 : 32} w="full">
<ParamControlAdapterIPMethod id={id} />
<ParamControlAdapterWeight id={id} />
<ParamControlAdapterBeginEnd id={id} />
</Flex>
{!isExpanded && (
<Flex alignItems="center" justifyContent="center" h={32} w={32} aspectRatio="1/1">
<ControlAdapterImagePreview id={id} isSmall />
</Flex>
)}
</Flex>
</Flex>
{isExpanded && (
<>
<Flex gap={2}>
<ParamControlAdapterControlMode id={id} />
<ParamControlAdapterResizeMode id={id} />
</Flex>
<ParamControlAdapterProcessorSelect id={id} />
<ControlAdapterImagePreview id={id} />
<ControlAdapterShouldAutoConfig id={id} />
<ControlAdapterProcessorComponent id={id} />
</>
)}
</Flex>
);
};
export default memo(ControlAdapterConfig);

View File

@ -1,227 +0,0 @@
import { Box, Flex, Spinner } from '@invoke-ai/ui-library';
import { skipToken } from '@reduxjs/toolkit/query';
import { createMemoizedSelector } from 'app/store/createMemoizedSelector';
import { useAppDispatch, useAppSelector } from 'app/store/storeHooks';
import IAIDndImage from 'common/components/IAIDndImage';
import IAIDndImageIcon from 'common/components/IAIDndImageIcon';
import { setBoundingBoxDimensions } from 'features/canvas/store/canvasSlice';
import { useControlAdapterControlImage } from 'features/controlAdapters/hooks/useControlAdapterControlImage';
import { useControlAdapterProcessedControlImage } from 'features/controlAdapters/hooks/useControlAdapterProcessedControlImage';
import { useControlAdapterProcessorType } from 'features/controlAdapters/hooks/useControlAdapterProcessorType';
import {
controlAdapterImageChanged,
selectControlAdaptersSlice,
} from 'features/controlAdapters/store/controlAdaptersSlice';
import { heightChanged, widthChanged } from 'features/controlLayers/store/canvasV2Slice';
import type { TypesafeDraggableData, TypesafeDroppableData } from 'features/dnd/types';
import { calculateNewSize } from 'features/parameters/components/ImageSize/calculateNewSize';
import { selectOptimalDimension } from 'features/parameters/store/generationSlice';
import { activeTabNameSelector } from 'features/ui/store/uiSelectors';
import { memo, useCallback, useEffect, useMemo, useState } from 'react';
import { useTranslation } from 'react-i18next';
import { PiArrowCounterClockwiseBold, PiFloppyDiskBold, PiRulerBold } from 'react-icons/pi';
import {
useAddImageToBoardMutation,
useChangeImageIsIntermediateMutation,
useGetImageDTOQuery,
useRemoveImageFromBoardMutation,
} from 'services/api/endpoints/images';
import type { PostUploadAction } from 'services/api/types';
type Props = {
id: string;
isSmall?: boolean;
};
const selectPendingControlImages = createMemoizedSelector(
selectControlAdaptersSlice,
(controlAdapters) => controlAdapters.pendingControlImages
);
const ControlAdapterImagePreview = ({ isSmall, id }: Props) => {
const { t } = useTranslation();
const dispatch = useAppDispatch();
const controlImageName = useControlAdapterControlImage(id);
const processedControlImageName = useControlAdapterProcessedControlImage(id);
const processorType = useControlAdapterProcessorType(id);
const autoAddBoardId = useAppSelector((s) => s.gallery.autoAddBoardId);
const isConnected = useAppSelector((s) => s.system.isConnected);
const activeTabName = useAppSelector(activeTabNameSelector);
const optimalDimension = useAppSelector(selectOptimalDimension);
const pendingControlImages = useAppSelector(selectPendingControlImages);
const [isMouseOverImage, setIsMouseOverImage] = useState(false);
const { currentData: controlImage, isError: isErrorControlImage } = useGetImageDTOQuery(
controlImageName ?? skipToken
);
const { currentData: processedControlImage, isError: isErrorProcessedControlImage } = useGetImageDTOQuery(
processedControlImageName ?? skipToken
);
const [changeIsIntermediate] = useChangeImageIsIntermediateMutation();
const [addToBoard] = useAddImageToBoardMutation();
const [removeFromBoard] = useRemoveImageFromBoardMutation();
const handleResetControlImage = useCallback(() => {
dispatch(controlAdapterImageChanged({ id, controlImage: null }));
}, [id, dispatch]);
const handleSaveControlImage = useCallback(async () => {
if (!processedControlImage) {
return;
}
await changeIsIntermediate({
imageDTO: processedControlImage,
is_intermediate: false,
}).unwrap();
if (autoAddBoardId !== 'none') {
addToBoard({
imageDTO: processedControlImage,
board_id: autoAddBoardId,
});
} else {
removeFromBoard({ imageDTO: processedControlImage });
}
}, [processedControlImage, changeIsIntermediate, autoAddBoardId, addToBoard, removeFromBoard]);
const handleSetControlImageToDimensions = useCallback(() => {
if (!controlImage) {
return;
}
if (activeTabName === 'canvas') {
dispatch(setBoundingBoxDimensions({ width: controlImage.width, height: controlImage.height }, optimalDimension));
} else {
const options = { updateAspectRatio: true, clamp: true };
const { width, height } = calculateNewSize(
controlImage.width / controlImage.height,
optimalDimension * optimalDimension
);
dispatch(widthChanged({ width, ...options }));
dispatch(heightChanged({ height, ...options }));
}
}, [controlImage, activeTabName, dispatch, optimalDimension]);
const handleMouseEnter = useCallback(() => {
setIsMouseOverImage(true);
}, []);
const handleMouseLeave = useCallback(() => {
setIsMouseOverImage(false);
}, []);
const draggableData = useMemo<TypesafeDraggableData | undefined>(() => {
if (controlImage) {
return {
id,
payloadType: 'IMAGE_DTO',
payload: { imageDTO: controlImage },
};
}
}, [controlImage, id]);
const droppableData = useMemo<TypesafeDroppableData | undefined>(
() => ({
id,
actionType: 'SET_CONTROL_ADAPTER_IMAGE',
context: { id },
}),
[id]
);
const postUploadAction = useMemo<PostUploadAction>(() => ({ type: 'SET_CONTROL_ADAPTER_IMAGE', id }), [id]);
const shouldShowProcessedImage =
controlImage &&
processedControlImage &&
!isMouseOverImage &&
!pendingControlImages.includes(id) &&
processorType !== 'none';
useEffect(() => {
if (isConnected && (isErrorControlImage || isErrorProcessedControlImage)) {
handleResetControlImage();
}
}, [handleResetControlImage, isConnected, isErrorControlImage, isErrorProcessedControlImage]);
return (
<Flex
onMouseEnter={handleMouseEnter}
onMouseLeave={handleMouseLeave}
position="relative"
w="full"
h={isSmall ? 32 : 366} // magic no touch
alignItems="center"
justifyContent="center"
>
<IAIDndImage
draggableData={draggableData}
droppableData={droppableData}
imageDTO={controlImage}
isDropDisabled={shouldShowProcessedImage}
postUploadAction={postUploadAction}
/>
<Box
position="absolute"
top={0}
insetInlineStart={0}
w="full"
h="full"
opacity={shouldShowProcessedImage ? 1 : 0}
transitionProperty="common"
transitionDuration="normal"
pointerEvents="none"
>
<IAIDndImage
draggableData={draggableData}
droppableData={droppableData}
imageDTO={processedControlImage}
isUploadDisabled={true}
/>
</Box>
{controlImage && (
<Flex position="absolute" flexDir="column" top={1} insetInlineEnd={1} gap={1}>
<IAIDndImageIcon
onClick={handleResetControlImage}
icon={<PiArrowCounterClockwiseBold size={16} />}
tooltip={t('controlnet.resetControlImage')}
/>
<IAIDndImageIcon
onClick={handleSaveControlImage}
icon={<PiFloppyDiskBold size={16} />}
tooltip={t('controlnet.saveControlImage')}
/>
<IAIDndImageIcon
onClick={handleSetControlImageToDimensions}
icon={<PiRulerBold size={16} />}
tooltip={t('controlnet.setControlImageDimensions')}
/>
</Flex>
)}
{pendingControlImages.includes(id) && (
<Flex
position="absolute"
top={0}
insetInlineStart={0}
w="full"
h="full"
alignItems="center"
justifyContent="center"
opacity={0.8}
borderRadius="base"
bg="base.900"
>
<Spinner size="xl" color="base.400" />
</Flex>
)}
</Flex>
);
};
export default memo(ControlAdapterImagePreview);

View File

@ -1,91 +0,0 @@
import { useControlAdapterIsEnabled } from 'features/controlAdapters/hooks/useControlAdapterIsEnabled';
import { useControlAdapterProcessorNode } from 'features/controlAdapters/hooks/useControlAdapterProcessorNode';
import { memo } from 'react';
import CannyProcessor from './processors/CannyProcessor';
import ColorMapProcessor from './processors/ColorMapProcessor';
import ContentShuffleProcessor from './processors/ContentShuffleProcessor';
import DepthAnyThingProcessor from './processors/DepthAnyThingProcessor';
import DWOpenposeProcessor from './processors/DWOpenposeProcessor';
import HedProcessor from './processors/HedProcessor';
import LineartAnimeProcessor from './processors/LineartAnimeProcessor';
import LineartProcessor from './processors/LineartProcessor';
import MediapipeFaceProcessor from './processors/MediapipeFaceProcessor';
import MidasDepthProcessor from './processors/MidasDepthProcessor';
import MlsdImageProcessor from './processors/MlsdImageProcessor';
import NormalBaeProcessor from './processors/NormalBaeProcessor';
import PidiProcessor from './processors/PidiProcessor';
import ZoeDepthProcessor from './processors/ZoeDepthProcessor';
type Props = {
id: string;
};
const ControlAdapterProcessorComponent = ({ id }: Props) => {
const isEnabled = useControlAdapterIsEnabled(id);
const processorNode = useControlAdapterProcessorNode(id);
if (!processorNode) {
return null;
}
if (processorNode.type === 'canny_image_processor') {
return <CannyProcessor controlNetId={id} processorNode={processorNode} isEnabled={isEnabled} />;
}
if (processorNode.type === 'color_map_image_processor') {
return <ColorMapProcessor controlNetId={id} processorNode={processorNode} isEnabled={isEnabled} />;
}
if (processorNode.type === 'depth_anything_image_processor') {
return <DepthAnyThingProcessor controlNetId={id} processorNode={processorNode} isEnabled={isEnabled} />;
}
if (processorNode.type === 'hed_image_processor') {
return <HedProcessor controlNetId={id} processorNode={processorNode} isEnabled={isEnabled} />;
}
if (processorNode.type === 'lineart_image_processor') {
return <LineartProcessor controlNetId={id} processorNode={processorNode} isEnabled={isEnabled} />;
}
if (processorNode.type === 'content_shuffle_image_processor') {
return <ContentShuffleProcessor controlNetId={id} processorNode={processorNode} isEnabled={isEnabled} />;
}
if (processorNode.type === 'lineart_anime_image_processor') {
return <LineartAnimeProcessor controlNetId={id} processorNode={processorNode} isEnabled={isEnabled} />;
}
if (processorNode.type === 'mediapipe_face_processor') {
return <MediapipeFaceProcessor controlNetId={id} processorNode={processorNode} isEnabled={isEnabled} />;
}
if (processorNode.type === 'midas_depth_image_processor') {
return <MidasDepthProcessor controlNetId={id} processorNode={processorNode} isEnabled={isEnabled} />;
}
if (processorNode.type === 'mlsd_image_processor') {
return <MlsdImageProcessor controlNetId={id} processorNode={processorNode} isEnabled={isEnabled} />;
}
if (processorNode.type === 'normalbae_image_processor') {
return <NormalBaeProcessor controlNetId={id} processorNode={processorNode} isEnabled={isEnabled} />;
}
if (processorNode.type === 'dw_openpose_image_processor') {
return <DWOpenposeProcessor controlNetId={id} processorNode={processorNode} isEnabled={isEnabled} />;
}
if (processorNode.type === 'pidi_image_processor') {
return <PidiProcessor controlNetId={id} processorNode={processorNode} isEnabled={isEnabled} />;
}
if (processorNode.type === 'zoe_depth_image_processor') {
return <ZoeDepthProcessor controlNetId={id} processorNode={processorNode} isEnabled={isEnabled} />;
}
return null;
};
export default memo(ControlAdapterProcessorComponent);

View File

@ -1,38 +0,0 @@
import { FormControl, FormLabel, Switch } from '@invoke-ai/ui-library';
import { useAppDispatch } from 'app/store/storeHooks';
import { useControlAdapterIsEnabled } from 'features/controlAdapters/hooks/useControlAdapterIsEnabled';
import { useControlAdapterModel } from 'features/controlAdapters/hooks/useControlAdapterModel';
import { useControlAdapterShouldAutoConfig } from 'features/controlAdapters/hooks/useControlAdapterShouldAutoConfig';
import { controlAdapterAutoConfigToggled } from 'features/controlAdapters/store/controlAdaptersSlice';
import { isNil } from 'lodash-es';
import { memo, useCallback } from 'react';
import { useTranslation } from 'react-i18next';
type Props = {
id: string;
};
const ControlAdapterShouldAutoConfig = ({ id }: Props) => {
const isEnabled = useControlAdapterIsEnabled(id);
const shouldAutoConfig = useControlAdapterShouldAutoConfig(id);
const { modelConfig } = useControlAdapterModel(id);
const dispatch = useAppDispatch();
const { t } = useTranslation();
const handleShouldAutoConfigChanged = useCallback(() => {
dispatch(controlAdapterAutoConfigToggled({ id, modelConfig }));
}, [id, dispatch, modelConfig]);
if (isNil(shouldAutoConfig)) {
return null;
}
return (
<FormControl isDisabled={!isEnabled}>
<FormLabel flexGrow={1}>{t('controlnet.autoConfigure')}</FormLabel>
<Switch isChecked={shouldAutoConfig} onChange={handleShouldAutoConfigChanged} />
</FormControl>
);
};
export default memo(ControlAdapterShouldAutoConfig);

View File

@ -1,20 +0,0 @@
import { useAppDispatch } from 'app/store/storeHooks';
import { controlAdapterProcessorParamsChanged } from 'features/controlAdapters/store/controlAdaptersSlice';
import type { ControlAdapterProcessorNode } from 'features/controlAdapters/store/types';
import { useCallback } from 'react';
export const useProcessorNodeChanged = () => {
const dispatch = useAppDispatch();
const handleProcessorNodeChanged = useCallback(
(id: string, params: Partial<ControlAdapterProcessorNode>) => {
dispatch(
controlAdapterProcessorParamsChanged({
id,
params,
})
);
},
[dispatch]
);
return handleProcessorNodeChanged;
};

View File

@ -1,45 +0,0 @@
import { Flex, IconButton } from '@invoke-ai/ui-library';
import { useAppDispatch } from 'app/store/storeHooks';
import { canvasImageToControlAdapter, canvasMaskToControlAdapter } from 'features/canvas/store/actions';
import { memo, useCallback } from 'react';
import { useTranslation } from 'react-i18next';
import { PiExcludeBold, PiImageSquareBold } from 'react-icons/pi';
type ControlNetCanvasImageImportsProps = {
id: string;
};
const ControlNetCanvasImageImports = (props: ControlNetCanvasImageImportsProps) => {
const { id } = props;
const dispatch = useAppDispatch();
const { t } = useTranslation();
const handleImportImageFromCanvas = useCallback(() => {
dispatch(canvasImageToControlAdapter({ id }));
}, [id, dispatch]);
const handleImportMaskFromCanvas = useCallback(() => {
dispatch(canvasMaskToControlAdapter({ id }));
}, [id, dispatch]);
return (
<Flex gap={4}>
<IconButton
size="sm"
icon={<PiImageSquareBold />}
tooltip={t('controlnet.importImageFromCanvas')}
aria-label={t('controlnet.importImageFromCanvas')}
onClick={handleImportImageFromCanvas}
/>
<IconButton
size="sm"
icon={<PiExcludeBold />}
tooltip={t('controlnet.importMaskFromCanvas')}
aria-label={t('controlnet.importMaskFromCanvas')}
onClick={handleImportMaskFromCanvas}
/>
</Flex>
);
};
export default memo(ControlNetCanvasImageImports);

View File

@ -1,89 +0,0 @@
import { CompositeRangeSlider, FormControl, FormLabel } from '@invoke-ai/ui-library';
import { useAppDispatch } from 'app/store/storeHooks';
import { InformationalPopover } from 'common/components/InformationalPopover/InformationalPopover';
import { useControlAdapterBeginEndStepPct } from 'features/controlAdapters/hooks/useControlAdapterBeginEndStepPct';
import { useControlAdapterIsEnabled } from 'features/controlAdapters/hooks/useControlAdapterIsEnabled';
import {
controlAdapterBeginStepPctChanged,
controlAdapterEndStepPctChanged,
} from 'features/controlAdapters/store/controlAdaptersSlice';
import { memo, useCallback, useMemo } from 'react';
import { useTranslation } from 'react-i18next';
type Props = {
id: string;
};
const formatPct = (v: number) => `${Math.round(v * 100)}%`;
export const ParamControlAdapterBeginEnd = memo(({ id }: Props) => {
const isEnabled = useControlAdapterIsEnabled(id);
const stepPcts = useControlAdapterBeginEndStepPct(id);
const dispatch = useAppDispatch();
const { t } = useTranslation();
const onChange = useCallback(
(v: [number, number]) => {
dispatch(
controlAdapterBeginStepPctChanged({
id,
beginStepPct: v[0],
})
);
dispatch(
controlAdapterEndStepPctChanged({
id,
endStepPct: v[1],
})
);
},
[dispatch, id]
);
const onReset = useCallback(() => {
dispatch(
controlAdapterBeginStepPctChanged({
id,
beginStepPct: 0,
})
);
dispatch(
controlAdapterEndStepPctChanged({
id,
endStepPct: 1,
})
);
}, [dispatch, id]);
const value = useMemo<[number, number]>(() => [stepPcts?.beginStepPct ?? 0, stepPcts?.endStepPct ?? 1], [stepPcts]);
if (!stepPcts) {
return null;
}
return (
<FormControl isDisabled={!isEnabled} orientation="vertical">
<InformationalPopover feature="controlNetBeginEnd">
<FormLabel>{t('controlnet.beginEndStepPercent')}</FormLabel>
</InformationalPopover>
<CompositeRangeSlider
aria-label={ariaLabel}
value={value}
onChange={onChange}
onReset={onReset}
min={0}
max={1}
step={0.05}
fineStep={0.01}
minStepsBetweenThumbs={1}
formatValue={formatPct}
marks
withThumbTooltip
/>
</FormControl>
);
});
ParamControlAdapterBeginEnd.displayName = 'ParamControlAdapterBeginEnd';
const ariaLabel = ['Begin Step %', 'End Step %'];

View File

@ -1,66 +0,0 @@
import type { ComboboxOnChange } from '@invoke-ai/ui-library';
import { Combobox, FormControl, FormLabel } from '@invoke-ai/ui-library';
import { useAppDispatch } from 'app/store/storeHooks';
import { InformationalPopover } from 'common/components/InformationalPopover/InformationalPopover';
import { useControlAdapterControlMode } from 'features/controlAdapters/hooks/useControlAdapterControlMode';
import { useControlAdapterIsEnabled } from 'features/controlAdapters/hooks/useControlAdapterIsEnabled';
import { controlAdapterControlModeChanged } from 'features/controlAdapters/store/controlAdaptersSlice';
import type { ControlMode } from 'features/controlAdapters/store/types';
import { memo, useCallback, useMemo } from 'react';
import { useTranslation } from 'react-i18next';
type Props = {
id: string;
};
const ParamControlAdapterControlMode = ({ id }: Props) => {
const isEnabled = useControlAdapterIsEnabled(id);
const controlMode = useControlAdapterControlMode(id);
const dispatch = useAppDispatch();
const { t } = useTranslation();
const CONTROL_MODE_DATA = useMemo(
() => [
{ label: t('controlnet.balanced'), value: 'balanced' },
{ label: t('controlnet.prompt'), value: 'more_prompt' },
{ label: t('controlnet.control'), value: 'more_control' },
{ label: t('controlnet.megaControl'), value: 'unbalanced' },
],
[t]
);
const handleControlModeChange = useCallback<ComboboxOnChange>(
(v) => {
if (!v) {
return;
}
dispatch(
controlAdapterControlModeChanged({
id,
controlMode: v.value as ControlMode,
})
);
},
[id, dispatch]
);
const value = useMemo(
() => CONTROL_MODE_DATA.filter((o) => o.value === controlMode)[0],
[CONTROL_MODE_DATA, controlMode]
);
if (!controlMode) {
return null;
}
return (
<FormControl isDisabled={!isEnabled}>
<InformationalPopover feature="controlNetControlMode">
<FormLabel>{t('controlnet.controlMode')}</FormLabel>
</InformationalPopover>
<Combobox value={value} options={CONTROL_MODE_DATA} onChange={handleControlModeChange} />
</FormControl>
);
};
export default memo(ParamControlAdapterControlMode);

View File

@ -1,63 +0,0 @@
import type { ComboboxOnChange } from '@invoke-ai/ui-library';
import { Combobox, FormControl, FormLabel } from '@invoke-ai/ui-library';
import { useAppDispatch } from 'app/store/storeHooks';
import { InformationalPopover } from 'common/components/InformationalPopover/InformationalPopover';
import { useControlAdapterIPMethod } from 'features/controlAdapters/hooks/useControlAdapterIPMethod';
import { useControlAdapterIsEnabled } from 'features/controlAdapters/hooks/useControlAdapterIsEnabled';
import { controlAdapterIPMethodChanged } from 'features/controlAdapters/store/controlAdaptersSlice';
import type { IPMethod } from 'features/controlAdapters/store/types';
import { isIPMethod } from 'features/controlAdapters/store/types';
import { memo, useCallback, useMemo } from 'react';
import { useTranslation } from 'react-i18next';
type Props = {
id: string;
};
const ParamControlAdapterIPMethod = ({ id }: Props) => {
const isEnabled = useControlAdapterIsEnabled(id);
const method = useControlAdapterIPMethod(id);
const dispatch = useAppDispatch();
const { t } = useTranslation();
const options: { label: string; value: IPMethod }[] = useMemo(
() => [
{ label: t('controlnet.full'), value: 'full' },
{ label: `${t('controlnet.style')} (${t('common.beta')})`, value: 'style' },
{ label: `${t('controlnet.composition')} (${t('common.beta')})`, value: 'composition' },
],
[t]
);
const handleIPMethodChanged = useCallback<ComboboxOnChange>(
(v) => {
if (!isIPMethod(v?.value)) {
return;
}
dispatch(
controlAdapterIPMethodChanged({
id,
method: v.value,
})
);
},
[id, dispatch]
);
const value = useMemo(() => options.find((o) => o.value === method), [options, method]);
if (!method) {
return null;
}
return (
<FormControl>
<InformationalPopover feature="controlNetResizeMode">
<FormLabel>{t('controlnet.ipAdapterMethod')}</FormLabel>
</InformationalPopover>
<Combobox value={value} options={options} isDisabled={!isEnabled} onChange={handleIPMethodChanged} />
</FormControl>
);
};
export default memo(ParamControlAdapterIPMethod);

View File

@ -1,139 +0,0 @@
import type { ComboboxOnChange, ComboboxOption } from '@invoke-ai/ui-library';
import { Combobox, Flex, FormControl, Tooltip } from '@invoke-ai/ui-library';
import { createMemoizedSelector } from 'app/store/createMemoizedSelector';
import { useAppDispatch, useAppSelector } from 'app/store/storeHooks';
import { useGroupedModelCombobox } from 'common/hooks/useGroupedModelCombobox';
import { useControlAdapterCLIPVisionModel } from 'features/controlAdapters/hooks/useControlAdapterCLIPVisionModel';
import { useControlAdapterIsEnabled } from 'features/controlAdapters/hooks/useControlAdapterIsEnabled';
import { useControlAdapterModel } from 'features/controlAdapters/hooks/useControlAdapterModel';
import { useControlAdapterModels } from 'features/controlAdapters/hooks/useControlAdapterModels';
import { useControlAdapterType } from 'features/controlAdapters/hooks/useControlAdapterType';
import {
controlAdapterCLIPVisionModelChanged,
controlAdapterModelChanged,
} from 'features/controlAdapters/store/controlAdaptersSlice';
import type { CLIPVisionModel } from 'features/controlAdapters/store/types';
import { selectGenerationSlice } from 'features/parameters/store/generationSlice';
import { memo, useCallback, useMemo } from 'react';
import { useTranslation } from 'react-i18next';
import type {
AnyModelConfig,
ControlNetModelConfig,
IPAdapterModelConfig,
T2IAdapterModelConfig,
} from 'services/api/types';
type ParamControlAdapterModelProps = {
id: string;
};
const selectMainModel = createMemoizedSelector(selectGenerationSlice, (generation) => generation.model);
const ParamControlAdapterModel = ({ id }: ParamControlAdapterModelProps) => {
const isEnabled = useControlAdapterIsEnabled(id);
const controlAdapterType = useControlAdapterType(id);
const { modelConfig } = useControlAdapterModel(id);
const dispatch = useAppDispatch();
const currentBaseModel = useAppSelector((s) => s.generation.model?.base);
const currentCLIPVisionModel = useControlAdapterCLIPVisionModel(id);
const mainModel = useAppSelector(selectMainModel);
const { t } = useTranslation();
const [modelConfigs, { isLoading }] = useControlAdapterModels(controlAdapterType);
const _onChange = useCallback(
(modelConfig: ControlNetModelConfig | IPAdapterModelConfig | T2IAdapterModelConfig | null) => {
if (!modelConfig) {
return;
}
dispatch(
controlAdapterModelChanged({
id,
modelConfig,
})
);
},
[dispatch, id]
);
const onCLIPVisionModelChange = useCallback<ComboboxOnChange>(
(v) => {
if (!v?.value) {
return;
}
dispatch(controlAdapterCLIPVisionModelChanged({ id, clipVisionModel: v.value as CLIPVisionModel }));
},
[dispatch, id]
);
const selectedModel = useMemo(
() => (modelConfig && controlAdapterType ? { ...modelConfig, model_type: controlAdapterType } : null),
[controlAdapterType, modelConfig]
);
const getIsDisabled = useCallback(
(model: AnyModelConfig): boolean => {
const isCompatible = currentBaseModel === model.base;
const hasMainModel = Boolean(currentBaseModel);
return !hasMainModel || !isCompatible;
},
[currentBaseModel]
);
const { options, value, onChange, noOptionsMessage } = useGroupedModelCombobox({
modelConfigs,
onChange: _onChange,
selectedModel,
getIsDisabled,
isLoading,
});
const clipVisionOptions = useMemo<ComboboxOption[]>(
() => [
{ label: 'ViT-H', value: 'ViT-H' },
{ label: 'ViT-G', value: 'ViT-G' },
],
[]
);
const clipVisionModel = useMemo(
() => clipVisionOptions.find((o) => o.value === currentCLIPVisionModel),
[clipVisionOptions, currentCLIPVisionModel]
);
return (
<Flex sx={{ gap: 2 }}>
<Tooltip label={selectedModel?.description}>
<FormControl
isDisabled={!isEnabled}
isInvalid={!value || mainModel?.base !== modelConfig?.base}
sx={{ width: '100%' }}
>
<Combobox
options={options}
placeholder={t('controlnet.selectModel')}
value={value}
onChange={onChange}
noOptionsMessage={noOptionsMessage}
/>
</FormControl>
</Tooltip>
{modelConfig?.type === 'ip_adapter' && modelConfig.format === 'checkpoint' && (
<FormControl
isDisabled={!isEnabled}
isInvalid={!value || mainModel?.base !== modelConfig?.base}
sx={{ width: 'max-content', minWidth: 28 }}
>
<Combobox
options={clipVisionOptions}
placeholder={t('controlnet.selectCLIPVisionModel')}
value={clipVisionModel}
onChange={onCLIPVisionModelChange}
/>
</FormControl>
)}
</Flex>
);
};
export default memo(ParamControlAdapterModel);

View File

@ -1,70 +0,0 @@
import type { ComboboxOnChange, ComboboxOption } from '@invoke-ai/ui-library';
import { Combobox, FormControl, FormLabel } from '@invoke-ai/ui-library';
import { createMemoizedSelector } from 'app/store/createMemoizedSelector';
import { useAppDispatch, useAppSelector } from 'app/store/storeHooks';
import { InformationalPopover } from 'common/components/InformationalPopover/InformationalPopover';
import { useControlAdapterIsEnabled } from 'features/controlAdapters/hooks/useControlAdapterIsEnabled';
import { useControlAdapterProcessorNode } from 'features/controlAdapters/hooks/useControlAdapterProcessorNode';
import { CONTROLNET_PROCESSORS } from 'features/controlAdapters/store/constants';
import { controlAdapterProcessortTypeChanged } from 'features/controlAdapters/store/controlAdaptersSlice';
import type { ControlAdapterProcessorType } from 'features/controlAdapters/store/types';
import { configSelector } from 'features/system/store/configSelectors';
import { map } from 'lodash-es';
import { memo, useCallback, useMemo } from 'react';
import { useTranslation } from 'react-i18next';
type Props = {
id: string;
};
const selectOptions = createMemoizedSelector(configSelector, (config) => {
const options: ComboboxOption[] = map(CONTROLNET_PROCESSORS, (p) => ({
value: p.type,
label: p.label,
}))
.sort((a, b) =>
// sort 'none' to the top
a.value === 'none' ? -1 : b.value === 'none' ? 1 : a.label.localeCompare(b.label)
)
.filter((d) => !config.sd.disabledControlNetProcessors.includes(d.value as ControlAdapterProcessorType));
return options;
});
const ParamControlAdapterProcessorSelect = ({ id }: Props) => {
const isEnabled = useControlAdapterIsEnabled(id);
const processorNode = useControlAdapterProcessorNode(id);
const dispatch = useAppDispatch();
const options = useAppSelector(selectOptions);
const { t } = useTranslation();
const onChange = useCallback<ComboboxOnChange>(
(v) => {
if (!v) {
return;
}
dispatch(
controlAdapterProcessortTypeChanged({
id,
processorType: v.value as ControlAdapterProcessorType, // TODO: need runtime check...
})
);
},
[id, dispatch]
);
const value = useMemo(() => options.find((o) => o.value === processorNode?.type), [options, processorNode]);
if (!processorNode) {
return null;
}
return (
<FormControl isDisabled={!isEnabled}>
<InformationalPopover feature="controlNetProcessor">
<FormLabel>{t('controlnet.processor')}</FormLabel>
</InformationalPopover>
<Combobox value={value} options={options} onChange={onChange} />
</FormControl>
);
};
export default memo(ParamControlAdapterProcessorSelect);

View File

@ -1,64 +0,0 @@
import type { ComboboxOnChange } from '@invoke-ai/ui-library';
import { Combobox, FormControl, FormLabel } from '@invoke-ai/ui-library';
import { useAppDispatch } from 'app/store/storeHooks';
import { InformationalPopover } from 'common/components/InformationalPopover/InformationalPopover';
import { useControlAdapterIsEnabled } from 'features/controlAdapters/hooks/useControlAdapterIsEnabled';
import { useControlAdapterResizeMode } from 'features/controlAdapters/hooks/useControlAdapterResizeMode';
import { controlAdapterResizeModeChanged } from 'features/controlAdapters/store/controlAdaptersSlice';
import type { ResizeMode } from 'features/controlAdapters/store/types';
import { isResizeMode } from 'features/controlAdapters/store/types';
import { memo, useCallback, useMemo } from 'react';
import { useTranslation } from 'react-i18next';
type Props = {
id: string;
};
const ParamControlAdapterResizeMode = ({ id }: Props) => {
const isEnabled = useControlAdapterIsEnabled(id);
const resizeMode = useControlAdapterResizeMode(id);
const dispatch = useAppDispatch();
const { t } = useTranslation();
const options: { label: string; value: ResizeMode }[] = useMemo(
() => [
{ label: t('controlnet.resize'), value: 'just_resize' },
{ label: t('controlnet.crop'), value: 'crop_resize' },
{ label: t('controlnet.fill'), value: 'fill_resize' },
{ label: t('controlnet.resizeSimple'), value: 'just_resize_simple' },
],
[t]
);
const handleResizeModeChange = useCallback<ComboboxOnChange>(
(v) => {
if (!isResizeMode(v?.value)) {
return;
}
dispatch(
controlAdapterResizeModeChanged({
id,
resizeMode: v.value,
})
);
},
[id, dispatch]
);
const value = useMemo(() => options.find((o) => o.value === resizeMode), [options, resizeMode]);
if (!resizeMode) {
return null;
}
return (
<FormControl>
<InformationalPopover feature="controlNetResizeMode">
<FormLabel>{t('controlnet.resizeMode')}</FormLabel>
</InformationalPopover>
<Combobox value={value} options={options} isDisabled={!isEnabled} onChange={handleResizeModeChange} />
</FormControl>
);
};
export default memo(ParamControlAdapterResizeMode);

View File

@ -1,74 +0,0 @@
import { CompositeNumberInput, CompositeSlider, FormControl, FormLabel } from '@invoke-ai/ui-library';
import { useAppDispatch, useAppSelector } from 'app/store/storeHooks';
import { InformationalPopover } from 'common/components/InformationalPopover/InformationalPopover';
import { useControlAdapterIsEnabled } from 'features/controlAdapters/hooks/useControlAdapterIsEnabled';
import { useControlAdapterWeight } from 'features/controlAdapters/hooks/useControlAdapterWeight';
import { controlAdapterWeightChanged } from 'features/controlAdapters/store/controlAdaptersSlice';
import { isNil } from 'lodash-es';
import { memo, useCallback } from 'react';
import { useTranslation } from 'react-i18next';
type ParamControlAdapterWeightProps = {
id: string;
};
const formatValue = (v: number) => v.toFixed(2);
const ParamControlAdapterWeight = ({ id }: ParamControlAdapterWeightProps) => {
const { t } = useTranslation();
const dispatch = useAppDispatch();
const isEnabled = useControlAdapterIsEnabled(id);
const weight = useControlAdapterWeight(id);
const initial = useAppSelector((s) => s.config.sd.ca.weight.initial);
const sliderMin = useAppSelector((s) => s.config.sd.ca.weight.sliderMin);
const sliderMax = useAppSelector((s) => s.config.sd.ca.weight.sliderMax);
const numberInputMin = useAppSelector((s) => s.config.sd.ca.weight.numberInputMin);
const numberInputMax = useAppSelector((s) => s.config.sd.ca.weight.numberInputMax);
const coarseStep = useAppSelector((s) => s.config.sd.ca.weight.coarseStep);
const fineStep = useAppSelector((s) => s.config.sd.ca.weight.fineStep);
const onChange = useCallback(
(weight: number) => {
dispatch(controlAdapterWeightChanged({ id, weight }));
},
[dispatch, id]
);
if (isNil(weight)) {
// should never happen
return null;
}
return (
<FormControl isDisabled={!isEnabled}>
<InformationalPopover feature="controlNetWeight">
<FormLabel>{t('controlnet.weight')}</FormLabel>
</InformationalPopover>
<CompositeSlider
value={weight}
onChange={onChange}
defaultValue={initial}
min={sliderMin}
max={sliderMax}
step={coarseStep}
fineStep={fineStep}
marks={marks}
formatValue={formatValue}
/>
<CompositeNumberInput
value={weight}
onChange={onChange}
min={numberInputMin}
max={numberInputMax}
step={coarseStep}
fineStep={fineStep}
maxW={20}
defaultValue={initial}
/>
</FormControl>
);
};
export default memo(ParamControlAdapterWeight);
const marks = [0, 1, 2];

View File

@ -1,129 +0,0 @@
import { CompositeNumberInput, CompositeSlider, FormControl, FormLabel } from '@invoke-ai/ui-library';
import { useProcessorNodeChanged } from 'features/controlAdapters/components/hooks/useProcessorNodeChanged';
import { useGetDefaultForControlnetProcessor } from 'features/controlAdapters/hooks/useGetDefaultForControlnetProcessor';
import type { RequiredCannyImageProcessorInvocation } from 'features/controlAdapters/store/types';
import { memo, useCallback } from 'react';
import { useTranslation } from 'react-i18next';
import ProcessorWrapper from './common/ProcessorWrapper';
type CannyProcessorProps = {
controlNetId: string;
processorNode: RequiredCannyImageProcessorInvocation;
isEnabled: boolean;
};
const CannyProcessor = (props: CannyProcessorProps) => {
const { controlNetId, processorNode, isEnabled } = props;
const { low_threshold, high_threshold, image_resolution, detect_resolution } = processorNode;
const processorChanged = useProcessorNodeChanged();
const { t } = useTranslation();
const defaults = useGetDefaultForControlnetProcessor(
'canny_image_processor'
) as RequiredCannyImageProcessorInvocation;
const handleLowThresholdChanged = useCallback(
(v: number) => {
processorChanged(controlNetId, { low_threshold: v });
},
[controlNetId, processorChanged]
);
const handleHighThresholdChanged = useCallback(
(v: number) => {
processorChanged(controlNetId, { high_threshold: v });
},
[controlNetId, processorChanged]
);
const handleImageResolutionChanged = useCallback(
(v: number) => {
processorChanged(controlNetId, { image_resolution: v });
},
[controlNetId, processorChanged]
);
const handleDetectResolutionChanged = useCallback(
(v: number) => {
processorChanged(controlNetId, { detect_resolution: v });
},
[controlNetId, processorChanged]
);
return (
<ProcessorWrapper>
<FormControl isDisabled={!isEnabled}>
<FormLabel>{t('controlnet.lowThreshold')}</FormLabel>
<CompositeSlider
value={low_threshold}
onChange={handleLowThresholdChanged}
defaultValue={defaults.low_threshold}
min={0}
max={255}
/>
<CompositeNumberInput
value={low_threshold}
onChange={handleLowThresholdChanged}
defaultValue={defaults.low_threshold}
min={0}
max={255}
/>
</FormControl>
<FormControl isDisabled={!isEnabled}>
<FormLabel>{t('controlnet.highThreshold')}</FormLabel>
<CompositeSlider
value={high_threshold}
onChange={handleHighThresholdChanged}
defaultValue={defaults.high_threshold}
min={0}
max={255}
/>
<CompositeNumberInput
value={high_threshold}
onChange={handleHighThresholdChanged}
defaultValue={defaults.high_threshold}
min={0}
max={255}
/>
</FormControl>
<FormControl isDisabled={!isEnabled}>
<FormLabel>{t('controlnet.imageResolution')}</FormLabel>
<CompositeSlider
value={image_resolution}
onChange={handleImageResolutionChanged}
defaultValue={defaults.image_resolution}
min={0}
max={4096}
marks
/>
<CompositeNumberInput
value={image_resolution}
onChange={handleImageResolutionChanged}
defaultValue={defaults.image_resolution}
min={0}
max={4096}
/>
</FormControl>
<FormControl isDisabled={!isEnabled}>
<FormLabel>{t('controlnet.detectResolution')}</FormLabel>
<CompositeSlider
value={detect_resolution}
onChange={handleDetectResolutionChanged}
defaultValue={defaults.detect_resolution}
min={0}
max={4096}
marks
/>
<CompositeNumberInput
value={detect_resolution}
onChange={handleDetectResolutionChanged}
defaultValue={defaults.detect_resolution}
min={0}
max={4096}
/>
</FormControl>
</ProcessorWrapper>
);
};
export default memo(CannyProcessor);

View File

@ -1,58 +0,0 @@
import { CompositeNumberInput, CompositeSlider, FormControl, FormLabel } from '@invoke-ai/ui-library';
import { useProcessorNodeChanged } from 'features/controlAdapters/components/hooks/useProcessorNodeChanged';
import { useGetDefaultForControlnetProcessor } from 'features/controlAdapters/hooks/useGetDefaultForControlnetProcessor';
import type { RequiredColorMapImageProcessorInvocation } from 'features/controlAdapters/store/types';
import { memo, useCallback } from 'react';
import { useTranslation } from 'react-i18next';
import ProcessorWrapper from './common/ProcessorWrapper';
type ColorMapProcessorProps = {
controlNetId: string;
processorNode: RequiredColorMapImageProcessorInvocation;
isEnabled: boolean;
};
const ColorMapProcessor = (props: ColorMapProcessorProps) => {
const { controlNetId, processorNode, isEnabled } = props;
const { color_map_tile_size } = processorNode;
const processorChanged = useProcessorNodeChanged();
const { t } = useTranslation();
const defaults = useGetDefaultForControlnetProcessor(
'color_map_image_processor'
) as RequiredColorMapImageProcessorInvocation;
const handleColorMapTileSizeChanged = useCallback(
(v: number) => {
processorChanged(controlNetId, { color_map_tile_size: v });
},
[controlNetId, processorChanged]
);
return (
<ProcessorWrapper>
<FormControl isDisabled={!isEnabled}>
<FormLabel>{t('controlnet.colorMapTileSize')}</FormLabel>
<CompositeSlider
value={color_map_tile_size}
defaultValue={defaults.color_map_tile_size}
onChange={handleColorMapTileSizeChanged}
min={1}
max={256}
step={1}
marks
/>
<CompositeNumberInput
value={color_map_tile_size}
defaultValue={defaults.color_map_tile_size}
onChange={handleColorMapTileSizeChanged}
min={1}
max={4096}
step={1}
/>
</FormControl>
</ProcessorWrapper>
);
};
export default memo(ColorMapProcessor);

View File

@ -1,118 +0,0 @@
import { CompositeNumberInput, CompositeSlider, FormControl, FormLabel } from '@invoke-ai/ui-library';
import { useProcessorNodeChanged } from 'features/controlAdapters/components/hooks/useProcessorNodeChanged';
import { useGetDefaultForControlnetProcessor } from 'features/controlAdapters/hooks/useGetDefaultForControlnetProcessor';
import type { RequiredContentShuffleImageProcessorInvocation } from 'features/controlAdapters/store/types';
import { memo, useCallback } from 'react';
import { useTranslation } from 'react-i18next';
import ProcessorWrapper from './common/ProcessorWrapper';
type Props = {
controlNetId: string;
processorNode: RequiredContentShuffleImageProcessorInvocation;
isEnabled: boolean;
};
const ContentShuffleProcessor = (props: Props) => {
const { controlNetId, processorNode, isEnabled } = props;
const { image_resolution, detect_resolution, w, h, f } = processorNode;
const processorChanged = useProcessorNodeChanged();
const { t } = useTranslation();
const defaults = useGetDefaultForControlnetProcessor(
'content_shuffle_image_processor'
) as RequiredContentShuffleImageProcessorInvocation;
const handleDetectResolutionChanged = useCallback(
(v: number) => {
processorChanged(controlNetId, { detect_resolution: v });
},
[controlNetId, processorChanged]
);
const handleImageResolutionChanged = useCallback(
(v: number) => {
processorChanged(controlNetId, { image_resolution: v });
},
[controlNetId, processorChanged]
);
const handleWChanged = useCallback(
(v: number) => {
processorChanged(controlNetId, { w: v });
},
[controlNetId, processorChanged]
);
const handleHChanged = useCallback(
(v: number) => {
processorChanged(controlNetId, { h: v });
},
[controlNetId, processorChanged]
);
const handleFChanged = useCallback(
(v: number) => {
processorChanged(controlNetId, { f: v });
},
[controlNetId, processorChanged]
);
return (
<ProcessorWrapper>
<FormControl isDisabled={!isEnabled}>
<FormLabel>{t('controlnet.detectResolution')}</FormLabel>
<CompositeSlider
value={detect_resolution}
defaultValue={defaults.detect_resolution}
onChange={handleDetectResolutionChanged}
min={0}
max={4096}
marks
/>
<CompositeNumberInput
value={detect_resolution}
defaultValue={defaults.detect_resolution}
onChange={handleDetectResolutionChanged}
min={0}
max={4096}
/>
</FormControl>
<FormControl isDisabled={!isEnabled}>
<FormLabel>{t('controlnet.imageResolution')}</FormLabel>
<CompositeSlider
value={image_resolution}
defaultValue={defaults.image_resolution}
onChange={handleImageResolutionChanged}
min={0}
max={4096}
marks
/>
<CompositeNumberInput
value={image_resolution}
defaultValue={defaults.image_resolution}
onChange={handleImageResolutionChanged}
min={0}
max={4096}
/>
</FormControl>
<FormControl isDisabled={!isEnabled}>
<FormLabel>{t('controlnet.w')}</FormLabel>
<CompositeSlider value={w} defaultValue={defaults.w} onChange={handleWChanged} min={0} max={4096} marks />
<CompositeNumberInput value={w} defaultValue={defaults.w} onChange={handleWChanged} min={0} max={4096} />
</FormControl>
<FormControl isDisabled={!isEnabled}>
<FormLabel>{t('controlnet.h')}</FormLabel>
<CompositeSlider value={h} defaultValue={defaults.h} onChange={handleHChanged} min={0} max={4096} marks />
<CompositeNumberInput value={h} defaultValue={defaults.h} onChange={handleHChanged} min={0} max={4096} />
</FormControl>
<FormControl isDisabled={!isEnabled}>
<FormLabel>{t('controlnet.f')}</FormLabel>
<CompositeSlider value={f} defaultValue={defaults.f} onChange={handleFChanged} min={0} max={4096} marks />
<CompositeNumberInput value={f} defaultValue={defaults.f} onChange={handleFChanged} min={0} max={4096} />
</FormControl>
</ProcessorWrapper>
);
};
export default memo(ContentShuffleProcessor);

View File

@ -1,93 +0,0 @@
import { CompositeNumberInput, CompositeSlider, Flex, FormControl, FormLabel, Switch } from '@invoke-ai/ui-library';
import { useProcessorNodeChanged } from 'features/controlAdapters/components/hooks/useProcessorNodeChanged';
import { useGetDefaultForControlnetProcessor } from 'features/controlAdapters/hooks/useGetDefaultForControlnetProcessor';
import type { RequiredDWOpenposeImageProcessorInvocation } from 'features/controlAdapters/store/types';
import type { ChangeEvent } from 'react';
import { memo, useCallback } from 'react';
import { useTranslation } from 'react-i18next';
import ProcessorWrapper from './common/ProcessorWrapper';
type Props = {
controlNetId: string;
processorNode: RequiredDWOpenposeImageProcessorInvocation;
isEnabled: boolean;
};
const DWOpenposeProcessor = (props: Props) => {
const { controlNetId, processorNode, isEnabled } = props;
const { image_resolution, draw_body, draw_face, draw_hands } = processorNode;
const processorChanged = useProcessorNodeChanged();
const { t } = useTranslation();
const defaults = useGetDefaultForControlnetProcessor(
'dw_openpose_image_processor'
) as RequiredDWOpenposeImageProcessorInvocation;
const handleDrawBodyChanged = useCallback(
(e: ChangeEvent<HTMLInputElement>) => {
processorChanged(controlNetId, { draw_body: e.target.checked });
},
[controlNetId, processorChanged]
);
const handleDrawFaceChanged = useCallback(
(e: ChangeEvent<HTMLInputElement>) => {
processorChanged(controlNetId, { draw_face: e.target.checked });
},
[controlNetId, processorChanged]
);
const handleDrawHandsChanged = useCallback(
(e: ChangeEvent<HTMLInputElement>) => {
processorChanged(controlNetId, { draw_hands: e.target.checked });
},
[controlNetId, processorChanged]
);
const handleImageResolutionChanged = useCallback(
(v: number) => {
processorChanged(controlNetId, { image_resolution: v });
},
[controlNetId, processorChanged]
);
return (
<ProcessorWrapper>
<Flex sx={{ flexDir: 'row', gap: 6 }}>
<FormControl isDisabled={!isEnabled} w="max-content">
<FormLabel>{t('controlnet.body')}</FormLabel>
<Switch defaultChecked={defaults.draw_body} isChecked={draw_body} onChange={handleDrawBodyChanged} />
</FormControl>
<FormControl isDisabled={!isEnabled} w="max-content">
<FormLabel>{t('controlnet.face')}</FormLabel>
<Switch defaultChecked={defaults.draw_face} isChecked={draw_face} onChange={handleDrawFaceChanged} />
</FormControl>
<FormControl isDisabled={!isEnabled} w="max-content">
<FormLabel>{t('controlnet.hands')}</FormLabel>
<Switch defaultChecked={defaults.draw_hands} isChecked={draw_hands} onChange={handleDrawHandsChanged} />
</FormControl>
</Flex>
<FormControl isDisabled={!isEnabled}>
<FormLabel>{t('controlnet.imageResolution')}</FormLabel>
<CompositeSlider
value={image_resolution}
onChange={handleImageResolutionChanged}
defaultValue={defaults.image_resolution}
min={0}
max={4096}
marks
/>
<CompositeNumberInput
value={image_resolution}
onChange={handleImageResolutionChanged}
defaultValue={defaults.image_resolution}
min={0}
max={4096}
/>
</FormControl>
</ProcessorWrapper>
);
};
export default memo(DWOpenposeProcessor);

View File

@ -1,102 +0,0 @@
import type { ComboboxOnChange } from '@invoke-ai/ui-library';
import { Combobox, CompositeNumberInput, CompositeSlider, FormControl, FormLabel } from '@invoke-ai/ui-library';
import { useProcessorNodeChanged } from 'features/controlAdapters/components/hooks/useProcessorNodeChanged';
import { useGetDefaultForControlnetProcessor } from 'features/controlAdapters/hooks/useGetDefaultForControlnetProcessor';
import type {
DepthAnythingModelSize,
RequiredDepthAnythingImageProcessorInvocation,
} from 'features/controlAdapters/store/types';
import { isDepthAnythingModelSize } from 'features/controlAdapters/store/types';
import { memo, useCallback, useMemo } from 'react';
import { useTranslation } from 'react-i18next';
import ProcessorWrapper from './common/ProcessorWrapper';
type Props = {
controlNetId: string;
processorNode: RequiredDepthAnythingImageProcessorInvocation;
isEnabled: boolean;
};
const DepthAnythingProcessor = (props: Props) => {
const { controlNetId, processorNode, isEnabled } = props;
const { model_size, resolution } = processorNode;
const processorChanged = useProcessorNodeChanged();
const { t } = useTranslation();
const defaults = useGetDefaultForControlnetProcessor(
'midas_depth_image_processor'
) as RequiredDepthAnythingImageProcessorInvocation;
const handleModelSizeChange = useCallback<ComboboxOnChange>(
(v) => {
if (!isDepthAnythingModelSize(v?.value)) {
return;
}
processorChanged(controlNetId, {
model_size: v.value,
});
},
[controlNetId, processorChanged]
);
const options: { label: string; value: DepthAnythingModelSize }[] = useMemo(
() => [
{ label: t('controlnet.depthAnythingSmallV2'), value: 'small_v2' },
{ label: t('controlnet.small'), value: 'small' },
{ label: t('controlnet.base'), value: 'base' },
{ label: t('controlnet.large'), value: 'large' },
],
[t]
);
const value = useMemo(() => options.filter((o) => o.value === model_size)[0], [options, model_size]);
const handleResolutionChange = useCallback(
(v: number) => {
processorChanged(controlNetId, { resolution: v });
},
[controlNetId, processorChanged]
);
const handleResolutionDefaultChange = useCallback(() => {
processorChanged(controlNetId, { resolution: 512 });
}, [controlNetId, processorChanged]);
return (
<ProcessorWrapper>
<FormControl isDisabled={!isEnabled}>
<FormLabel>{t('controlnet.modelSize')}</FormLabel>
<Combobox
value={value}
defaultInputValue={defaults.model_size}
options={options}
onChange={handleModelSizeChange}
/>
</FormControl>
<FormControl isDisabled={!isEnabled}>
<FormLabel>{t('controlnet.imageResolution')}</FormLabel>
<CompositeSlider
value={resolution}
onChange={handleResolutionChange}
defaultValue={defaults.resolution}
min={64}
max={4096}
step={64}
marks
onReset={handleResolutionDefaultChange}
/>
<CompositeNumberInput
value={resolution}
onChange={handleResolutionChange}
defaultValue={defaults.resolution}
min={64}
max={4096}
step={64}
/>
</FormControl>
</ProcessorWrapper>
);
};
export default memo(DepthAnythingProcessor);

View File

@ -1,95 +0,0 @@
import { CompositeNumberInput, CompositeSlider, FormControl, FormLabel, Switch } from '@invoke-ai/ui-library';
import { useProcessorNodeChanged } from 'features/controlAdapters/components/hooks/useProcessorNodeChanged';
import { useGetDefaultForControlnetProcessor } from 'features/controlAdapters/hooks/useGetDefaultForControlnetProcessor';
import type { RequiredHedImageProcessorInvocation } from 'features/controlAdapters/store/types';
import type { ChangeEvent } from 'react';
import { memo, useCallback } from 'react';
import { useTranslation } from 'react-i18next';
import ProcessorWrapper from './common/ProcessorWrapper';
type HedProcessorProps = {
controlNetId: string;
processorNode: RequiredHedImageProcessorInvocation;
isEnabled: boolean;
};
const HedPreprocessor = (props: HedProcessorProps) => {
const {
controlNetId,
processorNode: { detect_resolution, image_resolution, scribble },
isEnabled,
} = props;
const processorChanged = useProcessorNodeChanged();
const { t } = useTranslation();
const defaults = useGetDefaultForControlnetProcessor('hed_image_processor') as RequiredHedImageProcessorInvocation;
const handleDetectResolutionChanged = useCallback(
(v: number) => {
processorChanged(controlNetId, { detect_resolution: v });
},
[controlNetId, processorChanged]
);
const handleImageResolutionChanged = useCallback(
(v: number) => {
processorChanged(controlNetId, { image_resolution: v });
},
[controlNetId, processorChanged]
);
const handleScribbleChanged = useCallback(
(e: ChangeEvent<HTMLInputElement>) => {
processorChanged(controlNetId, { scribble: e.target.checked });
},
[controlNetId, processorChanged]
);
return (
<ProcessorWrapper>
<FormControl isDisabled={!isEnabled}>
<FormLabel>{t('controlnet.detectResolution')}</FormLabel>
<CompositeSlider
value={detect_resolution}
defaultValue={defaults.detect_resolution}
onChange={handleDetectResolutionChanged}
min={0}
max={4096}
marks
/>
<CompositeNumberInput
value={detect_resolution}
defaultValue={defaults.detect_resolution}
onChange={handleDetectResolutionChanged}
min={0}
max={4096}
/>
</FormControl>
<FormControl isDisabled={!isEnabled}>
<FormLabel>{t('controlnet.imageResolution')}</FormLabel>
<CompositeSlider
value={image_resolution}
onChange={handleImageResolutionChanged}
defaultValue={defaults.image_resolution}
min={0}
max={4096}
marks
/>
<CompositeNumberInput
value={image_resolution}
onChange={handleImageResolutionChanged}
defaultValue={defaults.image_resolution}
min={0}
max={4096}
/>
</FormControl>
<FormControl isDisabled={!isEnabled}>
<FormLabel>{t('controlnet.scribble')}</FormLabel>
<Switch isChecked={scribble} onChange={handleScribbleChanged} />
</FormControl>
</ProcessorWrapper>
);
};
export default memo(HedPreprocessor);

View File

@ -1,82 +0,0 @@
import { CompositeNumberInput, CompositeSlider, FormControl, FormLabel } from '@invoke-ai/ui-library';
import { useProcessorNodeChanged } from 'features/controlAdapters/components/hooks/useProcessorNodeChanged';
import { useGetDefaultForControlnetProcessor } from 'features/controlAdapters/hooks/useGetDefaultForControlnetProcessor';
import type { RequiredLineartAnimeImageProcessorInvocation } from 'features/controlAdapters/store/types';
import { memo, useCallback } from 'react';
import { useTranslation } from 'react-i18next';
import ProcessorWrapper from './common/ProcessorWrapper';
type Props = {
controlNetId: string;
processorNode: RequiredLineartAnimeImageProcessorInvocation;
isEnabled: boolean;
};
const LineartAnimeProcessor = (props: Props) => {
const { controlNetId, processorNode, isEnabled } = props;
const { image_resolution, detect_resolution } = processorNode;
const processorChanged = useProcessorNodeChanged();
const { t } = useTranslation();
const defaults = useGetDefaultForControlnetProcessor(
'lineart_anime_image_processor'
) as RequiredLineartAnimeImageProcessorInvocation;
const handleDetectResolutionChanged = useCallback(
(v: number) => {
processorChanged(controlNetId, { detect_resolution: v });
},
[controlNetId, processorChanged]
);
const handleImageResolutionChanged = useCallback(
(v: number) => {
processorChanged(controlNetId, { image_resolution: v });
},
[controlNetId, processorChanged]
);
return (
<ProcessorWrapper>
<FormControl isDisabled={!isEnabled}>
<FormLabel>{t('controlnet.detectResolution')}</FormLabel>
<CompositeSlider
value={detect_resolution}
defaultValue={defaults.detect_resolution}
onChange={handleDetectResolutionChanged}
min={0}
max={4096}
marks
/>
<CompositeNumberInput
value={detect_resolution}
defaultValue={defaults.detect_resolution}
onChange={handleDetectResolutionChanged}
min={0}
max={4096}
/>
</FormControl>
<FormControl isDisabled={!isEnabled}>
<FormLabel>{t('controlnet.imageResolution')}</FormLabel>
<CompositeSlider
value={image_resolution}
onChange={handleImageResolutionChanged}
defaultValue={defaults.image_resolution}
min={0}
max={4096}
marks
/>
<CompositeNumberInput
value={image_resolution}
onChange={handleImageResolutionChanged}
defaultValue={defaults.image_resolution}
min={0}
max={4096}
/>
</FormControl>
</ProcessorWrapper>
);
};
export default memo(LineartAnimeProcessor);

View File

@ -1,94 +0,0 @@
import { CompositeNumberInput, CompositeSlider, FormControl, FormLabel, Switch } from '@invoke-ai/ui-library';
import { useProcessorNodeChanged } from 'features/controlAdapters/components/hooks/useProcessorNodeChanged';
import { useGetDefaultForControlnetProcessor } from 'features/controlAdapters/hooks/useGetDefaultForControlnetProcessor';
import type { RequiredLineartImageProcessorInvocation } from 'features/controlAdapters/store/types';
import type { ChangeEvent } from 'react';
import { memo, useCallback } from 'react';
import { useTranslation } from 'react-i18next';
import ProcessorWrapper from './common/ProcessorWrapper';
type LineartProcessorProps = {
controlNetId: string;
processorNode: RequiredLineartImageProcessorInvocation;
isEnabled: boolean;
};
const LineartProcessor = (props: LineartProcessorProps) => {
const { controlNetId, processorNode, isEnabled } = props;
const { image_resolution, detect_resolution, coarse } = processorNode;
const processorChanged = useProcessorNodeChanged();
const { t } = useTranslation();
const defaults = useGetDefaultForControlnetProcessor(
'lineart_image_processor'
) as RequiredLineartImageProcessorInvocation;
const handleDetectResolutionChanged = useCallback(
(v: number) => {
processorChanged(controlNetId, { detect_resolution: v });
},
[controlNetId, processorChanged]
);
const handleImageResolutionChanged = useCallback(
(v: number) => {
processorChanged(controlNetId, { image_resolution: v });
},
[controlNetId, processorChanged]
);
const handleCoarseChanged = useCallback(
(e: ChangeEvent<HTMLInputElement>) => {
processorChanged(controlNetId, { coarse: e.target.checked });
},
[controlNetId, processorChanged]
);
return (
<ProcessorWrapper>
<FormControl isDisabled={!isEnabled}>
<FormLabel>{t('controlnet.detectResolution')}</FormLabel>
<CompositeSlider
value={detect_resolution}
onChange={handleDetectResolutionChanged}
defaultValue={defaults.detect_resolution}
min={0}
max={4096}
marks
/>
<CompositeNumberInput
value={detect_resolution}
onChange={handleDetectResolutionChanged}
defaultValue={defaults.detect_resolution}
min={0}
max={4096}
/>
</FormControl>
<FormControl isDisabled={!isEnabled}>
<FormLabel>{t('controlnet.imageResolution')}</FormLabel>
<CompositeSlider
value={image_resolution}
onChange={handleImageResolutionChanged}
defaultValue={defaults.image_resolution}
min={0}
max={4096}
marks
/>
<CompositeNumberInput
value={image_resolution}
onChange={handleImageResolutionChanged}
defaultValue={defaults.image_resolution}
min={0}
max={4096}
/>
</FormControl>
<FormControl isDisabled={!isEnabled}>
<FormLabel>{t('controlnet.coarse')}</FormLabel>
<Switch isChecked={coarse} onChange={handleCoarseChanged} />
</FormControl>
</ProcessorWrapper>
);
};
export default memo(LineartProcessor);

View File

@ -1,134 +0,0 @@
import { CompositeNumberInput, CompositeSlider, FormControl, FormLabel } from '@invoke-ai/ui-library';
import { useProcessorNodeChanged } from 'features/controlAdapters/components/hooks/useProcessorNodeChanged';
import { useGetDefaultForControlnetProcessor } from 'features/controlAdapters/hooks/useGetDefaultForControlnetProcessor';
import type { RequiredMediapipeFaceProcessorInvocation } from 'features/controlAdapters/store/types';
import { memo, useCallback } from 'react';
import { useTranslation } from 'react-i18next';
import ProcessorWrapper from './common/ProcessorWrapper';
type Props = {
controlNetId: string;
processorNode: RequiredMediapipeFaceProcessorInvocation;
isEnabled: boolean;
};
const MediapipeFaceProcessor = (props: Props) => {
const { controlNetId, processorNode, isEnabled } = props;
const { max_faces, min_confidence, image_resolution, detect_resolution } = processorNode;
const processorChanged = useProcessorNodeChanged();
const { t } = useTranslation();
const defaults = useGetDefaultForControlnetProcessor(
'mediapipe_face_processor'
) as RequiredMediapipeFaceProcessorInvocation;
const handleMaxFacesChanged = useCallback(
(v: number) => {
processorChanged(controlNetId, { max_faces: v });
},
[controlNetId, processorChanged]
);
const handleMinConfidenceChanged = useCallback(
(v: number) => {
processorChanged(controlNetId, { min_confidence: v });
},
[controlNetId, processorChanged]
);
const handleImageResolutionChanged = useCallback(
(v: number) => {
processorChanged(controlNetId, { image_resolution: v });
},
[controlNetId, processorChanged]
);
const handleDetectResolutionChanged = useCallback(
(v: number) => {
processorChanged(controlNetId, { detect_resolution: v });
},
[controlNetId, processorChanged]
);
return (
<ProcessorWrapper>
<FormControl isDisabled={!isEnabled}>
<FormLabel>{t('controlnet.maxFaces')}</FormLabel>
<CompositeSlider
value={max_faces}
onChange={handleMaxFacesChanged}
defaultValue={defaults.max_faces}
min={1}
max={20}
marks
/>
<CompositeNumberInput
value={max_faces}
onChange={handleMaxFacesChanged}
defaultValue={defaults.max_faces}
min={1}
max={20}
/>
</FormControl>
<FormControl isDisabled={!isEnabled}>
<FormLabel>{t('controlnet.minConfidence')}</FormLabel>
<CompositeSlider
value={min_confidence}
onChange={handleMinConfidenceChanged}
defaultValue={defaults.min_confidence}
min={0}
max={1}
step={0.01}
marks
/>
<CompositeNumberInput
value={min_confidence}
onChange={handleMinConfidenceChanged}
defaultValue={defaults.min_confidence}
min={0}
max={1}
step={0.01}
/>
</FormControl>
<FormControl isDisabled={!isEnabled}>
<FormLabel>{t('controlnet.imageResolution')}</FormLabel>
<CompositeSlider
value={image_resolution}
onChange={handleImageResolutionChanged}
defaultValue={defaults.image_resolution}
min={0}
max={4096}
marks
/>
<CompositeNumberInput
value={image_resolution}
onChange={handleImageResolutionChanged}
defaultValue={defaults.image_resolution}
min={0}
max={4096}
/>
</FormControl>
<FormControl isDisabled={!isEnabled}>
<FormLabel>{t('controlnet.detectResolution')}</FormLabel>
<CompositeSlider
value={detect_resolution}
onChange={handleDetectResolutionChanged}
defaultValue={defaults.detect_resolution}
min={0}
max={4096}
marks
/>
<CompositeNumberInput
value={detect_resolution}
onChange={handleDetectResolutionChanged}
defaultValue={defaults.detect_resolution}
min={0}
max={4096}
/>
</FormControl>
</ProcessorWrapper>
);
};
export default memo(MediapipeFaceProcessor);

View File

@ -1,136 +0,0 @@
import { CompositeNumberInput, CompositeSlider, FormControl, FormLabel } from '@invoke-ai/ui-library';
import { useProcessorNodeChanged } from 'features/controlAdapters/components/hooks/useProcessorNodeChanged';
import { useGetDefaultForControlnetProcessor } from 'features/controlAdapters/hooks/useGetDefaultForControlnetProcessor';
import type { RequiredMidasDepthImageProcessorInvocation } from 'features/controlAdapters/store/types';
import { memo, useCallback } from 'react';
import { useTranslation } from 'react-i18next';
import ProcessorWrapper from './common/ProcessorWrapper';
type Props = {
controlNetId: string;
processorNode: RequiredMidasDepthImageProcessorInvocation;
isEnabled: boolean;
};
const MidasDepthProcessor = (props: Props) => {
const { controlNetId, processorNode, isEnabled } = props;
const { a_mult, bg_th, image_resolution, detect_resolution } = processorNode;
const processorChanged = useProcessorNodeChanged();
const { t } = useTranslation();
const defaults = useGetDefaultForControlnetProcessor(
'midas_depth_image_processor'
) as RequiredMidasDepthImageProcessorInvocation;
const handleAMultChanged = useCallback(
(v: number) => {
processorChanged(controlNetId, { a_mult: v });
},
[controlNetId, processorChanged]
);
const handleBgThChanged = useCallback(
(v: number) => {
processorChanged(controlNetId, { bg_th: v });
},
[controlNetId, processorChanged]
);
const handleImageResolutionChanged = useCallback(
(v: number) => {
processorChanged(controlNetId, { image_resolution: v });
},
[controlNetId, processorChanged]
);
const handleDetectResolutionChanged = useCallback(
(v: number) => {
processorChanged(controlNetId, { detect_resolution: v });
},
[controlNetId, processorChanged]
);
return (
<ProcessorWrapper>
<FormControl isDisabled={!isEnabled}>
<FormLabel>{t('controlnet.amult')}</FormLabel>
<CompositeSlider
value={a_mult}
onChange={handleAMultChanged}
defaultValue={defaults.a_mult}
min={0}
max={20}
step={0.01}
marks
/>
<CompositeNumberInput
value={a_mult}
onChange={handleAMultChanged}
defaultValue={defaults.a_mult}
min={0}
max={20}
step={0.01}
/>
</FormControl>
<FormControl isDisabled={!isEnabled}>
<FormLabel>{t('controlnet.bgth')}</FormLabel>
<CompositeSlider
value={bg_th}
onChange={handleBgThChanged}
defaultValue={defaults.bg_th}
min={0}
max={20}
step={0.01}
marks
/>
<CompositeNumberInput
value={bg_th}
onChange={handleBgThChanged}
defaultValue={defaults.bg_th}
min={0}
max={20}
step={0.01}
/>
</FormControl>
<FormControl isDisabled={!isEnabled}>
<FormLabel>{t('controlnet.imageResolution')}</FormLabel>
<CompositeSlider
value={image_resolution}
onChange={handleImageResolutionChanged}
defaultValue={defaults.image_resolution}
min={0}
max={4096}
marks
/>
<CompositeNumberInput
value={image_resolution}
onChange={handleImageResolutionChanged}
defaultValue={defaults.image_resolution}
min={0}
max={4096}
/>
</FormControl>
<FormControl isDisabled={!isEnabled}>
<FormLabel>{t('controlnet.detectResolution')}</FormLabel>
<CompositeSlider
value={detect_resolution}
onChange={handleDetectResolutionChanged}
defaultValue={defaults.detect_resolution}
min={0}
max={4096}
marks
/>
<CompositeNumberInput
value={detect_resolution}
onChange={handleDetectResolutionChanged}
defaultValue={defaults.detect_resolution}
min={0}
max={4096}
/>
</FormControl>
</ProcessorWrapper>
);
};
export default memo(MidasDepthProcessor);

View File

@ -1,137 +0,0 @@
import { CompositeNumberInput, CompositeSlider, FormControl, FormLabel } from '@invoke-ai/ui-library';
import { useProcessorNodeChanged } from 'features/controlAdapters/components/hooks/useProcessorNodeChanged';
import { useGetDefaultForControlnetProcessor } from 'features/controlAdapters/hooks/useGetDefaultForControlnetProcessor';
import type { RequiredMlsdImageProcessorInvocation } from 'features/controlAdapters/store/types';
import { memo, useCallback } from 'react';
import { useTranslation } from 'react-i18next';
import ProcessorWrapper from './common/ProcessorWrapper';
type Props = {
controlNetId: string;
processorNode: RequiredMlsdImageProcessorInvocation;
isEnabled: boolean;
};
const MlsdImageProcessor = (props: Props) => {
const { controlNetId, processorNode, isEnabled } = props;
const { image_resolution, detect_resolution, thr_d, thr_v } = processorNode;
const processorChanged = useProcessorNodeChanged();
const { t } = useTranslation();
const defaults = useGetDefaultForControlnetProcessor('mlsd_image_processor') as RequiredMlsdImageProcessorInvocation;
const handleDetectResolutionChanged = useCallback(
(v: number) => {
processorChanged(controlNetId, { detect_resolution: v });
},
[controlNetId, processorChanged]
);
const handleImageResolutionChanged = useCallback(
(v: number) => {
processorChanged(controlNetId, { image_resolution: v });
},
[controlNetId, processorChanged]
);
const handleThrDChanged = useCallback(
(v: number) => {
processorChanged(controlNetId, { thr_d: v });
},
[controlNetId, processorChanged]
);
const handleThrVChanged = useCallback(
(v: number) => {
processorChanged(controlNetId, { thr_v: v });
},
[controlNetId, processorChanged]
);
return (
<ProcessorWrapper>
<FormControl isDisabled={!isEnabled}>
<FormLabel>{t('controlnet.detectResolution')}</FormLabel>
<CompositeSlider
value={detect_resolution}
onChange={handleDetectResolutionChanged}
defaultValue={defaults.detect_resolution}
min={0}
max={4096}
marks={marks0to4096}
/>
<CompositeNumberInput
value={detect_resolution}
onChange={handleDetectResolutionChanged}
defaultValue={defaults.detect_resolution}
min={0}
max={4096}
/>
</FormControl>
<FormControl isDisabled={!isEnabled}>
<FormLabel>{t('controlnet.imageResolution')}</FormLabel>
<CompositeSlider
value={image_resolution}
onChange={handleImageResolutionChanged}
defaultValue={defaults.image_resolution}
min={0}
max={4096}
marks={marks0to4096}
/>
<CompositeNumberInput
value={image_resolution}
onChange={handleImageResolutionChanged}
defaultValue={defaults.image_resolution}
min={0}
max={4096}
/>
</FormControl>
<FormControl isDisabled={!isEnabled}>
<FormLabel>{t('controlnet.w')} </FormLabel>
<CompositeSlider
value={thr_d}
onChange={handleThrDChanged}
defaultValue={defaults.thr_d}
min={0}
max={1}
step={0.01}
marks={marks0to1}
/>
<CompositeNumberInput
value={thr_d}
onChange={handleThrDChanged}
defaultValue={defaults.thr_d}
min={0}
max={1}
step={0.01}
/>
</FormControl>
<FormControl isDisabled={!isEnabled}>
<FormLabel>{t('controlnet.h')} </FormLabel>
<CompositeSlider
value={thr_v}
onChange={handleThrVChanged}
defaultValue={defaults.thr_v}
min={0}
max={1}
step={0.01}
marks={marks0to1}
/>
<CompositeNumberInput
value={thr_v}
onChange={handleThrVChanged}
defaultValue={defaults.thr_v}
min={0}
max={1}
step={0.01}
/>
</FormControl>
</ProcessorWrapper>
);
};
export default memo(MlsdImageProcessor);
const marks0to4096 = [0, 4096];
const marks0to1 = [0, 1];

View File

@ -1,82 +0,0 @@
import { CompositeNumberInput, CompositeSlider, FormControl, FormLabel } from '@invoke-ai/ui-library';
import { useProcessorNodeChanged } from 'features/controlAdapters/components/hooks/useProcessorNodeChanged';
import { useGetDefaultForControlnetProcessor } from 'features/controlAdapters/hooks/useGetDefaultForControlnetProcessor';
import type { RequiredNormalbaeImageProcessorInvocation } from 'features/controlAdapters/store/types';
import { memo, useCallback } from 'react';
import { useTranslation } from 'react-i18next';
import ProcessorWrapper from './common/ProcessorWrapper';
type Props = {
controlNetId: string;
processorNode: RequiredNormalbaeImageProcessorInvocation;
isEnabled: boolean;
};
const NormalBaeProcessor = (props: Props) => {
const { controlNetId, processorNode, isEnabled } = props;
const { image_resolution, detect_resolution } = processorNode;
const processorChanged = useProcessorNodeChanged();
const { t } = useTranslation();
const defaults = useGetDefaultForControlnetProcessor(
'normalbae_image_processor'
) as RequiredNormalbaeImageProcessorInvocation;
const handleDetectResolutionChanged = useCallback(
(v: number) => {
processorChanged(controlNetId, { detect_resolution: v });
},
[controlNetId, processorChanged]
);
const handleImageResolutionChanged = useCallback(
(v: number) => {
processorChanged(controlNetId, { image_resolution: v });
},
[controlNetId, processorChanged]
);
return (
<ProcessorWrapper>
<FormControl isDisabled={!isEnabled}>
<FormLabel>{t('controlnet.detectResolution')}</FormLabel>
<CompositeSlider
value={detect_resolution}
onChange={handleDetectResolutionChanged}
defaultValue={defaults.detect_resolution}
min={0}
max={4096}
marks
/>
<CompositeNumberInput
value={detect_resolution}
onChange={handleDetectResolutionChanged}
defaultValue={defaults.detect_resolution}
min={0}
max={4096}
/>
</FormControl>
<FormControl isDisabled={!isEnabled}>
<FormLabel>{t('controlnet.imageResolution')}</FormLabel>
<CompositeSlider
value={image_resolution}
onChange={handleImageResolutionChanged}
defaultValue={defaults.image_resolution}
min={0}
max={4096}
marks
/>
<CompositeNumberInput
value={image_resolution}
onChange={handleImageResolutionChanged}
defaultValue={defaults.image_resolution}
min={0}
max={4096}
/>
</FormControl>
</ProcessorWrapper>
);
};
export default memo(NormalBaeProcessor);

View File

@ -1,103 +0,0 @@
import { CompositeNumberInput, CompositeSlider, FormControl, FormLabel, Switch } from '@invoke-ai/ui-library';
import { useProcessorNodeChanged } from 'features/controlAdapters/components/hooks/useProcessorNodeChanged';
import { useGetDefaultForControlnetProcessor } from 'features/controlAdapters/hooks/useGetDefaultForControlnetProcessor';
import type { RequiredPidiImageProcessorInvocation } from 'features/controlAdapters/store/types';
import type { ChangeEvent } from 'react';
import { memo, useCallback } from 'react';
import { useTranslation } from 'react-i18next';
import ProcessorWrapper from './common/ProcessorWrapper';
type Props = {
controlNetId: string;
processorNode: RequiredPidiImageProcessorInvocation;
isEnabled: boolean;
};
const PidiProcessor = (props: Props) => {
const { controlNetId, processorNode, isEnabled } = props;
const { image_resolution, detect_resolution, scribble, safe } = processorNode;
const processorChanged = useProcessorNodeChanged();
const { t } = useTranslation();
const defaults = useGetDefaultForControlnetProcessor('pidi_image_processor') as RequiredPidiImageProcessorInvocation;
const handleDetectResolutionChanged = useCallback(
(v: number) => {
processorChanged(controlNetId, { detect_resolution: v });
},
[controlNetId, processorChanged]
);
const handleImageResolutionChanged = useCallback(
(v: number) => {
processorChanged(controlNetId, { image_resolution: v });
},
[controlNetId, processorChanged]
);
const handleScribbleChanged = useCallback(
(e: ChangeEvent<HTMLInputElement>) => {
processorChanged(controlNetId, { scribble: e.target.checked });
},
[controlNetId, processorChanged]
);
const handleSafeChanged = useCallback(
(e: ChangeEvent<HTMLInputElement>) => {
processorChanged(controlNetId, { safe: e.target.checked });
},
[controlNetId, processorChanged]
);
return (
<ProcessorWrapper>
<FormControl isDisabled={!isEnabled}>
<FormLabel>{t('controlnet.detectResolution')}</FormLabel>
<CompositeSlider
value={detect_resolution}
onChange={handleDetectResolutionChanged}
defaultValue={defaults.detect_resolution}
min={0}
max={4096}
marks
/>
<CompositeNumberInput
value={detect_resolution}
onChange={handleDetectResolutionChanged}
defaultValue={defaults.detect_resolution}
min={0}
max={4096}
/>
</FormControl>
<FormControl isDisabled={!isEnabled}>
<FormLabel>{t('controlnet.imageResolution')}</FormLabel>
<CompositeSlider
value={image_resolution}
onChange={handleImageResolutionChanged}
defaultValue={defaults.image_resolution}
min={0}
max={4096}
marks
/>
<CompositeNumberInput
value={image_resolution}
onChange={handleImageResolutionChanged}
defaultValue={defaults.image_resolution}
min={0}
max={4096}
/>
</FormControl>
<FormControl isDisabled={!isEnabled}>
<FormLabel>{t('controlnet.scribble')}</FormLabel>
<Switch isChecked={scribble} onChange={handleScribbleChanged} />
</FormControl>
<FormControl isDisabled={!isEnabled}>
<FormLabel>{t('controlnet.safe')}</FormLabel>
<Switch isChecked={safe} onChange={handleSafeChanged} />
</FormControl>
</ProcessorWrapper>
);
};
export default memo(PidiProcessor);

View File

@ -1,15 +0,0 @@
import type { RequiredZoeDepthImageProcessorInvocation } from 'features/controlAdapters/store/types';
import { memo } from 'react';
type Props = {
controlNetId: string;
processorNode: RequiredZoeDepthImageProcessorInvocation;
isEnabled: boolean;
};
const ZoeDepthProcessor = (_props: Props) => {
// Has no parameters?
return null;
};
export default memo(ZoeDepthProcessor);

View File

@ -1,15 +0,0 @@
import { Flex } from '@invoke-ai/ui-library';
import type { PropsWithChildren } from 'react';
import { memo } from 'react';
type Props = PropsWithChildren;
const ProcessorWrapper = (props: Props) => {
return (
<Flex flexDir="column" gap={4}>
{props.children}
</Flex>
);
};
export default memo(ProcessorWrapper);

View File

@ -1,61 +0,0 @@
import { useAppDispatch, useAppSelector } from 'app/store/storeHooks';
import { useControlAdapterModels } from 'features/controlAdapters/hooks/useControlAdapterModels';
import { CONTROLNET_PROCESSORS } from 'features/controlAdapters/store/constants';
import { controlAdapterAdded } from 'features/controlAdapters/store/controlAdaptersSlice';
import { type ControlAdapterType, isControlAdapterProcessorType } from 'features/controlAdapters/store/types';
import { useCallback, useMemo } from 'react';
import type { ControlNetModelConfig, IPAdapterModelConfig, T2IAdapterModelConfig } from 'services/api/types';
export const useAddControlAdapter = (type: ControlAdapterType) => {
const baseModel = useAppSelector((s) => s.generation.model?.base);
const dispatch = useAppDispatch();
const [models] = useControlAdapterModels(type);
const firstModel: ControlNetModelConfig | T2IAdapterModelConfig | IPAdapterModelConfig | undefined = useMemo(() => {
// prefer to use a model that matches the base model
const firstCompatibleModel = models.filter((m) => (baseModel ? m.base === baseModel : true))[0];
if (firstCompatibleModel) {
return firstCompatibleModel;
}
return models[0];
}, [baseModel, models]);
const isDisabled = useMemo(() => !firstModel, [firstModel]);
const addControlAdapter = useCallback(() => {
if (isDisabled) {
return;
}
if (
(type === 'controlnet' || type === 't2i_adapter') &&
(firstModel?.type === 'controlnet' || firstModel?.type === 't2i_adapter')
) {
const defaultPreprocessor = firstModel.default_settings?.preprocessor;
const processorType = isControlAdapterProcessorType(defaultPreprocessor) ? defaultPreprocessor : 'none';
const processorNode = CONTROLNET_PROCESSORS[processorType].buildDefaults(baseModel);
dispatch(
controlAdapterAdded({
type,
overrides: {
model: firstModel,
processorType,
processorNode,
},
})
);
return;
}
dispatch(
controlAdapterAdded({
type,
overrides: { model: firstModel },
})
);
}, [dispatch, firstModel, isDisabled, type, baseModel]);
return [addControlAdapter, isDisabled] as const;
};

View File

@ -1,27 +0,0 @@
import { createMemoizedSelector } from 'app/store/createMemoizedSelector';
import { useAppSelector } from 'app/store/storeHooks';
import {
selectControlAdapterById,
selectControlAdaptersSlice,
} from 'features/controlAdapters/store/controlAdaptersSlice';
import { useMemo } from 'react';
export const useControlAdapterBeginEndStepPct = (id: string) => {
const selector = useMemo(
() =>
createMemoizedSelector(selectControlAdaptersSlice, (controlAdapters) => {
const cn = selectControlAdapterById(controlAdapters, id);
return cn
? {
beginStepPct: cn.beginStepPct,
endStepPct: cn.endStepPct,
}
: undefined;
}),
[id]
);
const stepPcts = useAppSelector(selector);
return stepPcts;
};

View File

@ -1,24 +0,0 @@
import { createMemoizedSelector } from 'app/store/createMemoizedSelector';
import { useAppSelector } from 'app/store/storeHooks';
import {
selectControlAdapterById,
selectControlAdaptersSlice,
} from 'features/controlAdapters/store/controlAdaptersSlice';
import { useMemo } from 'react';
export const useControlAdapterCLIPVisionModel = (id: string) => {
const selector = useMemo(
() =>
createMemoizedSelector(selectControlAdaptersSlice, (controlAdapters) => {
const cn = selectControlAdapterById(controlAdapters, id);
if (cn && cn?.type === 'ip_adapter') {
return cn.clipVisionModel;
}
}),
[id]
);
const clipVisionModel = useAppSelector(selector);
return clipVisionModel;
};

View File

@ -1,22 +0,0 @@
import { createSelector } from '@reduxjs/toolkit';
import { useAppSelector } from 'app/store/storeHooks';
import {
selectControlAdapterById,
selectControlAdaptersSlice,
} from 'features/controlAdapters/store/controlAdaptersSlice';
import { useMemo } from 'react';
export const useControlAdapterControlImage = (id: string) => {
const selector = useMemo(
() =>
createSelector(
selectControlAdaptersSlice,
(controlAdapters) => selectControlAdapterById(controlAdapters, id)?.controlImage
),
[id]
);
const controlImageName = useAppSelector(selector);
return controlImageName;
};

View File

@ -1,26 +0,0 @@
import { createSelector } from '@reduxjs/toolkit';
import { useAppSelector } from 'app/store/storeHooks';
import {
selectControlAdapterById,
selectControlAdaptersSlice,
} from 'features/controlAdapters/store/controlAdaptersSlice';
import { isControlNet } from 'features/controlAdapters/store/types';
import { useMemo } from 'react';
export const useControlAdapterControlMode = (id: string) => {
const selector = useMemo(
() =>
createSelector(selectControlAdaptersSlice, (controlAdapters) => {
const ca = selectControlAdapterById(controlAdapters, id);
if (ca && isControlNet(ca)) {
return ca.controlMode;
}
return undefined;
}),
[id]
);
const controlMode = useAppSelector(selector);
return controlMode;
};

View File

@ -1,24 +0,0 @@
import { createMemoizedSelector } from 'app/store/createMemoizedSelector';
import { useAppSelector } from 'app/store/storeHooks';
import {
selectControlAdapterById,
selectControlAdaptersSlice,
} from 'features/controlAdapters/store/controlAdaptersSlice';
import { useMemo } from 'react';
export const useControlAdapterIPMethod = (id: string) => {
const selector = useMemo(
() =>
createMemoizedSelector(selectControlAdaptersSlice, (controlAdapters) => {
const cn = selectControlAdapterById(controlAdapters, id);
if (cn && cn?.type === 'ip_adapter') {
return cn.method;
}
}),
[id]
);
const method = useAppSelector(selector);
return method;
};

View File

@ -1,22 +0,0 @@
import { createSelector } from '@reduxjs/toolkit';
import { useAppSelector } from 'app/store/storeHooks';
import {
selectControlAdapterById,
selectControlAdaptersSlice,
} from 'features/controlAdapters/store/controlAdaptersSlice';
import { useMemo } from 'react';
export const useControlAdapterIsEnabled = (id: string) => {
const selector = useMemo(
() =>
createSelector(
selectControlAdaptersSlice,
(controlAdapters) => selectControlAdapterById(controlAdapters, id)?.isEnabled ?? false
),
[id]
);
const isEnabled = useAppSelector(selector);
return isEnabled;
};

View File

@ -1,27 +0,0 @@
import { skipToken } from '@reduxjs/toolkit/query';
import { createMemoizedSelector } from 'app/store/createMemoizedSelector';
import { useAppSelector } from 'app/store/storeHooks';
import {
selectControlAdapterById,
selectControlAdaptersSlice,
} from 'features/controlAdapters/store/controlAdaptersSlice';
import { useMemo } from 'react';
import { useGetModelConfigWithTypeGuard } from 'services/api/hooks/useGetModelConfigWithTypeGuard';
import { isControlAdapterModelConfig } from 'services/api/types';
export const useControlAdapterModel = (id: string) => {
const selector = useMemo(
() =>
createMemoizedSelector(
selectControlAdaptersSlice,
(controlAdapters) => selectControlAdapterById(controlAdapters, id)?.model?.key
),
[id]
);
const key = useAppSelector(selector);
const result = useGetModelConfigWithTypeGuard(key ?? skipToken, isControlAdapterModelConfig);
return result;
};

View File

@ -1,22 +0,0 @@
import type { ControlAdapterType } from 'features/controlAdapters/store/types';
import { useControlNetModels, useIPAdapterModels, useT2IAdapterModels } from 'services/api/hooks/modelsByType';
export const useControlAdapterModels = (type: ControlAdapterType) => {
const controlNetModels = useControlNetModels();
const t2iAdapterModels = useT2IAdapterModels();
const ipAdapterModels = useIPAdapterModels();
if (type === 'controlnet') {
return controlNetModels;
}
if (type === 't2i_adapter') {
return t2iAdapterModels;
}
if (type === 'ip_adapter') {
return ipAdapterModels;
}
// Assert that the end of the function is not reachable.
const exhaustiveCheck: never = type;
return exhaustiveCheck;
};

View File

@ -1,24 +0,0 @@
import { createSelector } from '@reduxjs/toolkit';
import { useAppSelector } from 'app/store/storeHooks';
import {
selectControlAdapterById,
selectControlAdaptersSlice,
} from 'features/controlAdapters/store/controlAdaptersSlice';
import { isControlNetOrT2IAdapter } from 'features/controlAdapters/store/types';
import { useMemo } from 'react';
export const useControlAdapterProcessedControlImage = (id: string) => {
const selector = useMemo(
() =>
createSelector(selectControlAdaptersSlice, (controlAdapters) => {
const ca = selectControlAdapterById(controlAdapters, id);
return ca && isControlNetOrT2IAdapter(ca) ? ca.processedControlImage : undefined;
}),
[id]
);
const weight = useAppSelector(selector);
return weight;
};

View File

@ -1,24 +0,0 @@
import { createMemoizedSelector } from 'app/store/createMemoizedSelector';
import { useAppSelector } from 'app/store/storeHooks';
import {
selectControlAdapterById,
selectControlAdaptersSlice,
} from 'features/controlAdapters/store/controlAdaptersSlice';
import { isControlNetOrT2IAdapter } from 'features/controlAdapters/store/types';
import { useMemo } from 'react';
export const useControlAdapterProcessorNode = (id: string) => {
const selector = useMemo(
() =>
createMemoizedSelector(selectControlAdaptersSlice, (controlAdapters) => {
const ca = selectControlAdapterById(controlAdapters, id);
return ca && isControlNetOrT2IAdapter(ca) ? ca.processorNode : undefined;
}),
[id]
);
const processorNode = useAppSelector(selector);
return processorNode;
};

View File

@ -1,24 +0,0 @@
import { createSelector } from '@reduxjs/toolkit';
import { useAppSelector } from 'app/store/storeHooks';
import {
selectControlAdapterById,
selectControlAdaptersSlice,
} from 'features/controlAdapters/store/controlAdaptersSlice';
import { isControlNetOrT2IAdapter } from 'features/controlAdapters/store/types';
import { useMemo } from 'react';
export const useControlAdapterProcessorType = (id: string) => {
const selector = useMemo(
() =>
createSelector(selectControlAdaptersSlice, (controlAdapters) => {
const ca = selectControlAdapterById(controlAdapters, id);
return ca && isControlNetOrT2IAdapter(ca) ? ca.processorType : undefined;
}),
[id]
);
const processorType = useAppSelector(selector);
return processorType;
};

View File

@ -1,26 +0,0 @@
import { createMemoizedSelector } from 'app/store/createMemoizedSelector';
import { useAppSelector } from 'app/store/storeHooks';
import {
selectControlAdapterById,
selectControlAdaptersSlice,
} from 'features/controlAdapters/store/controlAdaptersSlice';
import { isControlNetOrT2IAdapter } from 'features/controlAdapters/store/types';
import { useMemo } from 'react';
export const useControlAdapterResizeMode = (id: string) => {
const selector = useMemo(
() =>
createMemoizedSelector(selectControlAdaptersSlice, (controlAdapters) => {
const ca = selectControlAdapterById(controlAdapters, id);
if (ca && isControlNetOrT2IAdapter(ca)) {
return ca.resizeMode;
}
return undefined;
}),
[id]
);
const controlMode = useAppSelector(selector);
return controlMode;
};

View File

@ -1,26 +0,0 @@
import { createSelector } from '@reduxjs/toolkit';
import { useAppSelector } from 'app/store/storeHooks';
import {
selectControlAdapterById,
selectControlAdaptersSlice,
} from 'features/controlAdapters/store/controlAdaptersSlice';
import { isControlNetOrT2IAdapter } from 'features/controlAdapters/store/types';
import { useMemo } from 'react';
export const useControlAdapterShouldAutoConfig = (id: string) => {
const selector = useMemo(
() =>
createSelector(selectControlAdaptersSlice, (controlAdapters) => {
const ca = selectControlAdapterById(controlAdapters, id);
if (ca && isControlNetOrT2IAdapter(ca)) {
return ca.shouldAutoConfig;
}
return undefined;
}),
[id]
);
const controlMode = useAppSelector(selector);
return controlMode;
};

View File

@ -1,24 +0,0 @@
import { createMemoizedSelector } from 'app/store/createMemoizedSelector';
import { useAppSelector } from 'app/store/storeHooks';
import {
selectControlAdapterById,
selectControlAdaptersSlice,
} from 'features/controlAdapters/store/controlAdaptersSlice';
import { useMemo } from 'react';
import { assert } from 'tsafe';
export const useControlAdapterType = (id: string) => {
const selector = useMemo(
() =>
createMemoizedSelector(selectControlAdaptersSlice, (controlAdapters) => {
const type = selectControlAdapterById(controlAdapters, id)?.type;
assert(type !== undefined, `Control adapter with id ${id} not found`);
return type;
}),
[id]
);
const type = useAppSelector(selector);
return type;
};

View File

@ -1,22 +0,0 @@
import { createSelector } from '@reduxjs/toolkit';
import { useAppSelector } from 'app/store/storeHooks';
import {
selectControlAdapterById,
selectControlAdaptersSlice,
} from 'features/controlAdapters/store/controlAdaptersSlice';
import { useMemo } from 'react';
export const useControlAdapterWeight = (id: string) => {
const selector = useMemo(
() =>
createSelector(
selectControlAdaptersSlice,
(controlAdapters) => selectControlAdapterById(controlAdapters, id)?.weight
),
[id]
);
const weight = useAppSelector(selector);
return weight;
};

View File

@ -1,14 +0,0 @@
import { useAppSelector } from 'app/store/storeHooks';
import { CONTROLNET_PROCESSORS } from 'features/controlAdapters/store/constants';
import type { ControlAdapterProcessorType } from 'features/controlAdapters/store/types';
import { useMemo } from 'react';
export const useGetDefaultForControlnetProcessor = (processorType: ControlAdapterProcessorType) => {
const baseModel = useAppSelector((s) => s.generation.model?.base);
const defaults = useMemo(() => {
return CONTROLNET_PROCESSORS[processorType].buildDefaults(baseModel);
}, [baseModel, processorType]);
return defaults;
};

View File

@ -1,5 +0,0 @@
import { createAction } from '@reduxjs/toolkit';
export const controlAdapterImageProcessed = createAction<{
id: string;
}>('controlAdapters/imageProcessed');

View File

@ -1,261 +0,0 @@
import i18n from 'i18next';
import type { BaseModelType } from 'services/api/types';
import type { ControlAdapterProcessorType, RequiredControlAdapterProcessorNode } from './types';
type ControlNetProcessorsDict = Record<
ControlAdapterProcessorType,
{
type: ControlAdapterProcessorType | 'none';
label: string;
description: string;
buildDefaults(baseModel?: BaseModelType): RequiredControlAdapterProcessorNode | { type: 'none' };
}
>;
/**
* A dict of ControlNet processors, including:
* - type
* - label
* - description
* - default values
*
* TODO: Generate from the OpenAPI schema
*/
export const CONTROLNET_PROCESSORS: ControlNetProcessorsDict = {
none: {
type: 'none',
get label() {
return i18n.t('controlnet.none');
},
get description() {
return i18n.t('controlnet.noneDescription');
},
buildDefaults: () => ({
type: 'none',
}),
},
canny_image_processor: {
type: 'canny_image_processor',
get label() {
return i18n.t('controlnet.canny');
},
get description() {
return i18n.t('controlnet.cannyDescription');
},
buildDefaults: (baseModel?: BaseModelType) => ({
id: 'canny_image_processor',
type: 'canny_image_processor',
low_threshold: 100,
high_threshold: 200,
image_resolution: baseModel === 'sdxl' ? 1024 : 512,
detect_resolution: baseModel === 'sdxl' ? 1024 : 512,
}),
},
color_map_image_processor: {
type: 'color_map_image_processor',
get label() {
return i18n.t('controlnet.colorMap');
},
get description() {
return i18n.t('controlnet.colorMapDescription');
},
buildDefaults: () => ({
id: 'color_map_image_processor',
type: 'color_map_image_processor',
color_map_tile_size: 64,
}),
},
content_shuffle_image_processor: {
type: 'content_shuffle_image_processor',
get label() {
return i18n.t('controlnet.contentShuffle');
},
get description() {
return i18n.t('controlnet.contentShuffleDescription');
},
buildDefaults: (baseModel?: BaseModelType) => ({
id: 'content_shuffle_image_processor',
type: 'content_shuffle_image_processor',
detect_resolution: baseModel === 'sdxl' ? 1024 : 512,
image_resolution: baseModel === 'sdxl' ? 1024 : 512,
h: baseModel === 'sdxl' ? 1024 : 512,
w: baseModel === 'sdxl' ? 1024 : 512,
f: baseModel === 'sdxl' ? 512 : 256,
}),
},
depth_anything_image_processor: {
type: 'depth_anything_image_processor',
get label() {
return i18n.t('controlnet.depthAnything');
},
get description() {
return i18n.t('controlnet.depthAnythingDescription');
},
buildDefaults: (baseModel?: BaseModelType) => ({
id: 'depth_anything_image_processor',
type: 'depth_anything_image_processor',
model_size: 'small_v2',
resolution: baseModel === 'sdxl' ? 1024 : 512,
}),
},
hed_image_processor: {
type: 'hed_image_processor',
get label() {
return i18n.t('controlnet.hed');
},
get description() {
return i18n.t('controlnet.hedDescription');
},
buildDefaults: (baseModel?: BaseModelType) => ({
id: 'hed_image_processor',
type: 'hed_image_processor',
detect_resolution: baseModel === 'sdxl' ? 1024 : 512,
image_resolution: baseModel === 'sdxl' ? 1024 : 512,
scribble: false,
}),
},
lineart_anime_image_processor: {
type: 'lineart_anime_image_processor',
get label() {
return i18n.t('controlnet.lineartAnime');
},
get description() {
return i18n.t('controlnet.lineartAnimeDescription');
},
buildDefaults: (baseModel?: BaseModelType) => ({
id: 'lineart_anime_image_processor',
type: 'lineart_anime_image_processor',
detect_resolution: baseModel === 'sdxl' ? 1024 : 512,
image_resolution: baseModel === 'sdxl' ? 1024 : 512,
}),
},
lineart_image_processor: {
type: 'lineart_image_processor',
get label() {
return i18n.t('controlnet.lineart');
},
get description() {
return i18n.t('controlnet.lineartDescription');
},
buildDefaults: (baseModel?: BaseModelType) => ({
id: 'lineart_image_processor',
type: 'lineart_image_processor',
detect_resolution: baseModel === 'sdxl' ? 1024 : 512,
image_resolution: baseModel === 'sdxl' ? 1024 : 512,
coarse: false,
}),
},
mediapipe_face_processor: {
type: 'mediapipe_face_processor',
get label() {
return i18n.t('controlnet.mediapipeFace');
},
get description() {
return i18n.t('controlnet.mediapipeFaceDescription');
},
buildDefaults: (baseModel?: BaseModelType) => ({
id: 'mediapipe_face_processor',
type: 'mediapipe_face_processor',
max_faces: 1,
min_confidence: 0.5,
image_resolution: baseModel === 'sdxl' ? 1024 : 512,
detect_resolution: baseModel === 'sdxl' ? 1024 : 512,
}),
},
midas_depth_image_processor: {
type: 'midas_depth_image_processor',
get label() {
return i18n.t('controlnet.depthMidas');
},
get description() {
return i18n.t('controlnet.depthMidasDescription');
},
buildDefaults: (baseModel?: BaseModelType) => ({
id: 'midas_depth_image_processor',
type: 'midas_depth_image_processor',
a_mult: 2,
bg_th: 0.1,
image_resolution: baseModel === 'sdxl' ? 1024 : 512,
detect_resolution: baseModel === 'sdxl' ? 1024 : 512,
}),
},
mlsd_image_processor: {
type: 'mlsd_image_processor',
get label() {
return i18n.t('controlnet.mlsd');
},
get description() {
return i18n.t('controlnet.mlsdDescription');
},
buildDefaults: (baseModel?: BaseModelType) => ({
id: 'mlsd_image_processor',
type: 'mlsd_image_processor',
detect_resolution: baseModel === 'sdxl' ? 1024 : 512,
image_resolution: baseModel === 'sdxl' ? 1024 : 512,
thr_d: 0.1,
thr_v: 0.1,
}),
},
normalbae_image_processor: {
type: 'normalbae_image_processor',
get label() {
return i18n.t('controlnet.normalBae');
},
get description() {
return i18n.t('controlnet.normalBaeDescription');
},
buildDefaults: (baseModel?: BaseModelType) => ({
id: 'normalbae_image_processor',
type: 'normalbae_image_processor',
detect_resolution: baseModel === 'sdxl' ? 1024 : 512,
image_resolution: baseModel === 'sdxl' ? 1024 : 512,
}),
},
dw_openpose_image_processor: {
type: 'dw_openpose_image_processor',
get label() {
return i18n.t('controlnet.dwOpenpose');
},
get description() {
return i18n.t('controlnet.dwOpenposeDescription');
},
buildDefaults: (baseModel?: BaseModelType) => ({
id: 'dw_openpose_image_processor',
type: 'dw_openpose_image_processor',
image_resolution: baseModel === 'sdxl' ? 1024 : 512,
draw_body: true,
draw_face: false,
draw_hands: false,
}),
},
pidi_image_processor: {
type: 'pidi_image_processor',
get label() {
return i18n.t('controlnet.pidi');
},
get description() {
return i18n.t('controlnet.pidiDescription');
},
buildDefaults: (baseModel?: BaseModelType) => ({
id: 'pidi_image_processor',
type: 'pidi_image_processor',
detect_resolution: baseModel === 'sdxl' ? 1024 : 512,
image_resolution: baseModel === 'sdxl' ? 1024 : 512,
scribble: false,
safe: false,
}),
},
zoe_depth_image_processor: {
type: 'zoe_depth_image_processor',
get label() {
return i18n.t('controlnet.depthZoe');
},
get description() {
return i18n.t('controlnet.depthZoeDescription');
},
buildDefaults: () => ({
id: 'zoe_depth_image_processor',
type: 'zoe_depth_image_processor',
}),
},
};

View File

@ -1,433 +0,0 @@
import type { PayloadAction, Update } from '@reduxjs/toolkit';
import { createEntityAdapter, createSlice } from '@reduxjs/toolkit';
import { getSelectorsOptions } from 'app/store/createMemoizedSelector';
import type { PersistConfig, RootState } from 'app/store/store';
import { deepClone } from 'common/util/deepClone';
import { buildControlAdapter } from 'features/controlAdapters/util/buildControlAdapter';
import { buildControlAdapterProcessor } from 'features/controlAdapters/util/buildControlAdapterProcessor';
import { zModelIdentifierField } from 'features/nodes/types/common';
import { merge, uniq } from 'lodash-es';
import type { ControlNetModelConfig, IPAdapterModelConfig, T2IAdapterModelConfig } from 'services/api/types';
import { socketInvocationError } from 'services/events/actions';
import { v4 as uuidv4 } from 'uuid';
import { controlAdapterImageProcessed } from './actions';
import { CONTROLNET_PROCESSORS } from './constants';
import type {
CLIPVisionModel,
ControlAdapterConfig,
ControlAdapterProcessorType,
ControlAdaptersState,
ControlAdapterType,
ControlMode,
ControlNetConfig,
IPMethod,
RequiredControlAdapterProcessorNode,
ResizeMode,
T2IAdapterConfig,
} from './types';
import { isControlNet, isControlNetOrT2IAdapter, isIPAdapter, isT2IAdapter } from './types';
const caAdapter = createEntityAdapter<ControlAdapterConfig, string>({
selectId: (ca) => ca.id,
});
const caAdapterSelectors = caAdapter.getSelectors(undefined, getSelectorsOptions);
export const {
selectById: selectControlAdapterById,
selectAll: selectControlAdapterAll,
selectIds: selectControlAdapterIds,
} = caAdapterSelectors;
const initialControlAdaptersState: ControlAdaptersState = caAdapter.getInitialState<{
_version: 2;
pendingControlImages: string[];
}>({
_version: 2,
pendingControlImages: [],
});
export const selectAllControlNets = (controlAdapters: ControlAdaptersState) =>
selectControlAdapterAll(controlAdapters).filter(isControlNet);
export const selectValidControlNets = (controlAdapters: ControlAdaptersState) =>
selectControlAdapterAll(controlAdapters)
.filter(isControlNet)
.filter(
(ca) =>
ca.isEnabled &&
ca.model &&
(Boolean(ca.processedControlImage) || (ca.processorType === 'none' && Boolean(ca.controlImage)))
);
export const selectAllIPAdapters = (controlAdapters: ControlAdaptersState) =>
selectControlAdapterAll(controlAdapters).filter(isIPAdapter);
export const selectValidIPAdapters = (controlAdapters: ControlAdaptersState) =>
selectControlAdapterAll(controlAdapters)
.filter(isIPAdapter)
.filter((ca) => ca.isEnabled && ca.model && Boolean(ca.controlImage));
export const selectAllT2IAdapters = (controlAdapters: ControlAdaptersState) =>
selectControlAdapterAll(controlAdapters).filter(isT2IAdapter);
export const selectValidT2IAdapters = (controlAdapters: ControlAdaptersState) =>
selectControlAdapterAll(controlAdapters)
.filter(isT2IAdapter)
.filter(
(ca) =>
ca.isEnabled &&
ca.model &&
(Boolean(ca.processedControlImage) || (ca.processorType === 'none' && Boolean(ca.controlImage)))
);
export const controlAdaptersSlice = createSlice({
name: 'controlAdapters',
initialState: initialControlAdaptersState,
reducers: {
controlAdapterAdded: {
reducer: (
state,
action: PayloadAction<{
id: string;
type: ControlAdapterType;
overrides?: Partial<ControlAdapterConfig>;
}>
) => {
const { id, type, overrides } = action.payload;
caAdapter.addOne(state, buildControlAdapter(id, type, overrides));
},
prepare: ({ type, overrides }: { type: ControlAdapterType; overrides?: Partial<ControlAdapterConfig> }) => {
return { payload: { id: uuidv4(), type, overrides } };
},
},
controlAdapterRecalled: (state, action: PayloadAction<ControlAdapterConfig>) => {
caAdapter.addOne(state, action.payload);
},
controlAdapterDuplicated: {
reducer: (
state,
action: PayloadAction<{
id: string;
newId: string;
}>
) => {
const { id, newId } = action.payload;
const controlAdapter = selectControlAdapterById(state, id);
if (!controlAdapter) {
return;
}
const newControlAdapter = merge(deepClone(controlAdapter), {
id: newId,
isEnabled: true,
});
caAdapter.addOne(state, newControlAdapter);
},
prepare: (id: string) => {
return { payload: { id, newId: uuidv4() } };
},
},
controlAdapterRemoved: (state, action: PayloadAction<{ id: string }>) => {
caAdapter.removeOne(state, action.payload.id);
},
controlAdapterIsEnabledChanged: (state, action: PayloadAction<{ id: string; isEnabled: boolean }>) => {
const { id, isEnabled } = action.payload;
caAdapter.updateOne(state, { id, changes: { isEnabled } });
},
controlAdapterImageChanged: (
state,
action: PayloadAction<{
id: string;
controlImage: string | null;
}>
) => {
const { id, controlImage } = action.payload;
const ca = selectControlAdapterById(state, id);
if (!ca) {
return;
}
caAdapter.updateOne(state, {
id,
changes: { controlImage, processedControlImage: null },
});
if (controlImage !== null && isControlNetOrT2IAdapter(ca) && ca.processorType !== 'none') {
state.pendingControlImages.push(id);
}
},
controlAdapterProcessedImageChanged: (
state,
action: PayloadAction<{
id: string;
processedControlImage: string | null;
}>
) => {
const { id, processedControlImage } = action.payload;
const cn = selectControlAdapterById(state, id);
if (!cn) {
return;
}
if (!isControlNetOrT2IAdapter(cn)) {
return;
}
caAdapter.updateOne(state, {
id,
changes: {
processedControlImage,
},
});
state.pendingControlImages = state.pendingControlImages.filter((pendingId) => pendingId !== id);
},
controlAdapterModelCleared: (state, action: PayloadAction<{ id: string }>) => {
caAdapter.updateOne(state, {
id: action.payload.id,
changes: { model: null },
});
},
controlAdapterModelChanged: (
state,
action: PayloadAction<{
id: string;
modelConfig: ControlNetModelConfig | T2IAdapterModelConfig | IPAdapterModelConfig;
}>
) => {
const { id, modelConfig } = action.payload;
const cn = selectControlAdapterById(state, id);
if (!cn) {
return;
}
const model = zModelIdentifierField.parse(modelConfig);
if (!isControlNetOrT2IAdapter(cn)) {
caAdapter.updateOne(state, { id, changes: { model } });
return;
}
const update: Update<ControlNetConfig | T2IAdapterConfig, string> = {
id,
changes: { model, shouldAutoConfig: true },
};
update.changes.processedControlImage = null;
if (modelConfig.type === 'ip_adapter') {
// should never happen...
return;
}
const processor = buildControlAdapterProcessor(modelConfig);
update.changes.processorType = processor.processorType;
update.changes.processorNode = processor.processorNode;
caAdapter.updateOne(state, update);
},
controlAdapterWeightChanged: (state, action: PayloadAction<{ id: string; weight: number }>) => {
const { id, weight } = action.payload;
caAdapter.updateOne(state, { id, changes: { weight } });
},
controlAdapterBeginStepPctChanged: (state, action: PayloadAction<{ id: string; beginStepPct: number }>) => {
const { id, beginStepPct } = action.payload;
caAdapter.updateOne(state, { id, changes: { beginStepPct } });
},
controlAdapterEndStepPctChanged: (state, action: PayloadAction<{ id: string; endStepPct: number }>) => {
const { id, endStepPct } = action.payload;
caAdapter.updateOne(state, { id, changes: { endStepPct } });
},
controlAdapterControlModeChanged: (state, action: PayloadAction<{ id: string; controlMode: ControlMode }>) => {
const { id, controlMode } = action.payload;
const cn = selectControlAdapterById(state, id);
if (!cn || !isControlNet(cn)) {
return;
}
caAdapter.updateOne(state, { id, changes: { controlMode } });
},
controlAdapterIPMethodChanged: (state, action: PayloadAction<{ id: string; method: IPMethod }>) => {
const { id, method } = action.payload;
caAdapter.updateOne(state, { id, changes: { method } });
},
controlAdapterCLIPVisionModelChanged: (
state,
action: PayloadAction<{ id: string; clipVisionModel: CLIPVisionModel }>
) => {
const { id, clipVisionModel } = action.payload;
caAdapter.updateOne(state, { id, changes: { clipVisionModel } });
},
controlAdapterResizeModeChanged: (
state,
action: PayloadAction<{
id: string;
resizeMode: ResizeMode;
}>
) => {
const { id, resizeMode } = action.payload;
const cn = selectControlAdapterById(state, id);
if (!cn || !isControlNetOrT2IAdapter(cn)) {
return;
}
caAdapter.updateOne(state, { id, changes: { resizeMode } });
},
controlAdapterProcessorParamsChanged: (
state,
action: PayloadAction<{
id: string;
params: Partial<RequiredControlAdapterProcessorNode>;
}>
) => {
const { id, params } = action.payload;
const cn = selectControlAdapterById(state, id);
if (!cn || !isControlNetOrT2IAdapter(cn) || !cn.processorNode) {
return;
}
const processorNode = merge(deepClone(cn.processorNode), params);
caAdapter.updateOne(state, {
id,
changes: {
shouldAutoConfig: false,
processorNode,
},
});
},
controlAdapterProcessortTypeChanged: (
state,
action: PayloadAction<{
id: string;
processorType: ControlAdapterProcessorType;
}>
) => {
const { id, processorType } = action.payload;
const cn = selectControlAdapterById(state, id);
if (!cn || !isControlNetOrT2IAdapter(cn)) {
return;
}
const processorNode = deepClone(
CONTROLNET_PROCESSORS[processorType].buildDefaults(cn.model?.base)
) as RequiredControlAdapterProcessorNode;
caAdapter.updateOne(state, {
id,
changes: {
processorType,
processedControlImage: null,
processorNode,
shouldAutoConfig: false,
},
});
},
controlAdapterAutoConfigToggled: (
state,
action: PayloadAction<{
id: string;
modelConfig?: ControlNetModelConfig | T2IAdapterModelConfig | IPAdapterModelConfig;
}>
) => {
const { id, modelConfig } = action.payload;
const cn = selectControlAdapterById(state, id);
if (!cn || !isControlNetOrT2IAdapter(cn) || modelConfig?.type === 'ip_adapter') {
return;
}
const update: Update<ControlNetConfig | T2IAdapterConfig, string> = {
id,
changes: { shouldAutoConfig: !cn.shouldAutoConfig },
};
if (update.changes.shouldAutoConfig && modelConfig) {
const processor = buildControlAdapterProcessor(modelConfig);
update.changes.processorType = processor.processorType;
update.changes.processorNode = processor.processorNode;
}
caAdapter.updateOne(state, update);
},
controlAdaptersReset: () => {
return deepClone(initialControlAdaptersState);
},
pendingControlImagesCleared: (state) => {
state.pendingControlImages = [];
},
ipAdaptersReset: (state) => {
selectAllIPAdapters(state).forEach((ca) => {
caAdapter.removeOne(state, ca.id);
});
},
controlNetsReset: (state) => {
selectAllControlNets(state).forEach((ca) => {
caAdapter.removeOne(state, ca.id);
});
},
t2iAdaptersReset: (state) => {
selectAllT2IAdapters(state).forEach((ca) => {
caAdapter.removeOne(state, ca.id);
});
},
},
extraReducers: (builder) => {
builder.addCase(controlAdapterImageProcessed, (state, action) => {
const cn = selectControlAdapterById(state, action.payload.id);
if (!cn) {
return;
}
if (cn.controlImage !== null) {
state.pendingControlImages = uniq(state.pendingControlImages.concat(action.payload.id));
}
});
builder.addCase(socketInvocationError, (state) => {
state.pendingControlImages = [];
});
},
});
export const {
controlAdapterAdded,
controlAdapterRecalled,
controlAdapterDuplicated,
controlAdapterRemoved,
controlAdapterImageChanged,
controlAdapterProcessedImageChanged,
controlAdapterIsEnabledChanged,
controlAdapterModelChanged,
controlAdapterCLIPVisionModelChanged,
controlAdapterIPMethodChanged,
controlAdapterWeightChanged,
controlAdapterBeginStepPctChanged,
controlAdapterEndStepPctChanged,
controlAdapterControlModeChanged,
controlAdapterResizeModeChanged,
controlAdapterProcessorParamsChanged,
controlAdapterProcessortTypeChanged,
controlAdaptersReset,
controlAdapterAutoConfigToggled,
pendingControlImagesCleared,
controlAdapterModelCleared,
ipAdaptersReset,
controlNetsReset,
t2iAdaptersReset,
} = controlAdaptersSlice.actions;
export const selectControlAdaptersSlice = (state: RootState) => state.controlAdapters;
/* eslint-disable-next-line @typescript-eslint/no-explicit-any */
const migrateControlAdaptersState = (state: any): any => {
if (!('_version' in state)) {
state._version = 1;
}
if (state._version === 1) {
state = deepClone(initialControlAdaptersState);
}
return state;
};
export const controlAdaptersPersistConfig: PersistConfig<ControlAdaptersState> = {
name: controlAdaptersSlice.name,
initialState: initialControlAdaptersState,
migrate: migrateControlAdaptersState,
persistDenylist: ['pendingControlImages'],
};

View File

@ -1,10 +0,0 @@
import type { ControlAdapterProcessorType, zControlAdapterProcessorType } from 'features/controlAdapters/store/types';
import type { Equals } from 'tsafe';
import { assert } from 'tsafe';
import { describe, test } from 'vitest';
import type { z } from 'zod';
describe('Control Adapter Types', () => {
test('ControlAdapterProcessorType', () =>
assert<Equals<ControlAdapterProcessorType, z.infer<typeof zControlAdapterProcessorType>>>());
});

View File

@ -1,274 +0,0 @@
import type { EntityState } from '@reduxjs/toolkit';
import type {
ParameterControlNetModel,
ParameterIPAdapterModel,
ParameterT2IAdapterModel,
} from 'features/parameters/types/parameterSchemas';
import type { components } from 'services/api/schema';
import type { Invocation } from 'services/api/types';
import type { O } from 'ts-toolbelt';
import { z } from 'zod';
/**
* Any ControlNet processor node
*/
export type ControlAdapterProcessorNode =
| Invocation<'canny_image_processor'>
| Invocation<'color_map_image_processor'>
| Invocation<'content_shuffle_image_processor'>
| Invocation<'depth_anything_image_processor'>
| Invocation<'hed_image_processor'>
| Invocation<'lineart_anime_image_processor'>
| Invocation<'lineart_image_processor'>
| Invocation<'mediapipe_face_processor'>
| Invocation<'midas_depth_image_processor'>
| Invocation<'mlsd_image_processor'>
| Invocation<'normalbae_image_processor'>
| Invocation<'dw_openpose_image_processor'>
| Invocation<'pidi_image_processor'>
| Invocation<'zoe_depth_image_processor'>;
/**
* Any ControlNet processor type
*/
export type ControlAdapterProcessorType = NonNullable<ControlAdapterProcessorNode['type'] | 'none'>;
export const zControlAdapterProcessorType = z.enum([
'canny_image_processor',
'color_map_image_processor',
'content_shuffle_image_processor',
'depth_anything_image_processor',
'hed_image_processor',
'lineart_anime_image_processor',
'lineart_image_processor',
'mediapipe_face_processor',
'midas_depth_image_processor',
'mlsd_image_processor',
'normalbae_image_processor',
'dw_openpose_image_processor',
'pidi_image_processor',
'zoe_depth_image_processor',
'none',
]);
export const isControlAdapterProcessorType = (v: unknown): v is ControlAdapterProcessorType =>
zControlAdapterProcessorType.safeParse(v).success;
/**
* The Canny processor node, with parameters flagged as required
*/
export type RequiredCannyImageProcessorInvocation = O.Required<
Invocation<'canny_image_processor'>,
'type' | 'low_threshold' | 'high_threshold' | 'image_resolution' | 'detect_resolution'
>;
/**
* The Color Map processor node, with parameters flagged as required
*/
export type RequiredColorMapImageProcessorInvocation = O.Required<
Invocation<'color_map_image_processor'>,
'type' | 'color_map_tile_size'
>;
/**
* The ContentShuffle processor node, with parameters flagged as required
*/
export type RequiredContentShuffleImageProcessorInvocation = O.Required<
Invocation<'content_shuffle_image_processor'>,
'type' | 'detect_resolution' | 'image_resolution' | 'w' | 'h' | 'f'
>;
/**
* The DepthAnything processor node, with parameters flagged as required
*/
export type RequiredDepthAnythingImageProcessorInvocation = O.Required<
Invocation<'depth_anything_image_processor'>,
'type' | 'model_size' | 'resolution' | 'offload'
>;
const zDepthAnythingModelSize = z.enum(['large', 'base', 'small', 'small_v2']);
export type DepthAnythingModelSize = z.infer<typeof zDepthAnythingModelSize>;
export const isDepthAnythingModelSize = (v: unknown): v is DepthAnythingModelSize =>
zDepthAnythingModelSize.safeParse(v).success;
/**
* The HED processor node, with parameters flagged as required
*/
export type RequiredHedImageProcessorInvocation = O.Required<
Invocation<'hed_image_processor'>,
'type' | 'detect_resolution' | 'image_resolution' | 'scribble'
>;
/**
* The Lineart Anime processor node, with parameters flagged as required
*/
export type RequiredLineartAnimeImageProcessorInvocation = O.Required<
Invocation<'lineart_anime_image_processor'>,
'type' | 'detect_resolution' | 'image_resolution'
>;
/**
* The Lineart processor node, with parameters flagged as required
*/
export type RequiredLineartImageProcessorInvocation = O.Required<
Invocation<'lineart_image_processor'>,
'type' | 'detect_resolution' | 'image_resolution' | 'coarse'
>;
/**
* The MediapipeFace processor node, with parameters flagged as required
*/
export type RequiredMediapipeFaceProcessorInvocation = O.Required<
Invocation<'mediapipe_face_processor'>,
'type' | 'max_faces' | 'min_confidence' | 'image_resolution' | 'detect_resolution'
>;
/**
* The MidasDepth processor node, with parameters flagged as required
*/
export type RequiredMidasDepthImageProcessorInvocation = O.Required<
Invocation<'midas_depth_image_processor'>,
'type' | 'a_mult' | 'bg_th' | 'image_resolution' | 'detect_resolution'
>;
/**
* The MLSD processor node, with parameters flagged as required
*/
export type RequiredMlsdImageProcessorInvocation = O.Required<
Invocation<'mlsd_image_processor'>,
'type' | 'detect_resolution' | 'image_resolution' | 'thr_v' | 'thr_d'
>;
/**
* The NormalBae processor node, with parameters flagged as required
*/
export type RequiredNormalbaeImageProcessorInvocation = O.Required<
Invocation<'normalbae_image_processor'>,
'type' | 'detect_resolution' | 'image_resolution'
>;
/**
* The DW Openpose processor node, with parameters flagged as required
*/
export type RequiredDWOpenposeImageProcessorInvocation = O.Required<
Invocation<'dw_openpose_image_processor'>,
'type' | 'image_resolution' | 'draw_body' | 'draw_face' | 'draw_hands'
>;
/**
* The Pidi processor node, with parameters flagged as required
*/
export type RequiredPidiImageProcessorInvocation = O.Required<
Invocation<'pidi_image_processor'>,
'type' | 'detect_resolution' | 'image_resolution' | 'safe' | 'scribble'
>;
/**
* The ZoeDepth processor node, with parameters flagged as required
*/
export type RequiredZoeDepthImageProcessorInvocation = O.Required<Invocation<'zoe_depth_image_processor'>, 'type'>;
/**
* Any ControlNet Processor node, with its parameters flagged as required
*/
export type RequiredControlAdapterProcessorNode =
| O.Required<
| RequiredCannyImageProcessorInvocation
| RequiredColorMapImageProcessorInvocation
| RequiredContentShuffleImageProcessorInvocation
| RequiredDepthAnythingImageProcessorInvocation
| RequiredHedImageProcessorInvocation
| RequiredLineartAnimeImageProcessorInvocation
| RequiredLineartImageProcessorInvocation
| RequiredMediapipeFaceProcessorInvocation
| RequiredMidasDepthImageProcessorInvocation
| RequiredMlsdImageProcessorInvocation
| RequiredNormalbaeImageProcessorInvocation
| RequiredDWOpenposeImageProcessorInvocation
| RequiredPidiImageProcessorInvocation
| RequiredZoeDepthImageProcessorInvocation,
'id'
>
| { type: 'none' };
export type ControlMode = NonNullable<components['schemas']['ControlNetInvocation']['control_mode']>;
const zResizeMode = z.enum(['just_resize', 'crop_resize', 'fill_resize', 'just_resize_simple']);
export type ResizeMode = z.infer<typeof zResizeMode>;
export const isResizeMode = (v: unknown): v is ResizeMode => zResizeMode.safeParse(v).success;
const zIPMethod = z.enum(['full', 'style', 'composition']);
export type IPMethod = z.infer<typeof zIPMethod>;
export const isIPMethod = (v: unknown): v is IPMethod => zIPMethod.safeParse(v).success;
export type ControlNetConfig = {
type: 'controlnet';
id: string;
isEnabled: boolean;
model: ParameterControlNetModel | null;
weight: number;
beginStepPct: number;
endStepPct: number;
controlMode: ControlMode;
resizeMode: ResizeMode;
controlImage: string | null;
processedControlImage: string | null;
processorType: ControlAdapterProcessorType;
processorNode: RequiredControlAdapterProcessorNode;
shouldAutoConfig: boolean;
};
export type T2IAdapterConfig = {
type: 't2i_adapter';
id: string;
isEnabled: boolean;
model: ParameterT2IAdapterModel | null;
weight: number;
beginStepPct: number;
endStepPct: number;
resizeMode: ResizeMode;
controlImage: string | null;
processedControlImage: string | null;
processorType: ControlAdapterProcessorType;
processorNode: RequiredControlAdapterProcessorNode;
shouldAutoConfig: boolean;
};
export type CLIPVisionModel = 'ViT-H' | 'ViT-G';
export type IPAdapterConfig = {
type: 'ip_adapter';
id: string;
isEnabled: boolean;
controlImage: string | null;
model: ParameterIPAdapterModel | null;
clipVisionModel: CLIPVisionModel;
weight: number;
method: IPMethod;
beginStepPct: number;
endStepPct: number;
};
export type ControlAdapterConfig = ControlNetConfig | IPAdapterConfig | T2IAdapterConfig;
export type ControlAdapterType = ControlAdapterConfig['type'];
export type ControlAdaptersState = EntityState<ControlAdapterConfig, string> & {
pendingControlImages: string[];
};
export const isControlNet = (controlAdapter: ControlAdapterConfig): controlAdapter is ControlNetConfig => {
return controlAdapter.type === 'controlnet';
};
export const isIPAdapter = (controlAdapter: ControlAdapterConfig): controlAdapter is IPAdapterConfig => {
return controlAdapter.type === 'ip_adapter';
};
export const isT2IAdapter = (controlAdapter: ControlAdapterConfig): controlAdapter is T2IAdapterConfig => {
return controlAdapter.type === 't2i_adapter';
};
export const isControlNetOrT2IAdapter = (
controlAdapter: ControlAdapterConfig
): controlAdapter is ControlNetConfig | T2IAdapterConfig => {
return isControlNet(controlAdapter) || isT2IAdapter(controlAdapter);
};

View File

@ -1,71 +0,0 @@
import { deepClone } from 'common/util/deepClone';
import { CONTROLNET_PROCESSORS } from 'features/controlAdapters/store/constants';
import type {
ControlAdapterConfig,
ControlAdapterType,
ControlNetConfig,
IPAdapterConfig,
RequiredCannyImageProcessorInvocation,
T2IAdapterConfig,
} from 'features/controlAdapters/store/types';
import { merge } from 'lodash-es';
export const initialControlNet: Omit<ControlNetConfig, 'id'> = {
type: 'controlnet',
isEnabled: true,
model: null,
weight: 1,
beginStepPct: 0,
endStepPct: 1,
controlMode: 'balanced',
resizeMode: 'just_resize',
controlImage: null,
processedControlImage: null,
processorType: 'canny_image_processor',
processorNode: CONTROLNET_PROCESSORS.canny_image_processor.buildDefaults() as RequiredCannyImageProcessorInvocation,
shouldAutoConfig: true,
};
export const initialT2IAdapter: Omit<T2IAdapterConfig, 'id'> = {
type: 't2i_adapter',
isEnabled: true,
model: null,
weight: 1,
beginStepPct: 0,
endStepPct: 1,
resizeMode: 'just_resize',
controlImage: null,
processedControlImage: null,
processorType: 'canny_image_processor',
processorNode: CONTROLNET_PROCESSORS.canny_image_processor.buildDefaults() as RequiredCannyImageProcessorInvocation,
shouldAutoConfig: true,
};
export const initialIPAdapter: Omit<IPAdapterConfig, 'id'> = {
type: 'ip_adapter',
isEnabled: true,
controlImage: null,
model: null,
method: 'full',
clipVisionModel: 'ViT-H',
weight: 1,
beginStepPct: 0,
endStepPct: 1,
};
export const buildControlAdapter = (
id: string,
type: ControlAdapterType,
overrides: Partial<ControlAdapterConfig> = {}
): ControlAdapterConfig => {
switch (type) {
case 'controlnet':
return merge(deepClone(initialControlNet), { id, ...overrides });
case 't2i_adapter':
return merge(deepClone(initialT2IAdapter), { id, ...overrides });
case 'ip_adapter':
return merge(deepClone(initialIPAdapter), { id, ...overrides });
default:
throw new Error(`Unknown control adapter type: ${type}`);
}
};

View File

@ -1,11 +0,0 @@
import { CONTROLNET_PROCESSORS } from 'features/controlAdapters/store/constants';
import { isControlAdapterProcessorType } from 'features/controlAdapters/store/types';
import type { ControlNetModelConfig, T2IAdapterModelConfig } from 'services/api/types';
export const buildControlAdapterProcessor = (modelConfig: ControlNetModelConfig | T2IAdapterModelConfig) => {
const defaultPreprocessor = modelConfig.default_settings?.preprocessor;
const processorType = isControlAdapterProcessorType(defaultPreprocessor) ? defaultPreprocessor : 'none';
const processorNode = CONTROLNET_PROCESSORS[processorType].buildDefaults(modelConfig.base);
return { processorType, processorNode };
};