mirror of
https://github.com/invoke-ai/InvokeAI
synced 2024-08-30 20:32:17 +00:00
refactor(ui): canvas v2 (wip)
This commit is contained in:
parent
8533f207dc
commit
28fc9a387c
@ -13,17 +13,17 @@ type Props = {
|
||||
export const CAEntity = memo(({ id }: Props) => {
|
||||
const dispatch = useAppDispatch();
|
||||
const isSelected = useAppSelector((s) => s.canvasV2.selectedEntityIdentifier?.id === id);
|
||||
const disclosure = useDisclosure({ defaultIsOpen: true });
|
||||
const { isOpen, onToggle } = useDisclosure({ defaultIsOpen: true });
|
||||
const onClick = useCallback(() => {
|
||||
dispatch(entitySelected({ id, type: 'control_adapter' }));
|
||||
}, [dispatch, id]);
|
||||
|
||||
return (
|
||||
<LayerWrapper onClick={onClick} borderColor={isSelected ? 'base.400' : 'base.800'}>
|
||||
<Flex gap={3} alignItems="center" p={3} cursor="pointer" onDoubleClick={disclosure.onToggle}>
|
||||
<Flex gap={3} alignItems="center" p={3} cursor="pointer" onDoubleClick={onToggle}>
|
||||
<CAHeaderItems id={id} />
|
||||
</Flex>
|
||||
{disclosure.isOpen && (
|
||||
{isOpen && (
|
||||
<Flex flexDir="column" gap={3} px={3} pb={3}>
|
||||
<CASettings id={id} />
|
||||
</Flex>
|
||||
|
@ -13,7 +13,7 @@ import {
|
||||
caMovedForwardOne,
|
||||
caMovedToBack,
|
||||
caMovedToFront,
|
||||
selectCA,
|
||||
selectCAOrThrow,
|
||||
selectControlAdaptersV2Slice,
|
||||
} from 'features/controlLayers/store/controlAdaptersSlice';
|
||||
import { memo, useCallback } from 'react';
|
||||
@ -25,7 +25,6 @@ import {
|
||||
PiArrowUpBold,
|
||||
PiTrashSimpleBold,
|
||||
} from 'react-icons/pi';
|
||||
import { assert } from 'tsafe';
|
||||
|
||||
type Props = {
|
||||
id: string;
|
||||
@ -34,8 +33,7 @@ type Props = {
|
||||
const selectValidActions = createAppSelector(
|
||||
[selectControlAdaptersV2Slice, (caState, id: string) => id],
|
||||
(caState, id) => {
|
||||
const ca = selectCA(caState, id);
|
||||
assert(ca, `CA with id ${id} not found`);
|
||||
const ca = selectCAOrThrow(caState, id);
|
||||
const caIndex = caState.controlAdapters.indexOf(ca);
|
||||
const caCount = caState.controlAdapters.length;
|
||||
return {
|
||||
@ -51,11 +49,7 @@ export const CAHeaderItems = memo(({ id }: Props) => {
|
||||
const { t } = useTranslation();
|
||||
const dispatch = useAppDispatch();
|
||||
const validActions = useAppSelector((s) => selectValidActions(s, id));
|
||||
const isEnabled = useAppSelector((s) => {
|
||||
const ca = selectCA(s.controlAdaptersV2, id);
|
||||
assert(ca, `CA with id ${id} not found`);
|
||||
return ca.isEnabled;
|
||||
});
|
||||
const isEnabled = useAppSelector((s) => selectCAOrThrow(s.controlAdaptersV2, id).isEnabled);
|
||||
const onToggle = useCallback(() => {
|
||||
dispatch(caIsEnabledToggled({ id }));
|
||||
}, [dispatch, id]);
|
||||
|
@ -1,14 +1,14 @@
|
||||
import { CannyProcessor } from 'features/controlLayers/components/ControlAndIPAdapter/processors/CannyProcessor';
|
||||
import { ColorMapProcessor } from 'features/controlLayers/components/ControlAndIPAdapter/processors/ColorMapProcessor';
|
||||
import { ContentShuffleProcessor } from 'features/controlLayers/components/ControlAndIPAdapter/processors/ContentShuffleProcessor';
|
||||
import { DepthAnythingProcessor } from 'features/controlLayers/components/ControlAndIPAdapter/processors/DepthAnythingProcessor';
|
||||
import { DWOpenposeProcessor } from 'features/controlLayers/components/ControlAndIPAdapter/processors/DWOpenposeProcessor';
|
||||
import { HedProcessor } from 'features/controlLayers/components/ControlAndIPAdapter/processors/HedProcessor';
|
||||
import { LineartProcessor } from 'features/controlLayers/components/ControlAndIPAdapter/processors/LineartProcessor';
|
||||
import { MediapipeFaceProcessor } from 'features/controlLayers/components/ControlAndIPAdapter/processors/MediapipeFaceProcessor';
|
||||
import { MidasDepthProcessor } from 'features/controlLayers/components/ControlAndIPAdapter/processors/MidasDepthProcessor';
|
||||
import { MlsdImageProcessor } from 'features/controlLayers/components/ControlAndIPAdapter/processors/MlsdImageProcessor';
|
||||
import { PidiProcessor } from 'features/controlLayers/components/ControlAndIPAdapter/processors/PidiProcessor';
|
||||
import { CannyProcessor } from 'features/controlLayers/components/ControlAdapter/processors/CannyProcessor';
|
||||
import { ColorMapProcessor } from 'features/controlLayers/components/ControlAdapter/processors/ColorMapProcessor';
|
||||
import { ContentShuffleProcessor } from 'features/controlLayers/components/ControlAdapter/processors/ContentShuffleProcessor';
|
||||
import { DepthAnythingProcessor } from 'features/controlLayers/components/ControlAdapter/processors/DepthAnythingProcessor';
|
||||
import { DWOpenposeProcessor } from 'features/controlLayers/components/ControlAdapter/processors/DWOpenposeProcessor';
|
||||
import { HedProcessor } from 'features/controlLayers/components/ControlAdapter/processors/HedProcessor';
|
||||
import { LineartProcessor } from 'features/controlLayers/components/ControlAdapter/processors/LineartProcessor';
|
||||
import { MediapipeFaceProcessor } from 'features/controlLayers/components/ControlAdapter/processors/MediapipeFaceProcessor';
|
||||
import { MidasDepthProcessor } from 'features/controlLayers/components/ControlAdapter/processors/MidasDepthProcessor';
|
||||
import { MlsdImageProcessor } from 'features/controlLayers/components/ControlAdapter/processors/MlsdImageProcessor';
|
||||
import { PidiProcessor } from 'features/controlLayers/components/ControlAdapter/processors/PidiProcessor';
|
||||
import type { ProcessorConfig } from 'features/controlLayers/store/types';
|
||||
import { memo } from 'react';
|
||||
|
||||
|
@ -1,7 +1,7 @@
|
||||
import { Box, Divider, Flex, Icon, IconButton } from '@invoke-ai/ui-library';
|
||||
import { useAppDispatch, useAppSelector } from 'app/store/storeHooks';
|
||||
import { BeginEndStepPct } from 'features/controlLayers/components/Common/BeginEndStepPct';
|
||||
import { Weight } from 'features/controlLayers/components/Common/Weight';
|
||||
import { BeginEndStepPct } from 'features/controlLayers/components/common/BeginEndStepPct';
|
||||
import { Weight } from 'features/controlLayers/components/common/Weight';
|
||||
import { CAControlModeSelect } from 'features/controlLayers/components/ControlAdapter/CAControlModeSelect';
|
||||
import { CAImagePreview } from 'features/controlLayers/components/ControlAdapter/CAImagePreview';
|
||||
import { CAModelCombobox } from 'features/controlLayers/components/ControlAdapter/CAModelCombobox';
|
||||
@ -15,6 +15,7 @@ import {
|
||||
caProcessedImageChanged,
|
||||
caProcessorConfigChanged,
|
||||
caWeightChanged,
|
||||
selectCAOrThrow,
|
||||
} from 'features/controlLayers/store/controlAdaptersSlice';
|
||||
import type { ControlModeV2, ProcessorConfig } from 'features/controlLayers/store/types';
|
||||
import type { CAImageDropData } from 'features/dnd/types';
|
||||
@ -28,7 +29,6 @@ import type {
|
||||
ImageDTO,
|
||||
T2IAdapterModelConfig,
|
||||
} from 'services/api/types';
|
||||
import { assert } from 'tsafe';
|
||||
|
||||
type Props = {
|
||||
id: string;
|
||||
@ -39,11 +39,7 @@ export const CASettings = memo(({ id }: Props) => {
|
||||
const { t } = useTranslation();
|
||||
const [isExpanded, toggleIsExpanded] = useToggle(false);
|
||||
|
||||
const controlAdapter = useAppSelector((s) => {
|
||||
const ca = s.controlAdaptersV2.controlAdapters.find((ca) => ca.id === id);
|
||||
assert(ca, `ControlAdapter with id ${id} not found`);
|
||||
return ca;
|
||||
});
|
||||
const controlAdapter = useAppSelector((s) => selectCAOrThrow(s.controlAdaptersV2, id));
|
||||
|
||||
const onChangeBeginEndStepPct = useCallback(
|
||||
(beginEndStepPct: [number, number]) => {
|
||||
|
@ -1,6 +1,7 @@
|
||||
import { CompositeNumberInput, CompositeSlider, FormControl, FormLabel } from '@invoke-ai/ui-library';
|
||||
import type { ProcessorComponentProps } from 'features/controlLayers/components/ControlAndIPAdapter/processors/types';
|
||||
import { CA_PROCESSOR_DATA, type CannyProcessorConfig } from 'features/controlLayers/util/controlAdapters';
|
||||
import type { ProcessorComponentProps } from 'features/controlLayers/components/ControlAdapter/processors/types';
|
||||
import type { CannyProcessorConfig } from 'features/controlLayers/store/types';
|
||||
import { CA_PROCESSOR_DATA } from 'features/controlLayers/store/types';
|
||||
import { useCallback } from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
|
||||
|
@ -1,6 +1,7 @@
|
||||
import { CompositeNumberInput, CompositeSlider, FormControl, FormLabel } from '@invoke-ai/ui-library';
|
||||
import type { ProcessorComponentProps } from 'features/controlLayers/components/ControlAndIPAdapter/processors/types';
|
||||
import { CA_PROCESSOR_DATA, type ColorMapProcessorConfig } from 'features/controlLayers/util/controlAdapters';
|
||||
import type { ProcessorComponentProps } from 'features/controlLayers/components/ControlAdapter/processors/types';
|
||||
import type { ColorMapProcessorConfig } from 'features/controlLayers/store/types';
|
||||
import { CA_PROCESSOR_DATA } from 'features/controlLayers/store/types';
|
||||
import { memo, useCallback } from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
|
||||
|
@ -1,7 +1,7 @@
|
||||
import { CompositeNumberInput, CompositeSlider, FormControl, FormLabel } from '@invoke-ai/ui-library';
|
||||
import type { ProcessorComponentProps } from 'features/controlLayers/components/ControlAndIPAdapter/processors/types';
|
||||
import type { ContentShuffleProcessorConfig } from 'features/controlLayers/util/controlAdapters';
|
||||
import { CA_PROCESSOR_DATA } from 'features/controlLayers/util/controlAdapters';
|
||||
import type { ProcessorComponentProps } from 'features/controlLayers/components/ControlAdapter/processors/types';
|
||||
import type { ContentShuffleProcessorConfig } from 'features/controlLayers/store/types';
|
||||
import { CA_PROCESSOR_DATA } from 'features/controlLayers/store/types';
|
||||
import { memo, useCallback } from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
|
||||
|
@ -1,7 +1,7 @@
|
||||
import { Flex, FormControl, FormLabel, Switch } from '@invoke-ai/ui-library';
|
||||
import type { ProcessorComponentProps } from 'features/controlLayers/components/ControlAndIPAdapter/processors/types';
|
||||
import type { DWOpenposeProcessorConfig } from 'features/controlLayers/util/controlAdapters';
|
||||
import { CA_PROCESSOR_DATA } from 'features/controlLayers/util/controlAdapters';
|
||||
import type { ProcessorComponentProps } from 'features/controlLayers/components/ControlAdapter/processors/types';
|
||||
import type { DWOpenposeProcessorConfig } from 'features/controlLayers/store/types';
|
||||
import { CA_PROCESSOR_DATA } from 'features/controlLayers/store/types';
|
||||
import type { ChangeEvent } from 'react';
|
||||
import { memo, useCallback } from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
|
@ -1,8 +1,8 @@
|
||||
import type { ComboboxOnChange } from '@invoke-ai/ui-library';
|
||||
import { Combobox, FormControl, FormLabel } from '@invoke-ai/ui-library';
|
||||
import type { ProcessorComponentProps } from 'features/controlLayers/components/ControlAndIPAdapter/processors/types';
|
||||
import type { DepthAnythingModelSize, DepthAnythingProcessorConfig } from 'features/controlLayers/util/controlAdapters';
|
||||
import { isDepthAnythingModelSize } from 'features/controlLayers/util/controlAdapters';
|
||||
import type { ProcessorComponentProps } from 'features/controlLayers/components/ControlAdapter/processors/types';
|
||||
import type { DepthAnythingModelSize, DepthAnythingProcessorConfig } from 'features/controlLayers/store/types';
|
||||
import { isDepthAnythingModelSize } from 'features/controlLayers/store/types';
|
||||
import { memo, useCallback, useMemo } from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
|
||||
|
@ -1,6 +1,6 @@
|
||||
import { FormControl, FormLabel, Switch } from '@invoke-ai/ui-library';
|
||||
import type { ProcessorComponentProps } from 'features/controlLayers/components/ControlAndIPAdapter/processors/types';
|
||||
import type { HedProcessorConfig } from 'features/controlLayers/util/controlAdapters';
|
||||
import type { ProcessorComponentProps } from 'features/controlLayers/components/ControlAdapter/processors/types';
|
||||
import type { HedProcessorConfig } from 'features/controlLayers/store/types';
|
||||
import type { ChangeEvent } from 'react';
|
||||
import { memo, useCallback } from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
|
@ -1,6 +1,6 @@
|
||||
import { FormControl, FormLabel, Switch } from '@invoke-ai/ui-library';
|
||||
import type { ProcessorComponentProps } from 'features/controlLayers/components/ControlAndIPAdapter/processors/types';
|
||||
import type { LineartProcessorConfig } from 'features/controlLayers/util/controlAdapters';
|
||||
import type { ProcessorComponentProps } from 'features/controlLayers/components/ControlAdapter/processors/types';
|
||||
import type { LineartProcessorConfig } from 'features/controlLayers/store/types';
|
||||
import type { ChangeEvent } from 'react';
|
||||
import { memo, useCallback } from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
|
@ -1,6 +1,7 @@
|
||||
import { CompositeNumberInput, CompositeSlider, FormControl, FormLabel } from '@invoke-ai/ui-library';
|
||||
import type { ProcessorComponentProps } from 'features/controlLayers/components/ControlAndIPAdapter/processors/types';
|
||||
import { CA_PROCESSOR_DATA, type MediapipeFaceProcessorConfig } from 'features/controlLayers/util/controlAdapters';
|
||||
import type { ProcessorComponentProps } from 'features/controlLayers/components/ControlAdapter/processors/types';
|
||||
import type { MediapipeFaceProcessorConfig } from 'features/controlLayers/store/types';
|
||||
import { CA_PROCESSOR_DATA } from 'features/controlLayers/store/types';
|
||||
import { memo, useCallback } from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
|
||||
|
@ -1,7 +1,7 @@
|
||||
import { CompositeNumberInput, CompositeSlider, FormControl, FormLabel } from '@invoke-ai/ui-library';
|
||||
import type { ProcessorComponentProps } from 'features/controlLayers/components/ControlAndIPAdapter/processors/types';
|
||||
import type { MidasDepthProcessorConfig } from 'features/controlLayers/util/controlAdapters';
|
||||
import { CA_PROCESSOR_DATA } from 'features/controlLayers/util/controlAdapters';
|
||||
import type { ProcessorComponentProps } from 'features/controlLayers/components/ControlAdapter/processors/types';
|
||||
import type { MidasDepthProcessorConfig } from 'features/controlLayers/store/types';
|
||||
import { CA_PROCESSOR_DATA } from 'features/controlLayers/store/types';
|
||||
import { memo, useCallback } from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
|
||||
|
@ -1,7 +1,7 @@
|
||||
import { CompositeNumberInput, CompositeSlider, FormControl, FormLabel } from '@invoke-ai/ui-library';
|
||||
import type { ProcessorComponentProps } from 'features/controlLayers/components/ControlAndIPAdapter/processors/types';
|
||||
import type { MlsdProcessorConfig } from 'features/controlLayers/util/controlAdapters';
|
||||
import { CA_PROCESSOR_DATA } from 'features/controlLayers/util/controlAdapters';
|
||||
import type { ProcessorComponentProps } from 'features/controlLayers/components/ControlAdapter/processors/types';
|
||||
import type { MlsdProcessorConfig } from 'features/controlLayers/store/types';
|
||||
import { CA_PROCESSOR_DATA } from 'features/controlLayers/store/types';
|
||||
import { memo, useCallback } from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
|
||||
|
@ -1,6 +1,6 @@
|
||||
import { FormControl, FormLabel, Switch } from '@invoke-ai/ui-library';
|
||||
import type { ProcessorComponentProps } from 'features/controlLayers/components/ControlAndIPAdapter/processors/types';
|
||||
import type { PidiProcessorConfig } from 'features/controlLayers/util/controlAdapters';
|
||||
import type { ProcessorComponentProps } from 'features/controlLayers/components/ControlAdapter/processors/types';
|
||||
import type { PidiProcessorConfig } from 'features/controlLayers/store/types';
|
||||
import type { ChangeEvent } from 'react';
|
||||
import { useCallback } from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
|
@ -1,4 +1,4 @@
|
||||
import type { ProcessorConfig } from 'features/controlLayers/util/controlAdapters';
|
||||
import type { ProcessorConfig } from 'features/controlLayers/store/types';
|
||||
|
||||
export type ProcessorComponentProps<T extends ProcessorConfig> = {
|
||||
onChange: (config: T) => void;
|
||||
|
@ -1,72 +0,0 @@
|
||||
import { Box, Flex } from '@invoke-ai/ui-library';
|
||||
import { BeginEndStepPct } from 'features/controlLayers/components/ControlAndIPAdapter/ControlAdapterBeginEndStepPct';
|
||||
import { ControlAdapterWeight } from 'features/controlLayers/components/ControlAndIPAdapter/ControlAdapterWeight';
|
||||
import { IPAdapterImagePreview } from 'features/controlLayers/components/ControlAndIPAdapter/IPAdapterImagePreview';
|
||||
import { IPAdapterMethod } from 'features/controlLayers/components/ControlAndIPAdapter/IPAdapterMethod';
|
||||
import { IPAdapterModelSelect } from 'features/controlLayers/components/ControlAndIPAdapter/IPAdapterModelSelect';
|
||||
import type { CLIPVisionModelV2, IPAdapterConfigV2, IPMethodV2 } from 'features/controlLayers/util/controlAdapters';
|
||||
import type { TypesafeDroppableData } from 'features/dnd/types';
|
||||
import { memo } from 'react';
|
||||
import type { ImageDTO, IPAdapterModelConfig, PostUploadAction } from 'services/api/types';
|
||||
|
||||
type Props = {
|
||||
ipAdapter: IPAdapterConfigV2;
|
||||
onChangeBeginEndStepPct: (beginEndStepPct: [number, number]) => void;
|
||||
onChangeWeight: (weight: number) => void;
|
||||
onChangeIPMethod: (method: IPMethodV2) => void;
|
||||
onChangeModel: (modelConfig: IPAdapterModelConfig) => void;
|
||||
onChangeCLIPVisionModel: (clipVisionModel: CLIPVisionModelV2) => void;
|
||||
onChangeImage: (imageDTO: ImageDTO | null) => void;
|
||||
droppableData: TypesafeDroppableData;
|
||||
postUploadAction: PostUploadAction;
|
||||
};
|
||||
|
||||
export const IPAdapter = memo(
|
||||
({
|
||||
ipAdapter,
|
||||
onChangeBeginEndStepPct,
|
||||
onChangeWeight,
|
||||
onChangeIPMethod,
|
||||
onChangeModel,
|
||||
onChangeCLIPVisionModel,
|
||||
onChangeImage,
|
||||
droppableData,
|
||||
postUploadAction,
|
||||
}: Props) => {
|
||||
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">
|
||||
<IPAdapterModelSelect
|
||||
modelKey={ipAdapter.model?.key ?? null}
|
||||
onChangeModel={onChangeModel}
|
||||
clipVisionModel={ipAdapter.clipVisionModel}
|
||||
onChangeCLIPVisionModel={onChangeCLIPVisionModel}
|
||||
/>
|
||||
</Box>
|
||||
</Flex>
|
||||
<Flex gap={4} w="full" alignItems="center">
|
||||
<Flex flexDir="column" gap={3} w="full">
|
||||
<IPAdapterMethod method={ipAdapter.method} onChange={onChangeIPMethod} />
|
||||
<ControlAdapterWeight weight={ipAdapter.weight} onChange={onChangeWeight} />
|
||||
<BeginEndStepPct
|
||||
beginEndStepPct={ipAdapter.beginEndStepPct}
|
||||
onChange={onChangeBeginEndStepPct}
|
||||
/>
|
||||
</Flex>
|
||||
<Flex alignItems="center" justifyContent="center" h={36} w={36} aspectRatio="1/1">
|
||||
<IPAdapterImagePreview
|
||||
image={ipAdapter.image}
|
||||
onChangeImage={onChangeImage}
|
||||
ipAdapterId={ipAdapter.id}
|
||||
droppableData={droppableData}
|
||||
postUploadAction={postUploadAction}
|
||||
/>
|
||||
</Flex>
|
||||
</Flex>
|
||||
</Flex>
|
||||
);
|
||||
}
|
||||
);
|
||||
|
||||
IPAdapter.displayName = 'IPAdapter';
|
@ -1,113 +0,0 @@
|
||||
import { Flex, useShiftModifier } from '@invoke-ai/ui-library';
|
||||
import { skipToken } from '@reduxjs/toolkit/query';
|
||||
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 { heightChanged, widthChanged } from 'features/controlLayers/store/controlLayersSlice';
|
||||
import type { ImageWithDims } from 'features/controlLayers/util/controlAdapters';
|
||||
import type { ImageDraggableData, 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 } from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { PiArrowCounterClockwiseBold, PiRulerBold } from 'react-icons/pi';
|
||||
import { useGetImageDTOQuery } from 'services/api/endpoints/images';
|
||||
import type { ImageDTO, PostUploadAction } from 'services/api/types';
|
||||
|
||||
type Props = {
|
||||
image: ImageWithDims | null;
|
||||
onChangeImage: (imageDTO: ImageDTO | null) => void;
|
||||
ipAdapterId: string; // required for the dnd/upload interactions
|
||||
droppableData: TypesafeDroppableData;
|
||||
postUploadAction: PostUploadAction;
|
||||
};
|
||||
|
||||
export const IPAdapterImagePreview = memo(
|
||||
({ image, onChangeImage, ipAdapterId, droppableData, postUploadAction }: Props) => {
|
||||
const { t } = useTranslation();
|
||||
const dispatch = useAppDispatch();
|
||||
const isConnected = useAppSelector((s) => s.system.isConnected);
|
||||
const activeTabName = useAppSelector(activeTabNameSelector);
|
||||
const optimalDimension = useAppSelector(selectOptimalDimension);
|
||||
const shift = useShiftModifier();
|
||||
|
||||
const { currentData: controlImage, isError: isErrorControlImage } = useGetImageDTOQuery(image?.name ?? skipToken);
|
||||
const handleResetControlImage = useCallback(() => {
|
||||
onChangeImage(null);
|
||||
}, [onChangeImage]);
|
||||
|
||||
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 };
|
||||
if (shift) {
|
||||
const { width, height } = controlImage;
|
||||
dispatch(widthChanged({ width, ...options }));
|
||||
dispatch(heightChanged({ height, ...options }));
|
||||
} else {
|
||||
const { width, height } = calculateNewSize(
|
||||
controlImage.width / controlImage.height,
|
||||
optimalDimension * optimalDimension
|
||||
);
|
||||
dispatch(widthChanged({ width, ...options }));
|
||||
dispatch(heightChanged({ height, ...options }));
|
||||
}
|
||||
}
|
||||
}, [controlImage, activeTabName, dispatch, optimalDimension, shift]);
|
||||
|
||||
const draggableData = useMemo<ImageDraggableData | undefined>(() => {
|
||||
if (controlImage) {
|
||||
return {
|
||||
id: ipAdapterId,
|
||||
payloadType: 'IMAGE_DTO',
|
||||
payload: { imageDTO: controlImage },
|
||||
};
|
||||
}
|
||||
}, [controlImage, ipAdapterId]);
|
||||
|
||||
useEffect(() => {
|
||||
if (isConnected && isErrorControlImage) {
|
||||
handleResetControlImage();
|
||||
}
|
||||
}, [handleResetControlImage, isConnected, isErrorControlImage]);
|
||||
|
||||
return (
|
||||
<Flex position="relative" w={36} h={36} alignItems="center">
|
||||
<IAIDndImage
|
||||
draggableData={draggableData}
|
||||
droppableData={droppableData}
|
||||
imageDTO={controlImage}
|
||||
postUploadAction={postUploadAction}
|
||||
/>
|
||||
|
||||
{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={handleSetControlImageToDimensions}
|
||||
icon={<PiRulerBold size={16} />}
|
||||
tooltip={
|
||||
shift ? t('controlnet.setControlImageDimensionsForce') : t('controlnet.setControlImageDimensions')
|
||||
}
|
||||
/>
|
||||
</Flex>
|
||||
)}
|
||||
</Flex>
|
||||
);
|
||||
}
|
||||
);
|
||||
|
||||
IPAdapterImagePreview.displayName = 'IPAdapterImagePreview';
|
@ -1,100 +0,0 @@
|
||||
import type { ComboboxOnChange } from '@invoke-ai/ui-library';
|
||||
import { Combobox, Flex, FormControl, Tooltip } from '@invoke-ai/ui-library';
|
||||
import { useAppSelector } from 'app/store/storeHooks';
|
||||
import { useGroupedModelCombobox } from 'common/hooks/useGroupedModelCombobox';
|
||||
import type { CLIPVisionModelV2 } from 'features/controlLayers/util/controlAdapters';
|
||||
import { isCLIPVisionModelV2 } from 'features/controlLayers/util/controlAdapters';
|
||||
import { memo, useCallback, useMemo } from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { useIPAdapterModels } from 'services/api/hooks/modelsByType';
|
||||
import type { AnyModelConfig, IPAdapterModelConfig } from 'services/api/types';
|
||||
import { assert } from 'tsafe';
|
||||
|
||||
const CLIP_VISION_OPTIONS = [
|
||||
{ label: 'ViT-H', value: 'ViT-H' },
|
||||
{ label: 'ViT-G', value: 'ViT-G' },
|
||||
];
|
||||
|
||||
type Props = {
|
||||
modelKey: string | null;
|
||||
onChangeModel: (modelConfig: IPAdapterModelConfig) => void;
|
||||
clipVisionModel: CLIPVisionModelV2;
|
||||
onChangeCLIPVisionModel: (clipVisionModel: CLIPVisionModelV2) => void;
|
||||
};
|
||||
|
||||
export const IPAdapterModelSelect = memo(
|
||||
({ modelKey, onChangeModel, clipVisionModel, onChangeCLIPVisionModel }: Props) => {
|
||||
const { t } = useTranslation();
|
||||
const currentBaseModel = useAppSelector((s) => s.generation.model?.base);
|
||||
const [modelConfigs, { isLoading }] = useIPAdapterModels();
|
||||
const selectedModel = useMemo(() => modelConfigs.find((m) => m.key === modelKey), [modelConfigs, modelKey]);
|
||||
|
||||
const _onChangeModel = useCallback(
|
||||
(modelConfig: IPAdapterModelConfig | null) => {
|
||||
if (!modelConfig) {
|
||||
return;
|
||||
}
|
||||
onChangeModel(modelConfig);
|
||||
},
|
||||
[onChangeModel]
|
||||
);
|
||||
|
||||
const _onChangeCLIPVisionModel = useCallback<ComboboxOnChange>(
|
||||
(v) => {
|
||||
assert(isCLIPVisionModelV2(v?.value));
|
||||
onChangeCLIPVisionModel(v.value);
|
||||
},
|
||||
[onChangeCLIPVisionModel]
|
||||
);
|
||||
|
||||
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: _onChangeModel,
|
||||
selectedModel,
|
||||
getIsDisabled,
|
||||
isLoading,
|
||||
});
|
||||
|
||||
const clipVisionModelValue = useMemo(
|
||||
() => CLIP_VISION_OPTIONS.find((o) => o.value === clipVisionModel),
|
||||
[clipVisionModel]
|
||||
);
|
||||
|
||||
return (
|
||||
<Flex gap={4}>
|
||||
<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>
|
||||
{selectedModel?.format === 'checkpoint' && (
|
||||
<FormControl isInvalid={!value || currentBaseModel !== selectedModel?.base} width="max-content" minWidth={28}>
|
||||
<Combobox
|
||||
options={CLIP_VISION_OPTIONS}
|
||||
placeholder={t('controlnet.selectCLIPVisionModel')}
|
||||
value={clipVisionModelValue}
|
||||
onChange={_onChangeCLIPVisionModel}
|
||||
/>
|
||||
</FormControl>
|
||||
)}
|
||||
</Flex>
|
||||
);
|
||||
}
|
||||
);
|
||||
|
||||
IPAdapterModelSelect.displayName = 'IPAdapterModelSelect';
|
@ -7,7 +7,6 @@ import { memo } from 'react';
|
||||
|
||||
export const HeadsUpDisplay = memo(() => {
|
||||
const stageAttrs = useStore($stageAttrs);
|
||||
const layerCount = useAppSelector((s) => s.canvasV2.layers.length);
|
||||
const bbox = useAppSelector((s) => s.canvasV2.bbox);
|
||||
|
||||
return (
|
||||
@ -15,7 +14,6 @@ export const HeadsUpDisplay = memo(() => {
|
||||
<HUDItem label="Scale" value={round(stageAttrs.scale, 3)} />
|
||||
<HUDItem label="Stage Pos" value={`${round(stageAttrs.x, 3)}, ${round(stageAttrs.y, 3)}`} />
|
||||
<HUDItem label="Stage Size" value={`${round(stageAttrs.width, 3)}, ${round(stageAttrs.height, 3)}`} />
|
||||
<HUDItem label="Layer Count" value={layerCount} />
|
||||
<HUDItem label="BBox Size" value={`${bbox.width}×${bbox.height}`} />
|
||||
<HUDItem label="BBox Position" value={`${bbox.x}, ${bbox.y}`} />
|
||||
<HUDItem label="BBox Width % 8" value={round(bbox.width % 8, 3)} />
|
||||
|
@ -1,109 +0,0 @@
|
||||
import { useAppDispatch, useAppSelector } from 'app/store/storeHooks';
|
||||
import { IPAdapter } from 'features/controlLayers/components/ControlAndIPAdapter/IPAdapter';
|
||||
import {
|
||||
caOrIPALayerBeginEndStepPctChanged,
|
||||
caOrIPALayerWeightChanged,
|
||||
ipAdapterCLIPVisionModelChanged,
|
||||
ipAdapterImageChanged,
|
||||
ipAdapterMethodChanged,
|
||||
ipAdapterModelChanged,
|
||||
selectLayerOrThrow,
|
||||
} from 'features/controlLayers/store/controlLayersSlice';
|
||||
import { isIPAdapterLayer } from 'features/controlLayers/store/types';
|
||||
import type { CLIPVisionModelV2, IPMethodV2 } from 'features/controlLayers/util/controlAdapters';
|
||||
import type { IPAImageDropData } from 'features/dnd/types';
|
||||
import { memo, useCallback, useMemo } from 'react';
|
||||
import type { ImageDTO, IPAdapterModelConfig, IPALayerImagePostUploadAction } from 'services/api/types';
|
||||
|
||||
type Props = {
|
||||
layerId: string;
|
||||
};
|
||||
|
||||
export const IPALayerIPAdapterWrapper = memo(({ layerId }: Props) => {
|
||||
const dispatch = useAppDispatch();
|
||||
const ipAdapter = useAppSelector(
|
||||
(s) => selectLayerOrThrow(s.canvasV2, layerId, isIPAdapterLayer).ipAdapter
|
||||
);
|
||||
|
||||
const onChangeBeginEndStepPct = useCallback(
|
||||
(beginEndStepPct: [number, number]) => {
|
||||
dispatch(
|
||||
caOrIPALayerBeginEndStepPctChanged({
|
||||
layerId,
|
||||
beginEndStepPct,
|
||||
})
|
||||
);
|
||||
},
|
||||
[dispatch, layerId]
|
||||
);
|
||||
|
||||
const onChangeWeight = useCallback(
|
||||
(weight: number) => {
|
||||
dispatch(caOrIPALayerWeightChanged({ layerId, weight }));
|
||||
},
|
||||
[dispatch, layerId]
|
||||
);
|
||||
|
||||
const onChangeIPMethod = useCallback(
|
||||
(method: IPMethodV2) => {
|
||||
dispatch(ipAdapterMethodChanged({ layerId, method }));
|
||||
},
|
||||
[dispatch, layerId]
|
||||
);
|
||||
|
||||
const onChangeModel = useCallback(
|
||||
(modelConfig: IPAdapterModelConfig) => {
|
||||
dispatch(ipAdapterModelChanged({ layerId, modelConfig }));
|
||||
},
|
||||
[dispatch, layerId]
|
||||
);
|
||||
|
||||
const onChangeCLIPVisionModel = useCallback(
|
||||
(clipVisionModel: CLIPVisionModelV2) => {
|
||||
dispatch(ipAdapterCLIPVisionModelChanged({ layerId, clipVisionModel }));
|
||||
},
|
||||
[dispatch, layerId]
|
||||
);
|
||||
|
||||
const onChangeImage = useCallback(
|
||||
(imageDTO: ImageDTO | null) => {
|
||||
dispatch(ipAdapterImageChanged({ layerId, imageDTO }));
|
||||
},
|
||||
[dispatch, layerId]
|
||||
);
|
||||
|
||||
const droppableData = useMemo<IPAImageDropData>(
|
||||
() => ({
|
||||
actionType: 'SET_IPA_LAYER_IMAGE',
|
||||
context: {
|
||||
layerId,
|
||||
},
|
||||
id: layerId,
|
||||
}),
|
||||
[layerId]
|
||||
);
|
||||
|
||||
const postUploadAction = useMemo<IPALayerImagePostUploadAction>(
|
||||
() => ({
|
||||
type: 'SET_IPA_LAYER_IMAGE',
|
||||
layerId,
|
||||
}),
|
||||
[layerId]
|
||||
);
|
||||
|
||||
return (
|
||||
<IPAdapter
|
||||
ipAdapter={ipAdapter}
|
||||
onChangeBeginEndStepPct={onChangeBeginEndStepPct}
|
||||
onChangeWeight={onChangeWeight}
|
||||
onChangeIPMethod={onChangeIPMethod}
|
||||
onChangeModel={onChangeModel}
|
||||
onChangeCLIPVisionModel={onChangeCLIPVisionModel}
|
||||
onChangeImage={onChangeImage}
|
||||
droppableData={droppableData}
|
||||
postUploadAction={postUploadAction}
|
||||
/>
|
||||
);
|
||||
});
|
||||
|
||||
IPALayerIPAdapterWrapper.displayName = 'IPALayerIPAdapterWrapper';
|
@ -1,9 +1,7 @@
|
||||
import { Flex, Spacer, useDisclosure } from '@invoke-ai/ui-library';
|
||||
import { Flex, useDisclosure } from '@invoke-ai/ui-library';
|
||||
import { useAppDispatch, useAppSelector } from 'app/store/storeHooks';
|
||||
import { IPALayerIPAdapterWrapper } from 'features/controlLayers/components/IPALayer/IPALayerIPAdapterWrapper';
|
||||
import { LayerDeleteButton } from 'features/controlLayers/components/LayerCommon/LayerDeleteButton';
|
||||
import { EntityTitle } from 'features/controlLayers/components/LayerCommon/LayerTitle';
|
||||
import { EntityEnabledToggle } from 'features/controlLayers/components/LayerCommon/LayerVisibilityToggle';
|
||||
import { IPAHeaderItems } from 'features/controlLayers/components/IPAdapter/IPAHeaderItems';
|
||||
import { IPASettings } from 'features/controlLayers/components/IPAdapter/IPASettings';
|
||||
import { LayerWrapper } from 'features/controlLayers/components/LayerCommon/LayerWrapper';
|
||||
import { entitySelected } from 'features/controlLayers/store/controlLayersSlice';
|
||||
import { memo, useCallback } from 'react';
|
||||
@ -23,14 +21,11 @@ export const IPAEntity = memo(({ id }: Props) => {
|
||||
return (
|
||||
<LayerWrapper onClick={onClick} borderColor={isSelected ? 'base.400' : 'base.800'}>
|
||||
<Flex gap={3} alignItems="center" p={3} cursor="pointer" onDoubleClick={onToggle}>
|
||||
<EntityEnabledToggle id={id} />
|
||||
<EntityTitle type="ip_adapter" />
|
||||
<Spacer />
|
||||
<LayerDeleteButton id={id} />
|
||||
<IPAHeaderItems id={id} />
|
||||
</Flex>
|
||||
{isOpen && (
|
||||
<Flex flexDir="column" gap={3} px={3} pb={3}>
|
||||
<IPALayerIPAdapterWrapper id={id} />
|
||||
<IPASettings id={id} />
|
||||
</Flex>
|
||||
)}
|
||||
</LayerWrapper>
|
@ -0,0 +1,39 @@
|
||||
import { Spacer } from '@invoke-ai/ui-library';
|
||||
import { useAppDispatch, useAppSelector } from 'app/store/storeHooks';
|
||||
import { EntityDeleteButton } from 'features/controlLayers/components/LayerCommon/EntityDeleteButton';
|
||||
import { EntityEnabledToggle } from 'features/controlLayers/components/LayerCommon/EntityEnabledToggle';
|
||||
import { EntityTitle } from 'features/controlLayers/components/LayerCommon/EntityTitle';
|
||||
import {
|
||||
ipaDeleted,
|
||||
ipaIsEnabledToggled,
|
||||
selectIPAOrThrow,
|
||||
} from 'features/controlLayers/store/ipAdaptersSlice';
|
||||
import { memo, useCallback } from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
|
||||
type Props = {
|
||||
id: string;
|
||||
};
|
||||
|
||||
export const IPAHeaderItems = memo(({ id }: Props) => {
|
||||
const { t } = useTranslation();
|
||||
const dispatch = useAppDispatch();
|
||||
const isEnabled = useAppSelector((s) => selectIPAOrThrow(s.ipAdapters, id).isEnabled);
|
||||
const onToggle = useCallback(() => {
|
||||
dispatch(ipaIsEnabledToggled({ id }));
|
||||
}, [dispatch, id]);
|
||||
const onDelete = useCallback(() => {
|
||||
dispatch(ipaDeleted({ id }));
|
||||
}, [dispatch, id]);
|
||||
|
||||
return (
|
||||
<>
|
||||
<EntityEnabledToggle isEnabled={isEnabled} onToggle={onToggle} />
|
||||
<EntityTitle title={t('controlLayers.ipAdapter')} />
|
||||
<Spacer />
|
||||
<EntityDeleteButton onDelete={onDelete} />
|
||||
</>
|
||||
);
|
||||
});
|
||||
|
||||
IPAHeaderItems.displayName = 'IPAHeaderItems';
|
@ -0,0 +1,100 @@
|
||||
import { Flex, useShiftModifier } from '@invoke-ai/ui-library';
|
||||
import { skipToken } from '@reduxjs/toolkit/query';
|
||||
import { useAppDispatch, useAppSelector } from 'app/store/storeHooks';
|
||||
import IAIDndImage from 'common/components/IAIDndImage';
|
||||
import IAIDndImageIcon from 'common/components/IAIDndImageIcon';
|
||||
import { heightChanged, widthChanged } from 'features/controlLayers/store/controlLayersSlice';
|
||||
import type { ImageWithDims } from 'features/controlLayers/store/types';
|
||||
import type { ImageDraggableData, TypesafeDroppableData } from 'features/dnd/types';
|
||||
import { calculateNewSize } from 'features/parameters/components/ImageSize/calculateNewSize';
|
||||
import { selectOptimalDimension } from 'features/parameters/store/generationSlice';
|
||||
import { memo, useCallback, useEffect, useMemo } from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { PiArrowCounterClockwiseBold, PiRulerBold } from 'react-icons/pi';
|
||||
import { useGetImageDTOQuery } from 'services/api/endpoints/images';
|
||||
import type { ImageDTO, PostUploadAction } from 'services/api/types';
|
||||
|
||||
type Props = {
|
||||
image: ImageWithDims | null;
|
||||
onChangeImage: (imageDTO: ImageDTO | null) => void;
|
||||
ipAdapterId: string; // required for the dnd/upload interactions
|
||||
droppableData: TypesafeDroppableData;
|
||||
postUploadAction: PostUploadAction;
|
||||
};
|
||||
|
||||
export const IPAImagePreview = memo(({ image, onChangeImage, ipAdapterId, droppableData, postUploadAction }: Props) => {
|
||||
const { t } = useTranslation();
|
||||
const dispatch = useAppDispatch();
|
||||
const isConnected = useAppSelector((s) => s.system.isConnected);
|
||||
const optimalDimension = useAppSelector(selectOptimalDimension);
|
||||
const shift = useShiftModifier();
|
||||
|
||||
const { currentData: controlImage, isError: isErrorControlImage } = useGetImageDTOQuery(image?.name ?? skipToken);
|
||||
const handleResetControlImage = useCallback(() => {
|
||||
onChangeImage(null);
|
||||
}, [onChangeImage]);
|
||||
|
||||
const handleSetControlImageToDimensions = useCallback(() => {
|
||||
if (!controlImage) {
|
||||
return;
|
||||
}
|
||||
|
||||
const options = { updateAspectRatio: true, clamp: true };
|
||||
if (shift) {
|
||||
const { width, height } = controlImage;
|
||||
dispatch(widthChanged({ width, ...options }));
|
||||
dispatch(heightChanged({ height, ...options }));
|
||||
} else {
|
||||
const { width, height } = calculateNewSize(
|
||||
controlImage.width / controlImage.height,
|
||||
optimalDimension * optimalDimension
|
||||
);
|
||||
dispatch(widthChanged({ width, ...options }));
|
||||
dispatch(heightChanged({ height, ...options }));
|
||||
}
|
||||
}, [controlImage, dispatch, optimalDimension, shift]);
|
||||
|
||||
const draggableData = useMemo<ImageDraggableData | undefined>(() => {
|
||||
if (controlImage) {
|
||||
return {
|
||||
id: ipAdapterId,
|
||||
payloadType: 'IMAGE_DTO',
|
||||
payload: { imageDTO: controlImage },
|
||||
};
|
||||
}
|
||||
}, [controlImage, ipAdapterId]);
|
||||
|
||||
useEffect(() => {
|
||||
if (isConnected && isErrorControlImage) {
|
||||
handleResetControlImage();
|
||||
}
|
||||
}, [handleResetControlImage, isConnected, isErrorControlImage]);
|
||||
|
||||
return (
|
||||
<Flex position="relative" w={36} h={36} alignItems="center">
|
||||
<IAIDndImage
|
||||
draggableData={draggableData}
|
||||
droppableData={droppableData}
|
||||
imageDTO={controlImage}
|
||||
postUploadAction={postUploadAction}
|
||||
/>
|
||||
|
||||
{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={handleSetControlImageToDimensions}
|
||||
icon={<PiRulerBold size={16} />}
|
||||
tooltip={shift ? t('controlnet.setControlImageDimensionsForce') : t('controlnet.setControlImageDimensions')}
|
||||
/>
|
||||
</Flex>
|
||||
)}
|
||||
</Flex>
|
||||
);
|
||||
});
|
||||
|
||||
IPAImagePreview.displayName = 'IPAImagePreview';
|
@ -1,8 +1,8 @@
|
||||
import type { ComboboxOnChange } from '@invoke-ai/ui-library';
|
||||
import { Combobox, FormControl, FormLabel } from '@invoke-ai/ui-library';
|
||||
import { InformationalPopover } from 'common/components/InformationalPopover/InformationalPopover';
|
||||
import type { IPMethodV2 } from 'features/controlLayers/util/controlAdapters';
|
||||
import { isIPMethodV2 } from 'features/controlLayers/util/controlAdapters';
|
||||
import type { IPMethodV2} from 'features/controlLayers/store/types';
|
||||
import { isIPMethodV2 } from 'features/controlLayers/store/types';
|
||||
import { memo, useCallback, useMemo } from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { assert } from 'tsafe';
|
||||
@ -12,7 +12,7 @@ type Props = {
|
||||
onChange: (method: IPMethodV2) => void;
|
||||
};
|
||||
|
||||
export const IPAdapterMethod = memo(({ method, onChange }: Props) => {
|
||||
export const IPAMethod = memo(({ method, onChange }: Props) => {
|
||||
const { t } = useTranslation();
|
||||
const options: { label: string; value: IPMethodV2 }[] = useMemo(
|
||||
() => [
|
||||
@ -41,4 +41,4 @@ export const IPAdapterMethod = memo(({ method, onChange }: Props) => {
|
||||
);
|
||||
});
|
||||
|
||||
IPAdapterMethod.displayName = 'IPAdapterMethod';
|
||||
IPAMethod.displayName = 'IPAMethod';
|
@ -0,0 +1,98 @@
|
||||
import type { ComboboxOnChange } from '@invoke-ai/ui-library';
|
||||
import { Combobox, Flex, FormControl, Tooltip } from '@invoke-ai/ui-library';
|
||||
import { useAppSelector } from 'app/store/storeHooks';
|
||||
import { useGroupedModelCombobox } from 'common/hooks/useGroupedModelCombobox';
|
||||
import type { CLIPVisionModelV2} from 'features/controlLayers/store/types';
|
||||
import { isCLIPVisionModelV2 } from 'features/controlLayers/store/types';
|
||||
import { memo, useCallback, useMemo } from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { useIPAdapterModels } from 'services/api/hooks/modelsByType';
|
||||
import type { AnyModelConfig, IPAdapterModelConfig } from 'services/api/types';
|
||||
import { assert } from 'tsafe';
|
||||
|
||||
const CLIP_VISION_OPTIONS = [
|
||||
{ label: 'ViT-H', value: 'ViT-H' },
|
||||
{ label: 'ViT-G', value: 'ViT-G' },
|
||||
];
|
||||
|
||||
type Props = {
|
||||
modelKey: string | null;
|
||||
onChangeModel: (modelConfig: IPAdapterModelConfig) => void;
|
||||
clipVisionModel: CLIPVisionModelV2;
|
||||
onChangeCLIPVisionModel: (clipVisionModel: CLIPVisionModelV2) => void;
|
||||
};
|
||||
|
||||
export const IPAModelCombobox = memo(({ modelKey, onChangeModel, clipVisionModel, onChangeCLIPVisionModel }: Props) => {
|
||||
const { t } = useTranslation();
|
||||
const currentBaseModel = useAppSelector((s) => s.generation.model?.base);
|
||||
const [modelConfigs, { isLoading }] = useIPAdapterModels();
|
||||
const selectedModel = useMemo(() => modelConfigs.find((m) => m.key === modelKey), [modelConfigs, modelKey]);
|
||||
|
||||
const _onChangeModel = useCallback(
|
||||
(modelConfig: IPAdapterModelConfig | null) => {
|
||||
if (!modelConfig) {
|
||||
return;
|
||||
}
|
||||
onChangeModel(modelConfig);
|
||||
},
|
||||
[onChangeModel]
|
||||
);
|
||||
|
||||
const _onChangeCLIPVisionModel = useCallback<ComboboxOnChange>(
|
||||
(v) => {
|
||||
assert(isCLIPVisionModelV2(v?.value));
|
||||
onChangeCLIPVisionModel(v.value);
|
||||
},
|
||||
[onChangeCLIPVisionModel]
|
||||
);
|
||||
|
||||
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: _onChangeModel,
|
||||
selectedModel,
|
||||
getIsDisabled,
|
||||
isLoading,
|
||||
});
|
||||
|
||||
const clipVisionModelValue = useMemo(
|
||||
() => CLIP_VISION_OPTIONS.find((o) => o.value === clipVisionModel),
|
||||
[clipVisionModel]
|
||||
);
|
||||
|
||||
return (
|
||||
<Flex gap={4}>
|
||||
<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>
|
||||
{selectedModel?.format === 'checkpoint' && (
|
||||
<FormControl isInvalid={!value || currentBaseModel !== selectedModel?.base} width="max-content" minWidth={28}>
|
||||
<Combobox
|
||||
options={CLIP_VISION_OPTIONS}
|
||||
placeholder={t('controlnet.selectCLIPVisionModel')}
|
||||
value={clipVisionModelValue}
|
||||
onChange={_onChangeCLIPVisionModel}
|
||||
/>
|
||||
</FormControl>
|
||||
)}
|
||||
</Flex>
|
||||
);
|
||||
});
|
||||
|
||||
IPAModelCombobox.displayName = 'IPAModelCombobox';
|
@ -0,0 +1,122 @@
|
||||
import { Box, Flex } from '@invoke-ai/ui-library';
|
||||
import { useAppDispatch, useAppSelector } from 'app/store/storeHooks';
|
||||
import { BeginEndStepPct } from 'features/controlLayers/components/common/BeginEndStepPct';
|
||||
import { Weight } from 'features/controlLayers/components/common/Weight';
|
||||
import { IPAMethod } from 'features/controlLayers/components/IPAdapter/IPAMethod';
|
||||
import {
|
||||
ipaBeginEndStepPctChanged,
|
||||
ipaCLIPVisionModelChanged,
|
||||
ipaImageChanged,
|
||||
ipaMethodChanged,
|
||||
ipaModelChanged,
|
||||
ipaWeightChanged,
|
||||
selectIPAOrThrow,
|
||||
} from 'features/controlLayers/store/ipAdaptersSlice';
|
||||
import type { CLIPVisionModelV2, IPMethodV2 } from 'features/controlLayers/store/types';
|
||||
import type { IPAImageDropData } from 'features/dnd/types';
|
||||
import { memo, useCallback, useMemo } from 'react';
|
||||
import type { ImageDTO, IPAdapterModelConfig, IPALayerImagePostUploadAction } from 'services/api/types';
|
||||
|
||||
import { IPAImagePreview } from './IPAImagePreview';
|
||||
import { IPAModelCombobox } from './IPAModelCombobox';
|
||||
|
||||
type Props = {
|
||||
id: string;
|
||||
};
|
||||
|
||||
export const IPASettings = memo(({ id }: Props) => {
|
||||
const dispatch = useAppDispatch();
|
||||
const ipAdapter = useAppSelector((s) => selectIPAOrThrow(s.ipAdapters, id));
|
||||
|
||||
const onChangeBeginEndStepPct = useCallback(
|
||||
(beginEndStepPct: [number, number]) => {
|
||||
dispatch(ipaBeginEndStepPctChanged({ id, beginEndStepPct }));
|
||||
},
|
||||
[dispatch, id]
|
||||
);
|
||||
|
||||
const onChangeWeight = useCallback(
|
||||
(weight: number) => {
|
||||
dispatch(ipaWeightChanged({ id, weight }));
|
||||
},
|
||||
[dispatch, id]
|
||||
);
|
||||
|
||||
const onChangeIPMethod = useCallback(
|
||||
(method: IPMethodV2) => {
|
||||
dispatch(ipaMethodChanged({ id, method }));
|
||||
},
|
||||
[dispatch, id]
|
||||
);
|
||||
|
||||
const onChangeModel = useCallback(
|
||||
(modelConfig: IPAdapterModelConfig) => {
|
||||
dispatch(ipaModelChanged({ id, modelConfig }));
|
||||
},
|
||||
[dispatch, id]
|
||||
);
|
||||
|
||||
const onChangeCLIPVisionModel = useCallback(
|
||||
(clipVisionModel: CLIPVisionModelV2) => {
|
||||
dispatch(ipaCLIPVisionModelChanged({ id, clipVisionModel }));
|
||||
},
|
||||
[dispatch, id]
|
||||
);
|
||||
|
||||
const onChangeImage = useCallback(
|
||||
(imageDTO: ImageDTO | null) => {
|
||||
dispatch(ipaImageChanged({ id, imageDTO }));
|
||||
},
|
||||
[dispatch, id]
|
||||
);
|
||||
|
||||
const droppableData = useMemo<IPAImageDropData>(
|
||||
() => ({
|
||||
actionType: 'SET_IPA_IMAGE',
|
||||
context: { id },
|
||||
id,
|
||||
}),
|
||||
[id]
|
||||
);
|
||||
|
||||
const postUploadAction = useMemo<IPALayerImagePostUploadAction>(
|
||||
() => ({
|
||||
type: 'SET_IPA_IMAGE',
|
||||
id,
|
||||
}),
|
||||
[id]
|
||||
);
|
||||
|
||||
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">
|
||||
<IPAModelCombobox
|
||||
modelKey={ipAdapter.model?.key ?? null}
|
||||
onChangeModel={onChangeModel}
|
||||
clipVisionModel={ipAdapter.clipVisionModel}
|
||||
onChangeCLIPVisionModel={onChangeCLIPVisionModel}
|
||||
/>
|
||||
</Box>
|
||||
</Flex>
|
||||
<Flex gap={4} w="full" alignItems="center">
|
||||
<Flex flexDir="column" gap={3} w="full">
|
||||
<IPAMethod method={ipAdapter.method} onChange={onChangeIPMethod} />
|
||||
<Weight weight={ipAdapter.weight} onChange={onChangeWeight} />
|
||||
<BeginEndStepPct beginEndStepPct={ipAdapter.beginEndStepPct} onChange={onChangeBeginEndStepPct} />
|
||||
</Flex>
|
||||
<Flex alignItems="center" justifyContent="center" h={36} w={36} aspectRatio="1/1">
|
||||
<IPAImagePreview
|
||||
image={ipAdapter.image}
|
||||
onChangeImage={onChangeImage}
|
||||
ipAdapterId={ipAdapter.id}
|
||||
droppableData={droppableData}
|
||||
postUploadAction={postUploadAction}
|
||||
/>
|
||||
</Flex>
|
||||
</Flex>
|
||||
</Flex>
|
||||
);
|
||||
});
|
||||
|
||||
IPASettings.displayName = 'IPASettings';
|
@ -1,14 +1,13 @@
|
||||
import { ButtonGroup, IconButton } from '@invoke-ai/ui-library';
|
||||
import { useStore } from '@nanostores/react';
|
||||
import { createSelector } from '@reduxjs/toolkit';
|
||||
import { createMemoizedSelector } from 'app/store/createMemoizedSelector';
|
||||
import { useAppDispatch, useAppSelector } from 'app/store/storeHooks';
|
||||
import {
|
||||
$tool,
|
||||
layerReset,
|
||||
selectCanvasV2Slice,
|
||||
selectedLayerDeleted,
|
||||
} from 'features/controlLayers/store/controlLayersSlice';
|
||||
import { useCallback } from 'react';
|
||||
import { caDeleted } from 'features/controlLayers/store/controlAdaptersSlice';
|
||||
import { selectCanvasV2Slice, toolChanged } from 'features/controlLayers/store/controlLayersSlice';
|
||||
import { ipaDeleted } from 'features/controlLayers/store/ipAdaptersSlice';
|
||||
import { layerDeleted, layerReset } from 'features/controlLayers/store/layersSlice';
|
||||
import { rgDeleted, rgReset } from 'features/controlLayers/store/regionalGuidanceSlice';
|
||||
import type { CanvasEntityIdentifier } from 'features/controlLayers/store/types';
|
||||
import { useCallback, useMemo } from 'react';
|
||||
import { useHotkeys } from 'react-hotkeys-hook';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import {
|
||||
@ -20,55 +19,94 @@ import {
|
||||
PiRectangleBold,
|
||||
} from 'react-icons/pi';
|
||||
|
||||
const selectIsDisabled = createSelector(selectCanvasV2Slice, (controlLayers) => {
|
||||
const selectedLayer = canvasV2.layers.find((l) => l.id === canvasV2.selectedLayerId);
|
||||
return selectedLayer?.type !== 'regional_guidance_layer' && selectedLayer?.type !== 'raster_layer';
|
||||
});
|
||||
const DRAWING_TOOL_TYPES = ['layer', 'regional_guidance', 'inpaint_mask'];
|
||||
|
||||
const getIsDrawingToolEnabled = (entityIdentifier: CanvasEntityIdentifier | null) => {
|
||||
if (!entityIdentifier) {
|
||||
return false;
|
||||
}
|
||||
return DRAWING_TOOL_TYPES.includes(entityIdentifier.type);
|
||||
};
|
||||
|
||||
const selectSelectedEntityIdentifier = createMemoizedSelector(
|
||||
selectCanvasV2Slice,
|
||||
(canvasV2State) => canvasV2State.selectedEntityIdentifier
|
||||
);
|
||||
|
||||
export const ToolChooser: React.FC = () => {
|
||||
const { t } = useTranslation();
|
||||
const dispatch = useAppDispatch();
|
||||
const isDisabled = useAppSelector(selectIsDisabled);
|
||||
const selectedLayerId = useAppSelector((s) => s.canvasV2.selectedLayerId);
|
||||
const tool = useStore($tool);
|
||||
const selectedEntityIdentifier = useAppSelector(selectSelectedEntityIdentifier);
|
||||
const isDrawingToolDisabled = useMemo(
|
||||
() => !getIsDrawingToolEnabled(selectedEntityIdentifier),
|
||||
[selectedEntityIdentifier]
|
||||
);
|
||||
const isMoveToolDisabled = useMemo(() => selectedEntityIdentifier === null, [selectedEntityIdentifier]);
|
||||
const tool = useAppSelector((s) => s.canvasV2.tool.selected);
|
||||
|
||||
const setToolToBrush = useCallback(() => {
|
||||
$tool.set('brush');
|
||||
}, []);
|
||||
useHotkeys('b', setToolToBrush, { enabled: !isDisabled }, [isDisabled]);
|
||||
dispatch(toolChanged('brush'));
|
||||
}, [dispatch]);
|
||||
useHotkeys('b', setToolToBrush, { enabled: !isDrawingToolDisabled }, [isDrawingToolDisabled, setToolToBrush]);
|
||||
const setToolToEraser = useCallback(() => {
|
||||
$tool.set('eraser');
|
||||
}, []);
|
||||
useHotkeys('e', setToolToEraser, { enabled: !isDisabled }, [isDisabled]);
|
||||
dispatch(toolChanged('eraser'));
|
||||
}, [dispatch]);
|
||||
useHotkeys('e', setToolToEraser, { enabled: !isDrawingToolDisabled }, [isDrawingToolDisabled, setToolToEraser]);
|
||||
const setToolToRect = useCallback(() => {
|
||||
$tool.set('rect');
|
||||
}, []);
|
||||
useHotkeys('u', setToolToRect, { enabled: !isDisabled }, [isDisabled]);
|
||||
dispatch(toolChanged('rect'));
|
||||
}, [dispatch]);
|
||||
useHotkeys('u', setToolToRect, { enabled: !isDrawingToolDisabled }, [isDrawingToolDisabled, setToolToRect]);
|
||||
const setToolToMove = useCallback(() => {
|
||||
$tool.set('move');
|
||||
}, []);
|
||||
useHotkeys('v', setToolToMove, { enabled: !isDisabled }, [isDisabled]);
|
||||
dispatch(toolChanged('move'));
|
||||
}, [dispatch]);
|
||||
useHotkeys('v', setToolToMove, { enabled: !isMoveToolDisabled }, [isMoveToolDisabled, setToolToMove]);
|
||||
const setToolToView = useCallback(() => {
|
||||
$tool.set('view');
|
||||
}, []);
|
||||
useHotkeys('h', setToolToView, { enabled: !isDisabled }, [isDisabled]);
|
||||
dispatch(toolChanged('view'));
|
||||
}, [dispatch]);
|
||||
useHotkeys('h', setToolToView, [setToolToView]);
|
||||
const setToolToBbox = useCallback(() => {
|
||||
$tool.set('bbox');
|
||||
}, []);
|
||||
useHotkeys('q', setToolToBbox, { enabled: !isDisabled }, [isDisabled]);
|
||||
dispatch(toolChanged('bbox'));
|
||||
}, [dispatch]);
|
||||
useHotkeys('q', setToolToBbox, [setToolToBbox]);
|
||||
|
||||
const resetSelectedLayer = useCallback(() => {
|
||||
if (selectedLayerId === null) {
|
||||
if (selectedEntityIdentifier === null) {
|
||||
return;
|
||||
}
|
||||
dispatch(layerReset(selectedLayerId));
|
||||
}, [dispatch, selectedLayerId]);
|
||||
useHotkeys('shift+c', resetSelectedLayer);
|
||||
const { type, id } = selectedEntityIdentifier;
|
||||
if (type === 'layer') {
|
||||
dispatch(layerReset({ id }));
|
||||
}
|
||||
if (type === 'regional_guidance') {
|
||||
dispatch(rgReset({ id }));
|
||||
}
|
||||
}, [dispatch, selectedEntityIdentifier]);
|
||||
const isResetEnabled = useMemo(
|
||||
() => selectedEntityIdentifier?.type === 'layer' || selectedEntityIdentifier?.type === 'regional_guidance',
|
||||
[selectedEntityIdentifier]
|
||||
);
|
||||
useHotkeys('shift+c', resetSelectedLayer, { enabled: isResetEnabled }, [isResetEnabled, resetSelectedLayer]);
|
||||
|
||||
const deleteSelectedLayer = useCallback(() => {
|
||||
dispatch(selectedLayerDeleted());
|
||||
}, [dispatch]);
|
||||
useHotkeys('shift+d', deleteSelectedLayer);
|
||||
if (selectedEntityIdentifier === null) {
|
||||
return;
|
||||
}
|
||||
const { type, id } = selectedEntityIdentifier;
|
||||
if (type === 'layer') {
|
||||
dispatch(layerDeleted({ id }));
|
||||
}
|
||||
if (type === 'regional_guidance') {
|
||||
dispatch(rgDeleted({ id }));
|
||||
}
|
||||
if (type === 'control_adapter') {
|
||||
dispatch(caDeleted({ id }));
|
||||
}
|
||||
if (type === 'ip_adapter') {
|
||||
dispatch(ipaDeleted({ id }));
|
||||
}
|
||||
}, [dispatch, selectedEntityIdentifier]);
|
||||
const isDeleteEnabled = useMemo(() => selectedEntityIdentifier !== null, [selectedEntityIdentifier]);
|
||||
useHotkeys('shift+d', deleteSelectedLayer, { enabled: isDeleteEnabled }, [isDeleteEnabled, deleteSelectedLayer]);
|
||||
|
||||
return (
|
||||
<ButtonGroup isAttached>
|
||||
@ -78,7 +116,7 @@ export const ToolChooser: React.FC = () => {
|
||||
icon={<PiPaintBrushBold />}
|
||||
variant={tool === 'brush' ? 'solid' : 'outline'}
|
||||
onClick={setToolToBrush}
|
||||
isDisabled={isDisabled}
|
||||
isDisabled={isDrawingToolDisabled}
|
||||
/>
|
||||
<IconButton
|
||||
aria-label={`${t('unifiedCanvas.eraser')} (E)`}
|
||||
@ -86,7 +124,7 @@ export const ToolChooser: React.FC = () => {
|
||||
icon={<PiEraserBold />}
|
||||
variant={tool === 'eraser' ? 'solid' : 'outline'}
|
||||
onClick={setToolToEraser}
|
||||
isDisabled={isDisabled}
|
||||
isDisabled={isDrawingToolDisabled}
|
||||
/>
|
||||
<IconButton
|
||||
aria-label={`${t('controlLayers.rectangle')} (U)`}
|
||||
@ -94,7 +132,7 @@ export const ToolChooser: React.FC = () => {
|
||||
icon={<PiRectangleBold />}
|
||||
variant={tool === 'rect' ? 'solid' : 'outline'}
|
||||
onClick={setToolToRect}
|
||||
isDisabled={isDisabled}
|
||||
isDisabled={isDrawingToolDisabled}
|
||||
/>
|
||||
<IconButton
|
||||
aria-label={`${t('unifiedCanvas.move')} (V)`}
|
||||
@ -102,7 +140,7 @@ export const ToolChooser: React.FC = () => {
|
||||
icon={<PiArrowsOutCardinalBold />}
|
||||
variant={tool === 'move' ? 'solid' : 'outline'}
|
||||
onClick={setToolToMove}
|
||||
isDisabled={isDisabled}
|
||||
isDisabled={isMoveToolDisabled}
|
||||
/>
|
||||
<IconButton
|
||||
aria-label={`${t('unifiedCanvas.view')} (H)`}
|
||||
@ -110,7 +148,6 @@ export const ToolChooser: React.FC = () => {
|
||||
icon={<PiHandBold />}
|
||||
variant={tool === 'view' ? 'solid' : 'outline'}
|
||||
onClick={setToolToView}
|
||||
isDisabled={isDisabled}
|
||||
/>
|
||||
<IconButton
|
||||
aria-label={`${t('controlLayers.bbox')} (Q)`}
|
||||
@ -118,7 +155,6 @@ export const ToolChooser: React.FC = () => {
|
||||
icon={<PiBoundingBoxBold />}
|
||||
variant={tool === 'bbox' ? 'solid' : 'outline'}
|
||||
onClick={setToolToBbox}
|
||||
isDisabled={isDisabled}
|
||||
/>
|
||||
</ButtonGroup>
|
||||
);
|
||||
|
@ -6,6 +6,7 @@ import { zModelIdentifierField } from 'features/nodes/types/common';
|
||||
import type { IRect } from 'konva/lib/types';
|
||||
import { isEqual } from 'lodash-es';
|
||||
import type { ControlNetModelConfig, ImageDTO, T2IAdapterModelConfig } from 'services/api/types';
|
||||
import { assert } from 'tsafe';
|
||||
import { v4 as uuidv4 } from 'uuid';
|
||||
|
||||
import type { ControlAdapterConfig, ControlAdapterData, ControlModeV2, Filter, ProcessorConfig } from './types';
|
||||
@ -22,6 +23,11 @@ const initialState: ControlAdaptersV2State = {
|
||||
};
|
||||
|
||||
export const selectCA = (state: ControlAdaptersV2State, id: string) => state.controlAdapters.find((ca) => ca.id === id);
|
||||
export const selectCAOrThrow = (state: ControlAdaptersV2State, id: string) => {
|
||||
const ca = selectCA(state, id);
|
||||
assert(ca, `Control Adapter with id ${id} not found`);
|
||||
return ca;
|
||||
};
|
||||
|
||||
export const controlAdaptersV2Slice = createSlice({
|
||||
name: 'controlAdaptersV2',
|
||||
|
@ -1,13 +1,13 @@
|
||||
import type { PayloadAction } from '@reduxjs/toolkit';
|
||||
import { createSlice } from '@reduxjs/toolkit';
|
||||
import type { PersistConfig, RootState } from 'app/store/store';
|
||||
import type { CLIPVisionModelV2, IPMethodV2 } from 'features/controlLayers/util/controlAdapters';
|
||||
import { imageDTOToImageWithDims } from 'features/controlLayers/util/controlAdapters';
|
||||
import { zModelIdentifierField } from 'features/nodes/types/common';
|
||||
import type { ImageDTO, IPAdapterModelConfig } from 'services/api/types';
|
||||
import { assert } from 'tsafe';
|
||||
import { v4 as uuidv4 } from 'uuid';
|
||||
|
||||
import type { IPAdapterConfig, IPAdapterData } from './types';
|
||||
import type { CLIPVisionModelV2, IPAdapterConfig, IPAdapterData, IPMethodV2 } from './types';
|
||||
import { imageDTOToImageWithDims } from './types';
|
||||
|
||||
type IPAdaptersState = {
|
||||
_version: 1;
|
||||
@ -19,7 +19,12 @@ const initialState: IPAdaptersState = {
|
||||
ipAdapters: [],
|
||||
};
|
||||
|
||||
const selectIpa = (state: IPAdaptersState, id: string) => state.ipAdapters.find((ipa) => ipa.id === id);
|
||||
export const selectIPA = (state: IPAdaptersState, id: string) => state.ipAdapters.find((ipa) => ipa.id === id);
|
||||
export const selectIPAOrThrow = (state: IPAdaptersState, id: string) => {
|
||||
const ipa = selectIPA(state, id);
|
||||
assert(ipa, `IP Adapter with id ${id} not found`);
|
||||
return ipa;
|
||||
};
|
||||
|
||||
export const ipAdaptersSlice = createSlice({
|
||||
name: 'ipAdapters',
|
||||
@ -41,11 +46,11 @@ export const ipAdaptersSlice = createSlice({
|
||||
ipaRecalled: (state, action: PayloadAction<{ data: IPAdapterData }>) => {
|
||||
state.ipAdapters.push(action.payload.data);
|
||||
},
|
||||
ipaIsEnabledChanged: (state, action: PayloadAction<{ id: string; isEnabled: boolean }>) => {
|
||||
const { id, isEnabled } = action.payload;
|
||||
const ipa = selectIpa(state, id);
|
||||
ipaIsEnabledToggled: (state, action: PayloadAction<{ id: string }>) => {
|
||||
const { id } = action.payload;
|
||||
const ipa = selectIPA(state, id);
|
||||
if (ipa) {
|
||||
ipa.isEnabled = isEnabled;
|
||||
ipa.isEnabled = !ipa.isEnabled;
|
||||
}
|
||||
},
|
||||
ipaDeleted: (state, action: PayloadAction<{ id: string }>) => {
|
||||
@ -53,7 +58,7 @@ export const ipAdaptersSlice = createSlice({
|
||||
},
|
||||
ipaImageChanged: (state, action: PayloadAction<{ id: string; imageDTO: ImageDTO | null }>) => {
|
||||
const { id, imageDTO } = action.payload;
|
||||
const ipa = selectIpa(state, id);
|
||||
const ipa = selectIPA(state, id);
|
||||
if (!ipa) {
|
||||
return;
|
||||
}
|
||||
@ -61,7 +66,7 @@ export const ipAdaptersSlice = createSlice({
|
||||
},
|
||||
ipaMethodChanged: (state, action: PayloadAction<{ id: string; method: IPMethodV2 }>) => {
|
||||
const { id, method } = action.payload;
|
||||
const ipa = selectIpa(state, id);
|
||||
const ipa = selectIPA(state, id);
|
||||
if (!ipa) {
|
||||
return;
|
||||
}
|
||||
@ -75,7 +80,7 @@ export const ipAdaptersSlice = createSlice({
|
||||
}>
|
||||
) => {
|
||||
const { id, modelConfig } = action.payload;
|
||||
const ipa = selectIpa(state, id);
|
||||
const ipa = selectIPA(state, id);
|
||||
if (!ipa) {
|
||||
return;
|
||||
}
|
||||
@ -87,7 +92,7 @@ export const ipAdaptersSlice = createSlice({
|
||||
},
|
||||
ipaCLIPVisionModelChanged: (state, action: PayloadAction<{ id: string; clipVisionModel: CLIPVisionModelV2 }>) => {
|
||||
const { id, clipVisionModel } = action.payload;
|
||||
const ipa = selectIpa(state, id);
|
||||
const ipa = selectIPA(state, id);
|
||||
if (!ipa) {
|
||||
return;
|
||||
}
|
||||
@ -95,7 +100,7 @@ export const ipAdaptersSlice = createSlice({
|
||||
},
|
||||
ipaWeightChanged: (state, action: PayloadAction<{ id: string; weight: number }>) => {
|
||||
const { id, weight } = action.payload;
|
||||
const ipa = selectIpa(state, id);
|
||||
const ipa = selectIPA(state, id);
|
||||
if (!ipa) {
|
||||
return;
|
||||
}
|
||||
@ -103,7 +108,7 @@ export const ipAdaptersSlice = createSlice({
|
||||
},
|
||||
ipaBeginEndStepPctChanged: (state, action: PayloadAction<{ id: string; beginEndStepPct: [number, number] }>) => {
|
||||
const { id, beginEndStepPct } = action.payload;
|
||||
const ipa = selectIpa(state, id);
|
||||
const ipa = selectIPA(state, id);
|
||||
if (!ipa) {
|
||||
return;
|
||||
}
|
||||
@ -115,7 +120,7 @@ export const ipAdaptersSlice = createSlice({
|
||||
export const {
|
||||
ipaAdded,
|
||||
ipaRecalled,
|
||||
ipaIsEnabledChanged,
|
||||
ipaIsEnabledToggled,
|
||||
ipaDeleted,
|
||||
ipaImageChanged,
|
||||
ipaMethodChanged,
|
||||
|
@ -87,6 +87,17 @@ export const regionalGuidanceSlice = createSlice({
|
||||
},
|
||||
prepare: () => ({ payload: { id: uuidv4() } }),
|
||||
},
|
||||
rgReset: (state, action: PayloadAction<{ id: string }>) => {
|
||||
const { id } = action.payload;
|
||||
const rg = selectRg(state, id);
|
||||
if (!rg) {
|
||||
return;
|
||||
}
|
||||
rg.objects = [];
|
||||
rg.bbox = null;
|
||||
rg.bboxNeedsUpdate = false;
|
||||
rg.imageCache = null;
|
||||
},
|
||||
rgRecalled: (state, action: PayloadAction<{ data: RegionalGuidanceData }>) => {
|
||||
const { data } = action.payload;
|
||||
state.regions.push(data);
|
||||
@ -388,6 +399,7 @@ export const regionalGuidanceSlice = createSlice({
|
||||
export const {
|
||||
rgAdded,
|
||||
rgRecalled,
|
||||
rgReset,
|
||||
rgIsEnabledToggled,
|
||||
rgTranslated,
|
||||
rgBboxChanged,
|
||||
|
@ -290,3 +290,14 @@ const InvokeTabs = () => {
|
||||
};
|
||||
|
||||
export default memo(InvokeTabs);
|
||||
|
||||
const ParametersPanelComponent = memo(() => {
|
||||
const activeTabName = useAppSelector(activeTabNameSelector);
|
||||
|
||||
if (activeTabName === 'workflows') {
|
||||
return <NodeEditorPanelGroup />;
|
||||
} else {
|
||||
return <ParametersPanelTextToImage />;
|
||||
}
|
||||
});
|
||||
ParametersPanelComponent.displayName = 'ParametersPanelComponent';
|
||||
|
Loading…
x
Reference in New Issue
Block a user