tidy(ui): "regional prompts" -> "control layers"

This commit is contained in:
psychedelicious 2024-04-30 13:28:03 +10:00 committed by Kent Keirsey
parent 8de56fd77c
commit 3441187c23
78 changed files with 336 additions and 340 deletions

View File

@ -1513,7 +1513,7 @@
"app": { "app": {
"storeNotInitialized": "Store is not initialized" "storeNotInitialized": "Store is not initialized"
}, },
"regionalPrompts": { "controlLayers": {
"deleteAll": "Delete All", "deleteAll": "Delete All",
"addLayer": "Add Layer", "addLayer": "Add Layer",
"moveToFront": "Move to Front", "moveToFront": "Move to Front",
@ -1521,8 +1521,7 @@
"moveForward": "Move Forward", "moveForward": "Move Forward",
"moveBackward": "Move Backward", "moveBackward": "Move Backward",
"brushSize": "Brush Size", "brushSize": "Brush Size",
"regionalControl": "Regional Control (ALPHA)", "controlLayers": "Control Layers (BETA)",
"enableRegionalPrompts": "Enable $t(regionalPrompts.regionalPrompts)",
"globalMaskOpacity": "Global Mask Opacity", "globalMaskOpacity": "Global Mask Opacity",
"autoNegative": "Auto Negative", "autoNegative": "Auto Negative",
"toggleVisibility": "Toggle Layer Visibility", "toggleVisibility": "Toggle Layer Visibility",
@ -1535,7 +1534,7 @@
"addNegativePrompt": "Add $t(common.negativePrompt)", "addNegativePrompt": "Add $t(common.negativePrompt)",
"addIPAdapter": "Add $t(common.ipAdapter)", "addIPAdapter": "Add $t(common.ipAdapter)",
"maskedGuidance": "Masked Guidance", "maskedGuidance": "Masked Guidance",
"maskedGuidanceLayer": "$t(regionalPrompts.maskedGuidance) $t(unifiedCanvas.layer)", "maskedGuidanceLayer": "$t(controlLayers.maskedGuidance) $t(unifiedCanvas.layer)",
"controlNetLayer": "$t(common.controlNet) $t(unifiedCanvas.layer)", "controlNetLayer": "$t(common.controlNet) $t(unifiedCanvas.layer)",
"ipAdapterLayer": "$t(common.ipAdapter) $t(unifiedCanvas.layer)", "ipAdapterLayer": "$t(common.ipAdapter) $t(unifiedCanvas.layer)",
"opacity": "Opacity" "opacity": "Opacity"

View File

@ -28,7 +28,7 @@ export type LoggerNamespace =
| 'session' | 'session'
| 'queue' | 'queue'
| 'dnd' | 'dnd'
| 'regionalPrompts'; | 'controlLayers';
export const logger = (namespace: LoggerNamespace) => $logger.get().child({ namespace }); export const logger = (namespace: LoggerNamespace) => $logger.get().child({ namespace });

View File

@ -6,7 +6,7 @@ import {
controlAdapterModelCleared, controlAdapterModelCleared,
selectControlAdapterAll, selectControlAdapterAll,
} from 'features/controlAdapters/store/controlAdaptersSlice'; } from 'features/controlAdapters/store/controlAdaptersSlice';
import { heightChanged, widthChanged } from 'features/controlLayers/store/regionalPromptsSlice'; import { heightChanged, widthChanged } from 'features/controlLayers/store/controlLayersSlice';
import { loraRemoved } from 'features/lora/store/loraSlice'; import { loraRemoved } from 'features/lora/store/loraSlice';
import { calculateNewSize } from 'features/parameters/components/ImageSize/calculateNewSize'; import { calculateNewSize } from 'features/parameters/components/ImageSize/calculateNewSize';
import { modelChanged, vaeSelected } from 'features/parameters/store/generationSlice'; import { modelChanged, vaeSelected } from 'features/parameters/store/generationSlice';
@ -72,15 +72,15 @@ const handleMainModels: ModelHandler = (models, state, dispatch, log) => {
const optimalDimension = getOptimalDimension(defaultModelInList); const optimalDimension = getOptimalDimension(defaultModelInList);
if ( if (
getIsSizeOptimal( getIsSizeOptimal(
state.regionalPrompts.present.size.width, state.controlLayers.present.size.width,
state.regionalPrompts.present.size.height, state.controlLayers.present.size.height,
optimalDimension optimalDimension
) )
) { ) {
return; return;
} }
const { width, height } = calculateNewSize( const { width, height } = calculateNewSize(
state.regionalPrompts.present.size.aspectRatio.value, state.controlLayers.present.size.aspectRatio.value,
optimalDimension * optimalDimension optimalDimension * optimalDimension
); );

View File

@ -1,6 +1,6 @@
import { isAnyOf } from '@reduxjs/toolkit'; import { isAnyOf } from '@reduxjs/toolkit';
import type { AppStartListening } from 'app/store/middleware/listenerMiddleware'; import type { AppStartListening } from 'app/store/middleware/listenerMiddleware';
import { positivePromptChanged } from 'features/controlLayers/store/regionalPromptsSlice'; import { positivePromptChanged } from 'features/controlLayers/store/controlLayersSlice';
import { import {
combinatorialToggled, combinatorialToggled,
isErrorChanged, isErrorChanged,
@ -28,7 +28,7 @@ export const addDynamicPromptsListener = (startAppListening: AppStartListening)
effect: async (action, { dispatch, getState, cancelActiveListeners, delay }) => { effect: async (action, { dispatch, getState, cancelActiveListeners, delay }) => {
cancelActiveListeners(); cancelActiveListeners();
const state = getState(); const state = getState();
const { positivePrompt } = state.regionalPrompts.present; const { positivePrompt } = state.controlLayers.present;
const { maxPrompts } = state.dynamicPrompts; const { maxPrompts } = state.dynamicPrompts;
if (state.config.disabledFeatures.includes('dynamicPrompting')) { if (state.config.disabledFeatures.includes('dynamicPrompting')) {

View File

@ -11,19 +11,19 @@ import {
maskedGuidanceLayerAdded, maskedGuidanceLayerAdded,
maskLayerIPAdapterAdded, maskLayerIPAdapterAdded,
maskLayerIPAdapterDeleted, maskLayerIPAdapterDeleted,
} from 'features/controlLayers/store/regionalPromptsSlice'; } from 'features/controlLayers/store/controlLayersSlice';
import type { Layer } from 'features/controlLayers/store/types'; import type { Layer } from 'features/controlLayers/store/types';
import { modelConfigsAdapterSelectors, modelsApi } from 'services/api/endpoints/models'; import { modelConfigsAdapterSelectors, modelsApi } from 'services/api/endpoints/models';
import { isControlNetModelConfig, isIPAdapterModelConfig } from 'services/api/types'; import { isControlNetModelConfig, isIPAdapterModelConfig } from 'services/api/types';
import { assert } from 'tsafe'; import { assert } from 'tsafe';
import { v4 as uuidv4 } from 'uuid'; import { v4 as uuidv4 } from 'uuid';
export const guidanceLayerAdded = createAction<Layer['type']>('regionalPrompts/guidanceLayerAdded'); export const guidanceLayerAdded = createAction<Layer['type']>('controlLayers/guidanceLayerAdded');
export const guidanceLayerDeleted = createAction<string>('regionalPrompts/guidanceLayerDeleted'); export const guidanceLayerDeleted = createAction<string>('controlLayers/guidanceLayerDeleted');
export const allLayersDeleted = createAction('regionalPrompts/allLayersDeleted'); export const allLayersDeleted = createAction('controlLayers/allLayersDeleted');
export const guidanceLayerIPAdapterAdded = createAction<string>('regionalPrompts/guidanceLayerIPAdapterAdded'); export const guidanceLayerIPAdapterAdded = createAction<string>('controlLayers/guidanceLayerIPAdapterAdded');
export const guidanceLayerIPAdapterDeleted = createAction<{ layerId: string; ipAdapterId: string }>( export const guidanceLayerIPAdapterDeleted = createAction<{ layerId: string; ipAdapterId: string }>(
'regionalPrompts/guidanceLayerIPAdapterDeleted' 'controlLayers/guidanceLayerIPAdapterDeleted'
); );
export const addRegionalControlToControlAdapterBridge = (startAppListening: AppStartListening) => { export const addRegionalControlToControlAdapterBridge = (startAppListening: AppStartListening) => {
@ -32,7 +32,7 @@ export const addRegionalControlToControlAdapterBridge = (startAppListening: AppS
effect: (action, { dispatch, getState }) => { effect: (action, { dispatch, getState }) => {
const type = action.payload; const type = action.payload;
const layerId = uuidv4(); const layerId = uuidv4();
if (type === 'masked_guidance_layer') { if (type === 'regional_guidance_layer') {
dispatch(maskedGuidanceLayerAdded({ layerId })); dispatch(maskedGuidanceLayerAdded({ layerId }));
return; return;
} }
@ -84,14 +84,14 @@ export const addRegionalControlToControlAdapterBridge = (startAppListening: AppS
effect: (action, { getState, dispatch }) => { effect: (action, { getState, dispatch }) => {
const layerId = action.payload; const layerId = action.payload;
const state = getState(); const state = getState();
const layer = state.regionalPrompts.present.layers.find((l) => l.id === layerId); const layer = state.controlLayers.present.layers.find((l) => l.id === layerId);
assert(layer, `Layer ${layerId} not found`); assert(layer, `Layer ${layerId} not found`);
if (layer.type === 'ip_adapter_layer') { if (layer.type === 'ip_adapter_layer') {
dispatch(controlAdapterRemoved({ id: layer.ipAdapterId })); dispatch(controlAdapterRemoved({ id: layer.ipAdapterId }));
} else if (layer.type === 'control_adapter_layer') { } else if (layer.type === 'control_adapter_layer') {
dispatch(controlAdapterRemoved({ id: layer.controlNetId })); dispatch(controlAdapterRemoved({ id: layer.controlNetId }));
} else if (layer.type === 'masked_guidance_layer') { } else if (layer.type === 'regional_guidance_layer') {
for (const ipAdapterId of layer.ipAdapterIds) { for (const ipAdapterId of layer.ipAdapterIds) {
dispatch(controlAdapterRemoved({ id: ipAdapterId })); dispatch(controlAdapterRemoved({ id: ipAdapterId }));
} }
@ -104,7 +104,7 @@ export const addRegionalControlToControlAdapterBridge = (startAppListening: AppS
actionCreator: allLayersDeleted, actionCreator: allLayersDeleted,
effect: (action, { dispatch, getOriginalState }) => { effect: (action, { dispatch, getOriginalState }) => {
const state = getOriginalState(); const state = getOriginalState();
for (const layer of state.regionalPrompts.present.layers) { for (const layer of state.controlLayers.present.layers) {
dispatch(guidanceLayerDeleted(layer.id)); dispatch(guidanceLayerDeleted(layer.id));
} }
}, },

View File

@ -1,5 +1,5 @@
import type { AppStartListening } from 'app/store/middleware/listenerMiddleware'; import type { AppStartListening } from 'app/store/middleware/listenerMiddleware';
import { heightChanged, widthChanged } from 'features/controlLayers/store/regionalPromptsSlice'; import { heightChanged, widthChanged } from 'features/controlLayers/store/controlLayersSlice';
import { setDefaultSettings } from 'features/parameters/store/actions'; import { setDefaultSettings } from 'features/parameters/store/actions';
import { import {
setCfgRescaleMultiplier, setCfgRescaleMultiplier,

View File

@ -11,10 +11,10 @@ import {
controlAdaptersSlice, controlAdaptersSlice,
} from 'features/controlAdapters/store/controlAdaptersSlice'; } from 'features/controlAdapters/store/controlAdaptersSlice';
import { import {
regionalPromptsPersistConfig, controlLayersPersistConfig,
regionalPromptsSlice, controlLayersSlice,
regionalPromptsUndoableConfig, controlLayersUndoableConfig,
} from 'features/controlLayers/store/regionalPromptsSlice'; } from 'features/controlLayers/store/controlLayersSlice';
import { deleteImageModalSlice } from 'features/deleteImageModal/store/slice'; import { deleteImageModalSlice } from 'features/deleteImageModal/store/slice';
import { dynamicPromptsPersistConfig, dynamicPromptsSlice } from 'features/dynamicPrompts/store/dynamicPromptsSlice'; import { dynamicPromptsPersistConfig, dynamicPromptsSlice } from 'features/dynamicPrompts/store/dynamicPromptsSlice';
import { galleryPersistConfig, gallerySlice } from 'features/gallery/store/gallerySlice'; import { galleryPersistConfig, gallerySlice } from 'features/gallery/store/gallerySlice';
@ -65,7 +65,7 @@ const allReducers = {
[queueSlice.name]: queueSlice.reducer, [queueSlice.name]: queueSlice.reducer,
[workflowSlice.name]: workflowSlice.reducer, [workflowSlice.name]: workflowSlice.reducer,
[hrfSlice.name]: hrfSlice.reducer, [hrfSlice.name]: hrfSlice.reducer,
[regionalPromptsSlice.name]: undoable(regionalPromptsSlice.reducer, regionalPromptsUndoableConfig), [controlLayersSlice.name]: undoable(controlLayersSlice.reducer, controlLayersUndoableConfig),
[api.reducerPath]: api.reducer, [api.reducerPath]: api.reducer,
}; };
@ -110,7 +110,7 @@ const persistConfigs: { [key in keyof typeof allReducers]?: PersistConfig } = {
[loraPersistConfig.name]: loraPersistConfig, [loraPersistConfig.name]: loraPersistConfig,
[modelManagerV2PersistConfig.name]: modelManagerV2PersistConfig, [modelManagerV2PersistConfig.name]: modelManagerV2PersistConfig,
[hrfPersistConfig.name]: hrfPersistConfig, [hrfPersistConfig.name]: hrfPersistConfig,
[regionalPromptsPersistConfig.name]: regionalPromptsPersistConfig, [controlLayersPersistConfig.name]: controlLayersPersistConfig,
}; };
const unserialize: UnserializeFunction = (data, key) => { const unserialize: UnserializeFunction = (data, key) => {

View File

@ -5,7 +5,7 @@ import {
selectControlAdaptersSlice, selectControlAdaptersSlice,
} from 'features/controlAdapters/store/controlAdaptersSlice'; } from 'features/controlAdapters/store/controlAdaptersSlice';
import { isControlNetOrT2IAdapter } from 'features/controlAdapters/store/types'; import { isControlNetOrT2IAdapter } from 'features/controlAdapters/store/types';
import { selectRegionalPromptsSlice } from 'features/controlLayers/store/regionalPromptsSlice'; import { selectControlLayersSlice } from 'features/controlLayers/store/controlLayersSlice';
import { selectDynamicPromptsSlice } from 'features/dynamicPrompts/store/dynamicPromptsSlice'; import { selectDynamicPromptsSlice } from 'features/dynamicPrompts/store/dynamicPromptsSlice';
import { getShouldProcessPrompt } from 'features/dynamicPrompts/util/getShouldProcessPrompt'; import { getShouldProcessPrompt } from 'features/dynamicPrompts/util/getShouldProcessPrompt';
import { selectNodesSlice } from 'features/nodes/store/nodesSlice'; import { selectNodesSlice } from 'features/nodes/store/nodesSlice';
@ -24,12 +24,12 @@ const selector = createMemoizedSelector(
selectSystemSlice, selectSystemSlice,
selectNodesSlice, selectNodesSlice,
selectDynamicPromptsSlice, selectDynamicPromptsSlice,
selectRegionalPromptsSlice, selectControlLayersSlice,
activeTabNameSelector, activeTabNameSelector,
], ],
(controlAdapters, generation, system, nodes, dynamicPrompts, regionalPrompts, activeTabName) => { (controlAdapters, generation, system, nodes, dynamicPrompts, controlLayers, activeTabName) => {
const { initialImage, model } = generation; const { initialImage, model } = generation;
const { positivePrompt } = regionalPrompts.present; const { positivePrompt } = controlLayers.present;
const { isConnected } = system; const { isConnected } = system;
@ -101,10 +101,10 @@ const selector = createMemoizedSelector(
if (activeTabName === 'txt2img') { if (activeTabName === 'txt2img') {
// Special handling for control layers on txt2img // Special handling for control layers on txt2img
const enabledControlLayersAdapterIds = regionalPrompts.present.layers const enabledControlLayersAdapterIds = controlLayers.present.layers
.filter((l) => l.isEnabled) .filter((l) => l.isEnabled)
.flatMap((layer) => { .flatMap((layer) => {
if (layer.type === 'masked_guidance_layer') { if (layer.type === 'regional_guidance_layer') {
return layer.ipAdapterIds; return layer.ipAdapterIds;
} }
if (layer.type === 'control_adapter_layer') { if (layer.type === 'control_adapter_layer') {
@ -117,8 +117,8 @@ const selector = createMemoizedSelector(
enabledControlAdapters = enabledControlAdapters.filter((ca) => enabledControlLayersAdapterIds.includes(ca.id)); enabledControlAdapters = enabledControlAdapters.filter((ca) => enabledControlLayersAdapterIds.includes(ca.id));
} else { } else {
const allControlLayerAdapterIds = regionalPrompts.present.layers.flatMap((layer) => { const allControlLayerAdapterIds = controlLayers.present.layers.flatMap((layer) => {
if (layer.type === 'masked_guidance_layer') { if (layer.type === 'regional_guidance_layer') {
return layer.ipAdapterIds; return layer.ipAdapterIds;
} }
if (layer.type === 'control_adapter_layer') { if (layer.type === 'control_adapter_layer') {

View File

@ -13,7 +13,7 @@ import {
controlAdapterImageChanged, controlAdapterImageChanged,
selectControlAdaptersSlice, selectControlAdaptersSlice,
} from 'features/controlAdapters/store/controlAdaptersSlice'; } from 'features/controlAdapters/store/controlAdaptersSlice';
import { heightChanged, widthChanged } from 'features/controlLayers/store/regionalPromptsSlice'; import { heightChanged, widthChanged } from 'features/controlLayers/store/controlLayersSlice';
import type { TypesafeDraggableData, TypesafeDroppableData } from 'features/dnd/types'; import type { TypesafeDraggableData, TypesafeDroppableData } from 'features/dnd/types';
import { calculateNewSize } from 'features/parameters/components/ImageSize/calculateNewSize'; import { calculateNewSize } from 'features/parameters/components/ImageSize/calculateNewSize';
import { selectOptimalDimension } from 'features/parameters/store/generationSlice'; import { selectOptimalDimension } from 'features/parameters/store/generationSlice';

View File

@ -9,7 +9,7 @@ export const AddLayerButton = memo(() => {
const { t } = useTranslation(); const { t } = useTranslation();
const dispatch = useAppDispatch(); const dispatch = useAppDispatch();
const addMaskedGuidanceLayer = useCallback(() => { const addMaskedGuidanceLayer = useCallback(() => {
dispatch(guidanceLayerAdded('masked_guidance_layer')); dispatch(guidanceLayerAdded('regional_guidance_layer'));
}, [dispatch]); }, [dispatch]);
const addControlNetLayer = useCallback(() => { const addControlNetLayer = useCallback(() => {
dispatch(guidanceLayerAdded('control_adapter_layer')); dispatch(guidanceLayerAdded('control_adapter_layer'));
@ -21,12 +21,12 @@ export const AddLayerButton = memo(() => {
return ( return (
<Menu> <Menu>
<MenuButton as={Button} leftIcon={<PiPlusBold />} variant="ghost"> <MenuButton as={Button} leftIcon={<PiPlusBold />} variant="ghost">
{t('regionalPrompts.addLayer')} {t('controlLayers.addLayer')}
</MenuButton> </MenuButton>
<MenuList> <MenuList>
<MenuItem onClick={addMaskedGuidanceLayer}> {t('regionalPrompts.maskedGuidanceLayer')}</MenuItem> <MenuItem onClick={addMaskedGuidanceLayer}> {t('controlLayers.maskedGuidanceLayer')}</MenuItem>
<MenuItem onClick={addControlNetLayer}> {t('regionalPrompts.controlNetLayer')}</MenuItem> <MenuItem onClick={addControlNetLayer}> {t('controlLayers.controlNetLayer')}</MenuItem>
<MenuItem onClick={addIPAdapterLayer}> {t('regionalPrompts.ipAdapterLayer')}</MenuItem> <MenuItem onClick={addIPAdapterLayer}> {t('controlLayers.ipAdapterLayer')}</MenuItem>
</MenuList> </MenuList>
</Menu> </Menu>
); );

View File

@ -6,8 +6,8 @@ import {
isMaskedGuidanceLayer, isMaskedGuidanceLayer,
maskLayerNegativePromptChanged, maskLayerNegativePromptChanged,
maskLayerPositivePromptChanged, maskLayerPositivePromptChanged,
selectRegionalPromptsSlice, selectControlLayersSlice,
} from 'features/controlLayers/store/regionalPromptsSlice'; } from 'features/controlLayers/store/controlLayersSlice';
import { useCallback, useMemo } from 'react'; import { useCallback, useMemo } from 'react';
import { useTranslation } from 'react-i18next'; import { useTranslation } from 'react-i18next';
import { PiPlusBold } from 'react-icons/pi'; import { PiPlusBold } from 'react-icons/pi';
@ -21,8 +21,8 @@ export const AddPromptButtons = ({ layerId }: AddPromptButtonProps) => {
const dispatch = useAppDispatch(); const dispatch = useAppDispatch();
const selectValidActions = useMemo( const selectValidActions = useMemo(
() => () =>
createMemoizedSelector(selectRegionalPromptsSlice, (regionalPrompts) => { createMemoizedSelector(selectControlLayersSlice, (controlLayers) => {
const layer = regionalPrompts.present.layers.find((l) => l.id === layerId); const layer = controlLayers.present.layers.find((l) => l.id === layerId);
assert(isMaskedGuidanceLayer(layer), `Layer ${layerId} not found or not an RP layer`); assert(isMaskedGuidanceLayer(layer), `Layer ${layerId} not found or not an RP layer`);
return { return {
canAddPositivePrompt: layer.positivePrompt === null, canAddPositivePrompt: layer.positivePrompt === null,

View File

@ -10,7 +10,7 @@ import {
PopoverTrigger, PopoverTrigger,
} from '@invoke-ai/ui-library'; } from '@invoke-ai/ui-library';
import { useAppDispatch, useAppSelector } from 'app/store/storeHooks'; import { useAppDispatch, useAppSelector } from 'app/store/storeHooks';
import { brushSizeChanged, initialRegionalPromptsState } from 'features/controlLayers/store/regionalPromptsSlice'; import { brushSizeChanged, initialControlLayersState } from 'features/controlLayers/store/controlLayersSlice';
import { memo, useCallback } from 'react'; import { memo, useCallback } from 'react';
import { useTranslation } from 'react-i18next'; import { useTranslation } from 'react-i18next';
@ -20,7 +20,7 @@ const formatPx = (v: number | string) => `${v} px`;
export const BrushSize = memo(() => { export const BrushSize = memo(() => {
const dispatch = useAppDispatch(); const dispatch = useAppDispatch();
const { t } = useTranslation(); const { t } = useTranslation();
const brushSize = useAppSelector((s) => s.regionalPrompts.present.brushSize); const brushSize = useAppSelector((s) => s.controlLayers.present.brushSize);
const onChange = useCallback( const onChange = useCallback(
(v: number) => { (v: number) => {
dispatch(brushSizeChanged(Math.round(v))); dispatch(brushSizeChanged(Math.round(v)));
@ -29,13 +29,13 @@ export const BrushSize = memo(() => {
); );
return ( return (
<FormControl w="min-content"> <FormControl w="min-content">
<FormLabel m={0}>{t('regionalPrompts.brushSize')}</FormLabel> <FormLabel m={0}>{t('controlLayers.brushSize')}</FormLabel>
<Popover isLazy> <Popover isLazy>
<PopoverTrigger> <PopoverTrigger>
<CompositeNumberInput <CompositeNumberInput
min={1} min={1}
max={600} max={600}
defaultValue={initialRegionalPromptsState.brushSize} defaultValue={initialControlLayersState.brushSize}
value={brushSize} value={brushSize}
onChange={onChange} onChange={onChange}
w={24} w={24}
@ -48,7 +48,7 @@ export const BrushSize = memo(() => {
<CompositeSlider <CompositeSlider
min={1} min={1}
max={300} max={300}
defaultValue={initialRegionalPromptsState.brushSize} defaultValue={initialControlLayersState.brushSize}
value={brushSize} value={brushSize}
onChange={onChange} onChange={onChange}
marks={marks} marks={marks}

View File

@ -13,7 +13,7 @@ import {
} from '@invoke-ai/ui-library'; } from '@invoke-ai/ui-library';
import { useAppDispatch } from 'app/store/storeHooks'; import { useAppDispatch } from 'app/store/storeHooks';
import { useLayerOpacity } from 'features/controlLayers/hooks/layerStateHooks'; import { useLayerOpacity } from 'features/controlLayers/hooks/layerStateHooks';
import { layerOpacityChanged } from 'features/controlLayers/store/regionalPromptsSlice'; import { layerOpacityChanged } from 'features/controlLayers/store/controlLayersSlice';
import { memo, useCallback } from 'react'; import { memo, useCallback } from 'react';
import { useTranslation } from 'react-i18next'; import { useTranslation } from 'react-i18next';
import { PiDropHalfFill } from 'react-icons/pi'; import { PiDropHalfFill } from 'react-icons/pi';
@ -39,7 +39,7 @@ const CALayerOpacity = ({ layerId }: Props) => {
<Popover isLazy> <Popover isLazy>
<PopoverTrigger> <PopoverTrigger>
<IconButton <IconButton
aria-label={t('regionalPrompts.opacity')} aria-label={t('controlLayers.opacity')}
size="sm" size="sm"
icon={<PiDropHalfFill size={16} />} icon={<PiDropHalfFill size={16} />}
variant="ghost" variant="ghost"
@ -50,7 +50,7 @@ const CALayerOpacity = ({ layerId }: Props) => {
<PopoverBody> <PopoverBody>
<Flex direction="column" gap={2}> <Flex direction="column" gap={2}>
<FormControl orientation="horizontal" minW={96}> <FormControl orientation="horizontal" minW={96}>
<FormLabel m={0}>{t('regionalPrompts.opacity')}</FormLabel> <FormLabel m={0}>{t('controlLayers.opacity')}</FormLabel>
<CompositeSlider <CompositeSlider
min={0} min={0}
max={100} max={100}

View File

@ -10,8 +10,8 @@ import { RPLayerVisibilityToggle } from 'features/controlLayers/components/RPLay
import { import {
isControlAdapterLayer, isControlAdapterLayer,
layerSelected, layerSelected,
selectRegionalPromptsSlice, selectControlLayersSlice,
} from 'features/controlLayers/store/regionalPromptsSlice'; } from 'features/controlLayers/store/controlLayersSlice';
import { memo, useCallback, useMemo } from 'react'; import { memo, useCallback, useMemo } from 'react';
import { assert } from 'tsafe'; import { assert } from 'tsafe';
@ -23,12 +23,12 @@ export const ControlAdapterLayerListItem = memo(({ layerId }: Props) => {
const dispatch = useAppDispatch(); const dispatch = useAppDispatch();
const selector = useMemo( const selector = useMemo(
() => () =>
createMemoizedSelector(selectRegionalPromptsSlice, (regionalPrompts) => { createMemoizedSelector(selectControlLayersSlice, (controlLayers) => {
const layer = regionalPrompts.present.layers.find((l) => l.id === layerId); const layer = controlLayers.present.layers.find((l) => l.id === layerId);
assert(isControlAdapterLayer(layer), `Layer ${layerId} not found or not a ControlNet layer`); assert(isControlAdapterLayer(layer), `Layer ${layerId} not found or not a ControlNet layer`);
return { return {
controlNetId: layer.controlNetId, controlNetId: layer.controlNetId,
isSelected: layerId === regionalPrompts.present.selectedLayerId, isSelected: layerId === controlLayers.present.selectedLayerId,
}; };
}), }),
[layerId] [layerId]

View File

@ -0,0 +1,24 @@
import { Flex } from '@invoke-ai/ui-library';
import type { Meta, StoryObj } from '@storybook/react';
import { ControlLayersEditor } from 'features/controlLayers/components/ControlLayersEditor';
const meta: Meta<typeof ControlLayersEditor> = {
title: 'Feature/ControlLayers',
tags: ['autodocs'],
component: ControlLayersEditor,
};
export default meta;
type Story = StoryObj<typeof ControlLayersEditor>;
const Component = () => {
return (
<Flex w={1500} h={1500}>
<ControlLayersEditor />
</Flex>
);
};
export const Default: Story = {
render: Component,
};

View File

@ -1,10 +1,10 @@
/* eslint-disable i18next/no-literal-string */ /* eslint-disable i18next/no-literal-string */
import { Flex } from '@invoke-ai/ui-library'; import { Flex } from '@invoke-ai/ui-library';
import { RegionalPromptsToolbar } from 'features/controlLayers/components/RegionalPromptsToolbar'; import { ControlLayersToolbar } from 'features/controlLayers/components/ControlLayersToolbar';
import { StageComponent } from 'features/controlLayers/components/StageComponent'; import { StageComponent } from 'features/controlLayers/components/StageComponent';
import { memo } from 'react'; import { memo } from 'react';
export const RegionalPromptsEditor = memo(() => { export const ControlLayersEditor = memo(() => {
return ( return (
<Flex <Flex
position="relative" position="relative"
@ -15,10 +15,10 @@ export const RegionalPromptsEditor = memo(() => {
alignItems="center" alignItems="center"
justifyContent="center" justifyContent="center"
> >
<RegionalPromptsToolbar /> <ControlLayersToolbar />
<StageComponent /> <StageComponent />
</Flex> </Flex>
); );
}); });
RegionalPromptsEditor.displayName = 'RegionalPromptsEditor'; ControlLayersEditor.displayName = 'ControlLayersEditor';

View File

@ -8,17 +8,17 @@ import { ControlAdapterLayerListItem } from 'features/controlLayers/components/C
import { DeleteAllLayersButton } from 'features/controlLayers/components/DeleteAllLayersButton'; import { DeleteAllLayersButton } from 'features/controlLayers/components/DeleteAllLayersButton';
import { IPAdapterLayerListItem } from 'features/controlLayers/components/IPAdapterLayerListItem'; import { IPAdapterLayerListItem } from 'features/controlLayers/components/IPAdapterLayerListItem';
import { MaskedGuidanceLayerListItem } from 'features/controlLayers/components/MaskedGuidanceLayerListItem'; import { MaskedGuidanceLayerListItem } from 'features/controlLayers/components/MaskedGuidanceLayerListItem';
import { isRenderableLayer, selectRegionalPromptsSlice } from 'features/controlLayers/store/regionalPromptsSlice'; import { isRenderableLayer, selectControlLayersSlice } from 'features/controlLayers/store/controlLayersSlice';
import type { Layer } from 'features/controlLayers/store/types'; import type { Layer } from 'features/controlLayers/store/types';
import { partition } from 'lodash-es'; import { partition } from 'lodash-es';
import { memo } from 'react'; import { memo } from 'react';
const selectLayerIdTypePairs = createMemoizedSelector(selectRegionalPromptsSlice, (regionalPrompts) => { const selectLayerIdTypePairs = createMemoizedSelector(selectControlLayersSlice, (controlLayers) => {
const [renderableLayers, ipAdapterLayers] = partition(regionalPrompts.present.layers, isRenderableLayer); const [renderableLayers, ipAdapterLayers] = partition(controlLayers.present.layers, isRenderableLayer);
return [...ipAdapterLayers, ...renderableLayers].map((l) => ({ id: l.id, type: l.type })).reverse(); return [...ipAdapterLayers, ...renderableLayers].map((l) => ({ id: l.id, type: l.type })).reverse();
}); });
export const RegionalPromptsPanelContent = memo(() => { export const ControlLayersPanelContent = memo(() => {
const layerIdTypePairs = useAppSelector(selectLayerIdTypePairs); const layerIdTypePairs = useAppSelector(selectLayerIdTypePairs);
return ( return (
<Flex flexDir="column" gap={4} w="full" h="full"> <Flex flexDir="column" gap={4} w="full" h="full">
@ -37,7 +37,7 @@ export const RegionalPromptsPanelContent = memo(() => {
); );
}); });
RegionalPromptsPanelContent.displayName = 'RegionalPromptsPanelContent'; ControlLayersPanelContent.displayName = 'ControlLayersPanelContent';
type LayerWrapperProps = { type LayerWrapperProps = {
id: string; id: string;
@ -45,7 +45,7 @@ type LayerWrapperProps = {
}; };
const LayerWrapper = memo(({ id, type }: LayerWrapperProps) => { const LayerWrapper = memo(({ id, type }: LayerWrapperProps) => {
if (type === 'masked_guidance_layer') { if (type === 'regional_guidance_layer') {
return <MaskedGuidanceLayerListItem key={id} layerId={id} />; return <MaskedGuidanceLayerListItem key={id} layerId={id} />;
} }
if (type === 'control_adapter_layer') { if (type === 'control_adapter_layer') {

View File

@ -6,7 +6,7 @@ import { ToolChooser } from 'features/controlLayers/components/ToolChooser';
import { UndoRedoButtonGroup } from 'features/controlLayers/components/UndoRedoButtonGroup'; import { UndoRedoButtonGroup } from 'features/controlLayers/components/UndoRedoButtonGroup';
import { memo } from 'react'; import { memo } from 'react';
export const RegionalPromptsToolbar = memo(() => { export const ControlLayersToolbar = memo(() => {
return ( return (
<Flex gap={4}> <Flex gap={4}>
<BrushSize /> <BrushSize />
@ -17,4 +17,4 @@ export const RegionalPromptsToolbar = memo(() => {
); );
}); });
RegionalPromptsToolbar.displayName = 'RegionalPromptsToolbar'; ControlLayersToolbar.displayName = 'ControlLayersToolbar';

View File

@ -14,7 +14,7 @@ export const DeleteAllLayersButton = memo(() => {
return ( return (
<Button onClick={onClick} leftIcon={<PiTrashSimpleBold />} variant="ghost" colorScheme="error"> <Button onClick={onClick} leftIcon={<PiTrashSimpleBold />} variant="ghost" colorScheme="error">
{t('regionalPrompts.deleteAll')} {t('controlLayers.deleteAll')}
</Button> </Button>
); );
}); });

View File

@ -2,8 +2,8 @@ import { CompositeNumberInput, CompositeSlider, Flex, FormControl, FormLabel } f
import { useAppDispatch, useAppSelector } from 'app/store/storeHooks'; import { useAppDispatch, useAppSelector } from 'app/store/storeHooks';
import { import {
globalMaskLayerOpacityChanged, globalMaskLayerOpacityChanged,
initialRegionalPromptsState, initialControlLayersState,
} from 'features/controlLayers/store/regionalPromptsSlice'; } from 'features/controlLayers/store/controlLayersSlice';
import { memo, useCallback } from 'react'; import { memo, useCallback } from 'react';
import { useTranslation } from 'react-i18next'; import { useTranslation } from 'react-i18next';
@ -14,7 +14,7 @@ export const GlobalMaskLayerOpacity = memo(() => {
const dispatch = useAppDispatch(); const dispatch = useAppDispatch();
const { t } = useTranslation(); const { t } = useTranslation();
const globalMaskLayerOpacity = useAppSelector((s) => const globalMaskLayerOpacity = useAppSelector((s) =>
Math.round(s.regionalPrompts.present.globalMaskLayerOpacity * 100) Math.round(s.controlLayers.present.globalMaskLayerOpacity * 100)
); );
const onChange = useCallback( const onChange = useCallback(
(v: number) => { (v: number) => {
@ -24,14 +24,14 @@ export const GlobalMaskLayerOpacity = memo(() => {
); );
return ( return (
<FormControl orientation="vertical"> <FormControl orientation="vertical">
<FormLabel m={0}>{t('regionalPrompts.globalMaskOpacity')}</FormLabel> <FormLabel m={0}>{t('controlLayers.globalMaskOpacity')}</FormLabel>
<Flex gap={4}> <Flex gap={4}>
<CompositeSlider <CompositeSlider
min={0} min={0}
max={100} max={100}
step={1} step={1}
value={globalMaskLayerOpacity} value={globalMaskLayerOpacity}
defaultValue={initialRegionalPromptsState.globalMaskLayerOpacity * 100} defaultValue={initialControlLayersState.globalMaskLayerOpacity * 100}
onChange={onChange} onChange={onChange}
marks={marks} marks={marks}
minW={48} minW={48}
@ -41,7 +41,7 @@ export const GlobalMaskLayerOpacity = memo(() => {
max={100} max={100}
step={1} step={1}
value={globalMaskLayerOpacity} value={globalMaskLayerOpacity}
defaultValue={initialRegionalPromptsState.globalMaskLayerOpacity * 100} defaultValue={initialControlLayersState.globalMaskLayerOpacity * 100}
onChange={onChange} onChange={onChange}
w={28} w={28}
format={formatPct} format={formatPct}

View File

@ -5,7 +5,7 @@ import ControlAdapterLayerConfig from 'features/controlLayers/components/control
import { LayerTitle } from 'features/controlLayers/components/LayerTitle'; import { LayerTitle } from 'features/controlLayers/components/LayerTitle';
import { RPLayerDeleteButton } from 'features/controlLayers/components/RPLayerDeleteButton'; import { RPLayerDeleteButton } from 'features/controlLayers/components/RPLayerDeleteButton';
import { RPLayerVisibilityToggle } from 'features/controlLayers/components/RPLayerVisibilityToggle'; import { RPLayerVisibilityToggle } from 'features/controlLayers/components/RPLayerVisibilityToggle';
import { isIPAdapterLayer, selectRegionalPromptsSlice } from 'features/controlLayers/store/regionalPromptsSlice'; import { isIPAdapterLayer, selectControlLayersSlice } from 'features/controlLayers/store/controlLayersSlice';
import { memo, useMemo } from 'react'; import { memo, useMemo } from 'react';
import { assert } from 'tsafe'; import { assert } from 'tsafe';
@ -16,8 +16,8 @@ type Props = {
export const IPAdapterLayerListItem = memo(({ layerId }: Props) => { export const IPAdapterLayerListItem = memo(({ layerId }: Props) => {
const selector = useMemo( const selector = useMemo(
() => () =>
createMemoizedSelector(selectRegionalPromptsSlice, (regionalPrompts) => { createMemoizedSelector(selectControlLayersSlice, (controlLayers) => {
const layer = regionalPrompts.present.layers.find((l) => l.id === layerId); const layer = controlLayers.present.layers.find((l) => l.id === layerId);
assert(isIPAdapterLayer(layer), `Layer ${layerId} not found or not an IP Adapter layer`); assert(isIPAdapterLayer(layer), `Layer ${layerId} not found or not an IP Adapter layer`);
return layer.ipAdapterId; return layer.ipAdapterId;
}), }),

View File

@ -10,8 +10,8 @@ type Props = {
export const LayerTitle = memo(({ type }: Props) => { export const LayerTitle = memo(({ type }: Props) => {
const { t } = useTranslation(); const { t } = useTranslation();
const title = useMemo(() => { const title = useMemo(() => {
if (type === 'masked_guidance_layer') { if (type === 'regional_guidance_layer') {
return t('regionalPrompts.maskedGuidance'); return t('controlLayers.maskedGuidance');
} else if (type === 'control_adapter_layer') { } else if (type === 'control_adapter_layer') {
return t('common.controlNet'); return t('common.controlNet');
} else if (type === 'ip_adapter_layer') { } else if (type === 'ip_adapter_layer') {

View File

@ -14,8 +14,8 @@ import { RPLayerVisibilityToggle } from 'features/controlLayers/components/RPLay
import { import {
isMaskedGuidanceLayer, isMaskedGuidanceLayer,
layerSelected, layerSelected,
selectRegionalPromptsSlice, selectControlLayersSlice,
} from 'features/controlLayers/store/regionalPromptsSlice'; } from 'features/controlLayers/store/controlLayersSlice';
import { memo, useCallback, useMemo } from 'react'; import { memo, useCallback, useMemo } from 'react';
import { useTranslation } from 'react-i18next'; import { useTranslation } from 'react-i18next';
import { assert } from 'tsafe'; import { assert } from 'tsafe';
@ -31,15 +31,15 @@ export const MaskedGuidanceLayerListItem = memo(({ layerId }: Props) => {
const dispatch = useAppDispatch(); const dispatch = useAppDispatch();
const selector = useMemo( const selector = useMemo(
() => () =>
createMemoizedSelector(selectRegionalPromptsSlice, (regionalPrompts) => { createMemoizedSelector(selectControlLayersSlice, (controlLayers) => {
const layer = regionalPrompts.present.layers.find((l) => l.id === layerId); const layer = controlLayers.present.layers.find((l) => l.id === layerId);
assert(isMaskedGuidanceLayer(layer), `Layer ${layerId} not found or not an RP layer`); assert(isMaskedGuidanceLayer(layer), `Layer ${layerId} not found or not an RP layer`);
return { return {
color: rgbColorToString(layer.previewColor), color: rgbColorToString(layer.previewColor),
hasPositivePrompt: layer.positivePrompt !== null, hasPositivePrompt: layer.positivePrompt !== null,
hasNegativePrompt: layer.negativePrompt !== null, hasNegativePrompt: layer.negativePrompt !== null,
hasIPAdapters: layer.ipAdapterIds.length > 0, hasIPAdapters: layer.ipAdapterIds.length > 0,
isSelected: layerId === regionalPrompts.present.selectedLayerId, isSelected: layerId === controlLayers.present.selectedLayerId,
autoNegative: layer.autoNegative, autoNegative: layer.autoNegative,
}; };
}), }),
@ -63,11 +63,11 @@ export const MaskedGuidanceLayerListItem = memo(({ layerId }: Props) => {
<Flex flexDir="column" w="full" bg="base.850" p={3} gap={3} borderRadius="base"> <Flex flexDir="column" w="full" bg="base.850" p={3} gap={3} borderRadius="base">
<Flex gap={3} alignItems="center" cursor="pointer"> <Flex gap={3} alignItems="center" cursor="pointer">
<RPLayerVisibilityToggle layerId={layerId} /> <RPLayerVisibilityToggle layerId={layerId} />
<LayerTitle type="masked_guidance_layer" /> <LayerTitle type="regional_guidance_layer" />
<Spacer /> <Spacer />
{autoNegative === 'invert' && ( {autoNegative === 'invert' && (
<Badge color="base.300" bg="transparent" borderWidth={1}> <Badge color="base.300" bg="transparent" borderWidth={1}>
{t('regionalPrompts.autoNegative')} {t('controlLayers.autoNegative')}
</Badge> </Badge>
)} )}
<RPLayerColorPicker layerId={layerId} /> <RPLayerColorPicker layerId={layerId} />

View File

@ -4,8 +4,8 @@ import { useAppDispatch, useAppSelector } from 'app/store/storeHooks';
import { import {
isMaskedGuidanceLayer, isMaskedGuidanceLayer,
maskLayerAutoNegativeChanged, maskLayerAutoNegativeChanged,
selectRegionalPromptsSlice, selectControlLayersSlice,
} from 'features/controlLayers/store/regionalPromptsSlice'; } from 'features/controlLayers/store/controlLayersSlice';
import type { ChangeEvent } from 'react'; import type { ChangeEvent } from 'react';
import { memo, useCallback, useMemo } from 'react'; import { memo, useCallback, useMemo } from 'react';
import { useTranslation } from 'react-i18next'; import { useTranslation } from 'react-i18next';
@ -18,8 +18,8 @@ type Props = {
const useAutoNegative = (layerId: string) => { const useAutoNegative = (layerId: string) => {
const selectAutoNegative = useMemo( const selectAutoNegative = useMemo(
() => () =>
createSelector(selectRegionalPromptsSlice, (regionalPrompts) => { createSelector(selectControlLayersSlice, (controlLayers) => {
const layer = regionalPrompts.present.layers.find((l) => l.id === layerId); const layer = controlLayers.present.layers.find((l) => l.id === layerId);
assert(isMaskedGuidanceLayer(layer), `Layer ${layerId} not found or not an RP layer`); assert(isMaskedGuidanceLayer(layer), `Layer ${layerId} not found or not an RP layer`);
return layer.autoNegative; return layer.autoNegative;
}), }),
@ -42,7 +42,7 @@ export const MaskedGuidanceLayerAutoNegativeCheckbox = memo(({ layerId }: Props)
return ( return (
<FormControl gap={2}> <FormControl gap={2}>
<FormLabel m={0}>{t('regionalPrompts.autoNegative')}</FormLabel> <FormLabel m={0}>{t('controlLayers.autoNegative')}</FormLabel>
<Checkbox size="md" isChecked={autoNegative === 'invert'} onChange={onChange} /> <Checkbox size="md" isChecked={autoNegative === 'invert'} onChange={onChange} />
</FormControl> </FormControl>
); );

View File

@ -6,8 +6,8 @@ import { rgbColorToString } from 'features/canvas/util/colorToString';
import { import {
isMaskedGuidanceLayer, isMaskedGuidanceLayer,
maskLayerPreviewColorChanged, maskLayerPreviewColorChanged,
selectRegionalPromptsSlice, selectControlLayersSlice,
} from 'features/controlLayers/store/regionalPromptsSlice'; } from 'features/controlLayers/store/controlLayersSlice';
import { memo, useCallback, useMemo } from 'react'; import { memo, useCallback, useMemo } from 'react';
import type { RgbColor } from 'react-colorful'; import type { RgbColor } from 'react-colorful';
import { useTranslation } from 'react-i18next'; import { useTranslation } from 'react-i18next';
@ -21,8 +21,8 @@ export const RPLayerColorPicker = memo(({ layerId }: Props) => {
const { t } = useTranslation(); const { t } = useTranslation();
const selectColor = useMemo( const selectColor = useMemo(
() => () =>
createMemoizedSelector(selectRegionalPromptsSlice, (regionalPrompts) => { createMemoizedSelector(selectControlLayersSlice, (controlLayers) => {
const layer = regionalPrompts.present.layers.find((l) => l.id === layerId); const layer = controlLayers.present.layers.find((l) => l.id === layerId);
assert(isMaskedGuidanceLayer(layer), `Layer ${layerId} not found or not an vector mask layer`); assert(isMaskedGuidanceLayer(layer), `Layer ${layerId} not found or not an vector mask layer`);
return layer.previewColor; return layer.previewColor;
}), }),
@ -40,10 +40,10 @@ export const RPLayerColorPicker = memo(({ layerId }: Props) => {
<Popover isLazy> <Popover isLazy>
<PopoverTrigger> <PopoverTrigger>
<span> <span>
<Tooltip label={t('regionalPrompts.maskPreviewColor')}> <Tooltip label={t('controlLayers.maskPreviewColor')}>
<Flex <Flex
as="button" as="button"
aria-label={t('regionalPrompts.maskPreviewColor')} aria-label={t('controlLayers.maskPreviewColor')}
borderRadius="base" borderRadius="base"
borderWidth={1} borderWidth={1}
bg={rgbColorToString(color)} bg={rgbColorToString(color)}

View File

@ -3,7 +3,7 @@ import { createMemoizedSelector } from 'app/store/createMemoizedSelector';
import { guidanceLayerIPAdapterDeleted } from 'app/store/middleware/listenerMiddleware/listeners/regionalControlToControlAdapterBridge'; import { guidanceLayerIPAdapterDeleted } from 'app/store/middleware/listenerMiddleware/listeners/regionalControlToControlAdapterBridge';
import { useAppDispatch, useAppSelector } from 'app/store/storeHooks'; import { useAppDispatch, useAppSelector } from 'app/store/storeHooks';
import ControlAdapterLayerConfig from 'features/controlLayers/components/controlAdapterOverrides/ControlAdapterLayerConfig'; import ControlAdapterLayerConfig from 'features/controlLayers/components/controlAdapterOverrides/ControlAdapterLayerConfig';
import { isMaskedGuidanceLayer, selectRegionalPromptsSlice } from 'features/controlLayers/store/regionalPromptsSlice'; import { isMaskedGuidanceLayer, selectControlLayersSlice } from 'features/controlLayers/store/controlLayersSlice';
import { memo, useCallback, useMemo } from 'react'; import { memo, useCallback, useMemo } from 'react';
import { PiTrashSimpleBold } from 'react-icons/pi'; import { PiTrashSimpleBold } from 'react-icons/pi';
import { assert } from 'tsafe'; import { assert } from 'tsafe';
@ -15,8 +15,8 @@ type Props = {
export const RPLayerIPAdapterList = memo(({ layerId }: Props) => { export const RPLayerIPAdapterList = memo(({ layerId }: Props) => {
const selectIPAdapterIds = useMemo( const selectIPAdapterIds = useMemo(
() => () =>
createMemoizedSelector(selectRegionalPromptsSlice, (regionalPrompts) => { createMemoizedSelector(selectControlLayersSlice, (controlLayers) => {
const layer = regionalPrompts.present.layers.filter(isMaskedGuidanceLayer).find((l) => l.id === layerId); const layer = controlLayers.present.layers.filter(isMaskedGuidanceLayer).find((l) => l.id === layerId);
assert(layer, `Layer ${layerId} not found`); assert(layer, `Layer ${layerId} not found`);
return layer.ipAdapterIds; return layer.ipAdapterIds;
}), }),

View File

@ -3,7 +3,7 @@ import { useAppDispatch } from 'app/store/storeHooks';
import { RPLayerMenuArrangeActions } from 'features/controlLayers/components/RPLayerMenuArrangeActions'; import { RPLayerMenuArrangeActions } from 'features/controlLayers/components/RPLayerMenuArrangeActions';
import { RPLayerMenuMaskedGuidanceActions } from 'features/controlLayers/components/RPLayerMenuMaskedGuidanceActions'; import { RPLayerMenuMaskedGuidanceActions } from 'features/controlLayers/components/RPLayerMenuMaskedGuidanceActions';
import { useLayerType } from 'features/controlLayers/hooks/layerStateHooks'; import { useLayerType } from 'features/controlLayers/hooks/layerStateHooks';
import { layerDeleted, layerReset } from 'features/controlLayers/store/regionalPromptsSlice'; import { layerDeleted, layerReset } from 'features/controlLayers/store/controlLayersSlice';
import { memo, useCallback } from 'react'; import { memo, useCallback } from 'react';
import { useTranslation } from 'react-i18next'; import { useTranslation } from 'react-i18next';
import { PiArrowCounterClockwiseBold, PiDotsThreeVerticalBold, PiTrashSimpleBold } from 'react-icons/pi'; import { PiArrowCounterClockwiseBold, PiDotsThreeVerticalBold, PiTrashSimpleBold } from 'react-icons/pi';
@ -24,19 +24,19 @@ export const RPLayerMenu = memo(({ layerId }: Props) => {
<Menu> <Menu>
<MenuButton as={IconButton} aria-label="Layer menu" size="sm" icon={<PiDotsThreeVerticalBold />} /> <MenuButton as={IconButton} aria-label="Layer menu" size="sm" icon={<PiDotsThreeVerticalBold />} />
<MenuList> <MenuList>
{layerType === 'masked_guidance_layer' && ( {layerType === 'regional_guidance_layer' && (
<> <>
<RPLayerMenuMaskedGuidanceActions layerId={layerId} /> <RPLayerMenuMaskedGuidanceActions layerId={layerId} />
<MenuDivider /> <MenuDivider />
</> </>
)} )}
{(layerType === 'masked_guidance_layer' || layerType === 'control_adapter_layer') && ( {(layerType === 'regional_guidance_layer' || layerType === 'control_adapter_layer') && (
<> <>
<RPLayerMenuArrangeActions layerId={layerId} /> <RPLayerMenuArrangeActions layerId={layerId} />
<MenuDivider /> <MenuDivider />
</> </>
)} )}
{layerType === 'masked_guidance_layer' && ( {layerType === 'regional_guidance_layer' && (
<MenuItem onClick={resetLayer} icon={<PiArrowCounterClockwiseBold />}> <MenuItem onClick={resetLayer} icon={<PiArrowCounterClockwiseBold />}>
{t('accessibility.reset')} {t('accessibility.reset')}
</MenuItem> </MenuItem>

View File

@ -7,8 +7,8 @@ import {
layerMovedForward, layerMovedForward,
layerMovedToBack, layerMovedToBack,
layerMovedToFront, layerMovedToFront,
selectRegionalPromptsSlice, selectControlLayersSlice,
} from 'features/controlLayers/store/regionalPromptsSlice'; } from 'features/controlLayers/store/controlLayersSlice';
import { memo, useCallback, useMemo } from 'react'; import { memo, useCallback, useMemo } from 'react';
import { useTranslation } from 'react-i18next'; import { useTranslation } from 'react-i18next';
import { PiArrowDownBold, PiArrowLineDownBold, PiArrowLineUpBold, PiArrowUpBold } from 'react-icons/pi'; import { PiArrowDownBold, PiArrowLineDownBold, PiArrowLineUpBold, PiArrowUpBold } from 'react-icons/pi';
@ -21,11 +21,11 @@ export const RPLayerMenuArrangeActions = memo(({ layerId }: Props) => {
const { t } = useTranslation(); const { t } = useTranslation();
const selectValidActions = useMemo( const selectValidActions = useMemo(
() => () =>
createMemoizedSelector(selectRegionalPromptsSlice, (regionalPrompts) => { createMemoizedSelector(selectControlLayersSlice, (controlLayers) => {
const layer = regionalPrompts.present.layers.find((l) => l.id === layerId); const layer = controlLayers.present.layers.find((l) => l.id === layerId);
assert(isRenderableLayer(layer), `Layer ${layerId} not found or not an RP layer`); assert(isRenderableLayer(layer), `Layer ${layerId} not found or not an RP layer`);
const layerIndex = regionalPrompts.present.layers.findIndex((l) => l.id === layerId); const layerIndex = controlLayers.present.layers.findIndex((l) => l.id === layerId);
const layerCount = regionalPrompts.present.layers.length; const layerCount = controlLayers.present.layers.length;
return { return {
canMoveForward: layerIndex < layerCount - 1, canMoveForward: layerIndex < layerCount - 1,
canMoveBackward: layerIndex > 0, canMoveBackward: layerIndex > 0,
@ -51,16 +51,16 @@ export const RPLayerMenuArrangeActions = memo(({ layerId }: Props) => {
return ( return (
<> <>
<MenuItem onClick={moveToFront} isDisabled={!validActions.canMoveToFront} icon={<PiArrowLineUpBold />}> <MenuItem onClick={moveToFront} isDisabled={!validActions.canMoveToFront} icon={<PiArrowLineUpBold />}>
{t('regionalPrompts.moveToFront')} {t('controlLayers.moveToFront')}
</MenuItem> </MenuItem>
<MenuItem onClick={moveForward} isDisabled={!validActions.canMoveForward} icon={<PiArrowUpBold />}> <MenuItem onClick={moveForward} isDisabled={!validActions.canMoveForward} icon={<PiArrowUpBold />}>
{t('regionalPrompts.moveForward')} {t('controlLayers.moveForward')}
</MenuItem> </MenuItem>
<MenuItem onClick={moveBackward} isDisabled={!validActions.canMoveBackward} icon={<PiArrowDownBold />}> <MenuItem onClick={moveBackward} isDisabled={!validActions.canMoveBackward} icon={<PiArrowDownBold />}>
{t('regionalPrompts.moveBackward')} {t('controlLayers.moveBackward')}
</MenuItem> </MenuItem>
<MenuItem onClick={moveToBack} isDisabled={!validActions.canMoveToBack} icon={<PiArrowLineDownBold />}> <MenuItem onClick={moveToBack} isDisabled={!validActions.canMoveToBack} icon={<PiArrowLineDownBold />}>
{t('regionalPrompts.moveToBack')} {t('controlLayers.moveToBack')}
</MenuItem> </MenuItem>
</> </>
); );

View File

@ -6,8 +6,8 @@ import {
isMaskedGuidanceLayer, isMaskedGuidanceLayer,
maskLayerNegativePromptChanged, maskLayerNegativePromptChanged,
maskLayerPositivePromptChanged, maskLayerPositivePromptChanged,
selectRegionalPromptsSlice, selectControlLayersSlice,
} from 'features/controlLayers/store/regionalPromptsSlice'; } from 'features/controlLayers/store/controlLayersSlice';
import { memo, useCallback, useMemo } from 'react'; import { memo, useCallback, useMemo } from 'react';
import { useTranslation } from 'react-i18next'; import { useTranslation } from 'react-i18next';
import { PiPlusBold } from 'react-icons/pi'; import { PiPlusBold } from 'react-icons/pi';
@ -20,8 +20,8 @@ export const RPLayerMenuMaskedGuidanceActions = memo(({ layerId }: Props) => {
const { t } = useTranslation(); const { t } = useTranslation();
const selectValidActions = useMemo( const selectValidActions = useMemo(
() => () =>
createMemoizedSelector(selectRegionalPromptsSlice, (regionalPrompts) => { createMemoizedSelector(selectControlLayersSlice, (controlLayers) => {
const layer = regionalPrompts.present.layers.find((l) => l.id === layerId); const layer = controlLayers.present.layers.find((l) => l.id === layerId);
assert(isMaskedGuidanceLayer(layer), `Layer ${layerId} not found or not an RP layer`); assert(isMaskedGuidanceLayer(layer), `Layer ${layerId} not found or not an RP layer`);
return { return {
canAddPositivePrompt: layer.positivePrompt === null, canAddPositivePrompt: layer.positivePrompt === null,
@ -43,13 +43,13 @@ export const RPLayerMenuMaskedGuidanceActions = memo(({ layerId }: Props) => {
return ( return (
<> <>
<MenuItem onClick={addPositivePrompt} isDisabled={!validActions.canAddPositivePrompt} icon={<PiPlusBold />}> <MenuItem onClick={addPositivePrompt} isDisabled={!validActions.canAddPositivePrompt} icon={<PiPlusBold />}>
{t('regionalPrompts.addPositivePrompt')} {t('controlLayers.addPositivePrompt')}
</MenuItem> </MenuItem>
<MenuItem onClick={addNegativePrompt} isDisabled={!validActions.canAddNegativePrompt} icon={<PiPlusBold />}> <MenuItem onClick={addNegativePrompt} isDisabled={!validActions.canAddNegativePrompt} icon={<PiPlusBold />}>
{t('regionalPrompts.addNegativePrompt')} {t('controlLayers.addNegativePrompt')}
</MenuItem> </MenuItem>
<MenuItem onClick={addIPAdapter} icon={<PiPlusBold />}> <MenuItem onClick={addIPAdapter} icon={<PiPlusBold />}>
{t('regionalPrompts.addIPAdapter')} {t('controlLayers.addIPAdapter')}
</MenuItem> </MenuItem>
</> </>
); );

View File

@ -2,7 +2,7 @@ import { Box, Textarea } from '@invoke-ai/ui-library';
import { useAppDispatch } from 'app/store/storeHooks'; import { useAppDispatch } from 'app/store/storeHooks';
import { RPLayerPromptDeleteButton } from 'features/controlLayers/components/RPLayerPromptDeleteButton'; import { RPLayerPromptDeleteButton } from 'features/controlLayers/components/RPLayerPromptDeleteButton';
import { useLayerNegativePrompt } from 'features/controlLayers/hooks/layerStateHooks'; import { useLayerNegativePrompt } from 'features/controlLayers/hooks/layerStateHooks';
import { maskLayerNegativePromptChanged } from 'features/controlLayers/store/regionalPromptsSlice'; import { maskLayerNegativePromptChanged } from 'features/controlLayers/store/controlLayersSlice';
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';

View File

@ -2,7 +2,7 @@ import { Box, Textarea } from '@invoke-ai/ui-library';
import { useAppDispatch } from 'app/store/storeHooks'; import { useAppDispatch } from 'app/store/storeHooks';
import { RPLayerPromptDeleteButton } from 'features/controlLayers/components/RPLayerPromptDeleteButton'; import { RPLayerPromptDeleteButton } from 'features/controlLayers/components/RPLayerPromptDeleteButton';
import { useLayerPositivePrompt } from 'features/controlLayers/hooks/layerStateHooks'; import { useLayerPositivePrompt } from 'features/controlLayers/hooks/layerStateHooks';
import { maskLayerPositivePromptChanged } from 'features/controlLayers/store/regionalPromptsSlice'; import { maskLayerPositivePromptChanged } from 'features/controlLayers/store/controlLayersSlice';
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';

View File

@ -3,7 +3,7 @@ import { useAppDispatch } from 'app/store/storeHooks';
import { import {
maskLayerNegativePromptChanged, maskLayerNegativePromptChanged,
maskLayerPositivePromptChanged, maskLayerPositivePromptChanged,
} from 'features/controlLayers/store/regionalPromptsSlice'; } from 'features/controlLayers/store/controlLayersSlice';
import { memo, useCallback } from 'react'; import { memo, useCallback } from 'react';
import { useTranslation } from 'react-i18next'; import { useTranslation } from 'react-i18next';
import { PiTrashSimpleBold } from 'react-icons/pi'; import { PiTrashSimpleBold } from 'react-icons/pi';
@ -24,10 +24,10 @@ export const RPLayerPromptDeleteButton = memo(({ layerId, polarity }: Props) =>
} }
}, [dispatch, layerId, polarity]); }, [dispatch, layerId, polarity]);
return ( return (
<Tooltip label={t('regionalPrompts.deletePrompt')}> <Tooltip label={t('controlLayers.deletePrompt')}>
<IconButton <IconButton
variant="promptOverlay" variant="promptOverlay"
aria-label={t('regionalPrompts.deletePrompt')} aria-label={t('controlLayers.deletePrompt')}
icon={<PiTrashSimpleBold />} icon={<PiTrashSimpleBold />}
onClick={onClick} onClick={onClick}
/> />

View File

@ -1,7 +1,7 @@
import { IconButton } from '@invoke-ai/ui-library'; import { IconButton } from '@invoke-ai/ui-library';
import { useAppDispatch } from 'app/store/storeHooks'; import { useAppDispatch } from 'app/store/storeHooks';
import { useLayerIsVisible } from 'features/controlLayers/hooks/layerStateHooks'; import { useLayerIsVisible } from 'features/controlLayers/hooks/layerStateHooks';
import { layerVisibilityToggled } from 'features/controlLayers/store/regionalPromptsSlice'; import { layerVisibilityToggled } from 'features/controlLayers/store/controlLayersSlice';
import { memo, useCallback } from 'react'; import { memo, useCallback } from 'react';
import { useTranslation } from 'react-i18next'; import { useTranslation } from 'react-i18next';
import { PiCheckBold } from 'react-icons/pi'; import { PiCheckBold } from 'react-icons/pi';
@ -21,8 +21,8 @@ export const RPLayerVisibilityToggle = memo(({ layerId }: Props) => {
return ( return (
<IconButton <IconButton
size="sm" size="sm"
aria-label={t('regionalPrompts.toggleVisibility')} aria-label={t('controlLayers.toggleVisibility')}
tooltip={t('regionalPrompts.toggleVisibility')} tooltip={t('controlLayers.toggleVisibility')}
variant="outline" variant="outline"
icon={isVisible ? <PiCheckBold /> : undefined} icon={isVisible ? <PiCheckBold /> : undefined}
onClick={onClick} onClick={onClick}

View File

@ -1,24 +0,0 @@
import { Flex } from '@invoke-ai/ui-library';
import type { Meta, StoryObj } from '@storybook/react';
import { RegionalPromptsEditor } from 'features/controlLayers/components/RegionalPromptsEditor';
const meta: Meta<typeof RegionalPromptsEditor> = {
title: 'Feature/RegionalPrompts',
tags: ['autodocs'],
component: RegionalPromptsEditor,
};
export default meta;
type Story = StoryObj<typeof RegionalPromptsEditor>;
const Component = () => {
return (
<Flex w={1500} h={1500}>
<RegionalPromptsEditor />
</Flex>
);
};
export const Default: Story = {
render: Component,
};

View File

@ -13,8 +13,8 @@ import {
isMaskedGuidanceLayer, isMaskedGuidanceLayer,
layerBboxChanged, layerBboxChanged,
layerTranslated, layerTranslated,
selectRegionalPromptsSlice, selectControlLayersSlice,
} from 'features/controlLayers/store/regionalPromptsSlice'; } from 'features/controlLayers/store/controlLayersSlice';
import { debouncedRenderers, renderers as normalRenderers } from 'features/controlLayers/util/renderers'; import { debouncedRenderers, renderers as normalRenderers } from 'features/controlLayers/util/renderers';
import Konva from 'konva'; import Konva from 'konva';
import type { IRect } from 'konva/lib/types'; import type { IRect } from 'konva/lib/types';
@ -25,17 +25,17 @@ import { v4 as uuidv4 } from 'uuid';
// This will log warnings when layers > 5 - maybe use `import.meta.env.MODE === 'development'` instead? // This will log warnings when layers > 5 - maybe use `import.meta.env.MODE === 'development'` instead?
Konva.showWarnings = false; Konva.showWarnings = false;
const log = logger('regionalPrompts'); const log = logger('controlLayers');
const selectSelectedLayerColor = createMemoizedSelector(selectRegionalPromptsSlice, (regionalPrompts) => { const selectSelectedLayerColor = createMemoizedSelector(selectControlLayersSlice, (controlLayers) => {
const layer = regionalPrompts.present.layers const layer = controlLayers.present.layers
.filter(isMaskedGuidanceLayer) .filter(isMaskedGuidanceLayer)
.find((l) => l.id === regionalPrompts.present.selectedLayerId); .find((l) => l.id === controlLayers.present.selectedLayerId);
return layer?.previewColor ?? null; return layer?.previewColor ?? null;
}); });
const selectSelectedLayerType = createSelector(selectRegionalPromptsSlice, (regionalPrompts) => { const selectSelectedLayerType = createSelector(selectControlLayersSlice, (controlLayers) => {
const selectedLayer = regionalPrompts.present.layers.find((l) => l.id === regionalPrompts.present.selectedLayerId); const selectedLayer = controlLayers.present.layers.find((l) => l.id === controlLayers.present.selectedLayerId);
return selectedLayer?.type ?? null; return selectedLayer?.type ?? null;
}); });
@ -46,7 +46,7 @@ const useStageRenderer = (
asPreview: boolean asPreview: boolean
) => { ) => {
const dispatch = useAppDispatch(); const dispatch = useAppDispatch();
const state = useAppSelector((s) => s.regionalPrompts.present); const state = useAppSelector((s) => s.controlLayers.present);
const tool = useStore($tool); const tool = useStore($tool);
const { onMouseDown, onMouseUp, onMouseMove, onMouseEnter, onMouseLeave, onMouseWheel } = useMouseEvents(); const { onMouseDown, onMouseUp, onMouseMove, onMouseEnter, onMouseLeave, onMouseWheel } = useMouseEvents();
const cursorPosition = useStore($cursorPosition); const cursorPosition = useStore($cursorPosition);

View File

@ -4,18 +4,18 @@ import { createSelector } from '@reduxjs/toolkit';
import { useAppDispatch, useAppSelector } from 'app/store/storeHooks'; import { useAppDispatch, useAppSelector } from 'app/store/storeHooks';
import { import {
$tool, $tool,
selectControlLayersSlice,
selectedLayerDeleted, selectedLayerDeleted,
selectedLayerReset, selectedLayerReset,
selectRegionalPromptsSlice, } from 'features/controlLayers/store/controlLayersSlice';
} from 'features/controlLayers/store/regionalPromptsSlice';
import { useCallback } from 'react'; import { useCallback } from 'react';
import { useHotkeys } from 'react-hotkeys-hook'; import { useHotkeys } from 'react-hotkeys-hook';
import { useTranslation } from 'react-i18next'; import { useTranslation } from 'react-i18next';
import { PiArrowsOutCardinalBold, PiEraserBold, PiPaintBrushBold, PiRectangleBold } from 'react-icons/pi'; import { PiArrowsOutCardinalBold, PiEraserBold, PiPaintBrushBold, PiRectangleBold } from 'react-icons/pi';
const selectIsDisabled = createSelector(selectRegionalPromptsSlice, (regionalPrompts) => { const selectIsDisabled = createSelector(selectControlLayersSlice, (controlLayers) => {
const selectedLayer = regionalPrompts.present.layers.find((l) => l.id === regionalPrompts.present.selectedLayerId); const selectedLayer = controlLayers.present.layers.find((l) => l.id === controlLayers.present.selectedLayerId);
return selectedLayer?.type !== 'masked_guidance_layer'; return selectedLayer?.type !== 'regional_guidance_layer';
}); });
export const ToolChooser: React.FC = () => { export const ToolChooser: React.FC = () => {
@ -70,8 +70,8 @@ export const ToolChooser: React.FC = () => {
isDisabled={isDisabled} isDisabled={isDisabled}
/> />
<IconButton <IconButton
aria-label={`${t('regionalPrompts.rectangle')} (U)`} aria-label={`${t('controlLayers.rectangle')} (U)`}
tooltip={`${t('regionalPrompts.rectangle')} (U)`} tooltip={`${t('controlLayers.rectangle')} (U)`}
icon={<PiRectangleBold />} icon={<PiRectangleBold />}
variant={tool === 'rect' ? 'solid' : 'outline'} variant={tool === 'rect' ? 'solid' : 'outline'}
onClick={setToolToRect} onClick={setToolToRect}

View File

@ -1,7 +1,7 @@
/* eslint-disable i18next/no-literal-string */ /* eslint-disable i18next/no-literal-string */
import { ButtonGroup, IconButton } from '@invoke-ai/ui-library'; import { ButtonGroup, IconButton } from '@invoke-ai/ui-library';
import { useAppDispatch, useAppSelector } from 'app/store/storeHooks'; import { useAppDispatch, useAppSelector } from 'app/store/storeHooks';
import { redo, undo } from 'features/controlLayers/store/regionalPromptsSlice'; import { redo, undo } from 'features/controlLayers/store/controlLayersSlice';
import { memo, useCallback } from 'react'; import { memo, useCallback } from 'react';
import { useHotkeys } from 'react-hotkeys-hook'; import { useHotkeys } from 'react-hotkeys-hook';
import { useTranslation } from 'react-i18next'; import { useTranslation } from 'react-i18next';
@ -11,13 +11,13 @@ export const UndoRedoButtonGroup = memo(() => {
const { t } = useTranslation(); const { t } = useTranslation();
const dispatch = useAppDispatch(); const dispatch = useAppDispatch();
const mayUndo = useAppSelector((s) => s.regionalPrompts.past.length > 0); const mayUndo = useAppSelector((s) => s.controlLayers.past.length > 0);
const handleUndo = useCallback(() => { const handleUndo = useCallback(() => {
dispatch(undo()); dispatch(undo());
}, [dispatch]); }, [dispatch]);
useHotkeys(['meta+z', 'ctrl+z'], handleUndo, { enabled: mayUndo, preventDefault: true }, [mayUndo, handleUndo]); useHotkeys(['meta+z', 'ctrl+z'], handleUndo, { enabled: mayUndo, preventDefault: true }, [mayUndo, handleUndo]);
const mayRedo = useAppSelector((s) => s.regionalPrompts.future.length > 0); const mayRedo = useAppSelector((s) => s.controlLayers.future.length > 0);
const handleRedo = useCallback(() => { const handleRedo = useCallback(() => {
dispatch(redo()); dispatch(redo());
}, [dispatch]); }, [dispatch]);

View File

@ -13,7 +13,7 @@ import {
controlAdapterImageChanged, controlAdapterImageChanged,
selectControlAdaptersSlice, selectControlAdaptersSlice,
} from 'features/controlAdapters/store/controlAdaptersSlice'; } from 'features/controlAdapters/store/controlAdaptersSlice';
import { heightChanged, widthChanged } from 'features/controlLayers/store/regionalPromptsSlice'; import { heightChanged, widthChanged } from 'features/controlLayers/store/controlLayersSlice';
import type { TypesafeDraggableData, TypesafeDroppableData } from 'features/dnd/types'; import type { TypesafeDraggableData, TypesafeDroppableData } from 'features/dnd/types';
import { calculateNewSize } from 'features/parameters/components/ImageSize/calculateNewSize'; import { calculateNewSize } from 'features/parameters/components/ImageSize/calculateNewSize';
import { selectOptimalDimension } from 'features/parameters/store/generationSlice'; import { selectOptimalDimension } from 'features/parameters/store/generationSlice';

View File

@ -3,16 +3,16 @@ import { useAppSelector } from 'app/store/storeHooks';
import { import {
isControlAdapterLayer, isControlAdapterLayer,
isMaskedGuidanceLayer, isMaskedGuidanceLayer,
selectRegionalPromptsSlice, selectControlLayersSlice,
} from 'features/controlLayers/store/regionalPromptsSlice'; } from 'features/controlLayers/store/controlLayersSlice';
import { useMemo } from 'react'; import { useMemo } from 'react';
import { assert } from 'tsafe'; import { assert } from 'tsafe';
export const useLayerPositivePrompt = (layerId: string) => { export const useLayerPositivePrompt = (layerId: string) => {
const selectLayer = useMemo( const selectLayer = useMemo(
() => () =>
createSelector(selectRegionalPromptsSlice, (regionalPrompts) => { createSelector(selectControlLayersSlice, (controlLayers) => {
const layer = regionalPrompts.present.layers.find((l) => l.id === layerId); const layer = controlLayers.present.layers.find((l) => l.id === layerId);
assert(isMaskedGuidanceLayer(layer), `Layer ${layerId} not found or not an RP layer`); assert(isMaskedGuidanceLayer(layer), `Layer ${layerId} not found or not an RP layer`);
assert(layer.positivePrompt !== null, `Layer ${layerId} does not have a positive prompt`); assert(layer.positivePrompt !== null, `Layer ${layerId} does not have a positive prompt`);
return layer.positivePrompt; return layer.positivePrompt;
@ -26,8 +26,8 @@ export const useLayerPositivePrompt = (layerId: string) => {
export const useLayerNegativePrompt = (layerId: string) => { export const useLayerNegativePrompt = (layerId: string) => {
const selectLayer = useMemo( const selectLayer = useMemo(
() => () =>
createSelector(selectRegionalPromptsSlice, (regionalPrompts) => { createSelector(selectControlLayersSlice, (controlLayers) => {
const layer = regionalPrompts.present.layers.find((l) => l.id === layerId); const layer = controlLayers.present.layers.find((l) => l.id === layerId);
assert(isMaskedGuidanceLayer(layer), `Layer ${layerId} not found or not an RP layer`); assert(isMaskedGuidanceLayer(layer), `Layer ${layerId} not found or not an RP layer`);
assert(layer.negativePrompt !== null, `Layer ${layerId} does not have a negative prompt`); assert(layer.negativePrompt !== null, `Layer ${layerId} does not have a negative prompt`);
return layer.negativePrompt; return layer.negativePrompt;
@ -41,8 +41,8 @@ export const useLayerNegativePrompt = (layerId: string) => {
export const useLayerIsVisible = (layerId: string) => { export const useLayerIsVisible = (layerId: string) => {
const selectLayer = useMemo( const selectLayer = useMemo(
() => () =>
createSelector(selectRegionalPromptsSlice, (regionalPrompts) => { createSelector(selectControlLayersSlice, (controlLayers) => {
const layer = regionalPrompts.present.layers.find((l) => l.id === layerId); const layer = controlLayers.present.layers.find((l) => l.id === layerId);
assert(layer, `Layer ${layerId} not found`); assert(layer, `Layer ${layerId} not found`);
return layer.isEnabled; return layer.isEnabled;
}), }),
@ -55,8 +55,8 @@ export const useLayerIsVisible = (layerId: string) => {
export const useLayerType = (layerId: string) => { export const useLayerType = (layerId: string) => {
const selectLayer = useMemo( const selectLayer = useMemo(
() => () =>
createSelector(selectRegionalPromptsSlice, (regionalPrompts) => { createSelector(selectControlLayersSlice, (controlLayers) => {
const layer = regionalPrompts.present.layers.find((l) => l.id === layerId); const layer = controlLayers.present.layers.find((l) => l.id === layerId);
assert(layer, `Layer ${layerId} not found`); assert(layer, `Layer ${layerId} not found`);
return layer.type; return layer.type;
}), }),
@ -69,8 +69,8 @@ export const useLayerType = (layerId: string) => {
export const useLayerOpacity = (layerId: string) => { export const useLayerOpacity = (layerId: string) => {
const selectLayer = useMemo( const selectLayer = useMemo(
() => () =>
createSelector(selectRegionalPromptsSlice, (regionalPrompts) => { createSelector(selectControlLayersSlice, (controlLayers) => {
const layer = regionalPrompts.present.layers.filter(isControlAdapterLayer).find((l) => l.id === layerId); const layer = controlLayers.present.layers.filter(isControlAdapterLayer).find((l) => l.id === layerId);
assert(layer, `Layer ${layerId} not found`); assert(layer, `Layer ${layerId} not found`);
return Math.round(layer.opacity * 100); return Math.round(layer.opacity * 100);
}), }),

View File

@ -12,7 +12,7 @@ import {
maskLayerLineAdded, maskLayerLineAdded,
maskLayerPointsAdded, maskLayerPointsAdded,
maskLayerRectAdded, maskLayerRectAdded,
} from 'features/controlLayers/store/regionalPromptsSlice'; } from 'features/controlLayers/store/controlLayersSlice';
import type Konva from 'konva'; import type Konva from 'konva';
import type { KonvaEventObject } from 'konva/lib/Node'; import type { KonvaEventObject } from 'konva/lib/Node';
import type { Vector2d } from 'konva/lib/types'; import type { Vector2d } from 'konva/lib/types';
@ -48,11 +48,11 @@ const BRUSH_SPACING = 20;
export const useMouseEvents = () => { export const useMouseEvents = () => {
const dispatch = useAppDispatch(); const dispatch = useAppDispatch();
const selectedLayerId = useAppSelector((s) => s.regionalPrompts.present.selectedLayerId); const selectedLayerId = useAppSelector((s) => s.controlLayers.present.selectedLayerId);
const tool = useStore($tool); const tool = useStore($tool);
const lastCursorPosRef = useRef<[number, number] | null>(null); const lastCursorPosRef = useRef<[number, number] | null>(null);
const shouldInvertBrushSizeScrollDirection = useAppSelector((s) => s.canvas.shouldInvertBrushSizeScrollDirection); const shouldInvertBrushSizeScrollDirection = useAppSelector((s) => s.canvas.shouldInvertBrushSizeScrollDirection);
const brushSize = useAppSelector((s) => s.regionalPrompts.present.brushSize); const brushSize = useAppSelector((s) => s.controlLayers.present.brushSize);
const onMouseDown = useCallback( const onMouseDown = useCallback(
(e: KonvaEventObject<MouseEvent | TouchEvent>) => { (e: KonvaEventObject<MouseEvent | TouchEvent>) => {

View File

@ -1,14 +1,14 @@
import { createSelector } from '@reduxjs/toolkit'; import { createSelector } from '@reduxjs/toolkit';
import { useAppSelector } from 'app/store/storeHooks'; import { useAppSelector } from 'app/store/storeHooks';
import { isMaskedGuidanceLayer, selectRegionalPromptsSlice } from 'features/controlLayers/store/regionalPromptsSlice'; import { isMaskedGuidanceLayer, selectControlLayersSlice } from 'features/controlLayers/store/controlLayersSlice';
import { useMemo } from 'react'; import { useMemo } from 'react';
import { useTranslation } from 'react-i18next'; import { useTranslation } from 'react-i18next';
const selectValidLayerCount = createSelector(selectRegionalPromptsSlice, (regionalPrompts) => { const selectValidLayerCount = createSelector(selectControlLayersSlice, (controlLayers) => {
if (!regionalPrompts.present.isEnabled) { if (!controlLayers.present.isEnabled) {
return 0; return 0;
} }
const validLayers = regionalPrompts.present.layers const validLayers = controlLayers.present.layers
.filter(isMaskedGuidanceLayer) .filter(isMaskedGuidanceLayer)
.filter((l) => l.isEnabled) .filter((l) => l.isEnabled)
.filter((l) => { .filter((l) => {
@ -25,7 +25,7 @@ export const useRegionalControlTitle = () => {
const validLayerCount = useAppSelector(selectValidLayerCount); const validLayerCount = useAppSelector(selectValidLayerCount);
const title = useMemo(() => { const title = useMemo(() => {
const suffix = validLayerCount > 0 ? ` (${validLayerCount})` : ''; const suffix = validLayerCount > 0 ? ` (${validLayerCount})` : '';
return `${t('regionalPrompts.regionalControl')}${suffix}`; return `${t('controlLayers.regionalControl')}${suffix}`;
}, [t, validLayerCount]); }, [t, validLayerCount]);
return title; return title;
}; };

View File

@ -25,17 +25,17 @@ import { v4 as uuidv4 } from 'uuid';
import type { import type {
ControlAdapterLayer, ControlAdapterLayer,
ControlLayersState,
DrawingTool, DrawingTool,
IPAdapterLayer, IPAdapterLayer,
Layer, Layer,
MaskedGuidanceLayer, RegionalGuidanceLayer,
RegionalPromptsState,
Tool, Tool,
VectorMaskLine, VectorMaskLine,
VectorMaskRect, VectorMaskRect,
} from './types'; } from './types';
export const initialRegionalPromptsState: RegionalPromptsState = { export const initialControlLayersState: ControlLayersState = {
_version: 1, _version: 1,
selectedLayerId: null, selectedLayerId: null,
brushSize: 100, brushSize: 100,
@ -56,15 +56,15 @@ export const initialRegionalPromptsState: RegionalPromptsState = {
}; };
const isLine = (obj: VectorMaskLine | VectorMaskRect): obj is VectorMaskLine => obj.type === 'vector_mask_line'; const isLine = (obj: VectorMaskLine | VectorMaskRect): obj is VectorMaskLine => obj.type === 'vector_mask_line';
export const isMaskedGuidanceLayer = (layer?: Layer): layer is MaskedGuidanceLayer => export const isMaskedGuidanceLayer = (layer?: Layer): layer is RegionalGuidanceLayer =>
layer?.type === 'masked_guidance_layer'; layer?.type === 'regional_guidance_layer';
export const isControlAdapterLayer = (layer?: Layer): layer is ControlAdapterLayer => export const isControlAdapterLayer = (layer?: Layer): layer is ControlAdapterLayer =>
layer?.type === 'control_adapter_layer'; layer?.type === 'control_adapter_layer';
export const isIPAdapterLayer = (layer?: Layer): layer is IPAdapterLayer => layer?.type === 'ip_adapter_layer'; export const isIPAdapterLayer = (layer?: Layer): layer is IPAdapterLayer => layer?.type === 'ip_adapter_layer';
export const isRenderableLayer = (layer?: Layer): layer is MaskedGuidanceLayer | ControlAdapterLayer => export const isRenderableLayer = (layer?: Layer): layer is RegionalGuidanceLayer | ControlAdapterLayer =>
layer?.type === 'masked_guidance_layer' || layer?.type === 'control_adapter_layer'; layer?.type === 'regional_guidance_layer' || layer?.type === 'control_adapter_layer';
const resetLayer = (layer: Layer) => { const resetLayer = (layer: Layer) => {
if (layer.type === 'masked_guidance_layer') { if (layer.type === 'regional_guidance_layer') {
layer.maskObjects = []; layer.maskObjects = [];
layer.bbox = null; layer.bbox = null;
layer.isEnabled = true; layer.isEnabled = true;
@ -77,22 +77,22 @@ const resetLayer = (layer: Layer) => {
// TODO // TODO
} }
}; };
const getVectorMaskPreviewColor = (state: RegionalPromptsState): RgbColor => { const getVectorMaskPreviewColor = (state: ControlLayersState): RgbColor => {
const vmLayers = state.layers.filter(isMaskedGuidanceLayer); const vmLayers = state.layers.filter(isMaskedGuidanceLayer);
const lastColor = vmLayers[vmLayers.length - 1]?.previewColor; const lastColor = vmLayers[vmLayers.length - 1]?.previewColor;
return LayerColors.next(lastColor); return LayerColors.next(lastColor);
}; };
export const regionalPromptsSlice = createSlice({ export const controlLayersSlice = createSlice({
name: 'regionalPrompts', name: 'controlLayers',
initialState: initialRegionalPromptsState, initialState: initialControlLayersState,
reducers: { reducers: {
//#region All Layers //#region All Layers
maskedGuidanceLayerAdded: (state, action: PayloadAction<{ layerId: string }>) => { maskedGuidanceLayerAdded: (state, action: PayloadAction<{ layerId: string }>) => {
const { layerId } = action.payload; const { layerId } = action.payload;
const layer: MaskedGuidanceLayer = { const layer: RegionalGuidanceLayer = {
id: getMaskedGuidanceLayerId(layerId), id: getMaskedGuidanceLayerId(layerId),
type: 'masked_guidance_layer', type: 'regional_guidance_layer',
isEnabled: true, isEnabled: true,
bbox: null, bbox: null,
bboxNeedsUpdate: false, bboxNeedsUpdate: false,
@ -181,7 +181,7 @@ export const regionalPromptsSlice = createSlice({
if (isRenderableLayer(layer)) { if (isRenderableLayer(layer)) {
layer.bbox = bbox; layer.bbox = bbox;
layer.bboxNeedsUpdate = false; layer.bboxNeedsUpdate = false;
if (bbox === null && layer.type === 'masked_guidance_layer') { if (bbox === null && layer.type === 'regional_guidance_layer') {
// The layer was fully erased, empty its objects to prevent accumulation of invisible objects // The layer was fully erased, empty its objects to prevent accumulation of invisible objects
layer.maskObjects = []; layer.maskObjects = [];
layer.needsPixelBbox = false; layer.needsPixelBbox = false;
@ -247,35 +247,35 @@ export const regionalPromptsSlice = createSlice({
maskLayerPositivePromptChanged: (state, action: PayloadAction<{ layerId: string; prompt: string | null }>) => { maskLayerPositivePromptChanged: (state, action: PayloadAction<{ layerId: string; prompt: string | null }>) => {
const { layerId, prompt } = action.payload; const { layerId, prompt } = action.payload;
const layer = state.layers.find((l) => l.id === layerId); const layer = state.layers.find((l) => l.id === layerId);
if (layer?.type === 'masked_guidance_layer') { if (layer?.type === 'regional_guidance_layer') {
layer.positivePrompt = prompt; layer.positivePrompt = prompt;
} }
}, },
maskLayerNegativePromptChanged: (state, action: PayloadAction<{ layerId: string; prompt: string | null }>) => { maskLayerNegativePromptChanged: (state, action: PayloadAction<{ layerId: string; prompt: string | null }>) => {
const { layerId, prompt } = action.payload; const { layerId, prompt } = action.payload;
const layer = state.layers.find((l) => l.id === layerId); const layer = state.layers.find((l) => l.id === layerId);
if (layer?.type === 'masked_guidance_layer') { if (layer?.type === 'regional_guidance_layer') {
layer.negativePrompt = prompt; layer.negativePrompt = prompt;
} }
}, },
maskLayerIPAdapterAdded: (state, action: PayloadAction<{ layerId: string; ipAdapterId: string }>) => { maskLayerIPAdapterAdded: (state, action: PayloadAction<{ layerId: string; ipAdapterId: string }>) => {
const { layerId, ipAdapterId } = action.payload; const { layerId, ipAdapterId } = action.payload;
const layer = state.layers.find((l) => l.id === layerId); const layer = state.layers.find((l) => l.id === layerId);
if (layer?.type === 'masked_guidance_layer') { if (layer?.type === 'regional_guidance_layer') {
layer.ipAdapterIds.push(ipAdapterId); layer.ipAdapterIds.push(ipAdapterId);
} }
}, },
maskLayerIPAdapterDeleted: (state, action: PayloadAction<{ layerId: string; ipAdapterId: string }>) => { maskLayerIPAdapterDeleted: (state, action: PayloadAction<{ layerId: string; ipAdapterId: string }>) => {
const { layerId, ipAdapterId } = action.payload; const { layerId, ipAdapterId } = action.payload;
const layer = state.layers.find((l) => l.id === layerId); const layer = state.layers.find((l) => l.id === layerId);
if (layer?.type === 'masked_guidance_layer') { if (layer?.type === 'regional_guidance_layer') {
layer.ipAdapterIds = layer.ipAdapterIds.filter((id) => id !== ipAdapterId); layer.ipAdapterIds = layer.ipAdapterIds.filter((id) => id !== ipAdapterId);
} }
}, },
maskLayerPreviewColorChanged: (state, action: PayloadAction<{ layerId: string; color: RgbColor }>) => { maskLayerPreviewColorChanged: (state, action: PayloadAction<{ layerId: string; color: RgbColor }>) => {
const { layerId, color } = action.payload; const { layerId, color } = action.payload;
const layer = state.layers.find((l) => l.id === layerId); const layer = state.layers.find((l) => l.id === layerId);
if (layer?.type === 'masked_guidance_layer') { if (layer?.type === 'regional_guidance_layer') {
layer.previewColor = color; layer.previewColor = color;
} }
}, },
@ -290,7 +290,7 @@ export const regionalPromptsSlice = createSlice({
) => { ) => {
const { layerId, points, tool } = action.payload; const { layerId, points, tool } = action.payload;
const layer = state.layers.find((l) => l.id === layerId); const layer = state.layers.find((l) => l.id === layerId);
if (layer?.type === 'masked_guidance_layer') { if (layer?.type === 'regional_guidance_layer') {
const lineId = getMaskedGuidanceLayerLineId(layer.id, action.meta.uuid); const lineId = getMaskedGuidanceLayerLineId(layer.id, action.meta.uuid);
layer.maskObjects.push({ layer.maskObjects.push({
type: 'vector_mask_line', type: 'vector_mask_line',
@ -315,7 +315,7 @@ export const regionalPromptsSlice = createSlice({
maskLayerPointsAdded: (state, action: PayloadAction<{ layerId: string; point: [number, number] }>) => { maskLayerPointsAdded: (state, action: PayloadAction<{ layerId: string; point: [number, number] }>) => {
const { layerId, point } = action.payload; const { layerId, point } = action.payload;
const layer = state.layers.find((l) => l.id === layerId); const layer = state.layers.find((l) => l.id === layerId);
if (layer?.type === 'masked_guidance_layer') { if (layer?.type === 'regional_guidance_layer') {
const lastLine = layer.maskObjects.findLast(isLine); const lastLine = layer.maskObjects.findLast(isLine);
if (!lastLine) { if (!lastLine) {
return; return;
@ -334,7 +334,7 @@ export const regionalPromptsSlice = createSlice({
return; return;
} }
const layer = state.layers.find((l) => l.id === layerId); const layer = state.layers.find((l) => l.id === layerId);
if (layer?.type === 'masked_guidance_layer') { if (layer?.type === 'regional_guidance_layer') {
const id = getMaskedGuidnaceLayerRectId(layer.id, action.meta.uuid); const id = getMaskedGuidnaceLayerRectId(layer.id, action.meta.uuid);
layer.maskObjects.push({ layer.maskObjects.push({
type: 'vector_mask_rect', type: 'vector_mask_rect',
@ -355,7 +355,7 @@ export const regionalPromptsSlice = createSlice({
) => { ) => {
const { layerId, autoNegative } = action.payload; const { layerId, autoNegative } = action.payload;
const layer = state.layers.find((l) => l.id === layerId); const layer = state.layers.find((l) => l.id === layerId);
if (layer?.type === 'masked_guidance_layer') { if (layer?.type === 'regional_guidance_layer') {
layer.autoNegative = autoNegative; layer.autoNegative = autoNegative;
} }
}, },
@ -546,26 +546,26 @@ export const {
globalMaskLayerOpacityChanged, globalMaskLayerOpacityChanged,
undo, undo,
redo, redo,
} = regionalPromptsSlice.actions; } = controlLayersSlice.actions;
export const selectAllControlAdapterIds = (regionalPrompts: RegionalPromptsState) => export const selectAllControlAdapterIds = (controlLayers: ControlLayersState) =>
regionalPrompts.layers.flatMap((l) => { controlLayers.layers.flatMap((l) => {
if (l.type === 'control_adapter_layer') { if (l.type === 'control_adapter_layer') {
return [l.controlNetId]; return [l.controlNetId];
} }
if (l.type === 'ip_adapter_layer') { if (l.type === 'ip_adapter_layer') {
return [l.ipAdapterId]; return [l.ipAdapterId];
} }
if (l.type === 'masked_guidance_layer') { if (l.type === 'regional_guidance_layer') {
return l.ipAdapterIds; return l.ipAdapterIds;
} }
return []; return [];
}); });
export const selectRegionalPromptsSlice = (state: RootState) => state.regionalPrompts; export const selectControlLayersSlice = (state: RootState) => state.controlLayers;
/* eslint-disable-next-line @typescript-eslint/no-explicit-any */ /* eslint-disable-next-line @typescript-eslint/no-explicit-any */
const migrateRegionalPromptsState = (state: any): any => { const migrateControlLayersState = (state: any): any => {
return state; return state;
}; };
@ -588,14 +588,14 @@ export const BACKGROUND_RECT_ID = 'background_layer.rect';
// Names (aka classes) for Konva layers and objects // Names (aka classes) for Konva layers and objects
export const CONTROLNET_LAYER_NAME = 'control_adapter_layer'; export const CONTROLNET_LAYER_NAME = 'control_adapter_layer';
export const CONTROLNET_LAYER_IMAGE_NAME = 'control_adapter_layer.image'; export const CONTROLNET_LAYER_IMAGE_NAME = 'control_adapter_layer.image';
export const MASKED_GUIDANCE_LAYER_NAME = 'masked_guidance_layer'; export const regional_guidance_layer_NAME = 'regional_guidance_layer';
export const MASKED_GUIDANCE_LAYER_LINE_NAME = 'masked_guidance_layer.line'; export const regional_guidance_layer_LINE_NAME = 'regional_guidance_layer.line';
export const MASKED_GUIDANCE_LAYER_OBJECT_GROUP_NAME = 'masked_guidance_layer.object_group'; export const regional_guidance_layer_OBJECT_GROUP_NAME = 'regional_guidance_layer.object_group';
export const MASKED_GUIDANCE_LAYER_RECT_NAME = 'masked_guidance_layer.rect'; export const regional_guidance_layer_RECT_NAME = 'regional_guidance_layer.rect';
export const LAYER_BBOX_NAME = 'layer.bbox'; export const LAYER_BBOX_NAME = 'layer.bbox';
// Getters for non-singleton layer and object IDs // Getters for non-singleton layer and object IDs
const getMaskedGuidanceLayerId = (layerId: string) => `${MASKED_GUIDANCE_LAYER_NAME}_${layerId}`; const getMaskedGuidanceLayerId = (layerId: string) => `${regional_guidance_layer_NAME}_${layerId}`;
const getMaskedGuidanceLayerLineId = (layerId: string, lineId: string) => `${layerId}.line_${lineId}`; const getMaskedGuidanceLayerLineId = (layerId: string, lineId: string) => `${layerId}.line_${lineId}`;
const getMaskedGuidnaceLayerRectId = (layerId: string, lineId: string) => `${layerId}.rect_${lineId}`; const getMaskedGuidnaceLayerRectId = (layerId: string, lineId: string) => `${layerId}.rect_${lineId}`;
export const getMaskedGuidanceLayerObjectGroupId = (layerId: string, groupId: string) => export const getMaskedGuidanceLayerObjectGroupId = (layerId: string, groupId: string) =>
@ -605,10 +605,10 @@ const getControlNetLayerId = (layerId: string) => `control_adapter_layer_${layer
export const getControlNetLayerImageId = (layerId: string, imageName: string) => `${layerId}.image_${imageName}`; export const getControlNetLayerImageId = (layerId: string, imageName: string) => `${layerId}.image_${imageName}`;
const getIPAdapterLayerId = (layerId: string) => `ip_adapter_layer_${layerId}`; const getIPAdapterLayerId = (layerId: string) => `ip_adapter_layer_${layerId}`;
export const regionalPromptsPersistConfig: PersistConfig<RegionalPromptsState> = { export const controlLayersPersistConfig: PersistConfig<ControlLayersState> = {
name: regionalPromptsSlice.name, name: controlLayersSlice.name,
initialState: initialRegionalPromptsState, initialState: initialControlLayersState,
migrate: migrateRegionalPromptsState, migrate: migrateControlLayersState,
persistDenylist: [], persistDenylist: [],
}; };
@ -626,10 +626,10 @@ const undoableGroupByMatcher = isAnyOf(
const LINE_1 = 'LINE_1'; const LINE_1 = 'LINE_1';
const LINE_2 = 'LINE_2'; const LINE_2 = 'LINE_2';
export const regionalPromptsUndoableConfig: UndoableOptions<RegionalPromptsState, UnknownAction> = { export const controlLayersUndoableConfig: UndoableOptions<ControlLayersState, UnknownAction> = {
limit: 64, limit: 64,
undoType: regionalPromptsSlice.actions.undo.type, undoType: controlLayersSlice.actions.undo.type,
redoType: regionalPromptsSlice.actions.redo.type, redoType: controlLayersSlice.actions.redo.type,
groupBy: (action, state, history) => { groupBy: (action, state, history) => {
// Lines are started with `maskLayerLineAdded` and may have any number of subsequent `maskLayerPointsAdded` events. // Lines are started with `maskLayerLineAdded` and may have any number of subsequent `maskLayerPointsAdded` events.
// We can use a double-buffer-esque trick to group each "logical" line as a single undoable action, without grouping // We can use a double-buffer-esque trick to group each "logical" line as a single undoable action, without grouping
@ -649,7 +649,7 @@ export const regionalPromptsUndoableConfig: UndoableOptions<RegionalPromptsState
}, },
filter: (action, _state, _history) => { filter: (action, _state, _history) => {
// Ignore all actions from other slices // Ignore all actions from other slices
if (!action.type.startsWith(regionalPromptsSlice.name)) { if (!action.type.startsWith(controlLayersSlice.name)) {
return false; return false;
} }
// This action is triggered on state changes, including when we undo. If we do not ignore this action, when we // This action is triggered on state changes, including when we undo. If we do not ignore this action, when we

View File

@ -57,8 +57,8 @@ export type IPAdapterLayer = LayerBase & {
ipAdapterId: string; ipAdapterId: string;
}; };
export type MaskedGuidanceLayer = RenderableLayerBase & { export type RegionalGuidanceLayer = RenderableLayerBase & {
type: 'masked_guidance_layer'; type: 'regional_guidance_layer';
maskObjects: (VectorMaskLine | VectorMaskRect)[]; maskObjects: (VectorMaskLine | VectorMaskRect)[];
positivePrompt: ParameterPositivePrompt | null; positivePrompt: ParameterPositivePrompt | null;
negativePrompt: ParameterNegativePrompt | null; // Up to one text prompt per mask negativePrompt: ParameterNegativePrompt | null; // Up to one text prompt per mask
@ -68,9 +68,9 @@ export type MaskedGuidanceLayer = RenderableLayerBase & {
needsPixelBbox: boolean; // Needs the slower pixel-based bbox calculation - set to true when an there is an eraser object needsPixelBbox: boolean; // Needs the slower pixel-based bbox calculation - set to true when an there is an eraser object
}; };
export type Layer = MaskedGuidanceLayer | ControlAdapterLayer | IPAdapterLayer; export type Layer = RegionalGuidanceLayer | ControlAdapterLayer | IPAdapterLayer;
export type RegionalPromptsState = { export type ControlLayersState = {
_version: 1; _version: 1;
selectedLayerId: string | null; selectedLayerId: string | null;
layers: Layer[]; layers: Layer[];

View File

@ -1,6 +1,6 @@
import openBase64ImageInTab from 'common/util/openBase64ImageInTab'; import openBase64ImageInTab from 'common/util/openBase64ImageInTab';
import { imageDataToDataURL } from 'features/canvas/util/blobToDataURL'; import { imageDataToDataURL } from 'features/canvas/util/blobToDataURL';
import { MASKED_GUIDANCE_LAYER_OBJECT_GROUP_NAME } from 'features/controlLayers/store/regionalPromptsSlice'; import { regional_guidance_layer_OBJECT_GROUP_NAME } from 'features/controlLayers/store/controlLayersSlice';
import Konva from 'konva'; import Konva from 'konva';
import type { Layer as KonvaLayerType } from 'konva/lib/Layer'; import type { Layer as KonvaLayerType } from 'konva/lib/Layer';
import type { IRect } from 'konva/lib/types'; import type { IRect } from 'konva/lib/types';
@ -81,7 +81,7 @@ export const getLayerBboxPixels = (layer: KonvaLayerType, preview: boolean = fal
offscreenStage.add(layerClone); offscreenStage.add(layerClone);
for (const child of layerClone.getChildren()) { for (const child of layerClone.getChildren()) {
if (child.name() === MASKED_GUIDANCE_LAYER_OBJECT_GROUP_NAME) { if (child.name() === regional_guidance_layer_OBJECT_GROUP_NAME) {
// We need to cache the group to ensure it composites out eraser strokes correctly // We need to cache the group to ensure it composites out eraser strokes correctly
child.opacity(1); child.opacity(1);
child.cache(); child.cache();

View File

@ -1,7 +1,7 @@
import { getStore } from 'app/store/nanostores/store'; import { getStore } from 'app/store/nanostores/store';
import openBase64ImageInTab from 'common/util/openBase64ImageInTab'; import openBase64ImageInTab from 'common/util/openBase64ImageInTab';
import { blobToDataURL } from 'features/canvas/util/blobToDataURL'; import { blobToDataURL } from 'features/canvas/util/blobToDataURL';
import { isMaskedGuidanceLayer, MASKED_GUIDANCE_LAYER_NAME } from 'features/controlLayers/store/regionalPromptsSlice'; import { isMaskedGuidanceLayer, regional_guidance_layer_NAME } from 'features/controlLayers/store/controlLayersSlice';
import { renderers } from 'features/controlLayers/util/renderers'; import { renderers } from 'features/controlLayers/util/renderers';
import Konva from 'konva'; import Konva from 'konva';
import { assert } from 'tsafe'; import { assert } from 'tsafe';
@ -17,14 +17,14 @@ export const getRegionalPromptLayerBlobs = async (
preview: boolean = false preview: boolean = false
): Promise<Record<string, Blob>> => { ): Promise<Record<string, Blob>> => {
const state = getStore().getState(); const state = getStore().getState();
const { layers } = state.regionalPrompts.present; const { layers } = state.controlLayers.present;
const { width, height } = state.regionalPrompts.present.size; const { width, height } = state.controlLayers.present.size;
const reduxLayers = layers.filter(isMaskedGuidanceLayer); const reduxLayers = layers.filter(isMaskedGuidanceLayer);
const container = document.createElement('div'); const container = document.createElement('div');
const stage = new Konva.Stage({ container, width, height }); const stage = new Konva.Stage({ container, width, height });
renderers.renderLayers(stage, reduxLayers, 1, 'brush'); renderers.renderLayers(stage, reduxLayers, 1, 'brush');
const konvaLayers = stage.find<Konva.Layer>(`.${MASKED_GUIDANCE_LAYER_NAME}`); const konvaLayers = stage.find<Konva.Layer>(`.${regional_guidance_layer_NAME}`);
const blobs: Record<string, Blob> = {}; const blobs: Record<string, Blob> = {};
// First remove all layers // First remove all layers

View File

@ -14,21 +14,21 @@ import {
isMaskedGuidanceLayer, isMaskedGuidanceLayer,
isRenderableLayer, isRenderableLayer,
LAYER_BBOX_NAME, LAYER_BBOX_NAME,
MASKED_GUIDANCE_LAYER_LINE_NAME, regional_guidance_layer_LINE_NAME,
MASKED_GUIDANCE_LAYER_NAME, regional_guidance_layer_NAME,
MASKED_GUIDANCE_LAYER_OBJECT_GROUP_NAME, regional_guidance_layer_OBJECT_GROUP_NAME,
MASKED_GUIDANCE_LAYER_RECT_NAME, regional_guidance_layer_RECT_NAME,
TOOL_PREVIEW_BRUSH_BORDER_INNER_ID, TOOL_PREVIEW_BRUSH_BORDER_INNER_ID,
TOOL_PREVIEW_BRUSH_BORDER_OUTER_ID, TOOL_PREVIEW_BRUSH_BORDER_OUTER_ID,
TOOL_PREVIEW_BRUSH_FILL_ID, TOOL_PREVIEW_BRUSH_FILL_ID,
TOOL_PREVIEW_BRUSH_GROUP_ID, TOOL_PREVIEW_BRUSH_GROUP_ID,
TOOL_PREVIEW_LAYER_ID, TOOL_PREVIEW_LAYER_ID,
TOOL_PREVIEW_RECT_ID, TOOL_PREVIEW_RECT_ID,
} from 'features/controlLayers/store/regionalPromptsSlice'; } from 'features/controlLayers/store/controlLayersSlice';
import type { import type {
ControlAdapterLayer, ControlAdapterLayer,
Layer, Layer,
MaskedGuidanceLayer, RegionalGuidanceLayer,
Tool, Tool,
VectorMaskLine, VectorMaskLine,
VectorMaskRect, VectorMaskRect,
@ -52,10 +52,10 @@ const STAGE_BG_DATAURL =
const mapId = (object: { id: string }) => object.id; const mapId = (object: { id: string }) => object.id;
const selectRenderableLayers = (n: Konva.Node) => const selectRenderableLayers = (n: Konva.Node) =>
n.name() === MASKED_GUIDANCE_LAYER_NAME || n.name() === CONTROLNET_LAYER_NAME; n.name() === regional_guidance_layer_NAME || n.name() === CONTROLNET_LAYER_NAME;
const selectVectorMaskObjects = (node: Konva.Node) => { const selectVectorMaskObjects = (node: Konva.Node) => {
return node.name() === MASKED_GUIDANCE_LAYER_LINE_NAME || node.name() === MASKED_GUIDANCE_LAYER_RECT_NAME; return node.name() === regional_guidance_layer_LINE_NAME || node.name() === regional_guidance_layer_RECT_NAME;
}; };
/** /**
@ -140,12 +140,12 @@ const renderToolPreview = (
isMouseOver: boolean, isMouseOver: boolean,
brushSize: number brushSize: number
) => { ) => {
const layerCount = stage.find(`.${MASKED_GUIDANCE_LAYER_NAME}`).length; const layerCount = stage.find(`.${regional_guidance_layer_NAME}`).length;
// Update the stage's pointer style // Update the stage's pointer style
if (layerCount === 0) { if (layerCount === 0) {
// We have no layers, so we should not render any tool // We have no layers, so we should not render any tool
stage.container().style.cursor = 'default'; stage.container().style.cursor = 'default';
} else if (selectedLayerType !== 'masked_guidance_layer') { } else if (selectedLayerType !== 'regional_guidance_layer') {
// Non-mask-guidance layers don't have tools // Non-mask-guidance layers don't have tools
stage.container().style.cursor = 'not-allowed'; stage.container().style.cursor = 'not-allowed';
} else if (tool === 'move') { } else if (tool === 'move') {
@ -226,13 +226,13 @@ const renderToolPreview = (
*/ */
const createMaskedGuidanceLayer = ( const createMaskedGuidanceLayer = (
stage: Konva.Stage, stage: Konva.Stage,
reduxLayer: MaskedGuidanceLayer, reduxLayer: RegionalGuidanceLayer,
onLayerPosChanged?: (layerId: string, x: number, y: number) => void onLayerPosChanged?: (layerId: string, x: number, y: number) => void
) => { ) => {
// This layer hasn't been added to the konva state yet // This layer hasn't been added to the konva state yet
const konvaLayer = new Konva.Layer({ const konvaLayer = new Konva.Layer({
id: reduxLayer.id, id: reduxLayer.id,
name: MASKED_GUIDANCE_LAYER_NAME, name: regional_guidance_layer_NAME,
draggable: true, draggable: true,
dragDistance: 0, dragDistance: 0,
}); });
@ -265,7 +265,7 @@ const createMaskedGuidanceLayer = (
// The object group holds all of the layer's objects (e.g. lines and rects) // The object group holds all of the layer's objects (e.g. lines and rects)
const konvaObjectGroup = new Konva.Group({ const konvaObjectGroup = new Konva.Group({
id: getMaskedGuidanceLayerObjectGroupId(reduxLayer.id, uuidv4()), id: getMaskedGuidanceLayerObjectGroupId(reduxLayer.id, uuidv4()),
name: MASKED_GUIDANCE_LAYER_OBJECT_GROUP_NAME, name: regional_guidance_layer_OBJECT_GROUP_NAME,
listening: false, listening: false,
}); });
konvaLayer.add(konvaObjectGroup); konvaLayer.add(konvaObjectGroup);
@ -284,7 +284,7 @@ const createVectorMaskLine = (reduxObject: VectorMaskLine, konvaGroup: Konva.Gro
const vectorMaskLine = new Konva.Line({ const vectorMaskLine = new Konva.Line({
id: reduxObject.id, id: reduxObject.id,
key: reduxObject.id, key: reduxObject.id,
name: MASKED_GUIDANCE_LAYER_LINE_NAME, name: regional_guidance_layer_LINE_NAME,
strokeWidth: reduxObject.strokeWidth, strokeWidth: reduxObject.strokeWidth,
tension: 0, tension: 0,
lineCap: 'round', lineCap: 'round',
@ -306,7 +306,7 @@ const createVectorMaskRect = (reduxObject: VectorMaskRect, konvaGroup: Konva.Gro
const vectorMaskRect = new Konva.Rect({ const vectorMaskRect = new Konva.Rect({
id: reduxObject.id, id: reduxObject.id,
key: reduxObject.id, key: reduxObject.id,
name: MASKED_GUIDANCE_LAYER_RECT_NAME, name: regional_guidance_layer_RECT_NAME,
x: reduxObject.x, x: reduxObject.x,
y: reduxObject.y, y: reduxObject.y,
width: reduxObject.width, width: reduxObject.width,
@ -327,7 +327,7 @@ const createVectorMaskRect = (reduxObject: VectorMaskRect, konvaGroup: Konva.Gro
*/ */
const renderMaskedGuidanceLayer = ( const renderMaskedGuidanceLayer = (
stage: Konva.Stage, stage: Konva.Stage,
reduxLayer: MaskedGuidanceLayer, reduxLayer: RegionalGuidanceLayer,
globalMaskLayerOpacity: number, globalMaskLayerOpacity: number,
tool: Tool, tool: Tool,
onLayerPosChanged?: (layerId: string, x: number, y: number) => void onLayerPosChanged?: (layerId: string, x: number, y: number) => void
@ -345,7 +345,7 @@ const renderMaskedGuidanceLayer = (
// Convert the color to a string, stripping the alpha - the object group will handle opacity. // Convert the color to a string, stripping the alpha - the object group will handle opacity.
const rgbColor = rgbColorToString(reduxLayer.previewColor); const rgbColor = rgbColorToString(reduxLayer.previewColor);
const konvaObjectGroup = konvaLayer.findOne<Konva.Group>(`.${MASKED_GUIDANCE_LAYER_OBJECT_GROUP_NAME}`); const konvaObjectGroup = konvaLayer.findOne<Konva.Group>(`.${regional_guidance_layer_OBJECT_GROUP_NAME}`);
assert(konvaObjectGroup, `Object group not found for layer ${reduxLayer.id}`); assert(konvaObjectGroup, `Object group not found for layer ${reduxLayer.id}`);
// We use caching to handle "global" layer opacity, but caching is expensive and we should only do it when required. // We use caching to handle "global" layer opacity, but caching is expensive and we should only do it when required.
@ -592,7 +592,7 @@ const renderBbox = (
} }
for (const reduxLayer of reduxLayers) { for (const reduxLayer of reduxLayers) {
if (reduxLayer.type === 'masked_guidance_layer') { if (reduxLayer.type === 'regional_guidance_layer') {
const konvaLayer = stage.findOne<Konva.Layer>(`#${reduxLayer.id}`); const konvaLayer = stage.findOne<Konva.Layer>(`#${reduxLayer.id}`);
assert(konvaLayer, `Layer ${reduxLayer.id} not found in stage`); assert(konvaLayer, `Layer ${reduxLayer.id} not found in stage`);

View File

@ -12,7 +12,7 @@ import {
positivePrompt2Changed, positivePrompt2Changed,
positivePromptChanged, positivePromptChanged,
widthChanged, widthChanged,
} from 'features/controlLayers/store/regionalPromptsSlice'; } from 'features/controlLayers/store/controlLayersSlice';
import { setHrfEnabled, setHrfMethod, setHrfStrength } from 'features/hrf/store/hrfSlice'; import { setHrfEnabled, setHrfMethod, setHrfStrength } from 'features/hrf/store/hrfSlice';
import type { LoRA } from 'features/lora/store/loraSlice'; import type { LoRA } from 'features/lora/store/loraSlice';
import { loraRecalled, lorasReset } from 'features/lora/store/loraSlice'; import { loraRecalled, lorasReset } from 'features/lora/store/loraSlice';

View File

@ -1,7 +1,7 @@
import { getStore } from 'app/store/nanostores/store'; import { getStore } from 'app/store/nanostores/store';
import type { RootState } from 'app/store/store'; import type { RootState } from 'app/store/store';
import { selectAllIPAdapters } from 'features/controlAdapters/store/controlAdaptersSlice'; import { selectAllIPAdapters } from 'features/controlAdapters/store/controlAdaptersSlice';
import { isMaskedGuidanceLayer } from 'features/controlLayers/store/regionalPromptsSlice'; import { isMaskedGuidanceLayer } from 'features/controlLayers/store/controlLayersSlice';
import { getRegionalPromptLayerBlobs } from 'features/controlLayers/util/getLayerBlobs'; import { getRegionalPromptLayerBlobs } from 'features/controlLayers/util/getLayerBlobs';
import { import {
IP_ADAPTER_COLLECT, IP_ADAPTER_COLLECT,
@ -20,13 +20,13 @@ import { imagesApi } from 'services/api/endpoints/images';
import type { CollectInvocation, Edge, IPAdapterInvocation, NonNullableGraph, S } from 'services/api/types'; import type { CollectInvocation, Edge, IPAdapterInvocation, NonNullableGraph, S } from 'services/api/types';
import { assert } from 'tsafe'; import { assert } from 'tsafe';
export const addRegionalPromptsToGraph = async (state: RootState, graph: NonNullableGraph, denoiseNodeId: string) => { export const addControlLayersToGraph = async (state: RootState, graph: NonNullableGraph, denoiseNodeId: string) => {
if (!state.regionalPrompts.present.isEnabled) { if (!state.controlLayers.present.isEnabled) {
return; return;
} }
const { dispatch } = getStore(); const { dispatch } = getStore();
const isSDXL = state.generation.model?.base === 'sdxl'; const isSDXL = state.generation.model?.base === 'sdxl';
const layers = state.regionalPrompts.present.layers const layers = state.controlLayers.present.layers
// Only support vector mask layers now // Only support vector mask layers now
// TODO: Image masks // TODO: Image masks
.filter(isMaskedGuidanceLayer) .filter(isMaskedGuidanceLayer)

View File

@ -1,7 +1,7 @@
import type { RootState } from 'app/store/store'; import type { RootState } from 'app/store/store';
import { selectValidControlNets } from 'features/controlAdapters/store/controlAdaptersSlice'; import { selectValidControlNets } from 'features/controlAdapters/store/controlAdaptersSlice';
import type { ControlAdapterProcessorType, ControlNetConfig } from 'features/controlAdapters/store/types'; import type { ControlAdapterProcessorType, ControlNetConfig } from 'features/controlAdapters/store/types';
import { isControlAdapterLayer } from 'features/controlLayers/store/regionalPromptsSlice'; import { isControlAdapterLayer } from 'features/controlLayers/store/controlLayersSlice';
import type { ImageField } from 'features/nodes/types/common'; import type { ImageField } from 'features/nodes/types/common';
import { activeTabNameSelector } from 'features/ui/store/uiSelectors'; import { activeTabNameSelector } from 'features/ui/store/uiSelectors';
import { differenceWith, intersectionWith } from 'lodash-es'; import { differenceWith, intersectionWith } from 'lodash-es';
@ -36,7 +36,7 @@ const getControlNets = (state: RootState) => {
if (activeTabName === 'txt2img') { if (activeTabName === 'txt2img') {
// Add only the cnets that are used in control layers // Add only the cnets that are used in control layers
// Collect all ControlNet ids for enabled ControlNet layers // Collect all ControlNet ids for enabled ControlNet layers
const layerControlNetIds = state.regionalPrompts.present.layers const layerControlNetIds = state.controlLayers.present.layers
.filter(isControlAdapterLayer) .filter(isControlAdapterLayer)
.filter((l) => l.isEnabled) .filter((l) => l.isEnabled)
.map((l) => l.controlNetId); .map((l) => l.controlNetId);
@ -44,7 +44,7 @@ const getControlNets = (state: RootState) => {
} else { } else {
// Else, we want to exclude the cnets that are used in control layers // Else, we want to exclude the cnets that are used in control layers
// Collect all ControlNet ids for all ControlNet layers // Collect all ControlNet ids for all ControlNet layers
const layerControlNetIds = state.regionalPrompts.present.layers const layerControlNetIds = state.controlLayers.present.layers
.filter(isControlAdapterLayer) .filter(isControlAdapterLayer)
.map((l) => l.controlNetId); .map((l) => l.controlNetId);
return differenceWith(validControlNets, layerControlNetIds, (a, b) => a.id === b); return differenceWith(validControlNets, layerControlNetIds, (a, b) => a.id === b);

View File

@ -110,7 +110,7 @@ export const addHrfToGraph = (state: RootState, graph: NonNullableGraph): void =
const { vae, seamlessXAxis, seamlessYAxis } = state.generation; const { vae, seamlessXAxis, seamlessYAxis } = state.generation;
const { hrfStrength, hrfEnabled, hrfMethod } = state.hrf; const { hrfStrength, hrfEnabled, hrfMethod } = state.hrf;
const { width, height } = state.regionalPrompts.present.size; const { width, height } = state.controlLayers.present.size;
const isAutoVae = !vae; const isAutoVae = !vae;
const isSeamlessEnabled = seamlessXAxis || seamlessYAxis; const isSeamlessEnabled = seamlessXAxis || seamlessYAxis;
const optimalDimension = selectOptimalDimension(state); const optimalDimension = selectOptimalDimension(state);

View File

@ -1,7 +1,7 @@
import type { RootState } from 'app/store/store'; import type { RootState } from 'app/store/store';
import { selectValidIPAdapters } from 'features/controlAdapters/store/controlAdaptersSlice'; import { selectValidIPAdapters } from 'features/controlAdapters/store/controlAdaptersSlice';
import type { IPAdapterConfig } from 'features/controlAdapters/store/types'; import type { IPAdapterConfig } from 'features/controlAdapters/store/types';
import { isIPAdapterLayer, isMaskedGuidanceLayer } from 'features/controlLayers/store/regionalPromptsSlice'; import { isIPAdapterLayer, isMaskedGuidanceLayer } from 'features/controlLayers/store/controlLayersSlice';
import type { ImageField } from 'features/nodes/types/common'; import type { ImageField } from 'features/nodes/types/common';
import { activeTabNameSelector } from 'features/ui/store/uiSelectors'; import { activeTabNameSelector } from 'features/ui/store/uiSelectors';
import { differenceWith, intersectionWith } from 'lodash-es'; import { differenceWith, intersectionWith } from 'lodash-es';
@ -27,7 +27,7 @@ const getIPAdapters = (state: RootState) => {
}); });
// Masked IP adapters are handled in the graph helper for regional control - skip them here // Masked IP adapters are handled in the graph helper for regional control - skip them here
const maskedIPAdapterIds = state.regionalPrompts.present.layers const maskedIPAdapterIds = state.controlLayers.present.layers
.filter(isMaskedGuidanceLayer) .filter(isMaskedGuidanceLayer)
.map((l) => l.ipAdapterIds) .map((l) => l.ipAdapterIds)
.flat(); .flat();
@ -40,7 +40,7 @@ const getIPAdapters = (state: RootState) => {
if (activeTabName === 'txt2img') { if (activeTabName === 'txt2img') {
// If we are on the t2i tab, we only want to add the IP adapters that are used in unmasked IP Adapter layers // If we are on the t2i tab, we only want to add the IP adapters that are used in unmasked IP Adapter layers
// Collect all IP Adapter ids for enabled IP adapter layers // Collect all IP Adapter ids for enabled IP adapter layers
const layerIPAdapterIds = state.regionalPrompts.present.layers const layerIPAdapterIds = state.controlLayers.present.layers
.filter(isIPAdapterLayer) .filter(isIPAdapterLayer)
.filter((l) => l.isEnabled) .filter((l) => l.isEnabled)
.map((l) => l.ipAdapterId); .map((l) => l.ipAdapterId);
@ -48,7 +48,7 @@ const getIPAdapters = (state: RootState) => {
} else { } else {
// Else, we want to exclude the IP adapters that are used in IP Adapter layers // Else, we want to exclude the IP adapters that are used in IP Adapter layers
// Collect all IP Adapter ids for enabled IP adapter layers // Collect all IP Adapter ids for enabled IP adapter layers
const layerIPAdapterIds = state.regionalPrompts.present.layers.filter(isIPAdapterLayer).map((l) => l.ipAdapterId); const layerIPAdapterIds = state.controlLayers.present.layers.filter(isIPAdapterLayer).map((l) => l.ipAdapterId);
return differenceWith(nonMaskedIPAdapters, layerIPAdapterIds, (a, b) => a.id === b); return differenceWith(nonMaskedIPAdapters, layerIPAdapterIds, (a, b) => a.id === b);
} }
}; };

View File

@ -1,7 +1,7 @@
import type { RootState } from 'app/store/store'; import type { RootState } from 'app/store/store';
import { selectValidT2IAdapters } from 'features/controlAdapters/store/controlAdaptersSlice'; import { selectValidT2IAdapters } from 'features/controlAdapters/store/controlAdaptersSlice';
import type { ControlAdapterProcessorType, T2IAdapterConfig } from 'features/controlAdapters/store/types'; import type { ControlAdapterProcessorType, T2IAdapterConfig } from 'features/controlAdapters/store/types';
import { isControlAdapterLayer } from 'features/controlLayers/store/regionalPromptsSlice'; import { isControlAdapterLayer } from 'features/controlLayers/store/controlLayersSlice';
import type { ImageField } from 'features/nodes/types/common'; import type { ImageField } from 'features/nodes/types/common';
import { activeTabNameSelector } from 'features/ui/store/uiSelectors'; import { activeTabNameSelector } from 'features/ui/store/uiSelectors';
import { differenceWith, intersectionWith } from 'lodash-es'; import { differenceWith, intersectionWith } from 'lodash-es';
@ -36,14 +36,14 @@ const getT2IAdapters = (state: RootState) => {
if (activeTabName === 'txt2img') { if (activeTabName === 'txt2img') {
// Add only the T2Is that are used in control layers // Add only the T2Is that are used in control layers
// Collect all ids for enabled control adapter layers // Collect all ids for enabled control adapter layers
const layerControlAdapterIds = state.regionalPrompts.present.layers const layerControlAdapterIds = state.controlLayers.present.layers
.filter(isControlAdapterLayer) .filter(isControlAdapterLayer)
.filter((l) => l.isEnabled) .filter((l) => l.isEnabled)
.map((l) => l.controlNetId); .map((l) => l.controlNetId);
return intersectionWith(validT2IAdapters, layerControlAdapterIds, (a, b) => a.id === b); return intersectionWith(validT2IAdapters, layerControlAdapterIds, (a, b) => a.id === b);
} else { } else {
// Else, we want to exclude the T2Is that are used in control layers // Else, we want to exclude the T2Is that are used in control layers
const layerControlAdapterIds = state.regionalPrompts.present.layers const layerControlAdapterIds = state.controlLayers.present.layers
.filter(isControlAdapterLayer) .filter(isControlAdapterLayer)
.map((l) => l.controlNetId); .map((l) => l.controlNetId);
return differenceWith(validT2IAdapters, layerControlAdapterIds, (a, b) => a.id === b); return differenceWith(validT2IAdapters, layerControlAdapterIds, (a, b) => a.id === b);

View File

@ -55,7 +55,7 @@ export const buildCanvasImageToImageGraph = async (
seamlessXAxis, seamlessXAxis,
seamlessYAxis, seamlessYAxis,
} = state.generation; } = state.generation;
const { positivePrompt, negativePrompt } = state.regionalPrompts.present; const { positivePrompt, negativePrompt } = state.controlLayers.present;
// The bounding box determines width and height, not the width and height params // The bounding box determines width and height, not the width and height params
const { width, height } = state.canvas.boundingBoxDimensions; const { width, height } = state.canvas.boundingBoxDimensions;

View File

@ -64,7 +64,7 @@ export const buildCanvasInpaintGraph = async (
canvasCoherenceEdgeSize, canvasCoherenceEdgeSize,
maskBlur, maskBlur,
} = state.generation; } = state.generation;
const { positivePrompt, negativePrompt } = state.regionalPrompts.present; const { positivePrompt, negativePrompt } = state.controlLayers.present;
if (!model) { if (!model) {
log.error('No model found in state'); log.error('No model found in state');

View File

@ -76,7 +76,7 @@ export const buildCanvasOutpaintGraph = async (
canvasCoherenceEdgeSize, canvasCoherenceEdgeSize,
maskBlur, maskBlur,
} = state.generation; } = state.generation;
const { positivePrompt, negativePrompt } = state.regionalPrompts.present; const { positivePrompt, negativePrompt } = state.controlLayers.present;
if (!model) { if (!model) {
log.error('No model found in state'); log.error('No model found in state');

View File

@ -55,7 +55,7 @@ export const buildCanvasSDXLImageToImageGraph = async (
seamlessYAxis, seamlessYAxis,
img2imgStrength: strength, img2imgStrength: strength,
} = state.generation; } = state.generation;
const { positivePrompt, negativePrompt } = state.regionalPrompts.present; const { positivePrompt, negativePrompt } = state.controlLayers.present;
const { refinerModel, refinerStart } = state.sdxl; const { refinerModel, refinerStart } = state.sdxl;

View File

@ -64,7 +64,7 @@ export const buildCanvasSDXLInpaintGraph = async (
canvasCoherenceEdgeSize, canvasCoherenceEdgeSize,
maskBlur, maskBlur,
} = state.generation; } = state.generation;
const { positivePrompt, negativePrompt } = state.regionalPrompts.present; const { positivePrompt, negativePrompt } = state.controlLayers.present;
const { refinerModel, refinerStart } = state.sdxl; const { refinerModel, refinerStart } = state.sdxl;

View File

@ -76,7 +76,7 @@ export const buildCanvasSDXLOutpaintGraph = async (
canvasCoherenceEdgeSize, canvasCoherenceEdgeSize,
maskBlur, maskBlur,
} = state.generation; } = state.generation;
const { positivePrompt, negativePrompt } = state.regionalPrompts.present; const { positivePrompt, negativePrompt } = state.controlLayers.present;
const { refinerModel, refinerStart } = state.sdxl; const { refinerModel, refinerStart } = state.sdxl;

View File

@ -44,7 +44,7 @@ export const buildCanvasSDXLTextToImageGraph = async (state: RootState): Promise
seamlessXAxis, seamlessXAxis,
seamlessYAxis, seamlessYAxis,
} = state.generation; } = state.generation;
const { positivePrompt, negativePrompt } = state.regionalPrompts.present; const { positivePrompt, negativePrompt } = state.controlLayers.present;
// The bounding box determines width and height, not the width and height params // The bounding box determines width and height, not the width and height params
const { width, height } = state.canvas.boundingBoxDimensions; const { width, height } = state.canvas.boundingBoxDimensions;

View File

@ -44,7 +44,7 @@ export const buildCanvasTextToImageGraph = async (state: RootState): Promise<Non
seamlessXAxis, seamlessXAxis,
seamlessYAxis, seamlessYAxis,
} = state.generation; } = state.generation;
const { positivePrompt, negativePrompt } = state.regionalPrompts.present; const { positivePrompt, negativePrompt } = state.controlLayers.present;
// The bounding box determines width and height, not the width and height params // The bounding box determines width and height, not the width and height params
const { width, height } = state.canvas.boundingBoxDimensions; const { width, height } = state.canvas.boundingBoxDimensions;

View File

@ -10,7 +10,7 @@ import { getHasMetadata, removeMetadata } from './metadata';
export const prepareLinearUIBatch = (state: RootState, graph: NonNullableGraph, prepend: boolean): BatchConfig => { export const prepareLinearUIBatch = (state: RootState, graph: NonNullableGraph, prepend: boolean): BatchConfig => {
const { iterations, model, shouldRandomizeSeed, seed } = state.generation; const { iterations, model, shouldRandomizeSeed, seed } = state.generation;
const { shouldConcatPrompts } = state.regionalPrompts.present; const { shouldConcatPrompts } = state.controlLayers.present;
const { prompts, seedBehaviour } = state.dynamicPrompts; const { prompts, seedBehaviour } = state.dynamicPrompts;
const data: Batch['data'] = []; const data: Batch['data'] = [];

View File

@ -53,8 +53,8 @@ export const buildLinearImageToImageGraph = async (state: RootState): Promise<No
seamlessXAxis, seamlessXAxis,
seamlessYAxis, seamlessYAxis,
} = state.generation; } = state.generation;
const { positivePrompt, negativePrompt } = state.regionalPrompts.present; const { positivePrompt, negativePrompt } = state.controlLayers.present;
const { width, height } = state.regionalPrompts.present.size; const { width, height } = state.controlLayers.present.size;
/** /**
* The easiest way to build linear graphs is to do it in the node editor, then copy and paste the * The easiest way to build linear graphs is to do it in the node editor, then copy and paste the

View File

@ -53,8 +53,8 @@ export const buildLinearSDXLImageToImageGraph = async (state: RootState): Promis
seamlessYAxis, seamlessYAxis,
img2imgStrength: strength, img2imgStrength: strength,
} = state.generation; } = state.generation;
const { positivePrompt, negativePrompt } = state.regionalPrompts.present; const { positivePrompt, negativePrompt } = state.controlLayers.present;
const { width, height } = state.regionalPrompts.present.size; const { width, height } = state.controlLayers.present.size;
const { refinerModel, refinerStart } = state.sdxl; const { refinerModel, refinerStart } = state.sdxl;

View File

@ -1,7 +1,7 @@
import { logger } from 'app/logging/logger'; import { logger } from 'app/logging/logger';
import type { RootState } from 'app/store/store'; import type { RootState } from 'app/store/store';
import { fetchModelConfigWithTypeGuard } from 'features/metadata/util/modelFetchingHelpers'; import { fetchModelConfigWithTypeGuard } from 'features/metadata/util/modelFetchingHelpers';
import { addRegionalPromptsToGraph } from 'features/nodes/util/graph/addRegionalPromptsToGraph'; import { addControlLayersToGraph } from 'features/nodes/util/graph/addControlLayersToGraph';
import { isNonRefinerMainModelConfig, type NonNullableGraph } from 'services/api/types'; import { isNonRefinerMainModelConfig, type NonNullableGraph } from 'services/api/types';
import { addControlNetToLinearGraph } from './addControlNetToLinearGraph'; import { addControlNetToLinearGraph } from './addControlNetToLinearGraph';
@ -41,8 +41,8 @@ export const buildLinearSDXLTextToImageGraph = async (state: RootState): Promise
seamlessXAxis, seamlessXAxis,
seamlessYAxis, seamlessYAxis,
} = state.generation; } = state.generation;
const { positivePrompt, negativePrompt } = state.regionalPrompts.present; const { positivePrompt, negativePrompt } = state.controlLayers.present;
const { width, height } = state.regionalPrompts.present.size; const { width, height } = state.controlLayers.present.size;
const { refinerModel, refinerStart } = state.sdxl; const { refinerModel, refinerStart } = state.sdxl;
@ -272,7 +272,7 @@ export const buildLinearSDXLTextToImageGraph = async (state: RootState): Promise
await addT2IAdaptersToLinearGraph(state, graph, SDXL_DENOISE_LATENTS); await addT2IAdaptersToLinearGraph(state, graph, SDXL_DENOISE_LATENTS);
await addRegionalPromptsToGraph(state, graph, SDXL_DENOISE_LATENTS); await addControlLayersToGraph(state, graph, SDXL_DENOISE_LATENTS);
// NSFW & watermark - must be last thing added to graph // NSFW & watermark - must be last thing added to graph
if (state.system.shouldUseNSFWChecker) { if (state.system.shouldUseNSFWChecker) {

View File

@ -1,7 +1,7 @@
import { logger } from 'app/logging/logger'; import { logger } from 'app/logging/logger';
import type { RootState } from 'app/store/store'; import type { RootState } from 'app/store/store';
import { fetchModelConfigWithTypeGuard } from 'features/metadata/util/modelFetchingHelpers'; import { fetchModelConfigWithTypeGuard } from 'features/metadata/util/modelFetchingHelpers';
import { addRegionalPromptsToGraph } from 'features/nodes/util/graph/addRegionalPromptsToGraph'; import { addControlLayersToGraph } from 'features/nodes/util/graph/addControlLayersToGraph';
import { getBoardField, getIsIntermediate } from 'features/nodes/util/graph/graphBuilderUtils'; import { getBoardField, getIsIntermediate } from 'features/nodes/util/graph/graphBuilderUtils';
import { isNonRefinerMainModelConfig, type NonNullableGraph } from 'services/api/types'; import { isNonRefinerMainModelConfig, type NonNullableGraph } from 'services/api/types';
@ -42,8 +42,8 @@ export const buildLinearTextToImageGraph = async (state: RootState): Promise<Non
seamlessYAxis, seamlessYAxis,
seed, seed,
} = state.generation; } = state.generation;
const { positivePrompt, negativePrompt } = state.regionalPrompts.present; const { positivePrompt, negativePrompt } = state.controlLayers.present;
const { width, height } = state.regionalPrompts.present.size; const { width, height } = state.controlLayers.present.size;
const use_cpu = shouldUseCpuNoise; const use_cpu = shouldUseCpuNoise;
@ -254,7 +254,7 @@ export const buildLinearTextToImageGraph = async (state: RootState): Promise<Non
await addT2IAdaptersToLinearGraph(state, graph, DENOISE_LATENTS); await addT2IAdaptersToLinearGraph(state, graph, DENOISE_LATENTS);
await addRegionalPromptsToGraph(state, graph, DENOISE_LATENTS); await addControlLayersToGraph(state, graph, DENOISE_LATENTS);
// High resolution fix. // High resolution fix.
if (state.hrf.hrfEnabled) { if (state.hrf.hrfEnabled) {

View File

@ -18,7 +18,7 @@ export const getBoardField = (state: RootState): BoardField | undefined => {
*/ */
export const getSDXLStylePrompts = (state: RootState): { positiveStylePrompt: string; negativeStylePrompt: string } => { export const getSDXLStylePrompts = (state: RootState): { positiveStylePrompt: string; negativeStylePrompt: string } => {
const { positivePrompt, negativePrompt, positivePrompt2, negativePrompt2, shouldConcatPrompts } = const { positivePrompt, negativePrompt, positivePrompt2, negativePrompt2, shouldConcatPrompts } =
state.regionalPrompts.present; state.controlLayers.present;
return { return {
positiveStylePrompt: shouldConcatPrompts ? positivePrompt : positivePrompt2, positiveStylePrompt: shouldConcatPrompts ? positivePrompt : positivePrompt2,

View File

@ -1,6 +1,6 @@
import { Box, Textarea } from '@invoke-ai/ui-library'; import { Box, Textarea } from '@invoke-ai/ui-library';
import { useAppDispatch, useAppSelector } from 'app/store/storeHooks'; import { useAppDispatch, useAppSelector } from 'app/store/storeHooks';
import { negativePromptChanged } from 'features/controlLayers/store/regionalPromptsSlice'; import { negativePromptChanged } from 'features/controlLayers/store/controlLayersSlice';
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';
@ -10,7 +10,7 @@ import { useTranslation } from 'react-i18next';
export const ParamNegativePrompt = memo(() => { export const ParamNegativePrompt = memo(() => {
const dispatch = useAppDispatch(); const dispatch = useAppDispatch();
const prompt = useAppSelector((s) => s.regionalPrompts.present.negativePrompt); const prompt = useAppSelector((s) => s.controlLayers.present.negativePrompt);
const textareaRef = useRef<HTMLTextAreaElement>(null); const textareaRef = useRef<HTMLTextAreaElement>(null);
const { t } = useTranslation(); const { t } = useTranslation();
const _onChange = useCallback( const _onChange = useCallback(

View File

@ -1,6 +1,6 @@
import { Box, Textarea } from '@invoke-ai/ui-library'; import { Box, Textarea } from '@invoke-ai/ui-library';
import { useAppDispatch, useAppSelector } from 'app/store/storeHooks'; import { useAppDispatch, useAppSelector } from 'app/store/storeHooks';
import { positivePromptChanged } from 'features/controlLayers/store/regionalPromptsSlice'; import { positivePromptChanged } from 'features/controlLayers/store/controlLayersSlice';
import { ShowDynamicPromptsPreviewButton } from 'features/dynamicPrompts/components/ShowDynamicPromptsPreviewButton'; import { ShowDynamicPromptsPreviewButton } from 'features/dynamicPrompts/components/ShowDynamicPromptsPreviewButton';
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';
@ -14,7 +14,7 @@ import { useTranslation } from 'react-i18next';
export const ParamPositivePrompt = memo(() => { export const ParamPositivePrompt = memo(() => {
const dispatch = useAppDispatch(); const dispatch = useAppDispatch();
const prompt = useAppSelector((s) => s.regionalPrompts.present.positivePrompt); const prompt = useAppSelector((s) => s.controlLayers.present.positivePrompt);
const baseModel = useAppSelector((s) => s.generation.model)?.base; const baseModel = useAppSelector((s) => s.generation.model)?.base;
const textareaRef = useRef<HTMLTextAreaElement>(null); const textareaRef = useRef<HTMLTextAreaElement>(null);

View File

@ -2,7 +2,7 @@ import { Divider, Flex, ListItem, Text, UnorderedList } from '@invoke-ai/ui-libr
import { createSelector } from '@reduxjs/toolkit'; import { createSelector } from '@reduxjs/toolkit';
import { useAppSelector } from 'app/store/storeHooks'; import { useAppSelector } from 'app/store/storeHooks';
import { useIsReadyToEnqueue } from 'common/hooks/useIsReadyToEnqueue'; import { useIsReadyToEnqueue } from 'common/hooks/useIsReadyToEnqueue';
import { selectRegionalPromptsSlice } from 'features/controlLayers/store/regionalPromptsSlice'; import { selectControlLayersSlice } from 'features/controlLayers/store/controlLayersSlice';
import { selectDynamicPromptsSlice } from 'features/dynamicPrompts/store/dynamicPromptsSlice'; import { selectDynamicPromptsSlice } from 'features/dynamicPrompts/store/dynamicPromptsSlice';
import { getShouldProcessPrompt } from 'features/dynamicPrompts/util/getShouldProcessPrompt'; import { getShouldProcessPrompt } from 'features/dynamicPrompts/util/getShouldProcessPrompt';
import { memo, useMemo } from 'react'; import { memo, useMemo } from 'react';
@ -11,10 +11,10 @@ import { useEnqueueBatchMutation } from 'services/api/endpoints/queue';
import { useBoardName } from 'services/api/hooks/useBoardName'; import { useBoardName } from 'services/api/hooks/useBoardName';
const selectPromptsCount = createSelector( const selectPromptsCount = createSelector(
selectRegionalPromptsSlice, selectControlLayersSlice,
selectDynamicPromptsSlice, selectDynamicPromptsSlice,
(regionalPrompts, dynamicPrompts) => (controlLayers, dynamicPrompts) =>
getShouldProcessPrompt(regionalPrompts.present.positivePrompt) ? dynamicPrompts.prompts.length : 1 getShouldProcessPrompt(controlLayers.present.positivePrompt) ? dynamicPrompts.prompts.length : 1
); );
type Props = { type Props = {

View File

@ -1,6 +1,6 @@
import { Box, Textarea } from '@invoke-ai/ui-library'; import { Box, Textarea } from '@invoke-ai/ui-library';
import { useAppDispatch, useAppSelector } from 'app/store/storeHooks'; import { useAppDispatch, useAppSelector } from 'app/store/storeHooks';
import { negativePrompt2Changed } from 'features/controlLayers/store/regionalPromptsSlice'; import { negativePrompt2Changed } from 'features/controlLayers/store/controlLayersSlice';
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';
@ -11,7 +11,7 @@ import { useTranslation } from 'react-i18next';
export const ParamSDXLNegativeStylePrompt = memo(() => { export const ParamSDXLNegativeStylePrompt = memo(() => {
const dispatch = useAppDispatch(); const dispatch = useAppDispatch();
const prompt = useAppSelector((s) => s.regionalPrompts.present.negativePrompt2); const prompt = useAppSelector((s) => s.controlLayers.present.negativePrompt2);
const textareaRef = useRef<HTMLTextAreaElement>(null); const textareaRef = useRef<HTMLTextAreaElement>(null);
const { t } = useTranslation(); const { t } = useTranslation();
const handleChange = useCallback( const handleChange = useCallback(

View File

@ -1,6 +1,6 @@
import { Box, Textarea } from '@invoke-ai/ui-library'; import { Box, Textarea } from '@invoke-ai/ui-library';
import { useAppDispatch, useAppSelector } from 'app/store/storeHooks'; import { useAppDispatch, useAppSelector } from 'app/store/storeHooks';
import { positivePrompt2Changed } from 'features/controlLayers/store/regionalPromptsSlice'; import { positivePrompt2Changed } from 'features/controlLayers/store/controlLayersSlice';
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';
@ -10,7 +10,7 @@ import { useTranslation } from 'react-i18next';
export const ParamSDXLPositiveStylePrompt = memo(() => { export const ParamSDXLPositiveStylePrompt = memo(() => {
const dispatch = useAppDispatch(); const dispatch = useAppDispatch();
const prompt = useAppSelector((s) => s.regionalPrompts.present.positivePrompt2); const prompt = useAppSelector((s) => s.controlLayers.present.positivePrompt2);
const textareaRef = useRef<HTMLTextAreaElement>(null); const textareaRef = useRef<HTMLTextAreaElement>(null);
const { t } = useTranslation(); const { t } = useTranslation();
const handleChange = useCallback( const handleChange = useCallback(

View File

@ -1,12 +1,12 @@
import { IconButton, Tooltip } from '@invoke-ai/ui-library'; import { IconButton, Tooltip } from '@invoke-ai/ui-library';
import { useAppDispatch, useAppSelector } from 'app/store/storeHooks'; import { useAppDispatch, useAppSelector } from 'app/store/storeHooks';
import { shouldConcatPromptsChanged } from 'features/controlLayers/store/regionalPromptsSlice'; import { shouldConcatPromptsChanged } from 'features/controlLayers/store/controlLayersSlice';
import { memo, useCallback, useMemo } from 'react'; import { memo, useCallback, useMemo } from 'react';
import { useTranslation } from 'react-i18next'; import { useTranslation } from 'react-i18next';
import { PiLinkSimpleBold, PiLinkSimpleBreakBold } from 'react-icons/pi'; import { PiLinkSimpleBold, PiLinkSimpleBreakBold } from 'react-icons/pi';
export const SDXLConcatButton = memo(() => { export const SDXLConcatButton = memo(() => {
const shouldConcatPrompts = useAppSelector((s) => s.regionalPrompts.present.shouldConcatPrompts); const shouldConcatPrompts = useAppSelector((s) => s.controlLayers.present.shouldConcatPrompts);
const dispatch = useAppDispatch(); const dispatch = useAppDispatch();
const { t } = useTranslation(); const { t } = useTranslation();

View File

@ -8,7 +8,7 @@ import { ParamSDXLNegativeStylePrompt } from './ParamSDXLNegativeStylePrompt';
import { ParamSDXLPositiveStylePrompt } from './ParamSDXLPositiveStylePrompt'; import { ParamSDXLPositiveStylePrompt } from './ParamSDXLPositiveStylePrompt';
export const SDXLPrompts = memo(() => { export const SDXLPrompts = memo(() => {
const shouldConcatPrompts = useAppSelector((s) => s.regionalPrompts.present.shouldConcatPrompts); const shouldConcatPrompts = useAppSelector((s) => s.controlLayers.present.shouldConcatPrompts);
return ( return (
<Flex flexDir="column" gap={2} pos="relative"> <Flex flexDir="column" gap={2} pos="relative">
<ParamPositivePrompt /> <ParamPositivePrompt />

View File

@ -13,10 +13,7 @@ import {
selectValidIPAdapters, selectValidIPAdapters,
selectValidT2IAdapters, selectValidT2IAdapters,
} from 'features/controlAdapters/store/controlAdaptersSlice'; } from 'features/controlAdapters/store/controlAdaptersSlice';
import { import { selectAllControlAdapterIds, selectControlLayersSlice } from 'features/controlLayers/store/controlLayersSlice';
selectAllControlAdapterIds,
selectRegionalPromptsSlice,
} from 'features/controlLayers/store/regionalPromptsSlice';
import { useStandaloneAccordionToggle } from 'features/settingsAccordions/hooks/useStandaloneAccordionToggle'; import { useStandaloneAccordionToggle } from 'features/settingsAccordions/hooks/useStandaloneAccordionToggle';
import { useFeatureStatus } from 'features/system/hooks/useFeatureStatus'; import { useFeatureStatus } from 'features/system/hooks/useFeatureStatus';
import { Fragment, memo } from 'react'; import { Fragment, memo } from 'react';
@ -24,12 +21,12 @@ import { useTranslation } from 'react-i18next';
import { PiPlusBold } from 'react-icons/pi'; import { PiPlusBold } from 'react-icons/pi';
const selector = createMemoizedSelector( const selector = createMemoizedSelector(
[selectControlAdaptersSlice, selectRegionalPromptsSlice], [selectControlAdaptersSlice, selectControlLayersSlice],
(controlAdapters, regionalPrompts) => { (controlAdapters, controlLayers) => {
const badges: string[] = []; const badges: string[] = [];
let isError = false; let isError = false;
const regionalControlAdapterIds = selectAllControlAdapterIds(regionalPrompts.present); const regionalControlAdapterIds = selectAllControlAdapterIds(controlLayers.present);
const enabledNonRegionalIPAdapterCount = selectAllIPAdapters(controlAdapters) const enabledNonRegionalIPAdapterCount = selectAllIPAdapters(controlAdapters)
.filter((ca) => !regionalControlAdapterIds.includes(ca.id)) .filter((ca) => !regionalControlAdapterIds.includes(ca.id))

View File

@ -3,7 +3,7 @@ import { Expander, Flex, FormControlGroup, StandaloneAccordion } from '@invoke-a
import { createMemoizedSelector } from 'app/store/createMemoizedSelector'; import { createMemoizedSelector } from 'app/store/createMemoizedSelector';
import { useAppSelector } from 'app/store/storeHooks'; import { useAppSelector } from 'app/store/storeHooks';
import { selectCanvasSlice } from 'features/canvas/store/canvasSlice'; import { selectCanvasSlice } from 'features/canvas/store/canvasSlice';
import { selectRegionalPromptsSlice } from 'features/controlLayers/store/regionalPromptsSlice'; import { selectControlLayersSlice } from 'features/controlLayers/store/controlLayersSlice';
import { HrfSettings } from 'features/hrf/components/HrfSettings'; import { HrfSettings } from 'features/hrf/components/HrfSettings';
import { selectHrfSlice } from 'features/hrf/store/hrfSlice'; import { selectHrfSlice } from 'features/hrf/store/hrfSlice';
import ParamScaleBeforeProcessing from 'features/parameters/components/Canvas/InfillAndScaling/ParamScaleBeforeProcessing'; import ParamScaleBeforeProcessing from 'features/parameters/components/Canvas/InfillAndScaling/ParamScaleBeforeProcessing';
@ -25,8 +25,8 @@ import { ImageSizeCanvas } from './ImageSizeCanvas';
import { ImageSizeLinear } from './ImageSizeLinear'; import { ImageSizeLinear } from './ImageSizeLinear';
const selector = createMemoizedSelector( const selector = createMemoizedSelector(
[selectGenerationSlice, selectCanvasSlice, selectHrfSlice, selectRegionalPromptsSlice, activeTabNameSelector], [selectGenerationSlice, selectCanvasSlice, selectHrfSlice, selectControlLayersSlice, activeTabNameSelector],
(generation, canvas, hrf, regionalPrompts, activeTabName) => { (generation, canvas, hrf, controlLayers, activeTabName) => {
const { shouldRandomizeSeed, model } = generation; const { shouldRandomizeSeed, model } = generation;
const { hrfEnabled } = hrf; const { hrfEnabled } = hrf;
const badges: string[] = []; const badges: string[] = [];
@ -43,7 +43,7 @@ const selector = createMemoizedSelector(
badges.push('locked'); badges.push('locked');
} }
} else { } else {
const { aspectRatio, width, height } = regionalPrompts.present.size; const { aspectRatio, width, height } = controlLayers.present.size;
badges.push(`${width}×${height}`); badges.push(`${width}×${height}`);
badges.push(aspectRatio.id); badges.push(aspectRatio.id);
if (aspectRatio.isLocked) { if (aspectRatio.isLocked) {

View File

@ -1,5 +1,5 @@
import { useAppDispatch, useAppSelector } from 'app/store/storeHooks'; import { useAppDispatch, useAppSelector } from 'app/store/storeHooks';
import { aspectRatioChanged, heightChanged, widthChanged } from 'features/controlLayers/store/regionalPromptsSlice'; import { aspectRatioChanged, heightChanged, widthChanged } from 'features/controlLayers/store/controlLayersSlice';
import { ParamHeight } from 'features/parameters/components/Core/ParamHeight'; import { ParamHeight } from 'features/parameters/components/Core/ParamHeight';
import { ParamWidth } from 'features/parameters/components/Core/ParamWidth'; import { ParamWidth } from 'features/parameters/components/Core/ParamWidth';
import { AspectRatioCanvasPreview } from 'features/parameters/components/ImageSize/AspectRatioCanvasPreview'; import { AspectRatioCanvasPreview } from 'features/parameters/components/ImageSize/AspectRatioCanvasPreview';
@ -12,9 +12,9 @@ import { memo, useCallback } from 'react';
export const ImageSizeLinear = memo(() => { export const ImageSizeLinear = memo(() => {
const dispatch = useAppDispatch(); const dispatch = useAppDispatch();
const tab = useAppSelector(activeTabNameSelector); const tab = useAppSelector(activeTabNameSelector);
const width = useAppSelector((s) => s.regionalPrompts.present.size.width); const width = useAppSelector((s) => s.controlLayers.present.size.width);
const height = useAppSelector((s) => s.regionalPrompts.present.size.height); const height = useAppSelector((s) => s.controlLayers.present.size.height);
const aspectRatioState = useAppSelector((s) => s.regionalPrompts.present.size.aspectRatio); const aspectRatioState = useAppSelector((s) => s.controlLayers.present.size.aspectRatio);
const onChangeWidth = useCallback( const onChangeWidth = useCallback(
(width: number) => { (width: number) => {

View File

@ -1,7 +1,7 @@
import { Box, Flex, Tab, TabList, TabPanel, TabPanels, Tabs } from '@invoke-ai/ui-library'; import { Box, Flex, Tab, TabList, TabPanel, TabPanels, Tabs } from '@invoke-ai/ui-library';
import { useAppSelector } from 'app/store/storeHooks'; import { useAppSelector } from 'app/store/storeHooks';
import { overlayScrollbarsParams } from 'common/components/OverlayScrollbars/constants'; import { overlayScrollbarsParams } from 'common/components/OverlayScrollbars/constants';
import { RegionalPromptsPanelContent } from 'features/controlLayers/components/RegionalPromptsPanelContent'; import { ControlLayersPanelContent } from 'features/controlLayers/components/ControlLayersPanelContent';
import { useRegionalControlTitle } from 'features/controlLayers/hooks/useRegionalControlTitle'; import { useRegionalControlTitle } from 'features/controlLayers/hooks/useRegionalControlTitle';
import { Prompts } from 'features/parameters/components/Prompts/Prompts'; import { Prompts } from 'features/parameters/components/Prompts/Prompts';
import QueueControls from 'features/queue/components/QueueControls'; import QueueControls from 'features/queue/components/QueueControls';
@ -55,7 +55,7 @@ const ParametersPanelTextToImage = () => {
</Flex> </Flex>
</TabPanel> </TabPanel>
<TabPanel> <TabPanel>
<RegionalPromptsPanelContent /> <ControlLayersPanelContent />
</TabPanel> </TabPanel>
</TabPanels> </TabPanels>
</Tabs> </Tabs>

View File

@ -1,5 +1,5 @@
import { Box, Tab, TabList, TabPanel, TabPanels, Tabs } from '@invoke-ai/ui-library'; import { Box, Tab, TabList, TabPanel, TabPanels, Tabs } from '@invoke-ai/ui-library';
import { RegionalPromptsEditor } from 'features/controlLayers/components/RegionalPromptsEditor'; import { ControlLayersEditor } from 'features/controlLayers/components/ControlLayersEditor';
import { useRegionalControlTitle } from 'features/controlLayers/hooks/useRegionalControlTitle'; import { useRegionalControlTitle } from 'features/controlLayers/hooks/useRegionalControlTitle';
import CurrentImageDisplay from 'features/gallery/components/CurrentImage/CurrentImageDisplay'; import CurrentImageDisplay from 'features/gallery/components/CurrentImage/CurrentImageDisplay';
import { memo } from 'react'; import { memo } from 'react';
@ -22,7 +22,7 @@ const TextToImageTab = () => {
<CurrentImageDisplay /> <CurrentImageDisplay />
</TabPanel> </TabPanel>
<TabPanel> <TabPanel>
<RegionalPromptsEditor /> <ControlLayersEditor />
</TabPanel> </TabPanel>
</TabPanels> </TabPanels>
</Tabs> </Tabs>