mirror of
https://github.com/invoke-ai/InvokeAI.git
synced 2026-04-23 03:00:31 -04:00
refactor(ui): refImage.ipAdapter -> refImage.config
This commit is contained in:
@@ -1,7 +1,7 @@
|
||||
import { logger } from 'app/logging/logger';
|
||||
import type { AppStartListening } from 'app/store/middleware/listenerMiddleware';
|
||||
import type { AppDispatch, RootState } from 'app/store/store';
|
||||
import { controlLayerModelChanged, rgIPAdapterModelChanged } from 'features/controlLayers/store/canvasSlice';
|
||||
import { controlLayerModelChanged, rgRefImageModelChanged } from 'features/controlLayers/store/canvasSlice';
|
||||
import { loraDeleted } from 'features/controlLayers/store/lorasSlice';
|
||||
import {
|
||||
clipEmbedModelSelected,
|
||||
@@ -11,9 +11,9 @@ import {
|
||||
t5EncoderModelSelected,
|
||||
vaeSelected,
|
||||
} from 'features/controlLayers/store/paramsSlice';
|
||||
import { referenceImageIPAdapterModelChanged, selectRefImagesSlice } from 'features/controlLayers/store/refImagesSlice';
|
||||
import { refImageModelChanged, selectRefImagesSlice } from 'features/controlLayers/store/refImagesSlice';
|
||||
import { selectCanvasSlice } from 'features/controlLayers/store/selectors';
|
||||
import { getEntityIdentifier } from 'features/controlLayers/store/types';
|
||||
import { getEntityIdentifier, isFLUXReduxConfig, isIPAdapterConfig } from 'features/controlLayers/store/types';
|
||||
import { modelSelected } from 'features/parameters/store/actions';
|
||||
import { postProcessingModelChanged, upscaleModelChanged } from 'features/parameters/store/upscaleSlice';
|
||||
import {
|
||||
@@ -208,11 +208,11 @@ const handleControlAdapterModels: ModelHandler = (models, state, dispatch, log)
|
||||
const handleIPAdapterModels: ModelHandler = (models, state, dispatch, log) => {
|
||||
const ipaModels = models.filter(isIPAdapterModelConfig);
|
||||
selectRefImagesSlice(state).entities.forEach((entity) => {
|
||||
if (entity.ipAdapter.type !== 'ip_adapter') {
|
||||
if (!isIPAdapterConfig(entity.config)) {
|
||||
return;
|
||||
}
|
||||
|
||||
const selectedIPAdapterModel = entity.ipAdapter.model;
|
||||
const selectedIPAdapterModel = entity.config.model;
|
||||
// `null` is a valid IP adapter model - no need to do anything.
|
||||
if (!selectedIPAdapterModel) {
|
||||
return;
|
||||
@@ -222,16 +222,16 @@ const handleIPAdapterModels: ModelHandler = (models, state, dispatch, log) => {
|
||||
return;
|
||||
}
|
||||
log.debug({ selectedIPAdapterModel }, 'Selected IP adapter model is not available, clearing');
|
||||
dispatch(referenceImageIPAdapterModelChanged({ id: entity.id, modelConfig: null }));
|
||||
dispatch(refImageModelChanged({ id: entity.id, modelConfig: null }));
|
||||
});
|
||||
|
||||
selectCanvasSlice(state).regionalGuidance.entities.forEach((entity) => {
|
||||
entity.referenceImages.forEach(({ id: referenceImageId, ipAdapter }) => {
|
||||
if (ipAdapter.type !== 'ip_adapter') {
|
||||
entity.referenceImages.forEach(({ id: referenceImageId, config }) => {
|
||||
if (!isIPAdapterConfig(config)) {
|
||||
return;
|
||||
}
|
||||
|
||||
const selectedIPAdapterModel = ipAdapter.model;
|
||||
const selectedIPAdapterModel = config.model;
|
||||
// `null` is a valid IP adapter model - no need to do anything.
|
||||
if (!selectedIPAdapterModel) {
|
||||
return;
|
||||
@@ -242,7 +242,7 @@ const handleIPAdapterModels: ModelHandler = (models, state, dispatch, log) => {
|
||||
}
|
||||
log.debug({ selectedIPAdapterModel }, 'Selected IP adapter model is not available, clearing');
|
||||
dispatch(
|
||||
rgIPAdapterModelChanged({ entityIdentifier: getEntityIdentifier(entity), referenceImageId, modelConfig: null })
|
||||
rgRefImageModelChanged({ entityIdentifier: getEntityIdentifier(entity), referenceImageId, modelConfig: null })
|
||||
);
|
||||
});
|
||||
});
|
||||
@@ -252,10 +252,10 @@ const handleFLUXReduxModels: ModelHandler = (models, state, dispatch, log) => {
|
||||
const fluxReduxModels = models.filter(isFluxReduxModelConfig);
|
||||
|
||||
selectRefImagesSlice(state).entities.forEach((entity) => {
|
||||
if (entity.ipAdapter.type !== 'flux_redux') {
|
||||
if (!isFLUXReduxConfig(entity.config)) {
|
||||
return;
|
||||
}
|
||||
const selectedFLUXReduxModel = entity.ipAdapter.model;
|
||||
const selectedFLUXReduxModel = entity.config.model;
|
||||
// `null` is a valid FLUX Redux model - no need to do anything.
|
||||
if (!selectedFLUXReduxModel) {
|
||||
return;
|
||||
@@ -265,16 +265,16 @@ const handleFLUXReduxModels: ModelHandler = (models, state, dispatch, log) => {
|
||||
return;
|
||||
}
|
||||
log.debug({ selectedFLUXReduxModel }, 'Selected FLUX Redux model is not available, clearing');
|
||||
dispatch(referenceImageIPAdapterModelChanged({ id: entity.id, modelConfig: null }));
|
||||
dispatch(refImageModelChanged({ id: entity.id, modelConfig: null }));
|
||||
});
|
||||
|
||||
selectCanvasSlice(state).regionalGuidance.entities.forEach((entity) => {
|
||||
entity.referenceImages.forEach(({ id: referenceImageId, ipAdapter }) => {
|
||||
if (ipAdapter.type !== 'flux_redux') {
|
||||
entity.referenceImages.forEach(({ id: referenceImageId, config }) => {
|
||||
if (!isFLUXReduxConfig(config)) {
|
||||
return;
|
||||
}
|
||||
|
||||
const selectedFLUXReduxModel = ipAdapter.model;
|
||||
const selectedFLUXReduxModel = config.model;
|
||||
// `null` is a valid FLUX Redux model - no need to do anything.
|
||||
if (!selectedFLUXReduxModel) {
|
||||
return;
|
||||
@@ -285,7 +285,7 @@ const handleFLUXReduxModels: ModelHandler = (models, state, dispatch, log) => {
|
||||
}
|
||||
log.debug({ selectedFLUXReduxModel }, 'Selected FLUX Redux model is not available, clearing');
|
||||
dispatch(
|
||||
rgIPAdapterModelChanged({ entityIdentifier: getEntityIdentifier(entity), referenceImageId, modelConfig: null })
|
||||
rgRefImageModelChanged({ entityIdentifier: getEntityIdentifier(entity), referenceImageId, modelConfig: null })
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -52,7 +52,7 @@ export const RefImagePreview = memo(() => {
|
||||
<Popover isLazy lazyBehavior="unmount" isOpen={disclosure.isOpen} closeOnBlur={false}>
|
||||
<PopoverAnchor>
|
||||
<Flex role="button" w={16} h={16} sx={sx} onClick={disclosure.open} data-is-open={disclosure.isOpen}>
|
||||
<Thumbnail image={entity.ipAdapter.image} />
|
||||
<Thumbnail image={entity.config.image} />
|
||||
</Flex>
|
||||
</PopoverAnchor>
|
||||
<Portal>
|
||||
|
||||
@@ -11,13 +11,13 @@ import { IPAdapterSettingsEmptyState } from 'features/controlLayers/components/I
|
||||
import { useRefImageIdContext } from 'features/controlLayers/contexts/RefImageIdContext';
|
||||
import { selectIsFLUX } from 'features/controlLayers/store/paramsSlice';
|
||||
import {
|
||||
referenceImageIPAdapterBeginEndStepPctChanged,
|
||||
referenceImageIPAdapterCLIPVisionModelChanged,
|
||||
referenceImageIPAdapterFLUXReduxImageInfluenceChanged,
|
||||
referenceImageIPAdapterImageChanged,
|
||||
referenceImageIPAdapterMethodChanged,
|
||||
referenceImageIPAdapterModelChanged,
|
||||
referenceImageIPAdapterWeightChanged,
|
||||
refImageFLUXReduxImageInfluenceChanged,
|
||||
refImageImageChanged,
|
||||
refImageIPAdapterBeginEndStepPctChanged,
|
||||
refImageIPAdapterCLIPVisionModelChanged,
|
||||
refImageIPAdapterMethodChanged,
|
||||
refImageIPAdapterWeightChanged,
|
||||
refImageModelChanged,
|
||||
selectRefImageEntity,
|
||||
selectRefImageEntityOrThrow,
|
||||
selectRefImagesSlice,
|
||||
@@ -35,64 +35,64 @@ import type { ApiModelConfig, FLUXReduxModelConfig, ImageDTO, IPAdapterModelConf
|
||||
|
||||
import { IPAdapterImagePreview } from './IPAdapterImagePreview';
|
||||
|
||||
const buildSelectIPAdapter = (id: string) =>
|
||||
const buildSelectConfig = (id: string) =>
|
||||
createSelector(
|
||||
selectRefImagesSlice,
|
||||
(refImages) => selectRefImageEntityOrThrow(refImages, id, 'IPAdapterSettings').ipAdapter
|
||||
(refImages) => selectRefImageEntityOrThrow(refImages, id, 'IPAdapterSettings').config
|
||||
);
|
||||
|
||||
const IPAdapterSettingsContent = memo(() => {
|
||||
const { t } = useTranslation();
|
||||
const dispatch = useAppDispatch();
|
||||
const id = useRefImageIdContext();
|
||||
const selectIPAdapter = useMemo(() => buildSelectIPAdapter(id), [id]);
|
||||
const selectIPAdapter = useMemo(() => buildSelectConfig(id), [id]);
|
||||
const ipAdapter = useAppSelector(selectIPAdapter);
|
||||
|
||||
const onChangeBeginEndStepPct = useCallback(
|
||||
(beginEndStepPct: [number, number]) => {
|
||||
dispatch(referenceImageIPAdapterBeginEndStepPctChanged({ id, beginEndStepPct }));
|
||||
dispatch(refImageIPAdapterBeginEndStepPctChanged({ id, beginEndStepPct }));
|
||||
},
|
||||
[dispatch, id]
|
||||
);
|
||||
|
||||
const onChangeWeight = useCallback(
|
||||
(weight: number) => {
|
||||
dispatch(referenceImageIPAdapterWeightChanged({ id, weight }));
|
||||
dispatch(refImageIPAdapterWeightChanged({ id, weight }));
|
||||
},
|
||||
[dispatch, id]
|
||||
);
|
||||
|
||||
const onChangeIPMethod = useCallback(
|
||||
(method: IPMethodV2) => {
|
||||
dispatch(referenceImageIPAdapterMethodChanged({ id, method }));
|
||||
dispatch(refImageIPAdapterMethodChanged({ id, method }));
|
||||
},
|
||||
[dispatch, id]
|
||||
);
|
||||
|
||||
const onChangeFLUXReduxImageInfluence = useCallback(
|
||||
(imageInfluence: FLUXReduxImageInfluenceType) => {
|
||||
dispatch(referenceImageIPAdapterFLUXReduxImageInfluenceChanged({ id, imageInfluence }));
|
||||
dispatch(refImageFLUXReduxImageInfluenceChanged({ id, imageInfluence }));
|
||||
},
|
||||
[dispatch, id]
|
||||
);
|
||||
|
||||
const onChangeModel = useCallback(
|
||||
(modelConfig: IPAdapterModelConfig | FLUXReduxModelConfig | ApiModelConfig) => {
|
||||
dispatch(referenceImageIPAdapterModelChanged({ id, modelConfig }));
|
||||
dispatch(refImageModelChanged({ id, modelConfig }));
|
||||
},
|
||||
[dispatch, id]
|
||||
);
|
||||
|
||||
const onChangeCLIPVisionModel = useCallback(
|
||||
(clipVisionModel: CLIPVisionModelV2) => {
|
||||
dispatch(referenceImageIPAdapterCLIPVisionModelChanged({ id, clipVisionModel }));
|
||||
dispatch(refImageIPAdapterCLIPVisionModelChanged({ id, clipVisionModel }));
|
||||
},
|
||||
[dispatch, id]
|
||||
);
|
||||
|
||||
const onChangeImage = useCallback(
|
||||
(imageDTO: ImageDTO | null) => {
|
||||
dispatch(referenceImageIPAdapterImageChanged({ id, imageDTO }));
|
||||
dispatch(refImageImageChanged({ id, imageDTO }));
|
||||
},
|
||||
[dispatch, id]
|
||||
);
|
||||
@@ -156,7 +156,7 @@ IPAdapterSettingsContent.displayName = 'IPAdapterSettingsContent';
|
||||
const buildSelectIPAdapterHasImage = (id: string) =>
|
||||
createSelector(selectRefImagesSlice, (refImages) => {
|
||||
const referenceImage = selectRefImageEntity(refImages, id);
|
||||
return !!referenceImage && referenceImage.ipAdapter.image !== null;
|
||||
return !!referenceImage && referenceImage.config.image !== null;
|
||||
});
|
||||
|
||||
export const IPAdapterSettings = memo(() => {
|
||||
|
||||
@@ -13,14 +13,14 @@ import { useEntityIdentifierContext } from 'features/controlLayers/contexts/Enti
|
||||
import { usePullBboxIntoRegionalGuidanceReferenceImage } from 'features/controlLayers/hooks/saveCanvasHooks';
|
||||
import { useCanvasIsBusy } from 'features/controlLayers/hooks/useCanvasIsBusy';
|
||||
import {
|
||||
rgIPAdapterBeginEndStepPctChanged,
|
||||
rgIPAdapterCLIPVisionModelChanged,
|
||||
rgIPAdapterDeleted,
|
||||
rgIPAdapterFLUXReduxImageInfluenceChanged,
|
||||
rgIPAdapterImageChanged,
|
||||
rgIPAdapterMethodChanged,
|
||||
rgIPAdapterModelChanged,
|
||||
rgIPAdapterWeightChanged,
|
||||
rgRefImageDeleted,
|
||||
rgRefImageFLUXReduxImageInfluenceChanged,
|
||||
rgRefImageImageChanged,
|
||||
rgRefImageIPAdapterBeginEndStepPctChanged,
|
||||
rgRefImageIPAdapterCLIPVisionModelChanged,
|
||||
rgRefImageIPAdapterMethodChanged,
|
||||
rgRefImageIPAdapterWeightChanged,
|
||||
rgRefImageModelChanged,
|
||||
} from 'features/controlLayers/store/canvasSlice';
|
||||
import { selectCanvasSlice, selectRegionalGuidanceReferenceImage } from 'features/controlLayers/store/selectors';
|
||||
import type {
|
||||
@@ -46,64 +46,64 @@ const RegionalGuidanceIPAdapterSettingsContent = memo(({ referenceImageId }: Pro
|
||||
const { t } = useTranslation();
|
||||
const dispatch = useAppDispatch();
|
||||
const onDeleteIPAdapter = useCallback(() => {
|
||||
dispatch(rgIPAdapterDeleted({ entityIdentifier, referenceImageId }));
|
||||
dispatch(rgRefImageDeleted({ entityIdentifier, referenceImageId }));
|
||||
}, [dispatch, entityIdentifier, referenceImageId]);
|
||||
const selectIPAdapter = useMemo(
|
||||
const selectConfig = useMemo(
|
||||
() =>
|
||||
createSelector(selectCanvasSlice, (canvas) => {
|
||||
const referenceImage = selectRegionalGuidanceReferenceImage(canvas, entityIdentifier, referenceImageId);
|
||||
assert(referenceImage, `Regional Guidance IP Adapter with id ${referenceImageId} not found`);
|
||||
return referenceImage.ipAdapter;
|
||||
return referenceImage.config;
|
||||
}),
|
||||
[entityIdentifier, referenceImageId]
|
||||
);
|
||||
const ipAdapter = useAppSelector(selectIPAdapter);
|
||||
const config = useAppSelector(selectConfig);
|
||||
|
||||
const onChangeBeginEndStepPct = useCallback(
|
||||
(beginEndStepPct: [number, number]) => {
|
||||
dispatch(rgIPAdapterBeginEndStepPctChanged({ entityIdentifier, referenceImageId, beginEndStepPct }));
|
||||
dispatch(rgRefImageIPAdapterBeginEndStepPctChanged({ entityIdentifier, referenceImageId, beginEndStepPct }));
|
||||
},
|
||||
[dispatch, entityIdentifier, referenceImageId]
|
||||
);
|
||||
|
||||
const onChangeWeight = useCallback(
|
||||
(weight: number) => {
|
||||
dispatch(rgIPAdapterWeightChanged({ entityIdentifier, referenceImageId, weight }));
|
||||
dispatch(rgRefImageIPAdapterWeightChanged({ entityIdentifier, referenceImageId, weight }));
|
||||
},
|
||||
[dispatch, entityIdentifier, referenceImageId]
|
||||
);
|
||||
|
||||
const onChangeIPMethod = useCallback(
|
||||
(method: IPMethodV2) => {
|
||||
dispatch(rgIPAdapterMethodChanged({ entityIdentifier, referenceImageId, method }));
|
||||
dispatch(rgRefImageIPAdapterMethodChanged({ entityIdentifier, referenceImageId, method }));
|
||||
},
|
||||
[dispatch, entityIdentifier, referenceImageId]
|
||||
);
|
||||
|
||||
const onChangeFLUXReduxImageInfluence = useCallback(
|
||||
(imageInfluence: FLUXReduxImageInfluenceType) => {
|
||||
dispatch(rgIPAdapterFLUXReduxImageInfluenceChanged({ entityIdentifier, referenceImageId, imageInfluence }));
|
||||
dispatch(rgRefImageFLUXReduxImageInfluenceChanged({ entityIdentifier, referenceImageId, imageInfluence }));
|
||||
},
|
||||
[dispatch, entityIdentifier, referenceImageId]
|
||||
);
|
||||
|
||||
const onChangeModel = useCallback(
|
||||
(modelConfig: IPAdapterModelConfig | FLUXReduxModelConfig) => {
|
||||
dispatch(rgIPAdapterModelChanged({ entityIdentifier, referenceImageId, modelConfig }));
|
||||
dispatch(rgRefImageModelChanged({ entityIdentifier, referenceImageId, modelConfig }));
|
||||
},
|
||||
[dispatch, entityIdentifier, referenceImageId]
|
||||
);
|
||||
|
||||
const onChangeCLIPVisionModel = useCallback(
|
||||
(clipVisionModel: CLIPVisionModelV2) => {
|
||||
dispatch(rgIPAdapterCLIPVisionModelChanged({ entityIdentifier, referenceImageId, clipVisionModel }));
|
||||
dispatch(rgRefImageIPAdapterCLIPVisionModelChanged({ entityIdentifier, referenceImageId, clipVisionModel }));
|
||||
},
|
||||
[dispatch, entityIdentifier, referenceImageId]
|
||||
);
|
||||
|
||||
const onChangeImage = useCallback(
|
||||
(imageDTO: ImageDTO | null) => {
|
||||
dispatch(rgIPAdapterImageChanged({ entityIdentifier, referenceImageId, imageDTO }));
|
||||
dispatch(rgRefImageImageChanged({ entityIdentifier, referenceImageId, imageDTO }));
|
||||
},
|
||||
[dispatch, entityIdentifier, referenceImageId]
|
||||
);
|
||||
@@ -112,9 +112,9 @@ const RegionalGuidanceIPAdapterSettingsContent = memo(({ referenceImageId }: Pro
|
||||
() =>
|
||||
setRegionalGuidanceReferenceImageDndTarget.getData(
|
||||
{ entityIdentifier, referenceImageId },
|
||||
ipAdapter.image?.image_name
|
||||
config.image?.image_name
|
||||
),
|
||||
[entityIdentifier, ipAdapter.image?.image_name, referenceImageId]
|
||||
[entityIdentifier, config.image?.image_name, referenceImageId]
|
||||
);
|
||||
|
||||
const pullBboxIntoIPAdapter = usePullBboxIntoRegionalGuidanceReferenceImage(entityIdentifier, referenceImageId);
|
||||
@@ -140,9 +140,9 @@ const RegionalGuidanceIPAdapterSettingsContent = memo(({ referenceImageId }: Pro
|
||||
</Flex>
|
||||
<Flex flexDir="column" gap={2} position="relative" w="full">
|
||||
<Flex gap={2} alignItems="center" w="full">
|
||||
<RegionalReferenceImageModel modelKey={ipAdapter.model?.key ?? null} onChangeModel={onChangeModel} />
|
||||
{ipAdapter.type === 'ip_adapter' && (
|
||||
<CLIPVisionModel model={ipAdapter.clipVisionModel} onChange={onChangeCLIPVisionModel} />
|
||||
<RegionalReferenceImageModel modelKey={config.model?.key ?? null} onChangeModel={onChangeModel} />
|
||||
{config.type === 'ip_adapter' && (
|
||||
<CLIPVisionModel model={config.clipVisionModel} onChange={onChangeCLIPVisionModel} />
|
||||
)}
|
||||
<IconButton
|
||||
onClick={pullBboxIntoIPAdapter}
|
||||
@@ -154,24 +154,24 @@ const RegionalGuidanceIPAdapterSettingsContent = memo(({ referenceImageId }: Pro
|
||||
/>
|
||||
</Flex>
|
||||
<Flex gap={2} w="full">
|
||||
{ipAdapter.type === 'ip_adapter' && (
|
||||
{config.type === 'ip_adapter' && (
|
||||
<Flex flexDir="column" gap={2} w="full">
|
||||
<IPAdapterMethod method={ipAdapter.method} onChange={onChangeIPMethod} />
|
||||
<Weight weight={ipAdapter.weight} onChange={onChangeWeight} />
|
||||
<BeginEndStepPct beginEndStepPct={ipAdapter.beginEndStepPct} onChange={onChangeBeginEndStepPct} />
|
||||
<IPAdapterMethod method={config.method} onChange={onChangeIPMethod} />
|
||||
<Weight weight={config.weight} onChange={onChangeWeight} />
|
||||
<BeginEndStepPct beginEndStepPct={config.beginEndStepPct} onChange={onChangeBeginEndStepPct} />
|
||||
</Flex>
|
||||
)}
|
||||
{ipAdapter.type === 'flux_redux' && (
|
||||
{config.type === 'flux_redux' && (
|
||||
<Flex flexDir="column" gap={2} w="full">
|
||||
<FLUXReduxImageInfluence
|
||||
imageInfluence={ipAdapter.imageInfluence ?? 'lowest'}
|
||||
imageInfluence={config.imageInfluence ?? 'lowest'}
|
||||
onChange={onChangeFLUXReduxImageInfluence}
|
||||
/>
|
||||
</Flex>
|
||||
)}
|
||||
<Flex alignItems="center" justifyContent="center" h={32} w={32} aspectRatio="1/1" flexGrow={1}>
|
||||
<IPAdapterImagePreview
|
||||
image={ipAdapter.image}
|
||||
image={config.image}
|
||||
onChangeImage={onChangeImage}
|
||||
dndTarget={setRegionalGuidanceReferenceImageDndTarget}
|
||||
dndTargetData={dndTargetData}
|
||||
@@ -191,17 +191,16 @@ const buildSelectIPAdapterHasImage = (
|
||||
) =>
|
||||
createSelector(selectCanvasSlice, (canvas) => {
|
||||
const referenceImage = selectRegionalGuidanceReferenceImage(canvas, entityIdentifier, referenceImageId);
|
||||
return !!referenceImage && referenceImage.ipAdapter.image !== null;
|
||||
return !!referenceImage && referenceImage.config.image !== null;
|
||||
});
|
||||
|
||||
export const RegionalGuidanceIPAdapterSettings = memo(({ referenceImageId }: Props) => {
|
||||
const entityIdentifier = useEntityIdentifierContext('regional_guidance');
|
||||
|
||||
const selectIPAdapterHasImage = useMemo(
|
||||
const selectHasImage = useMemo(
|
||||
() => buildSelectIPAdapterHasImage(entityIdentifier, referenceImageId),
|
||||
[entityIdentifier, referenceImageId]
|
||||
);
|
||||
const hasImage = useAppSelector(selectIPAdapterHasImage);
|
||||
const hasImage = useAppSelector(selectHasImage);
|
||||
|
||||
if (!hasImage) {
|
||||
return <RegionalGuidanceIPAdapterSettingsEmptyState referenceImageId={referenceImageId} />;
|
||||
|
||||
@@ -4,7 +4,7 @@ import { useImageUploadButton } from 'common/hooks/useImageUploadButton';
|
||||
import { useEntityIdentifierContext } from 'features/controlLayers/contexts/EntityIdentifierContext';
|
||||
import { usePullBboxIntoRegionalGuidanceReferenceImage } from 'features/controlLayers/hooks/saveCanvasHooks';
|
||||
import { useCanvasIsBusy } from 'features/controlLayers/hooks/useCanvasIsBusy';
|
||||
import { rgIPAdapterDeleted } from 'features/controlLayers/store/canvasSlice';
|
||||
import { rgRefImageDeleted } from 'features/controlLayers/store/canvasSlice';
|
||||
import type { SetRegionalGuidanceReferenceImageDndTargetData } from 'features/dnd/dnd';
|
||||
import { setRegionalGuidanceReferenceImageDndTarget } from 'features/dnd/dnd';
|
||||
import { DndDropTarget } from 'features/dnd/DndDropTarget';
|
||||
@@ -35,7 +35,7 @@ export const RegionalGuidanceIPAdapterSettingsEmptyState = memo(({ referenceImag
|
||||
dispatch(activeTabCanvasRightPanelChanged('gallery'));
|
||||
}, [dispatch]);
|
||||
const onDeleteIPAdapter = useCallback(() => {
|
||||
dispatch(rgIPAdapterDeleted({ entityIdentifier, referenceImageId }));
|
||||
dispatch(rgRefImageDeleted({ entityIdentifier, referenceImageId }));
|
||||
}, [dispatch, entityIdentifier, referenceImageId]);
|
||||
const pullBboxIntoIPAdapter = usePullBboxIntoRegionalGuidanceReferenceImage(entityIdentifier, referenceImageId);
|
||||
|
||||
|
||||
@@ -10,20 +10,20 @@ import {
|
||||
inpaintMaskNoiseAdded,
|
||||
rasterLayerAdded,
|
||||
rgAdded,
|
||||
rgIPAdapterAdded,
|
||||
rgNegativePromptChanged,
|
||||
rgPositivePromptChanged,
|
||||
rgRefImageAdded,
|
||||
} from 'features/controlLayers/store/canvasSlice';
|
||||
import { selectBase, selectMainModelConfig } from 'features/controlLayers/store/paramsSlice';
|
||||
import { referenceImageAdded } from 'features/controlLayers/store/refImagesSlice';
|
||||
import { refImageAdded } from 'features/controlLayers/store/refImagesSlice';
|
||||
import { selectCanvasSlice, selectEntity } from 'features/controlLayers/store/selectors';
|
||||
import type {
|
||||
CanvasEntityIdentifier,
|
||||
CanvasReferenceImageState,
|
||||
CanvasRegionalGuidanceState,
|
||||
ControlLoRAConfig,
|
||||
ControlNetConfig,
|
||||
IPAdapterConfig,
|
||||
RefImageState,
|
||||
T2IAdapterConfig,
|
||||
} from 'features/controlLayers/store/types';
|
||||
import {
|
||||
@@ -76,7 +76,7 @@ export const selectDefaultRefImageConfig = createSelector(
|
||||
selectMainModelConfig,
|
||||
selectModelConfigsQuery,
|
||||
selectBase,
|
||||
(selectedMainModel, query, base): CanvasReferenceImageState['ipAdapter'] => {
|
||||
(selectedMainModel, query, base): RefImageState['config'] => {
|
||||
if (selectedMainModel?.base === 'chatgpt-4o') {
|
||||
const referenceImage = deepClone(initialChatGPT4oReferenceImage);
|
||||
referenceImage.model = zModelIdentifierField.parse(selectedMainModel);
|
||||
@@ -172,7 +172,7 @@ export const useAddRegionalReferenceImage = () => {
|
||||
const func = useCallback(() => {
|
||||
const overrides: Partial<CanvasRegionalGuidanceState> = {
|
||||
referenceImages: [
|
||||
{ id: getPrefixedId('regional_guidance_reference_image'), ipAdapter: deepClone(defaultIPAdapter) },
|
||||
{ id: getPrefixedId('regional_guidance_reference_image'), config: deepClone(defaultIPAdapter) },
|
||||
],
|
||||
};
|
||||
dispatch(rgAdded({ isSelected: true, overrides }));
|
||||
@@ -185,8 +185,8 @@ export const useAddGlobalReferenceImage = () => {
|
||||
const dispatch = useAppDispatch();
|
||||
const defaultRefImage = useAppSelector(selectDefaultRefImageConfig);
|
||||
const func = useCallback(() => {
|
||||
const overrides = { ipAdapter: deepClone(defaultRefImage) };
|
||||
dispatch(referenceImageAdded({ isSelected: true, overrides }));
|
||||
const overrides = { config: deepClone(defaultRefImage) };
|
||||
dispatch(refImageAdded({ isSelected: true, overrides }));
|
||||
}, [defaultRefImage, dispatch]);
|
||||
|
||||
return func;
|
||||
@@ -196,7 +196,7 @@ export const useAddRegionalGuidanceIPAdapter = (entityIdentifier: CanvasEntityId
|
||||
const dispatch = useAppDispatch();
|
||||
const defaultIPAdapter = useAppSelector(selectDefaultIPAdapter);
|
||||
const func = useCallback(() => {
|
||||
dispatch(rgIPAdapterAdded({ entityIdentifier, overrides: { ipAdapter: deepClone(defaultIPAdapter) } }));
|
||||
dispatch(rgRefImageAdded({ entityIdentifier, overrides: { config: deepClone(defaultIPAdapter) } }));
|
||||
}, [defaultIPAdapter, dispatch, entityIdentifier]);
|
||||
|
||||
return func;
|
||||
|
||||
@@ -10,7 +10,7 @@ import {
|
||||
entityRasterized,
|
||||
rasterLayerAdded,
|
||||
rgAdded,
|
||||
rgIPAdapterImageChanged,
|
||||
rgRefImageImageChanged,
|
||||
} from 'features/controlLayers/store/canvasSlice';
|
||||
import {
|
||||
selectMainModelConfig,
|
||||
@@ -18,16 +18,16 @@ import {
|
||||
selectPositivePrompt,
|
||||
selectSeed,
|
||||
} from 'features/controlLayers/store/paramsSlice';
|
||||
import { referenceImageAdded, referenceImageIPAdapterImageChanged } from 'features/controlLayers/store/refImagesSlice';
|
||||
import { refImageAdded,refImageImageChanged } from 'features/controlLayers/store/refImagesSlice';
|
||||
import { selectCanvasMetadata } from 'features/controlLayers/store/selectors';
|
||||
import type {
|
||||
CanvasControlLayerState,
|
||||
CanvasEntityIdentifier,
|
||||
CanvasRasterLayerState,
|
||||
CanvasReferenceImageState,
|
||||
CanvasRegionalGuidanceState,
|
||||
Rect,
|
||||
RegionalGuidanceReferenceImageState,
|
||||
RefImageState,
|
||||
RegionalGuidanceRefImageState,
|
||||
} from 'features/controlLayers/store/types';
|
||||
import { imageDTOToImageObject, imageDTOToImageWithDims, initialControlNet } from 'features/controlLayers/store/util';
|
||||
import { selectAutoAddBoardId } from 'features/gallery/store/gallerySelectors';
|
||||
@@ -172,9 +172,9 @@ export const useNewRegionalReferenceImageFromBbox = () => {
|
||||
|
||||
const arg = useMemo<UseSaveCanvasArg>(() => {
|
||||
const onSave = (imageDTO: ImageDTO) => {
|
||||
const ipAdapter: RegionalGuidanceReferenceImageState = {
|
||||
const ipAdapter: RegionalGuidanceRefImageState = {
|
||||
id: getPrefixedId('regional_guidance_reference_image'),
|
||||
ipAdapter: {
|
||||
config: {
|
||||
...deepClone(defaultIPAdapter),
|
||||
image: imageDTOToImageWithDims(imageDTO),
|
||||
},
|
||||
@@ -205,13 +205,13 @@ export const useNewGlobalReferenceImageFromBbox = () => {
|
||||
|
||||
const arg = useMemo<UseSaveCanvasArg>(() => {
|
||||
const onSave = (imageDTO: ImageDTO) => {
|
||||
const overrides: Partial<CanvasReferenceImageState> = {
|
||||
ipAdapter: {
|
||||
const overrides: Partial<RefImageState> = {
|
||||
config: {
|
||||
...deepClone(defaultIPAdapter),
|
||||
image: imageDTOToImageWithDims(imageDTO),
|
||||
},
|
||||
};
|
||||
dispatch(referenceImageAdded({ overrides, isSelected: true }));
|
||||
dispatch(refImageAdded({ overrides, isSelected: true }));
|
||||
};
|
||||
|
||||
return {
|
||||
@@ -311,7 +311,7 @@ export const usePullBboxIntoGlobalReferenceImage = (id: string) => {
|
||||
|
||||
const arg = useMemo<UseSaveCanvasArg>(() => {
|
||||
const onSave = (imageDTO: ImageDTO, _: Rect) => {
|
||||
dispatch(referenceImageIPAdapterImageChanged({ id, imageDTO }));
|
||||
dispatch(refImageImageChanged({ id, imageDTO }));
|
||||
};
|
||||
|
||||
return {
|
||||
@@ -336,7 +336,7 @@ export const usePullBboxIntoRegionalGuidanceReferenceImage = (
|
||||
|
||||
const arg = useMemo<UseSaveCanvasArg>(() => {
|
||||
const onSave = (imageDTO: ImageDTO, _: Rect) => {
|
||||
dispatch(rgIPAdapterImageChanged({ entityIdentifier, referenceImageId, imageDTO }));
|
||||
dispatch(rgRefImageImageChanged({ entityIdentifier, referenceImageId, imageDTO }));
|
||||
};
|
||||
|
||||
return {
|
||||
|
||||
@@ -23,7 +23,7 @@ import type {
|
||||
EntityMovedByPayload,
|
||||
FillStyle,
|
||||
FLUXReduxImageInfluence,
|
||||
RegionalGuidanceReferenceImageState,
|
||||
RegionalGuidanceRefImageState,
|
||||
RgbColor,
|
||||
} from 'features/controlLayers/store/types';
|
||||
import {
|
||||
@@ -38,13 +38,15 @@ import { getGridSize, getIsSizeOptimal, getOptimalDimension } from 'features/par
|
||||
import type { IRect } from 'konva/lib/types';
|
||||
import { merge } from 'lodash-es';
|
||||
import type { UndoableOptions } from 'redux-undo';
|
||||
import type {
|
||||
ControlLoRAModelConfig,
|
||||
ControlNetModelConfig,
|
||||
FLUXReduxModelConfig,
|
||||
ImageDTO,
|
||||
IPAdapterModelConfig,
|
||||
T2IAdapterModelConfig,
|
||||
import {
|
||||
type ControlLoRAModelConfig,
|
||||
type ControlNetModelConfig,
|
||||
type FLUXReduxModelConfig,
|
||||
type ImageDTO,
|
||||
type IPAdapterModelConfig,
|
||||
isFluxReduxModelConfig,
|
||||
isIPAdapterModelConfig,
|
||||
type T2IAdapterModelConfig,
|
||||
} from 'services/api/types';
|
||||
|
||||
import type {
|
||||
@@ -72,7 +74,9 @@ import {
|
||||
getEntityIdentifier,
|
||||
getInitialCanvasState,
|
||||
isChatGPT4oAspectRatioID,
|
||||
isFLUXReduxConfig,
|
||||
isImagenAspectRatioID,
|
||||
isIPAdapterConfig,
|
||||
} from './types';
|
||||
import {
|
||||
converters,
|
||||
@@ -669,12 +673,12 @@ export const canvasSlice = createSlice({
|
||||
}
|
||||
rg.autoNegative = !rg.autoNegative;
|
||||
},
|
||||
rgIPAdapterAdded: {
|
||||
rgRefImageAdded: {
|
||||
reducer: (
|
||||
state,
|
||||
action: PayloadAction<
|
||||
EntityIdentifierPayload<
|
||||
{ referenceImageId: string; overrides?: Partial<RegionalGuidanceReferenceImageState> },
|
||||
{ referenceImageId: string; overrides?: Partial<RegionalGuidanceRefImageState> },
|
||||
'regional_guidance'
|
||||
>
|
||||
>
|
||||
@@ -684,20 +688,17 @@ export const canvasSlice = createSlice({
|
||||
if (!entity) {
|
||||
return;
|
||||
}
|
||||
const ipAdapter = { id: referenceImageId, ipAdapter: deepClone(initialIPAdapter) };
|
||||
merge(ipAdapter, overrides);
|
||||
entity.referenceImages.push(ipAdapter);
|
||||
const config = { id: referenceImageId, config: deepClone(initialIPAdapter) };
|
||||
merge(config, overrides);
|
||||
entity.referenceImages.push(config);
|
||||
},
|
||||
prepare: (
|
||||
payload: EntityIdentifierPayload<
|
||||
{ overrides?: Partial<RegionalGuidanceReferenceImageState> },
|
||||
'regional_guidance'
|
||||
>
|
||||
payload: EntityIdentifierPayload<{ overrides?: Partial<RegionalGuidanceRefImageState> }, 'regional_guidance'>
|
||||
) => ({
|
||||
payload: { ...payload, referenceImageId: getPrefixedId('regional_guidance_ip_adapter') },
|
||||
}),
|
||||
},
|
||||
rgIPAdapterDeleted: (
|
||||
rgRefImageDeleted: (
|
||||
state,
|
||||
action: PayloadAction<EntityIdentifierPayload<{ referenceImageId: string }, 'regional_guidance'>>
|
||||
) => {
|
||||
@@ -706,9 +707,9 @@ export const canvasSlice = createSlice({
|
||||
if (!entity) {
|
||||
return;
|
||||
}
|
||||
entity.referenceImages = entity.referenceImages.filter((ipAdapter) => ipAdapter.id !== referenceImageId);
|
||||
entity.referenceImages = entity.referenceImages.filter((config) => config.id !== referenceImageId);
|
||||
},
|
||||
rgIPAdapterImageChanged: (
|
||||
rgRefImageImageChanged: (
|
||||
state,
|
||||
action: PayloadAction<
|
||||
EntityIdentifierPayload<{ referenceImageId: string; imageDTO: ImageDTO | null }, 'regional_guidance'>
|
||||
@@ -719,9 +720,9 @@ export const canvasSlice = createSlice({
|
||||
if (!referenceImage) {
|
||||
return;
|
||||
}
|
||||
referenceImage.ipAdapter.image = imageDTO ? imageDTOToImageWithDims(imageDTO) : null;
|
||||
referenceImage.config.image = imageDTO ? imageDTOToImageWithDims(imageDTO) : null;
|
||||
},
|
||||
rgIPAdapterWeightChanged: (
|
||||
rgRefImageIPAdapterWeightChanged: (
|
||||
state,
|
||||
action: PayloadAction<EntityIdentifierPayload<{ referenceImageId: string; weight: number }, 'regional_guidance'>>
|
||||
) => {
|
||||
@@ -730,13 +731,13 @@ export const canvasSlice = createSlice({
|
||||
if (!referenceImage) {
|
||||
return;
|
||||
}
|
||||
if (referenceImage.ipAdapter.type !== 'ip_adapter') {
|
||||
if (!isIPAdapterConfig(referenceImage.config)) {
|
||||
return;
|
||||
}
|
||||
|
||||
referenceImage.ipAdapter.weight = weight;
|
||||
referenceImage.config.weight = weight;
|
||||
},
|
||||
rgIPAdapterBeginEndStepPctChanged: (
|
||||
rgRefImageIPAdapterBeginEndStepPctChanged: (
|
||||
state,
|
||||
action: PayloadAction<
|
||||
EntityIdentifierPayload<{ referenceImageId: string; beginEndStepPct: [number, number] }, 'regional_guidance'>
|
||||
@@ -747,13 +748,12 @@ export const canvasSlice = createSlice({
|
||||
if (!referenceImage) {
|
||||
return;
|
||||
}
|
||||
if (referenceImage.ipAdapter.type !== 'ip_adapter') {
|
||||
if (!isIPAdapterConfig(referenceImage.config)) {
|
||||
return;
|
||||
}
|
||||
|
||||
referenceImage.ipAdapter.beginEndStepPct = beginEndStepPct;
|
||||
referenceImage.config.beginEndStepPct = beginEndStepPct;
|
||||
},
|
||||
rgIPAdapterMethodChanged: (
|
||||
rgRefImageIPAdapterMethodChanged: (
|
||||
state,
|
||||
action: PayloadAction<
|
||||
EntityIdentifierPayload<{ referenceImageId: string; method: IPMethodV2 }, 'regional_guidance'>
|
||||
@@ -764,13 +764,12 @@ export const canvasSlice = createSlice({
|
||||
if (!referenceImage) {
|
||||
return;
|
||||
}
|
||||
if (referenceImage.ipAdapter.type !== 'ip_adapter') {
|
||||
if (!isIPAdapterConfig(referenceImage.config)) {
|
||||
return;
|
||||
}
|
||||
|
||||
referenceImage.ipAdapter.method = method;
|
||||
referenceImage.config.method = method;
|
||||
},
|
||||
rgIPAdapterFLUXReduxImageInfluenceChanged: (
|
||||
rgRefImageFLUXReduxImageInfluenceChanged: (
|
||||
state,
|
||||
action: PayloadAction<
|
||||
EntityIdentifierPayload<
|
||||
@@ -784,13 +783,13 @@ export const canvasSlice = createSlice({
|
||||
if (!referenceImage) {
|
||||
return;
|
||||
}
|
||||
if (referenceImage.ipAdapter.type !== 'flux_redux') {
|
||||
if (!isFLUXReduxConfig(referenceImage.config)) {
|
||||
return;
|
||||
}
|
||||
|
||||
referenceImage.ipAdapter.imageInfluence = imageInfluence;
|
||||
referenceImage.config.imageInfluence = imageInfluence;
|
||||
},
|
||||
rgIPAdapterModelChanged: (
|
||||
rgRefImageModelChanged: (
|
||||
state,
|
||||
action: PayloadAction<
|
||||
EntityIdentifierPayload<
|
||||
@@ -807,43 +806,43 @@ export const canvasSlice = createSlice({
|
||||
if (!referenceImage) {
|
||||
return;
|
||||
}
|
||||
referenceImage.ipAdapter.model = modelConfig ? zModelIdentifierField.parse(modelConfig) : null;
|
||||
|
||||
if (!referenceImage.ipAdapter.model) {
|
||||
if (!modelConfig) {
|
||||
referenceImage.config.model = null;
|
||||
return;
|
||||
}
|
||||
|
||||
if (referenceImage.ipAdapter.type === 'ip_adapter' && referenceImage.ipAdapter.model.type === 'flux_redux') {
|
||||
if (isIPAdapterConfig(referenceImage.config) && isFluxReduxModelConfig(modelConfig)) {
|
||||
// Switching from ip_adapter to flux_redux
|
||||
referenceImage.ipAdapter = {
|
||||
referenceImage.config = {
|
||||
...initialFLUXRedux,
|
||||
image: referenceImage.ipAdapter.image,
|
||||
model: referenceImage.ipAdapter.model,
|
||||
image: referenceImage.config.image,
|
||||
model: zModelIdentifierField.parse(modelConfig),
|
||||
};
|
||||
return;
|
||||
}
|
||||
|
||||
if (referenceImage.ipAdapter.type === 'flux_redux' && referenceImage.ipAdapter.model.type === 'ip_adapter') {
|
||||
if (isFLUXReduxConfig(referenceImage.config) && isIPAdapterModelConfig(modelConfig)) {
|
||||
// Switching from flux_redux to ip_adapter
|
||||
referenceImage.ipAdapter = {
|
||||
referenceImage.config = {
|
||||
...initialIPAdapter,
|
||||
image: referenceImage.ipAdapter.image,
|
||||
model: referenceImage.ipAdapter.model,
|
||||
image: referenceImage.config.image,
|
||||
model: zModelIdentifierField.parse(modelConfig),
|
||||
};
|
||||
return;
|
||||
}
|
||||
|
||||
if (referenceImage.ipAdapter.type === 'ip_adapter') {
|
||||
if (isIPAdapterConfig(referenceImage.config)) {
|
||||
// Ensure that the IP Adapter model is compatible with the CLIP Vision model
|
||||
if (referenceImage.ipAdapter.model?.base === 'flux') {
|
||||
referenceImage.ipAdapter.clipVisionModel = 'ViT-L';
|
||||
} else if (referenceImage.ipAdapter.clipVisionModel === 'ViT-L') {
|
||||
if (referenceImage.config.model?.base === 'flux') {
|
||||
referenceImage.config.clipVisionModel = 'ViT-L';
|
||||
} else if (referenceImage.config.clipVisionModel === 'ViT-L') {
|
||||
// Fall back to ViT-H (ViT-G would also work)
|
||||
referenceImage.ipAdapter.clipVisionModel = 'ViT-H';
|
||||
referenceImage.config.clipVisionModel = 'ViT-H';
|
||||
}
|
||||
}
|
||||
},
|
||||
rgIPAdapterCLIPVisionModelChanged: (
|
||||
rgRefImageIPAdapterCLIPVisionModelChanged: (
|
||||
state,
|
||||
action: PayloadAction<
|
||||
EntityIdentifierPayload<{ referenceImageId: string; clipVisionModel: CLIPVisionModelV2 }, 'regional_guidance'>
|
||||
@@ -854,11 +853,10 @@ export const canvasSlice = createSlice({
|
||||
if (!referenceImage) {
|
||||
return;
|
||||
}
|
||||
if (referenceImage.ipAdapter.type !== 'ip_adapter') {
|
||||
if (!isIPAdapterConfig(referenceImage.config)) {
|
||||
return;
|
||||
}
|
||||
|
||||
referenceImage.ipAdapter.clipVisionModel = clipVisionModel;
|
||||
referenceImage.config.clipVisionModel = clipVisionModel;
|
||||
},
|
||||
//#region Inpaint mask
|
||||
inpaintMaskAdded: {
|
||||
@@ -1660,15 +1658,15 @@ export const {
|
||||
rgPositivePromptChanged,
|
||||
rgNegativePromptChanged,
|
||||
rgAutoNegativeToggled,
|
||||
rgIPAdapterAdded,
|
||||
rgIPAdapterDeleted,
|
||||
rgIPAdapterImageChanged,
|
||||
rgIPAdapterWeightChanged,
|
||||
rgIPAdapterBeginEndStepPctChanged,
|
||||
rgIPAdapterMethodChanged,
|
||||
rgIPAdapterModelChanged,
|
||||
rgIPAdapterCLIPVisionModelChanged,
|
||||
rgIPAdapterFLUXReduxImageInfluenceChanged,
|
||||
rgRefImageAdded,
|
||||
rgRefImageDeleted,
|
||||
rgRefImageImageChanged,
|
||||
rgRefImageIPAdapterWeightChanged,
|
||||
rgRefImageIPAdapterBeginEndStepPctChanged,
|
||||
rgRefImageIPAdapterMethodChanged,
|
||||
rgRefImageModelChanged,
|
||||
rgRefImageIPAdapterCLIPVisionModelChanged,
|
||||
rgRefImageFLUXReduxImageInfluenceChanged,
|
||||
// Inpaint mask
|
||||
inpaintMaskAdded,
|
||||
inpaintMaskConvertedToRegionalGuidance,
|
||||
|
||||
@@ -2,7 +2,6 @@ import type { PayloadAction } from '@reduxjs/toolkit';
|
||||
import { createSelector, createSlice } from '@reduxjs/toolkit';
|
||||
import { createMemoizedSelector } from 'app/store/createMemoizedSelector';
|
||||
import type { PersistConfig, RootState } from 'app/store/store';
|
||||
import { deepClone } from 'common/util/deepClone';
|
||||
import { getPrefixedId } from 'features/controlLayers/konva/util';
|
||||
import { canvasMetadataRecalled } from 'features/controlLayers/store/canvasSlice';
|
||||
import type { FLUXReduxImageInfluence, RefImagesState } from 'features/controlLayers/store/types';
|
||||
@@ -12,8 +11,8 @@ import type { ApiModelConfig, FLUXReduxModelConfig, ImageDTO, IPAdapterModelConf
|
||||
import { assert } from 'tsafe';
|
||||
import type { PartialDeep } from 'type-fest';
|
||||
|
||||
import type { CanvasReferenceImageState, CLIPVisionModelV2, IPMethodV2 } from './types';
|
||||
import { getInitialRefImagesState } from './types';
|
||||
import type { CLIPVisionModelV2, IPMethodV2, RefImageState } from './types';
|
||||
import { getInitialRefImagesState, isFLUXReduxConfig, isIPAdapterConfig } from './types';
|
||||
import {
|
||||
getReferenceImageState,
|
||||
imageDTOToImageWithDims,
|
||||
@@ -22,84 +21,69 @@ import {
|
||||
initialIPAdapter,
|
||||
} from './util';
|
||||
|
||||
type PayloadWithId<T = void> = T extends void
|
||||
? { id: string }
|
||||
: {
|
||||
id: string;
|
||||
} & T;
|
||||
type PayloadActionWithId<T = void> = T extends void
|
||||
? PayloadAction<{ id: string }>
|
||||
: PayloadAction<
|
||||
{
|
||||
id: string;
|
||||
} & T
|
||||
>;
|
||||
|
||||
export const refImagesSlice = createSlice({
|
||||
name: 'refImages',
|
||||
initialState: getInitialRefImagesState(),
|
||||
reducers: {
|
||||
referenceImageAdded: {
|
||||
reducer: (
|
||||
state,
|
||||
action: PayloadAction<{
|
||||
id: string;
|
||||
overrides?: PartialDeep<CanvasReferenceImageState>;
|
||||
isSelected?: boolean;
|
||||
}>
|
||||
) => {
|
||||
const { id, overrides, isSelected } = action.payload;
|
||||
refImageAdded: {
|
||||
reducer: (state, action: PayloadActionWithId<{ overrides?: PartialDeep<RefImageState> }>) => {
|
||||
const { id, overrides } = action.payload;
|
||||
const entityState = getReferenceImageState(id, overrides);
|
||||
|
||||
state.entities.push(entityState);
|
||||
|
||||
if (isSelected) {
|
||||
state.selectedId = entityState.id;
|
||||
}
|
||||
},
|
||||
prepare: (payload?: { overrides?: PartialDeep<CanvasReferenceImageState>; isSelected?: boolean }) => ({
|
||||
prepare: (payload?: { overrides?: PartialDeep<RefImageState>; isSelected?: boolean }) => ({
|
||||
payload: { ...payload, id: getPrefixedId('reference_image') },
|
||||
}),
|
||||
},
|
||||
referenceImageRecalled: (state, action: PayloadAction<{ data: CanvasReferenceImageState }>) => {
|
||||
refImageRecalled: (state, action: PayloadAction<{ data: RefImageState }>) => {
|
||||
const { data } = action.payload;
|
||||
state.entities.push(data);
|
||||
state.selectedId = data.id;
|
||||
},
|
||||
referenceImageIPAdapterImageChanged: (
|
||||
state,
|
||||
action: PayloadAction<PayloadWithId<{ imageDTO: ImageDTO | null }>>
|
||||
) => {
|
||||
refImageImageChanged: (state, action: PayloadActionWithId<{ imageDTO: ImageDTO | null }>) => {
|
||||
const { id, imageDTO } = action.payload;
|
||||
const entity = selectRefImageEntity(state, id);
|
||||
if (!entity) {
|
||||
return;
|
||||
}
|
||||
entity.ipAdapter.image = imageDTO ? imageDTOToImageWithDims(imageDTO) : null;
|
||||
entity.config.image = imageDTO ? imageDTOToImageWithDims(imageDTO) : null;
|
||||
},
|
||||
referenceImageIPAdapterMethodChanged: (state, action: PayloadAction<PayloadWithId<{ method: IPMethodV2 }>>) => {
|
||||
refImageIPAdapterMethodChanged: (state, action: PayloadActionWithId<{ method: IPMethodV2 }>) => {
|
||||
const { id, method } = action.payload;
|
||||
const entity = selectRefImageEntity(state, id);
|
||||
if (!entity) {
|
||||
return;
|
||||
}
|
||||
if (entity.ipAdapter.type !== 'ip_adapter') {
|
||||
if (!isIPAdapterConfig(entity.config)) {
|
||||
return;
|
||||
}
|
||||
entity.ipAdapter.method = method;
|
||||
entity.config.method = method;
|
||||
},
|
||||
referenceImageIPAdapterFLUXReduxImageInfluenceChanged: (
|
||||
refImageFLUXReduxImageInfluenceChanged: (
|
||||
state,
|
||||
action: PayloadAction<PayloadWithId<{ imageInfluence: FLUXReduxImageInfluence }>>
|
||||
action: PayloadActionWithId<{ imageInfluence: FLUXReduxImageInfluence }>
|
||||
) => {
|
||||
const { id, imageInfluence } = action.payload;
|
||||
const entity = selectRefImageEntity(state, id);
|
||||
if (!entity) {
|
||||
return;
|
||||
}
|
||||
if (entity.ipAdapter.type !== 'flux_redux') {
|
||||
if (!isFLUXReduxConfig(entity.config)) {
|
||||
return;
|
||||
}
|
||||
entity.ipAdapter.imageInfluence = imageInfluence;
|
||||
entity.config.imageInfluence = imageInfluence;
|
||||
},
|
||||
referenceImageIPAdapterModelChanged: (
|
||||
refImageModelChanged: (
|
||||
state,
|
||||
action: PayloadAction<
|
||||
PayloadWithId<{ modelConfig: IPAdapterModelConfig | FLUXReduxModelConfig | ApiModelConfig | null }>
|
||||
>
|
||||
action: PayloadActionWithId<{ modelConfig: IPAdapterModelConfig | FLUXReduxModelConfig | ApiModelConfig | null }>
|
||||
) => {
|
||||
const { id, modelConfig } = action.payload;
|
||||
const entity = selectRefImageEntity(state, id);
|
||||
@@ -107,16 +91,16 @@ export const refImagesSlice = createSlice({
|
||||
return;
|
||||
}
|
||||
|
||||
const oldModel = entity.ipAdapter.model;
|
||||
const oldModel = entity.config.model;
|
||||
|
||||
// First set the new model
|
||||
entity.ipAdapter.model = modelConfig ? zModelIdentifierField.parse(modelConfig) : null;
|
||||
entity.config.model = modelConfig ? zModelIdentifierField.parse(modelConfig) : null;
|
||||
|
||||
if (!entity.ipAdapter.model) {
|
||||
if (!entity.config.model) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (isEqual(oldModel, entity.ipAdapter.model)) {
|
||||
if (isEqual(oldModel, entity.config.model)) {
|
||||
// Nothing changed, so we don't need to do anything
|
||||
return;
|
||||
}
|
||||
@@ -124,147 +108,85 @@ export const refImagesSlice = createSlice({
|
||||
// The type of ref image depends on the model. When the user switches the model, we rebuild the ref image.
|
||||
// When we switch the model, we keep the image the same, but change the other parameters.
|
||||
|
||||
if (entity.ipAdapter.model.base === 'chatgpt-4o') {
|
||||
if (entity.config.model.base === 'chatgpt-4o') {
|
||||
// Switching to chatgpt-4o ref image
|
||||
entity.ipAdapter = {
|
||||
entity.config = {
|
||||
...initialChatGPT4oReferenceImage,
|
||||
image: entity.ipAdapter.image,
|
||||
model: entity.ipAdapter.model,
|
||||
image: entity.config.image,
|
||||
model: entity.config.model,
|
||||
};
|
||||
return;
|
||||
}
|
||||
|
||||
if (entity.ipAdapter.model.type === 'flux_redux') {
|
||||
if (entity.config.model.type === 'flux_redux') {
|
||||
// Switching to flux_redux
|
||||
entity.ipAdapter = {
|
||||
entity.config = {
|
||||
...initialFLUXRedux,
|
||||
image: entity.ipAdapter.image,
|
||||
model: entity.ipAdapter.model,
|
||||
image: entity.config.image,
|
||||
model: entity.config.model,
|
||||
};
|
||||
return;
|
||||
}
|
||||
|
||||
if (entity.ipAdapter.model.type === 'ip_adapter') {
|
||||
if (entity.config.model.type === 'ip_adapter') {
|
||||
// Switching to ip_adapter
|
||||
entity.ipAdapter = {
|
||||
entity.config = {
|
||||
...initialIPAdapter,
|
||||
image: entity.ipAdapter.image,
|
||||
model: entity.ipAdapter.model,
|
||||
image: entity.config.image,
|
||||
model: entity.config.model,
|
||||
};
|
||||
// Ensure that the IP Adapter model is compatible with the CLIP Vision model
|
||||
if (entity.ipAdapter.model?.base === 'flux') {
|
||||
entity.ipAdapter.clipVisionModel = 'ViT-L';
|
||||
} else if (entity.ipAdapter.clipVisionModel === 'ViT-L') {
|
||||
if (entity.config.model?.base === 'flux') {
|
||||
entity.config.clipVisionModel = 'ViT-L';
|
||||
} else if (entity.config.clipVisionModel === 'ViT-L') {
|
||||
// Fall back to ViT-H (ViT-G would also work)
|
||||
entity.ipAdapter.clipVisionModel = 'ViT-H';
|
||||
entity.config.clipVisionModel = 'ViT-H';
|
||||
}
|
||||
return;
|
||||
}
|
||||
},
|
||||
referenceImageIPAdapterCLIPVisionModelChanged: (
|
||||
refImageIPAdapterCLIPVisionModelChanged: (
|
||||
state,
|
||||
action: PayloadAction<PayloadWithId<{ clipVisionModel: CLIPVisionModelV2 }>>
|
||||
action: PayloadActionWithId<{ clipVisionModel: CLIPVisionModelV2 }>
|
||||
) => {
|
||||
const { id, clipVisionModel } = action.payload;
|
||||
const entity = selectRefImageEntity(state, id);
|
||||
if (!entity) {
|
||||
return;
|
||||
}
|
||||
if (entity.ipAdapter.type !== 'ip_adapter') {
|
||||
if (!isIPAdapterConfig(entity.config)) {
|
||||
return;
|
||||
}
|
||||
entity.ipAdapter.clipVisionModel = clipVisionModel;
|
||||
entity.config.clipVisionModel = clipVisionModel;
|
||||
},
|
||||
referenceImageIPAdapterWeightChanged: (state, action: PayloadAction<PayloadWithId<{ weight: number }>>) => {
|
||||
refImageIPAdapterWeightChanged: (state, action: PayloadActionWithId<{ weight: number }>) => {
|
||||
const { id, weight } = action.payload;
|
||||
const entity = selectRefImageEntity(state, id);
|
||||
if (!entity) {
|
||||
return;
|
||||
}
|
||||
if (entity.ipAdapter.type !== 'ip_adapter') {
|
||||
if (!isIPAdapterConfig(entity.config)) {
|
||||
return;
|
||||
}
|
||||
entity.ipAdapter.weight = weight;
|
||||
entity.config.weight = weight;
|
||||
},
|
||||
referenceImageIPAdapterBeginEndStepPctChanged: (
|
||||
refImageIPAdapterBeginEndStepPctChanged: (
|
||||
state,
|
||||
action: PayloadAction<PayloadWithId<{ beginEndStepPct: [number, number] }>>
|
||||
action: PayloadActionWithId<{ beginEndStepPct: [number, number] }>
|
||||
) => {
|
||||
const { id, beginEndStepPct } = action.payload;
|
||||
const entity = selectRefImageEntity(state, id);
|
||||
if (!entity) {
|
||||
return;
|
||||
}
|
||||
if (entity.ipAdapter.type !== 'ip_adapter') {
|
||||
if (!isIPAdapterConfig(entity.config)) {
|
||||
return;
|
||||
}
|
||||
entity.ipAdapter.beginEndStepPct = beginEndStepPct;
|
||||
entity.config.beginEndStepPct = beginEndStepPct;
|
||||
},
|
||||
//#region Shared entity
|
||||
entitySelected: (state, action: PayloadAction<{ id: string }>) => {
|
||||
refImageDeleted: (state, action: PayloadActionWithId) => {
|
||||
const { id } = action.payload;
|
||||
const entity = selectRefImageEntity(state, id);
|
||||
if (!entity) {
|
||||
// Cannot select a non-existent entity
|
||||
return;
|
||||
}
|
||||
state.selectedId = id;
|
||||
},
|
||||
entityNameChanged: (state, action: PayloadAction<PayloadWithId<{ name: string | null }>>) => {
|
||||
const { id, name } = action.payload;
|
||||
const entity = selectRefImageEntity(state, id);
|
||||
if (!entity) {
|
||||
return;
|
||||
}
|
||||
entity.name = name;
|
||||
},
|
||||
entityDuplicated: (state, action: PayloadAction<PayloadWithId>) => {
|
||||
const { id } = action.payload;
|
||||
const entity = selectRefImageEntity(state, id);
|
||||
if (!entity) {
|
||||
return;
|
||||
}
|
||||
|
||||
const newEntity = deepClone(entity);
|
||||
if (newEntity.name) {
|
||||
newEntity.name = `${newEntity.name} (Copy)`;
|
||||
}
|
||||
newEntity.id = getPrefixedId('reference_image');
|
||||
state.entities.push(newEntity);
|
||||
|
||||
state.selectedId = newEntity.id;
|
||||
},
|
||||
entityIsEnabledToggled: (state, action: PayloadAction<PayloadWithId>) => {
|
||||
const { id } = action.payload;
|
||||
const entity = selectRefImageEntity(state, id);
|
||||
if (!entity) {
|
||||
return;
|
||||
}
|
||||
entity.isEnabled = !entity.isEnabled;
|
||||
},
|
||||
entityIsLockedToggled: (state, action: PayloadAction<PayloadWithId>) => {
|
||||
const { id } = action.payload;
|
||||
const entity = selectRefImageEntity(state, id);
|
||||
if (!entity) {
|
||||
return;
|
||||
}
|
||||
entity.isLocked = !entity.isLocked;
|
||||
},
|
||||
entityDeleted: (state, action: PayloadAction<PayloadWithId>) => {
|
||||
const { id } = action.payload;
|
||||
|
||||
let selectedId: string | null = null;
|
||||
const entities = state.entities;
|
||||
const index = entities.findIndex((entity) => entity.id === id);
|
||||
const nextIndex = entities.length > 1 ? (index + 1) % entities.length : -1;
|
||||
if (nextIndex !== -1) {
|
||||
const nextEntity = entities[nextIndex];
|
||||
if (nextEntity) {
|
||||
selectedId = nextEntity.id;
|
||||
}
|
||||
}
|
||||
state.entities = state.entities.filter((rg) => rg.id !== id);
|
||||
state.selectedId = selectedId;
|
||||
},
|
||||
refImagesReset: () => getInitialRefImagesState(),
|
||||
},
|
||||
@@ -277,15 +199,14 @@ export const refImagesSlice = createSlice({
|
||||
});
|
||||
|
||||
export const {
|
||||
referenceImageAdded,
|
||||
// referenceImageRecalled,
|
||||
referenceImageIPAdapterImageChanged,
|
||||
referenceImageIPAdapterMethodChanged,
|
||||
referenceImageIPAdapterModelChanged,
|
||||
referenceImageIPAdapterCLIPVisionModelChanged,
|
||||
referenceImageIPAdapterWeightChanged,
|
||||
referenceImageIPAdapterBeginEndStepPctChanged,
|
||||
referenceImageIPAdapterFLUXReduxImageInfluenceChanged,
|
||||
refImageAdded,
|
||||
refImageImageChanged,
|
||||
refImageIPAdapterMethodChanged,
|
||||
refImageModelChanged,
|
||||
refImageIPAdapterCLIPVisionModelChanged,
|
||||
refImageIPAdapterWeightChanged,
|
||||
refImageIPAdapterBeginEndStepPctChanged,
|
||||
refImageFLUXReduxImageInfluenceChanged,
|
||||
} = refImagesSlice.actions;
|
||||
|
||||
/* eslint-disable-next-line @typescript-eslint/no-explicit-any */
|
||||
@@ -303,20 +224,13 @@ export const refImagesPersistConfig: PersistConfig<RefImagesState> = {
|
||||
export const selectRefImagesSlice = (state: RootState) => state.refImages;
|
||||
|
||||
export const selectReferenceImageEntities = createSelector(selectRefImagesSlice, (state) => state.entities);
|
||||
export const selectActiveReferenceImageEntities = createSelector(selectReferenceImageEntities, (entities) =>
|
||||
entities.filter((e) => e.isEnabled)
|
||||
);
|
||||
export const selectRefImageEntityIds = createMemoizedSelector(selectReferenceImageEntities, (entities) =>
|
||||
entities.map((e) => e.id)
|
||||
);
|
||||
export const selectRefImageEntity = (state: RefImagesState, id: string) =>
|
||||
state.entities.find((entity) => entity.id === id) ?? null;
|
||||
|
||||
export function selectRefImageEntityOrThrow(
|
||||
state: RefImagesState,
|
||||
id: string,
|
||||
caller: string
|
||||
): CanvasReferenceImageState {
|
||||
export function selectRefImageEntityOrThrow(state: RefImagesState, id: string, caller: string): RefImageState {
|
||||
const entity = selectRefImageEntity(state, id);
|
||||
assert(entity, `Entity with id ${id} not found in ${caller}`);
|
||||
return entity;
|
||||
|
||||
@@ -293,20 +293,20 @@ const zCanvasEntityBase = z.object({
|
||||
isLocked: z.boolean(),
|
||||
});
|
||||
|
||||
const zCanvasReferenceImageState = zCanvasEntityBase.extend({
|
||||
type: z.literal('reference_image'),
|
||||
const zRefImageState = z.object({
|
||||
id: zId,
|
||||
// This should be named `referenceImage` but we need to keep it as `ipAdapter` for backwards compatibility
|
||||
ipAdapter: z.discriminatedUnion('type', [zIPAdapterConfig, zFLUXReduxConfig, zChatGPT4oReferenceImageConfig]),
|
||||
config: z.discriminatedUnion('type', [zIPAdapterConfig, zFLUXReduxConfig, zChatGPT4oReferenceImageConfig]),
|
||||
});
|
||||
export type CanvasReferenceImageState = z.infer<typeof zCanvasReferenceImageState>;
|
||||
export type RefImageState = z.infer<typeof zRefImageState>;
|
||||
|
||||
export const isIPAdapterConfig = (config: CanvasReferenceImageState['ipAdapter']): config is IPAdapterConfig =>
|
||||
export const isIPAdapterConfig = (config: RefImageState['config']): config is IPAdapterConfig =>
|
||||
config.type === 'ip_adapter';
|
||||
|
||||
export const isFLUXReduxConfig = (config: CanvasReferenceImageState['ipAdapter']): config is FLUXReduxConfig =>
|
||||
export const isFLUXReduxConfig = (config: RefImageState['config']): config is FLUXReduxConfig =>
|
||||
config.type === 'flux_redux';
|
||||
export const isChatGPT4oReferenceImageConfig = (
|
||||
config: CanvasReferenceImageState['ipAdapter']
|
||||
config: RefImageState['config']
|
||||
): config is ChatGPT4oReferenceImageConfig => config.type === 'chatgpt_4o_reference_image';
|
||||
|
||||
const zFillStyle = z.enum(['solid', 'grid', 'crosshatch', 'diagonal', 'horizontal', 'vertical']);
|
||||
@@ -314,11 +314,11 @@ export type FillStyle = z.infer<typeof zFillStyle>;
|
||||
export const isFillStyle = (v: unknown): v is FillStyle => zFillStyle.safeParse(v).success;
|
||||
const zFill = z.object({ style: zFillStyle, color: zRgbColor });
|
||||
|
||||
const zRegionalGuidanceReferenceImageState = z.object({
|
||||
const zRegionalGuidanceRefImageState = z.object({
|
||||
id: zId,
|
||||
ipAdapter: z.discriminatedUnion('type', [zIPAdapterConfig, zFLUXReduxConfig]),
|
||||
config: z.discriminatedUnion('type', [zIPAdapterConfig, zFLUXReduxConfig]),
|
||||
});
|
||||
export type RegionalGuidanceReferenceImageState = z.infer<typeof zRegionalGuidanceReferenceImageState>;
|
||||
export type RegionalGuidanceRefImageState = z.infer<typeof zRegionalGuidanceRefImageState>;
|
||||
|
||||
const zCanvasRegionalGuidanceState = zCanvasEntityBase.extend({
|
||||
type: z.literal('regional_guidance'),
|
||||
@@ -328,7 +328,7 @@ const zCanvasRegionalGuidanceState = zCanvasEntityBase.extend({
|
||||
fill: zFill,
|
||||
positivePrompt: zParameterPositivePrompt.nullable(),
|
||||
negativePrompt: zParameterNegativePrompt.nullable(),
|
||||
referenceImages: z.array(zRegionalGuidanceReferenceImageState),
|
||||
referenceImages: z.array(zRegionalGuidanceRefImageState),
|
||||
autoNegative: z.boolean(),
|
||||
});
|
||||
export type CanvasRegionalGuidanceState = z.infer<typeof zCanvasRegionalGuidanceState>;
|
||||
@@ -559,8 +559,7 @@ const zCanvasState = z.object({
|
||||
export type CanvasState = z.infer<typeof zCanvasState>;
|
||||
|
||||
const zRefImagesState = z.object({
|
||||
selectedId: zId.nullable().default(null),
|
||||
entities: z.array(zCanvasReferenceImageState).default(() => []),
|
||||
entities: z.array(zRefImageState).default(() => []),
|
||||
});
|
||||
export type RefImagesState = z.infer<typeof zRefImagesState>;
|
||||
const INITIAL_REF_IMAGES_STATE = zRefImagesState.parse({});
|
||||
@@ -577,7 +576,7 @@ export const zCanvasMetadata = z.object({
|
||||
rasterLayers: z.array(zCanvasRasterLayerState),
|
||||
controlLayers: z.array(zCanvasControlLayerState),
|
||||
regionalGuidance: z.array(zCanvasRegionalGuidanceState),
|
||||
referenceImages: z.array(zCanvasReferenceImageState),
|
||||
referenceImages: z.array(zRefImageState),
|
||||
});
|
||||
export type CanvasMetadata = z.infer<typeof zCanvasMetadata>;
|
||||
|
||||
|
||||
@@ -5,7 +5,6 @@ import type {
|
||||
CanvasImageState,
|
||||
CanvasInpaintMaskState,
|
||||
CanvasRasterLayerState,
|
||||
CanvasReferenceImageState,
|
||||
CanvasRegionalGuidanceState,
|
||||
ChatGPT4oReferenceImageConfig,
|
||||
ControlLoRAConfig,
|
||||
@@ -14,6 +13,7 @@ import type {
|
||||
FLUXReduxConfig,
|
||||
ImageWithDims,
|
||||
IPAdapterConfig,
|
||||
RefImageState,
|
||||
RgbColor,
|
||||
T2IAdapterConfig,
|
||||
} from 'features/controlLayers/store/types';
|
||||
@@ -120,17 +120,10 @@ export const initialControlLoRA: ControlLoRAConfig = {
|
||||
weight: 0.75,
|
||||
};
|
||||
|
||||
export const getReferenceImageState = (
|
||||
id: string,
|
||||
overrides?: PartialDeep<CanvasReferenceImageState>
|
||||
): CanvasReferenceImageState => {
|
||||
const entityState: CanvasReferenceImageState = {
|
||||
export const getReferenceImageState = (id: string, overrides?: PartialDeep<RefImageState>): RefImageState => {
|
||||
const entityState: RefImageState = {
|
||||
id,
|
||||
type: 'reference_image',
|
||||
name: null,
|
||||
isLocked: false,
|
||||
isEnabled: true,
|
||||
ipAdapter: deepClone(initialIPAdapter),
|
||||
config: deepClone(initialIPAdapter),
|
||||
};
|
||||
merge(entityState, overrides);
|
||||
return entityState;
|
||||
|
||||
@@ -2,8 +2,8 @@ import type {
|
||||
CanvasControlLayerState,
|
||||
CanvasInpaintMaskState,
|
||||
CanvasRasterLayerState,
|
||||
CanvasReferenceImageState,
|
||||
CanvasRegionalGuidanceState,
|
||||
RefImageState,
|
||||
} from 'features/controlLayers/store/types';
|
||||
import type { MainModelConfig } from 'services/api/types';
|
||||
|
||||
@@ -58,16 +58,16 @@ export const getRegionalGuidanceWarnings = (
|
||||
}
|
||||
}
|
||||
|
||||
entity.referenceImages.forEach(({ ipAdapter }) => {
|
||||
if (!ipAdapter.model) {
|
||||
entity.referenceImages.forEach(({ config }) => {
|
||||
if (!config.model) {
|
||||
// No model selected
|
||||
warnings.push(WARNINGS.IP_ADAPTER_NO_MODEL_SELECTED);
|
||||
} else if (ipAdapter.model.base !== model.base) {
|
||||
} else if (config.model.base !== model.base) {
|
||||
// Supported model architecture but doesn't match
|
||||
warnings.push(WARNINGS.IP_ADAPTER_INCOMPATIBLE_BASE_MODEL);
|
||||
}
|
||||
|
||||
if (!ipAdapter.image) {
|
||||
if (!config.image) {
|
||||
// No image selected
|
||||
warnings.push(WARNINGS.IP_ADAPTER_NO_IMAGE_SELECTED);
|
||||
}
|
||||
@@ -78,7 +78,7 @@ export const getRegionalGuidanceWarnings = (
|
||||
};
|
||||
|
||||
export const getGlobalReferenceImageWarnings = (
|
||||
entity: CanvasReferenceImageState,
|
||||
entity: RefImageState,
|
||||
model: MainModelConfig | null | undefined
|
||||
): WarningTKey[] => {
|
||||
const warnings: WarningTKey[] = [];
|
||||
@@ -90,17 +90,17 @@ export const getGlobalReferenceImageWarnings = (
|
||||
return warnings;
|
||||
}
|
||||
|
||||
const { ipAdapter } = entity;
|
||||
const { config } = entity;
|
||||
|
||||
if (!ipAdapter.model) {
|
||||
if (!config.model) {
|
||||
// No model selected
|
||||
warnings.push(WARNINGS.IP_ADAPTER_NO_MODEL_SELECTED);
|
||||
} else if (ipAdapter.model.base !== model.base) {
|
||||
} else if (config.model.base !== model.base) {
|
||||
// Supported model architecture but doesn't match
|
||||
warnings.push(WARNINGS.IP_ADAPTER_INCOMPATIBLE_BASE_MODEL);
|
||||
}
|
||||
|
||||
if (!entity.ipAdapter.image) {
|
||||
if (!entity.config.image) {
|
||||
// No image selected
|
||||
warnings.push(WARNINGS.IP_ADAPTER_NO_IMAGE_SELECTED);
|
||||
}
|
||||
|
||||
@@ -3,7 +3,7 @@ import { getStore, useAppStore } from 'app/store/nanostores/store';
|
||||
import type { AppDispatch, AppGetState, RootState } from 'app/store/store';
|
||||
import { entityDeleted } from 'features/controlLayers/store/canvasSlice';
|
||||
import {
|
||||
referenceImageIPAdapterImageChanged,
|
||||
refImageImageChanged,
|
||||
selectReferenceImageEntities,
|
||||
selectRefImagesSlice,
|
||||
} from 'features/controlLayers/store/refImagesSlice';
|
||||
@@ -228,8 +228,8 @@ const deleteControlLayerImages = (state: RootState, dispatch: AppDispatch, image
|
||||
|
||||
const deleteReferenceImages = (state: RootState, dispatch: AppDispatch, imageDTO: ImageDTO) => {
|
||||
selectReferenceImageEntities(state).forEach((entity) => {
|
||||
if (entity.ipAdapter.image?.image_name === imageDTO.image_name) {
|
||||
dispatch(referenceImageIPAdapterImageChanged({ id: entity.id, imageDTO: null }));
|
||||
if (entity.config.image?.image_name === imageDTO.image_name) {
|
||||
dispatch(refImageImageChanged({ id: entity.id, imageDTO: null }));
|
||||
}
|
||||
});
|
||||
};
|
||||
@@ -276,7 +276,7 @@ export const getImageUsage = (
|
||||
|
||||
const isUpscaleImage = upscale.upscaleInitialImage?.image_name === image_name;
|
||||
|
||||
const isReferenceImage = refImages.entities.some(({ ipAdapter }) => ipAdapter.image?.image_name === image_name);
|
||||
const isReferenceImage = refImages.entities.some(({ config }) => config.image?.image_name === image_name);
|
||||
|
||||
const isRasterLayerImage = canvas.rasterLayers.entities.some(({ objects }) =>
|
||||
objects.some((obj) => obj.type === 'image' && 'image_name' in obj.image && obj.image.image_name === image_name)
|
||||
@@ -291,7 +291,7 @@ export const getImageUsage = (
|
||||
);
|
||||
|
||||
const isRegionalGuidanceImage = canvas.regionalGuidance.entities.some(({ referenceImages }) =>
|
||||
referenceImages.some(({ ipAdapter }) => ipAdapter.image?.image_name === image_name)
|
||||
referenceImages.some(({ config }) => config.image?.image_name === image_name)
|
||||
);
|
||||
|
||||
const imageUsage: ImageUsage = {
|
||||
|
||||
@@ -3,7 +3,7 @@ import { useAppStore } from 'app/store/nanostores/store';
|
||||
import { SubMenuButtonContent, useSubMenu } from 'common/hooks/useSubMenu';
|
||||
import { NewLayerIcon } from 'features/controlLayers/components/common/icons';
|
||||
import { useCanvasIsBusySafe } from 'features/controlLayers/hooks/useCanvasIsBusy';
|
||||
import { referenceImageAdded } from 'features/controlLayers/store/refImagesSlice';
|
||||
import { refImageAdded } from 'features/controlLayers/store/refImagesSlice';
|
||||
import { imageDTOToImageWithDims } from 'features/controlLayers/store/util';
|
||||
import { useImageViewer } from 'features/gallery/components/ImageViewer/useImageViewer';
|
||||
import { useImageDTOContext } from 'features/gallery/contexts/ImageDTOContext';
|
||||
@@ -77,7 +77,7 @@ export const ImageMenuItemNewLayerFromImageSubMenu = memo(() => {
|
||||
|
||||
const onClickNewGlobalReferenceImageFromImage = useCallback(() => {
|
||||
const { dispatch } = store;
|
||||
dispatch(referenceImageAdded({ overrides: { ipAdapter: { image: imageDTOToImageWithDims(imageDTO) } } }));
|
||||
dispatch(refImageAdded({ overrides: { config: { image: imageDTOToImageWithDims(imageDTO) } } }));
|
||||
dispatch(sentImageToCanvas());
|
||||
dispatch(setActiveTab('canvas'));
|
||||
imageViewer.close();
|
||||
|
||||
@@ -11,10 +11,10 @@ import {
|
||||
inpaintMaskAdded,
|
||||
rasterLayerAdded,
|
||||
rgAdded,
|
||||
rgIPAdapterImageChanged,
|
||||
rgRefImageImageChanged,
|
||||
} from 'features/controlLayers/store/canvasSlice';
|
||||
import { canvasSessionTypeChanged } from 'features/controlLayers/store/canvasStagingAreaSlice';
|
||||
import { referenceImageAdded, referenceImageIPAdapterImageChanged } from 'features/controlLayers/store/refImagesSlice';
|
||||
import { refImageAdded, refImageImageChanged } from 'features/controlLayers/store/refImagesSlice';
|
||||
import { selectBboxModelBase, selectBboxRect } from 'features/controlLayers/store/selectors';
|
||||
import type {
|
||||
CanvasControlLayerState,
|
||||
@@ -41,7 +41,7 @@ import { assert } from 'tsafe';
|
||||
|
||||
export const setGlobalReferenceImage = (arg: { imageDTO: ImageDTO; id: string; dispatch: AppDispatch }) => {
|
||||
const { imageDTO, id, dispatch } = arg;
|
||||
dispatch(referenceImageIPAdapterImageChanged({ id, imageDTO }));
|
||||
dispatch(refImageImageChanged({ id, imageDTO }));
|
||||
};
|
||||
|
||||
export const setRegionalGuidanceReferenceImage = (arg: {
|
||||
@@ -51,7 +51,7 @@ export const setRegionalGuidanceReferenceImage = (arg: {
|
||||
dispatch: AppDispatch;
|
||||
}) => {
|
||||
const { imageDTO, entityIdentifier, referenceImageId, dispatch } = arg;
|
||||
dispatch(rgIPAdapterImageChanged({ entityIdentifier, referenceImageId, imageDTO }));
|
||||
dispatch(rgRefImageImageChanged({ entityIdentifier, referenceImageId, imageDTO }));
|
||||
};
|
||||
|
||||
export const setUpscaleInitialImage = (arg: { imageDTO: ImageDTO; dispatch: AppDispatch }) => {
|
||||
@@ -112,9 +112,9 @@ export const createNewCanvasEntityFromImage = (arg: {
|
||||
break;
|
||||
}
|
||||
case 'regional_guidance_with_reference_image': {
|
||||
const ipAdapter = deepClone(selectDefaultIPAdapter(getState()));
|
||||
ipAdapter.image = imageDTOToImageWithDims(imageDTO);
|
||||
const referenceImages = [{ id: getPrefixedId('regional_guidance_reference_image'), ipAdapter }];
|
||||
const config = deepClone(selectDefaultIPAdapter(getState()));
|
||||
config.image = imageDTOToImageWithDims(imageDTO);
|
||||
const referenceImages = [{ id: getPrefixedId('regional_guidance_reference_image'), config }];
|
||||
dispatch(rgAdded({ overrides: { referenceImages }, isSelected: true }));
|
||||
break;
|
||||
}
|
||||
@@ -242,10 +242,10 @@ export const newCanvasFromImage = async (arg: {
|
||||
break;
|
||||
}
|
||||
case 'reference_image': {
|
||||
const ipAdapter = deepClone(selectDefaultRefImageConfig(getState()));
|
||||
ipAdapter.image = imageDTOToImageWithDims(imageDTO);
|
||||
const config = deepClone(selectDefaultRefImageConfig(getState()));
|
||||
config.image = imageDTOToImageWithDims(imageDTO);
|
||||
dispatch(canvasSessionTypeChanged({ type: 'advanced' }));
|
||||
dispatch(referenceImageAdded({ overrides: { ipAdapter }, isSelected: true }));
|
||||
dispatch(refImageAdded({ overrides: { config }, isSelected: true }));
|
||||
if (withInpaintMask) {
|
||||
dispatch(inpaintMaskAdded({ isSelected: true, isBookmarked: true }));
|
||||
}
|
||||
@@ -253,9 +253,9 @@ export const newCanvasFromImage = async (arg: {
|
||||
break;
|
||||
}
|
||||
case 'regional_guidance_with_reference_image': {
|
||||
const ipAdapter = deepClone(selectDefaultIPAdapter(getState()));
|
||||
ipAdapter.image = imageDTOToImageWithDims(imageDTO);
|
||||
const referenceImages = [{ id: getPrefixedId('regional_guidance_reference_image'), ipAdapter }];
|
||||
const config = deepClone(selectDefaultIPAdapter(getState()));
|
||||
config.image = imageDTOToImageWithDims(imageDTO);
|
||||
const referenceImages = [{ id: getPrefixedId('regional_guidance_reference_image'), config }];
|
||||
dispatch(canvasSessionTypeChanged({ type: 'advanced' }));
|
||||
dispatch(rgAdded({ overrides: { referenceImages }, isSelected: true }));
|
||||
if (withInpaintMask) {
|
||||
|
||||
@@ -5,9 +5,9 @@ import type {
|
||||
CanvasInpaintMaskState,
|
||||
CanvasMetadata,
|
||||
CanvasRasterLayerState,
|
||||
CanvasReferenceImageState,
|
||||
CanvasRegionalGuidanceState,
|
||||
LoRA,
|
||||
RefImageState,
|
||||
} from 'features/controlLayers/store/types';
|
||||
import { zCanvasMetadata, zCanvasRasterLayerState } from 'features/controlLayers/store/types';
|
||||
import {
|
||||
@@ -417,7 +417,7 @@ const parseAllIPAdapters: MetadataParseFunc<IPAdapterConfigMetadata[]> = async (
|
||||
const parseLayer: MetadataParseFunc<
|
||||
| CanvasRasterLayerState
|
||||
| CanvasControlLayerState
|
||||
| CanvasReferenceImageState
|
||||
| RefImageState
|
||||
| CanvasRegionalGuidanceState
|
||||
| CanvasInpaintMaskState
|
||||
> = (metadataItem) => zCanvasRasterLayerState.parseAsync(metadataItem);
|
||||
@@ -431,7 +431,7 @@ const parseLayers: MetadataParseFunc<
|
||||
(
|
||||
| CanvasRasterLayerState
|
||||
| CanvasControlLayerState
|
||||
| CanvasReferenceImageState
|
||||
| RefImageState
|
||||
| CanvasRegionalGuidanceState
|
||||
| CanvasInpaintMaskState
|
||||
)[]
|
||||
@@ -444,7 +444,7 @@ const parseLayers: MetadataParseFunc<
|
||||
const layers: (
|
||||
| CanvasRasterLayerState
|
||||
| CanvasControlLayerState
|
||||
| CanvasReferenceImageState
|
||||
| RefImageState
|
||||
| CanvasRegionalGuidanceState
|
||||
| CanvasInpaintMaskState
|
||||
)[] = [];
|
||||
@@ -493,7 +493,7 @@ const parseLayers: MetadataParseFunc<
|
||||
ipAdaptersRaw.map(async (cn) => await parseIPAdapterToIPAdapterLayer(cn))
|
||||
);
|
||||
const ipAdaptersAsLayers = ipAdaptersParseResults
|
||||
.filter((result): result is PromiseFulfilledResult<CanvasReferenceImageState> => result.status === 'fulfilled')
|
||||
.filter((result): result is PromiseFulfilledResult<RefImageState> => result.status === 'fulfilled')
|
||||
.map((result) => result.value);
|
||||
layers.push(...ipAdaptersAsLayers);
|
||||
} catch {
|
||||
@@ -598,7 +598,7 @@ const parseT2IAdapterToControlAdapterLayer: MetadataParseFunc<CanvasControlLayer
|
||||
return layer;
|
||||
};
|
||||
|
||||
const parseIPAdapterToIPAdapterLayer: MetadataParseFunc<CanvasReferenceImageState> = async (metadataItem) => {
|
||||
const parseIPAdapterToIPAdapterLayer: MetadataParseFunc<RefImageState> = async (metadataItem) => {
|
||||
const ip_adapter_model = await getProperty(metadataItem, 'ip_adapter_model');
|
||||
const key = await getModelKey(ip_adapter_model, 'ip_adapter');
|
||||
const ipAdapterModel = await fetchModelConfigWithTypeGuard(key, isIPAdapterModelConfig);
|
||||
@@ -630,13 +630,9 @@ const parseIPAdapterToIPAdapterLayer: MetadataParseFunc<CanvasReferenceImageStat
|
||||
];
|
||||
const imageDTO = image ? await getImageDTOSafe(image.image_name) : null;
|
||||
|
||||
const layer: CanvasReferenceImageState = {
|
||||
const layer: RefImageState = {
|
||||
id: getPrefixedId('ip_adapter'),
|
||||
type: 'reference_image',
|
||||
isEnabled: true,
|
||||
isLocked: false,
|
||||
name: null,
|
||||
ipAdapter: {
|
||||
config: {
|
||||
type: 'ip_adapter',
|
||||
model: zModelIdentifierField.parse(ipAdapterModel),
|
||||
weight: typeof weight === 'number' ? weight : initialIPAdapter.weight,
|
||||
|
||||
@@ -1,8 +1,4 @@
|
||||
import type {
|
||||
CanvasReferenceImageState,
|
||||
FLUXReduxConfig,
|
||||
FLUXReduxImageInfluence,
|
||||
} from 'features/controlLayers/store/types';
|
||||
import type { FLUXReduxConfig, FLUXReduxImageInfluence, RefImageState } from 'features/controlLayers/store/types';
|
||||
import { isFLUXReduxConfig } from 'features/controlLayers/store/types';
|
||||
import { getGlobalReferenceImageWarnings } from 'features/controlLayers/store/validators';
|
||||
import type { Graph } from 'features/nodes/util/graph/generation/Graph';
|
||||
@@ -14,7 +10,7 @@ type AddFLUXReduxResult = {
|
||||
};
|
||||
|
||||
type AddFLUXReduxArg = {
|
||||
entities: CanvasReferenceImageState[];
|
||||
entities: RefImageState[];
|
||||
g: Graph;
|
||||
collector: Invocation<'collect'>;
|
||||
model: MainModelConfig;
|
||||
@@ -22,19 +18,18 @@ type AddFLUXReduxArg = {
|
||||
|
||||
export const addFLUXReduxes = ({ entities, g, collector, model }: AddFLUXReduxArg): AddFLUXReduxResult => {
|
||||
const validFLUXReduxes = entities
|
||||
.filter((entity) => entity.isEnabled)
|
||||
.filter((entity) => isFLUXReduxConfig(entity.ipAdapter))
|
||||
.filter((entity) => isFLUXReduxConfig(entity.config))
|
||||
.filter((entity) => getGlobalReferenceImageWarnings(entity, model).length === 0);
|
||||
|
||||
const result: AddFLUXReduxResult = {
|
||||
addedFLUXReduxes: 0,
|
||||
};
|
||||
|
||||
for (const { id, ipAdapter } of validFLUXReduxes) {
|
||||
assert(isFLUXReduxConfig(ipAdapter), 'This should have been filtered out');
|
||||
for (const { id, config } of validFLUXReduxes) {
|
||||
assert(isFLUXReduxConfig(config), 'This should have been filtered out');
|
||||
result.addedFLUXReduxes++;
|
||||
|
||||
addFLUXRedux(id, ipAdapter, g, collector);
|
||||
addFLUXRedux(id, config, g, collector);
|
||||
}
|
||||
|
||||
return result;
|
||||
|
||||
@@ -1,8 +1,4 @@
|
||||
import {
|
||||
type CanvasReferenceImageState,
|
||||
type IPAdapterConfig,
|
||||
isIPAdapterConfig,
|
||||
} from 'features/controlLayers/store/types';
|
||||
import { type IPAdapterConfig, isIPAdapterConfig,type RefImageState } from 'features/controlLayers/store/types';
|
||||
import { getGlobalReferenceImageWarnings } from 'features/controlLayers/store/validators';
|
||||
import type { Graph } from 'features/nodes/util/graph/generation/Graph';
|
||||
import type { Invocation, MainModelConfig } from 'services/api/types';
|
||||
@@ -13,7 +9,7 @@ type AddIPAdaptersResult = {
|
||||
};
|
||||
|
||||
type AddIPAdaptersArg = {
|
||||
entities: CanvasReferenceImageState[];
|
||||
entities: RefImageState[];
|
||||
g: Graph;
|
||||
collector: Invocation<'collect'>;
|
||||
model: MainModelConfig;
|
||||
@@ -21,19 +17,18 @@ type AddIPAdaptersArg = {
|
||||
|
||||
export const addIPAdapters = ({ entities, g, collector, model }: AddIPAdaptersArg): AddIPAdaptersResult => {
|
||||
const validIPAdapters = entities
|
||||
.filter((entity) => entity.isEnabled)
|
||||
.filter((entity) => isIPAdapterConfig(entity.ipAdapter))
|
||||
.filter((entity) => isIPAdapterConfig(entity.config))
|
||||
.filter((entity) => getGlobalReferenceImageWarnings(entity, model).length === 0);
|
||||
|
||||
const result: AddIPAdaptersResult = {
|
||||
addedIPAdapters: 0,
|
||||
};
|
||||
|
||||
for (const { id, ipAdapter } of validIPAdapters) {
|
||||
assert(isIPAdapterConfig(ipAdapter), 'This should have been filtered out');
|
||||
for (const { id, config } of validIPAdapters) {
|
||||
assert(isIPAdapterConfig(config), 'This should have been filtered out');
|
||||
result.addedIPAdapters++;
|
||||
|
||||
addIPAdapter(id, ipAdapter, g, collector);
|
||||
addIPAdapter(id, config, g, collector);
|
||||
}
|
||||
|
||||
return result;
|
||||
|
||||
@@ -3,7 +3,12 @@ import { deepClone } from 'common/util/deepClone';
|
||||
import { withResultAsync } from 'common/util/result';
|
||||
import type { CanvasManager } from 'features/controlLayers/konva/CanvasManager';
|
||||
import { getPrefixedId } from 'features/controlLayers/konva/util';
|
||||
import type { CanvasRegionalGuidanceState, Rect } from 'features/controlLayers/store/types';
|
||||
import {
|
||||
type CanvasRegionalGuidanceState,
|
||||
isFLUXReduxConfig,
|
||||
isIPAdapterConfig,
|
||||
type Rect,
|
||||
} from 'features/controlLayers/store/types';
|
||||
import { getRegionalGuidanceWarnings } from 'features/controlLayers/store/validators';
|
||||
import { IMAGE_INFLUENCE_TO_SETTINGS } from 'features/nodes/util/graph/generation/addFLUXRedux';
|
||||
import type { Graph } from 'features/nodes/util/graph/generation/Graph';
|
||||
@@ -273,12 +278,12 @@ export const addRegions = async ({
|
||||
}
|
||||
}
|
||||
|
||||
for (const { id, ipAdapter } of region.referenceImages) {
|
||||
if (ipAdapter.type === 'ip_adapter') {
|
||||
for (const { id, config } of region.referenceImages) {
|
||||
if (isIPAdapterConfig(config)) {
|
||||
assert(!isFLUX, 'Regional IP adapters are not supported for FLUX.');
|
||||
|
||||
result.addedIPAdapters++;
|
||||
const { weight, model, clipVisionModel, method, beginEndStepPct, image } = ipAdapter;
|
||||
const { weight, model, clipVisionModel, method, beginEndStepPct, image } = config;
|
||||
assert(model, 'IP Adapter model is required');
|
||||
assert(image, 'IP Adapter image is required');
|
||||
|
||||
@@ -299,11 +304,11 @@ export const addRegions = async ({
|
||||
// Connect the mask to the conditioning
|
||||
g.addEdge(maskToTensor, 'mask', ipAdapterNode, 'mask');
|
||||
g.addEdge(ipAdapterNode, 'ip_adapter', ipAdapterCollect, 'item');
|
||||
} else if (ipAdapter.type === 'flux_redux') {
|
||||
} else if (isFLUXReduxConfig(config)) {
|
||||
assert(isFLUX, 'Regional FLUX Redux requires FLUX.');
|
||||
assert(fluxReduxCollect !== null, 'FLUX Redux collector is required.');
|
||||
result.addedFLUXReduxes++;
|
||||
const { model: fluxReduxModel, image } = ipAdapter;
|
||||
const { model: fluxReduxModel, image } = config;
|
||||
assert(fluxReduxModel, 'FLUX Redux model is required');
|
||||
assert(image, 'FLUX Redux image is required');
|
||||
|
||||
@@ -314,7 +319,7 @@ export const addRegions = async ({
|
||||
image: {
|
||||
image_name: image.image_name,
|
||||
},
|
||||
...IMAGE_INFLUENCE_TO_SETTINGS[ipAdapter.imageInfluence ?? 'highest'],
|
||||
...IMAGE_INFLUENCE_TO_SETTINGS[config.imageInfluence ?? 'highest'],
|
||||
});
|
||||
|
||||
// Connect the mask to the conditioning
|
||||
|
||||
@@ -44,8 +44,7 @@ export const buildChatGPT4oGraph = async (
|
||||
assert(isChatGPT4oAspectRatioID(bbox.aspectRatio.id), 'ChatGPT 4o does not support this aspect ratio');
|
||||
|
||||
const validRefImages = refImages.entities
|
||||
.filter((entity) => entity.isEnabled)
|
||||
.filter((entity) => isChatGPT4oReferenceImageConfig(entity.ipAdapter))
|
||||
.filter((entity) => isChatGPT4oReferenceImageConfig(entity.config))
|
||||
.filter((entity) => getGlobalReferenceImageWarnings(entity, model).length === 0)
|
||||
.toReversed(); // sends them in order they are displayed in the list
|
||||
|
||||
@@ -54,9 +53,9 @@ export const buildChatGPT4oGraph = async (
|
||||
if (validRefImages.length > 0) {
|
||||
reference_images = [];
|
||||
for (const entity of validRefImages) {
|
||||
assert(entity.ipAdapter.image, 'Image is required for reference image');
|
||||
assert(entity.config.image, 'Image is required for reference image');
|
||||
reference_images.push({
|
||||
image_name: entity.ipAdapter.image.image_name,
|
||||
image_name: entity.config.image.image_name,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@@ -522,20 +522,17 @@ const getReasonsWhyCannotEnqueueCanvasTab = (arg: {
|
||||
}
|
||||
});
|
||||
|
||||
refImages.entities
|
||||
.filter((entity) => entity.isEnabled)
|
||||
.forEach((entity, i) => {
|
||||
const layerLiteral = i18n.t('controlLayers.layer_one');
|
||||
const layerNumber = i + 1;
|
||||
const layerType = i18n.t(LAYER_TYPE_TO_TKEY[entity.type]);
|
||||
const prefix = `${layerLiteral} #${layerNumber} (${layerType})`;
|
||||
const problems = getGlobalReferenceImageWarnings(entity, model);
|
||||
refImages.entities.forEach((entity, i) => {
|
||||
const layerNumber = i + 1;
|
||||
const refImageLiteral = i18n.t(LAYER_TYPE_TO_TKEY['reference_image']);
|
||||
const prefix = `${refImageLiteral} #${layerNumber}`;
|
||||
const problems = getGlobalReferenceImageWarnings(entity, model);
|
||||
|
||||
if (problems.length) {
|
||||
const content = upperFirst(problems.map((p) => i18n.t(p)).join(', '));
|
||||
reasons.push({ prefix, content });
|
||||
}
|
||||
});
|
||||
if (problems.length) {
|
||||
const content = upperFirst(problems.map((p) => i18n.t(p)).join(', '));
|
||||
reasons.push({ prefix, content });
|
||||
}
|
||||
});
|
||||
|
||||
canvas.regionalGuidance.entities
|
||||
.filter((entity) => entity.isEnabled)
|
||||
|
||||
Reference in New Issue
Block a user