mirror of
https://github.com/invoke-ai/InvokeAI
synced 2024-08-30 20:32:17 +00:00
tidy(ui): "regional prompts" -> "control layers"
This commit is contained in:
parent
8de56fd77c
commit
3441187c23
@ -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"
|
||||||
|
@ -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 });
|
||||||
|
|
||||||
|
@ -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
|
||||||
);
|
);
|
||||||
|
|
||||||
|
@ -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')) {
|
||||||
|
@ -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));
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
@ -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,
|
||||||
|
@ -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) => {
|
||||||
|
@ -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') {
|
||||||
|
@ -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';
|
||||||
|
@ -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>
|
||||||
);
|
);
|
||||||
|
@ -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,
|
||||||
|
@ -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}
|
||||||
|
@ -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}
|
||||||
|
@ -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]
|
||||||
|
@ -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,
|
||||||
|
};
|
@ -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';
|
@ -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') {
|
@ -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';
|
@ -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>
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
@ -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}
|
||||||
|
@ -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;
|
||||||
}),
|
}),
|
||||||
|
@ -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') {
|
||||||
|
@ -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} />
|
||||||
|
@ -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>
|
||||||
);
|
);
|
||||||
|
@ -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)}
|
||||||
|
@ -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;
|
||||||
}),
|
}),
|
||||||
|
@ -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>
|
||||||
|
@ -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>
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
|
@ -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>
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
|
@ -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';
|
||||||
|
@ -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';
|
||||||
|
@ -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}
|
||||||
/>
|
/>
|
||||||
|
@ -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}
|
||||||
|
@ -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,
|
|
||||||
};
|
|
@ -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);
|
||||||
|
@ -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}
|
||||||
|
@ -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]);
|
||||||
|
@ -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';
|
||||||
|
@ -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);
|
||||||
}),
|
}),
|
||||||
|
@ -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>) => {
|
||||||
|
@ -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;
|
||||||
};
|
};
|
||||||
|
@ -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
|
@ -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[];
|
||||||
|
@ -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();
|
||||||
|
@ -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
|
||||||
|
@ -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`);
|
||||||
|
|
||||||
|
@ -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';
|
||||||
|
@ -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)
|
@ -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);
|
||||||
|
@ -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);
|
||||||
|
@ -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);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
@ -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);
|
||||||
|
@ -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;
|
||||||
|
@ -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');
|
||||||
|
@ -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');
|
||||||
|
@ -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;
|
||||||
|
|
||||||
|
@ -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;
|
||||||
|
|
||||||
|
@ -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;
|
||||||
|
|
||||||
|
@ -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;
|
||||||
|
@ -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;
|
||||||
|
@ -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'] = [];
|
||||||
|
@ -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
|
||||||
|
@ -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;
|
||||||
|
|
||||||
|
@ -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) {
|
||||||
|
@ -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) {
|
||||||
|
@ -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,
|
||||||
|
@ -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(
|
||||||
|
@ -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);
|
||||||
|
@ -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 = {
|
||||||
|
@ -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(
|
||||||
|
@ -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(
|
||||||
|
@ -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();
|
||||||
|
@ -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 />
|
||||||
|
@ -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))
|
||||||
|
@ -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) {
|
||||||
|
@ -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) => {
|
||||||
|
@ -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>
|
||||||
|
@ -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>
|
||||||
|
Loading…
Reference in New Issue
Block a user