diff --git a/invokeai/frontend/web/src/features/controlLayers/components/RefImage/RefImageHeader.tsx b/invokeai/frontend/web/src/features/controlLayers/components/RefImage/RefImageHeader.tsx index 972dc26b89..e366d0cad9 100644 --- a/invokeai/frontend/web/src/features/controlLayers/components/RefImage/RefImageHeader.tsx +++ b/invokeai/frontend/web/src/features/controlLayers/components/RefImage/RefImageHeader.tsx @@ -4,9 +4,13 @@ import { createSelector } from '@reduxjs/toolkit'; import { useAppDispatch, useAppSelector } from 'app/store/storeHooks'; import { useRefImageEntity } from 'features/controlLayers/components/RefImage/useRefImageEntity'; import { useRefImageIdContext } from 'features/controlLayers/contexts/RefImageIdContext'; -import { refImageDeleted, selectRefImageEntityIds } from 'features/controlLayers/store/refImagesSlice'; +import { + refImageDeleted, + refImageIsEnabledToggled, + selectRefImageEntityIds, +} from 'features/controlLayers/store/refImagesSlice'; import { memo, useCallback, useMemo } from 'react'; -import { PiTrashBold } from 'react-icons/pi'; +import { PiEyeBold, PiEyeSlashBold, PiTrashBold } from 'react-icons/pi'; const textSx: SystemStyleObject = { color: 'base.300', @@ -28,21 +32,37 @@ export const RefImageHeader = memo(() => { dispatch(refImageDeleted({ id })); }, [dispatch, id]); + const toggleIsEnabled = useCallback(() => { + dispatch(refImageIsEnabledToggled({ id })); + }, [dispatch, id]); + return ( Reference Image #{refImageNumber} - } - colorScheme="error" - /> + + : } + colorScheme={entity.isEnabled ? 'base' : 'warning'} + /> + } + colorScheme="error" + /> + ); }); diff --git a/invokeai/frontend/web/src/features/controlLayers/components/RefImage/RefImagePreview.tsx b/invokeai/frontend/web/src/features/controlLayers/components/RefImage/RefImagePreview.tsx index e5a0e5c02a..100e7496ff 100644 --- a/invokeai/frontend/web/src/features/controlLayers/components/RefImage/RefImagePreview.tsx +++ b/invokeai/frontend/web/src/features/controlLayers/components/RefImage/RefImagePreview.tsx @@ -19,6 +19,9 @@ const baseSx: SystemStyleObject = { '&[data-is-open="true"]': { borderColor: 'invokeBlue.300', }, + '&[data-is-disabled="true"]': { + opacity: 0.4, + }, }; const weightDisplaySx: SystemStyleObject = { @@ -36,6 +39,9 @@ const getImageSxWithWeight = (weight: number): SystemStyleObject => { return { ...baseSx, + '&[data-is-disabled="true"]': { + opacity: 0.4, + }, _after: { content: '""', position: 'absolute', @@ -97,6 +103,7 @@ export const RefImagePreview = memo(() => { flexShrink={0} data-is-open={selectedEntityId === id && isPanelOpen} data-is-error={true} + data-is-disabled={!entity.isEnabled} sx={sx} /> ); @@ -114,6 +121,7 @@ export const RefImagePreview = memo(() => { sx={sx} data-is-open={selectedEntityId === id && isPanelOpen} data-is-error={!entity.config.model} + data-is-disabled={!entity.isEnabled} role="button" onClick={onClick} cursor="pointer" diff --git a/invokeai/frontend/web/src/features/controlLayers/store/refImagesSlice.ts b/invokeai/frontend/web/src/features/controlLayers/store/refImagesSlice.ts index b9725b3f17..e709372118 100644 --- a/invokeai/frontend/web/src/features/controlLayers/store/refImagesSlice.ts +++ b/invokeai/frontend/web/src/features/controlLayers/store/refImagesSlice.ts @@ -222,6 +222,14 @@ export const refImagesSlice = createSlice({ } state.selectedEntityId = id; }, + refImageIsEnabledToggled: (state, action: PayloadActionWithId) => { + const { id } = action.payload; + const entity = selectRefImageEntity(state, id); + if (!entity) { + return; + } + entity.isEnabled = !entity.isEnabled; + }, refImagesReset: () => getInitialRefImagesState(), }, extraReducers(builder) { @@ -243,6 +251,7 @@ export const { refImageIPAdapterWeightChanged, refImageIPAdapterBeginEndStepPctChanged, refImageFLUXReduxImageInfluenceChanged, + refImageIsEnabledToggled, } = refImagesSlice.actions; /* eslint-disable-next-line @typescript-eslint/no-explicit-any */ diff --git a/invokeai/frontend/web/src/features/controlLayers/store/types.ts b/invokeai/frontend/web/src/features/controlLayers/store/types.ts index abb22cb4ea..88d24d703d 100644 --- a/invokeai/frontend/web/src/features/controlLayers/store/types.ts +++ b/invokeai/frontend/web/src/features/controlLayers/store/types.ts @@ -302,6 +302,7 @@ const zCanvasEntityBase = z.object({ const zRefImageState = z.object({ id: zId, + isEnabled: z.boolean().default(true), // This should be named `referenceImage` but we need to keep it as `ipAdapter` for backwards compatibility config: z.discriminatedUnion('type', [ zIPAdapterConfig, diff --git a/invokeai/frontend/web/src/features/controlLayers/store/util.ts b/invokeai/frontend/web/src/features/controlLayers/store/util.ts index aad3669df1..2d40cf1779 100644 --- a/invokeai/frontend/web/src/features/controlLayers/store/util.ts +++ b/invokeai/frontend/web/src/features/controlLayers/store/util.ts @@ -132,6 +132,7 @@ export const initialControlLoRA: ControlLoRAConfig = { export const getReferenceImageState = (id: string, overrides?: PartialDeep): RefImageState => { const entityState: RefImageState = { id, + isEnabled: true, config: deepClone(initialIPAdapter), }; merge(entityState, overrides); diff --git a/invokeai/frontend/web/src/features/metadata/util/parsers.ts b/invokeai/frontend/web/src/features/metadata/util/parsers.ts index de961ddee4..dbeeb0a2ba 100644 --- a/invokeai/frontend/web/src/features/metadata/util/parsers.ts +++ b/invokeai/frontend/web/src/features/metadata/util/parsers.ts @@ -632,6 +632,7 @@ const parseIPAdapterToIPAdapterLayer: MetadataParseFunc = async ( const layer: RefImageState = { id: getPrefixedId('ip_adapter'), + isEnabled: true, config: { type: 'ip_adapter', model: zModelIdentifierField.parse(ipAdapterModel), diff --git a/invokeai/frontend/web/src/features/nodes/util/graph/generation/addFLUXRedux.ts b/invokeai/frontend/web/src/features/nodes/util/graph/generation/addFLUXRedux.ts index bac14a218d..7ab0cb2d38 100644 --- a/invokeai/frontend/web/src/features/nodes/util/graph/generation/addFLUXRedux.ts +++ b/invokeai/frontend/web/src/features/nodes/util/graph/generation/addFLUXRedux.ts @@ -18,6 +18,7 @@ type AddFLUXReduxArg = { export const addFLUXReduxes = ({ entities, g, collector, model }: AddFLUXReduxArg): AddFLUXReduxResult => { const validFLUXReduxes = entities + .filter((entity) => entity.isEnabled) .filter((entity) => isFLUXReduxConfig(entity.config)) .filter((entity) => getGlobalReferenceImageWarnings(entity, model).length === 0); diff --git a/invokeai/frontend/web/src/features/nodes/util/graph/generation/addIPAdapters.ts b/invokeai/frontend/web/src/features/nodes/util/graph/generation/addIPAdapters.ts index ab5fb74465..0302d73397 100644 --- a/invokeai/frontend/web/src/features/nodes/util/graph/generation/addIPAdapters.ts +++ b/invokeai/frontend/web/src/features/nodes/util/graph/generation/addIPAdapters.ts @@ -17,6 +17,7 @@ type AddIPAdaptersArg = { export const addIPAdapters = ({ entities, g, collector, model }: AddIPAdaptersArg): AddIPAdaptersResult => { const validIPAdapters = entities + .filter((entity) => entity.isEnabled) .filter((entity) => isIPAdapterConfig(entity.config)) .filter((entity) => getGlobalReferenceImageWarnings(entity, model).length === 0); diff --git a/invokeai/frontend/web/src/features/nodes/util/graph/generation/buildChatGPT4oGraph.ts b/invokeai/frontend/web/src/features/nodes/util/graph/generation/buildChatGPT4oGraph.ts index eb64c4b19b..4ff09fb2e2 100644 --- a/invokeai/frontend/web/src/features/nodes/util/graph/generation/buildChatGPT4oGraph.ts +++ b/invokeai/frontend/web/src/features/nodes/util/graph/generation/buildChatGPT4oGraph.ts @@ -39,6 +39,7 @@ export const buildChatGPT4oGraph = async (arg: GraphBuilderArg): Promise entity.isEnabled) .filter((entity) => isChatGPT4oReferenceImageConfig(entity.config)) .filter((entity) => getGlobalReferenceImageWarnings(entity, model).length === 0) .toReversed(); // sends them in order they are displayed in the list diff --git a/invokeai/frontend/web/src/features/nodes/util/graph/generation/buildFluxKontextGraph.ts b/invokeai/frontend/web/src/features/nodes/util/graph/generation/buildFluxKontextGraph.ts index e8a55a7e8d..60852daad1 100644 --- a/invokeai/frontend/web/src/features/nodes/util/graph/generation/buildFluxKontextGraph.ts +++ b/invokeai/frontend/web/src/features/nodes/util/graph/generation/buildFluxKontextGraph.ts @@ -37,6 +37,7 @@ export const buildFluxKontextGraph = (arg: GraphBuilderArg): GraphBuilderReturn assert(model.base === 'flux-kontext', 'Model is not a Flux Kontext model'); const validRefImages = refImages.entities + .filter((entity) => entity.isEnabled) .filter((entity) => isFluxKontextReferenceImageConfig(entity.config)) .filter((entity) => getGlobalReferenceImageWarnings(entity, model).length === 0) .toReversed(); // sends them in order they are displayed in the list diff --git a/invokeai/frontend/web/src/features/queue/store/readiness.ts b/invokeai/frontend/web/src/features/queue/store/readiness.ts index ae2fb9a204..f8c36fef29 100644 --- a/invokeai/frontend/web/src/features/queue/store/readiness.ts +++ b/invokeai/frontend/web/src/features/queue/store/readiness.ts @@ -632,7 +632,7 @@ const getReasonsWhyCannotEnqueueCanvasTab = (arg: { }); // Flux Kontext only supports 1x Reference Image at a time. - const referenceImageCount = refImages.entities.length; + const referenceImageCount = refImages.entities.filter((entity) => entity.isEnabled).length; if (model?.base === 'flux-kontext' && referenceImageCount > 1) { reasons.push({ content: i18n.t('parameters.invoke.fluxKontextMultipleReferenceImages') });