refactor(ui): refImage.ipAdapter -> refImage.config

This commit is contained in:
psychedelicious
2025-06-13 12:22:02 +10:00
parent 952483b3f3
commit 626ca236d6
21 changed files with 311 additions and 421 deletions

View File

@@ -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 })
);
});
});

View File

@@ -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>

View File

@@ -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(() => {

View File

@@ -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} />;

View File

@@ -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);

View File

@@ -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;

View File

@@ -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 {

View File

@@ -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,

View File

@@ -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;

View File

@@ -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>;

View File

@@ -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;

View File

@@ -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);
}

View File

@@ -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 = {

View File

@@ -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();

View File

@@ -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) {

View File

@@ -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,

View File

@@ -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;

View File

@@ -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;

View File

@@ -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

View File

@@ -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,
});
}
}

View File

@@ -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)