mirror of
https://github.com/invoke-ai/InvokeAI
synced 2024-08-30 20:32:17 +00:00
refactor(ui): add CA config components (wip)
This commit is contained in:
parent
811e8a5a8b
commit
6007218a51
@ -1,7 +1,7 @@
|
|||||||
import { Flex, Spacer, useDisclosure } from '@invoke-ai/ui-library';
|
import { Flex, Spacer, useDisclosure } from '@invoke-ai/ui-library';
|
||||||
import { createMemoizedSelector } from 'app/store/createMemoizedSelector';
|
import { createMemoizedSelector } from 'app/store/createMemoizedSelector';
|
||||||
import { useAppDispatch, useAppSelector } from 'app/store/storeHooks';
|
import { useAppDispatch, useAppSelector } from 'app/store/storeHooks';
|
||||||
import ControlAdapterLayerConfig from 'features/controlLayers/components/controlAdapterOverrides/ControlAdapterLayerConfig';
|
import ControlAdapterLayerConfig from 'features/controlLayers/components/CALayer/ControlAdapterLayerConfig';
|
||||||
import { LayerDeleteButton } from 'features/controlLayers/components/LayerCommon/LayerDeleteButton';
|
import { LayerDeleteButton } from 'features/controlLayers/components/LayerCommon/LayerDeleteButton';
|
||||||
import { LayerMenu } from 'features/controlLayers/components/LayerCommon/LayerMenu';
|
import { LayerMenu } from 'features/controlLayers/components/LayerCommon/LayerMenu';
|
||||||
import { LayerTitle } from 'features/controlLayers/components/LayerCommon/LayerTitle';
|
import { LayerTitle } from 'features/controlLayers/components/LayerCommon/LayerTitle';
|
||||||
|
@ -0,0 +1,66 @@
|
|||||||
|
import { CompositeRangeSlider, FormControl, FormLabel } from '@invoke-ai/ui-library';
|
||||||
|
import { useAppDispatch, useAppSelector } from 'app/store/storeHooks';
|
||||||
|
import { InformationalPopover } from 'common/components/InformationalPopover/InformationalPopover';
|
||||||
|
import { caLayerBeginEndStepPctChanged, selectCALayer } from 'features/controlLayers/store/controlLayersSlice';
|
||||||
|
import { memo, useCallback } from 'react';
|
||||||
|
import { useTranslation } from 'react-i18next';
|
||||||
|
|
||||||
|
type Props = {
|
||||||
|
layerId: string;
|
||||||
|
};
|
||||||
|
|
||||||
|
const formatPct = (v: number) => `${Math.round(v * 100)}%`;
|
||||||
|
const ariaLabel = ['Begin Step %', 'End Step %'];
|
||||||
|
|
||||||
|
export const CALayerBeginEndStepPct = memo(({ layerId }: Props) => {
|
||||||
|
const dispatch = useAppDispatch();
|
||||||
|
const { t } = useTranslation();
|
||||||
|
const beginEndStepPct = useAppSelector(
|
||||||
|
(s) => selectCALayer(s.controlLayers.present, layerId).controlAdapter.beginEndStepPct
|
||||||
|
);
|
||||||
|
|
||||||
|
const onChange = useCallback(
|
||||||
|
(v: [number, number]) => {
|
||||||
|
dispatch(
|
||||||
|
caLayerBeginEndStepPctChanged({
|
||||||
|
layerId,
|
||||||
|
beginEndStepPct: v,
|
||||||
|
})
|
||||||
|
);
|
||||||
|
},
|
||||||
|
[dispatch, layerId]
|
||||||
|
);
|
||||||
|
|
||||||
|
const onReset = useCallback(() => {
|
||||||
|
dispatch(
|
||||||
|
caLayerBeginEndStepPctChanged({
|
||||||
|
layerId,
|
||||||
|
beginEndStepPct: [0, 1],
|
||||||
|
})
|
||||||
|
);
|
||||||
|
}, [dispatch, layerId]);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<FormControl orientation="horizontal">
|
||||||
|
<InformationalPopover feature="controlNetBeginEnd">
|
||||||
|
<FormLabel m={0}>{t('controlnet.beginEndStepPercentShort')}</FormLabel>
|
||||||
|
</InformationalPopover>
|
||||||
|
<CompositeRangeSlider
|
||||||
|
aria-label={ariaLabel}
|
||||||
|
value={beginEndStepPct}
|
||||||
|
onChange={onChange}
|
||||||
|
onReset={onReset}
|
||||||
|
min={0}
|
||||||
|
max={1}
|
||||||
|
step={0.05}
|
||||||
|
fineStep={0.01}
|
||||||
|
minStepsBetweenThumbs={1}
|
||||||
|
formatValue={formatPct}
|
||||||
|
marks
|
||||||
|
withThumbTooltip
|
||||||
|
/>
|
||||||
|
</FormControl>
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
CALayerBeginEndStepPct.displayName = 'CALayerBeginEndStepPct';
|
@ -0,0 +1,70 @@
|
|||||||
|
import { Box, Flex, Icon, IconButton } from '@invoke-ai/ui-library';
|
||||||
|
import { useAppSelector } from 'app/store/storeHooks';
|
||||||
|
import ControlAdapterProcessorComponent from 'features/controlAdapters/components/ControlAdapterProcessorComponent';
|
||||||
|
import { CALayerModelCombobox } from 'features/controlLayers/components/CALayer/CALayerModelCombobox';
|
||||||
|
import { selectCALayer } from 'features/controlLayers/store/controlLayersSlice';
|
||||||
|
import { memo } from 'react';
|
||||||
|
import { useTranslation } from 'react-i18next';
|
||||||
|
import { PiCaretUpBold } from 'react-icons/pi';
|
||||||
|
import { useToggle } from 'react-use';
|
||||||
|
|
||||||
|
import { CALayerBeginEndStepPct } from './CALayerBeginEndStepPct';
|
||||||
|
import { CALayerControlMode } from './CALayerControlMode';
|
||||||
|
import { CALayerImagePreview } from './CALayerImagePreview';
|
||||||
|
import { CALayerProcessorCombobox } from './CALayerProcessorCombobox';
|
||||||
|
import { CALayerWeight } from './CALayerWeight';
|
||||||
|
|
||||||
|
type Props = {
|
||||||
|
layerId: string;
|
||||||
|
};
|
||||||
|
|
||||||
|
export const CALayerConfig = memo(({ layerId }: Props) => {
|
||||||
|
const caType = useAppSelector((s) => selectCALayer(s.controlLayers.present, layerId).controlAdapter.type);
|
||||||
|
const { t } = useTranslation();
|
||||||
|
const [isExpanded, toggleIsExpanded] = useToggle(false);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Flex flexDir="column" gap={4} position="relative" w="full">
|
||||||
|
<Flex gap={3} alignItems="center" w="full">
|
||||||
|
<Box minW={0} w="full" transitionProperty="common" transitionDuration="0.1s">
|
||||||
|
<CALayerModelCombobox layerId={layerId} />
|
||||||
|
</Box>
|
||||||
|
|
||||||
|
<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 gap={4} w="full" alignItems="center">
|
||||||
|
<Flex flexDir="column" gap={3} w="full">
|
||||||
|
{caType === 'controlnet' && <CALayerControlMode layerId={layerId} />}
|
||||||
|
<CALayerWeight layerId={layerId} />
|
||||||
|
<CALayerBeginEndStepPct layerId={layerId} />
|
||||||
|
</Flex>
|
||||||
|
<Flex alignItems="center" justifyContent="center" h={36} w={36} aspectRatio="1/1">
|
||||||
|
<CALayerImagePreview layerId={layerId} />
|
||||||
|
</Flex>
|
||||||
|
</Flex>
|
||||||
|
{isExpanded && (
|
||||||
|
<>
|
||||||
|
<CALayerProcessorCombobox layerId={layerId} />
|
||||||
|
<ControlAdapterProcessorComponent id={id} />
|
||||||
|
</>
|
||||||
|
)}
|
||||||
|
</Flex>
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
CALayerConfig.displayName = 'CALayerConfig';
|
@ -1,23 +1,25 @@
|
|||||||
import type { ComboboxOnChange } from '@invoke-ai/ui-library';
|
import type { ComboboxOnChange } from '@invoke-ai/ui-library';
|
||||||
import { Combobox, FormControl, FormLabel } from '@invoke-ai/ui-library';
|
import { Combobox, FormControl, FormLabel } from '@invoke-ai/ui-library';
|
||||||
import { useAppDispatch } from 'app/store/storeHooks';
|
import { useAppDispatch, useAppSelector } from 'app/store/storeHooks';
|
||||||
import { InformationalPopover } from 'common/components/InformationalPopover/InformationalPopover';
|
import { InformationalPopover } from 'common/components/InformationalPopover/InformationalPopover';
|
||||||
import { useControlAdapterControlMode } from 'features/controlAdapters/hooks/useControlAdapterControlMode';
|
import { caLayerControlModeChanged, selectCALayer } from 'features/controlLayers/store/controlLayersSlice';
|
||||||
import { useControlAdapterIsEnabled } from 'features/controlAdapters/hooks/useControlAdapterIsEnabled';
|
import { isControlMode } from 'features/controlLayers/util/controlAdapters';
|
||||||
import { controlAdapterControlModeChanged } from 'features/controlAdapters/store/controlAdaptersSlice';
|
|
||||||
import type { ControlMode } from 'features/controlAdapters/store/types';
|
|
||||||
import { memo, useCallback, useMemo } from 'react';
|
import { memo, useCallback, useMemo } from 'react';
|
||||||
import { useTranslation } from 'react-i18next';
|
import { useTranslation } from 'react-i18next';
|
||||||
|
import { assert } from 'tsafe';
|
||||||
|
|
||||||
type Props = {
|
type Props = {
|
||||||
id: string;
|
layerId: string;
|
||||||
};
|
};
|
||||||
|
|
||||||
const ParamControlAdapterControlMode = ({ id }: Props) => {
|
export const CALayerControlMode = memo(({ layerId }: Props) => {
|
||||||
const isEnabled = useControlAdapterIsEnabled(id);
|
|
||||||
const controlMode = useControlAdapterControlMode(id);
|
|
||||||
const dispatch = useAppDispatch();
|
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
|
const dispatch = useAppDispatch();
|
||||||
|
const controlMode = useAppSelector((s) => {
|
||||||
|
const ca = selectCALayer(s.controlLayers.present, layerId).controlAdapter;
|
||||||
|
assert(ca.type === 'controlnet');
|
||||||
|
return ca.controlMode;
|
||||||
|
});
|
||||||
|
|
||||||
const CONTROL_MODE_DATA = useMemo(
|
const CONTROL_MODE_DATA = useMemo(
|
||||||
() => [
|
() => [
|
||||||
@ -31,17 +33,15 @@ const ParamControlAdapterControlMode = ({ id }: Props) => {
|
|||||||
|
|
||||||
const handleControlModeChange = useCallback<ComboboxOnChange>(
|
const handleControlModeChange = useCallback<ComboboxOnChange>(
|
||||||
(v) => {
|
(v) => {
|
||||||
if (!v) {
|
assert(isControlMode(v?.value));
|
||||||
return;
|
|
||||||
}
|
|
||||||
dispatch(
|
dispatch(
|
||||||
controlAdapterControlModeChanged({
|
caLayerControlModeChanged({
|
||||||
id,
|
layerId,
|
||||||
controlMode: v.value as ControlMode,
|
controlMode: v.value,
|
||||||
})
|
})
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
[id, dispatch]
|
[layerId, dispatch]
|
||||||
);
|
);
|
||||||
|
|
||||||
const value = useMemo(
|
const value = useMemo(
|
||||||
@ -54,13 +54,19 @@ const ParamControlAdapterControlMode = ({ id }: Props) => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<FormControl isDisabled={!isEnabled}>
|
<FormControl>
|
||||||
<InformationalPopover feature="controlNetControlMode">
|
<InformationalPopover feature="controlNetControlMode">
|
||||||
<FormLabel m={0}>{t('controlnet.control')}</FormLabel>
|
<FormLabel m={0}>{t('controlnet.control')}</FormLabel>
|
||||||
</InformationalPopover>
|
</InformationalPopover>
|
||||||
<Combobox value={value} options={CONTROL_MODE_DATA} onChange={handleControlModeChange} />
|
<Combobox
|
||||||
|
value={value}
|
||||||
|
options={CONTROL_MODE_DATA}
|
||||||
|
onChange={handleControlModeChange}
|
||||||
|
isClearable={false}
|
||||||
|
isSearchable={false}
|
||||||
|
/>
|
||||||
</FormControl>
|
</FormControl>
|
||||||
);
|
);
|
||||||
};
|
});
|
||||||
|
|
||||||
export default memo(ParamControlAdapterControlMode);
|
CALayerControlMode.displayName = 'CALayerControlMode';
|
@ -6,15 +6,15 @@ import { useAppDispatch, useAppSelector } from 'app/store/storeHooks';
|
|||||||
import IAIDndImage from 'common/components/IAIDndImage';
|
import IAIDndImage from 'common/components/IAIDndImage';
|
||||||
import IAIDndImageIcon from 'common/components/IAIDndImageIcon';
|
import IAIDndImageIcon from 'common/components/IAIDndImageIcon';
|
||||||
import { setBoundingBoxDimensions } from 'features/canvas/store/canvasSlice';
|
import { setBoundingBoxDimensions } from 'features/canvas/store/canvasSlice';
|
||||||
import { useControlAdapterControlImage } from 'features/controlAdapters/hooks/useControlAdapterControlImage';
|
import { selectControlAdaptersSlice } from 'features/controlAdapters/store/controlAdaptersSlice';
|
||||||
import { useControlAdapterProcessedControlImage } from 'features/controlAdapters/hooks/useControlAdapterProcessedControlImage';
|
|
||||||
import { useControlAdapterProcessorType } from 'features/controlAdapters/hooks/useControlAdapterProcessorType';
|
|
||||||
import {
|
import {
|
||||||
controlAdapterImageChanged,
|
caLayerImageChanged,
|
||||||
selectControlAdaptersSlice,
|
heightChanged,
|
||||||
} from 'features/controlAdapters/store/controlAdaptersSlice';
|
selectCALayer,
|
||||||
import { heightChanged, widthChanged } from 'features/controlLayers/store/controlLayersSlice';
|
selectControlLayersSlice,
|
||||||
import type { TypesafeDraggableData, TypesafeDroppableData } from 'features/dnd/types';
|
widthChanged,
|
||||||
|
} from 'features/controlLayers/store/controlLayersSlice';
|
||||||
|
import type { ControlLayerDropData, ImageDraggableData } from 'features/dnd/types';
|
||||||
import { calculateNewSize } from 'features/parameters/components/ImageSize/calculateNewSize';
|
import { calculateNewSize } from 'features/parameters/components/ImageSize/calculateNewSize';
|
||||||
import { selectOptimalDimension } from 'features/parameters/store/generationSlice';
|
import { selectOptimalDimension } from 'features/parameters/store/generationSlice';
|
||||||
import { activeTabNameSelector } from 'features/ui/store/uiSelectors';
|
import { activeTabNameSelector } from 'features/ui/store/uiSelectors';
|
||||||
@ -27,11 +27,10 @@ import {
|
|||||||
useGetImageDTOQuery,
|
useGetImageDTOQuery,
|
||||||
useRemoveImageFromBoardMutation,
|
useRemoveImageFromBoardMutation,
|
||||||
} from 'services/api/endpoints/images';
|
} from 'services/api/endpoints/images';
|
||||||
import type { PostUploadAction } from 'services/api/types';
|
import type { ControlLayerAction } from 'services/api/types';
|
||||||
|
|
||||||
type Props = {
|
type Props = {
|
||||||
id: string;
|
layerId: string;
|
||||||
isSmall?: boolean;
|
|
||||||
};
|
};
|
||||||
|
|
||||||
const selectPendingControlImages = createMemoizedSelector(
|
const selectPendingControlImages = createMemoizedSelector(
|
||||||
@ -39,12 +38,23 @@ const selectPendingControlImages = createMemoizedSelector(
|
|||||||
(controlAdapters) => controlAdapters.pendingControlImages
|
(controlAdapters) => controlAdapters.pendingControlImages
|
||||||
);
|
);
|
||||||
|
|
||||||
const ControlAdapterImagePreview = ({ isSmall, id }: Props) => {
|
export const CALayerImagePreview = memo(({ layerId }: Props) => {
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
const dispatch = useAppDispatch();
|
const dispatch = useAppDispatch();
|
||||||
const controlImageName = useControlAdapterControlImage(id);
|
const selector = useMemo(
|
||||||
const processedControlImageName = useControlAdapterProcessedControlImage(id);
|
() =>
|
||||||
const processorType = useControlAdapterProcessorType(id);
|
createMemoizedSelector(selectControlLayersSlice, (controlLayers) => {
|
||||||
|
const layer = selectCALayer(controlLayers.present, layerId);
|
||||||
|
const { image, processedImage, processorConfig } = layer.controlAdapter;
|
||||||
|
return {
|
||||||
|
imageName: image?.imageName ?? null,
|
||||||
|
processedImageName: processedImage?.imageName ?? null,
|
||||||
|
hasProcessor: Boolean(processorConfig),
|
||||||
|
};
|
||||||
|
}),
|
||||||
|
[layerId]
|
||||||
|
);
|
||||||
|
const { imageName, processedImageName, hasProcessor } = useAppSelector(selector);
|
||||||
const autoAddBoardId = useAppSelector((s) => s.gallery.autoAddBoardId);
|
const autoAddBoardId = useAppSelector((s) => s.gallery.autoAddBoardId);
|
||||||
const isConnected = useAppSelector((s) => s.system.isConnected);
|
const isConnected = useAppSelector((s) => s.system.isConnected);
|
||||||
const activeTabName = useAppSelector(activeTabNameSelector);
|
const activeTabName = useAppSelector(activeTabNameSelector);
|
||||||
@ -54,20 +64,17 @@ const ControlAdapterImagePreview = ({ isSmall, id }: Props) => {
|
|||||||
|
|
||||||
const [isMouseOverImage, setIsMouseOverImage] = useState(false);
|
const [isMouseOverImage, setIsMouseOverImage] = useState(false);
|
||||||
|
|
||||||
const { currentData: controlImage, isError: isErrorControlImage } = useGetImageDTOQuery(
|
const { currentData: controlImage, isError: isErrorControlImage } = useGetImageDTOQuery(imageName ?? skipToken);
|
||||||
controlImageName ?? skipToken
|
|
||||||
);
|
|
||||||
|
|
||||||
const { currentData: processedControlImage, isError: isErrorProcessedControlImage } = useGetImageDTOQuery(
|
const { currentData: processedControlImage, isError: isErrorProcessedControlImage } = useGetImageDTOQuery(
|
||||||
processedControlImageName ?? skipToken
|
processedImageName ?? skipToken
|
||||||
);
|
);
|
||||||
|
|
||||||
const [changeIsIntermediate] = useChangeImageIsIntermediateMutation();
|
const [changeIsIntermediate] = useChangeImageIsIntermediateMutation();
|
||||||
const [addToBoard] = useAddImageToBoardMutation();
|
const [addToBoard] = useAddImageToBoardMutation();
|
||||||
const [removeFromBoard] = useRemoveImageFromBoardMutation();
|
const [removeFromBoard] = useRemoveImageFromBoardMutation();
|
||||||
const handleResetControlImage = useCallback(() => {
|
const handleResetControlImage = useCallback(() => {
|
||||||
dispatch(controlAdapterImageChanged({ id, controlImage: null }));
|
dispatch(caLayerImageChanged({ layerId, imageDTO: null }));
|
||||||
}, [id, dispatch]);
|
}, [layerId, dispatch]);
|
||||||
|
|
||||||
const handleSaveControlImage = useCallback(async () => {
|
const handleSaveControlImage = useCallback(async () => {
|
||||||
if (!processedControlImage) {
|
if (!processedControlImage) {
|
||||||
@ -120,33 +127,33 @@ const ControlAdapterImagePreview = ({ isSmall, id }: Props) => {
|
|||||||
setIsMouseOverImage(false);
|
setIsMouseOverImage(false);
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
const draggableData = useMemo<TypesafeDraggableData | undefined>(() => {
|
const draggableData = useMemo<ImageDraggableData | undefined>(() => {
|
||||||
if (controlImage) {
|
if (controlImage) {
|
||||||
return {
|
return {
|
||||||
id,
|
id: layerId,
|
||||||
payloadType: 'IMAGE_DTO',
|
payloadType: 'IMAGE_DTO',
|
||||||
payload: { imageDTO: controlImage },
|
payload: { imageDTO: controlImage },
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
}, [controlImage, id]);
|
}, [controlImage, layerId]);
|
||||||
|
|
||||||
const droppableData = useMemo<TypesafeDroppableData | undefined>(
|
const droppableData = useMemo<ControlLayerDropData>(
|
||||||
() => ({
|
() => ({
|
||||||
id,
|
id: layerId,
|
||||||
actionType: 'SET_CONTROL_ADAPTER_IMAGE',
|
actionType: 'SET_CONTROL_LAYER_IMAGE',
|
||||||
context: { id },
|
context: { layerId },
|
||||||
}),
|
}),
|
||||||
[id]
|
[layerId]
|
||||||
);
|
);
|
||||||
|
|
||||||
const postUploadAction = useMemo<PostUploadAction>(() => ({ type: 'SET_CONTROL_ADAPTER_IMAGE', id }), [id]);
|
const postUploadAction = useMemo<ControlLayerAction>(() => ({ type: 'SET_CONTROL_LAYER_IMAGE', layerId }), [layerId]);
|
||||||
|
|
||||||
const shouldShowProcessedImage =
|
const shouldShowProcessedImage =
|
||||||
controlImage &&
|
controlImage &&
|
||||||
processedControlImage &&
|
processedControlImage &&
|
||||||
!isMouseOverImage &&
|
!isMouseOverImage &&
|
||||||
!pendingControlImages.includes(id) &&
|
!pendingControlImages.includes(layerId) &&
|
||||||
processorType !== 'none';
|
hasProcessor;
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (isConnected && (isErrorControlImage || isErrorProcessedControlImage)) {
|
if (isConnected && (isErrorControlImage || isErrorProcessedControlImage)) {
|
||||||
@ -160,7 +167,7 @@ const ControlAdapterImagePreview = ({ isSmall, id }: Props) => {
|
|||||||
onMouseLeave={handleMouseLeave}
|
onMouseLeave={handleMouseLeave}
|
||||||
position="relative"
|
position="relative"
|
||||||
w="full"
|
w="full"
|
||||||
h={isSmall ? 36 : 366} // magic no touch
|
h={36}
|
||||||
alignItems="center"
|
alignItems="center"
|
||||||
justifyContent="center"
|
justifyContent="center"
|
||||||
>
|
>
|
||||||
@ -211,7 +218,7 @@ const ControlAdapterImagePreview = ({ isSmall, id }: Props) => {
|
|||||||
/>
|
/>
|
||||||
</>
|
</>
|
||||||
|
|
||||||
{pendingControlImages.includes(id) && (
|
{pendingControlImages.includes(layerId) && (
|
||||||
<Flex
|
<Flex
|
||||||
position="absolute"
|
position="absolute"
|
||||||
top={0}
|
top={0}
|
||||||
@ -229,9 +236,9 @@ const ControlAdapterImagePreview = ({ isSmall, id }: Props) => {
|
|||||||
)}
|
)}
|
||||||
</Flex>
|
</Flex>
|
||||||
);
|
);
|
||||||
};
|
});
|
||||||
|
|
||||||
export default memo(ControlAdapterImagePreview);
|
CALayerImagePreview.displayName = 'CALayerImagePreview';
|
||||||
|
|
||||||
const saveControlImageStyleOverrides: SystemStyleObject = { mt: 6 };
|
const saveControlImageStyleOverrides: SystemStyleObject = { mt: 6 };
|
||||||
const setControlImageDimensionsStyleOverrides: SystemStyleObject = { mt: 12 };
|
const setControlImageDimensionsStyleOverrides: SystemStyleObject = { mt: 12 };
|
@ -0,0 +1,71 @@
|
|||||||
|
import { Combobox, FormControl, Tooltip } from '@invoke-ai/ui-library';
|
||||||
|
import { useAppDispatch, useAppSelector } from 'app/store/storeHooks';
|
||||||
|
import { useGroupedModelCombobox } from 'common/hooks/useGroupedModelCombobox';
|
||||||
|
import { caLayerModelChanged, selectCALayer } from 'features/controlLayers/store/controlLayersSlice';
|
||||||
|
import { memo, useCallback, useMemo } from 'react';
|
||||||
|
import { useTranslation } from 'react-i18next';
|
||||||
|
import { useControlNetAndT2IAdapterModels } from 'services/api/hooks/modelsByType';
|
||||||
|
import type { AnyModelConfig, ControlNetModelConfig, T2IAdapterModelConfig } from 'services/api/types';
|
||||||
|
|
||||||
|
type Props = {
|
||||||
|
layerId: string;
|
||||||
|
};
|
||||||
|
|
||||||
|
export const CALayerModelCombobox = memo(({ layerId }: Props) => {
|
||||||
|
const { t } = useTranslation();
|
||||||
|
const dispatch = useAppDispatch();
|
||||||
|
|
||||||
|
const caModelKey = useAppSelector((s) => selectCALayer(s.controlLayers.present, layerId).controlAdapter.model?.key);
|
||||||
|
const currentBaseModel = useAppSelector((s) => s.generation.model?.base);
|
||||||
|
|
||||||
|
const [modelConfigs, { isLoading }] = useControlNetAndT2IAdapterModels();
|
||||||
|
const selectedModel = useMemo(() => modelConfigs.find((m) => m.key === caModelKey), [modelConfigs, caModelKey]);
|
||||||
|
|
||||||
|
const _onChange = useCallback(
|
||||||
|
(modelConfig: ControlNetModelConfig | T2IAdapterModelConfig | null) => {
|
||||||
|
if (!modelConfig) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
dispatch(
|
||||||
|
caLayerModelChanged({
|
||||||
|
layerId,
|
||||||
|
modelConfig,
|
||||||
|
})
|
||||||
|
);
|
||||||
|
},
|
||||||
|
[dispatch, layerId]
|
||||||
|
);
|
||||||
|
|
||||||
|
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,
|
||||||
|
});
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Tooltip label={selectedModel?.description}>
|
||||||
|
<FormControl isInvalid={!value || currentBaseModel !== selectedModel?.base} w="full">
|
||||||
|
<Combobox
|
||||||
|
options={options}
|
||||||
|
placeholder={t('controlnet.selectModel')}
|
||||||
|
value={value}
|
||||||
|
onChange={onChange}
|
||||||
|
noOptionsMessage={noOptionsMessage}
|
||||||
|
/>
|
||||||
|
</FormControl>
|
||||||
|
</Tooltip>
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
CALayerModelCombobox.displayName = 'CALayerModelCombobox';
|
@ -0,0 +1,86 @@
|
|||||||
|
import type { ComboboxOnChange } from '@invoke-ai/ui-library';
|
||||||
|
import { Combobox, Flex, FormControl, FormLabel, IconButton } 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 { caLayerProcessorConfigChanged, selectCALayer } from 'features/controlLayers/store/controlLayersSlice';
|
||||||
|
import { CONTROLNET_PROCESSORS, isProcessorType } from 'features/controlLayers/util/controlAdapters';
|
||||||
|
import { configSelector } from 'features/system/store/configSelectors';
|
||||||
|
import { includes, map } from 'lodash-es';
|
||||||
|
import { memo, useCallback, useMemo } from 'react';
|
||||||
|
import { useTranslation } from 'react-i18next';
|
||||||
|
import { PiXBold } from 'react-icons/pi';
|
||||||
|
import { assert } from 'tsafe';
|
||||||
|
|
||||||
|
type Props = {
|
||||||
|
layerId: string;
|
||||||
|
};
|
||||||
|
|
||||||
|
const selectDisabledProcessors = createMemoizedSelector(
|
||||||
|
configSelector,
|
||||||
|
(config) => config.sd.disabledControlNetProcessors
|
||||||
|
);
|
||||||
|
|
||||||
|
export const CALayerProcessorCombobox = memo(({ layerId }: Props) => {
|
||||||
|
const { t } = useTranslation();
|
||||||
|
const dispatch = useAppDispatch();
|
||||||
|
const disabledProcessors = useAppSelector(selectDisabledProcessors);
|
||||||
|
const processorType = useAppSelector(
|
||||||
|
(s) => selectCALayer(s.controlLayers.present, layerId).controlAdapter.processorConfig?.type ?? null
|
||||||
|
);
|
||||||
|
const options = useMemo(() => {
|
||||||
|
return map(CONTROLNET_PROCESSORS, ({ labelTKey }, type) => ({ value: type, label: t(labelTKey) })).filter(
|
||||||
|
(o) => !includes(disabledProcessors, o.value)
|
||||||
|
);
|
||||||
|
}, [disabledProcessors, t]);
|
||||||
|
|
||||||
|
const onChange = useCallback<ComboboxOnChange>(
|
||||||
|
(v) => {
|
||||||
|
if (!v) {
|
||||||
|
dispatch(
|
||||||
|
caLayerProcessorConfigChanged({
|
||||||
|
layerId,
|
||||||
|
processorConfig: null,
|
||||||
|
})
|
||||||
|
);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
assert(isProcessorType(v.value));
|
||||||
|
dispatch(
|
||||||
|
caLayerProcessorConfigChanged({
|
||||||
|
layerId,
|
||||||
|
processorConfig: CONTROLNET_PROCESSORS[v.value].buildDefaults(),
|
||||||
|
})
|
||||||
|
);
|
||||||
|
},
|
||||||
|
[dispatch, layerId]
|
||||||
|
);
|
||||||
|
const clearProcessor = useCallback(() => {
|
||||||
|
dispatch(
|
||||||
|
caLayerProcessorConfigChanged({
|
||||||
|
layerId,
|
||||||
|
processorConfig: null,
|
||||||
|
})
|
||||||
|
);
|
||||||
|
}, [dispatch, layerId]);
|
||||||
|
const value = useMemo(() => options.find((o) => o.value === processorType) ?? null, [options, processorType]);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<FormControl>
|
||||||
|
<InformationalPopover feature="controlNetProcessor">
|
||||||
|
<FormLabel>{t('controlnet.processor')}</FormLabel>
|
||||||
|
</InformationalPopover>
|
||||||
|
<Flex gap={4}>
|
||||||
|
<Combobox value={value} options={options} onChange={onChange} />
|
||||||
|
<IconButton
|
||||||
|
aria-label={t('controlnet.processor')}
|
||||||
|
onClick={clearProcessor}
|
||||||
|
icon={<PiXBold />}
|
||||||
|
variant="ghost"
|
||||||
|
/>
|
||||||
|
</Flex>
|
||||||
|
</FormControl>
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
CALayerProcessorCombobox.displayName = 'CALayerProcessorCombobox';
|
@ -1,24 +1,21 @@
|
|||||||
import { CompositeNumberInput, CompositeSlider, FormControl, FormLabel } from '@invoke-ai/ui-library';
|
import { CompositeNumberInput, CompositeSlider, FormControl, FormLabel } from '@invoke-ai/ui-library';
|
||||||
import { useAppDispatch, useAppSelector } from 'app/store/storeHooks';
|
import { useAppDispatch, useAppSelector } from 'app/store/storeHooks';
|
||||||
import { InformationalPopover } from 'common/components/InformationalPopover/InformationalPopover';
|
import { InformationalPopover } from 'common/components/InformationalPopover/InformationalPopover';
|
||||||
import { useControlAdapterIsEnabled } from 'features/controlAdapters/hooks/useControlAdapterIsEnabled';
|
import { caLayerWeightChanged, selectCALayer } from 'features/controlLayers/store/controlLayersSlice';
|
||||||
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 { memo, useCallback } from 'react';
|
||||||
import { useTranslation } from 'react-i18next';
|
import { useTranslation } from 'react-i18next';
|
||||||
|
|
||||||
type ParamControlAdapterWeightProps = {
|
type Props = {
|
||||||
id: string;
|
layerId: string;
|
||||||
};
|
};
|
||||||
|
|
||||||
const formatValue = (v: number) => v.toFixed(2);
|
const formatValue = (v: number) => v.toFixed(2);
|
||||||
|
const marks = [0, 1, 2];
|
||||||
|
|
||||||
const ParamControlAdapterWeight = ({ id }: ParamControlAdapterWeightProps) => {
|
export const CALayerWeight = memo(({ layerId }: Props) => {
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
const dispatch = useAppDispatch();
|
const dispatch = useAppDispatch();
|
||||||
const isEnabled = useControlAdapterIsEnabled(id);
|
const weight = useAppSelector((s) => selectCALayer(s.controlLayers.present, layerId).controlAdapter.weight);
|
||||||
const weight = useControlAdapterWeight(id);
|
|
||||||
const initial = useAppSelector((s) => s.config.sd.ca.weight.initial);
|
const initial = useAppSelector((s) => s.config.sd.ca.weight.initial);
|
||||||
const sliderMin = useAppSelector((s) => s.config.sd.ca.weight.sliderMin);
|
const sliderMin = useAppSelector((s) => s.config.sd.ca.weight.sliderMin);
|
||||||
const sliderMax = useAppSelector((s) => s.config.sd.ca.weight.sliderMax);
|
const sliderMax = useAppSelector((s) => s.config.sd.ca.weight.sliderMax);
|
||||||
@ -29,18 +26,13 @@ const ParamControlAdapterWeight = ({ id }: ParamControlAdapterWeightProps) => {
|
|||||||
|
|
||||||
const onChange = useCallback(
|
const onChange = useCallback(
|
||||||
(weight: number) => {
|
(weight: number) => {
|
||||||
dispatch(controlAdapterWeightChanged({ id, weight }));
|
dispatch(caLayerWeightChanged({ layerId, weight }));
|
||||||
},
|
},
|
||||||
[dispatch, id]
|
[dispatch, layerId]
|
||||||
);
|
);
|
||||||
|
|
||||||
if (isNil(weight)) {
|
|
||||||
// should never happen
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<FormControl isDisabled={!isEnabled} orientation="horizontal">
|
<FormControl orientation="horizontal">
|
||||||
<InformationalPopover feature="controlNetWeight">
|
<InformationalPopover feature="controlNetWeight">
|
||||||
<FormLabel m={0}>{t('controlnet.weight')}</FormLabel>
|
<FormLabel m={0}>{t('controlnet.weight')}</FormLabel>
|
||||||
</InformationalPopover>
|
</InformationalPopover>
|
||||||
@ -67,8 +59,6 @@ const ParamControlAdapterWeight = ({ id }: ParamControlAdapterWeightProps) => {
|
|||||||
/>
|
/>
|
||||||
</FormControl>
|
</FormControl>
|
||||||
);
|
);
|
||||||
};
|
});
|
||||||
|
|
||||||
export default memo(ParamControlAdapterWeight);
|
CALayerWeight.displayName = 'CALayerWeight';
|
||||||
|
|
||||||
const marks = [0, 1, 2];
|
|
@ -1,23 +1,26 @@
|
|||||||
import { Box, Flex, Icon, IconButton } from '@invoke-ai/ui-library';
|
import { Box, Flex, Icon, IconButton } from '@invoke-ai/ui-library';
|
||||||
|
import { useAppSelector } from 'app/store/storeHooks';
|
||||||
import ControlAdapterProcessorComponent from 'features/controlAdapters/components/ControlAdapterProcessorComponent';
|
import ControlAdapterProcessorComponent from 'features/controlAdapters/components/ControlAdapterProcessorComponent';
|
||||||
import ControlAdapterShouldAutoConfig from 'features/controlAdapters/components/ControlAdapterShouldAutoConfig';
|
import ControlAdapterShouldAutoConfig from 'features/controlAdapters/components/ControlAdapterShouldAutoConfig';
|
||||||
import ParamControlAdapterIPMethod from 'features/controlAdapters/components/parameters/ParamControlAdapterIPMethod';
|
import ParamControlAdapterIPMethod from 'features/controlAdapters/components/parameters/ParamControlAdapterIPMethod';
|
||||||
import ParamControlAdapterProcessorSelect from 'features/controlAdapters/components/parameters/ParamControlAdapterProcessorSelect';
|
import ParamControlAdapterProcessorSelect from 'features/controlAdapters/components/parameters/ParamControlAdapterProcessorSelect';
|
||||||
import { useControlAdapterType } from 'features/controlAdapters/hooks/useControlAdapterType';
|
import { ParamControlAdapterBeginEnd } from 'features/controlLayers/components/CALayer/CALayerBeginEndStepPct';
|
||||||
|
import ParamControlAdapterControlMode from 'features/controlLayers/components/CALayer/CALayerControlMode';
|
||||||
|
import { CALayerImagePreview } from 'features/controlLayers/components/CALayer/CALayerImagePreview';
|
||||||
|
import ParamControlAdapterModel from 'features/controlLayers/components/CALayer/CALayerModelCombobox';
|
||||||
|
import ParamControlAdapterWeight from 'features/controlLayers/components/CALayer/CALayerWeight';
|
||||||
|
import { selectCALayer } from 'features/controlLayers/store/controlLayersSlice';
|
||||||
import { memo } from 'react';
|
import { memo } from 'react';
|
||||||
import { useTranslation } from 'react-i18next';
|
import { useTranslation } from 'react-i18next';
|
||||||
import { PiCaretUpBold } from 'react-icons/pi';
|
import { PiCaretUpBold } from 'react-icons/pi';
|
||||||
import { useToggle } from 'react-use';
|
import { useToggle } from 'react-use';
|
||||||
|
|
||||||
import ControlAdapterImagePreview from './ControlAdapterImagePreview';
|
type Props = {
|
||||||
import { ParamControlAdapterBeginEnd } from './ParamControlAdapterBeginEnd';
|
layerId: string;
|
||||||
import ParamControlAdapterControlMode from './ParamControlAdapterControlMode';
|
};
|
||||||
import ParamControlAdapterModel from './ParamControlAdapterModel';
|
|
||||||
import ParamControlAdapterWeight from './ParamControlAdapterWeight';
|
|
||||||
|
|
||||||
const ControlAdapterLayerConfig = (props: { id: string }) => {
|
export const CALayerCAConfig = memo(({ layerId }: Props) => {
|
||||||
const { id } = props;
|
const caType = useAppSelector((s) => selectCALayer(s.controlLayers.present, layerId).controlAdapter.type);
|
||||||
const controlAdapterType = useControlAdapterType(id);
|
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
const [isExpanded, toggleIsExpanded] = useToggle(false);
|
const [isExpanded, toggleIsExpanded] = useToggle(false);
|
||||||
|
|
||||||
@ -55,7 +58,7 @@ const ControlAdapterLayerConfig = (props: { id: string }) => {
|
|||||||
<ParamControlAdapterBeginEnd id={id} />
|
<ParamControlAdapterBeginEnd id={id} />
|
||||||
</Flex>
|
</Flex>
|
||||||
<Flex alignItems="center" justifyContent="center" h={36} w={36} aspectRatio="1/1">
|
<Flex alignItems="center" justifyContent="center" h={36} w={36} aspectRatio="1/1">
|
||||||
<ControlAdapterImagePreview id={id} isSmall />
|
<CALayerImagePreview id={id} isSmall />
|
||||||
</Flex>
|
</Flex>
|
||||||
</Flex>
|
</Flex>
|
||||||
{isExpanded && (
|
{isExpanded && (
|
||||||
@ -67,6 +70,6 @@ const ControlAdapterLayerConfig = (props: { id: string }) => {
|
|||||||
)}
|
)}
|
||||||
</Flex>
|
</Flex>
|
||||||
);
|
);
|
||||||
};
|
});
|
||||||
|
|
||||||
export default memo(ControlAdapterLayerConfig);
|
CALayerCAConfig.displayName = 'CALayerCAConfig';
|
@ -1,7 +1,7 @@
|
|||||||
import { Flex, Spacer, useDisclosure } from '@invoke-ai/ui-library';
|
import { Flex, Spacer, useDisclosure } from '@invoke-ai/ui-library';
|
||||||
import { createMemoizedSelector } from 'app/store/createMemoizedSelector';
|
import { createMemoizedSelector } from 'app/store/createMemoizedSelector';
|
||||||
import { useAppSelector } from 'app/store/storeHooks';
|
import { useAppSelector } from 'app/store/storeHooks';
|
||||||
import ControlAdapterLayerConfig from 'features/controlLayers/components/controlAdapterOverrides/ControlAdapterLayerConfig';
|
import ControlAdapterLayerConfig from 'features/controlLayers/components/CALayer/ControlAdapterLayerConfig';
|
||||||
import { LayerDeleteButton } from 'features/controlLayers/components/LayerCommon/LayerDeleteButton';
|
import { LayerDeleteButton } from 'features/controlLayers/components/LayerCommon/LayerDeleteButton';
|
||||||
import { LayerTitle } from 'features/controlLayers/components/LayerCommon/LayerTitle';
|
import { LayerTitle } from 'features/controlLayers/components/LayerCommon/LayerTitle';
|
||||||
import { LayerVisibilityToggle } from 'features/controlLayers/components/LayerCommon/LayerVisibilityToggle';
|
import { LayerVisibilityToggle } from 'features/controlLayers/components/LayerCommon/LayerVisibilityToggle';
|
||||||
|
@ -2,7 +2,7 @@ import { Divider, Flex, IconButton, Spacer, Text } from '@invoke-ai/ui-library';
|
|||||||
import { createMemoizedSelector } from 'app/store/createMemoizedSelector';
|
import { createMemoizedSelector } from 'app/store/createMemoizedSelector';
|
||||||
import { guidanceLayerIPAdapterDeleted } from 'app/store/middleware/listenerMiddleware/listeners/controlLayersToControlAdapterBridge';
|
import { guidanceLayerIPAdapterDeleted } from 'app/store/middleware/listenerMiddleware/listeners/controlLayersToControlAdapterBridge';
|
||||||
import { useAppDispatch, useAppSelector } from 'app/store/storeHooks';
|
import { useAppDispatch, useAppSelector } from 'app/store/storeHooks';
|
||||||
import ControlAdapterLayerConfig from 'features/controlLayers/components/controlAdapterOverrides/ControlAdapterLayerConfig';
|
import ControlAdapterLayerConfig from 'features/controlLayers/components/CALayer/ControlAdapterLayerConfig';
|
||||||
import { isRegionalGuidanceLayer, selectControlLayersSlice } from 'features/controlLayers/store/controlLayersSlice';
|
import { isRegionalGuidanceLayer, selectControlLayersSlice } from 'features/controlLayers/store/controlLayersSlice';
|
||||||
import { memo, useCallback, useMemo } from 'react';
|
import { memo, useCallback, useMemo } from 'react';
|
||||||
import { PiTrashSimpleBold } from 'react-icons/pi';
|
import { PiTrashSimpleBold } from 'react-icons/pi';
|
||||||
|
@ -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="horizontal">
|
|
||||||
<InformationalPopover feature="controlNetBeginEnd">
|
|
||||||
<FormLabel m={0}>{t('controlnet.beginEndStepPercentShort')}</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 %'];
|
|
@ -82,33 +82,34 @@ const resetLayer = (layer: Layer) => {
|
|||||||
// TODO
|
// TODO
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
const getVectorMaskPreviewColor = (state: ControlLayersState): RgbColor => {
|
|
||||||
const vmLayers = state.layers.filter(isRegionalGuidanceLayer);
|
export const selectCALayer = (state: ControlLayersState, layerId: string): ControlAdapterLayer => {
|
||||||
const lastColor = vmLayers[vmLayers.length - 1]?.previewColor;
|
|
||||||
return LayerColors.next(lastColor);
|
|
||||||
};
|
|
||||||
const getCALayer = (state: ControlLayersState, layerId: string): ControlAdapterLayer => {
|
|
||||||
const layer = state.layers.find((l) => l.id === layerId);
|
const layer = state.layers.find((l) => l.id === layerId);
|
||||||
assert(isControlAdapterLayer(layer));
|
assert(isControlAdapterLayer(layer));
|
||||||
return layer;
|
return layer;
|
||||||
};
|
};
|
||||||
const getIPALayer = (state: ControlLayersState, layerId: string): IPAdapterLayer => {
|
const selectIPALayer = (state: ControlLayersState, layerId: string): IPAdapterLayer => {
|
||||||
const layer = state.layers.find((l) => l.id === layerId);
|
const layer = state.layers.find((l) => l.id === layerId);
|
||||||
assert(isIPAdapterLayer(layer));
|
assert(isIPAdapterLayer(layer));
|
||||||
return layer;
|
return layer;
|
||||||
};
|
};
|
||||||
const getRGLayer = (state: ControlLayersState, layerId: string): RegionalGuidanceLayer => {
|
const selectRGLayer = (state: ControlLayersState, layerId: string): RegionalGuidanceLayer => {
|
||||||
const layer = state.layers.find((l) => l.id === layerId);
|
const layer = state.layers.find((l) => l.id === layerId);
|
||||||
assert(isRegionalGuidanceLayer(layer));
|
assert(isRegionalGuidanceLayer(layer));
|
||||||
return layer;
|
return layer;
|
||||||
};
|
};
|
||||||
const getRGLayerIPAdapter = (state: ControlLayersState, layerId: string, ipAdapterId: string): IPAdapterConfig => {
|
const selectRGLayerIPAdapter = (state: ControlLayersState, layerId: string, ipAdapterId: string): IPAdapterConfig => {
|
||||||
const layer = state.layers.find((l) => l.id === layerId);
|
const layer = state.layers.find((l) => l.id === layerId);
|
||||||
assert(isRegionalGuidanceLayer(layer));
|
assert(isRegionalGuidanceLayer(layer));
|
||||||
const ipAdapter = layer.ipAdapters.find((ipAdapter) => ipAdapter.id === ipAdapterId);
|
const ipAdapter = layer.ipAdapters.find((ipAdapter) => ipAdapter.id === ipAdapterId);
|
||||||
assert(ipAdapter);
|
assert(ipAdapter);
|
||||||
return ipAdapter;
|
return ipAdapter;
|
||||||
};
|
};
|
||||||
|
const getVectorMaskPreviewColor = (state: ControlLayersState): RgbColor => {
|
||||||
|
const rgLayers = state.layers.filter(isRegionalGuidanceLayer);
|
||||||
|
const lastColor = rgLayers[rgLayers.length - 1]?.previewColor;
|
||||||
|
return LayerColors.next(lastColor);
|
||||||
|
};
|
||||||
|
|
||||||
export const controlLayersSlice = createSlice({
|
export const controlLayersSlice = createSlice({
|
||||||
name: 'controlLayers',
|
name: 'controlLayers',
|
||||||
@ -234,15 +235,16 @@ export const controlLayersSlice = createSlice({
|
|||||||
},
|
},
|
||||||
caLayerImageChanged: (state, action: PayloadAction<{ layerId: string; imageDTO: ImageDTO | null }>) => {
|
caLayerImageChanged: (state, action: PayloadAction<{ layerId: string; imageDTO: ImageDTO | null }>) => {
|
||||||
const { layerId, imageDTO } = action.payload;
|
const { layerId, imageDTO } = action.payload;
|
||||||
const layer = getCALayer(state, layerId);
|
const layer = selectCALayer(state, layerId);
|
||||||
layer.bbox = null;
|
layer.bbox = null;
|
||||||
layer.bboxNeedsUpdate = true;
|
layer.bboxNeedsUpdate = true;
|
||||||
layer.isEnabled = true;
|
layer.isEnabled = true;
|
||||||
layer.controlAdapter.image = imageDTO ? imageDTOToImageWithDims(imageDTO) : null;
|
layer.controlAdapter.image = imageDTO ? imageDTOToImageWithDims(imageDTO) : null;
|
||||||
|
layer.controlAdapter.processedImage = null;
|
||||||
},
|
},
|
||||||
caLayerProcessedImageChanged: (state, action: PayloadAction<{ layerId: string; imageDTO: ImageDTO | null }>) => {
|
caLayerProcessedImageChanged: (state, action: PayloadAction<{ layerId: string; imageDTO: ImageDTO | null }>) => {
|
||||||
const { layerId, imageDTO } = action.payload;
|
const { layerId, imageDTO } = action.payload;
|
||||||
const layer = getCALayer(state, layerId);
|
const layer = selectCALayer(state, layerId);
|
||||||
layer.bbox = null;
|
layer.bbox = null;
|
||||||
layer.bboxNeedsUpdate = true;
|
layer.bboxNeedsUpdate = true;
|
||||||
layer.isEnabled = true;
|
layer.isEnabled = true;
|
||||||
@ -256,7 +258,7 @@ export const controlLayersSlice = createSlice({
|
|||||||
}>
|
}>
|
||||||
) => {
|
) => {
|
||||||
const { layerId, modelConfig } = action.payload;
|
const { layerId, modelConfig } = action.payload;
|
||||||
const layer = getCALayer(state, layerId);
|
const layer = selectCALayer(state, layerId);
|
||||||
if (!modelConfig) {
|
if (!modelConfig) {
|
||||||
layer.controlAdapter.model = null;
|
layer.controlAdapter.model = null;
|
||||||
return;
|
return;
|
||||||
@ -272,7 +274,7 @@ export const controlLayersSlice = createSlice({
|
|||||||
},
|
},
|
||||||
caLayerWeightChanged: (state, action: PayloadAction<{ layerId: string; weight: number }>) => {
|
caLayerWeightChanged: (state, action: PayloadAction<{ layerId: string; weight: number }>) => {
|
||||||
const { layerId, weight } = action.payload;
|
const { layerId, weight } = action.payload;
|
||||||
const layer = getCALayer(state, layerId);
|
const layer = selectCALayer(state, layerId);
|
||||||
layer.controlAdapter.weight = weight;
|
layer.controlAdapter.weight = weight;
|
||||||
},
|
},
|
||||||
caLayerBeginEndStepPctChanged: (
|
caLayerBeginEndStepPctChanged: (
|
||||||
@ -280,31 +282,31 @@ export const controlLayersSlice = createSlice({
|
|||||||
action: PayloadAction<{ layerId: string; beginEndStepPct: [number, number] }>
|
action: PayloadAction<{ layerId: string; beginEndStepPct: [number, number] }>
|
||||||
) => {
|
) => {
|
||||||
const { layerId, beginEndStepPct } = action.payload;
|
const { layerId, beginEndStepPct } = action.payload;
|
||||||
const layer = getCALayer(state, layerId);
|
const layer = selectCALayer(state, layerId);
|
||||||
layer.controlAdapter.beginEndStepPct = beginEndStepPct;
|
layer.controlAdapter.beginEndStepPct = beginEndStepPct;
|
||||||
},
|
},
|
||||||
caLayerControlModeChanged: (state, action: PayloadAction<{ layerId: string; controlMode: ControlMode }>) => {
|
caLayerControlModeChanged: (state, action: PayloadAction<{ layerId: string; controlMode: ControlMode }>) => {
|
||||||
const { layerId, controlMode } = action.payload;
|
const { layerId, controlMode } = action.payload;
|
||||||
const layer = getCALayer(state, layerId);
|
const layer = selectCALayer(state, layerId);
|
||||||
assert(layer.controlAdapter.type === 'controlnet');
|
assert(layer.controlAdapter.type === 'controlnet');
|
||||||
layer.controlAdapter.controlMode = controlMode;
|
layer.controlAdapter.controlMode = controlMode;
|
||||||
},
|
},
|
||||||
caLayerProcessorConfigChanged: (
|
caLayerProcessorConfigChanged: (
|
||||||
state,
|
state,
|
||||||
action: PayloadAction<{ layerId: string; processorConfig: ProcessorConfig }>
|
action: PayloadAction<{ layerId: string; processorConfig: ProcessorConfig | null }>
|
||||||
) => {
|
) => {
|
||||||
const { layerId, processorConfig } = action.payload;
|
const { layerId, processorConfig } = action.payload;
|
||||||
const layer = getCALayer(state, layerId);
|
const layer = selectCALayer(state, layerId);
|
||||||
layer.controlAdapter.processorConfig = processorConfig;
|
layer.controlAdapter.processorConfig = processorConfig;
|
||||||
},
|
},
|
||||||
caLayerIsFilterEnabledChanged: (state, action: PayloadAction<{ layerId: string; isFilterEnabled: boolean }>) => {
|
caLayerIsFilterEnabledChanged: (state, action: PayloadAction<{ layerId: string; isFilterEnabled: boolean }>) => {
|
||||||
const { layerId, isFilterEnabled } = action.payload;
|
const { layerId, isFilterEnabled } = action.payload;
|
||||||
const layer = getCALayer(state, layerId);
|
const layer = selectCALayer(state, layerId);
|
||||||
layer.isFilterEnabled = isFilterEnabled;
|
layer.isFilterEnabled = isFilterEnabled;
|
||||||
},
|
},
|
||||||
caLayerOpacityChanged: (state, action: PayloadAction<{ layerId: string; opacity: number }>) => {
|
caLayerOpacityChanged: (state, action: PayloadAction<{ layerId: string; opacity: number }>) => {
|
||||||
const { layerId, opacity } = action.payload;
|
const { layerId, opacity } = action.payload;
|
||||||
const layer = getCALayer(state, layerId);
|
const layer = selectCALayer(state, layerId);
|
||||||
layer.opacity = opacity;
|
layer.opacity = opacity;
|
||||||
},
|
},
|
||||||
//#endregion
|
//#endregion
|
||||||
@ -325,12 +327,12 @@ export const controlLayersSlice = createSlice({
|
|||||||
},
|
},
|
||||||
ipaLayerImageChanged: (state, action: PayloadAction<{ layerId: string; imageDTO: ImageDTO | null }>) => {
|
ipaLayerImageChanged: (state, action: PayloadAction<{ layerId: string; imageDTO: ImageDTO | null }>) => {
|
||||||
const { layerId, imageDTO } = action.payload;
|
const { layerId, imageDTO } = action.payload;
|
||||||
const layer = getIPALayer(state, layerId);
|
const layer = selectIPALayer(state, layerId);
|
||||||
layer.ipAdapter.image = imageDTO ? imageDTOToImageWithDims(imageDTO) : null;
|
layer.ipAdapter.image = imageDTO ? imageDTOToImageWithDims(imageDTO) : null;
|
||||||
},
|
},
|
||||||
ipaLayerWeightChanged: (state, action: PayloadAction<{ layerId: string; weight: number }>) => {
|
ipaLayerWeightChanged: (state, action: PayloadAction<{ layerId: string; weight: number }>) => {
|
||||||
const { layerId, weight } = action.payload;
|
const { layerId, weight } = action.payload;
|
||||||
const layer = getIPALayer(state, layerId);
|
const layer = selectIPALayer(state, layerId);
|
||||||
layer.ipAdapter.weight = weight;
|
layer.ipAdapter.weight = weight;
|
||||||
},
|
},
|
||||||
ipaLayerBeginEndStepPctChanged: (
|
ipaLayerBeginEndStepPctChanged: (
|
||||||
@ -338,12 +340,12 @@ export const controlLayersSlice = createSlice({
|
|||||||
action: PayloadAction<{ layerId: string; beginEndStepPct: [number, number] }>
|
action: PayloadAction<{ layerId: string; beginEndStepPct: [number, number] }>
|
||||||
) => {
|
) => {
|
||||||
const { layerId, beginEndStepPct } = action.payload;
|
const { layerId, beginEndStepPct } = action.payload;
|
||||||
const layer = getIPALayer(state, layerId);
|
const layer = selectIPALayer(state, layerId);
|
||||||
layer.ipAdapter.beginEndStepPct = beginEndStepPct;
|
layer.ipAdapter.beginEndStepPct = beginEndStepPct;
|
||||||
},
|
},
|
||||||
ipaLayerMethodChanged: (state, action: PayloadAction<{ layerId: string; method: IPMethod }>) => {
|
ipaLayerMethodChanged: (state, action: PayloadAction<{ layerId: string; method: IPMethod }>) => {
|
||||||
const { layerId, method } = action.payload;
|
const { layerId, method } = action.payload;
|
||||||
const layer = getIPALayer(state, layerId);
|
const layer = selectIPALayer(state, layerId);
|
||||||
layer.ipAdapter.method = method;
|
layer.ipAdapter.method = method;
|
||||||
},
|
},
|
||||||
ipaLayerCLIPVisionModelChanged: (
|
ipaLayerCLIPVisionModelChanged: (
|
||||||
@ -351,7 +353,7 @@ export const controlLayersSlice = createSlice({
|
|||||||
action: PayloadAction<{ layerId: string; clipVisionModel: CLIPVisionModel }>
|
action: PayloadAction<{ layerId: string; clipVisionModel: CLIPVisionModel }>
|
||||||
) => {
|
) => {
|
||||||
const { layerId, clipVisionModel } = action.payload;
|
const { layerId, clipVisionModel } = action.payload;
|
||||||
const layer = getIPALayer(state, layerId);
|
const layer = selectIPALayer(state, layerId);
|
||||||
layer.ipAdapter.clipVisionModel = clipVisionModel;
|
layer.ipAdapter.clipVisionModel = clipVisionModel;
|
||||||
},
|
},
|
||||||
//#endregion
|
//#endregion
|
||||||
@ -386,27 +388,27 @@ export const controlLayersSlice = createSlice({
|
|||||||
},
|
},
|
||||||
rgLayerPositivePromptChanged: (state, action: PayloadAction<{ layerId: string; prompt: string | null }>) => {
|
rgLayerPositivePromptChanged: (state, action: PayloadAction<{ layerId: string; prompt: string | null }>) => {
|
||||||
const { layerId, prompt } = action.payload;
|
const { layerId, prompt } = action.payload;
|
||||||
const layer = getRGLayer(state, layerId);
|
const layer = selectRGLayer(state, layerId);
|
||||||
layer.positivePrompt = prompt;
|
layer.positivePrompt = prompt;
|
||||||
},
|
},
|
||||||
rgLayerNegativePromptChanged: (state, action: PayloadAction<{ layerId: string; prompt: string | null }>) => {
|
rgLayerNegativePromptChanged: (state, action: PayloadAction<{ layerId: string; prompt: string | null }>) => {
|
||||||
const { layerId, prompt } = action.payload;
|
const { layerId, prompt } = action.payload;
|
||||||
const layer = getRGLayer(state, layerId);
|
const layer = selectRGLayer(state, layerId);
|
||||||
layer.negativePrompt = prompt;
|
layer.negativePrompt = prompt;
|
||||||
},
|
},
|
||||||
rgLayerIPAdapterAdded: (state, action: PayloadAction<{ layerId: string; ipAdapter: IPAdapterConfig }>) => {
|
rgLayerIPAdapterAdded: (state, action: PayloadAction<{ layerId: string; ipAdapter: IPAdapterConfig }>) => {
|
||||||
const { layerId, ipAdapter } = action.payload;
|
const { layerId, ipAdapter } = action.payload;
|
||||||
const layer = getRGLayer(state, layerId);
|
const layer = selectRGLayer(state, layerId);
|
||||||
layer.ipAdapters.push(ipAdapter);
|
layer.ipAdapters.push(ipAdapter);
|
||||||
},
|
},
|
||||||
rgLayerIPAdapterDeleted: (state, action: PayloadAction<{ layerId: string; ipAdapterId: string }>) => {
|
rgLayerIPAdapterDeleted: (state, action: PayloadAction<{ layerId: string; ipAdapterId: string }>) => {
|
||||||
const { layerId, ipAdapterId } = action.payload;
|
const { layerId, ipAdapterId } = action.payload;
|
||||||
const layer = getRGLayer(state, layerId);
|
const layer = selectRGLayer(state, layerId);
|
||||||
layer.ipAdapters = layer.ipAdapters.filter((ipAdapter) => ipAdapter.id !== ipAdapterId);
|
layer.ipAdapters = layer.ipAdapters.filter((ipAdapter) => ipAdapter.id !== ipAdapterId);
|
||||||
},
|
},
|
||||||
rgLayerPreviewColorChanged: (state, action: PayloadAction<{ layerId: string; color: RgbColor }>) => {
|
rgLayerPreviewColorChanged: (state, action: PayloadAction<{ layerId: string; color: RgbColor }>) => {
|
||||||
const { layerId, color } = action.payload;
|
const { layerId, color } = action.payload;
|
||||||
const layer = getRGLayer(state, layerId);
|
const layer = selectRGLayer(state, layerId);
|
||||||
layer.previewColor = color;
|
layer.previewColor = color;
|
||||||
},
|
},
|
||||||
rgLayerLineAdded: {
|
rgLayerLineAdded: {
|
||||||
@ -420,7 +422,7 @@ export const controlLayersSlice = createSlice({
|
|||||||
}>
|
}>
|
||||||
) => {
|
) => {
|
||||||
const { layerId, points, tool, lineUuid } = action.payload;
|
const { layerId, points, tool, lineUuid } = action.payload;
|
||||||
const layer = getRGLayer(state, layerId);
|
const layer = selectRGLayer(state, layerId);
|
||||||
const lineId = getRGLayerLineId(layer.id, lineUuid);
|
const lineId = getRGLayerLineId(layer.id, lineUuid);
|
||||||
layer.maskObjects.push({
|
layer.maskObjects.push({
|
||||||
type: 'vector_mask_line',
|
type: 'vector_mask_line',
|
||||||
@ -442,7 +444,7 @@ export const controlLayersSlice = createSlice({
|
|||||||
},
|
},
|
||||||
rgLayerPointsAdded: (state, action: PayloadAction<{ layerId: string; point: [number, number] }>) => {
|
rgLayerPointsAdded: (state, action: PayloadAction<{ layerId: string; point: [number, number] }>) => {
|
||||||
const { layerId, point } = action.payload;
|
const { layerId, point } = action.payload;
|
||||||
const layer = getRGLayer(state, layerId);
|
const layer = selectRGLayer(state, layerId);
|
||||||
const lastLine = layer.maskObjects.findLast(isLine);
|
const lastLine = layer.maskObjects.findLast(isLine);
|
||||||
if (!lastLine) {
|
if (!lastLine) {
|
||||||
return;
|
return;
|
||||||
@ -459,7 +461,7 @@ export const controlLayersSlice = createSlice({
|
|||||||
// Ignore zero-area rectangles
|
// Ignore zero-area rectangles
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
const layer = getRGLayer(state, layerId);
|
const layer = selectRGLayer(state, layerId);
|
||||||
const id = getRGLayerRectId(layer.id, rectUuid);
|
const id = getRGLayerRectId(layer.id, rectUuid);
|
||||||
layer.maskObjects.push({
|
layer.maskObjects.push({
|
||||||
type: 'vector_mask_rect',
|
type: 'vector_mask_rect',
|
||||||
@ -478,7 +480,7 @@ export const controlLayersSlice = createSlice({
|
|||||||
action: PayloadAction<{ layerId: string; autoNegative: ParameterAutoNegative }>
|
action: PayloadAction<{ layerId: string; autoNegative: ParameterAutoNegative }>
|
||||||
) => {
|
) => {
|
||||||
const { layerId, autoNegative } = action.payload;
|
const { layerId, autoNegative } = action.payload;
|
||||||
const layer = getRGLayer(state, layerId);
|
const layer = selectRGLayer(state, layerId);
|
||||||
layer.autoNegative = autoNegative;
|
layer.autoNegative = autoNegative;
|
||||||
},
|
},
|
||||||
rgLayerIPAdapterImageChanged: (
|
rgLayerIPAdapterImageChanged: (
|
||||||
@ -486,7 +488,7 @@ export const controlLayersSlice = createSlice({
|
|||||||
action: PayloadAction<{ layerId: string; ipAdapterId: string; imageDTO: ImageDTO | null }>
|
action: PayloadAction<{ layerId: string; ipAdapterId: string; imageDTO: ImageDTO | null }>
|
||||||
) => {
|
) => {
|
||||||
const { layerId, ipAdapterId, imageDTO } = action.payload;
|
const { layerId, ipAdapterId, imageDTO } = action.payload;
|
||||||
const ipAdapter = getRGLayerIPAdapter(state, layerId, ipAdapterId);
|
const ipAdapter = selectRGLayerIPAdapter(state, layerId, ipAdapterId);
|
||||||
ipAdapter.image = imageDTO ? imageDTOToImageWithDims(imageDTO) : null;
|
ipAdapter.image = imageDTO ? imageDTOToImageWithDims(imageDTO) : null;
|
||||||
},
|
},
|
||||||
rgLayerIPAdapterWeightChanged: (
|
rgLayerIPAdapterWeightChanged: (
|
||||||
@ -494,7 +496,7 @@ export const controlLayersSlice = createSlice({
|
|||||||
action: PayloadAction<{ layerId: string; ipAdapterId: string; weight: number }>
|
action: PayloadAction<{ layerId: string; ipAdapterId: string; weight: number }>
|
||||||
) => {
|
) => {
|
||||||
const { layerId, ipAdapterId, weight } = action.payload;
|
const { layerId, ipAdapterId, weight } = action.payload;
|
||||||
const ipAdapter = getRGLayerIPAdapter(state, layerId, ipAdapterId);
|
const ipAdapter = selectRGLayerIPAdapter(state, layerId, ipAdapterId);
|
||||||
ipAdapter.weight = weight;
|
ipAdapter.weight = weight;
|
||||||
},
|
},
|
||||||
rgLayerIPAdapterBeginEndStepPctChanged: (
|
rgLayerIPAdapterBeginEndStepPctChanged: (
|
||||||
@ -502,7 +504,7 @@ export const controlLayersSlice = createSlice({
|
|||||||
action: PayloadAction<{ layerId: string; ipAdapterId: string; beginEndStepPct: [number, number] }>
|
action: PayloadAction<{ layerId: string; ipAdapterId: string; beginEndStepPct: [number, number] }>
|
||||||
) => {
|
) => {
|
||||||
const { layerId, ipAdapterId, beginEndStepPct } = action.payload;
|
const { layerId, ipAdapterId, beginEndStepPct } = action.payload;
|
||||||
const ipAdapter = getRGLayerIPAdapter(state, layerId, ipAdapterId);
|
const ipAdapter = selectRGLayerIPAdapter(state, layerId, ipAdapterId);
|
||||||
ipAdapter.beginEndStepPct = beginEndStepPct;
|
ipAdapter.beginEndStepPct = beginEndStepPct;
|
||||||
},
|
},
|
||||||
rgLayerIPAdapterMethodChanged: (
|
rgLayerIPAdapterMethodChanged: (
|
||||||
@ -510,7 +512,7 @@ export const controlLayersSlice = createSlice({
|
|||||||
action: PayloadAction<{ layerId: string; ipAdapterId: string; method: IPMethod }>
|
action: PayloadAction<{ layerId: string; ipAdapterId: string; method: IPMethod }>
|
||||||
) => {
|
) => {
|
||||||
const { layerId, ipAdapterId, method } = action.payload;
|
const { layerId, ipAdapterId, method } = action.payload;
|
||||||
const ipAdapter = getRGLayerIPAdapter(state, layerId, ipAdapterId);
|
const ipAdapter = selectRGLayerIPAdapter(state, layerId, ipAdapterId);
|
||||||
ipAdapter.method = method;
|
ipAdapter.method = method;
|
||||||
},
|
},
|
||||||
rgLayerIPAdapterCLIPVisionModelChanged: (
|
rgLayerIPAdapterCLIPVisionModelChanged: (
|
||||||
@ -518,7 +520,7 @@ export const controlLayersSlice = createSlice({
|
|||||||
action: PayloadAction<{ layerId: string; ipAdapterId: string; clipVisionModel: CLIPVisionModel }>
|
action: PayloadAction<{ layerId: string; ipAdapterId: string; clipVisionModel: CLIPVisionModel }>
|
||||||
) => {
|
) => {
|
||||||
const { layerId, ipAdapterId, clipVisionModel } = action.payload;
|
const { layerId, ipAdapterId, clipVisionModel } = action.payload;
|
||||||
const ipAdapter = getRGLayerIPAdapter(state, layerId, ipAdapterId);
|
const ipAdapter = selectRGLayerIPAdapter(state, layerId, ipAdapterId);
|
||||||
ipAdapter.clipVisionModel = clipVisionModel;
|
ipAdapter.clipVisionModel = clipVisionModel;
|
||||||
},
|
},
|
||||||
//#endregion
|
//#endregion
|
||||||
|
@ -0,0 +1,14 @@
|
|||||||
|
import type { S } from 'services/api/types';
|
||||||
|
import type { Equals } from 'tsafe';
|
||||||
|
import { assert } from 'tsafe';
|
||||||
|
import { describe, test } from 'vitest';
|
||||||
|
|
||||||
|
import type { CLIPVisionModel, ControlMode, IPMethod, ProcessorConfig, ProcessorType } from './controlAdapters';
|
||||||
|
|
||||||
|
describe('Control Adapter Types', () => {
|
||||||
|
test('ProcessorType', () => assert<Equals<ProcessorConfig['type'], ProcessorType>>());
|
||||||
|
test('IP Adapter Method', () => assert<Equals<NonNullable<S['IPAdapterInvocation']['method']>, IPMethod>>());
|
||||||
|
test('CLIP Vision Model', () =>
|
||||||
|
assert<Equals<NonNullable<S['IPAdapterInvocation']['clip_vision_model']>, CLIPVisionModel>>());
|
||||||
|
test('Control Mode', () => assert<Equals<NonNullable<S['ControlNetInvocation']['control_mode']>, ControlMode>>());
|
||||||
|
});
|
@ -10,7 +10,6 @@ import type {
|
|||||||
CannyImageProcessorInvocation,
|
CannyImageProcessorInvocation,
|
||||||
ColorMapImageProcessorInvocation,
|
ColorMapImageProcessorInvocation,
|
||||||
ContentShuffleImageProcessorInvocation,
|
ContentShuffleImageProcessorInvocation,
|
||||||
ControlNetInvocation,
|
|
||||||
ControlNetModelConfig,
|
ControlNetModelConfig,
|
||||||
DepthAnythingImageProcessorInvocation,
|
DepthAnythingImageProcessorInvocation,
|
||||||
DWOpenposeImageProcessorInvocation,
|
DWOpenposeImageProcessorInvocation,
|
||||||
@ -88,7 +87,10 @@ type ControlAdapterBase = {
|
|||||||
processorConfig: ProcessorConfig | null;
|
processorConfig: ProcessorConfig | null;
|
||||||
beginEndStepPct: [number, number];
|
beginEndStepPct: [number, number];
|
||||||
};
|
};
|
||||||
export type ControlMode = NonNullable<ControlNetInvocation['control_mode']>;
|
|
||||||
|
const zControlMode = z.enum(['balanced', 'more_prompt', 'more_control', 'unbalanced']);
|
||||||
|
export type ControlMode = z.infer<typeof zControlMode>;
|
||||||
|
export const isControlMode = (v: unknown): v is ControlMode => zControlMode.safeParse(v).success;
|
||||||
|
|
||||||
export type ControlNetConfig = ControlAdapterBase & {
|
export type ControlNetConfig = ControlAdapterBase & {
|
||||||
type: 'controlnet';
|
type: 'controlnet';
|
||||||
@ -101,10 +103,11 @@ export type T2IAdapterConfig = ControlAdapterBase & {
|
|||||||
model: ParameterT2IAdapterModel | null;
|
model: ParameterT2IAdapterModel | null;
|
||||||
};
|
};
|
||||||
|
|
||||||
export type CLIPVisionModel = 'ViT-H' | 'ViT-G';
|
const zCLIPVisionModel = z.enum(['ViT-H', 'ViT-G']);
|
||||||
|
export type CLIPVisionModel = z.infer<typeof zCLIPVisionModel>;
|
||||||
|
export const isCLIPVisionModel = (v: unknown): v is CLIPVisionModel => zCLIPVisionModel.safeParse(v).success;
|
||||||
|
|
||||||
const zIPMethod = z.enum(['full', 'style', 'composition']);
|
const zIPMethod = z.enum(['full', 'style', 'composition']);
|
||||||
|
|
||||||
export type IPMethod = z.infer<typeof zIPMethod>;
|
export type IPMethod = z.infer<typeof zIPMethod>;
|
||||||
export const isIPMethod = (v: unknown): v is IPMethod => zIPMethod.safeParse(v).success;
|
export const isIPMethod = (v: unknown): v is IPMethod => zIPMethod.safeParse(v).success;
|
||||||
|
|
||||||
@ -270,7 +273,7 @@ export const CONTROLNET_PROCESSORS: ControlNetProcessorsDict = {
|
|||||||
type: 'zoe_depth_image_processor',
|
type: 'zoe_depth_image_processor',
|
||||||
}),
|
}),
|
||||||
},
|
},
|
||||||
};
|
}
|
||||||
export const zProcessorType = z.enum([
|
export const zProcessorType = z.enum([
|
||||||
'canny_image_processor',
|
'canny_image_processor',
|
||||||
'color_map_image_processor',
|
'color_map_image_processor',
|
||||||
@ -288,7 +291,7 @@ export const zProcessorType = z.enum([
|
|||||||
'zoe_depth_image_processor',
|
'zoe_depth_image_processor',
|
||||||
]);
|
]);
|
||||||
export type ProcessorType = z.infer<typeof zProcessorType>;
|
export type ProcessorType = z.infer<typeof zProcessorType>;
|
||||||
export const isControlAdapterProcessorType = (v: unknown): v is ProcessorType => zProcessorType.safeParse(v).success;
|
export const isProcessorType = (v: unknown): v is ProcessorType => zProcessorType.safeParse(v).success;
|
||||||
|
|
||||||
export const initialControlNet: Omit<ControlNetConfig, 'id'> = {
|
export const initialControlNet: Omit<ControlNetConfig, 'id'> = {
|
||||||
type: 'controlnet',
|
type: 'controlnet',
|
||||||
@ -340,7 +343,7 @@ export const buildControlAdapterProcessor = (
|
|||||||
modelConfig: ControlNetModelConfig | T2IAdapterModelConfig
|
modelConfig: ControlNetModelConfig | T2IAdapterModelConfig
|
||||||
): ProcessorConfig | null => {
|
): ProcessorConfig | null => {
|
||||||
const defaultPreprocessor = modelConfig.default_settings?.preprocessor;
|
const defaultPreprocessor = modelConfig.default_settings?.preprocessor;
|
||||||
if (!isControlAdapterProcessorType(defaultPreprocessor)) {
|
if (!isProcessorType(defaultPreprocessor)) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
const processorConfig = CONTROLNET_PROCESSORS[defaultPreprocessor].buildDefaults(modelConfig.base);
|
const processorConfig = CONTROLNET_PROCESSORS[defaultPreprocessor].buildDefaults(modelConfig.base);
|
||||||
|
@ -33,6 +33,13 @@ type ControlAdapterDropData = BaseDropData & {
|
|||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export type ControlLayerDropData = BaseDropData & {
|
||||||
|
actionType: 'SET_CONTROL_LAYER_IMAGE';
|
||||||
|
context: {
|
||||||
|
layerId: string;
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
export type CanvasInitialImageDropData = BaseDropData & {
|
export type CanvasInitialImageDropData = BaseDropData & {
|
||||||
actionType: 'SET_CANVAS_INITIAL_IMAGE';
|
actionType: 'SET_CANVAS_INITIAL_IMAGE';
|
||||||
};
|
};
|
||||||
@ -61,7 +68,8 @@ export type TypesafeDroppableData =
|
|||||||
| CanvasInitialImageDropData
|
| CanvasInitialImageDropData
|
||||||
| NodesImageDropData
|
| NodesImageDropData
|
||||||
| AddToBoardDropData
|
| AddToBoardDropData
|
||||||
| RemoveFromBoardDropData;
|
| RemoveFromBoardDropData
|
||||||
|
| ControlLayerDropData;
|
||||||
|
|
||||||
type BaseDragData = {
|
type BaseDragData = {
|
||||||
id: string;
|
id: string;
|
||||||
|
@ -4,6 +4,7 @@ import { modelConfigsAdapterSelectors, useGetModelConfigsQuery } from 'services/
|
|||||||
import type { AnyModelConfig } from 'services/api/types';
|
import type { AnyModelConfig } from 'services/api/types';
|
||||||
import {
|
import {
|
||||||
isControlNetModelConfig,
|
isControlNetModelConfig,
|
||||||
|
isControlNetOrT2IAdapterModelConfig,
|
||||||
isIPAdapterModelConfig,
|
isIPAdapterModelConfig,
|
||||||
isLoRAModelConfig,
|
isLoRAModelConfig,
|
||||||
isNonRefinerMainModelConfig,
|
isNonRefinerMainModelConfig,
|
||||||
@ -35,6 +36,7 @@ export const useNonSDXLMainModels = buildModelsHook(isNonSDXLMainModelConfig);
|
|||||||
export const useRefinerModels = buildModelsHook(isRefinerMainModelModelConfig);
|
export const useRefinerModels = buildModelsHook(isRefinerMainModelModelConfig);
|
||||||
export const useSDXLModels = buildModelsHook(isSDXLMainModelModelConfig);
|
export const useSDXLModels = buildModelsHook(isSDXLMainModelModelConfig);
|
||||||
export const useLoRAModels = buildModelsHook(isLoRAModelConfig);
|
export const useLoRAModels = buildModelsHook(isLoRAModelConfig);
|
||||||
|
export const useControlNetAndT2IAdapterModels = buildModelsHook(isControlNetOrT2IAdapterModelConfig);
|
||||||
export const useControlNetModels = buildModelsHook(isControlNetModelConfig);
|
export const useControlNetModels = buildModelsHook(isControlNetModelConfig);
|
||||||
export const useT2IAdapterModels = buildModelsHook(isT2IAdapterModelConfig);
|
export const useT2IAdapterModels = buildModelsHook(isT2IAdapterModelConfig);
|
||||||
export const useIPAdapterModels = buildModelsHook(isIPAdapterModelConfig);
|
export const useIPAdapterModels = buildModelsHook(isIPAdapterModelConfig);
|
||||||
|
@ -177,6 +177,11 @@ type ControlAdapterAction = {
|
|||||||
id: string;
|
id: string;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export type ControlLayerAction = {
|
||||||
|
type: 'SET_CONTROL_LAYER_IMAGE';
|
||||||
|
layerId: string;
|
||||||
|
};
|
||||||
|
|
||||||
type InitialImageAction = {
|
type InitialImageAction = {
|
||||||
type: 'SET_INITIAL_IMAGE';
|
type: 'SET_INITIAL_IMAGE';
|
||||||
};
|
};
|
||||||
@ -206,4 +211,5 @@ export type PostUploadAction =
|
|||||||
| NodesAction
|
| NodesAction
|
||||||
| CanvasInitialImageAction
|
| CanvasInitialImageAction
|
||||||
| ToastAction
|
| ToastAction
|
||||||
| AddToBatchAction;
|
| AddToBatchAction
|
||||||
|
| ControlLayerAction;
|
||||||
|
Loading…
x
Reference in New Issue
Block a user