mirror of
https://github.com/invoke-ai/InvokeAI
synced 2024-08-30 20:32:17 +00:00
fix(ui): ip adapters work
This commit is contained in:
parent
4adb2eabf5
commit
7178fc6253
@ -5,6 +5,7 @@ import type { JSONObject } from 'common/types';
|
|||||||
import {
|
import {
|
||||||
bboxHeightChanged,
|
bboxHeightChanged,
|
||||||
bboxWidthChanged,
|
bboxWidthChanged,
|
||||||
|
controlLayerModelChanged,
|
||||||
ipaModelChanged,
|
ipaModelChanged,
|
||||||
loraDeleted,
|
loraDeleted,
|
||||||
modelChanged,
|
modelChanged,
|
||||||
@ -20,6 +21,7 @@ import type { Logger } from 'roarr';
|
|||||||
import { modelConfigsAdapterSelectors, modelsApi } from 'services/api/endpoints/models';
|
import { modelConfigsAdapterSelectors, modelsApi } from 'services/api/endpoints/models';
|
||||||
import type { AnyModelConfig } from 'services/api/types';
|
import type { AnyModelConfig } from 'services/api/types';
|
||||||
import {
|
import {
|
||||||
|
isControlNetOrT2IAdapterModelConfig,
|
||||||
isIPAdapterModelConfig,
|
isIPAdapterModelConfig,
|
||||||
isLoRAModelConfig,
|
isLoRAModelConfig,
|
||||||
isNonRefinerMainModelConfig,
|
isNonRefinerMainModelConfig,
|
||||||
@ -31,7 +33,7 @@ import {
|
|||||||
export const addModelsLoadedListener = (startAppListening: AppStartListening) => {
|
export const addModelsLoadedListener = (startAppListening: AppStartListening) => {
|
||||||
startAppListening({
|
startAppListening({
|
||||||
predicate: modelsApi.endpoints.getModelConfigs.matchFulfilled,
|
predicate: modelsApi.endpoints.getModelConfigs.matchFulfilled,
|
||||||
effect: async (action, { getState, dispatch }) => {
|
effect: (action, { getState, dispatch }) => {
|
||||||
// models loaded, we need to ensure the selected model is available and if not, select the first one
|
// models loaded, we need to ensure the selected model is available and if not, select the first one
|
||||||
const log = logger('models');
|
const log = logger('models');
|
||||||
log.info({ models: action.payload.entities }, `Models loaded (${action.payload.ids.length})`);
|
log.info({ models: action.payload.entities }, `Models loaded (${action.payload.ids.length})`);
|
||||||
@ -169,24 +171,24 @@ const handleLoRAModels: ModelHandler = (models, state, dispatch, _log) => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
const handleControlAdapterModels: ModelHandler = (models, state, dispatch, _log) => {
|
const handleControlAdapterModels: ModelHandler = (models, state, dispatch, _log) => {
|
||||||
// const caModels = models.filter(isControlNetOrT2IAdapterModelConfig);
|
const caModels = models.filter(isControlNetOrT2IAdapterModelConfig);
|
||||||
// state.canvasV2.controlAdapters.entities.forEach((ca) => {
|
state.canvasV2.controlLayers.entities.forEach((entity) => {
|
||||||
// const isModelAvailable = caModels.some((m) => m.key === ca.model?.key);
|
const isModelAvailable = caModels.some((m) => m.key === entity.controlAdapter.model?.key);
|
||||||
// if (isModelAvailable) {
|
if (isModelAvailable) {
|
||||||
// return;
|
return;
|
||||||
// }
|
}
|
||||||
// dispatch(caModelChanged({ id: ca.id, modelConfig: null }));
|
dispatch(controlLayerModelChanged({ id: entity.id, modelConfig: null }));
|
||||||
// });
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleIPAdapterModels: ModelHandler = (models, state, dispatch, _log) => {
|
const handleIPAdapterModels: ModelHandler = (models, state, dispatch, _log) => {
|
||||||
const ipaModels = models.filter(isIPAdapterModelConfig);
|
const ipaModels = models.filter(isIPAdapterModelConfig);
|
||||||
state.canvasV2.ipAdapters.entities.forEach(({ id, model }) => {
|
state.canvasV2.ipAdapters.entities.forEach((entity) => {
|
||||||
const isModelAvailable = ipaModels.some((m) => m.key === model?.key);
|
const isModelAvailable = ipaModels.some((m) => m.key === entity.ipAdapter.model?.key);
|
||||||
if (isModelAvailable) {
|
if (isModelAvailable) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
dispatch(ipaModelChanged({ id, modelConfig: null }));
|
dispatch(ipaModelChanged({ id: entity.id, modelConfig: null }));
|
||||||
});
|
});
|
||||||
|
|
||||||
state.canvasV2.regions.entities.forEach(({ id, ipAdapters }) => {
|
state.canvasV2.regions.entities.forEach(({ id, ipAdapters }) => {
|
||||||
|
@ -154,24 +154,24 @@ const createSelector = (templates: Templates) =>
|
|||||||
});
|
});
|
||||||
|
|
||||||
canvasV2.ipAdapters.entities
|
canvasV2.ipAdapters.entities
|
||||||
.filter((ipa) => ipa.isEnabled)
|
.filter((entity) => entity.isEnabled)
|
||||||
.forEach((ipa, i) => {
|
.forEach((entity, i) => {
|
||||||
const layerLiteral = i18n.t('controlLayers.layers_one');
|
const layerLiteral = i18n.t('controlLayers.layers_one');
|
||||||
const layerNumber = i + 1;
|
const layerNumber = i + 1;
|
||||||
const layerType = i18n.t(LAYER_TYPE_TO_TKEY[ipa.type]);
|
const layerType = i18n.t(LAYER_TYPE_TO_TKEY[entity.type]);
|
||||||
const prefix = `${layerLiteral} #${layerNumber} (${layerType})`;
|
const prefix = `${layerLiteral} #${layerNumber} (${layerType})`;
|
||||||
const problems: string[] = [];
|
const problems: string[] = [];
|
||||||
|
|
||||||
// Must have model
|
// Must have model
|
||||||
if (!ipa.model) {
|
if (!entity.ipAdapter.model) {
|
||||||
problems.push(i18n.t('parameters.invoke.layer.ipAdapterNoModelSelected'));
|
problems.push(i18n.t('parameters.invoke.layer.ipAdapterNoModelSelected'));
|
||||||
}
|
}
|
||||||
// Model base must match
|
// Model base must match
|
||||||
if (ipa.model?.base !== model?.base) {
|
if (entity.ipAdapter.model?.base !== model?.base) {
|
||||||
problems.push(i18n.t('parameters.invoke.layer.ipAdapterIncompatibleBaseModel'));
|
problems.push(i18n.t('parameters.invoke.layer.ipAdapterIncompatibleBaseModel'));
|
||||||
}
|
}
|
||||||
// Must have an image
|
// Must have an image
|
||||||
if (!ipa.imageObject) {
|
if (!entity.ipAdapter.image) {
|
||||||
problems.push(i18n.t('parameters.invoke.layer.ipAdapterNoImageSelected'));
|
problems.push(i18n.t('parameters.invoke.layer.ipAdapterNoImageSelected'));
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -182,22 +182,22 @@ const createSelector = (templates: Templates) =>
|
|||||||
});
|
});
|
||||||
|
|
||||||
canvasV2.regions.entities
|
canvasV2.regions.entities
|
||||||
.filter((rg) => rg.isEnabled)
|
.filter((entity) => entity.isEnabled)
|
||||||
.forEach((rg, i) => {
|
.forEach((entity, i) => {
|
||||||
const layerLiteral = i18n.t('controlLayers.layers_one');
|
const layerLiteral = i18n.t('controlLayers.layers_one');
|
||||||
const layerNumber = i + 1;
|
const layerNumber = i + 1;
|
||||||
const layerType = i18n.t(LAYER_TYPE_TO_TKEY[rg.type]);
|
const layerType = i18n.t(LAYER_TYPE_TO_TKEY[entity.type]);
|
||||||
const prefix = `${layerLiteral} #${layerNumber} (${layerType})`;
|
const prefix = `${layerLiteral} #${layerNumber} (${layerType})`;
|
||||||
const problems: string[] = [];
|
const problems: string[] = [];
|
||||||
// Must have a region
|
// Must have a region
|
||||||
if (rg.objects.length === 0) {
|
if (entity.objects.length === 0) {
|
||||||
problems.push(i18n.t('parameters.invoke.layer.rgNoRegion'));
|
problems.push(i18n.t('parameters.invoke.layer.rgNoRegion'));
|
||||||
}
|
}
|
||||||
// Must have at least 1 prompt or IP Adapter
|
// Must have at least 1 prompt or IP Adapter
|
||||||
if (rg.positivePrompt === null && rg.negativePrompt === null && rg.ipAdapters.length === 0) {
|
if (entity.positivePrompt === null && entity.negativePrompt === null && entity.ipAdapters.length === 0) {
|
||||||
problems.push(i18n.t('parameters.invoke.layer.rgNoPromptsOrIPAdapters'));
|
problems.push(i18n.t('parameters.invoke.layer.rgNoPromptsOrIPAdapters'));
|
||||||
}
|
}
|
||||||
rg.ipAdapters.forEach((ipAdapter) => {
|
entity.ipAdapters.forEach((ipAdapter) => {
|
||||||
// Must have model
|
// Must have model
|
||||||
if (!ipAdapter.model) {
|
if (!ipAdapter.model) {
|
||||||
problems.push(i18n.t('parameters.invoke.layer.ipAdapterNoModelSelected'));
|
problems.push(i18n.t('parameters.invoke.layer.ipAdapterNoModelSelected'));
|
||||||
@ -207,7 +207,7 @@ const createSelector = (templates: Templates) =>
|
|||||||
problems.push(i18n.t('parameters.invoke.layer.ipAdapterIncompatibleBaseModel'));
|
problems.push(i18n.t('parameters.invoke.layer.ipAdapterIncompatibleBaseModel'));
|
||||||
}
|
}
|
||||||
// Must have an image
|
// Must have an image
|
||||||
if (!ipAdapter.imageObject) {
|
if (!ipAdapter.image) {
|
||||||
problems.push(i18n.t('parameters.invoke.layer.ipAdapterNoImageSelected'));
|
problems.push(i18n.t('parameters.invoke.layer.ipAdapterNoImageSelected'));
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
@ -219,12 +219,11 @@ const createSelector = (templates: Templates) =>
|
|||||||
});
|
});
|
||||||
|
|
||||||
canvasV2.rasterLayers.entities
|
canvasV2.rasterLayers.entities
|
||||||
.filter((l) => l.isEnabled)
|
.filter((entity) => entity.isEnabled)
|
||||||
.filter((l) => l.type === 'raster_layer')
|
.forEach((entity, i) => {
|
||||||
.forEach((l, i) => {
|
|
||||||
const layerLiteral = i18n.t('controlLayers.layers_one');
|
const layerLiteral = i18n.t('controlLayers.layers_one');
|
||||||
const layerNumber = i + 1;
|
const layerNumber = i + 1;
|
||||||
const layerType = i18n.t(LAYER_TYPE_TO_TKEY[l.type]);
|
const layerType = i18n.t(LAYER_TYPE_TO_TKEY[entity.type]);
|
||||||
const prefix = `${layerLiteral} #${layerNumber} (${layerType})`;
|
const prefix = `${layerLiteral} #${layerNumber} (${layerType})`;
|
||||||
const problems: string[] = [];
|
const problems: string[] = [];
|
||||||
|
|
||||||
|
@ -21,7 +21,7 @@ export const AddLayerButton = memo(() => {
|
|||||||
dispatch(controlLayerAdded({ isSelected: true, overrides: { controlAdapter: defaultControlAdapter } }));
|
dispatch(controlLayerAdded({ isSelected: true, overrides: { controlAdapter: defaultControlAdapter } }));
|
||||||
}, [defaultControlAdapter, dispatch]);
|
}, [defaultControlAdapter, dispatch]);
|
||||||
const addIPAdapter = useCallback(() => {
|
const addIPAdapter = useCallback(() => {
|
||||||
dispatch(ipaAdded({ config: defaultIPAdapter }));
|
dispatch(ipaAdded({ ipAdapter: defaultIPAdapter }));
|
||||||
}, [defaultIPAdapter, dispatch]);
|
}, [defaultIPAdapter, dispatch]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
@ -1,4 +1,5 @@
|
|||||||
import { Spacer, useDisclosure } from '@invoke-ai/ui-library';
|
import { Spacer } from '@invoke-ai/ui-library';
|
||||||
|
import { useBoolean } from 'common/hooks/useBoolean';
|
||||||
import { CanvasEntityContainer } from 'features/controlLayers/components/common/CanvasEntityContainer';
|
import { CanvasEntityContainer } from 'features/controlLayers/components/common/CanvasEntityContainer';
|
||||||
import { CanvasEntityDeleteButton } from 'features/controlLayers/components/common/CanvasEntityDeleteButton';
|
import { CanvasEntityDeleteButton } from 'features/controlLayers/components/common/CanvasEntityDeleteButton';
|
||||||
import { CanvasEntityEnabledToggle } from 'features/controlLayers/components/common/CanvasEntityEnabledToggle';
|
import { CanvasEntityEnabledToggle } from 'features/controlLayers/components/common/CanvasEntityEnabledToggle';
|
||||||
@ -17,14 +18,14 @@ type Props = {
|
|||||||
|
|
||||||
export const ControlLayer = memo(({ id }: Props) => {
|
export const ControlLayer = memo(({ id }: Props) => {
|
||||||
const entityIdentifier = useMemo<CanvasEntityIdentifier>(() => ({ id, type: 'control_layer' }), [id]);
|
const entityIdentifier = useMemo<CanvasEntityIdentifier>(() => ({ id, type: 'control_layer' }), [id]);
|
||||||
const editing = useDisclosure({ defaultIsOpen: false });
|
const editing = useBoolean(false);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<EntityIdentifierContext.Provider value={entityIdentifier}>
|
<EntityIdentifierContext.Provider value={entityIdentifier}>
|
||||||
<CanvasEntityContainer>
|
<CanvasEntityContainer>
|
||||||
<CanvasEntityHeader onDoubleClick={editing.onOpen}>
|
<CanvasEntityHeader onDoubleClick={editing.setTrue}>
|
||||||
<CanvasEntityEnabledToggle />
|
<CanvasEntityEnabledToggle />
|
||||||
{editing.isOpen ? <CanvasEntityTitleEdit onStopEditing={editing.onClose} /> : <CanvasEntityTitle />}
|
{editing.isTrue ? <CanvasEntityTitleEdit onStopEditing={editing.setFalse} /> : <CanvasEntityTitle />}
|
||||||
<Spacer />
|
<Spacer />
|
||||||
<CanvasEntityDeleteButton />
|
<CanvasEntityDeleteButton />
|
||||||
</CanvasEntityHeader>
|
</CanvasEntityHeader>
|
||||||
|
@ -1,9 +1,11 @@
|
|||||||
import { Spacer } from '@invoke-ai/ui-library';
|
import { Spacer } from '@invoke-ai/ui-library';
|
||||||
|
import { useBoolean } from 'common/hooks/useBoolean';
|
||||||
import { CanvasEntityContainer } from 'features/controlLayers/components/common/CanvasEntityContainer';
|
import { CanvasEntityContainer } from 'features/controlLayers/components/common/CanvasEntityContainer';
|
||||||
import { CanvasEntityDeleteButton } from 'features/controlLayers/components/common/CanvasEntityDeleteButton';
|
import { CanvasEntityDeleteButton } from 'features/controlLayers/components/common/CanvasEntityDeleteButton';
|
||||||
import { CanvasEntityEnabledToggle } from 'features/controlLayers/components/common/CanvasEntityEnabledToggle';
|
import { CanvasEntityEnabledToggle } from 'features/controlLayers/components/common/CanvasEntityEnabledToggle';
|
||||||
import { CanvasEntityHeader } from 'features/controlLayers/components/common/CanvasEntityHeader';
|
import { CanvasEntityHeader } from 'features/controlLayers/components/common/CanvasEntityHeader';
|
||||||
import { CanvasEntityTitle } from 'features/controlLayers/components/common/CanvasEntityTitle';
|
import { CanvasEntityTitle } from 'features/controlLayers/components/common/CanvasEntityTitle';
|
||||||
|
import { CanvasEntityTitleEdit } from 'features/controlLayers/components/common/CanvasEntityTitleEdit';
|
||||||
import { IPAdapterSettings } from 'features/controlLayers/components/IPAdapter/IPAdapterSettings';
|
import { IPAdapterSettings } from 'features/controlLayers/components/IPAdapter/IPAdapterSettings';
|
||||||
import { EntityIdentifierContext } from 'features/controlLayers/contexts/EntityIdentifierContext';
|
import { EntityIdentifierContext } from 'features/controlLayers/contexts/EntityIdentifierContext';
|
||||||
import type { CanvasEntityIdentifier } from 'features/controlLayers/store/types';
|
import type { CanvasEntityIdentifier } from 'features/controlLayers/store/types';
|
||||||
@ -15,13 +17,14 @@ type Props = {
|
|||||||
|
|
||||||
export const IPAdapter = memo(({ id }: Props) => {
|
export const IPAdapter = memo(({ id }: Props) => {
|
||||||
const entityIdentifier = useMemo<CanvasEntityIdentifier>(() => ({ id, type: 'ip_adapter' }), [id]);
|
const entityIdentifier = useMemo<CanvasEntityIdentifier>(() => ({ id, type: 'ip_adapter' }), [id]);
|
||||||
|
const editing = useBoolean(false);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<EntityIdentifierContext.Provider value={entityIdentifier}>
|
<EntityIdentifierContext.Provider value={entityIdentifier}>
|
||||||
<CanvasEntityContainer>
|
<CanvasEntityContainer>
|
||||||
<CanvasEntityHeader>
|
<CanvasEntityHeader onDoubleClick={editing.setTrue}>
|
||||||
<CanvasEntityEnabledToggle />
|
<CanvasEntityEnabledToggle />
|
||||||
<CanvasEntityTitle />
|
{editing.isTrue ? <CanvasEntityTitleEdit onStopEditing={editing.setFalse} /> : <CanvasEntityTitle />}
|
||||||
<Spacer />
|
<Spacer />
|
||||||
<CanvasEntityDeleteButton />
|
<CanvasEntityDeleteButton />
|
||||||
</CanvasEntityHeader>
|
</CanvasEntityHeader>
|
||||||
|
@ -13,7 +13,7 @@ import {
|
|||||||
ipaModelChanged,
|
ipaModelChanged,
|
||||||
ipaWeightChanged,
|
ipaWeightChanged,
|
||||||
} from 'features/controlLayers/store/canvasV2Slice';
|
} from 'features/controlLayers/store/canvasV2Slice';
|
||||||
import { selectIPAOrThrow } from 'features/controlLayers/store/ipAdaptersReducers';
|
import { selectIPAdapterEntityOrThrow } from 'features/controlLayers/store/ipAdaptersReducers';
|
||||||
import type { CLIPVisionModelV2, IPMethodV2 } from 'features/controlLayers/store/types';
|
import type { CLIPVisionModelV2, IPMethodV2 } from 'features/controlLayers/store/types';
|
||||||
import type { IPAImageDropData } from 'features/dnd/types';
|
import type { IPAImageDropData } from 'features/dnd/types';
|
||||||
import { memo, useCallback, useMemo } from 'react';
|
import { memo, useCallback, useMemo } from 'react';
|
||||||
@ -25,7 +25,7 @@ import { IPAdapterModel } from './IPAdapterModel';
|
|||||||
export const IPAdapterSettings = memo(() => {
|
export const IPAdapterSettings = memo(() => {
|
||||||
const dispatch = useAppDispatch();
|
const dispatch = useAppDispatch();
|
||||||
const { id } = useEntityIdentifierContext();
|
const { id } = useEntityIdentifierContext();
|
||||||
const ipAdapter = useAppSelector((s) => selectIPAOrThrow(s.canvasV2, id));
|
const ipAdapter = useAppSelector((s) => selectIPAdapterEntityOrThrow(s.canvasV2, id).ipAdapter);
|
||||||
|
|
||||||
const onChangeBeginEndStepPct = useCallback(
|
const onChangeBeginEndStepPct = useCallback(
|
||||||
(beginEndStepPct: [number, number]) => {
|
(beginEndStepPct: [number, number]) => {
|
||||||
@ -93,9 +93,9 @@ export const IPAdapterSettings = memo(() => {
|
|||||||
</Flex>
|
</Flex>
|
||||||
<Flex alignItems="center" justifyContent="center" h={36} w={36} aspectRatio="1/1">
|
<Flex alignItems="center" justifyContent="center" h={36} w={36} aspectRatio="1/1">
|
||||||
<IPAdapterImagePreview
|
<IPAdapterImagePreview
|
||||||
image={ipAdapter.imageObject?.image ?? null}
|
image={ipAdapter.image ?? null}
|
||||||
onChangeImage={onChangeImage}
|
onChangeImage={onChangeImage}
|
||||||
ipAdapterId={ipAdapter.id}
|
ipAdapterId={id}
|
||||||
droppableData={droppableData}
|
droppableData={droppableData}
|
||||||
postUploadAction={postUploadAction}
|
postUploadAction={postUploadAction}
|
||||||
/>
|
/>
|
||||||
|
@ -1,4 +1,5 @@
|
|||||||
import { Spacer, useDisclosure } from '@invoke-ai/ui-library';
|
import { Spacer } from '@invoke-ai/ui-library';
|
||||||
|
import { useBoolean } from 'common/hooks/useBoolean';
|
||||||
import { CanvasEntityContainer } from 'features/controlLayers/components/common/CanvasEntityContainer';
|
import { CanvasEntityContainer } from 'features/controlLayers/components/common/CanvasEntityContainer';
|
||||||
import { CanvasEntityDeleteButton } from 'features/controlLayers/components/common/CanvasEntityDeleteButton';
|
import { CanvasEntityDeleteButton } from 'features/controlLayers/components/common/CanvasEntityDeleteButton';
|
||||||
import { CanvasEntityEnabledToggle } from 'features/controlLayers/components/common/CanvasEntityEnabledToggle';
|
import { CanvasEntityEnabledToggle } from 'features/controlLayers/components/common/CanvasEntityEnabledToggle';
|
||||||
@ -15,14 +16,14 @@ type Props = {
|
|||||||
|
|
||||||
export const RasterLayer = memo(({ id }: Props) => {
|
export const RasterLayer = memo(({ id }: Props) => {
|
||||||
const entityIdentifier = useMemo<CanvasEntityIdentifier>(() => ({ id, type: 'raster_layer' }), [id]);
|
const entityIdentifier = useMemo<CanvasEntityIdentifier>(() => ({ id, type: 'raster_layer' }), [id]);
|
||||||
const editing = useDisclosure({ defaultIsOpen: false });
|
const editing = useBoolean(false);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<EntityIdentifierContext.Provider value={entityIdentifier}>
|
<EntityIdentifierContext.Provider value={entityIdentifier}>
|
||||||
<CanvasEntityContainer>
|
<CanvasEntityContainer>
|
||||||
<CanvasEntityHeader onDoubleClick={editing.onOpen}>
|
<CanvasEntityHeader onDoubleClick={editing.setTrue}>
|
||||||
<CanvasEntityEnabledToggle />
|
<CanvasEntityEnabledToggle />
|
||||||
{editing.isOpen ? <CanvasEntityTitleEdit onStopEditing={editing.onClose} /> : <CanvasEntityTitle />}
|
{editing.isTrue ? <CanvasEntityTitleEdit onStopEditing={editing.setFalse} /> : <CanvasEntityTitle />}
|
||||||
<Spacer />
|
<Spacer />
|
||||||
<CanvasEntityDeleteButton />
|
<CanvasEntityDeleteButton />
|
||||||
</CanvasEntityHeader>
|
</CanvasEntityHeader>
|
||||||
|
@ -1,4 +1,5 @@
|
|||||||
import { Spacer, useDisclosure } from '@invoke-ai/ui-library';
|
import { Spacer } from '@invoke-ai/ui-library';
|
||||||
|
import { useBoolean } from 'common/hooks/useBoolean';
|
||||||
import { CanvasEntityContainer } from 'features/controlLayers/components/common/CanvasEntityContainer';
|
import { CanvasEntityContainer } from 'features/controlLayers/components/common/CanvasEntityContainer';
|
||||||
import { CanvasEntityDeleteButton } from 'features/controlLayers/components/common/CanvasEntityDeleteButton';
|
import { CanvasEntityDeleteButton } from 'features/controlLayers/components/common/CanvasEntityDeleteButton';
|
||||||
import { CanvasEntityEnabledToggle } from 'features/controlLayers/components/common/CanvasEntityEnabledToggle';
|
import { CanvasEntityEnabledToggle } from 'features/controlLayers/components/common/CanvasEntityEnabledToggle';
|
||||||
@ -20,14 +21,14 @@ type Props = {
|
|||||||
|
|
||||||
export const RegionalGuidance = memo(({ id }: Props) => {
|
export const RegionalGuidance = memo(({ id }: Props) => {
|
||||||
const entityIdentifier = useMemo<CanvasEntityIdentifier>(() => ({ id, type: 'regional_guidance' }), [id]);
|
const entityIdentifier = useMemo<CanvasEntityIdentifier>(() => ({ id, type: 'regional_guidance' }), [id]);
|
||||||
const editing = useDisclosure({ defaultIsOpen: false });
|
const editing = useBoolean(false);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<EntityIdentifierContext.Provider value={entityIdentifier}>
|
<EntityIdentifierContext.Provider value={entityIdentifier}>
|
||||||
<CanvasEntityContainer>
|
<CanvasEntityContainer>
|
||||||
<CanvasEntityHeader onDoubleClick={editing.onOpen}>
|
<CanvasEntityHeader onDoubleClick={editing.setTrue}>
|
||||||
<CanvasEntityEnabledToggle />
|
<CanvasEntityEnabledToggle />
|
||||||
{editing.isOpen ? <CanvasEntityTitleEdit onStopEditing={editing.onClose} /> : <CanvasEntityTitle />}
|
{editing.isTrue ? <CanvasEntityTitleEdit onStopEditing={editing.setFalse} /> : <CanvasEntityTitle />}
|
||||||
<Spacer />
|
<Spacer />
|
||||||
<RegionalGuidanceBadges />
|
<RegionalGuidanceBadges />
|
||||||
<RegionalGuidanceMaskFillColorPicker />
|
<RegionalGuidanceMaskFillColorPicker />
|
||||||
|
@ -1,14 +1,14 @@
|
|||||||
import { Badge } from '@invoke-ai/ui-library';
|
import { Badge } from '@invoke-ai/ui-library';
|
||||||
import { useAppSelector } from 'app/store/storeHooks';
|
import { useAppSelector } from 'app/store/storeHooks';
|
||||||
import { useEntityIdentifierContext } from 'features/controlLayers/contexts/EntityIdentifierContext';
|
import { useEntityIdentifierContext } from 'features/controlLayers/contexts/EntityIdentifierContext';
|
||||||
import { selectRGOrThrow } from 'features/controlLayers/store/regionsReducers';
|
import { selectRegionalGuidanceEntityOrThrow } from 'features/controlLayers/store/regionsReducers';
|
||||||
import { memo } from 'react';
|
import { memo } from 'react';
|
||||||
import { useTranslation } from 'react-i18next';
|
import { useTranslation } from 'react-i18next';
|
||||||
|
|
||||||
export const RegionalGuidanceBadges = memo(() => {
|
export const RegionalGuidanceBadges = memo(() => {
|
||||||
const { id } = useEntityIdentifierContext();
|
const { id } = useEntityIdentifierContext();
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
const autoNegative = useAppSelector((s) => selectRGOrThrow(s.canvasV2, id).autoNegative);
|
const autoNegative = useAppSelector((s) => selectRegionalGuidanceEntityOrThrow(s.canvasV2, id).autoNegative);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
|
@ -14,7 +14,7 @@ import {
|
|||||||
rgIPAdapterModelChanged,
|
rgIPAdapterModelChanged,
|
||||||
rgIPAdapterWeightChanged,
|
rgIPAdapterWeightChanged,
|
||||||
} from 'features/controlLayers/store/canvasV2Slice';
|
} from 'features/controlLayers/store/canvasV2Slice';
|
||||||
import { selectRGOrThrow } from 'features/controlLayers/store/regionsReducers';
|
import { selectRegionalGuidanceEntityOrThrow } from 'features/controlLayers/store/regionsReducers';
|
||||||
import type { CLIPVisionModelV2, IPMethodV2 } from 'features/controlLayers/store/types';
|
import type { CLIPVisionModelV2, IPMethodV2 } from 'features/controlLayers/store/types';
|
||||||
import type { RGIPAdapterImageDropData } from 'features/dnd/types';
|
import type { RGIPAdapterImageDropData } from 'features/dnd/types';
|
||||||
import { memo, useCallback, useMemo } from 'react';
|
import { memo, useCallback, useMemo } from 'react';
|
||||||
@ -34,7 +34,7 @@ export const RegionalGuidanceIPAdapterSettings = memo(({ id, ipAdapterId, ipAdap
|
|||||||
dispatch(rgIPAdapterDeleted({ id, ipAdapterId }));
|
dispatch(rgIPAdapterDeleted({ id, ipAdapterId }));
|
||||||
}, [dispatch, ipAdapterId, id]);
|
}, [dispatch, ipAdapterId, id]);
|
||||||
const ipAdapter = useAppSelector((s) => {
|
const ipAdapter = useAppSelector((s) => {
|
||||||
const ipa = selectRGOrThrow(s.canvasV2, id).ipAdapters.find((ipa) => ipa.id === ipAdapterId);
|
const ipa = selectRegionalGuidanceEntityOrThrow(s.canvasV2, id).ipAdapters.find((ipa) => ipa.id === ipAdapterId);
|
||||||
assert(ipa, `Regional GuidanceIP Adapter with id ${ipAdapterId} not found`);
|
assert(ipa, `Regional GuidanceIP Adapter with id ${ipAdapterId} not found`);
|
||||||
return ipa;
|
return ipa;
|
||||||
});
|
});
|
||||||
@ -123,7 +123,7 @@ export const RegionalGuidanceIPAdapterSettings = memo(({ id, ipAdapterId, ipAdap
|
|||||||
</Flex>
|
</Flex>
|
||||||
<Flex alignItems="center" justifyContent="center" h={36} w={36} aspectRatio="1/1">
|
<Flex alignItems="center" justifyContent="center" h={36} w={36} aspectRatio="1/1">
|
||||||
<IPAdapterImagePreview
|
<IPAdapterImagePreview
|
||||||
image={ipAdapter.imageObject?.image ?? null}
|
image={ipAdapter.image ?? null}
|
||||||
onChangeImage={onChangeImage}
|
onChangeImage={onChangeImage}
|
||||||
ipAdapterId={ipAdapter.id}
|
ipAdapterId={ipAdapter.id}
|
||||||
droppableData={droppableData}
|
droppableData={droppableData}
|
||||||
|
@ -1,15 +1,30 @@
|
|||||||
import { Divider, Flex } from '@invoke-ai/ui-library';
|
import { Divider } from '@invoke-ai/ui-library';
|
||||||
|
import { EMPTY_ARRAY } from 'app/store/constants';
|
||||||
|
import { createMemoizedSelector } from 'app/store/createMemoizedSelector';
|
||||||
import { useAppSelector } from 'app/store/storeHooks';
|
import { useAppSelector } from 'app/store/storeHooks';
|
||||||
import { RegionalGuidanceIPAdapterSettings } from 'features/controlLayers/components/RegionalGuidance/RegionalGuidanceIPAdapterSettings';
|
import { RegionalGuidanceIPAdapterSettings } from 'features/controlLayers/components/RegionalGuidance/RegionalGuidanceIPAdapterSettings';
|
||||||
import { selectRGOrThrow } from 'features/controlLayers/store/regionsReducers';
|
import { selectCanvasV2Slice } from 'features/controlLayers/store/canvasV2Slice';
|
||||||
import { memo } from 'react';
|
import { selectRegionalGuidanceEntityOrThrow } from 'features/controlLayers/store/regionsReducers';
|
||||||
|
import { Fragment, memo, useMemo } from 'react';
|
||||||
|
|
||||||
type Props = {
|
type Props = {
|
||||||
id: string;
|
id: string;
|
||||||
};
|
};
|
||||||
|
|
||||||
export const RegionalGuidanceIPAdapters = memo(({ id }: Props) => {
|
export const RegionalGuidanceIPAdapters = memo(({ id }: Props) => {
|
||||||
const ipAdapterIds = useAppSelector((s) => selectRGOrThrow(s.canvasV2, id).ipAdapters.map(({ id }) => id));
|
const selectIPAdapterIds = useMemo(
|
||||||
|
() =>
|
||||||
|
createMemoizedSelector(selectCanvasV2Slice, (canvasV2) => {
|
||||||
|
const ipAdapterIds = selectRegionalGuidanceEntityOrThrow(canvasV2, id).ipAdapters.map(({ id }) => id);
|
||||||
|
if (ipAdapterIds.length === 0) {
|
||||||
|
return EMPTY_ARRAY;
|
||||||
|
}
|
||||||
|
return ipAdapterIds;
|
||||||
|
}),
|
||||||
|
[id]
|
||||||
|
);
|
||||||
|
|
||||||
|
const ipAdapterIds = useAppSelector(selectIPAdapterIds);
|
||||||
|
|
||||||
if (ipAdapterIds.length === 0) {
|
if (ipAdapterIds.length === 0) {
|
||||||
return null;
|
return null;
|
||||||
@ -17,15 +32,11 @@ export const RegionalGuidanceIPAdapters = memo(({ id }: Props) => {
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
{ipAdapterIds.map((id, index) => (
|
{ipAdapterIds.map((ipAdapterId, index) => (
|
||||||
<Flex flexDir="column" key={id}>
|
<Fragment key={ipAdapterId}>
|
||||||
{index > 0 && (
|
{index > 0 && <Divider />}
|
||||||
<Flex pb={3}>
|
<RegionalGuidanceIPAdapterSettings id={id} ipAdapterId={ipAdapterId} ipAdapterNumber={index + 1} />
|
||||||
<Divider />
|
</Fragment>
|
||||||
</Flex>
|
|
||||||
)}
|
|
||||||
<RegionalGuidanceIPAdapterSettings id={id} ipAdapterId={id} ipAdapterNumber={index + 1} />
|
|
||||||
</Flex>
|
|
||||||
))}
|
))}
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
|
@ -5,7 +5,7 @@ import { rgbColorToString } from 'common/util/colorCodeTransformers';
|
|||||||
import { stopPropagation } from 'common/util/stopPropagation';
|
import { stopPropagation } from 'common/util/stopPropagation';
|
||||||
import { useEntityIdentifierContext } from 'features/controlLayers/contexts/EntityIdentifierContext';
|
import { useEntityIdentifierContext } from 'features/controlLayers/contexts/EntityIdentifierContext';
|
||||||
import { rgFillChanged } from 'features/controlLayers/store/canvasV2Slice';
|
import { rgFillChanged } from 'features/controlLayers/store/canvasV2Slice';
|
||||||
import { selectRGOrThrow } from 'features/controlLayers/store/regionsReducers';
|
import { selectRegionalGuidanceEntityOrThrow } from 'features/controlLayers/store/regionsReducers';
|
||||||
import { memo, useCallback } from 'react';
|
import { memo, useCallback } from 'react';
|
||||||
import type { RgbColor } from 'react-colorful';
|
import type { RgbColor } from 'react-colorful';
|
||||||
import { useTranslation } from 'react-i18next';
|
import { useTranslation } from 'react-i18next';
|
||||||
@ -14,7 +14,7 @@ export const RegionalGuidanceMaskFillColorPicker = memo(() => {
|
|||||||
const entityIdentifier = useEntityIdentifierContext();
|
const entityIdentifier = useEntityIdentifierContext();
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
const dispatch = useAppDispatch();
|
const dispatch = useAppDispatch();
|
||||||
const fill = useAppSelector((s) => selectRGOrThrow(s.canvasV2, entityIdentifier.id).fill);
|
const fill = useAppSelector((s) => selectRegionalGuidanceEntityOrThrow(s.canvasV2, entityIdentifier.id).fill);
|
||||||
const onChange = useCallback(
|
const onChange = useCallback(
|
||||||
(fill: RgbColor) => {
|
(fill: RgbColor) => {
|
||||||
dispatch(rgFillChanged({ id: entityIdentifier.id, fill }));
|
dispatch(rgFillChanged({ id: entityIdentifier.id, fill }));
|
||||||
|
@ -37,9 +37,7 @@ export const RegionalGuidanceMenuItemsAddPromptsAndIPAdapter = memo(() => {
|
|||||||
dispatch(rgNegativePromptChanged({ id: id, prompt: '' }));
|
dispatch(rgNegativePromptChanged({ id: id, prompt: '' }));
|
||||||
}, [dispatch, id]);
|
}, [dispatch, id]);
|
||||||
const addIPAdapter = useCallback(() => {
|
const addIPAdapter = useCallback(() => {
|
||||||
dispatch(
|
dispatch(rgIPAdapterAdded({ id, ipAdapter: { ...defaultIPAdapter, id: nanoid() } }));
|
||||||
rgIPAdapterAdded({ id, ipAdapter: { ...defaultIPAdapter, id: nanoid(), type: 'ip_adapter', isEnabled: true } })
|
|
||||||
);
|
|
||||||
}, [defaultIPAdapter, dispatch, id]);
|
}, [defaultIPAdapter, dispatch, id]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
@ -2,7 +2,7 @@ import { Box, Textarea } from '@invoke-ai/ui-library';
|
|||||||
import { useAppDispatch, useAppSelector } from 'app/store/storeHooks';
|
import { useAppDispatch, useAppSelector } from 'app/store/storeHooks';
|
||||||
import { RegionalGuidanceDeletePromptButton } from 'features/controlLayers/components/RegionalGuidance/RegionalGuidanceDeletePromptButton';
|
import { RegionalGuidanceDeletePromptButton } from 'features/controlLayers/components/RegionalGuidance/RegionalGuidanceDeletePromptButton';
|
||||||
import { rgNegativePromptChanged } from 'features/controlLayers/store/canvasV2Slice';
|
import { rgNegativePromptChanged } from 'features/controlLayers/store/canvasV2Slice';
|
||||||
import { selectRGOrThrow } from 'features/controlLayers/store/regionsReducers';
|
import { selectRegionalGuidanceEntityOrThrow } from 'features/controlLayers/store/regionsReducers';
|
||||||
import { PromptOverlayButtonWrapper } from 'features/parameters/components/Prompts/PromptOverlayButtonWrapper';
|
import { PromptOverlayButtonWrapper } from 'features/parameters/components/Prompts/PromptOverlayButtonWrapper';
|
||||||
import { AddPromptTriggerButton } from 'features/prompt/AddPromptTriggerButton';
|
import { AddPromptTriggerButton } from 'features/prompt/AddPromptTriggerButton';
|
||||||
import { PromptPopover } from 'features/prompt/PromptPopover';
|
import { PromptPopover } from 'features/prompt/PromptPopover';
|
||||||
@ -15,7 +15,7 @@ type Props = {
|
|||||||
};
|
};
|
||||||
|
|
||||||
export const RegionalGuidanceNegativePrompt = memo(({ id }: Props) => {
|
export const RegionalGuidanceNegativePrompt = memo(({ id }: Props) => {
|
||||||
const prompt = useAppSelector((s) => selectRGOrThrow(s.canvasV2, id).negativePrompt ?? '');
|
const prompt = useAppSelector((s) => selectRegionalGuidanceEntityOrThrow(s.canvasV2, id).negativePrompt ?? '');
|
||||||
const dispatch = useAppDispatch();
|
const dispatch = useAppDispatch();
|
||||||
const textareaRef = useRef<HTMLTextAreaElement>(null);
|
const textareaRef = useRef<HTMLTextAreaElement>(null);
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
|
@ -2,7 +2,7 @@ import { Box, Textarea } from '@invoke-ai/ui-library';
|
|||||||
import { useAppDispatch, useAppSelector } from 'app/store/storeHooks';
|
import { useAppDispatch, useAppSelector } from 'app/store/storeHooks';
|
||||||
import { RegionalGuidanceDeletePromptButton } from 'features/controlLayers/components/RegionalGuidance/RegionalGuidanceDeletePromptButton';
|
import { RegionalGuidanceDeletePromptButton } from 'features/controlLayers/components/RegionalGuidance/RegionalGuidanceDeletePromptButton';
|
||||||
import { rgPositivePromptChanged } from 'features/controlLayers/store/canvasV2Slice';
|
import { rgPositivePromptChanged } from 'features/controlLayers/store/canvasV2Slice';
|
||||||
import { selectRGOrThrow } from 'features/controlLayers/store/regionsReducers';
|
import { selectRegionalGuidanceEntityOrThrow } from 'features/controlLayers/store/regionsReducers';
|
||||||
import { PromptOverlayButtonWrapper } from 'features/parameters/components/Prompts/PromptOverlayButtonWrapper';
|
import { PromptOverlayButtonWrapper } from 'features/parameters/components/Prompts/PromptOverlayButtonWrapper';
|
||||||
import { AddPromptTriggerButton } from 'features/prompt/AddPromptTriggerButton';
|
import { AddPromptTriggerButton } from 'features/prompt/AddPromptTriggerButton';
|
||||||
import { PromptPopover } from 'features/prompt/PromptPopover';
|
import { PromptPopover } from 'features/prompt/PromptPopover';
|
||||||
@ -15,7 +15,7 @@ type Props = {
|
|||||||
};
|
};
|
||||||
|
|
||||||
export const RegionalGuidancePositivePrompt = memo(({ id }: Props) => {
|
export const RegionalGuidancePositivePrompt = memo(({ id }: Props) => {
|
||||||
const prompt = useAppSelector((s) => selectRGOrThrow(s.canvasV2, id).positivePrompt ?? '');
|
const prompt = useAppSelector((s) => selectRegionalGuidanceEntityOrThrow(s.canvasV2, id).positivePrompt ?? '');
|
||||||
const dispatch = useAppDispatch();
|
const dispatch = useAppDispatch();
|
||||||
const textareaRef = useRef<HTMLTextAreaElement>(null);
|
const textareaRef = useRef<HTMLTextAreaElement>(null);
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
|
@ -1,8 +1,9 @@
|
|||||||
|
import { Divider } from '@invoke-ai/ui-library';
|
||||||
import { useAppSelector } from 'app/store/storeHooks';
|
import { useAppSelector } from 'app/store/storeHooks';
|
||||||
import { AddPromptButtons } from 'features/controlLayers/components/AddPromptButtons';
|
import { AddPromptButtons } from 'features/controlLayers/components/AddPromptButtons';
|
||||||
import { CanvasEntitySettingsWrapper } from 'features/controlLayers/components/common/CanvasEntitySettingsWrapper';
|
import { CanvasEntitySettingsWrapper } from 'features/controlLayers/components/common/CanvasEntitySettingsWrapper';
|
||||||
import { useEntityIdentifierContext } from 'features/controlLayers/contexts/EntityIdentifierContext';
|
import { useEntityIdentifierContext } from 'features/controlLayers/contexts/EntityIdentifierContext';
|
||||||
import { selectRGOrThrow } from 'features/controlLayers/store/regionsReducers';
|
import { selectRegionalGuidanceEntityOrThrow } from 'features/controlLayers/store/regionsReducers';
|
||||||
import { memo } from 'react';
|
import { memo } from 'react';
|
||||||
|
|
||||||
import { RegionalGuidanceIPAdapters } from './RegionalGuidanceIPAdapters';
|
import { RegionalGuidanceIPAdapters } from './RegionalGuidanceIPAdapters';
|
||||||
@ -11,15 +12,31 @@ import { RegionalGuidancePositivePrompt } from './RegionalGuidancePositivePrompt
|
|||||||
|
|
||||||
export const RegionalGuidanceSettings = memo(() => {
|
export const RegionalGuidanceSettings = memo(() => {
|
||||||
const { id } = useEntityIdentifierContext();
|
const { id } = useEntityIdentifierContext();
|
||||||
const hasPositivePrompt = useAppSelector((s) => selectRGOrThrow(s.canvasV2, id).positivePrompt !== null);
|
const hasPositivePrompt = useAppSelector(
|
||||||
const hasNegativePrompt = useAppSelector((s) => selectRGOrThrow(s.canvasV2, id).negativePrompt !== null);
|
(s) => selectRegionalGuidanceEntityOrThrow(s.canvasV2, id).positivePrompt !== null
|
||||||
const hasIPAdapters = useAppSelector((s) => selectRGOrThrow(s.canvasV2, id).ipAdapters.length > 0);
|
);
|
||||||
|
const hasNegativePrompt = useAppSelector(
|
||||||
|
(s) => selectRegionalGuidanceEntityOrThrow(s.canvasV2, id).negativePrompt !== null
|
||||||
|
);
|
||||||
|
const hasIPAdapters = useAppSelector(
|
||||||
|
(s) => selectRegionalGuidanceEntityOrThrow(s.canvasV2, id).ipAdapters.length > 0
|
||||||
|
);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<CanvasEntitySettingsWrapper>
|
<CanvasEntitySettingsWrapper>
|
||||||
{!hasPositivePrompt && !hasNegativePrompt && !hasIPAdapters && <AddPromptButtons id={id} />}
|
{!hasPositivePrompt && !hasNegativePrompt && !hasIPAdapters && <AddPromptButtons id={id} />}
|
||||||
{hasPositivePrompt && <RegionalGuidancePositivePrompt id={id} />}
|
{hasPositivePrompt && (
|
||||||
{hasNegativePrompt && <RegionalGuidanceNegativePrompt id={id} />}
|
<>
|
||||||
|
<RegionalGuidancePositivePrompt id={id} />
|
||||||
|
{(hasNegativePrompt || hasIPAdapters) && <Divider />}
|
||||||
|
</>
|
||||||
|
)}
|
||||||
|
{hasNegativePrompt && (
|
||||||
|
<>
|
||||||
|
<RegionalGuidanceNegativePrompt id={id} />
|
||||||
|
{hasIPAdapters && <Divider />}
|
||||||
|
</>
|
||||||
|
)}
|
||||||
{hasIPAdapters && <RegionalGuidanceIPAdapters id={id} />}
|
{hasIPAdapters && <RegionalGuidanceIPAdapters id={id} />}
|
||||||
</CanvasEntitySettingsWrapper>
|
</CanvasEntitySettingsWrapper>
|
||||||
);
|
);
|
||||||
|
@ -14,7 +14,7 @@ import { useAppDispatch, useAppSelector } from 'app/store/storeHooks';
|
|||||||
import { stopPropagation } from 'common/util/stopPropagation';
|
import { stopPropagation } from 'common/util/stopPropagation';
|
||||||
import { useEntityIdentifierContext } from 'features/controlLayers/contexts/EntityIdentifierContext';
|
import { useEntityIdentifierContext } from 'features/controlLayers/contexts/EntityIdentifierContext';
|
||||||
import { rgAutoNegativeChanged } from 'features/controlLayers/store/canvasV2Slice';
|
import { rgAutoNegativeChanged } from 'features/controlLayers/store/canvasV2Slice';
|
||||||
import { selectRGOrThrow } from 'features/controlLayers/store/regionsReducers';
|
import { selectRegionalGuidanceEntityOrThrow } from 'features/controlLayers/store/regionsReducers';
|
||||||
import type { ChangeEvent } from 'react';
|
import type { ChangeEvent } from 'react';
|
||||||
import { memo, useCallback } from 'react';
|
import { memo, useCallback } from 'react';
|
||||||
import { useTranslation } from 'react-i18next';
|
import { useTranslation } from 'react-i18next';
|
||||||
@ -24,7 +24,7 @@ export const RegionalGuidanceSettingsPopover = memo(() => {
|
|||||||
const entityIdentifier = useEntityIdentifierContext();
|
const entityIdentifier = useEntityIdentifierContext();
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
const dispatch = useAppDispatch();
|
const dispatch = useAppDispatch();
|
||||||
const autoNegative = useAppSelector((s) => selectRGOrThrow(s.canvasV2, entityIdentifier.id).autoNegative);
|
const autoNegative = useAppSelector((s) => selectRegionalGuidanceEntityOrThrow(s.canvasV2, entityIdentifier.id).autoNegative);
|
||||||
const onChange = useCallback(
|
const onChange = useCallback(
|
||||||
(e: ChangeEvent<HTMLInputElement>) => {
|
(e: ChangeEvent<HTMLInputElement>) => {
|
||||||
dispatch(rgAutoNegativeChanged({ id: entityIdentifier.id, autoNegative: e.target.checked ? 'invert' : 'off' }));
|
dispatch(rgAutoNegativeChanged({ id: entityIdentifier.id, autoNegative: e.target.checked ? 'invert' : 'off' }));
|
||||||
|
@ -49,7 +49,15 @@ export const CanvasEntityTitleEdit = memo(({ onStopEditing }: Props) => {
|
|||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Input ref={ref} value={localTitle} onChange={onChange} onBlur={onBlur} onKeyDown={onKeyDown} variant="outline" />
|
<Input
|
||||||
|
ref={ref}
|
||||||
|
value={localTitle}
|
||||||
|
onChange={onChange}
|
||||||
|
onBlur={onBlur}
|
||||||
|
onKeyDown={onKeyDown}
|
||||||
|
variant="outline"
|
||||||
|
_focusVisible={{ borderWidth: 1, borderColor: 'invokeBlueAlpha.400', borderRadius: 'base' }}
|
||||||
|
/>
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -38,7 +38,7 @@ export const useEntityTitle = (entityIdentifier: CanvasEntityIdentifier) => {
|
|||||||
} else if (entityIdentifier.type === 'raster_layer') {
|
} else if (entityIdentifier.type === 'raster_layer') {
|
||||||
parts.push(t('controlLayers.rasterLayer'));
|
parts.push(t('controlLayers.rasterLayer'));
|
||||||
} else if (entityIdentifier.type === 'ip_adapter') {
|
} else if (entityIdentifier.type === 'ip_adapter') {
|
||||||
parts.push(t('controlLayers.ipAdapter'));
|
parts.push(t('common.ipAdapter'));
|
||||||
} else if (entityIdentifier.type === 'regional_guidance') {
|
} else if (entityIdentifier.type === 'regional_guidance') {
|
||||||
parts.push(t('controlLayers.regionalGuidance'));
|
parts.push(t('controlLayers.regionalGuidance'));
|
||||||
} else {
|
} else {
|
||||||
|
@ -3,7 +3,12 @@ import { useAppSelector } from 'app/store/storeHooks';
|
|||||||
import { deepClone } from 'common/util/deepClone';
|
import { deepClone } from 'common/util/deepClone';
|
||||||
import { selectCanvasV2Slice } from 'features/controlLayers/store/canvasV2Slice';
|
import { selectCanvasV2Slice } from 'features/controlLayers/store/canvasV2Slice';
|
||||||
import { selectControlLayerOrThrow } from 'features/controlLayers/store/controlLayersReducers';
|
import { selectControlLayerOrThrow } from 'features/controlLayers/store/controlLayersReducers';
|
||||||
import type { CanvasEntityIdentifier } from 'features/controlLayers/store/types';
|
import type {
|
||||||
|
CanvasEntityIdentifier,
|
||||||
|
ControlNetConfig,
|
||||||
|
IPAdapterConfig,
|
||||||
|
T2IAdapterConfig,
|
||||||
|
} from 'features/controlLayers/store/types';
|
||||||
import { initialControlNetV2, initialIPAdapterV2, initialT2IAdapterV2 } from 'features/controlLayers/store/types';
|
import { initialControlNetV2, initialIPAdapterV2, initialT2IAdapterV2 } from 'features/controlLayers/store/types';
|
||||||
import { zModelIdentifierField } from 'features/nodes/types/common';
|
import { zModelIdentifierField } from 'features/nodes/types/common';
|
||||||
import { useMemo } from 'react';
|
import { useMemo } from 'react';
|
||||||
@ -22,7 +27,7 @@ export const useControlLayerControlAdapter = (entityIdentifier: CanvasEntityIden
|
|||||||
return controlAdapter;
|
return controlAdapter;
|
||||||
};
|
};
|
||||||
|
|
||||||
export const useDefaultControlAdapter = () => {
|
export const useDefaultControlAdapter = (): ControlNetConfig | T2IAdapterConfig => {
|
||||||
const [modelConfigs] = useControlNetAndT2IAdapterModels();
|
const [modelConfigs] = useControlNetAndT2IAdapterModels();
|
||||||
|
|
||||||
const baseModel = useAppSelector((s) => s.canvasV2.params.model?.base);
|
const baseModel = useAppSelector((s) => s.canvasV2.params.model?.base);
|
||||||
@ -43,7 +48,7 @@ export const useDefaultControlAdapter = () => {
|
|||||||
return defaultControlAdapter;
|
return defaultControlAdapter;
|
||||||
};
|
};
|
||||||
|
|
||||||
export const useDefaultIPAdapter = () => {
|
export const useDefaultIPAdapter = (): IPAdapterConfig => {
|
||||||
const [modelConfigs] = useIPAdapterModels();
|
const [modelConfigs] = useIPAdapterModels();
|
||||||
|
|
||||||
const baseModel = useAppSelector((s) => s.canvasV2.params.model?.base);
|
const baseModel = useAppSelector((s) => s.canvasV2.params.model?.base);
|
||||||
|
@ -38,8 +38,8 @@ export class CanvasFilter {
|
|||||||
const { config } = this.manager.stateApi.getFilterState();
|
const { config } = this.manager.stateApi.getFilterState();
|
||||||
this.log.trace({ config }, 'Previewing filter');
|
this.log.trace({ config }, 'Previewing filter');
|
||||||
const dispatch = this.manager.stateApi._store.dispatch;
|
const dispatch = this.manager.stateApi._store.dispatch;
|
||||||
|
const rect = this.parent.transformer.getRelativeRect()
|
||||||
const imageDTO = await this.parent.renderer.rasterize();
|
const imageDTO = await this.parent.renderer.rasterize(rect, false);
|
||||||
// TODO(psyche): I can't get TS to be happy, it thinkgs `config` is `never` but it should be inferred from the generic... I'll just cast it for now
|
// TODO(psyche): I can't get TS to be happy, it thinkgs `config` is `never` but it should be inferred from the generic... I'll just cast it for now
|
||||||
const filterNode = IMAGE_FILTERS[config.type].buildNode(imageDTO, config as never);
|
const filterNode = IMAGE_FILTERS[config.type].buildNode(imageDTO, config as never);
|
||||||
const enqueueBatchArg: BatchConfig = {
|
const enqueueBatchArg: BatchConfig = {
|
||||||
@ -106,6 +106,7 @@ export class CanvasFilter {
|
|||||||
width: this.imageState.image.height,
|
width: this.imageState.image.height,
|
||||||
height: this.imageState.image.width,
|
height: this.imageState.image.width,
|
||||||
},
|
},
|
||||||
|
replaceObjects: true,
|
||||||
});
|
});
|
||||||
this.parent.renderer.showObjects();
|
this.parent.renderer.showObjects();
|
||||||
this.manager.stateApi.$filteringEntity.set(null);
|
this.manager.stateApi.$filteringEntity.set(null);
|
||||||
|
@ -20,7 +20,6 @@ import type {
|
|||||||
ImageCache,
|
ImageCache,
|
||||||
Rect,
|
Rect,
|
||||||
} from 'features/controlLayers/store/types';
|
} from 'features/controlLayers/store/types';
|
||||||
import { isValidLayerWithoutControlAdapter } from 'features/nodes/util/graph/generation/addLayers';
|
|
||||||
import type Konva from 'konva';
|
import type Konva from 'konva';
|
||||||
import { clamp, isEqual } from 'lodash-es';
|
import { clamp, isEqual } from 'lodash-es';
|
||||||
import { atom } from 'nanostores';
|
import { atom } from 'nanostores';
|
||||||
@ -538,7 +537,8 @@ export class CanvasManager {
|
|||||||
stageClone.x(0);
|
stageClone.x(0);
|
||||||
stageClone.y(0);
|
stageClone.y(0);
|
||||||
|
|
||||||
const validLayers = layersState.entities.filter(isValidLayerWithoutControlAdapter);
|
const validLayers = layersState.entities.filter((entity) => entity.isEnabled && entity.objects.length > 0);
|
||||||
|
|
||||||
// getLayers() returns the internal `children` array of the stage directly - calling destroy on a layer will
|
// getLayers() returns the internal `children` array of the stage directly - calling destroy on a layer will
|
||||||
// mutate that array. We need to clone the array to avoid mutating the original.
|
// mutate that array. We need to clone the array to avoid mutating the original.
|
||||||
for (const konvaLayer of stageClone.getLayers().slice()) {
|
for (const konvaLayer of stageClone.getLayers().slice()) {
|
||||||
|
@ -374,8 +374,7 @@ export class CanvasObjectRenderer {
|
|||||||
* @param rect The rect to rasterize. If omitted, the entity's full rect will be used.
|
* @param rect The rect to rasterize. If omitted, the entity's full rect will be used.
|
||||||
* @returns A promise that resolves to the rasterized image DTO.
|
* @returns A promise that resolves to the rasterized image DTO.
|
||||||
*/
|
*/
|
||||||
rasterize = async (rect?: Rect): Promise<ImageDTO> => {
|
rasterize = async (rect: Rect, replaceObjects: boolean = false): Promise<ImageDTO> => {
|
||||||
rect = rect ?? this.parent.transformer.getRelativeRect();
|
|
||||||
let imageDTO: ImageDTO | null = null;
|
let imageDTO: ImageDTO | null = null;
|
||||||
const rasterizedImageCache = this.getRasterizedImageCache(rect);
|
const rasterizedImageCache = this.getRasterizedImageCache(rect);
|
||||||
|
|
||||||
@ -400,6 +399,7 @@ export class CanvasObjectRenderer {
|
|||||||
entityIdentifier: this.parent.getEntityIdentifier(),
|
entityIdentifier: this.parent.getEntityIdentifier(),
|
||||||
imageObject,
|
imageObject,
|
||||||
rect: { x: Math.round(rect.x), y: Math.round(rect.y), width: imageDTO.width, height: imageDTO.height },
|
rect: { x: Math.round(rect.x), y: Math.round(rect.y), width: imageDTO.width, height: imageDTO.height },
|
||||||
|
replaceObjects,
|
||||||
});
|
});
|
||||||
|
|
||||||
return imageDTO;
|
return imageDTO;
|
||||||
|
@ -58,7 +58,7 @@ export class CanvasTransformer {
|
|||||||
/**
|
/**
|
||||||
* Whether the transformer is currently calculating the rect of the parent.
|
* Whether the transformer is currently calculating the rect of the parent.
|
||||||
*/
|
*/
|
||||||
isPendingRectCalculation: boolean = false;
|
isPendingRectCalculation: boolean = true;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* A set of subscriptions that should be cleaned up when the transformer is destroyed.
|
* A set of subscriptions that should be cleaned up when the transformer is destroyed.
|
||||||
@ -506,7 +506,8 @@ export class CanvasTransformer {
|
|||||||
*/
|
*/
|
||||||
applyTransform = async () => {
|
applyTransform = async () => {
|
||||||
this.log.debug('Applying transform');
|
this.log.debug('Applying transform');
|
||||||
await this.parent.renderer.rasterize();
|
const rect = this.getRelativeRect();
|
||||||
|
await this.parent.renderer.rasterize(rect, true);
|
||||||
this.requestRectCalculation();
|
this.requestRectCalculation();
|
||||||
this.stopTransform();
|
this.stopTransform();
|
||||||
};
|
};
|
||||||
@ -589,7 +590,7 @@ export class CanvasTransformer {
|
|||||||
};
|
};
|
||||||
|
|
||||||
updateBbox = () => {
|
updateBbox = () => {
|
||||||
this.log.trace('Updating bbox');
|
this.log.trace({ nodeRect: this.nodeRect, pixelRect: this.pixelRect }, 'Updating bbox');
|
||||||
|
|
||||||
if (this.isPendingRectCalculation) {
|
if (this.isPendingRectCalculation) {
|
||||||
this.syncInteractionState();
|
this.syncInteractionState();
|
||||||
@ -600,10 +601,8 @@ export class CanvasTransformer {
|
|||||||
// eraser lines, fully clipped brush lines or if it has been fully erased.
|
// eraser lines, fully clipped brush lines or if it has been fully erased.
|
||||||
if (this.pixelRect.width === 0 || this.pixelRect.height === 0) {
|
if (this.pixelRect.width === 0 || this.pixelRect.height === 0) {
|
||||||
// We shouldn't reset on the first render - the bbox will be calculated on the next render
|
// We shouldn't reset on the first render - the bbox will be calculated on the next render
|
||||||
if (!this.parent.renderer.hasObjects()) {
|
|
||||||
// The layer is fully transparent but has objects - reset it
|
// The layer is fully transparent but has objects - reset it
|
||||||
this.manager.stateApi.resetEntity({ entityIdentifier: this.parent.getEntityIdentifier() });
|
this.manager.stateApi.resetEntity({ entityIdentifier: this.parent.getEntityIdentifier() });
|
||||||
}
|
|
||||||
this.syncInteractionState();
|
this.syncInteractionState();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
@ -154,6 +154,8 @@ export function selectEntity(state: CanvasV2State, { id, type }: CanvasEntityIde
|
|||||||
return state.inpaintMask;
|
return state.inpaintMask;
|
||||||
case 'regional_guidance':
|
case 'regional_guidance':
|
||||||
return state.regions.entities.find((rg) => rg.id === id);
|
return state.regions.entities.find((rg) => rg.id === id);
|
||||||
|
case 'ip_adapter':
|
||||||
|
return state.ipAdapters.entities.find((ipa) => ipa.id === id);
|
||||||
default:
|
default:
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@ -246,19 +248,22 @@ export const canvasV2Slice = createSlice({
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
entityRasterized: (state, action: PayloadAction<EntityRasterizedPayload>) => {
|
entityRasterized: (state, action: PayloadAction<EntityRasterizedPayload>) => {
|
||||||
const { entityIdentifier, imageObject, rect } = action.payload;
|
const { entityIdentifier, imageObject, rect, replaceObjects } = action.payload;
|
||||||
const entity = selectEntity(state, entityIdentifier);
|
const entity = selectEntity(state, entityIdentifier);
|
||||||
if (!entity) {
|
if (!entity) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (isDrawableEntity(entity)) {
|
if (isDrawableEntity(entity)) {
|
||||||
entity.objects = [imageObject];
|
|
||||||
entity.position = { x: rect.x, y: rect.y };
|
|
||||||
// Remove the cache for the given rect. This should never happen, because we should never rasterize the same
|
// Remove the cache for the given rect. This should never happen, because we should never rasterize the same
|
||||||
// rect twice. Just in case, we remove the old cache.
|
// rect twice. Just in case, we remove the old cache.
|
||||||
entity.rasterizationCache = entity.rasterizationCache.filter((cache) => !isEqual(cache.rect, rect));
|
entity.rasterizationCache = entity.rasterizationCache.filter((cache) => !isEqual(cache.rect, rect));
|
||||||
entity.rasterizationCache.push({ imageName: imageObject.image.image_name, rect });
|
entity.rasterizationCache.push({ imageName: imageObject.image.image_name, rect });
|
||||||
|
|
||||||
|
if (replaceObjects) {
|
||||||
|
entity.objects = [imageObject];
|
||||||
|
entity.position = { x: rect.x, y: rect.y };
|
||||||
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
entityBrushLineAdded: (state, action: PayloadAction<EntityBrushLineAddedPayload>) => {
|
entityBrushLineAdded: (state, action: PayloadAction<EntityBrushLineAddedPayload>) => {
|
||||||
@ -328,6 +333,13 @@ export const canvasV2Slice = createSlice({
|
|||||||
if (region) {
|
if (region) {
|
||||||
selectedEntityIdentifier = { type: region.type, id: region.id };
|
selectedEntityIdentifier = { type: region.type, id: region.id };
|
||||||
}
|
}
|
||||||
|
} else if (entityIdentifier.type === 'ip_adapter') {
|
||||||
|
const index = state.ipAdapters.entities.findIndex((layer) => layer.id === entityIdentifier.id);
|
||||||
|
state.ipAdapters.entities = state.ipAdapters.entities.filter((rg) => rg.id !== entityIdentifier.id);
|
||||||
|
const entity = state.ipAdapters.entities[index];
|
||||||
|
if (entity) {
|
||||||
|
selectedEntityIdentifier = { type: entity.type, id: entity.id };
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
assert(false, 'Not implemented');
|
assert(false, 'Not implemented');
|
||||||
}
|
}
|
||||||
|
@ -4,30 +4,32 @@ import type { ImageDTO, IPAdapterModelConfig } from 'services/api/types';
|
|||||||
import { assert } from 'tsafe';
|
import { assert } from 'tsafe';
|
||||||
import { v4 as uuidv4 } from 'uuid';
|
import { v4 as uuidv4 } from 'uuid';
|
||||||
|
|
||||||
import type { CanvasV2State, CLIPVisionModelV2, IPAdapterConfig, CanvasIPAdapterState, IPMethodV2 } from './types';
|
import type { CanvasIPAdapterState, CanvasV2State, CLIPVisionModelV2, IPAdapterConfig, IPMethodV2 } from './types';
|
||||||
import { imageDTOToImageObject } from './types';
|
import { imageDTOToImageWithDims } from './types';
|
||||||
|
|
||||||
export const selectIPA = (state: CanvasV2State, id: string) => state.ipAdapters.entities.find((ipa) => ipa.id === id);
|
export const selectIPAdapterEntity = (state: CanvasV2State, id: string) =>
|
||||||
export const selectIPAOrThrow = (state: CanvasV2State, id: string) => {
|
state.ipAdapters.entities.find((ipa) => ipa.id === id);
|
||||||
const ipa = selectIPA(state, id);
|
export const selectIPAdapterEntityOrThrow = (state: CanvasV2State, id: string) => {
|
||||||
assert(ipa, `IP Adapter with id ${id} not found`);
|
const entity = selectIPAdapterEntity(state, id);
|
||||||
return ipa;
|
assert(entity, `IP Adapter with id ${id} not found`);
|
||||||
|
return entity;
|
||||||
};
|
};
|
||||||
|
|
||||||
export const ipAdaptersReducers = {
|
export const ipAdaptersReducers = {
|
||||||
ipaAdded: {
|
ipaAdded: {
|
||||||
reducer: (state, action: PayloadAction<{ id: string; config: IPAdapterConfig }>) => {
|
reducer: (state, action: PayloadAction<{ id: string; ipAdapter: IPAdapterConfig }>) => {
|
||||||
const { id, config } = action.payload;
|
const { id, ipAdapter } = action.payload;
|
||||||
const layer: CanvasIPAdapterState = {
|
const layer: CanvasIPAdapterState = {
|
||||||
id,
|
id,
|
||||||
type: 'ip_adapter',
|
type: 'ip_adapter',
|
||||||
|
name: null,
|
||||||
isEnabled: true,
|
isEnabled: true,
|
||||||
...config,
|
ipAdapter,
|
||||||
};
|
};
|
||||||
state.ipAdapters.entities.push(layer);
|
state.ipAdapters.entities.push(layer);
|
||||||
state.selectedEntityIdentifier = { type: 'ip_adapter', id };
|
state.selectedEntityIdentifier = { type: 'ip_adapter', id };
|
||||||
},
|
},
|
||||||
prepare: (payload: { config: IPAdapterConfig }) => ({ payload: { id: uuidv4(), ...payload } }),
|
prepare: (payload: { ipAdapter: IPAdapterConfig }) => ({ payload: { id: uuidv4(), ...payload } }),
|
||||||
},
|
},
|
||||||
ipaRecalled: (state, action: PayloadAction<{ data: CanvasIPAdapterState }>) => {
|
ipaRecalled: (state, action: PayloadAction<{ data: CanvasIPAdapterState }>) => {
|
||||||
const { data } = action.payload;
|
const { data } = action.payload;
|
||||||
@ -36,7 +38,7 @@ export const ipAdaptersReducers = {
|
|||||||
},
|
},
|
||||||
ipaIsEnabledToggled: (state, action: PayloadAction<{ id: string }>) => {
|
ipaIsEnabledToggled: (state, action: PayloadAction<{ id: string }>) => {
|
||||||
const { id } = action.payload;
|
const { id } = action.payload;
|
||||||
const ipa = selectIPA(state, id);
|
const ipa = selectIPAdapterEntity(state, id);
|
||||||
if (ipa) {
|
if (ipa) {
|
||||||
ipa.isEnabled = !ipa.isEnabled;
|
ipa.isEnabled = !ipa.isEnabled;
|
||||||
}
|
}
|
||||||
@ -49,64 +51,54 @@ export const ipAdaptersReducers = {
|
|||||||
state.ipAdapters.entities = [];
|
state.ipAdapters.entities = [];
|
||||||
},
|
},
|
||||||
ipaImageChanged: {
|
ipaImageChanged: {
|
||||||
reducer: (state, action: PayloadAction<{ id: string; imageDTO: ImageDTO | null; objectId: string }>) => {
|
reducer: (state, action: PayloadAction<{ id: string; imageDTO: ImageDTO | null }>) => {
|
||||||
const { id, imageDTO, objectId } = action.payload;
|
const { id, imageDTO } = action.payload;
|
||||||
const ipa = selectIPA(state, id);
|
const entity = selectIPAdapterEntity(state, id);
|
||||||
if (!ipa) {
|
if (!entity) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
ipa.imageObject = imageDTO ? imageDTOToImageObject(imageDTO) : null;
|
entity.ipAdapter.image = imageDTO ? imageDTOToImageWithDims(imageDTO) : null;
|
||||||
},
|
},
|
||||||
prepare: (payload: { id: string; imageDTO: ImageDTO | null }) => ({ payload: { ...payload, objectId: uuidv4() } }),
|
prepare: (payload: { id: string; imageDTO: ImageDTO | null }) => ({ payload: { ...payload, objectId: uuidv4() } }),
|
||||||
},
|
},
|
||||||
ipaMethodChanged: (state, action: PayloadAction<{ id: string; method: IPMethodV2 }>) => {
|
ipaMethodChanged: (state, action: PayloadAction<{ id: string; method: IPMethodV2 }>) => {
|
||||||
const { id, method } = action.payload;
|
const { id, method } = action.payload;
|
||||||
const ipa = selectIPA(state, id);
|
const entity = selectIPAdapterEntity(state, id);
|
||||||
if (!ipa) {
|
if (!entity) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
ipa.method = method;
|
entity.ipAdapter.method = method;
|
||||||
},
|
},
|
||||||
ipaModelChanged: (
|
ipaModelChanged: (state, action: PayloadAction<{ id: string; modelConfig: IPAdapterModelConfig | null }>) => {
|
||||||
state,
|
|
||||||
action: PayloadAction<{
|
|
||||||
id: string;
|
|
||||||
modelConfig: IPAdapterModelConfig | null;
|
|
||||||
}>
|
|
||||||
) => {
|
|
||||||
const { id, modelConfig } = action.payload;
|
const { id, modelConfig } = action.payload;
|
||||||
const ipa = selectIPA(state, id);
|
const entity = selectIPAdapterEntity(state, id);
|
||||||
if (!ipa) {
|
if (!entity) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if (modelConfig) {
|
entity.ipAdapter.model = modelConfig ? zModelIdentifierField.parse(modelConfig) : null;
|
||||||
ipa.model = zModelIdentifierField.parse(modelConfig);
|
|
||||||
} else {
|
|
||||||
ipa.model = null;
|
|
||||||
}
|
|
||||||
},
|
},
|
||||||
ipaCLIPVisionModelChanged: (state, action: PayloadAction<{ id: string; clipVisionModel: CLIPVisionModelV2 }>) => {
|
ipaCLIPVisionModelChanged: (state, action: PayloadAction<{ id: string; clipVisionModel: CLIPVisionModelV2 }>) => {
|
||||||
const { id, clipVisionModel } = action.payload;
|
const { id, clipVisionModel } = action.payload;
|
||||||
const ipa = selectIPA(state, id);
|
const entity = selectIPAdapterEntity(state, id);
|
||||||
if (!ipa) {
|
if (!entity) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
ipa.clipVisionModel = clipVisionModel;
|
entity.ipAdapter.clipVisionModel = clipVisionModel;
|
||||||
},
|
},
|
||||||
ipaWeightChanged: (state, action: PayloadAction<{ id: string; weight: number }>) => {
|
ipaWeightChanged: (state, action: PayloadAction<{ id: string; weight: number }>) => {
|
||||||
const { id, weight } = action.payload;
|
const { id, weight } = action.payload;
|
||||||
const ipa = selectIPA(state, id);
|
const entity = selectIPAdapterEntity(state, id);
|
||||||
if (!ipa) {
|
if (!entity) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
ipa.weight = weight;
|
entity.ipAdapter.weight = weight;
|
||||||
},
|
},
|
||||||
ipaBeginEndStepPctChanged: (state, action: PayloadAction<{ id: string; beginEndStepPct: [number, number] }>) => {
|
ipaBeginEndStepPctChanged: (state, action: PayloadAction<{ id: string; beginEndStepPct: [number, number] }>) => {
|
||||||
const { id, beginEndStepPct } = action.payload;
|
const { id, beginEndStepPct } = action.payload;
|
||||||
const ipa = selectIPA(state, id);
|
const entity = selectIPAdapterEntity(state, id);
|
||||||
if (!ipa) {
|
if (!entity) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
ipa.beginEndStepPct = beginEndStepPct;
|
entity.ipAdapter.beginEndStepPct = beginEndStepPct;
|
||||||
},
|
},
|
||||||
} satisfies SliceCaseReducers<CanvasV2State>;
|
} satisfies SliceCaseReducers<CanvasV2State>;
|
||||||
|
@ -1,18 +1,32 @@
|
|||||||
import type { PayloadAction, SliceCaseReducers } from '@reduxjs/toolkit';
|
import type { PayloadAction, SliceCaseReducers } from '@reduxjs/toolkit';
|
||||||
import { getPrefixedId } from 'features/controlLayers/konva/util';
|
import { getPrefixedId } from 'features/controlLayers/konva/util';
|
||||||
import type { CanvasV2State, CLIPVisionModelV2, IPMethodV2 } from 'features/controlLayers/store/types';
|
import type {
|
||||||
import { imageDTOToImageObject } from 'features/controlLayers/store/types';
|
CanvasV2State,
|
||||||
|
CLIPVisionModelV2,
|
||||||
|
IPMethodV2,
|
||||||
|
RegionalGuidanceIPAdapterConfig,
|
||||||
|
} from 'features/controlLayers/store/types';
|
||||||
|
import { imageDTOToImageWithDims } from 'features/controlLayers/store/types';
|
||||||
import { zModelIdentifierField } from 'features/nodes/types/common';
|
import { zModelIdentifierField } from 'features/nodes/types/common';
|
||||||
import type { ParameterAutoNegative } from 'features/parameters/types/parameterSchemas';
|
import type { ParameterAutoNegative } from 'features/parameters/types/parameterSchemas';
|
||||||
import { isEqual } from 'lodash-es';
|
import { isEqual } from 'lodash-es';
|
||||||
import type { ImageDTO, IPAdapterModelConfig } from 'services/api/types';
|
import type { ImageDTO, IPAdapterModelConfig } from 'services/api/types';
|
||||||
import { assert } from 'tsafe';
|
import { assert } from 'tsafe';
|
||||||
|
|
||||||
import type { CanvasIPAdapterState, CanvasRegionalGuidanceState, RgbColor } from './types';
|
import type { CanvasRegionalGuidanceState, RgbColor } from './types';
|
||||||
|
|
||||||
export const selectRG = (state: CanvasV2State, id: string) => state.regions.entities.find((rg) => rg.id === id);
|
export const selectRegionalGuidanceEntity = (state: CanvasV2State, id: string) => {
|
||||||
export const selectRGOrThrow = (state: CanvasV2State, id: string) => {
|
return state.regions.entities.find((rg) => rg.id === id);
|
||||||
const rg = selectRG(state, id);
|
};
|
||||||
|
export const selectRegionalGuidanceIPAdapter = (state: CanvasV2State, id: string, ipAdapterId: string) => {
|
||||||
|
const entity = state.regions.entities.find((rg) => rg.id === id);
|
||||||
|
if (!entity) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
return entity.ipAdapters.find((ipa) => ipa.id === ipAdapterId);
|
||||||
|
};
|
||||||
|
export const selectRegionalGuidanceEntityOrThrow = (state: CanvasV2State, id: string) => {
|
||||||
|
const rg = selectRegionalGuidanceEntity(state, id);
|
||||||
assert(rg, `Region with id ${id} not found`);
|
assert(rg, `Region with id ${id} not found`);
|
||||||
return rg;
|
return rg;
|
||||||
};
|
};
|
||||||
@ -72,105 +86,89 @@ export const regionsReducers = {
|
|||||||
},
|
},
|
||||||
rgPositivePromptChanged: (state, action: PayloadAction<{ id: string; prompt: string | null }>) => {
|
rgPositivePromptChanged: (state, action: PayloadAction<{ id: string; prompt: string | null }>) => {
|
||||||
const { id, prompt } = action.payload;
|
const { id, prompt } = action.payload;
|
||||||
const rg = selectRG(state, id);
|
const entity = selectRegionalGuidanceEntity(state, id);
|
||||||
if (!rg) {
|
if (!entity) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
rg.positivePrompt = prompt;
|
entity.positivePrompt = prompt;
|
||||||
},
|
},
|
||||||
rgNegativePromptChanged: (state, action: PayloadAction<{ id: string; prompt: string | null }>) => {
|
rgNegativePromptChanged: (state, action: PayloadAction<{ id: string; prompt: string | null }>) => {
|
||||||
const { id, prompt } = action.payload;
|
const { id, prompt } = action.payload;
|
||||||
const rg = selectRG(state, id);
|
const entity = selectRegionalGuidanceEntity(state, id);
|
||||||
if (!rg) {
|
if (!entity) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
rg.negativePrompt = prompt;
|
entity.negativePrompt = prompt;
|
||||||
},
|
},
|
||||||
rgFillChanged: (state, action: PayloadAction<{ id: string; fill: RgbColor }>) => {
|
rgFillChanged: (state, action: PayloadAction<{ id: string; fill: RgbColor }>) => {
|
||||||
const { id, fill } = action.payload;
|
const { id, fill } = action.payload;
|
||||||
const rg = selectRG(state, id);
|
const entity = selectRegionalGuidanceEntity(state, id);
|
||||||
if (!rg) {
|
if (!entity) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
rg.fill = fill;
|
entity.fill = fill;
|
||||||
},
|
},
|
||||||
rgAutoNegativeChanged: (state, action: PayloadAction<{ id: string; autoNegative: ParameterAutoNegative }>) => {
|
rgAutoNegativeChanged: (state, action: PayloadAction<{ id: string; autoNegative: ParameterAutoNegative }>) => {
|
||||||
const { id, autoNegative } = action.payload;
|
const { id, autoNegative } = action.payload;
|
||||||
const rg = selectRG(state, id);
|
const rg = selectRegionalGuidanceEntity(state, id);
|
||||||
if (!rg) {
|
if (!rg) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
rg.autoNegative = autoNegative;
|
rg.autoNegative = autoNegative;
|
||||||
},
|
},
|
||||||
rgIPAdapterAdded: (state, action: PayloadAction<{ id: string; ipAdapter: CanvasIPAdapterState }>) => {
|
rgIPAdapterAdded: (state, action: PayloadAction<{ id: string; ipAdapter: RegionalGuidanceIPAdapterConfig }>) => {
|
||||||
const { id, ipAdapter } = action.payload;
|
const { id, ipAdapter } = action.payload;
|
||||||
const rg = selectRG(state, id);
|
const entity = selectRegionalGuidanceEntity(state, id);
|
||||||
if (!rg) {
|
if (!entity) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
rg.ipAdapters.push(ipAdapter);
|
entity.ipAdapters.push(ipAdapter);
|
||||||
},
|
},
|
||||||
rgIPAdapterDeleted: (state, action: PayloadAction<{ id: string; ipAdapterId: string }>) => {
|
rgIPAdapterDeleted: (state, action: PayloadAction<{ id: string; ipAdapterId: string }>) => {
|
||||||
const { id, ipAdapterId } = action.payload;
|
const { id, ipAdapterId } = action.payload;
|
||||||
const rg = selectRG(state, id);
|
const entity = selectRegionalGuidanceEntity(state, id);
|
||||||
if (!rg) {
|
if (!entity) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
rg.ipAdapters = rg.ipAdapters.filter((ipAdapter) => ipAdapter.id !== ipAdapterId);
|
entity.ipAdapters = entity.ipAdapters.filter((ipAdapter) => ipAdapter.id !== ipAdapterId);
|
||||||
},
|
},
|
||||||
rgIPAdapterImageChanged: (
|
rgIPAdapterImageChanged: (
|
||||||
state,
|
state,
|
||||||
action: PayloadAction<{ id: string; ipAdapterId: string; imageDTO: ImageDTO | null; objectId: string }>
|
action: PayloadAction<{ id: string; ipAdapterId: string; imageDTO: ImageDTO | null }>
|
||||||
) => {
|
) => {
|
||||||
const { id, ipAdapterId, imageDTO } = action.payload;
|
const { id, ipAdapterId, imageDTO } = action.payload;
|
||||||
const rg = selectRG(state, id);
|
const ipAdapter = selectRegionalGuidanceIPAdapter(state, id, ipAdapterId);
|
||||||
if (!rg) {
|
if (!ipAdapter) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
const ipa = rg.ipAdapters.find((ipa) => ipa.id === ipAdapterId);
|
ipAdapter.image = imageDTO ? imageDTOToImageWithDims(imageDTO) : null;
|
||||||
if (!ipa) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
ipa.imageObject = imageDTO ? imageDTOToImageObject(imageDTO) : null;
|
|
||||||
},
|
},
|
||||||
rgIPAdapterWeightChanged: (state, action: PayloadAction<{ id: string; ipAdapterId: string; weight: number }>) => {
|
rgIPAdapterWeightChanged: (state, action: PayloadAction<{ id: string; ipAdapterId: string; weight: number }>) => {
|
||||||
const { id, ipAdapterId, weight } = action.payload;
|
const { id, ipAdapterId, weight } = action.payload;
|
||||||
const rg = selectRG(state, id);
|
const ipAdapter = selectRegionalGuidanceIPAdapter(state, id, ipAdapterId);
|
||||||
if (!rg) {
|
if (!ipAdapter) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
const ipa = rg.ipAdapters.find((ipa) => ipa.id === ipAdapterId);
|
ipAdapter.weight = weight;
|
||||||
if (!ipa) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
ipa.weight = weight;
|
|
||||||
},
|
},
|
||||||
rgIPAdapterBeginEndStepPctChanged: (
|
rgIPAdapterBeginEndStepPctChanged: (
|
||||||
state,
|
state,
|
||||||
action: PayloadAction<{ id: string; ipAdapterId: string; beginEndStepPct: [number, number] }>
|
action: PayloadAction<{ id: string; ipAdapterId: string; beginEndStepPct: [number, number] }>
|
||||||
) => {
|
) => {
|
||||||
const { id, ipAdapterId, beginEndStepPct } = action.payload;
|
const { id, ipAdapterId, beginEndStepPct } = action.payload;
|
||||||
const rg = selectRG(state, id);
|
const ipAdapter = selectRegionalGuidanceIPAdapter(state, id, ipAdapterId);
|
||||||
if (!rg) {
|
if (!ipAdapter) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
const ipa = rg.ipAdapters.find((ipa) => ipa.id === ipAdapterId);
|
ipAdapter.beginEndStepPct = beginEndStepPct;
|
||||||
if (!ipa) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
ipa.beginEndStepPct = beginEndStepPct;
|
|
||||||
},
|
},
|
||||||
rgIPAdapterMethodChanged: (state, action: PayloadAction<{ id: string; ipAdapterId: string; method: IPMethodV2 }>) => {
|
rgIPAdapterMethodChanged: (state, action: PayloadAction<{ id: string; ipAdapterId: string; method: IPMethodV2 }>) => {
|
||||||
const { id, ipAdapterId, method } = action.payload;
|
const { id, ipAdapterId, method } = action.payload;
|
||||||
const rg = selectRG(state, id);
|
const ipAdapter = selectRegionalGuidanceIPAdapter(state, id, ipAdapterId);
|
||||||
if (!rg) {
|
if (!ipAdapter) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
const ipa = rg.ipAdapters.find((ipa) => ipa.id === ipAdapterId);
|
ipAdapter.method = method;
|
||||||
if (!ipa) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
ipa.method = method;
|
|
||||||
},
|
},
|
||||||
rgIPAdapterModelChanged: (
|
rgIPAdapterModelChanged: (
|
||||||
state,
|
state,
|
||||||
@ -181,33 +179,21 @@ export const regionsReducers = {
|
|||||||
}>
|
}>
|
||||||
) => {
|
) => {
|
||||||
const { id, ipAdapterId, modelConfig } = action.payload;
|
const { id, ipAdapterId, modelConfig } = action.payload;
|
||||||
const rg = selectRG(state, id);
|
const ipAdapter = selectRegionalGuidanceIPAdapter(state, id, ipAdapterId);
|
||||||
if (!rg) {
|
if (!ipAdapter) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
const ipa = rg.ipAdapters.find((ipa) => ipa.id === ipAdapterId);
|
ipAdapter.model = modelConfig ? zModelIdentifierField.parse(modelConfig) : null;
|
||||||
if (!ipa) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
if (modelConfig) {
|
|
||||||
ipa.model = zModelIdentifierField.parse(modelConfig);
|
|
||||||
} else {
|
|
||||||
ipa.model = null;
|
|
||||||
}
|
|
||||||
},
|
},
|
||||||
rgIPAdapterCLIPVisionModelChanged: (
|
rgIPAdapterCLIPVisionModelChanged: (
|
||||||
state,
|
state,
|
||||||
action: PayloadAction<{ id: string; ipAdapterId: string; clipVisionModel: CLIPVisionModelV2 }>
|
action: PayloadAction<{ id: string; ipAdapterId: string; clipVisionModel: CLIPVisionModelV2 }>
|
||||||
) => {
|
) => {
|
||||||
const { id, ipAdapterId, clipVisionModel } = action.payload;
|
const { id, ipAdapterId, clipVisionModel } = action.payload;
|
||||||
const rg = selectRG(state, id);
|
const ipAdapter = selectRegionalGuidanceIPAdapter(state, id, ipAdapterId);
|
||||||
if (!rg) {
|
if (!ipAdapter) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
const ipa = rg.ipAdapters.find((ipa) => ipa.id === ipAdapterId);
|
ipAdapter.clipVisionModel = clipVisionModel;
|
||||||
if (!ipa) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
ipa.clipVisionModel = clipVisionModel;
|
|
||||||
},
|
},
|
||||||
} satisfies SliceCaseReducers<CanvasV2State>;
|
} satisfies SliceCaseReducers<CanvasV2State>;
|
||||||
|
@ -581,22 +581,24 @@ export function isCanvasBrushLineState(obj: CanvasObjectState): obj is CanvasBru
|
|||||||
return obj.type === 'brush_line';
|
return obj.type === 'brush_line';
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const zIPAdapterConfig = z.object({
|
||||||
|
image: zImageWithDims.nullable(),
|
||||||
|
model: zModelIdentifierField.nullable(),
|
||||||
|
weight: z.number().gte(-1).lte(2),
|
||||||
|
beginEndStepPct: zBeginEndStepPct,
|
||||||
|
method: zIPMethodV2,
|
||||||
|
clipVisionModel: zCLIPVisionModelV2,
|
||||||
|
});
|
||||||
|
export type IPAdapterConfig = z.infer<typeof zIPAdapterConfig>;
|
||||||
|
|
||||||
export const zCanvasIPAdapterState = z.object({
|
export const zCanvasIPAdapterState = z.object({
|
||||||
id: zId,
|
id: zId,
|
||||||
|
name: z.string().nullable(),
|
||||||
type: z.literal('ip_adapter'),
|
type: z.literal('ip_adapter'),
|
||||||
isEnabled: z.boolean(),
|
isEnabled: z.boolean(),
|
||||||
weight: z.number().gte(-1).lte(2),
|
ipAdapter: zIPAdapterConfig,
|
||||||
method: zIPMethodV2,
|
|
||||||
imageObject: zCanvasImageState.nullable(),
|
|
||||||
model: zModelIdentifierField.nullable(),
|
|
||||||
clipVisionModel: zCLIPVisionModelV2,
|
|
||||||
beginEndStepPct: zBeginEndStepPct,
|
|
||||||
});
|
});
|
||||||
export type CanvasIPAdapterState = z.infer<typeof zCanvasIPAdapterState>;
|
export type CanvasIPAdapterState = z.infer<typeof zCanvasIPAdapterState>;
|
||||||
export type IPAdapterConfig = Pick<
|
|
||||||
CanvasIPAdapterState,
|
|
||||||
'weight' | 'imageObject' | 'beginEndStepPct' | 'model' | 'clipVisionModel' | 'method'
|
|
||||||
>;
|
|
||||||
|
|
||||||
const zMaskObject = z
|
const zMaskObject = z
|
||||||
.discriminatedUnion('type', [
|
.discriminatedUnion('type', [
|
||||||
@ -645,6 +647,17 @@ const zImageCache = z.object({
|
|||||||
});
|
});
|
||||||
export type ImageCache = z.infer<typeof zImageCache>;
|
export type ImageCache = z.infer<typeof zImageCache>;
|
||||||
|
|
||||||
|
const zRegionalGuidanceIPAdapterConfig = z.object({
|
||||||
|
id: zId,
|
||||||
|
image: zImageWithDims.nullable(),
|
||||||
|
model: zModelIdentifierField.nullable(),
|
||||||
|
weight: z.number().gte(-1).lte(2),
|
||||||
|
beginEndStepPct: zBeginEndStepPct,
|
||||||
|
method: zIPMethodV2,
|
||||||
|
clipVisionModel: zCLIPVisionModelV2,
|
||||||
|
});
|
||||||
|
export type RegionalGuidanceIPAdapterConfig = z.infer<typeof zRegionalGuidanceIPAdapterConfig>;
|
||||||
|
|
||||||
export const zCanvasRegionalGuidanceState = z.object({
|
export const zCanvasRegionalGuidanceState = z.object({
|
||||||
id: zId,
|
id: zId,
|
||||||
name: z.string().nullable(),
|
name: z.string().nullable(),
|
||||||
@ -655,7 +668,7 @@ export const zCanvasRegionalGuidanceState = z.object({
|
|||||||
fill: zRgbColor,
|
fill: zRgbColor,
|
||||||
positivePrompt: zParameterPositivePrompt.nullable(),
|
positivePrompt: zParameterPositivePrompt.nullable(),
|
||||||
negativePrompt: zParameterNegativePrompt.nullable(),
|
negativePrompt: zParameterNegativePrompt.nullable(),
|
||||||
ipAdapters: z.array(zCanvasIPAdapterState),
|
ipAdapters: z.array(zRegionalGuidanceIPAdapterConfig),
|
||||||
autoNegative: zAutoNegative,
|
autoNegative: zAutoNegative,
|
||||||
rasterizationCache: z.array(zImageCache),
|
rasterizationCache: z.array(zImageCache),
|
||||||
});
|
});
|
||||||
@ -763,7 +776,7 @@ export const initialT2IAdapterV2: T2IAdapterConfig = {
|
|||||||
};
|
};
|
||||||
|
|
||||||
export const initialIPAdapterV2: IPAdapterConfig = {
|
export const initialIPAdapterV2: IPAdapterConfig = {
|
||||||
imageObject: null,
|
image: null,
|
||||||
model: null,
|
model: null,
|
||||||
beginEndStepPct: [0, 1],
|
beginEndStepPct: [0, 1],
|
||||||
method: 'full',
|
method: 'full',
|
||||||
@ -943,6 +956,7 @@ export type EntityRasterizedPayload = {
|
|||||||
entityIdentifier: CanvasEntityIdentifier;
|
entityIdentifier: CanvasEntityIdentifier;
|
||||||
imageObject: CanvasImageState;
|
imageObject: CanvasImageState;
|
||||||
rect: Rect;
|
rect: Rect;
|
||||||
|
replaceObjects: boolean;
|
||||||
};
|
};
|
||||||
export type ImageObjectAddedArg = { id: string; imageDTO: ImageDTO; position?: Coordinate };
|
export type ImageObjectAddedArg = { id: string; imageDTO: ImageDTO; position?: Coordinate };
|
||||||
|
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
import type { CanvasIPAdapterState } from 'features/controlLayers/store/types';
|
import type { CanvasIPAdapterState, IPAdapterConfig } from 'features/controlLayers/store/types';
|
||||||
import { IP_ADAPTER_COLLECT } from 'features/nodes/util/graph/constants';
|
import { IP_ADAPTER_COLLECT } from 'features/nodes/util/graph/constants';
|
||||||
import type { Graph } from 'features/nodes/util/graph/generation/Graph';
|
import type { Graph } from 'features/nodes/util/graph/generation/Graph';
|
||||||
import type { BaseModelType, Invocation } from 'services/api/types';
|
import type { BaseModelType, Invocation } from 'services/api/types';
|
||||||
@ -10,7 +10,7 @@ export const addIPAdapters = (
|
|||||||
denoise: Invocation<'denoise_latents'>,
|
denoise: Invocation<'denoise_latents'>,
|
||||||
base: BaseModelType
|
base: BaseModelType
|
||||||
): CanvasIPAdapterState[] => {
|
): CanvasIPAdapterState[] => {
|
||||||
const validIPAdapters = ipAdapters.filter((ipa) => isValidIPAdapter(ipa, base));
|
const validIPAdapters = ipAdapters.filter((entity) => isValidIPAdapter(entity.ipAdapter, base));
|
||||||
for (const ipa of validIPAdapters) {
|
for (const ipa of validIPAdapters) {
|
||||||
addIPAdapter(ipa, g, denoise);
|
addIPAdapter(ipa, g, denoise);
|
||||||
}
|
}
|
||||||
@ -33,13 +33,14 @@ export const addIPAdapterCollectorSafe = (g: Graph, denoise: Invocation<'denoise
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
const addIPAdapter = (ipa: CanvasIPAdapterState, g: Graph, denoise: Invocation<'denoise_latents'>) => {
|
const addIPAdapter = (entity: CanvasIPAdapterState, g: Graph, denoise: Invocation<'denoise_latents'>) => {
|
||||||
const { id, weight, model, clipVisionModel, method, beginEndStepPct, imageObject } = ipa;
|
const { id, ipAdapter } = entity;
|
||||||
assert(imageObject, 'IP Adapter image is required');
|
const { weight, model, clipVisionModel, method, beginEndStepPct, image } = ipAdapter;
|
||||||
|
assert(image, 'IP Adapter image is required');
|
||||||
assert(model, 'IP Adapter model is required');
|
assert(model, 'IP Adapter model is required');
|
||||||
const ipAdapterCollect = addIPAdapterCollectorSafe(g, denoise);
|
const ipAdapterCollect = addIPAdapterCollectorSafe(g, denoise);
|
||||||
|
|
||||||
const ipAdapter = g.addNode({
|
const ipAdapterNode = g.addNode({
|
||||||
id: `ip_adapter_${id}`,
|
id: `ip_adapter_${id}`,
|
||||||
type: 'ip_adapter',
|
type: 'ip_adapter',
|
||||||
weight,
|
weight,
|
||||||
@ -49,16 +50,16 @@ const addIPAdapter = (ipa: CanvasIPAdapterState, g: Graph, denoise: Invocation<'
|
|||||||
begin_step_percent: beginEndStepPct[0],
|
begin_step_percent: beginEndStepPct[0],
|
||||||
end_step_percent: beginEndStepPct[1],
|
end_step_percent: beginEndStepPct[1],
|
||||||
image: {
|
image: {
|
||||||
image_name: imageObject.image.image_name,
|
image_name: image.image_name,
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
g.addEdge(ipAdapter, 'ip_adapter', ipAdapterCollect, 'item');
|
g.addEdge(ipAdapterNode, 'ip_adapter', ipAdapterCollect, 'item');
|
||||||
};
|
};
|
||||||
|
|
||||||
export const isValidIPAdapter = (ipa: CanvasIPAdapterState, base: BaseModelType): boolean => {
|
export const isValidIPAdapter = (ipAdapter: IPAdapterConfig, base: BaseModelType): boolean => {
|
||||||
// Must be have a model that matches the current base and must have a control image
|
// Must be have a model that matches the current base and must have a control image
|
||||||
const hasModel = Boolean(ipa.model);
|
const hasModel = Boolean(ipAdapter.model);
|
||||||
const modelMatchesBase = ipa.model?.base === base;
|
const modelMatchesBase = ipAdapter.model?.base === base;
|
||||||
const hasImage = Boolean(ipa.imageObject);
|
const hasImage = Boolean(ipAdapter.image);
|
||||||
return hasModel && modelMatchesBase && hasImage;
|
return hasModel && modelMatchesBase && hasImage;
|
||||||
};
|
};
|
||||||
|
@ -1,10 +1,5 @@
|
|||||||
import type { CanvasRasterLayerState } from 'features/controlLayers/store/types';
|
import type { CanvasRasterLayerState } from 'features/controlLayers/store/types';
|
||||||
|
|
||||||
export const isValidLayerWithoutControlAdapter = (layer: CanvasRasterLayerState) => {
|
export const isValidLayer = (layer: CanvasRasterLayerState) => {
|
||||||
return (
|
return layer.isEnabled && layer.objects.length > 0;
|
||||||
layer.isEnabled &&
|
|
||||||
// Boolean(entity.bbox) && TODO(psyche): Re-enable this check when we have a way to calculate bbox for all layers
|
|
||||||
layer.objects.length > 0 &&
|
|
||||||
layer.controlAdapter === null
|
|
||||||
);
|
|
||||||
};
|
};
|
||||||
|
@ -1,6 +1,10 @@
|
|||||||
import { deepClone } from 'common/util/deepClone';
|
import { deepClone } from 'common/util/deepClone';
|
||||||
import type { CanvasManager } from 'features/controlLayers/konva/CanvasManager';
|
import type { CanvasManager } from 'features/controlLayers/konva/CanvasManager';
|
||||||
import type { CanvasIPAdapterState, CanvasRegionalGuidanceState, Rect } from 'features/controlLayers/store/types';
|
import type {
|
||||||
|
CanvasRegionalGuidanceState,
|
||||||
|
Rect,
|
||||||
|
RegionalGuidanceIPAdapterConfig,
|
||||||
|
} from 'features/controlLayers/store/types';
|
||||||
import {
|
import {
|
||||||
PROMPT_REGION_INVERT_TENSOR_MASK_PREFIX,
|
PROMPT_REGION_INVERT_TENSOR_MASK_PREFIX,
|
||||||
PROMPT_REGION_MASK_TO_TENSOR_PREFIX,
|
PROMPT_REGION_MASK_TO_TENSOR_PREFIX,
|
||||||
@ -174,13 +178,15 @@ export const addRegions = async (
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const validRGIPAdapters: CanvasIPAdapterState[] = region.ipAdapters.filter((ipa) => isValidIPAdapter(ipa, base));
|
const validRGIPAdapters: RegionalGuidanceIPAdapterConfig[] = region.ipAdapters.filter((ipAdapter) =>
|
||||||
|
isValidIPAdapter(ipAdapter, base)
|
||||||
|
);
|
||||||
|
|
||||||
for (const ipa of validRGIPAdapters) {
|
for (const ipa of validRGIPAdapters) {
|
||||||
const ipAdapterCollect = addIPAdapterCollectorSafe(g, denoise);
|
const ipAdapterCollect = addIPAdapterCollectorSafe(g, denoise);
|
||||||
const { id, weight, model, clipVisionModel, method, beginEndStepPct, imageObject } = ipa;
|
const { id, weight, model, clipVisionModel, method, beginEndStepPct, image } = ipa;
|
||||||
assert(model, 'IP Adapter model is required');
|
assert(model, 'IP Adapter model is required');
|
||||||
assert(imageObject, 'IP Adapter image is required');
|
assert(image, 'IP Adapter image is required');
|
||||||
|
|
||||||
const ipAdapter = g.addNode({
|
const ipAdapter = g.addNode({
|
||||||
id: `ip_adapter_${id}`,
|
id: `ip_adapter_${id}`,
|
||||||
@ -192,7 +198,7 @@ export const addRegions = async (
|
|||||||
begin_step_percent: beginEndStepPct[0],
|
begin_step_percent: beginEndStepPct[0],
|
||||||
end_step_percent: beginEndStepPct[1],
|
end_step_percent: beginEndStepPct[1],
|
||||||
image: {
|
image: {
|
||||||
image_name: imageObject.image.image_name,
|
image_name: image.image_name,
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -215,7 +215,7 @@ export const buildSD1Graph = async (state: RootState, manager: CanvasManager): P
|
|||||||
|
|
||||||
const _addedCAs = await addControlAdapters(
|
const _addedCAs = await addControlAdapters(
|
||||||
manager,
|
manager,
|
||||||
state.canvasV2.rasterLayers.entities,
|
state.canvasV2.controlLayers.entities,
|
||||||
g,
|
g,
|
||||||
state.canvasV2.bbox.rect,
|
state.canvasV2.bbox.rect,
|
||||||
denoise,
|
denoise,
|
||||||
|
@ -219,7 +219,7 @@ export const buildSDXLGraph = async (state: RootState, manager: CanvasManager):
|
|||||||
|
|
||||||
const _addedCAs = await addControlAdapters(
|
const _addedCAs = await addControlAdapters(
|
||||||
manager,
|
manager,
|
||||||
state.canvasV2.rasterLayers.entities,
|
state.canvasV2.controlLayers.entities,
|
||||||
g,
|
g,
|
||||||
state.canvasV2.bbox.rect,
|
state.canvasV2.bbox.rect,
|
||||||
denoise,
|
denoise,
|
||||||
|
Loading…
Reference in New Issue
Block a user