From d861bc690e8d37f1c282fbec7cea6db9301c2435 Mon Sep 17 00:00:00 2001 From: psychedelicious <4822129+psychedelicious@users.noreply.github.com> Date: Tue, 30 Apr 2024 21:09:18 +1000 Subject: [PATCH 01/52] feat(mm): handle PC_PATH_MAX on external drives on macOS `PC_PATH_MAX` doesn't exist for (some?) external drives on macOS. We need error handling when retrieving this value. Also added error handling for `PC_NAME_MAX` just in case. This does work for me for external drives on macOS, though. Closes #6277 --- .../app/services/download/download_default.py | 26 ++++++++++++++++--- 1 file changed, 22 insertions(+), 4 deletions(-) diff --git a/invokeai/app/services/download/download_default.py b/invokeai/app/services/download/download_default.py index f393a18dcb..7d8229fba1 100644 --- a/invokeai/app/services/download/download_default.py +++ b/invokeai/app/services/download/download_default.py @@ -318,10 +318,8 @@ class DownloadQueueService(DownloadQueueServiceBase): in_progress_path.rename(job.download_path) def _validate_filename(self, directory: str, filename: str) -> bool: - pc_name_max = os.pathconf(directory, "PC_NAME_MAX") if hasattr(os, "pathconf") else 260 # hardcoded for windows - pc_path_max = ( - os.pathconf(directory, "PC_PATH_MAX") if hasattr(os, "pathconf") else 32767 - ) # hardcoded for windows with long names enabled + pc_name_max = get_pc_name_max(directory) + pc_path_max = get_pc_path_max(directory) if "/" in filename: return False if filename.startswith(".."): @@ -419,6 +417,26 @@ class DownloadQueueService(DownloadQueueServiceBase): self._logger.warning(excp) +def get_pc_name_max(directory: str) -> int: + if hasattr(os, "pathconf"): + try: + return os.pathconf(directory, "PC_NAME_MAX") + except OSError: + # macOS w/ external drives raise OSError + pass + return 260 # hardcoded for windows + + +def get_pc_path_max(directory: str) -> int: + if hasattr(os, "pathconf"): + try: + return os.pathconf(directory, "PC_PATH_MAX") + except OSError: + # some platforms may not have this value + pass + return 32767 # hardcoded for windows with long names enabled + + # Example on_progress event handler to display a TQDM status bar # Activate with: # download_service.download(DownloadJob('http://foo.bar/baz', '/tmp', on_progress=TqdmProgress().update)) From c686625076859ff5ad10f88cf65826c27f7cc989 Mon Sep 17 00:00:00 2001 From: psychedelicious <4822129+psychedelicious@users.noreply.github.com> Date: Wed, 24 Apr 2024 16:05:34 +1000 Subject: [PATCH 02/52] feat(ui): add 'control_layer' type --- .../util/graph/addIPAdapterToLinearGraph.ts | 27 +++++--- .../components/RPLayerIPAdapterList.tsx | 4 +- .../hooks/useRegionalControlTitle.ts | 3 +- .../store/regionalPromptsSlice.ts | 67 ++++++++++++------- .../regionalPrompts/util/getLayerBlobs.ts | 4 +- .../regionalPrompts/util/renderers.ts | 55 ++++++++------- .../ControlSettingsAccordion.tsx | 16 +++-- 7 files changed, 105 insertions(+), 71 deletions(-) diff --git a/invokeai/frontend/web/src/features/nodes/util/graph/addIPAdapterToLinearGraph.ts b/invokeai/frontend/web/src/features/nodes/util/graph/addIPAdapterToLinearGraph.ts index 0a90622e04..4a287b5335 100644 --- a/invokeai/frontend/web/src/features/nodes/util/graph/addIPAdapterToLinearGraph.ts +++ b/invokeai/frontend/web/src/features/nodes/util/graph/addIPAdapterToLinearGraph.ts @@ -2,6 +2,8 @@ import type { RootState } from 'app/store/store'; import { selectValidIPAdapters } from 'features/controlAdapters/store/controlAdaptersSlice'; import type { IPAdapterConfig } from 'features/controlAdapters/store/types'; import type { ImageField } from 'features/nodes/types/common'; +import { isVectorMaskLayer } from 'features/regionalPrompts/store/regionalPromptsSlice'; +import { differenceBy } from 'lodash-es'; import type { CollectInvocation, CoreMetadataInvocation, @@ -19,16 +21,21 @@ export const addIPAdapterToLinearGraph = async ( graph: NonNullableGraph, baseNodeId: string ): Promise => { - const validIPAdapters = selectValidIPAdapters(state.controlAdapters) - .filter(({ model, controlImage, isEnabled }) => { - const hasModel = Boolean(model); - const doesBaseMatch = model?.base === state.generation.model?.base; - const hasControlImage = controlImage; - return isEnabled && hasModel && doesBaseMatch && hasControlImage; - }) - .filter((ca) => !state.regionalPrompts.present.layers.some((l) => l.ipAdapterIds.includes(ca.id))); + const validIPAdapters = selectValidIPAdapters(state.controlAdapters).filter(({ model, controlImage, isEnabled }) => { + const hasModel = Boolean(model); + const doesBaseMatch = model?.base === state.generation.model?.base; + const hasControlImage = controlImage; + return isEnabled && hasModel && doesBaseMatch && hasControlImage; + }); - if (validIPAdapters.length) { + const regionalIPAdapterIds = state.regionalPrompts.present.layers + .filter(isVectorMaskLayer) + .map((l) => l.ipAdapterIds) + .flat(); + + const nonRegionalIPAdapters = differenceBy(validIPAdapters, regionalIPAdapterIds, 'id'); + + if (nonRegionalIPAdapters.length) { // Even though denoise_latents' ip adapter input is collection or scalar, keep it simple and always use a collect const ipAdapterCollectNode: CollectInvocation = { id: IP_ADAPTER_COLLECT, @@ -46,7 +53,7 @@ export const addIPAdapterToLinearGraph = async ( const ipAdapterMetdata: CoreMetadataInvocation['ipAdapters'] = []; - for (const ipAdapter of validIPAdapters) { + for (const ipAdapter of nonRegionalIPAdapters) { if (!ipAdapter.model) { return; } diff --git a/invokeai/frontend/web/src/features/regionalPrompts/components/RPLayerIPAdapterList.tsx b/invokeai/frontend/web/src/features/regionalPrompts/components/RPLayerIPAdapterList.tsx index c5d1ca62e9..91cc2d0736 100644 --- a/invokeai/frontend/web/src/features/regionalPrompts/components/RPLayerIPAdapterList.tsx +++ b/invokeai/frontend/web/src/features/regionalPrompts/components/RPLayerIPAdapterList.tsx @@ -2,7 +2,7 @@ import { Flex } from '@invoke-ai/ui-library'; import { createMemoizedSelector } from 'app/store/createMemoizedSelector'; import { useAppSelector } from 'app/store/storeHooks'; import ControlAdapterConfig from 'features/controlAdapters/components/ControlAdapterConfig'; -import { selectRegionalPromptsSlice } from 'features/regionalPrompts/store/regionalPromptsSlice'; +import { isVectorMaskLayer,selectRegionalPromptsSlice } from 'features/regionalPrompts/store/regionalPromptsSlice'; import { memo, useMemo } from 'react'; import { assert } from 'tsafe'; @@ -14,7 +14,7 @@ export const RPLayerIPAdapterList = memo(({ layerId }: Props) => { const selectIPAdapterIds = useMemo( () => createMemoizedSelector(selectRegionalPromptsSlice, (regionalPrompts) => { - const layer = regionalPrompts.present.layers.find((l) => l.id === layerId); + const layer = regionalPrompts.present.layers.filter(isVectorMaskLayer).find((l) => l.id === layerId); assert(layer, `Layer ${layerId} not found`); return layer.ipAdapterIds; }), diff --git a/invokeai/frontend/web/src/features/regionalPrompts/hooks/useRegionalControlTitle.ts b/invokeai/frontend/web/src/features/regionalPrompts/hooks/useRegionalControlTitle.ts index 4f23804c2a..24532d2fa1 100644 --- a/invokeai/frontend/web/src/features/regionalPrompts/hooks/useRegionalControlTitle.ts +++ b/invokeai/frontend/web/src/features/regionalPrompts/hooks/useRegionalControlTitle.ts @@ -1,6 +1,6 @@ import { createSelector } from '@reduxjs/toolkit'; import { useAppSelector } from 'app/store/storeHooks'; -import { selectRegionalPromptsSlice } from 'features/regionalPrompts/store/regionalPromptsSlice'; +import { isVectorMaskLayer, selectRegionalPromptsSlice } from 'features/regionalPrompts/store/regionalPromptsSlice'; import { useMemo } from 'react'; import { useTranslation } from 'react-i18next'; @@ -9,6 +9,7 @@ const selectValidLayerCount = createSelector(selectRegionalPromptsSlice, (region return 0; } const validLayers = regionalPrompts.present.layers + .filter(isVectorMaskLayer) .filter((l) => l.isVisible) .filter((l) => { const hasTextPrompt = Boolean(l.positivePrompt || l.negativePrompt); diff --git a/invokeai/frontend/web/src/features/regionalPrompts/store/regionalPromptsSlice.ts b/invokeai/frontend/web/src/features/regionalPrompts/store/regionalPromptsSlice.ts index 1d32938868..5593f0dff6 100644 --- a/invokeai/frontend/web/src/features/regionalPrompts/store/regionalPromptsSlice.ts +++ b/invokeai/frontend/web/src/features/regionalPrompts/store/regionalPromptsSlice.ts @@ -3,6 +3,7 @@ import { createSlice, isAnyOf } from '@reduxjs/toolkit'; import type { PersistConfig, RootState } from 'app/store/store'; import { moveBackward, moveForward, moveToBack, moveToFront } from 'common/util/arrayUtils'; import { controlAdapterRemoved } from 'features/controlAdapters/store/controlAdaptersSlice'; +import type { ControlAdapterConfig } from 'features/controlAdapters/store/types'; import type { ParameterAutoNegative } from 'features/parameters/types/parameterSchemas'; import type { IRect, Vector2d } from 'konva/lib/types'; import { isEqual } from 'lodash-es'; @@ -42,6 +43,11 @@ type LayerBase = { isVisible: boolean; }; +type ControlLayer = LayerBase & { + type: 'control_layer'; + controlAdapter: ControlAdapterConfig; +}; + type MaskLayerBase = LayerBase & { positivePrompt: string | null; negativePrompt: string | null; // Up to one text prompt per mask @@ -56,7 +62,7 @@ export type VectorMaskLayer = MaskLayerBase & { objects: (VectorMaskLine | VectorMaskRect)[]; }; -export type Layer = VectorMaskLayer; +export type Layer = VectorMaskLayer | ControlLayer; type RegionalPromptsState = { _version: 1; @@ -78,12 +84,24 @@ export const initialRegionalPromptsState: RegionalPromptsState = { const isLine = (obj: VectorMaskLine | VectorMaskRect): obj is VectorMaskLine => obj.type === 'vector_mask_line'; export const isVectorMaskLayer = (layer?: Layer): layer is VectorMaskLayer => layer?.type === 'vector_mask_layer'; -const resetLayer = (layer: VectorMaskLayer) => { - layer.objects = []; - layer.bbox = null; - layer.isVisible = true; - layer.needsPixelBbox = false; - layer.bboxNeedsUpdate = false; +const resetLayer = (layer: Layer) => { + if (layer.type === 'vector_mask_layer') { + layer.objects = []; + layer.bbox = null; + layer.isVisible = true; + layer.needsPixelBbox = false; + layer.bboxNeedsUpdate = false; + return; + } + + if (layer.type === 'control_layer') { + // TODO + } +}; +const getVectorMaskPreviewColor = (state: RegionalPromptsState): RgbColor => { + const vmLayers = state.layers.filter(isVectorMaskLayer); + const lastColor = vmLayers[vmLayers.length - 1]?.previewColor; + return LayerColors.next(lastColor); }; export const regionalPromptsSlice = createSlice({ @@ -93,18 +111,16 @@ export const regionalPromptsSlice = createSlice({ //#region All Layers layerAdded: { reducer: (state, action: PayloadAction) => { - const kind = action.payload; - if (action.payload === 'vector_mask_layer') { - const lastColor = state.layers[state.layers.length - 1]?.previewColor; - const previewColor = LayerColors.next(lastColor); + const type = action.payload; + if (type === 'vector_mask_layer') { const layer: VectorMaskLayer = { id: getVectorMaskLayerId(action.meta.uuid), - type: kind, + type, isVisible: true, bbox: null, bboxNeedsUpdate: false, objects: [], - previewColor, + previewColor: getVectorMaskPreviewColor(state), x: 0, y: 0, autoNegative: 'invert', @@ -117,6 +133,11 @@ export const regionalPromptsSlice = createSlice({ state.selectedLayerId = layer.id; return; } + + if (type === 'control_layer') { + // TODO + return; + } }, prepare: (payload: Layer['type']) => ({ payload, meta: { uuid: uuidv4() } }), }, @@ -196,21 +217,21 @@ export const regionalPromptsSlice = createSlice({ maskLayerPositivePromptChanged: (state, action: PayloadAction<{ layerId: string; prompt: string | null }>) => { const { layerId, prompt } = action.payload; const layer = state.layers.find((l) => l.id === layerId); - if (layer) { + if (layer?.type === 'vector_mask_layer') { layer.positivePrompt = prompt; } }, maskLayerNegativePromptChanged: (state, action: PayloadAction<{ layerId: string; prompt: string | null }>) => { const { layerId, prompt } = action.payload; const layer = state.layers.find((l) => l.id === layerId); - if (layer) { + if (layer?.type === 'vector_mask_layer') { layer.negativePrompt = prompt; } }, maskLayerIPAdapterAdded: { reducer: (state, action: PayloadAction) => { const layer = state.layers.find((l) => l.id === action.payload); - if (layer) { + if (layer?.type === 'vector_mask_layer') { layer.ipAdapterIds.push(action.meta.uuid); } }, @@ -219,7 +240,7 @@ export const regionalPromptsSlice = createSlice({ maskLayerPreviewColorChanged: (state, action: PayloadAction<{ layerId: string; color: RgbColor }>) => { const { layerId, color } = action.payload; const layer = state.layers.find((l) => l.id === layerId); - if (layer) { + if (layer?.type === 'vector_mask_layer') { layer.previewColor = color; } }, @@ -234,7 +255,7 @@ export const regionalPromptsSlice = createSlice({ ) => { const { layerId, points, tool } = action.payload; const layer = state.layers.find((l) => l.id === layerId); - if (layer) { + if (layer?.type === 'vector_mask_layer') { const lineId = getVectorMaskLayerLineId(layer.id, action.meta.uuid); layer.objects.push({ type: 'vector_mask_line', @@ -259,7 +280,7 @@ export const regionalPromptsSlice = createSlice({ maskLayerPointsAdded: (state, action: PayloadAction<{ layerId: string; point: [number, number] }>) => { const { layerId, point } = action.payload; const layer = state.layers.find((l) => l.id === layerId); - if (layer) { + if (layer?.type === 'vector_mask_layer') { const lastLine = layer.objects.findLast(isLine); if (!lastLine) { return; @@ -278,7 +299,7 @@ export const regionalPromptsSlice = createSlice({ return; } const layer = state.layers.find((l) => l.id === layerId); - if (layer) { + if (layer?.type === 'vector_mask_layer') { const id = getVectorMaskLayerRectId(layer.id, action.meta.uuid); layer.objects.push({ type: 'vector_mask_rect', @@ -299,7 +320,7 @@ export const regionalPromptsSlice = createSlice({ ) => { const { layerId, autoNegative } = action.payload; const layer = state.layers.find((l) => l.id === layerId); - if (layer) { + if (layer?.type === 'vector_mask_layer') { layer.autoNegative = autoNegative; } }, @@ -331,9 +352,9 @@ export const regionalPromptsSlice = createSlice({ }, extraReducers(builder) { builder.addCase(controlAdapterRemoved, (state, action) => { - for (const layer of state.layers) { + state.layers.filter(isVectorMaskLayer).forEach((layer) => { layer.ipAdapterIds = layer.ipAdapterIds.filter((id) => id !== action.payload.id); - } + }); }); }, }); diff --git a/invokeai/frontend/web/src/features/regionalPrompts/util/getLayerBlobs.ts b/invokeai/frontend/web/src/features/regionalPrompts/util/getLayerBlobs.ts index 28a11b649d..02c1ae8b60 100644 --- a/invokeai/frontend/web/src/features/regionalPrompts/util/getLayerBlobs.ts +++ b/invokeai/frontend/web/src/features/regionalPrompts/util/getLayerBlobs.ts @@ -1,7 +1,7 @@ import { getStore } from 'app/store/nanostores/store'; import openBase64ImageInTab from 'common/util/openBase64ImageInTab'; import { blobToDataURL } from 'features/canvas/util/blobToDataURL'; -import { VECTOR_MASK_LAYER_NAME } from 'features/regionalPrompts/store/regionalPromptsSlice'; +import { isVectorMaskLayer, VECTOR_MASK_LAYER_NAME } from 'features/regionalPrompts/store/regionalPromptsSlice'; import { renderers } from 'features/regionalPrompts/util/renderers'; import Konva from 'konva'; import { assert } from 'tsafe'; @@ -17,7 +17,7 @@ export const getRegionalPromptLayerBlobs = async ( preview: boolean = false ): Promise> => { const state = getStore().getState(); - const reduxLayers = state.regionalPrompts.present.layers; + const reduxLayers = state.regionalPrompts.present.layers.filter(isVectorMaskLayer); const container = document.createElement('div'); const stage = new Konva.Stage({ container, width: state.generation.width, height: state.generation.height }); renderers.renderLayers(stage, reduxLayers, 1, 'brush'); diff --git a/invokeai/frontend/web/src/features/regionalPrompts/util/renderers.ts b/invokeai/frontend/web/src/features/regionalPrompts/util/renderers.ts index 20e5f75ab7..4f008c7758 100644 --- a/invokeai/frontend/web/src/features/regionalPrompts/util/renderers.ts +++ b/invokeai/frontend/web/src/features/regionalPrompts/util/renderers.ts @@ -494,35 +494,38 @@ const renderBbox = ( } for (const reduxLayer of reduxLayers) { - const konvaLayer = stage.findOne(`#${reduxLayer.id}`); - assert(konvaLayer, `Layer ${reduxLayer.id} not found in stage`); + if (reduxLayer.type === 'vector_mask_layer') { + const konvaLayer = stage.findOne(`#${reduxLayer.id}`); + assert(konvaLayer, `Layer ${reduxLayer.id} not found in stage`); - let bbox = reduxLayer.bbox; + let bbox = reduxLayer.bbox; - // We only need to recalculate the bbox if the layer has changed and it has objects - if (reduxLayer.bboxNeedsUpdate && reduxLayer.objects.length) { - // We only need to use the pixel-perfect bounding box if the layer has eraser strokes - bbox = reduxLayer.needsPixelBbox ? getLayerBboxPixels(konvaLayer) : getLayerBboxFast(konvaLayer); - // Update the layer's bbox in the redux store - onBboxChanged(reduxLayer.id, bbox); + // We only need to recalculate the bbox if the layer has changed and it has objects + if (reduxLayer.bboxNeedsUpdate && reduxLayer.objects.length) { + // We only need to use the pixel-perfect bounding box if the layer has eraser strokes + bbox = reduxLayer.needsPixelBbox ? getLayerBboxPixels(konvaLayer) : getLayerBboxFast(konvaLayer); + // Update the layer's bbox in the redux store + onBboxChanged(reduxLayer.id, bbox); + } + + if (!bbox) { + continue; + } + + const rect = + konvaLayer.findOne(`.${LAYER_BBOX_NAME}`) ?? + createBboxRect(reduxLayer, konvaLayer, onBboxMouseDown); + + rect.setAttrs({ + visible: true, + listening: true, + x: bbox.x, + y: bbox.y, + width: bbox.width, + height: bbox.height, + stroke: reduxLayer.id === selectedLayerId ? BBOX_SELECTED_STROKE : BBOX_NOT_SELECTED_STROKE, + }); } - - if (!bbox) { - continue; - } - - const rect = - konvaLayer.findOne(`.${LAYER_BBOX_NAME}`) ?? createBboxRect(reduxLayer, konvaLayer, onBboxMouseDown); - - rect.setAttrs({ - visible: true, - listening: true, - x: bbox.x, - y: bbox.y, - width: bbox.width, - height: bbox.height, - stroke: reduxLayer.id === selectedLayerId ? BBOX_SELECTED_STROKE : BBOX_NOT_SELECTED_STROKE, - }); } }; diff --git a/invokeai/frontend/web/src/features/settingsAccordions/components/ControlSettingsAccordion/ControlSettingsAccordion.tsx b/invokeai/frontend/web/src/features/settingsAccordions/components/ControlSettingsAccordion/ControlSettingsAccordion.tsx index 36448c8909..449091536e 100644 --- a/invokeai/frontend/web/src/features/settingsAccordions/components/ControlSettingsAccordion/ControlSettingsAccordion.tsx +++ b/invokeai/frontend/web/src/features/settingsAccordions/components/ControlSettingsAccordion/ControlSettingsAccordion.tsx @@ -13,7 +13,7 @@ import { selectValidIPAdapters, selectValidT2IAdapters, } from 'features/controlAdapters/store/controlAdaptersSlice'; -import { selectRegionalPromptsSlice } from 'features/regionalPrompts/store/regionalPromptsSlice'; +import { isVectorMaskLayer, selectRegionalPromptsSlice } from 'features/regionalPrompts/store/regionalPromptsSlice'; import { useStandaloneAccordionToggle } from 'features/settingsAccordions/hooks/useStandaloneAccordionToggle'; import { useFeatureStatus } from 'features/system/hooks/useFeatureStatus'; import { Fragment, memo } from 'react'; @@ -26,15 +26,17 @@ const selector = createMemoizedSelector( const badges: string[] = []; let isError = false; - const enabledIPAdapterCount = selectAllIPAdapters(controlAdapters) - .filter((ca) => !regionalPrompts.present.layers.some((l) => l.ipAdapterIds.includes(ca.id))) + const enabledNonRegionalIPAdapterCount = selectAllIPAdapters(controlAdapters) + .filter( + (ca) => !regionalPrompts.present.layers.filter(isVectorMaskLayer).some((l) => l.ipAdapterIds.includes(ca.id)) + ) .filter((ca) => ca.isEnabled).length; const validIPAdapterCount = selectValidIPAdapters(controlAdapters).length; - if (enabledIPAdapterCount > 0) { - badges.push(`${enabledIPAdapterCount} IP`); + if (enabledNonRegionalIPAdapterCount > 0) { + badges.push(`${enabledNonRegionalIPAdapterCount} IP`); } - if (enabledIPAdapterCount > validIPAdapterCount) { + if (enabledNonRegionalIPAdapterCount > validIPAdapterCount) { isError = true; } @@ -57,7 +59,7 @@ const selector = createMemoizedSelector( } const controlAdapterIds = selectControlAdapterIds(controlAdapters).filter( - (id) => !regionalPrompts.present.layers.some((l) => l.ipAdapterIds.includes(id)) + (id) => !regionalPrompts.present.layers.filter(isVectorMaskLayer).some((l) => l.ipAdapterIds.includes(id)) ); return { From 1cf1e53a6c688bc367e8b3790e68bd1ef0d461be Mon Sep 17 00:00:00 2001 From: psychedelicious <4822129+psychedelicious@users.noreply.github.com> Date: Thu, 25 Apr 2024 18:41:42 +1000 Subject: [PATCH 03/52] refactor(ui): move positive and negative prompt to regional --- .../listeners/promptChanged.ts | 18 ++++++++---- .../src/common/hooks/useIsReadyToEnqueue.ts | 7 +++-- .../src/features/metadata/util/recallers.ts | 7 ++--- .../graph/buildCanvasImageToImageGraph.ts | 3 +- .../util/graph/buildCanvasInpaintGraph.ts | 3 +- .../util/graph/buildCanvasOutpaintGraph.ts | 3 +- .../graph/buildCanvasSDXLImageToImageGraph.ts | 3 +- .../util/graph/buildCanvasSDXLInpaintGraph.ts | 3 +- .../graph/buildCanvasSDXLOutpaintGraph.ts | 3 +- .../graph/buildCanvasSDXLTextToImageGraph.ts | 3 +- .../util/graph/buildCanvasTextToImageGraph.ts | 3 +- .../graph/buildLinearImageToImageGraph.ts | 3 +- .../graph/buildLinearSDXLImageToImageGraph.ts | 3 +- .../graph/buildLinearSDXLTextToImageGraph.ts | 3 +- .../util/graph/buildLinearTextToImageGraph.ts | 3 +- .../nodes/util/graph/graphBuilderUtils.ts | 2 +- .../components/Core/ParamNegativePrompt.tsx | 6 ++-- .../components/Core/ParamPositivePrompt.tsx | 6 ++-- .../parameters/store/generationSlice.ts | 10 ------- .../src/features/parameters/store/types.ts | 4 --- .../queue/components/QueueButtonTooltip.tsx | 8 +++--- .../store/regionalPromptsSlice.ts | 28 ++++++++++++++++++- 22 files changed, 70 insertions(+), 62 deletions(-) diff --git a/invokeai/frontend/web/src/app/store/middleware/listenerMiddleware/listeners/promptChanged.ts b/invokeai/frontend/web/src/app/store/middleware/listenerMiddleware/listeners/promptChanged.ts index b78ddc3f69..93926f4b08 100644 --- a/invokeai/frontend/web/src/app/store/middleware/listenerMiddleware/listeners/promptChanged.ts +++ b/invokeai/frontend/web/src/app/store/middleware/listenerMiddleware/listeners/promptChanged.ts @@ -10,11 +10,17 @@ import { promptsChanged, } from 'features/dynamicPrompts/store/dynamicPromptsSlice'; import { getShouldProcessPrompt } from 'features/dynamicPrompts/util/getShouldProcessPrompt'; -import { setPositivePrompt } from 'features/parameters/store/generationSlice'; +import { positivePromptChanged } from 'features/regionalPrompts/store/regionalPromptsSlice'; import { utilitiesApi } from 'services/api/endpoints/utilities'; import { socketConnected } from 'services/events/actions'; -const matcher = isAnyOf(setPositivePrompt, combinatorialToggled, maxPromptsChanged, maxPromptsReset, socketConnected); +const matcher = isAnyOf( + positivePromptChanged, + combinatorialToggled, + maxPromptsChanged, + maxPromptsReset, + socketConnected +); export const addDynamicPromptsListener = (startAppListening: AppStartListening) => { startAppListening({ @@ -22,7 +28,7 @@ export const addDynamicPromptsListener = (startAppListening: AppStartListening) effect: async (action, { dispatch, getState, cancelActiveListeners, delay }) => { cancelActiveListeners(); const state = getState(); - const { positivePrompt } = state.generation; + const { positivePrompt } = state.regionalPrompts.present.baseLayer; const { maxPrompts } = state.dynamicPrompts; if (state.config.disabledFeatures.includes('dynamicPrompting')) { @@ -32,7 +38,7 @@ export const addDynamicPromptsListener = (startAppListening: AppStartListening) const cachedPrompts = utilitiesApi.endpoints.dynamicPrompts.select({ prompt: positivePrompt, max_prompts: maxPrompts, - })(getState()).data; + })(state).data; if (cachedPrompts) { dispatch(promptsChanged(cachedPrompts.prompts)); @@ -40,8 +46,8 @@ export const addDynamicPromptsListener = (startAppListening: AppStartListening) return; } - if (!getShouldProcessPrompt(state.generation.positivePrompt)) { - dispatch(promptsChanged([state.generation.positivePrompt])); + if (!getShouldProcessPrompt(positivePrompt)) { + dispatch(promptsChanged([positivePrompt])); dispatch(parsingErrorChanged(undefined)); dispatch(isErrorChanged(false)); return; diff --git a/invokeai/frontend/web/src/common/hooks/useIsReadyToEnqueue.ts b/invokeai/frontend/web/src/common/hooks/useIsReadyToEnqueue.ts index b31efed970..fa4c1aaf45 100644 --- a/invokeai/frontend/web/src/common/hooks/useIsReadyToEnqueue.ts +++ b/invokeai/frontend/web/src/common/hooks/useIsReadyToEnqueue.ts @@ -10,6 +10,7 @@ import { getShouldProcessPrompt } from 'features/dynamicPrompts/util/getShouldPr import { selectNodesSlice } from 'features/nodes/store/nodesSlice'; import { isInvocationNode } from 'features/nodes/types/invocation'; import { selectGenerationSlice } from 'features/parameters/store/generationSlice'; +import { selectRegionalPromptsSlice } from 'features/regionalPrompts/store/regionalPromptsSlice'; import { selectSystemSlice } from 'features/system/store/systemSlice'; import { activeTabNameSelector } from 'features/ui/store/uiSelectors'; import i18n from 'i18next'; @@ -23,10 +24,12 @@ const selector = createMemoizedSelector( selectSystemSlice, selectNodesSlice, selectDynamicPromptsSlice, + selectRegionalPromptsSlice, activeTabNameSelector, ], - (controlAdapters, generation, system, nodes, dynamicPrompts, activeTabName) => { - const { initialImage, model, positivePrompt } = generation; + (controlAdapters, generation, system, nodes, dynamicPrompts, regionalPrompts, activeTabName) => { + const { initialImage, model } = generation; + const { positivePrompt } = regionalPrompts.present.baseLayer; const { isConnected } = system; diff --git a/invokeai/frontend/web/src/features/metadata/util/recallers.ts b/invokeai/frontend/web/src/features/metadata/util/recallers.ts index 4f332e23a9..192077b2ed 100644 --- a/invokeai/frontend/web/src/features/metadata/util/recallers.ts +++ b/invokeai/frontend/web/src/features/metadata/util/recallers.ts @@ -21,8 +21,6 @@ import { setCfgRescaleMultiplier, setCfgScale, setImg2imgStrength, - setNegativePrompt, - setPositivePrompt, setScheduler, setSeed, setSteps, @@ -51,6 +49,7 @@ import type { ParameterVAEModel, ParameterWidth, } from 'features/parameters/types/parameterSchemas'; +import { negativePromptChanged, positivePromptChanged } from 'features/regionalPrompts/store/regionalPromptsSlice'; import { refinerModelChanged, setNegativeStylePromptSDXL, @@ -65,11 +64,11 @@ import { import type { ImageDTO } from 'services/api/types'; const recallPositivePrompt: MetadataRecallFunc = (positivePrompt) => { - getStore().dispatch(setPositivePrompt(positivePrompt)); + getStore().dispatch(positivePromptChanged(positivePrompt)); }; const recallNegativePrompt: MetadataRecallFunc = (negativePrompt) => { - getStore().dispatch(setNegativePrompt(negativePrompt)); + getStore().dispatch(negativePromptChanged(negativePrompt)); }; const recallSDXLPositiveStylePrompt: MetadataRecallFunc = (positiveStylePrompt) => { diff --git a/invokeai/frontend/web/src/features/nodes/util/graph/buildCanvasImageToImageGraph.ts b/invokeai/frontend/web/src/features/nodes/util/graph/buildCanvasImageToImageGraph.ts index 16c42cd111..e0844057ba 100644 --- a/invokeai/frontend/web/src/features/nodes/util/graph/buildCanvasImageToImageGraph.ts +++ b/invokeai/frontend/web/src/features/nodes/util/graph/buildCanvasImageToImageGraph.ts @@ -42,8 +42,6 @@ export const buildCanvasImageToImageGraph = async ( ): Promise => { const log = logger('nodes'); const { - positivePrompt, - negativePrompt, model, cfgScale: cfg_scale, cfgRescaleMultiplier: cfg_rescale_multiplier, @@ -57,6 +55,7 @@ export const buildCanvasImageToImageGraph = async ( seamlessXAxis, seamlessYAxis, } = state.generation; + const { positivePrompt, negativePrompt } = state.regionalPrompts.present.baseLayer; // The bounding box determines width and height, not the width and height params const { width, height } = state.canvas.boundingBoxDimensions; diff --git a/invokeai/frontend/web/src/features/nodes/util/graph/buildCanvasInpaintGraph.ts b/invokeai/frontend/web/src/features/nodes/util/graph/buildCanvasInpaintGraph.ts index f8390b8b9a..6c1c954f58 100644 --- a/invokeai/frontend/web/src/features/nodes/util/graph/buildCanvasInpaintGraph.ts +++ b/invokeai/frontend/web/src/features/nodes/util/graph/buildCanvasInpaintGraph.ts @@ -47,8 +47,6 @@ export const buildCanvasInpaintGraph = async ( ): Promise => { const log = logger('nodes'); const { - positivePrompt, - negativePrompt, model, cfgScale: cfg_scale, cfgRescaleMultiplier: cfg_rescale_multiplier, @@ -66,6 +64,7 @@ export const buildCanvasInpaintGraph = async ( canvasCoherenceEdgeSize, maskBlur, } = state.generation; + const { positivePrompt, negativePrompt } = state.regionalPrompts.present.baseLayer; if (!model) { log.error('No model found in state'); diff --git a/invokeai/frontend/web/src/features/nodes/util/graph/buildCanvasOutpaintGraph.ts b/invokeai/frontend/web/src/features/nodes/util/graph/buildCanvasOutpaintGraph.ts index 39eab4aa50..4a9beef3eb 100644 --- a/invokeai/frontend/web/src/features/nodes/util/graph/buildCanvasOutpaintGraph.ts +++ b/invokeai/frontend/web/src/features/nodes/util/graph/buildCanvasOutpaintGraph.ts @@ -51,8 +51,6 @@ export const buildCanvasOutpaintGraph = async ( ): Promise => { const log = logger('nodes'); const { - positivePrompt, - negativePrompt, model, cfgScale: cfg_scale, cfgRescaleMultiplier: cfg_rescale_multiplier, @@ -78,6 +76,7 @@ export const buildCanvasOutpaintGraph = async ( canvasCoherenceEdgeSize, maskBlur, } = state.generation; + const { positivePrompt, negativePrompt } = state.regionalPrompts.present.baseLayer; if (!model) { log.error('No model found in state'); diff --git a/invokeai/frontend/web/src/features/nodes/util/graph/buildCanvasSDXLImageToImageGraph.ts b/invokeai/frontend/web/src/features/nodes/util/graph/buildCanvasSDXLImageToImageGraph.ts index 059003c34b..8e06dde054 100644 --- a/invokeai/frontend/web/src/features/nodes/util/graph/buildCanvasSDXLImageToImageGraph.ts +++ b/invokeai/frontend/web/src/features/nodes/util/graph/buildCanvasSDXLImageToImageGraph.ts @@ -43,8 +43,6 @@ export const buildCanvasSDXLImageToImageGraph = async ( ): Promise => { const log = logger('nodes'); const { - positivePrompt, - negativePrompt, model, cfgScale: cfg_scale, cfgRescaleMultiplier: cfg_rescale_multiplier, @@ -57,6 +55,7 @@ export const buildCanvasSDXLImageToImageGraph = async ( seamlessYAxis, img2imgStrength: strength, } = state.generation; + const { positivePrompt, negativePrompt } = state.regionalPrompts.present.baseLayer; const { refinerModel, refinerStart } = state.sdxl; diff --git a/invokeai/frontend/web/src/features/nodes/util/graph/buildCanvasSDXLInpaintGraph.ts b/invokeai/frontend/web/src/features/nodes/util/graph/buildCanvasSDXLInpaintGraph.ts index 6a33a6ef99..ee3d0897b8 100644 --- a/invokeai/frontend/web/src/features/nodes/util/graph/buildCanvasSDXLInpaintGraph.ts +++ b/invokeai/frontend/web/src/features/nodes/util/graph/buildCanvasSDXLInpaintGraph.ts @@ -48,8 +48,6 @@ export const buildCanvasSDXLInpaintGraph = async ( ): Promise => { const log = logger('nodes'); const { - positivePrompt, - negativePrompt, model, cfgScale: cfg_scale, cfgRescaleMultiplier: cfg_rescale_multiplier, @@ -66,6 +64,7 @@ export const buildCanvasSDXLInpaintGraph = async ( canvasCoherenceEdgeSize, maskBlur, } = state.generation; + const { positivePrompt, negativePrompt } = state.regionalPrompts.present.baseLayer; const { refinerModel, refinerStart } = state.sdxl; diff --git a/invokeai/frontend/web/src/features/nodes/util/graph/buildCanvasSDXLOutpaintGraph.ts b/invokeai/frontend/web/src/features/nodes/util/graph/buildCanvasSDXLOutpaintGraph.ts index 7cb215d5ec..ddcbb168ef 100644 --- a/invokeai/frontend/web/src/features/nodes/util/graph/buildCanvasSDXLOutpaintGraph.ts +++ b/invokeai/frontend/web/src/features/nodes/util/graph/buildCanvasSDXLOutpaintGraph.ts @@ -52,8 +52,6 @@ export const buildCanvasSDXLOutpaintGraph = async ( ): Promise => { const log = logger('nodes'); const { - positivePrompt, - negativePrompt, model, cfgScale: cfg_scale, cfgRescaleMultiplier: cfg_rescale_multiplier, @@ -78,6 +76,7 @@ export const buildCanvasSDXLOutpaintGraph = async ( canvasCoherenceEdgeSize, maskBlur, } = state.generation; + const { positivePrompt, negativePrompt } = state.regionalPrompts.present.baseLayer; const { refinerModel, refinerStart } = state.sdxl; diff --git a/invokeai/frontend/web/src/features/nodes/util/graph/buildCanvasSDXLTextToImageGraph.ts b/invokeai/frontend/web/src/features/nodes/util/graph/buildCanvasSDXLTextToImageGraph.ts index b7e1ae80b0..9365faee81 100644 --- a/invokeai/frontend/web/src/features/nodes/util/graph/buildCanvasSDXLTextToImageGraph.ts +++ b/invokeai/frontend/web/src/features/nodes/util/graph/buildCanvasSDXLTextToImageGraph.ts @@ -33,8 +33,6 @@ import { addCoreMetadataNode, getModelMetadataField } from './metadata'; export const buildCanvasSDXLTextToImageGraph = async (state: RootState): Promise => { const log = logger('nodes'); const { - positivePrompt, - negativePrompt, model, cfgScale: cfg_scale, cfgRescaleMultiplier: cfg_rescale_multiplier, @@ -46,6 +44,7 @@ export const buildCanvasSDXLTextToImageGraph = async (state: RootState): Promise seamlessXAxis, seamlessYAxis, } = state.generation; + const { positivePrompt, negativePrompt } = state.regionalPrompts.present.baseLayer; // The bounding box determines width and height, not the width and height params const { width, height } = state.canvas.boundingBoxDimensions; diff --git a/invokeai/frontend/web/src/features/nodes/util/graph/buildCanvasTextToImageGraph.ts b/invokeai/frontend/web/src/features/nodes/util/graph/buildCanvasTextToImageGraph.ts index c14da86e3e..57529f9a02 100644 --- a/invokeai/frontend/web/src/features/nodes/util/graph/buildCanvasTextToImageGraph.ts +++ b/invokeai/frontend/web/src/features/nodes/util/graph/buildCanvasTextToImageGraph.ts @@ -32,8 +32,6 @@ import { addCoreMetadataNode, getModelMetadataField } from './metadata'; export const buildCanvasTextToImageGraph = async (state: RootState): Promise => { const log = logger('nodes'); const { - positivePrompt, - negativePrompt, model, cfgScale: cfg_scale, cfgRescaleMultiplier: cfg_rescale_multiplier, @@ -46,6 +44,7 @@ export const buildCanvasTextToImageGraph = async (state: RootState): Promise => { const log = logger('nodes'); const { - positivePrompt, - negativePrompt, model, cfgScale: cfg_scale, cfgRescaleMultiplier: cfg_rescale_multiplier, @@ -57,6 +55,7 @@ export const buildLinearImageToImageGraph = async (state: RootState): Promise => { const log = logger('nodes'); const { - positivePrompt, - negativePrompt, model, cfgScale: cfg_scale, cfgRescaleMultiplier: cfg_rescale_multiplier, @@ -57,6 +55,7 @@ export const buildLinearSDXLImageToImageGraph = async (state: RootState): Promis seamlessYAxis, img2imgStrength: strength, } = state.generation; + const { positivePrompt, negativePrompt } = state.regionalPrompts.present.baseLayer; const { refinerModel, refinerStart } = state.sdxl; diff --git a/invokeai/frontend/web/src/features/nodes/util/graph/buildLinearSDXLTextToImageGraph.ts b/invokeai/frontend/web/src/features/nodes/util/graph/buildLinearSDXLTextToImageGraph.ts index b0b1140d5b..7e9b4cac3a 100644 --- a/invokeai/frontend/web/src/features/nodes/util/graph/buildLinearSDXLTextToImageGraph.ts +++ b/invokeai/frontend/web/src/features/nodes/util/graph/buildLinearSDXLTextToImageGraph.ts @@ -30,8 +30,6 @@ import { addCoreMetadataNode, getModelMetadataField } from './metadata'; export const buildLinearSDXLTextToImageGraph = async (state: RootState): Promise => { const log = logger('nodes'); const { - positivePrompt, - negativePrompt, model, cfgScale: cfg_scale, cfgRescaleMultiplier: cfg_rescale_multiplier, @@ -45,6 +43,7 @@ export const buildLinearSDXLTextToImageGraph = async (state: RootState): Promise seamlessXAxis, seamlessYAxis, } = state.generation; + const { positivePrompt, negativePrompt } = state.regionalPrompts.present.baseLayer; const { refinerModel, refinerStart } = state.sdxl; diff --git a/invokeai/frontend/web/src/features/nodes/util/graph/buildLinearTextToImageGraph.ts b/invokeai/frontend/web/src/features/nodes/util/graph/buildLinearTextToImageGraph.ts index 90101add6d..35d02f38ea 100644 --- a/invokeai/frontend/web/src/features/nodes/util/graph/buildLinearTextToImageGraph.ts +++ b/invokeai/frontend/web/src/features/nodes/util/graph/buildLinearTextToImageGraph.ts @@ -30,8 +30,6 @@ import { addCoreMetadataNode, getModelMetadataField } from './metadata'; export const buildLinearTextToImageGraph = async (state: RootState): Promise => { const log = logger('nodes'); const { - positivePrompt, - negativePrompt, model, cfgScale: cfg_scale, cfgRescaleMultiplier: cfg_rescale_multiplier, @@ -46,6 +44,7 @@ export const buildLinearTextToImageGraph = async (state: RootState): Promise { * Gets the SDXL style prompts, based on the concat setting. */ export const getSDXLStylePrompts = (state: RootState): { positiveStylePrompt: string; negativeStylePrompt: string } => { - const { positivePrompt, negativePrompt } = state.generation; + const { positivePrompt, negativePrompt } = state.regionalPrompts.present.baseLayer; const { positiveStylePrompt, negativeStylePrompt, shouldConcatSDXLStylePrompt } = state.sdxl; return { diff --git a/invokeai/frontend/web/src/features/parameters/components/Core/ParamNegativePrompt.tsx b/invokeai/frontend/web/src/features/parameters/components/Core/ParamNegativePrompt.tsx index 55d97757e6..70e36e8dcc 100644 --- a/invokeai/frontend/web/src/features/parameters/components/Core/ParamNegativePrompt.tsx +++ b/invokeai/frontend/web/src/features/parameters/components/Core/ParamNegativePrompt.tsx @@ -1,21 +1,21 @@ import { Box, Textarea } from '@invoke-ai/ui-library'; import { useAppDispatch, useAppSelector } from 'app/store/storeHooks'; import { PromptOverlayButtonWrapper } from 'features/parameters/components/Prompts/PromptOverlayButtonWrapper'; -import { setNegativePrompt } from 'features/parameters/store/generationSlice'; import { AddPromptTriggerButton } from 'features/prompt/AddPromptTriggerButton'; import { PromptPopover } from 'features/prompt/PromptPopover'; import { usePrompt } from 'features/prompt/usePrompt'; +import { negativePromptChanged } from 'features/regionalPrompts/store/regionalPromptsSlice'; import { memo, useCallback, useRef } from 'react'; import { useTranslation } from 'react-i18next'; export const ParamNegativePrompt = memo(() => { const dispatch = useAppDispatch(); - const prompt = useAppSelector((s) => s.generation.negativePrompt); + const prompt = useAppSelector((s) => s.regionalPrompts.present.baseLayer.negativePrompt); const textareaRef = useRef(null); const { t } = useTranslation(); const _onChange = useCallback( (v: string) => { - dispatch(setNegativePrompt(v)); + dispatch(negativePromptChanged(v)); }, [dispatch] ); diff --git a/invokeai/frontend/web/src/features/parameters/components/Core/ParamPositivePrompt.tsx b/invokeai/frontend/web/src/features/parameters/components/Core/ParamPositivePrompt.tsx index a1c63def8d..732a50fa1b 100644 --- a/invokeai/frontend/web/src/features/parameters/components/Core/ParamPositivePrompt.tsx +++ b/invokeai/frontend/web/src/features/parameters/components/Core/ParamPositivePrompt.tsx @@ -2,10 +2,10 @@ import { Box, Textarea } from '@invoke-ai/ui-library'; import { useAppDispatch, useAppSelector } from 'app/store/storeHooks'; import { ShowDynamicPromptsPreviewButton } from 'features/dynamicPrompts/components/ShowDynamicPromptsPreviewButton'; import { PromptOverlayButtonWrapper } from 'features/parameters/components/Prompts/PromptOverlayButtonWrapper'; -import { setPositivePrompt } from 'features/parameters/store/generationSlice'; import { AddPromptTriggerButton } from 'features/prompt/AddPromptTriggerButton'; import { PromptPopover } from 'features/prompt/PromptPopover'; import { usePrompt } from 'features/prompt/usePrompt'; +import { positivePromptChanged } from 'features/regionalPrompts/store/regionalPromptsSlice'; import { SDXLConcatButton } from 'features/sdxl/components/SDXLPrompts/SDXLConcatButton'; import { memo, useCallback, useRef } from 'react'; import type { HotkeyCallback } from 'react-hotkeys-hook'; @@ -14,14 +14,14 @@ import { useTranslation } from 'react-i18next'; export const ParamPositivePrompt = memo(() => { const dispatch = useAppDispatch(); - const prompt = useAppSelector((s) => s.generation.positivePrompt); + const prompt = useAppSelector((s) => s.regionalPrompts.present.baseLayer.positivePrompt); const baseModel = useAppSelector((s) => s.generation.model)?.base; const textareaRef = useRef(null); const { t } = useTranslation(); const handleChange = useCallback( (v: string) => { - dispatch(setPositivePrompt(v)); + dispatch(positivePromptChanged(v)); }, [dispatch] ); diff --git a/invokeai/frontend/web/src/features/parameters/store/generationSlice.ts b/invokeai/frontend/web/src/features/parameters/store/generationSlice.ts index 0da6e21d9f..d8b747e17e 100644 --- a/invokeai/frontend/web/src/features/parameters/store/generationSlice.ts +++ b/invokeai/frontend/web/src/features/parameters/store/generationSlice.ts @@ -32,8 +32,6 @@ const initialGenerationState: GenerationState = { img2imgStrength: 0.75, infillMethod: 'patchmatch', iterations: 1, - positivePrompt: '', - negativePrompt: '', scheduler: 'euler', maskBlur: 16, maskBlurMethod: 'box', @@ -67,12 +65,6 @@ export const generationSlice = createSlice({ name: 'generation', initialState: initialGenerationState, reducers: { - setPositivePrompt: (state, action: PayloadAction) => { - state.positivePrompt = action.payload; - }, - setNegativePrompt: (state, action: PayloadAction) => { - state.negativePrompt = action.payload; - }, setIterations: (state, action: PayloadAction) => { state.iterations = action.payload; }, @@ -259,8 +251,6 @@ export const { setImg2imgStrength, setInfillMethod, setIterations, - setPositivePrompt, - setNegativePrompt, setScheduler, setMaskBlur, setCanvasCoherenceMode, diff --git a/invokeai/frontend/web/src/features/parameters/store/types.ts b/invokeai/frontend/web/src/features/parameters/store/types.ts index 773cfbf925..55e6ae63f9 100644 --- a/invokeai/frontend/web/src/features/parameters/store/types.ts +++ b/invokeai/frontend/web/src/features/parameters/store/types.ts @@ -7,8 +7,6 @@ import type { ParameterHeight, ParameterMaskBlurMethod, ParameterModel, - ParameterNegativePrompt, - ParameterPositivePrompt, ParameterPrecision, ParameterScheduler, ParameterSeed, @@ -28,8 +26,6 @@ export interface GenerationState { infillMethod: string; initialImage?: { imageName: string; width: number; height: number }; iterations: number; - positivePrompt: ParameterPositivePrompt; - negativePrompt: ParameterNegativePrompt; scheduler: ParameterScheduler; maskBlur: number; maskBlurMethod: ParameterMaskBlurMethod; diff --git a/invokeai/frontend/web/src/features/queue/components/QueueButtonTooltip.tsx b/invokeai/frontend/web/src/features/queue/components/QueueButtonTooltip.tsx index 5d1b7264ea..3ea7d95309 100644 --- a/invokeai/frontend/web/src/features/queue/components/QueueButtonTooltip.tsx +++ b/invokeai/frontend/web/src/features/queue/components/QueueButtonTooltip.tsx @@ -4,17 +4,17 @@ import { useAppSelector } from 'app/store/storeHooks'; import { useIsReadyToEnqueue } from 'common/hooks/useIsReadyToEnqueue'; import { selectDynamicPromptsSlice } from 'features/dynamicPrompts/store/dynamicPromptsSlice'; import { getShouldProcessPrompt } from 'features/dynamicPrompts/util/getShouldProcessPrompt'; -import { selectGenerationSlice } from 'features/parameters/store/generationSlice'; +import { selectRegionalPromptsSlice } from 'features/regionalPrompts/store/regionalPromptsSlice'; import { memo, useMemo } from 'react'; import { useTranslation } from 'react-i18next'; import { useEnqueueBatchMutation } from 'services/api/endpoints/queue'; import { useBoardName } from 'services/api/hooks/useBoardName'; const selectPromptsCount = createSelector( - selectGenerationSlice, + selectRegionalPromptsSlice, selectDynamicPromptsSlice, - (generation, dynamicPrompts) => - getShouldProcessPrompt(generation.positivePrompt) ? dynamicPrompts.prompts.length : 1 + (regionalPrompts, dynamicPrompts) => + getShouldProcessPrompt(regionalPrompts.present.baseLayer.positivePrompt) ? dynamicPrompts.prompts.length : 1 ); type Props = { diff --git a/invokeai/frontend/web/src/features/regionalPrompts/store/regionalPromptsSlice.ts b/invokeai/frontend/web/src/features/regionalPrompts/store/regionalPromptsSlice.ts index 5593f0dff6..ec87b6c57b 100644 --- a/invokeai/frontend/web/src/features/regionalPrompts/store/regionalPromptsSlice.ts +++ b/invokeai/frontend/web/src/features/regionalPrompts/store/regionalPromptsSlice.ts @@ -4,7 +4,11 @@ import type { PersistConfig, RootState } from 'app/store/store'; import { moveBackward, moveForward, moveToBack, moveToFront } from 'common/util/arrayUtils'; import { controlAdapterRemoved } from 'features/controlAdapters/store/controlAdaptersSlice'; import type { ControlAdapterConfig } from 'features/controlAdapters/store/types'; -import type { ParameterAutoNegative } from 'features/parameters/types/parameterSchemas'; +import type { + ParameterAutoNegative, + ParameterNegativePrompt, + ParameterPositivePrompt, +} from 'features/parameters/types/parameterSchemas'; import type { IRect, Vector2d } from 'konva/lib/types'; import { isEqual } from 'lodash-es'; import { atom } from 'nanostores'; @@ -64,6 +68,11 @@ export type VectorMaskLayer = MaskLayerBase & { export type Layer = VectorMaskLayer | ControlLayer; +type BaseLayerState = { + positivePrompt: ParameterPositivePrompt; + negativePrompt: ParameterNegativePrompt; +}; + type RegionalPromptsState = { _version: 1; selectedLayerId: string | null; @@ -71,6 +80,7 @@ type RegionalPromptsState = { brushSize: number; globalMaskLayerOpacity: number; isEnabled: boolean; + baseLayer: BaseLayerState; }; export const initialRegionalPromptsState: RegionalPromptsState = { @@ -80,6 +90,10 @@ export const initialRegionalPromptsState: RegionalPromptsState = { layers: [], globalMaskLayerOpacity: 0.5, // this globally changes all mask layers' opacity isEnabled: true, + baseLayer: { + positivePrompt: '', + negativePrompt: '', + }, }; const isLine = (obj: VectorMaskLine | VectorMaskRect): obj is VectorMaskLine => obj.type === 'vector_mask_line'; @@ -326,6 +340,15 @@ export const regionalPromptsSlice = createSlice({ }, //#endregion + //#region Base Layer + positivePromptChanged: (state, action: PayloadAction) => { + state.baseLayer.positivePrompt = action.payload; + }, + negativePromptChanged: (state, action: PayloadAction) => { + state.baseLayer.negativePrompt = action.payload; + }, + //#endregion + //#region General brushSizeChanged: (state, action: PayloadAction) => { state.brushSize = action.payload; @@ -415,6 +438,9 @@ export const { maskLayerIPAdapterAdded, maskLayerAutoNegativeChanged, maskLayerPreviewColorChanged, + // Base layer actions + positivePromptChanged, + negativePromptChanged, // General actions brushSizeChanged, globalMaskLayerOpacityChanged, From b6a45e53f129b97070d5bf363edaf7c0aecb85ab Mon Sep 17 00:00:00 2001 From: psychedelicious <4822129+psychedelicious@users.noreply.github.com> Date: Thu, 25 Apr 2024 18:50:00 +1000 Subject: [PATCH 04/52] refactor(ui): move positive2 and negative2 prompt to regional --- .../src/features/metadata/util/recallers.ts | 13 ++++++---- .../util/graph/buildLinearBatchConfig.ts | 4 +-- .../nodes/util/graph/graphBuilderUtils.ts | 8 +++--- .../store/regionalPromptsSlice.ts | 20 +++++++++++++++ .../ParamSDXLNegativeStylePrompt.tsx | 6 ++--- .../ParamSDXLPositiveStylePrompt.tsx | 6 ++--- .../SDXLPrompts/SDXLConcatButton.tsx | 14 +++++------ .../components/SDXLPrompts/SDXLPrompts.tsx | 6 ++--- .../web/src/features/sdxl/store/sdxlSlice.ts | 25 +------------------ 9 files changed, 51 insertions(+), 51 deletions(-) diff --git a/invokeai/frontend/web/src/features/metadata/util/recallers.ts b/invokeai/frontend/web/src/features/metadata/util/recallers.ts index 192077b2ed..7850871cb3 100644 --- a/invokeai/frontend/web/src/features/metadata/util/recallers.ts +++ b/invokeai/frontend/web/src/features/metadata/util/recallers.ts @@ -49,11 +49,14 @@ import type { ParameterVAEModel, ParameterWidth, } from 'features/parameters/types/parameterSchemas'; -import { negativePromptChanged, positivePromptChanged } from 'features/regionalPrompts/store/regionalPromptsSlice'; +import { + negativePrompt2Changed, + negativePromptChanged, + positivePrompt2Changed, + positivePromptChanged, +} from 'features/regionalPrompts/store/regionalPromptsSlice'; import { refinerModelChanged, - setNegativeStylePromptSDXL, - setPositiveStylePromptSDXL, setRefinerCFGScale, setRefinerNegativeAestheticScore, setRefinerPositiveAestheticScore, @@ -72,11 +75,11 @@ const recallNegativePrompt: MetadataRecallFunc = (negat }; const recallSDXLPositiveStylePrompt: MetadataRecallFunc = (positiveStylePrompt) => { - getStore().dispatch(setPositiveStylePromptSDXL(positiveStylePrompt)); + getStore().dispatch(positivePrompt2Changed(positiveStylePrompt)); }; const recallSDXLNegativeStylePrompt: MetadataRecallFunc = (negativeStylePrompt) => { - getStore().dispatch(setNegativeStylePromptSDXL(negativeStylePrompt)); + getStore().dispatch(negativePrompt2Changed(negativeStylePrompt)); }; const recallSeed: MetadataRecallFunc = (seed) => { diff --git a/invokeai/frontend/web/src/features/nodes/util/graph/buildLinearBatchConfig.ts b/invokeai/frontend/web/src/features/nodes/util/graph/buildLinearBatchConfig.ts index 9fcc6afaa0..564b986598 100644 --- a/invokeai/frontend/web/src/features/nodes/util/graph/buildLinearBatchConfig.ts +++ b/invokeai/frontend/web/src/features/nodes/util/graph/buildLinearBatchConfig.ts @@ -10,7 +10,7 @@ import { getHasMetadata, removeMetadata } from './metadata'; export const prepareLinearUIBatch = (state: RootState, graph: NonNullableGraph, prepend: boolean): BatchConfig => { const { iterations, model, shouldRandomizeSeed, seed } = state.generation; - const { shouldConcatSDXLStylePrompt } = state.sdxl; + const { shouldConcatPrompts } = state.regionalPrompts.present.baseLayer; const { prompts, seedBehaviour } = state.dynamicPrompts; const data: Batch['data'] = []; @@ -105,7 +105,7 @@ export const prepareLinearUIBatch = (state: RootState, graph: NonNullableGraph, }); } - if (shouldConcatSDXLStylePrompt && model?.base === 'sdxl') { + if (shouldConcatPrompts && model?.base === 'sdxl') { if (graph.nodes[POSITIVE_CONDITIONING]) { firstBatchDatumList.push({ node_path: POSITIVE_CONDITIONING, diff --git a/invokeai/frontend/web/src/features/nodes/util/graph/graphBuilderUtils.ts b/invokeai/frontend/web/src/features/nodes/util/graph/graphBuilderUtils.ts index 89571a3679..b8770d7ede 100644 --- a/invokeai/frontend/web/src/features/nodes/util/graph/graphBuilderUtils.ts +++ b/invokeai/frontend/web/src/features/nodes/util/graph/graphBuilderUtils.ts @@ -17,12 +17,12 @@ export const getBoardField = (state: RootState): BoardField | undefined => { * Gets the SDXL style prompts, based on the concat setting. */ export const getSDXLStylePrompts = (state: RootState): { positiveStylePrompt: string; negativeStylePrompt: string } => { - const { positivePrompt, negativePrompt } = state.regionalPrompts.present.baseLayer; - const { positiveStylePrompt, negativeStylePrompt, shouldConcatSDXLStylePrompt } = state.sdxl; + const { positivePrompt, negativePrompt, positivePrompt2, negativePrompt2, shouldConcatPrompts } = + state.regionalPrompts.present.baseLayer; return { - positiveStylePrompt: shouldConcatSDXLStylePrompt ? positivePrompt : positiveStylePrompt, - negativeStylePrompt: shouldConcatSDXLStylePrompt ? negativePrompt : negativeStylePrompt, + positiveStylePrompt: shouldConcatPrompts ? positivePrompt : positivePrompt2, + negativeStylePrompt: shouldConcatPrompts ? negativePrompt : negativePrompt2, }; }; diff --git a/invokeai/frontend/web/src/features/regionalPrompts/store/regionalPromptsSlice.ts b/invokeai/frontend/web/src/features/regionalPrompts/store/regionalPromptsSlice.ts index ec87b6c57b..8e72e2526b 100644 --- a/invokeai/frontend/web/src/features/regionalPrompts/store/regionalPromptsSlice.ts +++ b/invokeai/frontend/web/src/features/regionalPrompts/store/regionalPromptsSlice.ts @@ -7,7 +7,9 @@ import type { ControlAdapterConfig } from 'features/controlAdapters/store/types' import type { ParameterAutoNegative, ParameterNegativePrompt, + ParameterNegativeStylePromptSDXL, ParameterPositivePrompt, + ParameterPositiveStylePromptSDXL, } from 'features/parameters/types/parameterSchemas'; import type { IRect, Vector2d } from 'konva/lib/types'; import { isEqual } from 'lodash-es'; @@ -71,6 +73,9 @@ export type Layer = VectorMaskLayer | ControlLayer; type BaseLayerState = { positivePrompt: ParameterPositivePrompt; negativePrompt: ParameterNegativePrompt; + positivePrompt2: ParameterPositiveStylePromptSDXL; + negativePrompt2: ParameterNegativeStylePromptSDXL; + shouldConcatPrompts: boolean; }; type RegionalPromptsState = { @@ -93,6 +98,9 @@ export const initialRegionalPromptsState: RegionalPromptsState = { baseLayer: { positivePrompt: '', negativePrompt: '', + positivePrompt2: '', + negativePrompt2: '', + shouldConcatPrompts: true, }, }; @@ -347,6 +355,15 @@ export const regionalPromptsSlice = createSlice({ negativePromptChanged: (state, action: PayloadAction) => { state.baseLayer.negativePrompt = action.payload; }, + positivePrompt2Changed: (state, action: PayloadAction) => { + state.baseLayer.positivePrompt2 = action.payload; + }, + negativePrompt2Changed: (state, action: PayloadAction) => { + state.baseLayer.negativePrompt2 = action.payload; + }, + shouldConcatPromptsChanged: (state, action: PayloadAction) => { + state.baseLayer.shouldConcatPrompts = action.payload; + }, //#endregion //#region General @@ -441,6 +458,9 @@ export const { // Base layer actions positivePromptChanged, negativePromptChanged, + positivePrompt2Changed, + negativePrompt2Changed, + shouldConcatPromptsChanged, // General actions brushSizeChanged, globalMaskLayerOpacityChanged, diff --git a/invokeai/frontend/web/src/features/sdxl/components/SDXLPrompts/ParamSDXLNegativeStylePrompt.tsx b/invokeai/frontend/web/src/features/sdxl/components/SDXLPrompts/ParamSDXLNegativeStylePrompt.tsx index 067319817a..f5d817a2e2 100644 --- a/invokeai/frontend/web/src/features/sdxl/components/SDXLPrompts/ParamSDXLNegativeStylePrompt.tsx +++ b/invokeai/frontend/web/src/features/sdxl/components/SDXLPrompts/ParamSDXLNegativeStylePrompt.tsx @@ -4,19 +4,19 @@ import { PromptOverlayButtonWrapper } from 'features/parameters/components/Promp import { AddPromptTriggerButton } from 'features/prompt/AddPromptTriggerButton'; import { PromptPopover } from 'features/prompt/PromptPopover'; import { usePrompt } from 'features/prompt/usePrompt'; -import { setNegativeStylePromptSDXL } from 'features/sdxl/store/sdxlSlice'; +import { negativePrompt2Changed } from 'features/regionalPrompts/store/regionalPromptsSlice'; import { memo, useCallback, useRef } from 'react'; import { useHotkeys } from 'react-hotkeys-hook'; import { useTranslation } from 'react-i18next'; export const ParamSDXLNegativeStylePrompt = memo(() => { const dispatch = useAppDispatch(); - const prompt = useAppSelector((s) => s.sdxl.negativeStylePrompt); + const prompt = useAppSelector((s) => s.regionalPrompts.present.baseLayer.negativePrompt2); const textareaRef = useRef(null); const { t } = useTranslation(); const handleChange = useCallback( (v: string) => { - dispatch(setNegativeStylePromptSDXL(v)); + dispatch(negativePrompt2Changed(v)); }, [dispatch] ); diff --git a/invokeai/frontend/web/src/features/sdxl/components/SDXLPrompts/ParamSDXLPositiveStylePrompt.tsx b/invokeai/frontend/web/src/features/sdxl/components/SDXLPrompts/ParamSDXLPositiveStylePrompt.tsx index 6fc302cd9c..39ce4eea09 100644 --- a/invokeai/frontend/web/src/features/sdxl/components/SDXLPrompts/ParamSDXLPositiveStylePrompt.tsx +++ b/invokeai/frontend/web/src/features/sdxl/components/SDXLPrompts/ParamSDXLPositiveStylePrompt.tsx @@ -4,18 +4,18 @@ import { PromptOverlayButtonWrapper } from 'features/parameters/components/Promp import { AddPromptTriggerButton } from 'features/prompt/AddPromptTriggerButton'; import { PromptPopover } from 'features/prompt/PromptPopover'; import { usePrompt } from 'features/prompt/usePrompt'; -import { setPositiveStylePromptSDXL } from 'features/sdxl/store/sdxlSlice'; +import { positivePrompt2Changed } from 'features/regionalPrompts/store/regionalPromptsSlice'; import { memo, useCallback, useRef } from 'react'; import { useTranslation } from 'react-i18next'; export const ParamSDXLPositiveStylePrompt = memo(() => { const dispatch = useAppDispatch(); - const prompt = useAppSelector((s) => s.sdxl.positiveStylePrompt); + const prompt = useAppSelector((s) => s.regionalPrompts.present.baseLayer.positivePrompt2); const textareaRef = useRef(null); const { t } = useTranslation(); const handleChange = useCallback( (v: string) => { - dispatch(setPositiveStylePromptSDXL(v)); + dispatch(positivePrompt2Changed(v)); }, [dispatch] ); diff --git a/invokeai/frontend/web/src/features/sdxl/components/SDXLPrompts/SDXLConcatButton.tsx b/invokeai/frontend/web/src/features/sdxl/components/SDXLPrompts/SDXLConcatButton.tsx index 31df7d62d0..01e859825d 100644 --- a/invokeai/frontend/web/src/features/sdxl/components/SDXLPrompts/SDXLConcatButton.tsx +++ b/invokeai/frontend/web/src/features/sdxl/components/SDXLPrompts/SDXLConcatButton.tsx @@ -1,23 +1,23 @@ import { IconButton, Tooltip } from '@invoke-ai/ui-library'; import { useAppDispatch, useAppSelector } from 'app/store/storeHooks'; -import { setShouldConcatSDXLStylePrompt } from 'features/sdxl/store/sdxlSlice'; +import { shouldConcatPromptsChanged } from 'features/regionalPrompts/store/regionalPromptsSlice'; import { memo, useCallback, useMemo } from 'react'; import { useTranslation } from 'react-i18next'; import { PiLinkSimpleBold, PiLinkSimpleBreakBold } from 'react-icons/pi'; export const SDXLConcatButton = memo(() => { - const shouldConcatSDXLStylePrompt = useAppSelector((s) => s.sdxl.shouldConcatSDXLStylePrompt); + const shouldConcatPrompts = useAppSelector((s) => s.regionalPrompts.present.baseLayer.shouldConcatPrompts); const dispatch = useAppDispatch(); const { t } = useTranslation(); const handleShouldConcatPromptChange = useCallback(() => { - dispatch(setShouldConcatSDXLStylePrompt(!shouldConcatSDXLStylePrompt)); - }, [dispatch, shouldConcatSDXLStylePrompt]); + dispatch(shouldConcatPromptsChanged(!shouldConcatPrompts)); + }, [dispatch, shouldConcatPrompts]); const label = useMemo( - () => (shouldConcatSDXLStylePrompt ? t('sdxl.concatPromptStyle') : t('sdxl.freePromptStyle')), - [shouldConcatSDXLStylePrompt, t] + () => (shouldConcatPrompts ? t('sdxl.concatPromptStyle') : t('sdxl.freePromptStyle')), + [shouldConcatPrompts, t] ); return ( @@ -25,7 +25,7 @@ export const SDXLConcatButton = memo(() => { : } + icon={shouldConcatPrompts ? : } variant="promptOverlay" fontSize={12} px={0.5} diff --git a/invokeai/frontend/web/src/features/sdxl/components/SDXLPrompts/SDXLPrompts.tsx b/invokeai/frontend/web/src/features/sdxl/components/SDXLPrompts/SDXLPrompts.tsx index 4aca9a85a6..4f47ecdda4 100644 --- a/invokeai/frontend/web/src/features/sdxl/components/SDXLPrompts/SDXLPrompts.tsx +++ b/invokeai/frontend/web/src/features/sdxl/components/SDXLPrompts/SDXLPrompts.tsx @@ -8,13 +8,13 @@ import { ParamSDXLNegativeStylePrompt } from './ParamSDXLNegativeStylePrompt'; import { ParamSDXLPositiveStylePrompt } from './ParamSDXLPositiveStylePrompt'; export const SDXLPrompts = memo(() => { - const shouldConcatSDXLStylePrompt = useAppSelector((s) => s.sdxl.shouldConcatSDXLStylePrompt); + const shouldConcatPrompts = useAppSelector((s) => s.regionalPrompts.present.baseLayer.shouldConcatPrompts); return ( - {!shouldConcatSDXLStylePrompt && } + {!shouldConcatPrompts && } - {!shouldConcatSDXLStylePrompt && } + {!shouldConcatPrompts && } ); }); diff --git a/invokeai/frontend/web/src/features/sdxl/store/sdxlSlice.ts b/invokeai/frontend/web/src/features/sdxl/store/sdxlSlice.ts index 91e1418e1d..10a8f861f1 100644 --- a/invokeai/frontend/web/src/features/sdxl/store/sdxlSlice.ts +++ b/invokeai/frontend/web/src/features/sdxl/store/sdxlSlice.ts @@ -1,18 +1,10 @@ import type { PayloadAction } from '@reduxjs/toolkit'; import { createSlice } from '@reduxjs/toolkit'; import type { PersistConfig, RootState } from 'app/store/store'; -import type { - ParameterNegativeStylePromptSDXL, - ParameterPositiveStylePromptSDXL, - ParameterScheduler, - ParameterSDXLRefinerModel, -} from 'features/parameters/types/parameterSchemas'; +import type { ParameterScheduler, ParameterSDXLRefinerModel } from 'features/parameters/types/parameterSchemas'; type SDXLState = { _version: 2; - positiveStylePrompt: ParameterPositiveStylePromptSDXL; - negativeStylePrompt: ParameterNegativeStylePromptSDXL; - shouldConcatSDXLStylePrompt: boolean; refinerModel: ParameterSDXLRefinerModel | null; refinerSteps: number; refinerCFGScale: number; @@ -24,9 +16,6 @@ type SDXLState = { const initialSDXLState: SDXLState = { _version: 2, - positiveStylePrompt: '', - negativeStylePrompt: '', - shouldConcatSDXLStylePrompt: true, refinerModel: null, refinerSteps: 20, refinerCFGScale: 7.5, @@ -40,15 +29,6 @@ export const sdxlSlice = createSlice({ name: 'sdxl', initialState: initialSDXLState, reducers: { - setPositiveStylePromptSDXL: (state, action: PayloadAction) => { - state.positiveStylePrompt = action.payload; - }, - setNegativeStylePromptSDXL: (state, action: PayloadAction) => { - state.negativeStylePrompt = action.payload; - }, - setShouldConcatSDXLStylePrompt: (state, action: PayloadAction) => { - state.shouldConcatSDXLStylePrompt = action.payload; - }, refinerModelChanged: (state, action: PayloadAction) => { state.refinerModel = action.payload; }, @@ -74,9 +54,6 @@ export const sdxlSlice = createSlice({ }); export const { - setPositiveStylePromptSDXL, - setNegativeStylePromptSDXL, - setShouldConcatSDXLStylePrompt, refinerModelChanged, setRefinerSteps, setRefinerCFGScale, From cc4bef4859f436be4e64a80a20d9d5fb92b39374 Mon Sep 17 00:00:00 2001 From: psychedelicious <4822129+psychedelicious@users.noreply.github.com> Date: Thu, 25 Apr 2024 20:08:21 +1000 Subject: [PATCH 05/52] refactor(ui): move size state to regional --- .../listeners/modelsLoaded.ts | 17 +++-- .../listeners/setDefaultSettings.ts | 7 +- .../components/ControlAdapterImagePreview.tsx | 7 +- .../src/features/metadata/util/recallers.ts | 8 +-- .../nodes/util/graph/addHrfToGraph.ts | 3 +- .../graph/buildLinearImageToImageGraph.ts | 3 +- .../graph/buildLinearSDXLImageToImageGraph.ts | 3 +- .../graph/buildLinearSDXLTextToImageGraph.ts | 3 +- .../util/graph/buildLinearTextToImageGraph.ts | 3 +- .../parameters/store/generationSlice.ts | 57 +-------------- .../src/features/parameters/store/types.ts | 6 -- .../components/StageComponent.tsx | 16 ++--- .../store/regionalPromptsSlice.ts | 69 ++++++++++++++++++- .../regionalPrompts/util/getLayerBlobs.ts | 6 +- .../ImageSettingsAccordion.tsx | 7 +- .../ImageSizeLinear.tsx | 12 ++-- 16 files changed, 118 insertions(+), 109 deletions(-) diff --git a/invokeai/frontend/web/src/app/store/middleware/listenerMiddleware/listeners/modelsLoaded.ts b/invokeai/frontend/web/src/app/store/middleware/listenerMiddleware/listeners/modelsLoaded.ts index 2ba9aa3cbf..587f06720f 100644 --- a/invokeai/frontend/web/src/app/store/middleware/listenerMiddleware/listeners/modelsLoaded.ts +++ b/invokeai/frontend/web/src/app/store/middleware/listenerMiddleware/listeners/modelsLoaded.ts @@ -8,9 +8,10 @@ import { } from 'features/controlAdapters/store/controlAdaptersSlice'; import { loraRemoved } from 'features/lora/store/loraSlice'; import { calculateNewSize } from 'features/parameters/components/ImageSize/calculateNewSize'; -import { heightChanged, modelChanged, vaeSelected, widthChanged } from 'features/parameters/store/generationSlice'; +import { modelChanged, vaeSelected } from 'features/parameters/store/generationSlice'; import { zParameterModel, zParameterVAEModel } from 'features/parameters/types/parameterSchemas'; import { getIsSizeOptimal, getOptimalDimension } from 'features/parameters/util/optimalDimension'; +import { heightChanged, widthChanged } from 'features/regionalPrompts/store/regionalPromptsSlice'; import { refinerModelChanged } from 'features/sdxl/store/sdxlSlice'; import { forEach } from 'lodash-es'; import type { Logger } from 'roarr'; @@ -69,16 +70,22 @@ const handleMainModels: ModelHandler = (models, state, dispatch, log) => { dispatch(modelChanged(defaultModelInList, currentModel)); const optimalDimension = getOptimalDimension(defaultModelInList); - if (getIsSizeOptimal(state.generation.width, state.generation.height, optimalDimension)) { + if ( + getIsSizeOptimal( + state.regionalPrompts.present.size.width, + state.regionalPrompts.present.size.height, + optimalDimension + ) + ) { return; } const { width, height } = calculateNewSize( - state.generation.aspectRatio.value, + state.regionalPrompts.present.size.aspectRatio.value, optimalDimension * optimalDimension ); - dispatch(widthChanged(width)); - dispatch(heightChanged(height)); + dispatch(widthChanged({ width })); + dispatch(heightChanged({ height })); return; } } diff --git a/invokeai/frontend/web/src/app/store/middleware/listenerMiddleware/listeners/setDefaultSettings.ts b/invokeai/frontend/web/src/app/store/middleware/listenerMiddleware/listeners/setDefaultSettings.ts index 7fbb55845f..83fadffb26 100644 --- a/invokeai/frontend/web/src/app/store/middleware/listenerMiddleware/listeners/setDefaultSettings.ts +++ b/invokeai/frontend/web/src/app/store/middleware/listenerMiddleware/listeners/setDefaultSettings.ts @@ -1,14 +1,12 @@ import type { AppStartListening } from 'app/store/middleware/listenerMiddleware'; import { setDefaultSettings } from 'features/parameters/store/actions'; import { - heightRecalled, setCfgRescaleMultiplier, setCfgScale, setScheduler, setSteps, vaePrecisionChanged, vaeSelected, - widthRecalled, } from 'features/parameters/store/generationSlice'; import { isParameterCFGRescaleMultiplier, @@ -20,6 +18,7 @@ import { isParameterWidth, zParameterVAEModel, } from 'features/parameters/types/parameterSchemas'; +import { heightChanged, widthChanged } from 'features/regionalPrompts/store/regionalPromptsSlice'; import { addToast } from 'features/system/store/systemSlice'; import { makeToast } from 'features/system/util/makeToast'; import { t } from 'i18next'; @@ -100,13 +99,13 @@ export const addSetDefaultSettingsListener = (startAppListening: AppStartListeni if (width) { if (isParameterWidth(width)) { - dispatch(widthRecalled(width)); + dispatch(widthChanged({ width, updateAspectRatio: true })); } } if (height) { if (isParameterHeight(height)) { - dispatch(heightRecalled(height)); + dispatch(heightChanged({ height, updateAspectRatio: true })); } } diff --git a/invokeai/frontend/web/src/features/controlAdapters/components/ControlAdapterImagePreview.tsx b/invokeai/frontend/web/src/features/controlAdapters/components/ControlAdapterImagePreview.tsx index c136fbe064..33269656ad 100644 --- a/invokeai/frontend/web/src/features/controlAdapters/components/ControlAdapterImagePreview.tsx +++ b/invokeai/frontend/web/src/features/controlAdapters/components/ControlAdapterImagePreview.tsx @@ -15,7 +15,8 @@ import { } from 'features/controlAdapters/store/controlAdaptersSlice'; import type { TypesafeDraggableData, TypesafeDroppableData } from 'features/dnd/types'; import { calculateNewSize } from 'features/parameters/components/ImageSize/calculateNewSize'; -import { heightChanged, selectOptimalDimension, widthChanged } from 'features/parameters/store/generationSlice'; +import { selectOptimalDimension } from 'features/parameters/store/generationSlice'; +import { heightChanged, widthChanged } from 'features/regionalPrompts/store/regionalPromptsSlice'; import { activeTabNameSelector } from 'features/ui/store/uiSelectors'; import { memo, useCallback, useEffect, useMemo, useState } from 'react'; import { useTranslation } from 'react-i18next'; @@ -99,8 +100,8 @@ const ControlAdapterImagePreview = ({ isSmall, id }: Props) => { controlImage.width / controlImage.height, optimalDimension * optimalDimension ); - dispatch(widthChanged(width)); - dispatch(heightChanged(height)); + dispatch(widthChanged({ width, updateAspectRatio: true })); + dispatch(heightChanged({ height, updateAspectRatio: true })); } }, [controlImage, activeTabName, dispatch, optimalDimension]); diff --git a/invokeai/frontend/web/src/features/metadata/util/recallers.ts b/invokeai/frontend/web/src/features/metadata/util/recallers.ts index 7850871cb3..fd0d8c9a33 100644 --- a/invokeai/frontend/web/src/features/metadata/util/recallers.ts +++ b/invokeai/frontend/web/src/features/metadata/util/recallers.ts @@ -16,7 +16,6 @@ import type { } from 'features/metadata/types'; import { modelSelected } from 'features/parameters/store/actions'; import { - heightRecalled, initialImageChanged, setCfgRescaleMultiplier, setCfgScale, @@ -25,7 +24,6 @@ import { setSeed, setSteps, vaeSelected, - widthRecalled, } from 'features/parameters/store/generationSlice'; import type { ParameterCFGRescaleMultiplier, @@ -50,10 +48,12 @@ import type { ParameterWidth, } from 'features/parameters/types/parameterSchemas'; import { + heightChanged, negativePrompt2Changed, negativePromptChanged, positivePrompt2Changed, positivePromptChanged, + widthChanged, } from 'features/regionalPrompts/store/regionalPromptsSlice'; import { refinerModelChanged, @@ -103,11 +103,11 @@ const recallInitialImage: MetadataRecallFunc = async (imageDTO) => { }; const recallWidth: MetadataRecallFunc = (width) => { - getStore().dispatch(widthRecalled(width)); + getStore().dispatch(widthChanged({ width, updateAspectRatio: true })); }; const recallHeight: MetadataRecallFunc = (height) => { - getStore().dispatch(heightRecalled(height)); + getStore().dispatch(heightChanged({ height, updateAspectRatio: true })); }; const recallSteps: MetadataRecallFunc = (steps) => { diff --git a/invokeai/frontend/web/src/features/nodes/util/graph/addHrfToGraph.ts b/invokeai/frontend/web/src/features/nodes/util/graph/addHrfToGraph.ts index 5632cfd112..d7c512728e 100644 --- a/invokeai/frontend/web/src/features/nodes/util/graph/addHrfToGraph.ts +++ b/invokeai/frontend/web/src/features/nodes/util/graph/addHrfToGraph.ts @@ -110,10 +110,9 @@ export const addHrfToGraph = (state: RootState, graph: NonNullableGraph): void = const { vae, seamlessXAxis, seamlessYAxis } = state.generation; const { hrfStrength, hrfEnabled, hrfMethod } = state.hrf; + const { width, height } = state.regionalPrompts.present.size; const isAutoVae = !vae; const isSeamlessEnabled = seamlessXAxis || seamlessYAxis; - const width = state.generation.width; - const height = state.generation.height; const optimalDimension = selectOptimalDimension(state); const { newWidth: hrfWidth, newHeight: hrfHeight } = calculateHrfRes(optimalDimension, width, height); diff --git a/invokeai/frontend/web/src/features/nodes/util/graph/buildLinearImageToImageGraph.ts b/invokeai/frontend/web/src/features/nodes/util/graph/buildLinearImageToImageGraph.ts index 08bad689ce..5c9b6b96db 100644 --- a/invokeai/frontend/web/src/features/nodes/util/graph/buildLinearImageToImageGraph.ts +++ b/invokeai/frontend/web/src/features/nodes/util/graph/buildLinearImageToImageGraph.ts @@ -47,8 +47,6 @@ export const buildLinearImageToImageGraph = async (state: RootState): Promise ({ payload, @@ -174,27 +154,6 @@ export const generationSlice = createSlice({ shouldUseCpuNoiseChanged: (state, action: PayloadAction) => { state.shouldUseCpuNoise = action.payload; }, - widthChanged: (state, action: PayloadAction) => { - state.width = action.payload; - }, - heightChanged: (state, action: PayloadAction) => { - state.height = action.payload; - }, - widthRecalled: (state, action: PayloadAction) => { - state.width = action.payload; - state.aspectRatio.value = action.payload / state.height; - state.aspectRatio.id = 'Free'; - state.aspectRatio.isLocked = false; - }, - heightRecalled: (state, action: PayloadAction) => { - state.height = action.payload; - state.aspectRatio.value = state.width / action.payload; - state.aspectRatio.id = 'Free'; - state.aspectRatio.isLocked = false; - }, - aspectRatioChanged: (state, action: PayloadAction) => { - state.aspectRatio = action.payload; - }, setInfillMethod: (state, action: PayloadAction) => { state.infillMethod = action.payload; }, @@ -229,15 +188,6 @@ export const generationSlice = createSlice({ state.vaePrecision = action.payload.sd.vaePrecision; } }); - - // TODO: This is a temp fix to reduce issues with T2I adapter having a different downscaling - // factor than the UNet. Hopefully we get an upstream fix in diffusers. - builder.addMatcher(isAnyControlAdapterAdded, (state, action) => { - if (action.payload.type === 't2i_adapter') { - state.width = roundToMultiple(state.width, 64); - state.height = roundToMultiple(state.height, 64); - } - }); }, selectors: { selectOptimalDimension: (slice) => getOptimalDimension(slice.model), @@ -268,11 +218,6 @@ export const { setClipSkip, shouldUseCpuNoiseChanged, vaePrecisionChanged, - aspectRatioChanged, - widthChanged, - heightChanged, - widthRecalled, - heightRecalled, setInfillTileSize, setInfillPatchmatchDownscaleSize, setInfillMosaicTileWidth, diff --git a/invokeai/frontend/web/src/features/parameters/store/types.ts b/invokeai/frontend/web/src/features/parameters/store/types.ts index 55e6ae63f9..9314f8d076 100644 --- a/invokeai/frontend/web/src/features/parameters/store/types.ts +++ b/invokeai/frontend/web/src/features/parameters/store/types.ts @@ -1,10 +1,8 @@ import type { PayloadAction } from '@reduxjs/toolkit'; -import type { AspectRatioState } from 'features/parameters/components/ImageSize/types'; import type { ParameterCanvasCoherenceMode, ParameterCFGRescaleMultiplier, ParameterCFGScale, - ParameterHeight, ParameterMaskBlurMethod, ParameterModel, ParameterPrecision, @@ -13,7 +11,6 @@ import type { ParameterSteps, ParameterStrength, ParameterVAEModel, - ParameterWidth, } from 'features/parameters/types/parameterSchemas'; import type { RgbaColor } from 'react-colorful'; @@ -21,7 +18,6 @@ export interface GenerationState { _version: 2; cfgScale: ParameterCFGScale; cfgRescaleMultiplier: ParameterCFGRescaleMultiplier; - height: ParameterHeight; img2imgStrength: ParameterStrength; infillMethod: string; initialImage?: { imageName: string; width: number; height: number }; @@ -36,7 +32,6 @@ export interface GenerationState { shouldFitToWidthHeight: boolean; shouldRandomizeSeed: boolean; steps: ParameterSteps; - width: ParameterWidth; model: ParameterModel | null; vae: ParameterVAEModel | null; vaePrecision: ParameterPrecision; @@ -45,7 +40,6 @@ export interface GenerationState { clipSkip: number; shouldUseCpuNoise: boolean; shouldShowAdvancedOptions: boolean; - aspectRatio: AspectRatioState; infillTileSize: number; infillPatchmatchDownscaleSize: number; infillMosaicTileWidth: number; diff --git a/invokeai/frontend/web/src/features/regionalPrompts/components/StageComponent.tsx b/invokeai/frontend/web/src/features/regionalPrompts/components/StageComponent.tsx index f286b75711..d6f6eff812 100644 --- a/invokeai/frontend/web/src/features/regionalPrompts/components/StageComponent.tsx +++ b/invokeai/frontend/web/src/features/regionalPrompts/components/StageComponent.tsx @@ -43,8 +43,6 @@ const useStageRenderer = ( asPreview: boolean ) => { const dispatch = useAppDispatch(); - const width = useAppSelector((s) => s.generation.width); - const height = useAppSelector((s) => s.generation.height); const state = useAppSelector((s) => s.regionalPrompts.present); const tool = useStore($tool); const { onMouseDown, onMouseUp, onMouseMove, onMouseEnter, onMouseLeave, onMouseWheel } = useMouseEvents(); @@ -121,11 +119,11 @@ const useStageRenderer = ( const stage = stageRef.current; const fitStageToContainer = () => { - const newXScale = wrapper.offsetWidth / width; - const newYScale = wrapper.offsetHeight / height; + const newXScale = wrapper.offsetWidth / state.size.width; + const newYScale = wrapper.offsetHeight / state.size.height; const newScale = Math.min(newXScale, newYScale, 1); - stage.width(width * newScale); - stage.height(height * newScale); + stage.width(state.size.width * newScale); + stage.height(state.size.height * newScale); stage.scaleX(newScale); stage.scaleY(newScale); }; @@ -137,7 +135,7 @@ const useStageRenderer = ( return () => { resizeObserver.disconnect(); }; - }, [stageRef, width, height, wrapper]); + }, [stageRef, state.size.width, state.size.height, wrapper]); useLayoutEffect(() => { log.trace('Rendering tool preview'); @@ -188,8 +186,8 @@ const useStageRenderer = ( // The preview should not have a background return; } - renderers.renderBackground(stageRef.current, width, height); - }, [stageRef, asPreview, width, height, renderers]); + renderers.renderBackground(stageRef.current, state.size.width, state.size.height); + }, [stageRef, asPreview, state.size.width, state.size.height, renderers]); useLayoutEffect(() => { log.trace('Arranging layers'); diff --git a/invokeai/frontend/web/src/features/regionalPrompts/store/regionalPromptsSlice.ts b/invokeai/frontend/web/src/features/regionalPrompts/store/regionalPromptsSlice.ts index 8e72e2526b..7d809f2695 100644 --- a/invokeai/frontend/web/src/features/regionalPrompts/store/regionalPromptsSlice.ts +++ b/invokeai/frontend/web/src/features/regionalPrompts/store/regionalPromptsSlice.ts @@ -2,15 +2,24 @@ import type { PayloadAction, UnknownAction } from '@reduxjs/toolkit'; import { createSlice, isAnyOf } from '@reduxjs/toolkit'; import type { PersistConfig, RootState } from 'app/store/store'; import { moveBackward, moveForward, moveToBack, moveToFront } from 'common/util/arrayUtils'; -import { controlAdapterRemoved } from 'features/controlAdapters/store/controlAdaptersSlice'; +import { deepClone } from 'common/util/deepClone'; +import { roundToMultiple } from 'common/util/roundDownToMultiple'; +import { controlAdapterRemoved, isAnyControlAdapterAdded } from 'features/controlAdapters/store/controlAdaptersSlice'; import type { ControlAdapterConfig } from 'features/controlAdapters/store/types'; +import { calculateNewSize } from 'features/parameters/components/ImageSize/calculateNewSize'; +import { initialAspectRatioState } from 'features/parameters/components/ImageSize/constants'; +import type { AspectRatioState } from 'features/parameters/components/ImageSize/types'; +import { modelChanged } from 'features/parameters/store/generationSlice'; import type { ParameterAutoNegative, + ParameterHeight, ParameterNegativePrompt, ParameterNegativeStylePromptSDXL, ParameterPositivePrompt, ParameterPositiveStylePromptSDXL, + ParameterWidth, } from 'features/parameters/types/parameterSchemas'; +import { getIsSizeOptimal, getOptimalDimension } from 'features/parameters/util/optimalDimension'; import type { IRect, Vector2d } from 'konva/lib/types'; import { isEqual } from 'lodash-es'; import { atom } from 'nanostores'; @@ -86,6 +95,11 @@ type RegionalPromptsState = { globalMaskLayerOpacity: number; isEnabled: boolean; baseLayer: BaseLayerState; + size: { + width: ParameterWidth; + height: ParameterHeight; + aspectRatio: AspectRatioState; + }; }; export const initialRegionalPromptsState: RegionalPromptsState = { @@ -102,6 +116,11 @@ export const initialRegionalPromptsState: RegionalPromptsState = { negativePrompt2: '', shouldConcatPrompts: true, }, + size: { + width: 512, + height: 512, + aspectRatio: deepClone(initialAspectRatioState), + }, }; const isLine = (obj: VectorMaskLine | VectorMaskRect): obj is VectorMaskLine => obj.type === 'vector_mask_line'; @@ -364,6 +383,27 @@ export const regionalPromptsSlice = createSlice({ shouldConcatPromptsChanged: (state, action: PayloadAction) => { state.baseLayer.shouldConcatPrompts = action.payload; }, + widthChanged: (state, action: PayloadAction<{ width: number; updateAspectRatio?: boolean }>) => { + const { width, updateAspectRatio } = action.payload; + state.size.width = width; + if (updateAspectRatio) { + state.size.aspectRatio.value = width / state.size.height; + state.size.aspectRatio.id = 'Free'; + state.size.aspectRatio.isLocked = false; + } + }, + heightChanged: (state, action: PayloadAction<{ height: number; updateAspectRatio?: boolean }>) => { + const { height, updateAspectRatio } = action.payload; + state.size.height = height; + if (updateAspectRatio) { + state.size.aspectRatio.value = state.size.width / height; + state.size.aspectRatio.id = 'Free'; + state.size.aspectRatio.isLocked = false; + } + }, + aspectRatioChanged: (state, action: PayloadAction) => { + state.size.aspectRatio = action.payload; + }, //#endregion //#region General @@ -396,6 +436,30 @@ export const regionalPromptsSlice = createSlice({ layer.ipAdapterIds = layer.ipAdapterIds.filter((id) => id !== action.payload.id); }); }); + + builder.addCase(modelChanged, (state, action) => { + const newModel = action.payload; + if (!newModel || action.meta.previousModel?.base === newModel.base) { + // Model was cleared or the base didn't change + return; + } + const optimalDimension = getOptimalDimension(newModel); + if (getIsSizeOptimal(state.size.width, state.size.height, optimalDimension)) { + return; + } + const { width, height } = calculateNewSize(state.size.aspectRatio.value, optimalDimension * optimalDimension); + state.size.width = width; + state.size.height = height; + }); + + // TODO: This is a temp fix to reduce issues with T2I adapter having a different downscaling + // factor than the UNet. Hopefully we get an upstream fix in diffusers. + builder.addMatcher(isAnyControlAdapterAdded, (state, action) => { + if (action.payload.type === 't2i_adapter') { + state.size.width = roundToMultiple(state.size.width, 64); + state.size.height = roundToMultiple(state.size.height, 64); + } + }); }, }); @@ -461,6 +525,9 @@ export const { positivePrompt2Changed, negativePrompt2Changed, shouldConcatPromptsChanged, + widthChanged, + heightChanged, + aspectRatioChanged, // General actions brushSizeChanged, globalMaskLayerOpacityChanged, diff --git a/invokeai/frontend/web/src/features/regionalPrompts/util/getLayerBlobs.ts b/invokeai/frontend/web/src/features/regionalPrompts/util/getLayerBlobs.ts index 02c1ae8b60..da8108784d 100644 --- a/invokeai/frontend/web/src/features/regionalPrompts/util/getLayerBlobs.ts +++ b/invokeai/frontend/web/src/features/regionalPrompts/util/getLayerBlobs.ts @@ -17,9 +17,11 @@ export const getRegionalPromptLayerBlobs = async ( preview: boolean = false ): Promise> => { const state = getStore().getState(); - const reduxLayers = state.regionalPrompts.present.layers.filter(isVectorMaskLayer); + const { layers } = state.regionalPrompts.present; + const { width, height } = state.regionalPrompts.present.size; + const reduxLayers = layers.filter(isVectorMaskLayer); const container = document.createElement('div'); - const stage = new Konva.Stage({ container, width: state.generation.width, height: state.generation.height }); + const stage = new Konva.Stage({ container, width, height }); renderers.renderLayers(stage, reduxLayers, 1, 'brush'); const konvaLayers = stage.find(`.${VECTOR_MASK_LAYER_NAME}`); diff --git a/invokeai/frontend/web/src/features/settingsAccordions/components/ImageSettingsAccordion/ImageSettingsAccordion.tsx b/invokeai/frontend/web/src/features/settingsAccordions/components/ImageSettingsAccordion/ImageSettingsAccordion.tsx index 125a611876..30f7f4b0ac 100644 --- a/invokeai/frontend/web/src/features/settingsAccordions/components/ImageSettingsAccordion/ImageSettingsAccordion.tsx +++ b/invokeai/frontend/web/src/features/settingsAccordions/components/ImageSettingsAccordion/ImageSettingsAccordion.tsx @@ -14,6 +14,7 @@ import { ParamSeedNumberInput } from 'features/parameters/components/Seed/ParamS import { ParamSeedRandomize } from 'features/parameters/components/Seed/ParamSeedRandomize'; import { ParamSeedShuffle } from 'features/parameters/components/Seed/ParamSeedShuffle'; import { selectGenerationSlice } from 'features/parameters/store/generationSlice'; +import { selectRegionalPromptsSlice } from 'features/regionalPrompts/store/regionalPromptsSlice'; import { useExpanderToggle } from 'features/settingsAccordions/hooks/useExpanderToggle'; import { useStandaloneAccordionToggle } from 'features/settingsAccordions/hooks/useStandaloneAccordionToggle'; import { activeTabNameSelector } from 'features/ui/store/uiSelectors'; @@ -24,8 +25,8 @@ import { ImageSizeCanvas } from './ImageSizeCanvas'; import { ImageSizeLinear } from './ImageSizeLinear'; const selector = createMemoizedSelector( - [selectGenerationSlice, selectCanvasSlice, selectHrfSlice, activeTabNameSelector], - (generation, canvas, hrf, activeTabName) => { + [selectGenerationSlice, selectCanvasSlice, selectHrfSlice, selectRegionalPromptsSlice, activeTabNameSelector], + (generation, canvas, hrf, regionalPrompts, activeTabName) => { const { shouldRandomizeSeed, model } = generation; const { hrfEnabled } = hrf; const badges: string[] = []; @@ -42,7 +43,7 @@ const selector = createMemoizedSelector( badges.push('locked'); } } else { - const { aspectRatio, width, height } = generation; + const { aspectRatio, width, height } = regionalPrompts.present.size; badges.push(`${width}×${height}`); badges.push(aspectRatio.id); if (aspectRatio.isLocked) { diff --git a/invokeai/frontend/web/src/features/settingsAccordions/components/ImageSettingsAccordion/ImageSizeLinear.tsx b/invokeai/frontend/web/src/features/settingsAccordions/components/ImageSettingsAccordion/ImageSizeLinear.tsx index 498faf452b..3a00496ec9 100644 --- a/invokeai/frontend/web/src/features/settingsAccordions/components/ImageSettingsAccordion/ImageSizeLinear.tsx +++ b/invokeai/frontend/web/src/features/settingsAccordions/components/ImageSettingsAccordion/ImageSizeLinear.tsx @@ -5,27 +5,27 @@ import { AspectRatioCanvasPreview } from 'features/parameters/components/ImageSi import { AspectRatioIconPreview } from 'features/parameters/components/ImageSize/AspectRatioIconPreview'; import { ImageSize } from 'features/parameters/components/ImageSize/ImageSize'; import type { AspectRatioState } from 'features/parameters/components/ImageSize/types'; -import { aspectRatioChanged, heightChanged, widthChanged } from 'features/parameters/store/generationSlice'; +import { aspectRatioChanged, heightChanged, widthChanged } from 'features/regionalPrompts/store/regionalPromptsSlice'; import { activeTabNameSelector } from 'features/ui/store/uiSelectors'; import { memo, useCallback } from 'react'; export const ImageSizeLinear = memo(() => { const dispatch = useAppDispatch(); const tab = useAppSelector(activeTabNameSelector); - const width = useAppSelector((s) => s.generation.width); - const height = useAppSelector((s) => s.generation.height); - const aspectRatioState = useAppSelector((s) => s.generation.aspectRatio); + const width = useAppSelector((s) => s.regionalPrompts.present.size.width); + const height = useAppSelector((s) => s.regionalPrompts.present.size.height); + const aspectRatioState = useAppSelector((s) => s.regionalPrompts.present.size.aspectRatio); const onChangeWidth = useCallback( (width: number) => { - dispatch(widthChanged(width)); + dispatch(widthChanged({ width })); }, [dispatch] ); const onChangeHeight = useCallback( (height: number) => { - dispatch(heightChanged(height)); + dispatch(heightChanged({ height })); }, [dispatch] ); From 2f6fec8c6c46e803de6a18e290ac7a6863809f10 Mon Sep 17 00:00:00 2001 From: psychedelicious <4822129+psychedelicious@users.noreply.github.com> Date: Thu, 25 Apr 2024 20:08:33 +1000 Subject: [PATCH 06/52] chore(ui): lint --- .../regionalPrompts/components/RPLayerIPAdapterList.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/invokeai/frontend/web/src/features/regionalPrompts/components/RPLayerIPAdapterList.tsx b/invokeai/frontend/web/src/features/regionalPrompts/components/RPLayerIPAdapterList.tsx index 91cc2d0736..2da01de92a 100644 --- a/invokeai/frontend/web/src/features/regionalPrompts/components/RPLayerIPAdapterList.tsx +++ b/invokeai/frontend/web/src/features/regionalPrompts/components/RPLayerIPAdapterList.tsx @@ -2,7 +2,7 @@ import { Flex } from '@invoke-ai/ui-library'; import { createMemoizedSelector } from 'app/store/createMemoizedSelector'; import { useAppSelector } from 'app/store/storeHooks'; import ControlAdapterConfig from 'features/controlAdapters/components/ControlAdapterConfig'; -import { isVectorMaskLayer,selectRegionalPromptsSlice } from 'features/regionalPrompts/store/regionalPromptsSlice'; +import { isVectorMaskLayer, selectRegionalPromptsSlice } from 'features/regionalPrompts/store/regionalPromptsSlice'; import { memo, useMemo } from 'react'; import { assert } from 'tsafe'; From 6f5f3381f973aec422ef6609fc539d28a2023e34 Mon Sep 17 00:00:00 2001 From: psychedelicious <4822129+psychedelicious@users.noreply.github.com> Date: Fri, 26 Apr 2024 16:56:07 +1000 Subject: [PATCH 07/52] feat(ui): revise internal state for RCC --- .../listeners/promptChanged.ts | 2 +- .../src/common/hooks/useIsReadyToEnqueue.ts | 2 +- .../util/graph/addIPAdapterToLinearGraph.ts | 4 +- .../util/graph/addRegionalPromptsToGraph.ts | 4 +- .../graph/buildCanvasImageToImageGraph.ts | 2 +- .../util/graph/buildCanvasInpaintGraph.ts | 2 +- .../util/graph/buildCanvasOutpaintGraph.ts | 2 +- .../graph/buildCanvasSDXLImageToImageGraph.ts | 2 +- .../util/graph/buildCanvasSDXLInpaintGraph.ts | 2 +- .../graph/buildCanvasSDXLOutpaintGraph.ts | 2 +- .../graph/buildCanvasSDXLTextToImageGraph.ts | 2 +- .../util/graph/buildCanvasTextToImageGraph.ts | 2 +- .../util/graph/buildLinearBatchConfig.ts | 2 +- .../graph/buildLinearImageToImageGraph.ts | 2 +- .../graph/buildLinearSDXLImageToImageGraph.ts | 2 +- .../graph/buildLinearSDXLTextToImageGraph.ts | 2 +- .../util/graph/buildLinearTextToImageGraph.ts | 2 +- .../nodes/util/graph/graphBuilderUtils.ts | 2 +- .../components/Core/ParamNegativePrompt.tsx | 2 +- .../components/Core/ParamPositivePrompt.tsx | 2 +- .../queue/components/QueueButtonTooltip.tsx | 2 +- .../components/AddLayerButton.tsx | 2 +- .../components/AddPromptButtons.tsx | 4 +- .../RPLayerAutoNegativeCheckbox.tsx | 4 +- .../components/RPLayerColorPicker.tsx | 4 +- .../components/RPLayerIPAdapterList.tsx | 4 +- .../components/RPLayerListItem.tsx | 4 +- .../components/RPLayerMenu.tsx | 4 +- .../RegionalPromptsPanelContent.tsx | 4 +- .../components/StageComponent.tsx | 4 +- .../components/ToolChooser.tsx | 2 +- .../regionalPrompts/hooks/layerStateHooks.ts | 8 +- .../hooks/useRegionalControlTitle.ts | 4 +- .../store/regionalPromptsSlice.ts | 161 ++++++++++-------- .../src/features/regionalPrompts/util/bbox.ts | 4 +- .../regionalPrompts/util/getLayerBlobs.ts | 6 +- .../regionalPrompts/util/renderers.ts | 46 ++--- .../ParamSDXLNegativeStylePrompt.tsx | 2 +- .../ParamSDXLPositiveStylePrompt.tsx | 2 +- .../SDXLPrompts/SDXLConcatButton.tsx | 2 +- .../components/SDXLPrompts/SDXLPrompts.tsx | 2 +- .../ControlSettingsAccordion.tsx | 6 +- 42 files changed, 170 insertions(+), 155 deletions(-) diff --git a/invokeai/frontend/web/src/app/store/middleware/listenerMiddleware/listeners/promptChanged.ts b/invokeai/frontend/web/src/app/store/middleware/listenerMiddleware/listeners/promptChanged.ts index 93926f4b08..db38a0aa2e 100644 --- a/invokeai/frontend/web/src/app/store/middleware/listenerMiddleware/listeners/promptChanged.ts +++ b/invokeai/frontend/web/src/app/store/middleware/listenerMiddleware/listeners/promptChanged.ts @@ -28,7 +28,7 @@ export const addDynamicPromptsListener = (startAppListening: AppStartListening) effect: async (action, { dispatch, getState, cancelActiveListeners, delay }) => { cancelActiveListeners(); const state = getState(); - const { positivePrompt } = state.regionalPrompts.present.baseLayer; + const { positivePrompt } = state.regionalPrompts.present; const { maxPrompts } = state.dynamicPrompts; if (state.config.disabledFeatures.includes('dynamicPrompting')) { diff --git a/invokeai/frontend/web/src/common/hooks/useIsReadyToEnqueue.ts b/invokeai/frontend/web/src/common/hooks/useIsReadyToEnqueue.ts index fa4c1aaf45..24abcd631d 100644 --- a/invokeai/frontend/web/src/common/hooks/useIsReadyToEnqueue.ts +++ b/invokeai/frontend/web/src/common/hooks/useIsReadyToEnqueue.ts @@ -29,7 +29,7 @@ const selector = createMemoizedSelector( ], (controlAdapters, generation, system, nodes, dynamicPrompts, regionalPrompts, activeTabName) => { const { initialImage, model } = generation; - const { positivePrompt } = regionalPrompts.present.baseLayer; + const { positivePrompt } = regionalPrompts.present; const { isConnected } = system; diff --git a/invokeai/frontend/web/src/features/nodes/util/graph/addIPAdapterToLinearGraph.ts b/invokeai/frontend/web/src/features/nodes/util/graph/addIPAdapterToLinearGraph.ts index 4a287b5335..5f39fd93d8 100644 --- a/invokeai/frontend/web/src/features/nodes/util/graph/addIPAdapterToLinearGraph.ts +++ b/invokeai/frontend/web/src/features/nodes/util/graph/addIPAdapterToLinearGraph.ts @@ -2,7 +2,7 @@ import type { RootState } from 'app/store/store'; import { selectValidIPAdapters } from 'features/controlAdapters/store/controlAdaptersSlice'; import type { IPAdapterConfig } from 'features/controlAdapters/store/types'; import type { ImageField } from 'features/nodes/types/common'; -import { isVectorMaskLayer } from 'features/regionalPrompts/store/regionalPromptsSlice'; +import { isMaskedGuidanceLayer } from 'features/regionalPrompts/store/regionalPromptsSlice'; import { differenceBy } from 'lodash-es'; import type { CollectInvocation, @@ -29,7 +29,7 @@ export const addIPAdapterToLinearGraph = async ( }); const regionalIPAdapterIds = state.regionalPrompts.present.layers - .filter(isVectorMaskLayer) + .filter(isMaskedGuidanceLayer) .map((l) => l.ipAdapterIds) .flat(); diff --git a/invokeai/frontend/web/src/features/nodes/util/graph/addRegionalPromptsToGraph.ts b/invokeai/frontend/web/src/features/nodes/util/graph/addRegionalPromptsToGraph.ts index 8d7a3a6c9a..d7451d3f1f 100644 --- a/invokeai/frontend/web/src/features/nodes/util/graph/addRegionalPromptsToGraph.ts +++ b/invokeai/frontend/web/src/features/nodes/util/graph/addRegionalPromptsToGraph.ts @@ -13,7 +13,7 @@ import { PROMPT_REGION_POSITIVE_COND_INVERTED_PREFIX, PROMPT_REGION_POSITIVE_COND_PREFIX, } from 'features/nodes/util/graph/constants'; -import { isVectorMaskLayer } from 'features/regionalPrompts/store/regionalPromptsSlice'; +import { isMaskedGuidanceLayer } from 'features/regionalPrompts/store/regionalPromptsSlice'; import { getRegionalPromptLayerBlobs } from 'features/regionalPrompts/util/getLayerBlobs'; import { size } from 'lodash-es'; import { imagesApi } from 'services/api/endpoints/images'; @@ -29,7 +29,7 @@ export const addRegionalPromptsToGraph = async (state: RootState, graph: NonNull const layers = state.regionalPrompts.present.layers // Only support vector mask layers now // TODO: Image masks - .filter(isVectorMaskLayer) + .filter(isMaskedGuidanceLayer) // Only visible layers are rendered on the canvas .filter((l) => l.isVisible) // Only layers with prompts get added to the graph diff --git a/invokeai/frontend/web/src/features/nodes/util/graph/buildCanvasImageToImageGraph.ts b/invokeai/frontend/web/src/features/nodes/util/graph/buildCanvasImageToImageGraph.ts index e0844057ba..144db94f3c 100644 --- a/invokeai/frontend/web/src/features/nodes/util/graph/buildCanvasImageToImageGraph.ts +++ b/invokeai/frontend/web/src/features/nodes/util/graph/buildCanvasImageToImageGraph.ts @@ -55,7 +55,7 @@ export const buildCanvasImageToImageGraph = async ( seamlessXAxis, seamlessYAxis, } = state.generation; - const { positivePrompt, negativePrompt } = state.regionalPrompts.present.baseLayer; + const { positivePrompt, negativePrompt } = state.regionalPrompts.present; // The bounding box determines width and height, not the width and height params const { width, height } = state.canvas.boundingBoxDimensions; diff --git a/invokeai/frontend/web/src/features/nodes/util/graph/buildCanvasInpaintGraph.ts b/invokeai/frontend/web/src/features/nodes/util/graph/buildCanvasInpaintGraph.ts index 6c1c954f58..49cd590780 100644 --- a/invokeai/frontend/web/src/features/nodes/util/graph/buildCanvasInpaintGraph.ts +++ b/invokeai/frontend/web/src/features/nodes/util/graph/buildCanvasInpaintGraph.ts @@ -64,7 +64,7 @@ export const buildCanvasInpaintGraph = async ( canvasCoherenceEdgeSize, maskBlur, } = state.generation; - const { positivePrompt, negativePrompt } = state.regionalPrompts.present.baseLayer; + const { positivePrompt, negativePrompt } = state.regionalPrompts.present; if (!model) { log.error('No model found in state'); diff --git a/invokeai/frontend/web/src/features/nodes/util/graph/buildCanvasOutpaintGraph.ts b/invokeai/frontend/web/src/features/nodes/util/graph/buildCanvasOutpaintGraph.ts index 4a9beef3eb..c94b07d395 100644 --- a/invokeai/frontend/web/src/features/nodes/util/graph/buildCanvasOutpaintGraph.ts +++ b/invokeai/frontend/web/src/features/nodes/util/graph/buildCanvasOutpaintGraph.ts @@ -76,7 +76,7 @@ export const buildCanvasOutpaintGraph = async ( canvasCoherenceEdgeSize, maskBlur, } = state.generation; - const { positivePrompt, negativePrompt } = state.regionalPrompts.present.baseLayer; + const { positivePrompt, negativePrompt } = state.regionalPrompts.present; if (!model) { log.error('No model found in state'); diff --git a/invokeai/frontend/web/src/features/nodes/util/graph/buildCanvasSDXLImageToImageGraph.ts b/invokeai/frontend/web/src/features/nodes/util/graph/buildCanvasSDXLImageToImageGraph.ts index 8e06dde054..7b29d0a8fb 100644 --- a/invokeai/frontend/web/src/features/nodes/util/graph/buildCanvasSDXLImageToImageGraph.ts +++ b/invokeai/frontend/web/src/features/nodes/util/graph/buildCanvasSDXLImageToImageGraph.ts @@ -55,7 +55,7 @@ export const buildCanvasSDXLImageToImageGraph = async ( seamlessYAxis, img2imgStrength: strength, } = state.generation; - const { positivePrompt, negativePrompt } = state.regionalPrompts.present.baseLayer; + const { positivePrompt, negativePrompt } = state.regionalPrompts.present; const { refinerModel, refinerStart } = state.sdxl; diff --git a/invokeai/frontend/web/src/features/nodes/util/graph/buildCanvasSDXLInpaintGraph.ts b/invokeai/frontend/web/src/features/nodes/util/graph/buildCanvasSDXLInpaintGraph.ts index ee3d0897b8..4b1fd4575a 100644 --- a/invokeai/frontend/web/src/features/nodes/util/graph/buildCanvasSDXLInpaintGraph.ts +++ b/invokeai/frontend/web/src/features/nodes/util/graph/buildCanvasSDXLInpaintGraph.ts @@ -64,7 +64,7 @@ export const buildCanvasSDXLInpaintGraph = async ( canvasCoherenceEdgeSize, maskBlur, } = state.generation; - const { positivePrompt, negativePrompt } = state.regionalPrompts.present.baseLayer; + const { positivePrompt, negativePrompt } = state.regionalPrompts.present; const { refinerModel, refinerStart } = state.sdxl; diff --git a/invokeai/frontend/web/src/features/nodes/util/graph/buildCanvasSDXLOutpaintGraph.ts b/invokeai/frontend/web/src/features/nodes/util/graph/buildCanvasSDXLOutpaintGraph.ts index ddcbb168ef..0561976edb 100644 --- a/invokeai/frontend/web/src/features/nodes/util/graph/buildCanvasSDXLOutpaintGraph.ts +++ b/invokeai/frontend/web/src/features/nodes/util/graph/buildCanvasSDXLOutpaintGraph.ts @@ -76,7 +76,7 @@ export const buildCanvasSDXLOutpaintGraph = async ( canvasCoherenceEdgeSize, maskBlur, } = state.generation; - const { positivePrompt, negativePrompt } = state.regionalPrompts.present.baseLayer; + const { positivePrompt, negativePrompt } = state.regionalPrompts.present; const { refinerModel, refinerStart } = state.sdxl; diff --git a/invokeai/frontend/web/src/features/nodes/util/graph/buildCanvasSDXLTextToImageGraph.ts b/invokeai/frontend/web/src/features/nodes/util/graph/buildCanvasSDXLTextToImageGraph.ts index 9365faee81..3f890e02ef 100644 --- a/invokeai/frontend/web/src/features/nodes/util/graph/buildCanvasSDXLTextToImageGraph.ts +++ b/invokeai/frontend/web/src/features/nodes/util/graph/buildCanvasSDXLTextToImageGraph.ts @@ -44,7 +44,7 @@ export const buildCanvasSDXLTextToImageGraph = async (state: RootState): Promise seamlessXAxis, seamlessYAxis, } = state.generation; - const { positivePrompt, negativePrompt } = state.regionalPrompts.present.baseLayer; + const { positivePrompt, negativePrompt } = state.regionalPrompts.present; // The bounding box determines width and height, not the width and height params const { width, height } = state.canvas.boundingBoxDimensions; diff --git a/invokeai/frontend/web/src/features/nodes/util/graph/buildCanvasTextToImageGraph.ts b/invokeai/frontend/web/src/features/nodes/util/graph/buildCanvasTextToImageGraph.ts index 57529f9a02..4458720be7 100644 --- a/invokeai/frontend/web/src/features/nodes/util/graph/buildCanvasTextToImageGraph.ts +++ b/invokeai/frontend/web/src/features/nodes/util/graph/buildCanvasTextToImageGraph.ts @@ -44,7 +44,7 @@ export const buildCanvasTextToImageGraph = async (state: RootState): Promise { const { iterations, model, shouldRandomizeSeed, seed } = state.generation; - const { shouldConcatPrompts } = state.regionalPrompts.present.baseLayer; + const { shouldConcatPrompts } = state.regionalPrompts.present; const { prompts, seedBehaviour } = state.dynamicPrompts; const data: Batch['data'] = []; diff --git a/invokeai/frontend/web/src/features/nodes/util/graph/buildLinearImageToImageGraph.ts b/invokeai/frontend/web/src/features/nodes/util/graph/buildLinearImageToImageGraph.ts index 5c9b6b96db..1a890ed57a 100644 --- a/invokeai/frontend/web/src/features/nodes/util/graph/buildLinearImageToImageGraph.ts +++ b/invokeai/frontend/web/src/features/nodes/util/graph/buildLinearImageToImageGraph.ts @@ -53,7 +53,7 @@ export const buildLinearImageToImageGraph = async (state: RootState): Promise { */ export const getSDXLStylePrompts = (state: RootState): { positiveStylePrompt: string; negativeStylePrompt: string } => { const { positivePrompt, negativePrompt, positivePrompt2, negativePrompt2, shouldConcatPrompts } = - state.regionalPrompts.present.baseLayer; + state.regionalPrompts.present; return { positiveStylePrompt: shouldConcatPrompts ? positivePrompt : positivePrompt2, diff --git a/invokeai/frontend/web/src/features/parameters/components/Core/ParamNegativePrompt.tsx b/invokeai/frontend/web/src/features/parameters/components/Core/ParamNegativePrompt.tsx index 70e36e8dcc..077bdca108 100644 --- a/invokeai/frontend/web/src/features/parameters/components/Core/ParamNegativePrompt.tsx +++ b/invokeai/frontend/web/src/features/parameters/components/Core/ParamNegativePrompt.tsx @@ -10,7 +10,7 @@ import { useTranslation } from 'react-i18next'; export const ParamNegativePrompt = memo(() => { const dispatch = useAppDispatch(); - const prompt = useAppSelector((s) => s.regionalPrompts.present.baseLayer.negativePrompt); + const prompt = useAppSelector((s) => s.regionalPrompts.present.negativePrompt); const textareaRef = useRef(null); const { t } = useTranslation(); const _onChange = useCallback( diff --git a/invokeai/frontend/web/src/features/parameters/components/Core/ParamPositivePrompt.tsx b/invokeai/frontend/web/src/features/parameters/components/Core/ParamPositivePrompt.tsx index 732a50fa1b..90b6ae3093 100644 --- a/invokeai/frontend/web/src/features/parameters/components/Core/ParamPositivePrompt.tsx +++ b/invokeai/frontend/web/src/features/parameters/components/Core/ParamPositivePrompt.tsx @@ -14,7 +14,7 @@ import { useTranslation } from 'react-i18next'; export const ParamPositivePrompt = memo(() => { const dispatch = useAppDispatch(); - const prompt = useAppSelector((s) => s.regionalPrompts.present.baseLayer.positivePrompt); + const prompt = useAppSelector((s) => s.regionalPrompts.present.positivePrompt); const baseModel = useAppSelector((s) => s.generation.model)?.base; const textareaRef = useRef(null); diff --git a/invokeai/frontend/web/src/features/queue/components/QueueButtonTooltip.tsx b/invokeai/frontend/web/src/features/queue/components/QueueButtonTooltip.tsx index 3ea7d95309..e020782aa1 100644 --- a/invokeai/frontend/web/src/features/queue/components/QueueButtonTooltip.tsx +++ b/invokeai/frontend/web/src/features/queue/components/QueueButtonTooltip.tsx @@ -14,7 +14,7 @@ const selectPromptsCount = createSelector( selectRegionalPromptsSlice, selectDynamicPromptsSlice, (regionalPrompts, dynamicPrompts) => - getShouldProcessPrompt(regionalPrompts.present.baseLayer.positivePrompt) ? dynamicPrompts.prompts.length : 1 + getShouldProcessPrompt(regionalPrompts.present.positivePrompt) ? dynamicPrompts.prompts.length : 1 ); type Props = { diff --git a/invokeai/frontend/web/src/features/regionalPrompts/components/AddLayerButton.tsx b/invokeai/frontend/web/src/features/regionalPrompts/components/AddLayerButton.tsx index 8acfc18c56..a9b3480b87 100644 --- a/invokeai/frontend/web/src/features/regionalPrompts/components/AddLayerButton.tsx +++ b/invokeai/frontend/web/src/features/regionalPrompts/components/AddLayerButton.tsx @@ -9,7 +9,7 @@ export const AddLayerButton = memo(() => { const { t } = useTranslation(); const dispatch = useAppDispatch(); const onClick = useCallback(() => { - dispatch(layerAdded('vector_mask_layer')); + dispatch(layerAdded('masked_guidance_layer')); }, [dispatch]); return ( diff --git a/invokeai/frontend/web/src/features/regionalPrompts/components/AddPromptButtons.tsx b/invokeai/frontend/web/src/features/regionalPrompts/components/AddPromptButtons.tsx index 2f1d4e8f4d..db2e35c234 100644 --- a/invokeai/frontend/web/src/features/regionalPrompts/components/AddPromptButtons.tsx +++ b/invokeai/frontend/web/src/features/regionalPrompts/components/AddPromptButtons.tsx @@ -2,7 +2,7 @@ import { Button, Flex } from '@invoke-ai/ui-library'; import { createMemoizedSelector } from 'app/store/createMemoizedSelector'; import { useAppDispatch, useAppSelector } from 'app/store/storeHooks'; import { - isVectorMaskLayer, + isMaskedGuidanceLayer, maskLayerIPAdapterAdded, maskLayerNegativePromptChanged, maskLayerPositivePromptChanged, @@ -23,7 +23,7 @@ export const AddPromptButtons = ({ layerId }: AddPromptButtonProps) => { () => createMemoizedSelector(selectRegionalPromptsSlice, (regionalPrompts) => { const layer = regionalPrompts.present.layers.find((l) => l.id === layerId); - assert(isVectorMaskLayer(layer), `Layer ${layerId} not found or not an RP layer`); + assert(isMaskedGuidanceLayer(layer), `Layer ${layerId} not found or not an RP layer`); return { canAddPositivePrompt: layer.positivePrompt === null, canAddNegativePrompt: layer.negativePrompt === null, diff --git a/invokeai/frontend/web/src/features/regionalPrompts/components/RPLayerAutoNegativeCheckbox.tsx b/invokeai/frontend/web/src/features/regionalPrompts/components/RPLayerAutoNegativeCheckbox.tsx index 454c30a6d2..e4e0f86796 100644 --- a/invokeai/frontend/web/src/features/regionalPrompts/components/RPLayerAutoNegativeCheckbox.tsx +++ b/invokeai/frontend/web/src/features/regionalPrompts/components/RPLayerAutoNegativeCheckbox.tsx @@ -2,7 +2,7 @@ import { Checkbox, FormControl, FormLabel } from '@invoke-ai/ui-library'; import { createSelector } from '@reduxjs/toolkit'; import { useAppDispatch, useAppSelector } from 'app/store/storeHooks'; import { - isVectorMaskLayer, + isMaskedGuidanceLayer, maskLayerAutoNegativeChanged, selectRegionalPromptsSlice, } from 'features/regionalPrompts/store/regionalPromptsSlice'; @@ -20,7 +20,7 @@ const useAutoNegative = (layerId: string) => { () => createSelector(selectRegionalPromptsSlice, (regionalPrompts) => { const layer = regionalPrompts.present.layers.find((l) => l.id === layerId); - assert(isVectorMaskLayer(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; }), [layerId] diff --git a/invokeai/frontend/web/src/features/regionalPrompts/components/RPLayerColorPicker.tsx b/invokeai/frontend/web/src/features/regionalPrompts/components/RPLayerColorPicker.tsx index 851cd8b13f..81c4b17fdd 100644 --- a/invokeai/frontend/web/src/features/regionalPrompts/components/RPLayerColorPicker.tsx +++ b/invokeai/frontend/web/src/features/regionalPrompts/components/RPLayerColorPicker.tsx @@ -4,7 +4,7 @@ import { useAppDispatch, useAppSelector } from 'app/store/storeHooks'; import RgbColorPicker from 'common/components/RgbColorPicker'; import { rgbColorToString } from 'features/canvas/util/colorToString'; import { - isVectorMaskLayer, + isMaskedGuidanceLayer, maskLayerPreviewColorChanged, selectRegionalPromptsSlice, } from 'features/regionalPrompts/store/regionalPromptsSlice'; @@ -23,7 +23,7 @@ export const RPLayerColorPicker = memo(({ layerId }: Props) => { () => createMemoizedSelector(selectRegionalPromptsSlice, (regionalPrompts) => { const layer = regionalPrompts.present.layers.find((l) => l.id === layerId); - assert(isVectorMaskLayer(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; }), [layerId] diff --git a/invokeai/frontend/web/src/features/regionalPrompts/components/RPLayerIPAdapterList.tsx b/invokeai/frontend/web/src/features/regionalPrompts/components/RPLayerIPAdapterList.tsx index 2da01de92a..ff2efaa0f9 100644 --- a/invokeai/frontend/web/src/features/regionalPrompts/components/RPLayerIPAdapterList.tsx +++ b/invokeai/frontend/web/src/features/regionalPrompts/components/RPLayerIPAdapterList.tsx @@ -2,7 +2,7 @@ import { Flex } from '@invoke-ai/ui-library'; import { createMemoizedSelector } from 'app/store/createMemoizedSelector'; import { useAppSelector } from 'app/store/storeHooks'; import ControlAdapterConfig from 'features/controlAdapters/components/ControlAdapterConfig'; -import { isVectorMaskLayer, selectRegionalPromptsSlice } from 'features/regionalPrompts/store/regionalPromptsSlice'; +import { isMaskedGuidanceLayer, selectRegionalPromptsSlice } from 'features/regionalPrompts/store/regionalPromptsSlice'; import { memo, useMemo } from 'react'; import { assert } from 'tsafe'; @@ -14,7 +14,7 @@ export const RPLayerIPAdapterList = memo(({ layerId }: Props) => { const selectIPAdapterIds = useMemo( () => createMemoizedSelector(selectRegionalPromptsSlice, (regionalPrompts) => { - const layer = regionalPrompts.present.layers.filter(isVectorMaskLayer).find((l) => l.id === layerId); + const layer = regionalPrompts.present.layers.filter(isMaskedGuidanceLayer).find((l) => l.id === layerId); assert(layer, `Layer ${layerId} not found`); return layer.ipAdapterIds; }), diff --git a/invokeai/frontend/web/src/features/regionalPrompts/components/RPLayerListItem.tsx b/invokeai/frontend/web/src/features/regionalPrompts/components/RPLayerListItem.tsx index ef98a659ac..989ee8c576 100644 --- a/invokeai/frontend/web/src/features/regionalPrompts/components/RPLayerListItem.tsx +++ b/invokeai/frontend/web/src/features/regionalPrompts/components/RPLayerListItem.tsx @@ -11,7 +11,7 @@ import { RPLayerPositivePrompt } from 'features/regionalPrompts/components/RPLay import RPLayerSettingsPopover from 'features/regionalPrompts/components/RPLayerSettingsPopover'; import { RPLayerVisibilityToggle } from 'features/regionalPrompts/components/RPLayerVisibilityToggle'; import { - isVectorMaskLayer, + isMaskedGuidanceLayer, layerSelected, selectRegionalPromptsSlice, } from 'features/regionalPrompts/store/regionalPromptsSlice'; @@ -32,7 +32,7 @@ export const RPLayerListItem = memo(({ layerId }: Props) => { () => createMemoizedSelector(selectRegionalPromptsSlice, (regionalPrompts) => { const layer = regionalPrompts.present.layers.find((l) => l.id === layerId); - assert(isVectorMaskLayer(layer), `Layer ${layerId} not found or not an RP layer`); + assert(isMaskedGuidanceLayer(layer), `Layer ${layerId} not found or not an RP layer`); return { color: rgbColorToString(layer.previewColor), hasPositivePrompt: layer.positivePrompt !== null, diff --git a/invokeai/frontend/web/src/features/regionalPrompts/components/RPLayerMenu.tsx b/invokeai/frontend/web/src/features/regionalPrompts/components/RPLayerMenu.tsx index f3c5317e10..665b11e1d9 100644 --- a/invokeai/frontend/web/src/features/regionalPrompts/components/RPLayerMenu.tsx +++ b/invokeai/frontend/web/src/features/regionalPrompts/components/RPLayerMenu.tsx @@ -2,7 +2,7 @@ import { IconButton, Menu, MenuButton, MenuDivider, MenuItem, MenuList } from '@ import { createMemoizedSelector } from 'app/store/createMemoizedSelector'; import { useAppDispatch, useAppSelector } from 'app/store/storeHooks'; import { - isVectorMaskLayer, + isMaskedGuidanceLayer, layerDeleted, layerMovedBackward, layerMovedForward, @@ -37,7 +37,7 @@ export const RPLayerMenu = memo(({ layerId }: Props) => { () => createMemoizedSelector(selectRegionalPromptsSlice, (regionalPrompts) => { const layer = regionalPrompts.present.layers.find((l) => l.id === layerId); - assert(isVectorMaskLayer(layer), `Layer ${layerId} not found or not an RP layer`); + assert(isMaskedGuidanceLayer(layer), `Layer ${layerId} not found or not an RP layer`); const layerIndex = regionalPrompts.present.layers.findIndex((l) => l.id === layerId); const layerCount = regionalPrompts.present.layers.length; return { diff --git a/invokeai/frontend/web/src/features/regionalPrompts/components/RegionalPromptsPanelContent.tsx b/invokeai/frontend/web/src/features/regionalPrompts/components/RegionalPromptsPanelContent.tsx index 1fe4d53623..74bc552239 100644 --- a/invokeai/frontend/web/src/features/regionalPrompts/components/RegionalPromptsPanelContent.tsx +++ b/invokeai/frontend/web/src/features/regionalPrompts/components/RegionalPromptsPanelContent.tsx @@ -6,12 +6,12 @@ import ScrollableContent from 'common/components/OverlayScrollbars/ScrollableCon import { AddLayerButton } from 'features/regionalPrompts/components/AddLayerButton'; import { DeleteAllLayersButton } from 'features/regionalPrompts/components/DeleteAllLayersButton'; import { RPLayerListItem } from 'features/regionalPrompts/components/RPLayerListItem'; -import { isVectorMaskLayer, selectRegionalPromptsSlice } from 'features/regionalPrompts/store/regionalPromptsSlice'; +import { isMaskedGuidanceLayer, selectRegionalPromptsSlice } from 'features/regionalPrompts/store/regionalPromptsSlice'; import { memo } from 'react'; const selectRPLayerIdsReversed = createMemoizedSelector(selectRegionalPromptsSlice, (regionalPrompts) => regionalPrompts.present.layers - .filter(isVectorMaskLayer) + .filter(isMaskedGuidanceLayer) .map((l) => l.id) .reverse() ); diff --git a/invokeai/frontend/web/src/features/regionalPrompts/components/StageComponent.tsx b/invokeai/frontend/web/src/features/regionalPrompts/components/StageComponent.tsx index d6f6eff812..40f02ace5b 100644 --- a/invokeai/frontend/web/src/features/regionalPrompts/components/StageComponent.tsx +++ b/invokeai/frontend/web/src/features/regionalPrompts/components/StageComponent.tsx @@ -9,7 +9,7 @@ import { $isMouseOver, $lastMouseDownPos, $tool, - isVectorMaskLayer, + isMaskedGuidanceLayer, layerBboxChanged, layerSelected, layerTranslated, @@ -32,7 +32,7 @@ const selectSelectedLayerColor = createMemoizedSelector(selectRegionalPromptsSli if (!layer) { return null; } - assert(isVectorMaskLayer(layer), `Layer ${regionalPrompts.present.selectedLayerId} is not an RP layer`); + assert(isMaskedGuidanceLayer(layer), `Layer ${regionalPrompts.present.selectedLayerId} is not an RP layer`); return layer.previewColor; }); diff --git a/invokeai/frontend/web/src/features/regionalPrompts/components/ToolChooser.tsx b/invokeai/frontend/web/src/features/regionalPrompts/components/ToolChooser.tsx index a79c443a2f..6ba73ccb7b 100644 --- a/invokeai/frontend/web/src/features/regionalPrompts/components/ToolChooser.tsx +++ b/invokeai/frontend/web/src/features/regionalPrompts/components/ToolChooser.tsx @@ -41,7 +41,7 @@ export const ToolChooser: React.FC = () => { useHotkeys('shift+c', resetSelectedLayer); const addLayer = useCallback(() => { - dispatch(layerAdded('vector_mask_layer')); + dispatch(layerAdded('masked_guidance_layer')); }, [dispatch]); useHotkeys('shift+a', addLayer); diff --git a/invokeai/frontend/web/src/features/regionalPrompts/hooks/layerStateHooks.ts b/invokeai/frontend/web/src/features/regionalPrompts/hooks/layerStateHooks.ts index 8ca830e228..a2ee3bd320 100644 --- a/invokeai/frontend/web/src/features/regionalPrompts/hooks/layerStateHooks.ts +++ b/invokeai/frontend/web/src/features/regionalPrompts/hooks/layerStateHooks.ts @@ -1,6 +1,6 @@ import { createSelector } from '@reduxjs/toolkit'; import { useAppSelector } from 'app/store/storeHooks'; -import { isVectorMaskLayer, selectRegionalPromptsSlice } from 'features/regionalPrompts/store/regionalPromptsSlice'; +import { isMaskedGuidanceLayer, selectRegionalPromptsSlice } from 'features/regionalPrompts/store/regionalPromptsSlice'; import { useMemo } from 'react'; import { assert } from 'tsafe'; @@ -9,7 +9,7 @@ export const useLayerPositivePrompt = (layerId: string) => { () => createSelector(selectRegionalPromptsSlice, (regionalPrompts) => { const layer = regionalPrompts.present.layers.find((l) => l.id === layerId); - assert(isVectorMaskLayer(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`); return layer.positivePrompt; }), @@ -24,7 +24,7 @@ export const useLayerNegativePrompt = (layerId: string) => { () => createSelector(selectRegionalPromptsSlice, (regionalPrompts) => { const layer = regionalPrompts.present.layers.find((l) => l.id === layerId); - assert(isVectorMaskLayer(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`); return layer.negativePrompt; }), @@ -39,7 +39,7 @@ export const useLayerIsVisible = (layerId: string) => { () => createSelector(selectRegionalPromptsSlice, (regionalPrompts) => { const layer = regionalPrompts.present.layers.find((l) => l.id === layerId); - assert(isVectorMaskLayer(layer), `Layer ${layerId} not found or not an RP layer`); + assert(isMaskedGuidanceLayer(layer), `Layer ${layerId} not found or not an RP layer`); return layer.isVisible; }), [layerId] diff --git a/invokeai/frontend/web/src/features/regionalPrompts/hooks/useRegionalControlTitle.ts b/invokeai/frontend/web/src/features/regionalPrompts/hooks/useRegionalControlTitle.ts index 24532d2fa1..bb9fd4f5e2 100644 --- a/invokeai/frontend/web/src/features/regionalPrompts/hooks/useRegionalControlTitle.ts +++ b/invokeai/frontend/web/src/features/regionalPrompts/hooks/useRegionalControlTitle.ts @@ -1,6 +1,6 @@ import { createSelector } from '@reduxjs/toolkit'; import { useAppSelector } from 'app/store/storeHooks'; -import { isVectorMaskLayer, selectRegionalPromptsSlice } from 'features/regionalPrompts/store/regionalPromptsSlice'; +import { isMaskedGuidanceLayer, selectRegionalPromptsSlice } from 'features/regionalPrompts/store/regionalPromptsSlice'; import { useMemo } from 'react'; import { useTranslation } from 'react-i18next'; @@ -9,7 +9,7 @@ const selectValidLayerCount = createSelector(selectRegionalPromptsSlice, (region return 0; } const validLayers = regionalPrompts.present.layers - .filter(isVectorMaskLayer) + .filter(isMaskedGuidanceLayer) .filter((l) => l.isVisible) .filter((l) => { const hasTextPrompt = Boolean(l.positivePrompt || l.negativePrompt); diff --git a/invokeai/frontend/web/src/features/regionalPrompts/store/regionalPromptsSlice.ts b/invokeai/frontend/web/src/features/regionalPrompts/store/regionalPromptsSlice.ts index 7d809f2695..bdc0f263b9 100644 --- a/invokeai/frontend/web/src/features/regionalPrompts/store/regionalPromptsSlice.ts +++ b/invokeai/frontend/web/src/features/regionalPrompts/store/regionalPromptsSlice.ts @@ -5,7 +5,6 @@ import { moveBackward, moveForward, moveToBack, moveToFront } from 'common/util/ import { deepClone } from 'common/util/deepClone'; import { roundToMultiple } from 'common/util/roundDownToMultiple'; import { controlAdapterRemoved, isAnyControlAdapterAdded } from 'features/controlAdapters/store/controlAdaptersSlice'; -import type { ControlAdapterConfig } from 'features/controlAdapters/store/types'; import { calculateNewSize } from 'features/parameters/components/ImageSize/calculateNewSize'; import { initialAspectRatioState } from 'features/parameters/components/ImageSize/constants'; import type { AspectRatioState } from 'features/parameters/components/ImageSize/types'; @@ -51,41 +50,38 @@ export type VectorMaskRect = { type LayerBase = { id: string; + isVisible: boolean; +}; + +type RenderableLayerBase = LayerBase & { x: number; y: number; bbox: IRect | null; bboxNeedsUpdate: boolean; - isVisible: boolean; }; -type ControlLayer = LayerBase & { - type: 'control_layer'; - controlAdapter: ControlAdapterConfig; +type ControlAdapterLayer = RenderableLayerBase & { + type: 'controlnet_layer'; // technically, also t2i adapter layer + controlAdapterId: string; }; -type MaskLayerBase = LayerBase & { - positivePrompt: string | null; - negativePrompt: string | null; // Up to one text prompt per mask +type IPAdapterLayer = LayerBase & { + type: 'ip_adapter_layer'; // technically, also t2i adapter layer + ipAdapterId: string; +}; + +export type MaskedGuidanceLayer = RenderableLayerBase & { + type: 'masked_guidance_layer'; + maskObjects: (VectorMaskLine | VectorMaskRect)[]; + positivePrompt: ParameterPositivePrompt | null; + negativePrompt: ParameterNegativePrompt | null; // Up to one text prompt per mask ipAdapterIds: string[]; // Any number of image prompts previewColor: RgbColor; autoNegative: ParameterAutoNegative; needsPixelBbox: boolean; // Needs the slower pixel-based bbox calculation - set to true when an there is an eraser object }; -export type VectorMaskLayer = MaskLayerBase & { - type: 'vector_mask_layer'; - objects: (VectorMaskLine | VectorMaskRect)[]; -}; - -export type Layer = VectorMaskLayer | ControlLayer; - -type BaseLayerState = { - positivePrompt: ParameterPositivePrompt; - negativePrompt: ParameterNegativePrompt; - positivePrompt2: ParameterPositiveStylePromptSDXL; - negativePrompt2: ParameterNegativeStylePromptSDXL; - shouldConcatPrompts: boolean; -}; +export type Layer = MaskedGuidanceLayer | ControlAdapterLayer | IPAdapterLayer; type RegionalPromptsState = { _version: 1; @@ -94,7 +90,12 @@ type RegionalPromptsState = { brushSize: number; globalMaskLayerOpacity: number; isEnabled: boolean; - baseLayer: BaseLayerState; + positivePrompt: ParameterPositivePrompt; + negativePrompt: ParameterNegativePrompt; + positivePrompt2: ParameterPositiveStylePromptSDXL; + negativePrompt2: ParameterNegativeStylePromptSDXL; + shouldConcatPrompts: boolean; + initialImage: string | null; size: { width: ParameterWidth; height: ParameterHeight; @@ -109,13 +110,12 @@ export const initialRegionalPromptsState: RegionalPromptsState = { layers: [], globalMaskLayerOpacity: 0.5, // this globally changes all mask layers' opacity isEnabled: true, - baseLayer: { - positivePrompt: '', - negativePrompt: '', - positivePrompt2: '', - negativePrompt2: '', - shouldConcatPrompts: true, - }, + positivePrompt: '', + negativePrompt: '', + positivePrompt2: '', + negativePrompt2: '', + shouldConcatPrompts: true, + initialImage: null, size: { width: 512, height: 512, @@ -124,10 +124,13 @@ export const initialRegionalPromptsState: RegionalPromptsState = { }; const isLine = (obj: VectorMaskLine | VectorMaskRect): obj is VectorMaskLine => obj.type === 'vector_mask_line'; -export const isVectorMaskLayer = (layer?: Layer): layer is VectorMaskLayer => layer?.type === 'vector_mask_layer'; +export const isMaskedGuidanceLayer = (layer?: Layer): layer is MaskedGuidanceLayer => + layer?.type === 'masked_guidance_layer'; +export const isRenderableLayer = (layer?: Layer): layer is MaskedGuidanceLayer => + layer?.type === 'masked_guidance_layer' || layer?.type === 'controlnet_layer'; const resetLayer = (layer: Layer) => { - if (layer.type === 'vector_mask_layer') { - layer.objects = []; + if (layer.type === 'masked_guidance_layer') { + layer.maskObjects = []; layer.bbox = null; layer.isVisible = true; layer.needsPixelBbox = false; @@ -135,12 +138,12 @@ const resetLayer = (layer: Layer) => { return; } - if (layer.type === 'control_layer') { + if (layer.type === 'controlnet_layer') { // TODO } }; const getVectorMaskPreviewColor = (state: RegionalPromptsState): RgbColor => { - const vmLayers = state.layers.filter(isVectorMaskLayer); + const vmLayers = state.layers.filter(isMaskedGuidanceLayer); const lastColor = vmLayers[vmLayers.length - 1]?.previewColor; return LayerColors.next(lastColor); }; @@ -153,14 +156,14 @@ export const regionalPromptsSlice = createSlice({ layerAdded: { reducer: (state, action: PayloadAction) => { const type = action.payload; - if (type === 'vector_mask_layer') { - const layer: VectorMaskLayer = { - id: getVectorMaskLayerId(action.meta.uuid), - type, + if (type === 'masked_guidance_layer') { + const layer: MaskedGuidanceLayer = { + id: getMaskedGuidanceLayerId(action.meta.uuid), + type: 'masked_guidance_layer', isVisible: true, bbox: null, bboxNeedsUpdate: false, - objects: [], + maskObjects: [], previewColor: getVectorMaskPreviewColor(state), x: 0, y: 0, @@ -175,8 +178,19 @@ export const regionalPromptsSlice = createSlice({ return; } - if (type === 'control_layer') { - // TODO + if (type === 'controlnet_layer') { + const layer: ControlAdapterLayer = { + id: getControlLayerId(action.meta.uuid), + type: 'controlnet_layer', + controlAdapterId: action.meta.uuid, + x: 0, + y: 0, + bbox: null, + bboxNeedsUpdate: false, + isVisible: true, + }; + state.layers.push(layer); + state.selectedLayerId = layer.id; return; } }, @@ -197,7 +211,7 @@ export const regionalPromptsSlice = createSlice({ layerTranslated: (state, action: PayloadAction<{ layerId: string; x: number; y: number }>) => { const { layerId, x, y } = action.payload; const layer = state.layers.find((l) => l.id === layerId); - if (layer) { + if (isRenderableLayer(layer)) { layer.x = x; layer.y = y; } @@ -205,7 +219,7 @@ export const regionalPromptsSlice = createSlice({ layerBboxChanged: (state, action: PayloadAction<{ layerId: string; bbox: IRect | null }>) => { const { layerId, bbox } = action.payload; const layer = state.layers.find((l) => l.id === layerId); - if (layer) { + if (isRenderableLayer(layer)) { layer.bbox = bbox; layer.bboxNeedsUpdate = false; } @@ -258,21 +272,21 @@ export const regionalPromptsSlice = createSlice({ maskLayerPositivePromptChanged: (state, action: PayloadAction<{ layerId: string; prompt: string | null }>) => { const { layerId, prompt } = action.payload; const layer = state.layers.find((l) => l.id === layerId); - if (layer?.type === 'vector_mask_layer') { + if (layer?.type === 'masked_guidance_layer') { layer.positivePrompt = prompt; } }, maskLayerNegativePromptChanged: (state, action: PayloadAction<{ layerId: string; prompt: string | null }>) => { const { layerId, prompt } = action.payload; const layer = state.layers.find((l) => l.id === layerId); - if (layer?.type === 'vector_mask_layer') { + if (layer?.type === 'masked_guidance_layer') { layer.negativePrompt = prompt; } }, maskLayerIPAdapterAdded: { reducer: (state, action: PayloadAction) => { const layer = state.layers.find((l) => l.id === action.payload); - if (layer?.type === 'vector_mask_layer') { + if (layer?.type === 'masked_guidance_layer') { layer.ipAdapterIds.push(action.meta.uuid); } }, @@ -281,7 +295,7 @@ export const regionalPromptsSlice = createSlice({ maskLayerPreviewColorChanged: (state, action: PayloadAction<{ layerId: string; color: RgbColor }>) => { const { layerId, color } = action.payload; const layer = state.layers.find((l) => l.id === layerId); - if (layer?.type === 'vector_mask_layer') { + if (layer?.type === 'masked_guidance_layer') { layer.previewColor = color; } }, @@ -296,9 +310,9 @@ export const regionalPromptsSlice = createSlice({ ) => { const { layerId, points, tool } = action.payload; const layer = state.layers.find((l) => l.id === layerId); - if (layer?.type === 'vector_mask_layer') { - const lineId = getVectorMaskLayerLineId(layer.id, action.meta.uuid); - layer.objects.push({ + if (layer?.type === 'masked_guidance_layer') { + const lineId = getMaskedGuidanceLayerLineId(layer.id, action.meta.uuid); + layer.maskObjects.push({ type: 'vector_mask_line', tool: tool, id: lineId, @@ -321,8 +335,8 @@ export const regionalPromptsSlice = createSlice({ maskLayerPointsAdded: (state, action: PayloadAction<{ layerId: string; point: [number, number] }>) => { const { layerId, point } = action.payload; const layer = state.layers.find((l) => l.id === layerId); - if (layer?.type === 'vector_mask_layer') { - const lastLine = layer.objects.findLast(isLine); + if (layer?.type === 'masked_guidance_layer') { + const lastLine = layer.maskObjects.findLast(isLine); if (!lastLine) { return; } @@ -340,9 +354,9 @@ export const regionalPromptsSlice = createSlice({ return; } const layer = state.layers.find((l) => l.id === layerId); - if (layer?.type === 'vector_mask_layer') { - const id = getVectorMaskLayerRectId(layer.id, action.meta.uuid); - layer.objects.push({ + if (layer?.type === 'masked_guidance_layer') { + const id = getMaskedGuidnaceLayerRectId(layer.id, action.meta.uuid); + layer.maskObjects.push({ type: 'vector_mask_rect', id, x: rect.x - layer.x, @@ -361,7 +375,7 @@ export const regionalPromptsSlice = createSlice({ ) => { const { layerId, autoNegative } = action.payload; const layer = state.layers.find((l) => l.id === layerId); - if (layer?.type === 'vector_mask_layer') { + if (layer?.type === 'masked_guidance_layer') { layer.autoNegative = autoNegative; } }, @@ -369,19 +383,19 @@ export const regionalPromptsSlice = createSlice({ //#region Base Layer positivePromptChanged: (state, action: PayloadAction) => { - state.baseLayer.positivePrompt = action.payload; + state.positivePrompt = action.payload; }, negativePromptChanged: (state, action: PayloadAction) => { - state.baseLayer.negativePrompt = action.payload; + state.negativePrompt = action.payload; }, positivePrompt2Changed: (state, action: PayloadAction) => { - state.baseLayer.positivePrompt2 = action.payload; + state.positivePrompt2 = action.payload; }, negativePrompt2Changed: (state, action: PayloadAction) => { - state.baseLayer.negativePrompt2 = action.payload; + state.negativePrompt2 = action.payload; }, shouldConcatPromptsChanged: (state, action: PayloadAction) => { - state.baseLayer.shouldConcatPrompts = action.payload; + state.shouldConcatPrompts = action.payload; }, widthChanged: (state, action: PayloadAction<{ width: number; updateAspectRatio?: boolean }>) => { const { width, updateAspectRatio } = action.payload; @@ -418,13 +432,13 @@ export const regionalPromptsSlice = createSlice({ }, undo: (state) => { // Invalidate the bbox for all layers to prevent stale bboxes - for (const layer of state.layers) { + for (const layer of state.layers.filter(isRenderableLayer)) { layer.bboxNeedsUpdate = true; } }, redo: (state) => { // Invalidate the bbox for all layers to prevent stale bboxes - for (const layer of state.layers) { + for (const layer of state.layers.filter(isRenderableLayer)) { layer.bboxNeedsUpdate = true; } }, @@ -432,7 +446,7 @@ export const regionalPromptsSlice = createSlice({ }, extraReducers(builder) { builder.addCase(controlAdapterRemoved, (state, action) => { - state.layers.filter(isVectorMaskLayer).forEach((layer) => { + state.layers.filter(isMaskedGuidanceLayer).forEach((layer) => { layer.ipAdapterIds = layer.ipAdapterIds.filter((id) => id !== action.payload.id); }); }); @@ -559,19 +573,20 @@ export const BACKGROUND_LAYER_ID = 'background_layer'; export const BACKGROUND_RECT_ID = 'background_layer.rect'; // Names (aka classes) for Konva layers and objects -export const VECTOR_MASK_LAYER_NAME = 'vector_mask_layer'; -export const VECTOR_MASK_LAYER_LINE_NAME = 'vector_mask_layer.line'; -export const VECTOR_MASK_LAYER_OBJECT_GROUP_NAME = 'vector_mask_layer.object_group'; -export const VECTOR_MASK_LAYER_RECT_NAME = 'vector_mask_layer.rect'; +export const MASKED_GUIDANCE_LAYER_NAME = 'masked_guidance_layer'; +export const MASKED_GUIDANCE_LAYER_LINE_NAME = 'masked_guidance_layer.line'; +export const MASKED_GUIDANCE_LAYER_OBJECT_GROUP_NAME = 'masked_guidance_layer.object_group'; +export const MASKED_GUIDANCE_LAYER_RECT_NAME = 'masked_guidance_layer.rect'; export const LAYER_BBOX_NAME = 'layer.bbox'; // Getters for non-singleton layer and object IDs -const getVectorMaskLayerId = (layerId: string) => `${VECTOR_MASK_LAYER_NAME}_${layerId}`; -const getVectorMaskLayerLineId = (layerId: string, lineId: string) => `${layerId}.line_${lineId}`; -const getVectorMaskLayerRectId = (layerId: string, lineId: string) => `${layerId}.rect_${lineId}`; -export const getVectorMaskLayerObjectGroupId = (layerId: string, groupId: string) => +const getMaskedGuidanceLayerId = (layerId: string) => `${MASKED_GUIDANCE_LAYER_NAME}_${layerId}`; +const getMaskedGuidanceLayerLineId = (layerId: string, lineId: string) => `${layerId}.line_${lineId}`; +const getMaskedGuidnaceLayerRectId = (layerId: string, lineId: string) => `${layerId}.rect_${lineId}`; +export const getMaskedGuidanceLayerObjectGroupId = (layerId: string, groupId: string) => `${layerId}.objectGroup_${groupId}`; export const getLayerBboxId = (layerId: string) => `${layerId}.bbox`; +const getControlLayerId = (layerId: string) => `control_layer_${layerId}`; export const regionalPromptsPersistConfig: PersistConfig = { name: regionalPromptsSlice.name, diff --git a/invokeai/frontend/web/src/features/regionalPrompts/util/bbox.ts b/invokeai/frontend/web/src/features/regionalPrompts/util/bbox.ts index 3182cbca7e..b17d805552 100644 --- a/invokeai/frontend/web/src/features/regionalPrompts/util/bbox.ts +++ b/invokeai/frontend/web/src/features/regionalPrompts/util/bbox.ts @@ -1,6 +1,6 @@ import openBase64ImageInTab from 'common/util/openBase64ImageInTab'; import { imageDataToDataURL } from 'features/canvas/util/blobToDataURL'; -import { VECTOR_MASK_LAYER_OBJECT_GROUP_NAME } from 'features/regionalPrompts/store/regionalPromptsSlice'; +import { MASKED_GUIDANCE_LAYER_OBJECT_GROUP_NAME } from 'features/regionalPrompts/store/regionalPromptsSlice'; import Konva from 'konva'; import type { Layer as KonvaLayerType } from 'konva/lib/Layer'; import type { IRect } from 'konva/lib/types'; @@ -81,7 +81,7 @@ export const getLayerBboxPixels = (layer: KonvaLayerType, preview: boolean = fal offscreenStage.add(layerClone); for (const child of layerClone.getChildren()) { - if (child.name() === VECTOR_MASK_LAYER_OBJECT_GROUP_NAME) { + if (child.name() === MASKED_GUIDANCE_LAYER_OBJECT_GROUP_NAME) { // We need to cache the group to ensure it composites out eraser strokes correctly child.opacity(1); child.cache(); diff --git a/invokeai/frontend/web/src/features/regionalPrompts/util/getLayerBlobs.ts b/invokeai/frontend/web/src/features/regionalPrompts/util/getLayerBlobs.ts index da8108784d..723d2611fd 100644 --- a/invokeai/frontend/web/src/features/regionalPrompts/util/getLayerBlobs.ts +++ b/invokeai/frontend/web/src/features/regionalPrompts/util/getLayerBlobs.ts @@ -1,7 +1,7 @@ import { getStore } from 'app/store/nanostores/store'; import openBase64ImageInTab from 'common/util/openBase64ImageInTab'; import { blobToDataURL } from 'features/canvas/util/blobToDataURL'; -import { isVectorMaskLayer, VECTOR_MASK_LAYER_NAME } from 'features/regionalPrompts/store/regionalPromptsSlice'; +import { isMaskedGuidanceLayer, MASKED_GUIDANCE_LAYER_NAME } from 'features/regionalPrompts/store/regionalPromptsSlice'; import { renderers } from 'features/regionalPrompts/util/renderers'; import Konva from 'konva'; import { assert } from 'tsafe'; @@ -19,12 +19,12 @@ export const getRegionalPromptLayerBlobs = async ( const state = getStore().getState(); const { layers } = state.regionalPrompts.present; const { width, height } = state.regionalPrompts.present.size; - const reduxLayers = layers.filter(isVectorMaskLayer); + const reduxLayers = layers.filter(isMaskedGuidanceLayer); const container = document.createElement('div'); const stage = new Konva.Stage({ container, width, height }); renderers.renderLayers(stage, reduxLayers, 1, 'brush'); - const konvaLayers = stage.find(`.${VECTOR_MASK_LAYER_NAME}`); + const konvaLayers = stage.find(`.${MASKED_GUIDANCE_LAYER_NAME}`); const blobs: Record = {}; // First remove all layers diff --git a/invokeai/frontend/web/src/features/regionalPrompts/util/renderers.ts b/invokeai/frontend/web/src/features/regionalPrompts/util/renderers.ts index 4f008c7758..903482b362 100644 --- a/invokeai/frontend/web/src/features/regionalPrompts/util/renderers.ts +++ b/invokeai/frontend/web/src/features/regionalPrompts/util/renderers.ts @@ -3,8 +3,8 @@ import { rgbaColorToString, rgbColorToString } from 'features/canvas/util/colorT import { getScaledFlooredCursorPosition } from 'features/regionalPrompts/hooks/mouseEventHooks'; import type { Layer, + MaskedGuidanceLayer, Tool, - VectorMaskLayer, VectorMaskLine, VectorMaskRect, } from 'features/regionalPrompts/store/regionalPromptsSlice'; @@ -13,19 +13,19 @@ import { BACKGROUND_LAYER_ID, BACKGROUND_RECT_ID, getLayerBboxId, - getVectorMaskLayerObjectGroupId, - isVectorMaskLayer, + getMaskedGuidanceLayerObjectGroupId, + isMaskedGuidanceLayer, LAYER_BBOX_NAME, + MASKED_GUIDANCE_LAYER_LINE_NAME, + MASKED_GUIDANCE_LAYER_NAME, + MASKED_GUIDANCE_LAYER_OBJECT_GROUP_NAME, + MASKED_GUIDANCE_LAYER_RECT_NAME, TOOL_PREVIEW_BRUSH_BORDER_INNER_ID, TOOL_PREVIEW_BRUSH_BORDER_OUTER_ID, TOOL_PREVIEW_BRUSH_FILL_ID, TOOL_PREVIEW_BRUSH_GROUP_ID, TOOL_PREVIEW_LAYER_ID, TOOL_PREVIEW_RECT_ID, - VECTOR_MASK_LAYER_LINE_NAME, - VECTOR_MASK_LAYER_NAME, - VECTOR_MASK_LAYER_OBJECT_GROUP_NAME, - VECTOR_MASK_LAYER_RECT_NAME, } from 'features/regionalPrompts/store/regionalPromptsSlice'; import { getLayerBboxFast, getLayerBboxPixels } from 'features/regionalPrompts/util/bbox'; import Konva from 'konva'; @@ -54,7 +54,7 @@ const getIsSelected = (layerId?: string | null) => { }; const selectVectorMaskObjects = (node: Konva.Node) => { - return node.name() === VECTOR_MASK_LAYER_LINE_NAME || node.name() === VECTOR_MASK_LAYER_RECT_NAME; + return node.name() === MASKED_GUIDANCE_LAYER_LINE_NAME || node.name() === MASKED_GUIDANCE_LAYER_RECT_NAME; }; /** @@ -138,7 +138,7 @@ const renderToolPreview = ( isMouseOver: boolean, brushSize: number ) => { - const layerCount = stage.find(`.${VECTOR_MASK_LAYER_NAME}`).length; + const layerCount = stage.find(`.${MASKED_GUIDANCE_LAYER_NAME}`).length; // Update the stage's pointer style if (layerCount === 0) { // We have no layers, so we should not render any tool @@ -221,13 +221,13 @@ const renderToolPreview = ( */ const createVectorMaskLayer = ( stage: Konva.Stage, - reduxLayer: VectorMaskLayer, + reduxLayer: MaskedGuidanceLayer, onLayerPosChanged?: (layerId: string, x: number, y: number) => void ) => { // This layer hasn't been added to the konva state yet const konvaLayer = new Konva.Layer({ id: reduxLayer.id, - name: VECTOR_MASK_LAYER_NAME, + name: MASKED_GUIDANCE_LAYER_NAME, draggable: true, dragDistance: 0, }); @@ -259,8 +259,8 @@ const createVectorMaskLayer = ( // The object group holds all of the layer's objects (e.g. lines and rects) const konvaObjectGroup = new Konva.Group({ - id: getVectorMaskLayerObjectGroupId(reduxLayer.id, uuidv4()), - name: VECTOR_MASK_LAYER_OBJECT_GROUP_NAME, + id: getMaskedGuidanceLayerObjectGroupId(reduxLayer.id, uuidv4()), + name: MASKED_GUIDANCE_LAYER_OBJECT_GROUP_NAME, listening: false, }); konvaLayer.add(konvaObjectGroup); @@ -279,7 +279,7 @@ const createVectorMaskLine = (reduxObject: VectorMaskLine, konvaGroup: Konva.Gro const vectorMaskLine = new Konva.Line({ id: reduxObject.id, key: reduxObject.id, - name: VECTOR_MASK_LAYER_LINE_NAME, + name: MASKED_GUIDANCE_LAYER_LINE_NAME, strokeWidth: reduxObject.strokeWidth, tension: 0, lineCap: 'round', @@ -301,7 +301,7 @@ const createVectorMaskRect = (reduxObject: VectorMaskRect, konvaGroup: Konva.Gro const vectorMaskRect = new Konva.Rect({ id: reduxObject.id, key: reduxObject.id, - name: VECTOR_MASK_LAYER_RECT_NAME, + name: MASKED_GUIDANCE_LAYER_RECT_NAME, x: reduxObject.x, y: reduxObject.y, width: reduxObject.width, @@ -322,7 +322,7 @@ const createVectorMaskRect = (reduxObject: VectorMaskRect, konvaGroup: Konva.Gro */ const renderVectorMaskLayer = ( stage: Konva.Stage, - reduxLayer: VectorMaskLayer, + reduxLayer: MaskedGuidanceLayer, globalMaskLayerOpacity: number, tool: Tool, onLayerPosChanged?: (layerId: string, x: number, y: number) => void @@ -340,13 +340,13 @@ const renderVectorMaskLayer = ( // Convert the color to a string, stripping the alpha - the object group will handle opacity. const rgbColor = rgbColorToString(reduxLayer.previewColor); - const konvaObjectGroup = konvaLayer.findOne(`.${VECTOR_MASK_LAYER_OBJECT_GROUP_NAME}`); + const konvaObjectGroup = konvaLayer.findOne(`.${MASKED_GUIDANCE_LAYER_OBJECT_GROUP_NAME}`); 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. let groupNeedsCache = false; - const objectIds = reduxLayer.objects.map(mapId); + const objectIds = reduxLayer.maskObjects.map(mapId); for (const objectNode of konvaObjectGroup.find(selectVectorMaskObjects)) { if (!objectIds.includes(objectNode.id())) { objectNode.destroy(); @@ -354,7 +354,7 @@ const renderVectorMaskLayer = ( } } - for (const reduxObject of reduxLayer.objects) { + for (const reduxObject of reduxLayer.maskObjects) { if (reduxObject.type === 'vector_mask_line') { const vectorMaskLine = stage.findOne(`#${reduxObject.id}`) ?? createVectorMaskLine(reduxObject, konvaObjectGroup); @@ -419,14 +419,14 @@ const renderLayers = ( const reduxLayerIds = reduxLayers.map(mapId); // Remove un-rendered layers - for (const konvaLayer of stage.find(`.${VECTOR_MASK_LAYER_NAME}`)) { + for (const konvaLayer of stage.find(`.${MASKED_GUIDANCE_LAYER_NAME}`)) { if (!reduxLayerIds.includes(konvaLayer.id())) { konvaLayer.destroy(); } } for (const reduxLayer of reduxLayers) { - if (isVectorMaskLayer(reduxLayer)) { + if (isMaskedGuidanceLayer(reduxLayer)) { renderVectorMaskLayer(stage, reduxLayer, globalMaskLayerOpacity, tool, onLayerPosChanged); } } @@ -494,14 +494,14 @@ const renderBbox = ( } for (const reduxLayer of reduxLayers) { - if (reduxLayer.type === 'vector_mask_layer') { + if (reduxLayer.type === 'masked_guidance_layer') { const konvaLayer = stage.findOne(`#${reduxLayer.id}`); assert(konvaLayer, `Layer ${reduxLayer.id} not found in stage`); let bbox = reduxLayer.bbox; // We only need to recalculate the bbox if the layer has changed and it has objects - if (reduxLayer.bboxNeedsUpdate && reduxLayer.objects.length) { + if (reduxLayer.bboxNeedsUpdate && reduxLayer.maskObjects.length) { // We only need to use the pixel-perfect bounding box if the layer has eraser strokes bbox = reduxLayer.needsPixelBbox ? getLayerBboxPixels(konvaLayer) : getLayerBboxFast(konvaLayer); // Update the layer's bbox in the redux store diff --git a/invokeai/frontend/web/src/features/sdxl/components/SDXLPrompts/ParamSDXLNegativeStylePrompt.tsx b/invokeai/frontend/web/src/features/sdxl/components/SDXLPrompts/ParamSDXLNegativeStylePrompt.tsx index f5d817a2e2..51347c769d 100644 --- a/invokeai/frontend/web/src/features/sdxl/components/SDXLPrompts/ParamSDXLNegativeStylePrompt.tsx +++ b/invokeai/frontend/web/src/features/sdxl/components/SDXLPrompts/ParamSDXLNegativeStylePrompt.tsx @@ -11,7 +11,7 @@ import { useTranslation } from 'react-i18next'; export const ParamSDXLNegativeStylePrompt = memo(() => { const dispatch = useAppDispatch(); - const prompt = useAppSelector((s) => s.regionalPrompts.present.baseLayer.negativePrompt2); + const prompt = useAppSelector((s) => s.regionalPrompts.present.negativePrompt2); const textareaRef = useRef(null); const { t } = useTranslation(); const handleChange = useCallback( diff --git a/invokeai/frontend/web/src/features/sdxl/components/SDXLPrompts/ParamSDXLPositiveStylePrompt.tsx b/invokeai/frontend/web/src/features/sdxl/components/SDXLPrompts/ParamSDXLPositiveStylePrompt.tsx index 39ce4eea09..cdff420b47 100644 --- a/invokeai/frontend/web/src/features/sdxl/components/SDXLPrompts/ParamSDXLPositiveStylePrompt.tsx +++ b/invokeai/frontend/web/src/features/sdxl/components/SDXLPrompts/ParamSDXLPositiveStylePrompt.tsx @@ -10,7 +10,7 @@ import { useTranslation } from 'react-i18next'; export const ParamSDXLPositiveStylePrompt = memo(() => { const dispatch = useAppDispatch(); - const prompt = useAppSelector((s) => s.regionalPrompts.present.baseLayer.positivePrompt2); + const prompt = useAppSelector((s) => s.regionalPrompts.present.positivePrompt2); const textareaRef = useRef(null); const { t } = useTranslation(); const handleChange = useCallback( diff --git a/invokeai/frontend/web/src/features/sdxl/components/SDXLPrompts/SDXLConcatButton.tsx b/invokeai/frontend/web/src/features/sdxl/components/SDXLPrompts/SDXLConcatButton.tsx index 01e859825d..b25698cc1d 100644 --- a/invokeai/frontend/web/src/features/sdxl/components/SDXLPrompts/SDXLConcatButton.tsx +++ b/invokeai/frontend/web/src/features/sdxl/components/SDXLPrompts/SDXLConcatButton.tsx @@ -6,7 +6,7 @@ import { useTranslation } from 'react-i18next'; import { PiLinkSimpleBold, PiLinkSimpleBreakBold } from 'react-icons/pi'; export const SDXLConcatButton = memo(() => { - const shouldConcatPrompts = useAppSelector((s) => s.regionalPrompts.present.baseLayer.shouldConcatPrompts); + const shouldConcatPrompts = useAppSelector((s) => s.regionalPrompts.present.shouldConcatPrompts); const dispatch = useAppDispatch(); const { t } = useTranslation(); diff --git a/invokeai/frontend/web/src/features/sdxl/components/SDXLPrompts/SDXLPrompts.tsx b/invokeai/frontend/web/src/features/sdxl/components/SDXLPrompts/SDXLPrompts.tsx index 4f47ecdda4..edfd6302a4 100644 --- a/invokeai/frontend/web/src/features/sdxl/components/SDXLPrompts/SDXLPrompts.tsx +++ b/invokeai/frontend/web/src/features/sdxl/components/SDXLPrompts/SDXLPrompts.tsx @@ -8,7 +8,7 @@ import { ParamSDXLNegativeStylePrompt } from './ParamSDXLNegativeStylePrompt'; import { ParamSDXLPositiveStylePrompt } from './ParamSDXLPositiveStylePrompt'; export const SDXLPrompts = memo(() => { - const shouldConcatPrompts = useAppSelector((s) => s.regionalPrompts.present.baseLayer.shouldConcatPrompts); + const shouldConcatPrompts = useAppSelector((s) => s.regionalPrompts.present.shouldConcatPrompts); return ( diff --git a/invokeai/frontend/web/src/features/settingsAccordions/components/ControlSettingsAccordion/ControlSettingsAccordion.tsx b/invokeai/frontend/web/src/features/settingsAccordions/components/ControlSettingsAccordion/ControlSettingsAccordion.tsx index 449091536e..843ed3a4b7 100644 --- a/invokeai/frontend/web/src/features/settingsAccordions/components/ControlSettingsAccordion/ControlSettingsAccordion.tsx +++ b/invokeai/frontend/web/src/features/settingsAccordions/components/ControlSettingsAccordion/ControlSettingsAccordion.tsx @@ -13,7 +13,7 @@ import { selectValidIPAdapters, selectValidT2IAdapters, } from 'features/controlAdapters/store/controlAdaptersSlice'; -import { isVectorMaskLayer, selectRegionalPromptsSlice } from 'features/regionalPrompts/store/regionalPromptsSlice'; +import { isMaskedGuidanceLayer, selectRegionalPromptsSlice } from 'features/regionalPrompts/store/regionalPromptsSlice'; import { useStandaloneAccordionToggle } from 'features/settingsAccordions/hooks/useStandaloneAccordionToggle'; import { useFeatureStatus } from 'features/system/hooks/useFeatureStatus'; import { Fragment, memo } from 'react'; @@ -28,7 +28,7 @@ const selector = createMemoizedSelector( const enabledNonRegionalIPAdapterCount = selectAllIPAdapters(controlAdapters) .filter( - (ca) => !regionalPrompts.present.layers.filter(isVectorMaskLayer).some((l) => l.ipAdapterIds.includes(ca.id)) + (ca) => !regionalPrompts.present.layers.filter(isMaskedGuidanceLayer).some((l) => l.ipAdapterIds.includes(ca.id)) ) .filter((ca) => ca.isEnabled).length; @@ -59,7 +59,7 @@ const selector = createMemoizedSelector( } const controlAdapterIds = selectControlAdapterIds(controlAdapters).filter( - (id) => !regionalPrompts.present.layers.filter(isVectorMaskLayer).some((l) => l.ipAdapterIds.includes(id)) + (id) => !regionalPrompts.present.layers.filter(isMaskedGuidanceLayer).some((l) => l.ipAdapterIds.includes(id)) ); return { From ebeae41cb2005af3d6b059e8bc23bf6ba00cbb3b Mon Sep 17 00:00:00 2001 From: psychedelicious <4822129+psychedelicious@users.noreply.github.com> Date: Mon, 29 Apr 2024 17:33:16 +1000 Subject: [PATCH 08/52] tidy(ui): minor ca component tidy --- .../components/ControlAdapterConfig.tsx | 2 +- .../parameters/ParamControlAdapterIPMethod.tsx | 4 ---- .../parameters/ParamControlAdapterModel.tsx | 11 ++++------- .../hooks/useControlAdapterIPMethod.ts | 8 ++++---- 4 files changed, 9 insertions(+), 16 deletions(-) diff --git a/invokeai/frontend/web/src/features/controlAdapters/components/ControlAdapterConfig.tsx b/invokeai/frontend/web/src/features/controlAdapters/components/ControlAdapterConfig.tsx index fcc816d75f..032e46f477 100644 --- a/invokeai/frontend/web/src/features/controlAdapters/components/ControlAdapterConfig.tsx +++ b/invokeai/frontend/web/src/features/controlAdapters/components/ControlAdapterConfig.tsx @@ -113,7 +113,7 @@ const ControlAdapterConfig = (props: { id: string; number: number }) => { - + {controlAdapterType === 'ip_adapter' && } diff --git a/invokeai/frontend/web/src/features/controlAdapters/components/parameters/ParamControlAdapterIPMethod.tsx b/invokeai/frontend/web/src/features/controlAdapters/components/parameters/ParamControlAdapterIPMethod.tsx index 7d531e2106..d7d91ab780 100644 --- a/invokeai/frontend/web/src/features/controlAdapters/components/parameters/ParamControlAdapterIPMethod.tsx +++ b/invokeai/frontend/web/src/features/controlAdapters/components/parameters/ParamControlAdapterIPMethod.tsx @@ -46,10 +46,6 @@ const ParamControlAdapterIPMethod = ({ id }: Props) => { const value = useMemo(() => options.find((o) => o.value === method), [options, method]); - if (!method) { - return null; - } - return ( diff --git a/invokeai/frontend/web/src/features/controlAdapters/components/parameters/ParamControlAdapterModel.tsx b/invokeai/frontend/web/src/features/controlAdapters/components/parameters/ParamControlAdapterModel.tsx index 00c7d5859d..73a7d695b3 100644 --- a/invokeai/frontend/web/src/features/controlAdapters/components/parameters/ParamControlAdapterModel.tsx +++ b/invokeai/frontend/web/src/features/controlAdapters/components/parameters/ParamControlAdapterModel.tsx @@ -102,13 +102,9 @@ const ParamControlAdapterModel = ({ id }: ParamControlAdapterModelProps) => { ); return ( - + - + { { const selector = useMemo( () => createMemoizedSelector(selectControlAdaptersSlice, (controlAdapters) => { - const cn = selectControlAdapterById(controlAdapters, id); - if (cn && cn?.type === 'ip_adapter') { - return cn.method; - } + const ca = selectControlAdapterById(controlAdapters, id); + assert(ca?.type === 'ip_adapter'); + return ca.method; }), [id] ); From 2d7b8c2a1bd55acf1d98ccafea2b36f3d9c5fc10 Mon Sep 17 00:00:00 2001 From: psychedelicious <4822129+psychedelicious@users.noreply.github.com> Date: Mon, 29 Apr 2024 22:38:05 +1000 Subject: [PATCH 09/52] fix(backend): do not round image dims to 64 in controlnet processor resize Rounding the dims results in control images that are subtly different than the input. We round to the nearest 8px later, there's no need to round now. --- invokeai/backend/image_util/util.py | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/invokeai/backend/image_util/util.py b/invokeai/backend/image_util/util.py index f704f068e3..5b2116975f 100644 --- a/invokeai/backend/image_util/util.py +++ b/invokeai/backend/image_util/util.py @@ -144,10 +144,8 @@ def resize_image_to_resolution(input_image: np.ndarray, resolution: int) -> np.n h = float(input_image.shape[0]) w = float(input_image.shape[1]) scaling_factor = float(resolution) / min(h, w) - h *= scaling_factor - w *= scaling_factor - h = int(np.round(h / 64.0)) * 64 - w = int(np.round(w / 64.0)) * 64 + h = int(h * scaling_factor) + w = int(w * scaling_factor) if scaling_factor > 1: return cv2.resize(input_image, (w, h), interpolation=cv2.INTER_LANCZOS4) else: From e822897b1c985e33ef5842f89c74fdd6d430e41f Mon Sep 17 00:00:00 2001 From: psychedelicious <4822129+psychedelicious@users.noreply.github.com> Date: Mon, 29 Apr 2024 22:38:29 +1000 Subject: [PATCH 10/52] feat(nodes): add prototype heuristic image resize node Uses the fancy cnet resize that retains edges. --- .../controlnet_image_processors.py | 29 +++++++++++++++++-- 1 file changed, 27 insertions(+), 2 deletions(-) diff --git a/invokeai/app/invocations/controlnet_image_processors.py b/invokeai/app/invocations/controlnet_image_processors.py index 354a736a74..6510d2f74a 100644 --- a/invokeai/app/invocations/controlnet_image_processors.py +++ b/invokeai/app/invocations/controlnet_image_processors.py @@ -35,15 +35,16 @@ from invokeai.app.invocations.model import ModelIdentifierField from invokeai.app.invocations.primitives import ImageOutput from invokeai.app.invocations.util import validate_begin_end_step, validate_weights from invokeai.app.services.shared.invocation_context import InvocationContext -from invokeai.app.util.controlnet_utils import CONTROLNET_MODE_VALUES, CONTROLNET_RESIZE_VALUES +from invokeai.app.util.controlnet_utils import CONTROLNET_MODE_VALUES, CONTROLNET_RESIZE_VALUES, heuristic_resize from invokeai.backend.image_util.canny import get_canny_edges from invokeai.backend.image_util.depth_anything import DepthAnythingDetector from invokeai.backend.image_util.dw_openpose import DWOpenposeDetector from invokeai.backend.image_util.hed import HEDProcessor from invokeai.backend.image_util.lineart import LineartProcessor from invokeai.backend.image_util.lineart_anime import LineartAnimeProcessor +from invokeai.backend.image_util.util import np_to_pil, pil_to_np -from .baseinvocation import BaseInvocation, BaseInvocationOutput, invocation, invocation_output +from .baseinvocation import BaseInvocation, BaseInvocationOutput, Classification, invocation, invocation_output class ControlField(BaseModel): @@ -634,3 +635,27 @@ class DWOpenposeImageProcessorInvocation(ImageProcessorInvocation): resolution=self.image_resolution, ) return processed_image + + +@invocation( + "heuristic_resize", + title="Heuristic Resize", + tags=["image, controlnet"], + category="image", + version="1.0.0", + classification=Classification.Prototype, +) +class HeuristicResizeInvocation(BaseInvocation): + """Resize an image using a heuristic method. Preserves edge maps.""" + + image: ImageField = InputField(description="The image to resize") + width: int = InputField(default=512, gt=0, description="The width to resize to (px)") + height: int = InputField(default=512, gt=0, description="The height to resize to (px)") + + def invoke(self, context: InvocationContext) -> ImageOutput: + image = context.images.get_pil(self.image.image_name, "RGB") + np_img = pil_to_np(image) + np_resized = heuristic_resize(np_img, (self.width, self.height)) + resized = np_to_pil(np_resized) + image_dto = context.images.save(image=resized) + return ImageOutput.build(image_dto) From ded82675050f839bd898f74c84ec546da9428cd6 Mon Sep 17 00:00:00 2001 From: psychedelicious <4822129+psychedelicious@users.noreply.github.com> Date: Mon, 29 Apr 2024 23:31:33 +1000 Subject: [PATCH 11/52] WIP control adapters in regional --- invokeai/frontend/web/public/locales/en.json | 7 +- .../middleware/listenerMiddleware/index.ts | 3 + .../listeners/canvasImageToControlNet.ts | 4 +- .../listeners/canvasMaskToControlNet.ts | 4 +- .../listeners/controlNetImageProcessed.ts | 2 +- .../listeners/imageDropped.ts | 2 +- .../listeners/imageUploaded.ts | 2 +- .../regionalControlToControlAdapterBridge.ts | 93 ++++++ .../store/controlAdaptersSlice.ts | 116 +++++-- .../features/controlAdapters/store/types.ts | 4 + .../util/buildControlAdapter.ts | 4 + .../web/src/features/metadata/util/parsers.ts | 6 +- .../util/graph/addControlNetToLinearGraph.ts | 38 ++- .../util/graph/addIPAdapterToLinearGraph.ts | 43 ++- .../util/graph/addRegionalPromptsToGraph.ts | 7 +- .../util/graph/addT2IAdapterToLinearGraph.ts | 39 ++- .../components/AddLayerButton.tsx | 27 +- .../components/AddPromptButtons.tsx | 4 +- .../regionalPrompts/components/BrushSize.tsx | 2 +- .../ControlAdapterLayerListItem.tsx | 62 ++++ .../components/DeleteAllLayersButton.tsx | 2 +- .../components/IPAdapterLayerListItem.tsx | 62 ++++ .../regionalPrompts/components/LayerTitle.tsx | 29 ++ ...em.tsx => MaskedGuidanceLayerListItem.tsx} | 13 +- .../RPLayerAutoNegativeCheckbox.tsx | 4 +- .../components/RPLayerIPAdapterList.tsx | 58 +++- .../components/RPLayerMenu.tsx | 4 +- .../components/RPLayerSettingsPopover.tsx | 4 +- .../RegionalPromptsPanelContent.tsx | 41 ++- .../components/StageComponent.tsx | 77 ++--- .../components/ToolChooser.tsx | 12 +- .../ControlAdapterImagePreview.tsx | 230 +++++++++++++ .../ControlAdapterLayerConfig.tsx | 70 ++++ .../ParamControlAdapterBeginEnd.tsx | 89 ++++++ .../ParamControlAdapterControlMode.tsx | 66 ++++ .../ParamControlAdapterModel.tsx | 136 ++++++++ .../ParamControlAdapterWeight.tsx | 74 +++++ .../regionalPrompts/hooks/layerStateHooks.ts | 9 +- .../hooks/useRegionalControlTitle.ts | 2 +- .../features/regionalPrompts/store/actions.ts | 3 + .../store/regionalPromptsSlice.ts | 302 +++++++++--------- .../features/regionalPrompts/store/types.ts | 91 ++++++ .../regionalPrompts/util/renderers.ts | 156 ++++++++- .../ControlSettingsAccordion.tsx | 21 +- .../ui/components/ParametersPanel.tsx | 2 +- 45 files changed, 1689 insertions(+), 337 deletions(-) create mode 100644 invokeai/frontend/web/src/app/store/middleware/listenerMiddleware/listeners/regionalControlToControlAdapterBridge.ts create mode 100644 invokeai/frontend/web/src/features/regionalPrompts/components/ControlAdapterLayerListItem.tsx create mode 100644 invokeai/frontend/web/src/features/regionalPrompts/components/IPAdapterLayerListItem.tsx create mode 100644 invokeai/frontend/web/src/features/regionalPrompts/components/LayerTitle.tsx rename invokeai/frontend/web/src/features/regionalPrompts/components/{RPLayerListItem.tsx => MaskedGuidanceLayerListItem.tsx} (91%) create mode 100644 invokeai/frontend/web/src/features/regionalPrompts/components/controlAdapterOverrides/ControlAdapterImagePreview.tsx create mode 100644 invokeai/frontend/web/src/features/regionalPrompts/components/controlAdapterOverrides/ControlAdapterLayerConfig.tsx create mode 100644 invokeai/frontend/web/src/features/regionalPrompts/components/controlAdapterOverrides/ParamControlAdapterBeginEnd.tsx create mode 100644 invokeai/frontend/web/src/features/regionalPrompts/components/controlAdapterOverrides/ParamControlAdapterControlMode.tsx create mode 100644 invokeai/frontend/web/src/features/regionalPrompts/components/controlAdapterOverrides/ParamControlAdapterModel.tsx create mode 100644 invokeai/frontend/web/src/features/regionalPrompts/components/controlAdapterOverrides/ParamControlAdapterWeight.tsx create mode 100644 invokeai/frontend/web/src/features/regionalPrompts/store/actions.ts create mode 100644 invokeai/frontend/web/src/features/regionalPrompts/store/types.ts diff --git a/invokeai/frontend/web/public/locales/en.json b/invokeai/frontend/web/public/locales/en.json index c130b11ba6..d901c4be74 100644 --- a/invokeai/frontend/web/public/locales/en.json +++ b/invokeai/frontend/web/public/locales/en.json @@ -156,6 +156,7 @@ "balanced": "Balanced", "base": "Base", "beginEndStepPercent": "Begin / End Step Percentage", + "beginEndStepPercentShort": "Begin/End %", "bgth": "bg_th", "canny": "Canny", "cannyDescription": "Canny edge detection", @@ -1531,6 +1532,10 @@ "maskPreviewColor": "Mask Preview Color", "addPositivePrompt": "Add $t(common.positivePrompt)", "addNegativePrompt": "Add $t(common.negativePrompt)", - "addIPAdapter": "Add $t(common.ipAdapter)" + "addIPAdapter": "Add $t(common.ipAdapter)", + "maskedGuidance": "Masked Guidance", + "maskedGuidanceLayer": "$t(regionalPrompts.maskedGuidance) $t(unifiedCanvas.layer)", + "controlNetLayer": "$t(common.controlNet) $t(unifiedCanvas.layer)", + "ipAdapterLayer": "$t(common.ipAdapter) $t(unifiedCanvas.layer)" } } diff --git a/invokeai/frontend/web/src/app/store/middleware/listenerMiddleware/index.ts b/invokeai/frontend/web/src/app/store/middleware/listenerMiddleware/index.ts index cd0c1290e9..7474a79f18 100644 --- a/invokeai/frontend/web/src/app/store/middleware/listenerMiddleware/index.ts +++ b/invokeai/frontend/web/src/app/store/middleware/listenerMiddleware/index.ts @@ -35,6 +35,7 @@ import { addInitialImageSelectedListener } from 'app/store/middleware/listenerMi import { addModelSelectedListener } from 'app/store/middleware/listenerMiddleware/listeners/modelSelected'; import { addModelsLoadedListener } from 'app/store/middleware/listenerMiddleware/listeners/modelsLoaded'; import { addDynamicPromptsListener } from 'app/store/middleware/listenerMiddleware/listeners/promptChanged'; +import { addRegionalControlToControlAdapterBridge } from 'app/store/middleware/listenerMiddleware/listeners/regionalControlToControlAdapterBridge'; import { addSocketConnectedEventListener } from 'app/store/middleware/listenerMiddleware/listeners/socketio/socketConnected'; import { addSocketDisconnectedEventListener } from 'app/store/middleware/listenerMiddleware/listeners/socketio/socketDisconnected'; import { addGeneratorProgressEventListener } from 'app/store/middleware/listenerMiddleware/listeners/socketio/socketGeneratorProgress'; @@ -157,3 +158,5 @@ addUpscaleRequestedListener(startAppListening); addDynamicPromptsListener(startAppListening); addSetDefaultSettingsListener(startAppListening); + +addRegionalControlToControlAdapterBridge(startAppListening); diff --git a/invokeai/frontend/web/src/app/store/middleware/listenerMiddleware/listeners/canvasImageToControlNet.ts b/invokeai/frontend/web/src/app/store/middleware/listenerMiddleware/listeners/canvasImageToControlNet.ts index 55392ebff4..b1b19b35dc 100644 --- a/invokeai/frontend/web/src/app/store/middleware/listenerMiddleware/listeners/canvasImageToControlNet.ts +++ b/invokeai/frontend/web/src/app/store/middleware/listenerMiddleware/listeners/canvasImageToControlNet.ts @@ -48,12 +48,10 @@ export const addCanvasImageToControlNetListener = (startAppListening: AppStartLi }) ).unwrap(); - const { image_name } = imageDTO; - dispatch( controlAdapterImageChanged({ id, - controlImage: image_name, + controlImage: imageDTO, }) ); }, diff --git a/invokeai/frontend/web/src/app/store/middleware/listenerMiddleware/listeners/canvasMaskToControlNet.ts b/invokeai/frontend/web/src/app/store/middleware/listenerMiddleware/listeners/canvasMaskToControlNet.ts index 569b4badc7..b3014277f1 100644 --- a/invokeai/frontend/web/src/app/store/middleware/listenerMiddleware/listeners/canvasMaskToControlNet.ts +++ b/invokeai/frontend/web/src/app/store/middleware/listenerMiddleware/listeners/canvasMaskToControlNet.ts @@ -58,12 +58,10 @@ export const addCanvasMaskToControlNetListener = (startAppListening: AppStartLis }) ).unwrap(); - const { image_name } = imageDTO; - dispatch( controlAdapterImageChanged({ id, - controlImage: image_name, + controlImage: imageDTO, }) ); }, diff --git a/invokeai/frontend/web/src/app/store/middleware/listenerMiddleware/listeners/controlNetImageProcessed.ts b/invokeai/frontend/web/src/app/store/middleware/listenerMiddleware/listeners/controlNetImageProcessed.ts index 0055866aa7..08afc98836 100644 --- a/invokeai/frontend/web/src/app/store/middleware/listenerMiddleware/listeners/controlNetImageProcessed.ts +++ b/invokeai/frontend/web/src/app/store/middleware/listenerMiddleware/listeners/controlNetImageProcessed.ts @@ -91,7 +91,7 @@ export const addControlNetImageProcessedListener = (startAppListening: AppStartL dispatch( controlAdapterProcessedImageChanged({ id, - processedControlImage: processedControlImage.image_name, + processedControlImage, }) ); } diff --git a/invokeai/frontend/web/src/app/store/middleware/listenerMiddleware/listeners/imageDropped.ts b/invokeai/frontend/web/src/app/store/middleware/listenerMiddleware/listeners/imageDropped.ts index 5c1f321b64..307e3487dd 100644 --- a/invokeai/frontend/web/src/app/store/middleware/listenerMiddleware/listeners/imageDropped.ts +++ b/invokeai/frontend/web/src/app/store/middleware/listenerMiddleware/listeners/imageDropped.ts @@ -71,7 +71,7 @@ export const addImageDroppedListener = (startAppListening: AppStartListening) => dispatch( controlAdapterImageChanged({ id, - controlImage: activeData.payload.imageDTO.image_name, + controlImage: activeData.payload.imageDTO, }) ); dispatch( diff --git a/invokeai/frontend/web/src/app/store/middleware/listenerMiddleware/listeners/imageUploaded.ts b/invokeai/frontend/web/src/app/store/middleware/listenerMiddleware/listeners/imageUploaded.ts index 2cebf0aef8..a2ca4baeb1 100644 --- a/invokeai/frontend/web/src/app/store/middleware/listenerMiddleware/listeners/imageUploaded.ts +++ b/invokeai/frontend/web/src/app/store/middleware/listenerMiddleware/listeners/imageUploaded.ts @@ -96,7 +96,7 @@ export const addImageUploadedFulfilledListener = (startAppListening: AppStartLis dispatch( controlAdapterImageChanged({ id, - controlImage: imageDTO.image_name, + controlImage: imageDTO, }) ); dispatch( diff --git a/invokeai/frontend/web/src/app/store/middleware/listenerMiddleware/listeners/regionalControlToControlAdapterBridge.ts b/invokeai/frontend/web/src/app/store/middleware/listenerMiddleware/listeners/regionalControlToControlAdapterBridge.ts new file mode 100644 index 0000000000..2d80902550 --- /dev/null +++ b/invokeai/frontend/web/src/app/store/middleware/listenerMiddleware/listeners/regionalControlToControlAdapterBridge.ts @@ -0,0 +1,93 @@ +import { createAction } from '@reduxjs/toolkit'; +import type { AppStartListening } from 'app/store/middleware/listenerMiddleware'; +import { controlAdapterAdded, controlAdapterRemoved } from 'features/controlAdapters/store/controlAdaptersSlice'; +import { + controlAdapterLayerAdded, + ipAdapterLayerAdded, + layerDeleted, + maskedGuidanceLayerAdded, + maskLayerIPAdapterAdded, + maskLayerIPAdapterDeleted, +} from 'features/regionalPrompts/store/regionalPromptsSlice'; +import type { Layer } from 'features/regionalPrompts/store/types'; +import { assert } from 'tsafe'; +import { v4 as uuidv4 } from 'uuid'; + +export const guidanceLayerAdded = createAction('regionalPrompts/guidanceLayerAdded'); +export const guidanceLayerDeleted = createAction('regionalPrompts/guidanceLayerDeleted'); +export const allLayersDeleted = createAction('regionalPrompts/allLayersDeleted'); +export const guidanceLayerIPAdapterAdded = createAction('regionalPrompts/guidanceLayerIPAdapterAdded'); +export const guidanceLayerIPAdapterDeleted = createAction<{ layerId: string; ipAdapterId: string }>( + 'regionalPrompts/guidanceLayerIPAdapterDeleted' +); + +export const addRegionalControlToControlAdapterBridge = (startAppListening: AppStartListening) => { + startAppListening({ + actionCreator: guidanceLayerAdded, + effect: (action, { dispatch }) => { + const type = action.payload; + const layerId = uuidv4(); + if (type === 'ip_adapter_layer') { + const ipAdapterId = uuidv4(); + dispatch(controlAdapterAdded({ type: 'ip_adapter', overrides: { id: ipAdapterId } })); + dispatch(ipAdapterLayerAdded({ layerId, ipAdapterId })); + } else if (type === 'control_adapter_layer') { + const controlNetId = uuidv4(); + dispatch(controlAdapterAdded({ type: 'controlnet', overrides: { id: controlNetId } })); + dispatch(controlAdapterLayerAdded({ layerId, controlNetId })); + } else if (type === 'masked_guidance_layer') { + dispatch(maskedGuidanceLayerAdded({ layerId })); + } + }, + }); + + startAppListening({ + actionCreator: guidanceLayerDeleted, + effect: (action, { getState, dispatch }) => { + const layerId = action.payload; + const state = getState(); + const layer = state.regionalPrompts.present.layers.find((l) => l.id === layerId); + assert(layer, `Layer ${layerId} not found`); + + if (layer.type === 'ip_adapter_layer') { + dispatch(controlAdapterRemoved({ id: layer.ipAdapterId })); + } else if (layer.type === 'control_adapter_layer') { + dispatch(controlAdapterRemoved({ id: layer.controlNetId })); + } else if (layer.type === 'masked_guidance_layer') { + for (const ipAdapterId of layer.ipAdapterIds) { + dispatch(controlAdapterRemoved({ id: ipAdapterId })); + } + } + dispatch(layerDeleted(layerId)); + }, + }); + + startAppListening({ + actionCreator: allLayersDeleted, + effect: (action, { dispatch, getOriginalState }) => { + const state = getOriginalState(); + for (const layer of state.regionalPrompts.present.layers) { + dispatch(guidanceLayerDeleted(layer.id)); + } + }, + }); + + startAppListening({ + actionCreator: guidanceLayerIPAdapterAdded, + effect: (action, { dispatch }) => { + const layerId = action.payload; + const ipAdapterId = uuidv4(); + dispatch(controlAdapterAdded({ type: 'ip_adapter', overrides: { id: ipAdapterId } })); + dispatch(maskLayerIPAdapterAdded({ layerId, ipAdapterId })); + }, + }); + + startAppListening({ + actionCreator: guidanceLayerIPAdapterDeleted, + effect: (action, { dispatch }) => { + const { layerId, ipAdapterId } = action.payload; + dispatch(controlAdapterRemoved({ id: ipAdapterId })); + dispatch(maskLayerIPAdapterDeleted({ layerId, ipAdapterId })); + }, + }); +}; diff --git a/invokeai/frontend/web/src/features/controlAdapters/store/controlAdaptersSlice.ts b/invokeai/frontend/web/src/features/controlAdapters/store/controlAdaptersSlice.ts index 3e335f4cc3..4c5a6fe85b 100644 --- a/invokeai/frontend/web/src/features/controlAdapters/store/controlAdaptersSlice.ts +++ b/invokeai/frontend/web/src/features/controlAdapters/store/controlAdaptersSlice.ts @@ -6,9 +6,8 @@ import { deepClone } from 'common/util/deepClone'; import { buildControlAdapter } from 'features/controlAdapters/util/buildControlAdapter'; import { buildControlAdapterProcessor } from 'features/controlAdapters/util/buildControlAdapterProcessor'; import { zModelIdentifierField } from 'features/nodes/types/common'; -import { maskLayerIPAdapterAdded } from 'features/regionalPrompts/store/regionalPromptsSlice'; import { merge, uniq } from 'lodash-es'; -import type { ControlNetModelConfig, IPAdapterModelConfig, T2IAdapterModelConfig } from 'services/api/types'; +import type { ControlNetModelConfig, ImageDTO, IPAdapterModelConfig, T2IAdapterModelConfig } from 'services/api/types'; import { socketInvocationError } from 'services/events/actions'; import { v4 as uuidv4 } from 'uuid'; @@ -135,23 +134,46 @@ export const controlAdaptersSlice = createSlice({ const { id, isEnabled } = action.payload; caAdapter.updateOne(state, { id, changes: { isEnabled } }); }, - controlAdapterImageChanged: ( - state, - action: PayloadAction<{ - id: string; - controlImage: string | null; - }> - ) => { + controlAdapterImageChanged: (state, action: PayloadAction<{ id: string; controlImage: ImageDTO | null }>) => { const { id, controlImage } = action.payload; const ca = selectControlAdapterById(state, id); if (!ca) { return; } - caAdapter.updateOne(state, { - id, - changes: { controlImage, processedControlImage: null }, - }); + if (isControlNetOrT2IAdapter(ca)) { + if (controlImage) { + const { image_name, width, height } = controlImage; + const processorNode = deepClone(ca.processorNode); + const minDim = Math.min(controlImage.width, controlImage.height); + if ('detect_resolution' in processorNode) { + processorNode.detect_resolution = minDim; + } + if ('image_resolution' in processorNode) { + processorNode.image_resolution = minDim; + } + if ('resolution' in processorNode) { + processorNode.resolution = minDim; + } + caAdapter.updateOne(state, { + id, + changes: { + processorNode, + controlImage: image_name, + controlImageDimensions: { width, height }, + processedControlImage: null, + }, + }); + } else { + caAdapter.updateOne(state, { + id, + changes: { controlImage: null, controlImageDimensions: null, processedControlImage: null }, + }); + } + } else { + // ip adapter + caAdapter.updateOne(state, { id, changes: { controlImage: controlImage?.image_name ?? null } }); + } if (controlImage !== null && isControlNetOrT2IAdapter(ca) && ca.processorType !== 'none') { state.pendingControlImages.push(id); @@ -161,7 +183,7 @@ export const controlAdaptersSlice = createSlice({ state, action: PayloadAction<{ id: string; - processedControlImage: string | null; + processedControlImage: ImageDTO | null; }> ) => { const { id, processedControlImage } = action.payload; @@ -174,12 +196,24 @@ export const controlAdaptersSlice = createSlice({ return; } - caAdapter.updateOne(state, { - id, - changes: { - processedControlImage, - }, - }); + if (processedControlImage) { + const { image_name, width, height } = processedControlImage; + caAdapter.updateOne(state, { + id, + changes: { + processedControlImage: image_name, + processedControlImageDimensions: { width, height }, + }, + }); + } else { + caAdapter.updateOne(state, { + id, + changes: { + processedControlImage: null, + processedControlImageDimensions: null, + }, + }); + } state.pendingControlImages = state.pendingControlImages.filter((pendingId) => pendingId !== id); }, @@ -222,9 +256,22 @@ export const controlAdaptersSlice = createSlice({ } const processor = buildControlAdapterProcessor(modelConfig); - update.changes.processorType = processor.processorType; - update.changes.processorNode = processor.processorNode; - + if (processor.processorType !== cn.processorNode.type) { + update.changes.processorType = processor.processorType; + update.changes.processorNode = processor.processorNode; + if (cn.controlImageDimensions) { + const minDim = Math.min(cn.controlImageDimensions.width, cn.controlImageDimensions.height); + if ('detect_resolution' in update.changes.processorNode) { + update.changes.processorNode.detect_resolution = minDim; + } + if ('image_resolution' in update.changes.processorNode) { + update.changes.processorNode.image_resolution = minDim; + } + if ('resolution' in update.changes.processorNode) { + update.changes.processorNode.resolution = minDim; + } + } + } caAdapter.updateOne(state, update); }, controlAdapterWeightChanged: (state, action: PayloadAction<{ id: string; weight: number }>) => { @@ -341,8 +388,23 @@ export const controlAdaptersSlice = createSlice({ if (update.changes.shouldAutoConfig && modelConfig) { const processor = buildControlAdapterProcessor(modelConfig); - update.changes.processorType = processor.processorType; - update.changes.processorNode = processor.processorNode; + if (processor.processorType !== cn.processorNode.type) { + update.changes.processorType = processor.processorType; + update.changes.processorNode = processor.processorNode; + // Copy image resolution settings, urgh + if (cn.controlImageDimensions) { + const minDim = Math.min(cn.controlImageDimensions.width, cn.controlImageDimensions.height); + if ('detect_resolution' in update.changes.processorNode) { + update.changes.processorNode.detect_resolution = minDim; + } + if ('image_resolution' in update.changes.processorNode) { + update.changes.processorNode.image_resolution = minDim; + } + if ('resolution' in update.changes.processorNode) { + update.changes.processorNode.resolution = minDim; + } + } + } } caAdapter.updateOne(state, update); @@ -383,10 +445,6 @@ export const controlAdaptersSlice = createSlice({ builder.addCase(socketInvocationError, (state) => { state.pendingControlImages = []; }); - - builder.addCase(maskLayerIPAdapterAdded, (state, action) => { - caAdapter.addOne(state, buildControlAdapter(action.meta.uuid, 'ip_adapter')); - }); }, }); diff --git a/invokeai/frontend/web/src/features/controlAdapters/store/types.ts b/invokeai/frontend/web/src/features/controlAdapters/store/types.ts index 7e2f18af5c..80af59cd01 100644 --- a/invokeai/frontend/web/src/features/controlAdapters/store/types.ts +++ b/invokeai/frontend/web/src/features/controlAdapters/store/types.ts @@ -225,7 +225,9 @@ export type ControlNetConfig = { controlMode: ControlMode; resizeMode: ResizeMode; controlImage: string | null; + controlImageDimensions: { width: number; height: number } | null; processedControlImage: string | null; + processedControlImageDimensions: { width: number; height: number } | null; processorType: ControlAdapterProcessorType; processorNode: RequiredControlAdapterProcessorNode; shouldAutoConfig: boolean; @@ -241,7 +243,9 @@ export type T2IAdapterConfig = { endStepPct: number; resizeMode: ResizeMode; controlImage: string | null; + controlImageDimensions: { width: number; height: number } | null; processedControlImage: string | null; + processedControlImageDimensions: { width: number; height: number } | null; processorType: ControlAdapterProcessorType; processorNode: RequiredControlAdapterProcessorNode; shouldAutoConfig: boolean; diff --git a/invokeai/frontend/web/src/features/controlAdapters/util/buildControlAdapter.ts b/invokeai/frontend/web/src/features/controlAdapters/util/buildControlAdapter.ts index ad7bdba363..7c9c28e2b3 100644 --- a/invokeai/frontend/web/src/features/controlAdapters/util/buildControlAdapter.ts +++ b/invokeai/frontend/web/src/features/controlAdapters/util/buildControlAdapter.ts @@ -20,7 +20,9 @@ export const initialControlNet: Omit = { controlMode: 'balanced', resizeMode: 'just_resize', controlImage: null, + controlImageDimensions: null, processedControlImage: null, + processedControlImageDimensions: null, processorType: 'canny_image_processor', processorNode: CONTROLNET_PROCESSORS.canny_image_processor.buildDefaults() as RequiredCannyImageProcessorInvocation, shouldAutoConfig: true, @@ -35,7 +37,9 @@ export const initialT2IAdapter: Omit = { endStepPct: 1, resizeMode: 'just_resize', controlImage: null, + controlImageDimensions: null, processedControlImage: null, + processedControlImageDimensions: null, processorType: 'canny_image_processor', processorNode: CONTROLNET_PROCESSORS.canny_image_processor.buildDefaults() as RequiredCannyImageProcessorInvocation, shouldAutoConfig: true, diff --git a/invokeai/frontend/web/src/features/metadata/util/parsers.ts b/invokeai/frontend/web/src/features/metadata/util/parsers.ts index 3decea6737..5d2bd78784 100644 --- a/invokeai/frontend/web/src/features/metadata/util/parsers.ts +++ b/invokeai/frontend/web/src/features/metadata/util/parsers.ts @@ -286,7 +286,9 @@ const parseControlNet: MetadataParseFunc = async (meta controlMode: control_mode ?? initialControlNet.controlMode, resizeMode: resize_mode ?? initialControlNet.resizeMode, controlImage: image?.image_name ?? null, + controlImageDimensions: null, processedControlImage: processedImage?.image_name ?? null, + processedControlImageDimensions: null, processorType, processorNode, shouldAutoConfig: true, @@ -350,9 +352,11 @@ const parseT2IAdapter: MetadataParseFunc = async (meta endStepPct: end_step_percent ?? initialT2IAdapter.endStepPct, resizeMode: resize_mode ?? initialT2IAdapter.resizeMode, controlImage: image?.image_name ?? null, + controlImageDimensions: null, processedControlImage: processedImage?.image_name ?? null, - processorType, + processedControlImageDimensions: null, processorNode, + processorType, shouldAutoConfig: true, id: uuidv4(), }; diff --git a/invokeai/frontend/web/src/features/nodes/util/graph/addControlNetToLinearGraph.ts b/invokeai/frontend/web/src/features/nodes/util/graph/addControlNetToLinearGraph.ts index e32f00fb86..bc58af7525 100644 --- a/invokeai/frontend/web/src/features/nodes/util/graph/addControlNetToLinearGraph.ts +++ b/invokeai/frontend/web/src/features/nodes/util/graph/addControlNetToLinearGraph.ts @@ -2,6 +2,9 @@ import type { RootState } from 'app/store/store'; import { selectValidControlNets } from 'features/controlAdapters/store/controlAdaptersSlice'; import type { ControlAdapterProcessorType, ControlNetConfig } from 'features/controlAdapters/store/types'; import type { ImageField } from 'features/nodes/types/common'; +import { isControlAdapterLayer } from 'features/regionalPrompts/store/regionalPromptsSlice'; +import { activeTabNameSelector } from 'features/ui/store/uiSelectors'; +import { differenceWith, intersectionWith } from 'lodash-es'; import type { CollectInvocation, ControlNetInvocation, @@ -14,11 +17,8 @@ import { assert } from 'tsafe'; import { CONTROL_NET_COLLECT } from './constants'; import { upsertMetadata } from './metadata'; -export const addControlNetToLinearGraph = async ( - state: RootState, - graph: NonNullableGraph, - baseNodeId: string -): Promise => { +const getControlNets = (state: RootState) => { + // Start with the valid controlnets const validControlNets = selectValidControlNets(state.controlAdapters).filter( ({ model, processedControlImage, processorType, controlImage, isEnabled }) => { const hasModel = Boolean(model); @@ -29,9 +29,33 @@ export const addControlNetToLinearGraph = async ( } ); + // txt2img tab has special handling - it uses layers exclusively, while the other tabs use the older control adapters + // accordion. We need to filter the list of valid T2I adapters according to the tab. + const activeTabName = activeTabNameSelector(state); + + // Collect all ControlNet ids for ControlNet layers + const layerControlNetIds = state.regionalPrompts.present.layers + .filter(isControlAdapterLayer) + .map((l) => l.controlNetId); + + if (activeTabName === 'txt2img') { + // Add only the cnets that are used in control layers + return intersectionWith(validControlNets, layerControlNetIds, (a, b) => a.id === b); + } else { + // Else, we want to exclude the cnets that are used in control layers + return differenceWith(validControlNets, layerControlNetIds, (a, b) => a.id === b); + } +}; + +export const addControlNetToLinearGraph = async ( + state: RootState, + graph: NonNullableGraph, + baseNodeId: string +): Promise => { + const controlNets = getControlNets(state); const controlNetMetadata: CoreMetadataInvocation['controlnets'] = []; - if (validControlNets.length) { + if (controlNets.length) { // Even though denoise_latents' control input is collection or scalar, keep it simple and always use a collect const controlNetIterateNode: CollectInvocation = { id: CONTROL_NET_COLLECT, @@ -47,7 +71,7 @@ export const addControlNetToLinearGraph = async ( }, }); - for (const controlNet of validControlNets) { + for (const controlNet of controlNets) { if (!controlNet.model) { return; } diff --git a/invokeai/frontend/web/src/features/nodes/util/graph/addIPAdapterToLinearGraph.ts b/invokeai/frontend/web/src/features/nodes/util/graph/addIPAdapterToLinearGraph.ts index 5f39fd93d8..746e2da81d 100644 --- a/invokeai/frontend/web/src/features/nodes/util/graph/addIPAdapterToLinearGraph.ts +++ b/invokeai/frontend/web/src/features/nodes/util/graph/addIPAdapterToLinearGraph.ts @@ -2,8 +2,9 @@ import type { RootState } from 'app/store/store'; import { selectValidIPAdapters } from 'features/controlAdapters/store/controlAdaptersSlice'; import type { IPAdapterConfig } from 'features/controlAdapters/store/types'; import type { ImageField } from 'features/nodes/types/common'; -import { isMaskedGuidanceLayer } from 'features/regionalPrompts/store/regionalPromptsSlice'; -import { differenceBy } from 'lodash-es'; +import { isIPAdapterLayer, isMaskedGuidanceLayer } from 'features/regionalPrompts/store/regionalPromptsSlice'; +import { activeTabNameSelector } from 'features/ui/store/uiSelectors'; +import { differenceWith, intersectionWith } from 'lodash-es'; import type { CollectInvocation, CoreMetadataInvocation, @@ -16,11 +17,8 @@ import { assert } from 'tsafe'; import { IP_ADAPTER_COLLECT } from './constants'; import { upsertMetadata } from './metadata'; -export const addIPAdapterToLinearGraph = async ( - state: RootState, - graph: NonNullableGraph, - baseNodeId: string -): Promise => { +const getIPAdapters = (state: RootState) => { + // Start with the valid IP adapters const validIPAdapters = selectValidIPAdapters(state.controlAdapters).filter(({ model, controlImage, isEnabled }) => { const hasModel = Boolean(model); const doesBaseMatch = model?.base === state.generation.model?.base; @@ -28,14 +26,37 @@ export const addIPAdapterToLinearGraph = async ( return isEnabled && hasModel && doesBaseMatch && hasControlImage; }); - const regionalIPAdapterIds = state.regionalPrompts.present.layers + // Masked IP adapters are handled in the graph helper for regional control - skip them here + const maskedIPAdapterIds = state.regionalPrompts.present.layers .filter(isMaskedGuidanceLayer) .map((l) => l.ipAdapterIds) .flat(); + const nonMaskedIPAdapters = differenceWith(validIPAdapters, maskedIPAdapterIds, (a, b) => a.id === b); - const nonRegionalIPAdapters = differenceBy(validIPAdapters, regionalIPAdapterIds, 'id'); + // txt2img tab has special handling - it uses layers exclusively, while the other tabs use the older control adapters + // accordion. We need to filter the list of valid IP adapters according to the tab. + const activeTabName = activeTabNameSelector(state); - if (nonRegionalIPAdapters.length) { + // Collect all IP Adapter ids for IP adapter layers + const layerIPAdapterIds = state.regionalPrompts.present.layers.filter(isIPAdapterLayer).map((l) => l.ipAdapterId); + + 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 + return intersectionWith(nonMaskedIPAdapters, layerIPAdapterIds, (a, b) => a.id === b); + } else { + // Else, we want to exclude the IP adapters that are used in IP Adapter layers + return differenceWith(nonMaskedIPAdapters, layerIPAdapterIds, (a, b) => a.id === b); + } +}; + +export const addIPAdapterToLinearGraph = async ( + state: RootState, + graph: NonNullableGraph, + baseNodeId: string +): Promise => { + const ipAdapters = getIPAdapters(state); + + if (ipAdapters.length) { // Even though denoise_latents' ip adapter input is collection or scalar, keep it simple and always use a collect const ipAdapterCollectNode: CollectInvocation = { id: IP_ADAPTER_COLLECT, @@ -53,7 +74,7 @@ export const addIPAdapterToLinearGraph = async ( const ipAdapterMetdata: CoreMetadataInvocation['ipAdapters'] = []; - for (const ipAdapter of nonRegionalIPAdapters) { + for (const ipAdapter of ipAdapters) { if (!ipAdapter.model) { return; } diff --git a/invokeai/frontend/web/src/features/nodes/util/graph/addRegionalPromptsToGraph.ts b/invokeai/frontend/web/src/features/nodes/util/graph/addRegionalPromptsToGraph.ts index d7451d3f1f..82725d8ace 100644 --- a/invokeai/frontend/web/src/features/nodes/util/graph/addRegionalPromptsToGraph.ts +++ b/invokeai/frontend/web/src/features/nodes/util/graph/addRegionalPromptsToGraph.ts @@ -31,7 +31,7 @@ export const addRegionalPromptsToGraph = async (state: RootState, graph: NonNull // TODO: Image masks .filter(isMaskedGuidanceLayer) // Only visible layers are rendered on the canvas - .filter((l) => l.isVisible) + .filter((l) => l.isEnabled) // Only layers with prompts get added to the graph .filter((l) => { const hasTextPrompt = Boolean(l.positivePrompt || l.negativePrompt); @@ -39,12 +39,15 @@ export const addRegionalPromptsToGraph = async (state: RootState, graph: NonNull return hasTextPrompt || hasIPAdapter; }); + // Collect all IP Adapter ids for IP adapter layers + const layerIPAdapterIds = layers.flatMap((l) => l.ipAdapterIds); + const regionalIPAdapters = selectAllIPAdapters(state.controlAdapters).filter( ({ id, model, controlImage, isEnabled }) => { const hasModel = Boolean(model); const doesBaseMatch = model?.base === state.generation.model?.base; const hasControlImage = controlImage; - const isRegional = layers.some((l) => l.ipAdapterIds.includes(id)); + const isRegional = layerIPAdapterIds.includes(id); return isEnabled && hasModel && doesBaseMatch && hasControlImage && isRegional; } ); diff --git a/invokeai/frontend/web/src/features/nodes/util/graph/addT2IAdapterToLinearGraph.ts b/invokeai/frontend/web/src/features/nodes/util/graph/addT2IAdapterToLinearGraph.ts index 42bd277201..0ac4affd1b 100644 --- a/invokeai/frontend/web/src/features/nodes/util/graph/addT2IAdapterToLinearGraph.ts +++ b/invokeai/frontend/web/src/features/nodes/util/graph/addT2IAdapterToLinearGraph.ts @@ -2,6 +2,9 @@ import type { RootState } from 'app/store/store'; import { selectValidT2IAdapters } from 'features/controlAdapters/store/controlAdaptersSlice'; import type { ControlAdapterProcessorType, T2IAdapterConfig } from 'features/controlAdapters/store/types'; import type { ImageField } from 'features/nodes/types/common'; +import { isControlAdapterLayer } from 'features/regionalPrompts/store/regionalPromptsSlice'; +import { activeTabNameSelector } from 'features/ui/store/uiSelectors'; +import { differenceWith, intersectionWith } from 'lodash-es'; import type { CollectInvocation, CoreMetadataInvocation, @@ -14,11 +17,8 @@ import { assert } from 'tsafe'; import { T2I_ADAPTER_COLLECT } from './constants'; import { upsertMetadata } from './metadata'; -export const addT2IAdaptersToLinearGraph = async ( - state: RootState, - graph: NonNullableGraph, - baseNodeId: string -): Promise => { +const getT2IAdapters = (state: RootState) => { + // Start with the valid controlnets const validT2IAdapters = selectValidT2IAdapters(state.controlAdapters).filter( ({ model, processedControlImage, processorType, controlImage, isEnabled }) => { const hasModel = Boolean(model); @@ -29,7 +29,32 @@ export const addT2IAdaptersToLinearGraph = async ( } ); - if (validT2IAdapters.length) { + // txt2img tab has special handling - it uses layers exclusively, while the other tabs use the older control adapters + // accordion. We need to filter the list of valid T2I adapters according to the tab. + const activeTabName = activeTabNameSelector(state); + + // Collect all ids for control adapter layers + const layerControlAdapterIds = state.regionalPrompts.present.layers + .filter(isControlAdapterLayer) + .map((l) => l.controlNetId); + + if (activeTabName === 'txt2img') { + // Add only the T2Is that are used in control layers + return intersectionWith(validT2IAdapters, layerControlAdapterIds, (a, b) => a.id === b); + } else { + // Else, we want to exclude the T2Is that are used in control layers + return differenceWith(validT2IAdapters, layerControlAdapterIds, (a, b) => a.id === b); + } +}; + +export const addT2IAdaptersToLinearGraph = async ( + state: RootState, + graph: NonNullableGraph, + baseNodeId: string +): Promise => { + const t2iAdapters = getT2IAdapters(state); + + if (t2iAdapters.length) { // Even though denoise_latents' t2i adapter input is collection or scalar, keep it simple and always use a collect const t2iAdapterCollectNode: CollectInvocation = { id: T2I_ADAPTER_COLLECT, @@ -47,7 +72,7 @@ export const addT2IAdaptersToLinearGraph = async ( const t2iAdapterMetadata: CoreMetadataInvocation['t2iAdapters'] = []; - for (const t2iAdapter of validT2IAdapters) { + for (const t2iAdapter of t2iAdapters) { if (!t2iAdapter.model) { return; } diff --git a/invokeai/frontend/web/src/features/regionalPrompts/components/AddLayerButton.tsx b/invokeai/frontend/web/src/features/regionalPrompts/components/AddLayerButton.tsx index a9b3480b87..d0d10ac987 100644 --- a/invokeai/frontend/web/src/features/regionalPrompts/components/AddLayerButton.tsx +++ b/invokeai/frontend/web/src/features/regionalPrompts/components/AddLayerButton.tsx @@ -1,6 +1,6 @@ -import { Button } from '@invoke-ai/ui-library'; +import { Button, Menu, MenuButton, MenuItem, MenuList } from '@invoke-ai/ui-library'; import { useAppDispatch } from 'app/store/storeHooks'; -import { layerAdded } from 'features/regionalPrompts/store/regionalPromptsSlice'; +import { guidanceLayerAdded } from 'app/store/middleware/listenerMiddleware/listeners/regionalControlToControlAdapterBridge'; import { memo, useCallback } from 'react'; import { useTranslation } from 'react-i18next'; import { PiPlusBold } from 'react-icons/pi'; @@ -8,14 +8,27 @@ import { PiPlusBold } from 'react-icons/pi'; export const AddLayerButton = memo(() => { const { t } = useTranslation(); const dispatch = useAppDispatch(); - const onClick = useCallback(() => { - dispatch(layerAdded('masked_guidance_layer')); + const addMaskedGuidanceLayer = useCallback(() => { + dispatch(guidanceLayerAdded('masked_guidance_layer')); + }, [dispatch]); + const addControlNetLayer = useCallback(() => { + dispatch(guidanceLayerAdded('control_adapter_layer')); + }, [dispatch]); + const addIPAdapterLayer = useCallback(() => { + dispatch(guidanceLayerAdded('ip_adapter_layer')); }, [dispatch]); return ( - + + } variant="ghost"> + {t('regionalPrompts.addLayer')} + + + {t('regionalPrompts.maskedGuidanceLayer')} + {t('regionalPrompts.controlNetLayer')} + {t('regionalPrompts.ipAdapterLayer')} + + ); }); diff --git a/invokeai/frontend/web/src/features/regionalPrompts/components/AddPromptButtons.tsx b/invokeai/frontend/web/src/features/regionalPrompts/components/AddPromptButtons.tsx index db2e35c234..ebb7ef217d 100644 --- a/invokeai/frontend/web/src/features/regionalPrompts/components/AddPromptButtons.tsx +++ b/invokeai/frontend/web/src/features/regionalPrompts/components/AddPromptButtons.tsx @@ -1,9 +1,9 @@ import { Button, Flex } from '@invoke-ai/ui-library'; import { createMemoizedSelector } from 'app/store/createMemoizedSelector'; +import { guidanceLayerIPAdapterAdded } from 'app/store/middleware/listenerMiddleware/listeners/regionalControlToControlAdapterBridge'; import { useAppDispatch, useAppSelector } from 'app/store/storeHooks'; import { isMaskedGuidanceLayer, - maskLayerIPAdapterAdded, maskLayerNegativePromptChanged, maskLayerPositivePromptChanged, selectRegionalPromptsSlice, @@ -39,7 +39,7 @@ export const AddPromptButtons = ({ layerId }: AddPromptButtonProps) => { dispatch(maskLayerNegativePromptChanged({ layerId, prompt: '' })); }, [dispatch, layerId]); const addIPAdapter = useCallback(() => { - dispatch(maskLayerIPAdapterAdded(layerId)); + dispatch(guidanceLayerIPAdapterAdded(layerId)); }, [dispatch, layerId]); return ( diff --git a/invokeai/frontend/web/src/features/regionalPrompts/components/BrushSize.tsx b/invokeai/frontend/web/src/features/regionalPrompts/components/BrushSize.tsx index e06e259f6e..ae042f848d 100644 --- a/invokeai/frontend/web/src/features/regionalPrompts/components/BrushSize.tsx +++ b/invokeai/frontend/web/src/features/regionalPrompts/components/BrushSize.tsx @@ -23,7 +23,7 @@ export const BrushSize = memo(() => { const brushSize = useAppSelector((s) => s.regionalPrompts.present.brushSize); const onChange = useCallback( (v: number) => { - dispatch(brushSizeChanged(v)); + dispatch(brushSizeChanged(Math.round(v))); }, [dispatch] ); diff --git a/invokeai/frontend/web/src/features/regionalPrompts/components/ControlAdapterLayerListItem.tsx b/invokeai/frontend/web/src/features/regionalPrompts/components/ControlAdapterLayerListItem.tsx new file mode 100644 index 0000000000..5f78843aab --- /dev/null +++ b/invokeai/frontend/web/src/features/regionalPrompts/components/ControlAdapterLayerListItem.tsx @@ -0,0 +1,62 @@ +import { Flex, Spacer } from '@invoke-ai/ui-library'; +import { createMemoizedSelector } from 'app/store/createMemoizedSelector'; +import { useAppDispatch, useAppSelector } from 'app/store/storeHooks'; +import ControlAdapterLayerConfig from 'features/regionalPrompts/components/controlAdapterOverrides/ControlAdapterLayerConfig'; +import { LayerTitle } from 'features/regionalPrompts/components/LayerTitle'; +import { RPLayerDeleteButton } from 'features/regionalPrompts/components/RPLayerDeleteButton'; +import { RPLayerVisibilityToggle } from 'features/regionalPrompts/components/RPLayerVisibilityToggle'; +import { + isControlAdapterLayer, + layerSelected, + selectRegionalPromptsSlice, +} from 'features/regionalPrompts/store/regionalPromptsSlice'; +import { memo, useCallback, useMemo } from 'react'; +import { assert } from 'tsafe'; + +type Props = { + layerId: string; +}; + +export const ControlAdapterLayerListItem = memo(({ layerId }: Props) => { + const dispatch = useAppDispatch(); + const selector = useMemo( + () => + createMemoizedSelector(selectRegionalPromptsSlice, (regionalPrompts) => { + const layer = regionalPrompts.present.layers.find((l) => l.id === layerId); + assert(isControlAdapterLayer(layer), `Layer ${layerId} not found or not a ControlNet layer`); + return { + controlNetId: layer.controlNetId, + isSelected: layerId === regionalPrompts.present.selectedLayerId, + }; + }), + [layerId] + ); + const { controlNetId, isSelected } = useAppSelector(selector); + const onClickCapture = useCallback(() => { + // Must be capture so that the layer is selected before deleting/resetting/etc + dispatch(layerSelected(layerId)); + }, [dispatch, layerId]); + return ( + + + + + + + + + + + + ); +}); + +ControlAdapterLayerListItem.displayName = 'ControlAdapterLayerListItem'; diff --git a/invokeai/frontend/web/src/features/regionalPrompts/components/DeleteAllLayersButton.tsx b/invokeai/frontend/web/src/features/regionalPrompts/components/DeleteAllLayersButton.tsx index 4306e3f3f3..83e464b844 100644 --- a/invokeai/frontend/web/src/features/regionalPrompts/components/DeleteAllLayersButton.tsx +++ b/invokeai/frontend/web/src/features/regionalPrompts/components/DeleteAllLayersButton.tsx @@ -1,6 +1,6 @@ import { Button } from '@invoke-ai/ui-library'; +import { allLayersDeleted } from 'app/store/middleware/listenerMiddleware/listeners/regionalControlToControlAdapterBridge'; import { useAppDispatch } from 'app/store/storeHooks'; -import { allLayersDeleted } from 'features/regionalPrompts/store/regionalPromptsSlice'; import { memo, useCallback } from 'react'; import { useTranslation } from 'react-i18next'; import { PiTrashSimpleBold } from 'react-icons/pi'; diff --git a/invokeai/frontend/web/src/features/regionalPrompts/components/IPAdapterLayerListItem.tsx b/invokeai/frontend/web/src/features/regionalPrompts/components/IPAdapterLayerListItem.tsx new file mode 100644 index 0000000000..bf4245f26b --- /dev/null +++ b/invokeai/frontend/web/src/features/regionalPrompts/components/IPAdapterLayerListItem.tsx @@ -0,0 +1,62 @@ +import { Flex, Spacer } from '@invoke-ai/ui-library'; +import { createMemoizedSelector } from 'app/store/createMemoizedSelector'; +import { useAppDispatch, useAppSelector } from 'app/store/storeHooks'; +import ControlAdapterLayerConfig from 'features/regionalPrompts/components/controlAdapterOverrides/ControlAdapterLayerConfig'; +import { LayerTitle } from 'features/regionalPrompts/components/LayerTitle'; +import { RPLayerDeleteButton } from 'features/regionalPrompts/components/RPLayerDeleteButton'; +import { RPLayerVisibilityToggle } from 'features/regionalPrompts/components/RPLayerVisibilityToggle'; +import { + isIPAdapterLayer, + layerSelected, + selectRegionalPromptsSlice, +} from 'features/regionalPrompts/store/regionalPromptsSlice'; +import { memo, useCallback, useMemo } from 'react'; +import { assert } from 'tsafe'; + +type Props = { + layerId: string; +}; + +export const IPAdapterLayerListItem = memo(({ layerId }: Props) => { + const dispatch = useAppDispatch(); + const selector = useMemo( + () => + createMemoizedSelector(selectRegionalPromptsSlice, (regionalPrompts) => { + const layer = regionalPrompts.present.layers.find((l) => l.id === layerId); + assert(isIPAdapterLayer(layer), `Layer ${layerId} not found or not an IP Adapter layer`); + return { + ipAdapterId: layer.ipAdapterId, + isSelected: layerId === regionalPrompts.present.selectedLayerId, + }; + }), + [layerId] + ); + const { ipAdapterId, isSelected } = useAppSelector(selector); + const onClickCapture = useCallback(() => { + // Must be capture so that the layer is selected before deleting/resetting/etc + dispatch(layerSelected(layerId)); + }, [dispatch, layerId]); + return ( + + + + + + + + + + + + ); +}); + +IPAdapterLayerListItem.displayName = 'IPAdapterLayerListItem'; diff --git a/invokeai/frontend/web/src/features/regionalPrompts/components/LayerTitle.tsx b/invokeai/frontend/web/src/features/regionalPrompts/components/LayerTitle.tsx new file mode 100644 index 0000000000..c5eafe92c2 --- /dev/null +++ b/invokeai/frontend/web/src/features/regionalPrompts/components/LayerTitle.tsx @@ -0,0 +1,29 @@ +import { Text } from '@invoke-ai/ui-library'; +import type { Layer } from 'features/regionalPrompts/store/types'; +import { memo, useMemo } from 'react'; +import { useTranslation } from 'react-i18next'; + +type Props = { + type: Layer['type']; +}; + +export const LayerTitle = memo(({ type }: Props) => { + const { t } = useTranslation(); + const title = useMemo(() => { + if (type === 'masked_guidance_layer') { + return t('regionalPrompts.maskedGuidance'); + } else if (type === 'control_adapter_layer') { + return t('common.controlNet'); + } else if (type === 'ip_adapter_layer') { + return t('common.ipAdapter'); + } + }, [t, type]); + + return ( + + {title} + + ); +}); + +LayerTitle.displayName = 'LayerTitle'; diff --git a/invokeai/frontend/web/src/features/regionalPrompts/components/RPLayerListItem.tsx b/invokeai/frontend/web/src/features/regionalPrompts/components/MaskedGuidanceLayerListItem.tsx similarity index 91% rename from invokeai/frontend/web/src/features/regionalPrompts/components/RPLayerListItem.tsx rename to invokeai/frontend/web/src/features/regionalPrompts/components/MaskedGuidanceLayerListItem.tsx index 989ee8c576..2b570a519a 100644 --- a/invokeai/frontend/web/src/features/regionalPrompts/components/RPLayerListItem.tsx +++ b/invokeai/frontend/web/src/features/regionalPrompts/components/MaskedGuidanceLayerListItem.tsx @@ -2,6 +2,7 @@ import { Badge, Flex, Spacer } from '@invoke-ai/ui-library'; import { createMemoizedSelector } from 'app/store/createMemoizedSelector'; import { useAppDispatch, useAppSelector } from 'app/store/storeHooks'; import { rgbColorToString } from 'features/canvas/util/colorToString'; +import { LayerTitle } from 'features/regionalPrompts/components/LayerTitle'; import { RPLayerColorPicker } from 'features/regionalPrompts/components/RPLayerColorPicker'; import { RPLayerDeleteButton } from 'features/regionalPrompts/components/RPLayerDeleteButton'; import { RPLayerIPAdapterList } from 'features/regionalPrompts/components/RPLayerIPAdapterList'; @@ -25,7 +26,7 @@ type Props = { layerId: string; }; -export const RPLayerListItem = memo(({ layerId }: Props) => { +export const MaskedGuidanceLayerListItem = memo(({ layerId }: Props) => { const { t } = useTranslation(); const dispatch = useAppDispatch(); const selector = useMemo( @@ -59,21 +60,21 @@ export const RPLayerListItem = memo(({ layerId }: Props) => { borderRadius="base" pe="1px" py="1px" - cursor="pointer" > - + - + {autoNegative === 'invert' && ( {t('regionalPrompts.autoNegative')} )} - + + {!hasPositivePrompt && !hasNegativePrompt && !hasIPAdapters && } {hasPositivePrompt && } @@ -84,4 +85,4 @@ export const RPLayerListItem = memo(({ layerId }: Props) => { ); }); -RPLayerListItem.displayName = 'RPLayerListItem'; +MaskedGuidanceLayerListItem.displayName = 'MaskedGuidanceLayerListItem'; diff --git a/invokeai/frontend/web/src/features/regionalPrompts/components/RPLayerAutoNegativeCheckbox.tsx b/invokeai/frontend/web/src/features/regionalPrompts/components/RPLayerAutoNegativeCheckbox.tsx index e4e0f86796..205b4b058e 100644 --- a/invokeai/frontend/web/src/features/regionalPrompts/components/RPLayerAutoNegativeCheckbox.tsx +++ b/invokeai/frontend/web/src/features/regionalPrompts/components/RPLayerAutoNegativeCheckbox.tsx @@ -29,7 +29,7 @@ const useAutoNegative = (layerId: string) => { return autoNegative; }; -export const RPLayerAutoNegativeCheckbox = memo(({ layerId }: Props) => { +export const MaskedGuidanceLayerAutoNegativeCheckbox = memo(({ layerId }: Props) => { const { t } = useTranslation(); const dispatch = useAppDispatch(); const autoNegative = useAutoNegative(layerId); @@ -48,4 +48,4 @@ export const RPLayerAutoNegativeCheckbox = memo(({ layerId }: Props) => { ); }); -RPLayerAutoNegativeCheckbox.displayName = 'RPLayerAutoNegativeCheckbox'; +MaskedGuidanceLayerAutoNegativeCheckbox.displayName = 'MaskedGuidanceLayerAutoNegativeCheckbox'; diff --git a/invokeai/frontend/web/src/features/regionalPrompts/components/RPLayerIPAdapterList.tsx b/invokeai/frontend/web/src/features/regionalPrompts/components/RPLayerIPAdapterList.tsx index ff2efaa0f9..5cab232baa 100644 --- a/invokeai/frontend/web/src/features/regionalPrompts/components/RPLayerIPAdapterList.tsx +++ b/invokeai/frontend/web/src/features/regionalPrompts/components/RPLayerIPAdapterList.tsx @@ -1,9 +1,11 @@ -import { Flex } from '@invoke-ai/ui-library'; +import { Divider, Flex, IconButton, Spacer, Text } from '@invoke-ai/ui-library'; import { createMemoizedSelector } from 'app/store/createMemoizedSelector'; -import { useAppSelector } from 'app/store/storeHooks'; -import ControlAdapterConfig from 'features/controlAdapters/components/ControlAdapterConfig'; +import { guidanceLayerIPAdapterDeleted } from 'app/store/middleware/listenerMiddleware/listeners/regionalControlToControlAdapterBridge'; +import { useAppDispatch, useAppSelector } from 'app/store/storeHooks'; +import ControlAdapterLayerConfig from 'features/regionalPrompts/components/controlAdapterOverrides/ControlAdapterLayerConfig'; import { isMaskedGuidanceLayer, selectRegionalPromptsSlice } from 'features/regionalPrompts/store/regionalPromptsSlice'; -import { memo, useMemo } from 'react'; +import { memo, useCallback, useMemo } from 'react'; +import { PiTrashSimpleBold } from 'react-icons/pi'; import { assert } from 'tsafe'; type Props = { @@ -22,13 +24,55 @@ export const RPLayerIPAdapterList = memo(({ layerId }: Props) => { ); const ipAdapterIds = useAppSelector(selectIPAdapterIds); + if (ipAdapterIds.length === 0) { + return null; + } + return ( - + <> {ipAdapterIds.map((id, index) => ( - + + + + + + ))} - + ); }); RPLayerIPAdapterList.displayName = 'RPLayerIPAdapterList'; + +type IPAdapterListItemProps = { + layerId: string; + ipAdapterId: string; + ipAdapterNumber: number; +}; + +const IPAdapterListItem = memo(({ layerId, ipAdapterId, ipAdapterNumber }: IPAdapterListItemProps) => { + const dispatch = useAppDispatch(); + const onDeleteIPAdapter = useCallback(() => { + dispatch(guidanceLayerIPAdapterDeleted({ layerId, ipAdapterId })); + }, [dispatch, ipAdapterId, layerId]); + + return ( + + + {`IP Adapter ${ipAdapterNumber}`} + + } + aria-label="Delete IP Adapter" + onClick={onDeleteIPAdapter} + variant="ghost" + colorScheme="error" + /> + + + + ); +}); + +IPAdapterListItem.displayName = 'IPAdapterListItem'; diff --git a/invokeai/frontend/web/src/features/regionalPrompts/components/RPLayerMenu.tsx b/invokeai/frontend/web/src/features/regionalPrompts/components/RPLayerMenu.tsx index 665b11e1d9..ebfa399227 100644 --- a/invokeai/frontend/web/src/features/regionalPrompts/components/RPLayerMenu.tsx +++ b/invokeai/frontend/web/src/features/regionalPrompts/components/RPLayerMenu.tsx @@ -1,5 +1,6 @@ import { IconButton, Menu, MenuButton, MenuDivider, MenuItem, MenuList } from '@invoke-ai/ui-library'; import { createMemoizedSelector } from 'app/store/createMemoizedSelector'; +import { guidanceLayerIPAdapterAdded } from 'app/store/middleware/listenerMiddleware/listeners/regionalControlToControlAdapterBridge'; import { useAppDispatch, useAppSelector } from 'app/store/storeHooks'; import { isMaskedGuidanceLayer, @@ -9,7 +10,6 @@ import { layerMovedToBack, layerMovedToFront, layerReset, - maskLayerIPAdapterAdded, maskLayerNegativePromptChanged, maskLayerPositivePromptChanged, selectRegionalPromptsSlice, @@ -59,7 +59,7 @@ export const RPLayerMenu = memo(({ layerId }: Props) => { dispatch(maskLayerNegativePromptChanged({ layerId, prompt: '' })); }, [dispatch, layerId]); const addIPAdapter = useCallback(() => { - dispatch(maskLayerIPAdapterAdded(layerId)); + dispatch(guidanceLayerIPAdapterAdded(layerId)); }, [dispatch, layerId]); const moveForward = useCallback(() => { dispatch(layerMovedForward(layerId)); diff --git a/invokeai/frontend/web/src/features/regionalPrompts/components/RPLayerSettingsPopover.tsx b/invokeai/frontend/web/src/features/regionalPrompts/components/RPLayerSettingsPopover.tsx index a9a450c3aa..10495f0900 100644 --- a/invokeai/frontend/web/src/features/regionalPrompts/components/RPLayerSettingsPopover.tsx +++ b/invokeai/frontend/web/src/features/regionalPrompts/components/RPLayerSettingsPopover.tsx @@ -9,7 +9,7 @@ import { PopoverContent, PopoverTrigger, } from '@invoke-ai/ui-library'; -import { RPLayerAutoNegativeCheckbox } from 'features/regionalPrompts/components/RPLayerAutoNegativeCheckbox'; +import { MaskedGuidanceLayerAutoNegativeCheckbox } from 'features/regionalPrompts/components/RPLayerAutoNegativeCheckbox'; import { memo } from 'react'; import { useTranslation } from 'react-i18next'; import { PiGearSixBold } from 'react-icons/pi'; @@ -41,7 +41,7 @@ const RPLayerSettingsPopover = ({ layerId }: Props) => { - + diff --git a/invokeai/frontend/web/src/features/regionalPrompts/components/RegionalPromptsPanelContent.tsx b/invokeai/frontend/web/src/features/regionalPrompts/components/RegionalPromptsPanelContent.tsx index 74bc552239..df422a5336 100644 --- a/invokeai/frontend/web/src/features/regionalPrompts/components/RegionalPromptsPanelContent.tsx +++ b/invokeai/frontend/web/src/features/regionalPrompts/components/RegionalPromptsPanelContent.tsx @@ -4,20 +4,43 @@ import { createMemoizedSelector } from 'app/store/createMemoizedSelector'; import { useAppSelector } from 'app/store/storeHooks'; import ScrollableContent from 'common/components/OverlayScrollbars/ScrollableContent'; import { AddLayerButton } from 'features/regionalPrompts/components/AddLayerButton'; +import { ControlAdapterLayerListItem } from 'features/regionalPrompts/components/ControlAdapterLayerListItem'; import { DeleteAllLayersButton } from 'features/regionalPrompts/components/DeleteAllLayersButton'; -import { RPLayerListItem } from 'features/regionalPrompts/components/RPLayerListItem'; -import { isMaskedGuidanceLayer, selectRegionalPromptsSlice } from 'features/regionalPrompts/store/regionalPromptsSlice'; +import { IPAdapterLayerListItem } from 'features/regionalPrompts/components/IPAdapterLayerListItem'; +import { MaskedGuidanceLayerListItem } from 'features/regionalPrompts/components/MaskedGuidanceLayerListItem'; +import { + isControlAdapterLayer, + isIPAdapterLayer, + isMaskedGuidanceLayer, + selectRegionalPromptsSlice, +} from 'features/regionalPrompts/store/regionalPromptsSlice'; import { memo } from 'react'; -const selectRPLayerIdsReversed = createMemoizedSelector(selectRegionalPromptsSlice, (regionalPrompts) => +const selectMaskedGuidanceLayerIds = createMemoizedSelector(selectRegionalPromptsSlice, (regionalPrompts) => regionalPrompts.present.layers .filter(isMaskedGuidanceLayer) .map((l) => l.id) .reverse() ); +const selectControlNetLayerIds = createMemoizedSelector(selectRegionalPromptsSlice, (regionalPrompts) => + regionalPrompts.present.layers + .filter(isControlAdapterLayer) + .map((l) => l.id) + .reverse() +); + +const selectIPAdapterLayerIds = createMemoizedSelector(selectRegionalPromptsSlice, (regionalPrompts) => + regionalPrompts.present.layers + .filter(isIPAdapterLayer) + .map((l) => l.id) + .reverse() +); + export const RegionalPromptsPanelContent = memo(() => { - const rpLayerIdsReversed = useAppSelector(selectRPLayerIdsReversed); + const maskedGuidanceLayerIds = useAppSelector(selectMaskedGuidanceLayerIds); + const controlNetLayerIds = useAppSelector(selectControlNetLayerIds); + const ipAdapterLayerIds = useAppSelector(selectIPAdapterLayerIds); return ( @@ -26,8 +49,14 @@ export const RegionalPromptsPanelContent = memo(() => { - {rpLayerIdsReversed.map((id) => ( - + {maskedGuidanceLayerIds.map((id) => ( + + ))} + {controlNetLayerIds.map((id) => ( + + ))} + {ipAdapterLayerIds.map((id) => ( + ))} diff --git a/invokeai/frontend/web/src/features/regionalPrompts/components/StageComponent.tsx b/invokeai/frontend/web/src/features/regionalPrompts/components/StageComponent.tsx index 40f02ace5b..e3018d3da9 100644 --- a/invokeai/frontend/web/src/features/regionalPrompts/components/StageComponent.tsx +++ b/invokeai/frontend/web/src/features/regionalPrompts/components/StageComponent.tsx @@ -18,9 +18,8 @@ import { import { debouncedRenderers, renderers as normalRenderers } from 'features/regionalPrompts/util/renderers'; import Konva from 'konva'; import type { IRect } from 'konva/lib/types'; -import type { MutableRefObject } from 'react'; -import { memo, useCallback, useLayoutEffect, useMemo, useRef, useState } from 'react'; -import { assert } from 'tsafe'; +import { memo, useCallback, useLayoutEffect, useMemo, useState } from 'react'; +import { v4 as uuidv4 } from 'uuid'; // This will log warnings when layers > 5 - maybe use `import.meta.env.MODE === 'development'` instead? Konva.showWarnings = false; @@ -28,16 +27,14 @@ Konva.showWarnings = false; const log = logger('regionalPrompts'); const selectSelectedLayerColor = createMemoizedSelector(selectRegionalPromptsSlice, (regionalPrompts) => { - const layer = regionalPrompts.present.layers.find((l) => l.id === regionalPrompts.present.selectedLayerId); - if (!layer) { - return null; - } - assert(isMaskedGuidanceLayer(layer), `Layer ${regionalPrompts.present.selectedLayerId} is not an RP layer`); - return layer.previewColor; + const layer = regionalPrompts.present.layers + .filter(isMaskedGuidanceLayer) + .find((l) => l.id === regionalPrompts.present.selectedLayerId); + return layer?.previewColor ?? null; }); const useStageRenderer = ( - stageRef: MutableRefObject, + stage: Konva.Stage, container: HTMLDivElement | null, wrapper: HTMLDivElement | null, asPreview: boolean @@ -79,25 +76,24 @@ const useStageRenderer = ( if (!container) { return; } - const stage = stageRef.current.container(container); + stage.container(container); return () => { log.trace('Cleaning up stage'); stage.destroy(); }; - }, [container, stageRef]); + }, [container, stage]); useLayoutEffect(() => { log.trace('Adding stage listeners'); if (asPreview) { return; } - stageRef.current.on('mousedown', onMouseDown); - stageRef.current.on('mouseup', onMouseUp); - stageRef.current.on('mousemove', onMouseMove); - stageRef.current.on('mouseenter', onMouseEnter); - stageRef.current.on('mouseleave', onMouseLeave); - stageRef.current.on('wheel', onMouseWheel); - const stage = stageRef.current; + stage.on('mousedown', onMouseDown); + stage.on('mouseup', onMouseUp); + stage.on('mousemove', onMouseMove); + stage.on('mouseenter', onMouseEnter); + stage.on('mouseleave', onMouseLeave); + stage.on('wheel', onMouseWheel); return () => { log.trace('Cleaning up stage listeners'); @@ -108,7 +104,7 @@ const useStageRenderer = ( stage.off('mouseleave', onMouseLeave); stage.off('wheel', onMouseWheel); }; - }, [stageRef, asPreview, onMouseDown, onMouseUp, onMouseMove, onMouseEnter, onMouseLeave, onMouseWheel]); + }, [stage, asPreview, onMouseDown, onMouseUp, onMouseMove, onMouseEnter, onMouseLeave, onMouseWheel]); useLayoutEffect(() => { log.trace('Updating stage dimensions'); @@ -116,8 +112,6 @@ const useStageRenderer = ( return; } - const stage = stageRef.current; - const fitStageToContainer = () => { const newXScale = wrapper.offsetWidth / state.size.width; const newYScale = wrapper.offsetHeight / state.size.height; @@ -135,7 +129,7 @@ const useStageRenderer = ( return () => { resizeObserver.disconnect(); }; - }, [stageRef, state.size.width, state.size.height, wrapper]); + }, [stage, state.size.width, state.size.height, wrapper]); useLayoutEffect(() => { log.trace('Rendering tool preview'); @@ -144,7 +138,7 @@ const useStageRenderer = ( return; } renderers.renderToolPreview( - stageRef.current, + stage, tool, selectedLayerIdColor, state.globalMaskLayerOpacity, @@ -155,7 +149,7 @@ const useStageRenderer = ( ); }, [ asPreview, - stageRef, + stage, tool, selectedLayerIdColor, state.globalMaskLayerOpacity, @@ -168,8 +162,17 @@ const useStageRenderer = ( useLayoutEffect(() => { log.trace('Rendering layers'); - renderers.renderLayers(stageRef.current, state.layers, state.globalMaskLayerOpacity, tool, onLayerPosChanged); - }, [stageRef, state.layers, state.globalMaskLayerOpacity, tool, onLayerPosChanged, renderers]); + renderers.renderLayers(stage, state.layers, state.globalMaskLayerOpacity, tool, onLayerPosChanged); + }, [ + stage, + state.layers, + state.globalMaskLayerOpacity, + tool, + onLayerPosChanged, + renderers, + state.size.width, + state.size.height, + ]); useLayoutEffect(() => { log.trace('Rendering bbox'); @@ -177,8 +180,8 @@ const useStageRenderer = ( // Preview should not display bboxes return; } - renderers.renderBbox(stageRef.current, state.layers, state.selectedLayerId, tool, onBboxChanged, onBboxMouseDown); - }, [stageRef, asPreview, state.layers, state.selectedLayerId, tool, onBboxChanged, onBboxMouseDown, renderers]); + renderers.renderBbox(stage, state.layers, state.selectedLayerId, tool, onBboxChanged, onBboxMouseDown); + }, [stage, asPreview, state.layers, state.selectedLayerId, tool, onBboxChanged, onBboxMouseDown, renderers]); useLayoutEffect(() => { log.trace('Rendering background'); @@ -186,13 +189,13 @@ const useStageRenderer = ( // The preview should not have a background return; } - renderers.renderBackground(stageRef.current, state.size.width, state.size.height); - }, [stageRef, asPreview, state.size.width, state.size.height, renderers]); + renderers.renderBackground(stage, state.size.width, state.size.height); + }, [stage, asPreview, state.size.width, state.size.height, renderers]); useLayoutEffect(() => { log.trace('Arranging layers'); - renderers.arrangeLayers(stageRef.current, layerIds); - }, [stageRef, layerIds, renderers]); + renderers.arrangeLayers(stage, layerIds); + }, [stage, layerIds, renderers]); }; type Props = { @@ -200,10 +203,8 @@ type Props = { }; export const StageComponent = memo(({ asPreview = false }: Props) => { - const stageRef = useRef( - new Konva.Stage({ - container: document.createElement('div'), // We will overwrite this shortly... - }) + const [stage] = useState( + () => new Konva.Stage({ id: uuidv4(), container: document.createElement('div'), listening: !asPreview }) ); const [container, setContainer] = useState(null); const [wrapper, setWrapper] = useState(null); @@ -216,7 +217,7 @@ export const StageComponent = memo(({ asPreview = false }: Props) => { setWrapper(el); }, []); - useStageRenderer(stageRef, container, wrapper, asPreview); + useStageRenderer(stage, container, wrapper, asPreview); return ( diff --git a/invokeai/frontend/web/src/features/regionalPrompts/components/ToolChooser.tsx b/invokeai/frontend/web/src/features/regionalPrompts/components/ToolChooser.tsx index 6ba73ccb7b..80ed271289 100644 --- a/invokeai/frontend/web/src/features/regionalPrompts/components/ToolChooser.tsx +++ b/invokeai/frontend/web/src/features/regionalPrompts/components/ToolChooser.tsx @@ -1,12 +1,7 @@ import { ButtonGroup, IconButton } from '@invoke-ai/ui-library'; import { useStore } from '@nanostores/react'; import { useAppDispatch, useAppSelector } from 'app/store/storeHooks'; -import { - $tool, - layerAdded, - selectedLayerDeleted, - selectedLayerReset, -} from 'features/regionalPrompts/store/regionalPromptsSlice'; +import { $tool, selectedLayerDeleted, selectedLayerReset } from 'features/regionalPrompts/store/regionalPromptsSlice'; import { useCallback } from 'react'; import { useHotkeys } from 'react-hotkeys-hook'; import { useTranslation } from 'react-i18next'; @@ -40,11 +35,6 @@ export const ToolChooser: React.FC = () => { }, [dispatch]); useHotkeys('shift+c', resetSelectedLayer); - const addLayer = useCallback(() => { - dispatch(layerAdded('masked_guidance_layer')); - }, [dispatch]); - useHotkeys('shift+a', addLayer); - const deleteSelectedLayer = useCallback(() => { dispatch(selectedLayerDeleted()); }, [dispatch]); diff --git a/invokeai/frontend/web/src/features/regionalPrompts/components/controlAdapterOverrides/ControlAdapterImagePreview.tsx b/invokeai/frontend/web/src/features/regionalPrompts/components/controlAdapterOverrides/ControlAdapterImagePreview.tsx new file mode 100644 index 0000000000..3c6c32838a --- /dev/null +++ b/invokeai/frontend/web/src/features/regionalPrompts/components/controlAdapterOverrides/ControlAdapterImagePreview.tsx @@ -0,0 +1,230 @@ +import type { SystemStyleObject } from '@invoke-ai/ui-library'; +import { Box, Flex, Spinner } from '@invoke-ai/ui-library'; +import { skipToken } from '@reduxjs/toolkit/query'; +import { createMemoizedSelector } from 'app/store/createMemoizedSelector'; +import { useAppDispatch, useAppSelector } from 'app/store/storeHooks'; +import IAIDndImage from 'common/components/IAIDndImage'; +import IAIDndImageIcon from 'common/components/IAIDndImageIcon'; +import { setBoundingBoxDimensions } from 'features/canvas/store/canvasSlice'; +import { useControlAdapterControlImage } from 'features/controlAdapters/hooks/useControlAdapterControlImage'; +import { useControlAdapterProcessedControlImage } from 'features/controlAdapters/hooks/useControlAdapterProcessedControlImage'; +import { useControlAdapterProcessorType } from 'features/controlAdapters/hooks/useControlAdapterProcessorType'; +import { + controlAdapterImageChanged, + selectControlAdaptersSlice, +} from 'features/controlAdapters/store/controlAdaptersSlice'; +import type { TypesafeDraggableData, TypesafeDroppableData } from 'features/dnd/types'; +import { calculateNewSize } from 'features/parameters/components/ImageSize/calculateNewSize'; +import { selectOptimalDimension } from 'features/parameters/store/generationSlice'; +import { heightChanged, widthChanged } from 'features/regionalPrompts/store/regionalPromptsSlice'; +import { activeTabNameSelector } from 'features/ui/store/uiSelectors'; +import { memo, useCallback, useEffect, useMemo, useState } from 'react'; +import { useTranslation } from 'react-i18next'; +import { PiArrowCounterClockwiseBold, PiFloppyDiskBold, PiRulerBold } from 'react-icons/pi'; +import { + useAddImageToBoardMutation, + useChangeImageIsIntermediateMutation, + useGetImageDTOQuery, + useRemoveImageFromBoardMutation, +} from 'services/api/endpoints/images'; +import type { PostUploadAction } from 'services/api/types'; + +type Props = { + id: string; + isSmall?: boolean; +}; + +const selectPendingControlImages = createMemoizedSelector( + selectControlAdaptersSlice, + (controlAdapters) => controlAdapters.pendingControlImages +); + +const ControlAdapterImagePreview = ({ isSmall, id }: Props) => { + const { t } = useTranslation(); + const dispatch = useAppDispatch(); + const controlImageName = useControlAdapterControlImage(id); + const processedControlImageName = useControlAdapterProcessedControlImage(id); + const processorType = useControlAdapterProcessorType(id); + const autoAddBoardId = useAppSelector((s) => s.gallery.autoAddBoardId); + const isConnected = useAppSelector((s) => s.system.isConnected); + const activeTabName = useAppSelector(activeTabNameSelector); + const optimalDimension = useAppSelector(selectOptimalDimension); + const pendingControlImages = useAppSelector(selectPendingControlImages); + + const [isMouseOverImage, setIsMouseOverImage] = useState(false); + + const { currentData: controlImage, isError: isErrorControlImage } = useGetImageDTOQuery( + controlImageName ?? skipToken + ); + + const { currentData: processedControlImage, isError: isErrorProcessedControlImage } = useGetImageDTOQuery( + processedControlImageName ?? skipToken + ); + + const [changeIsIntermediate] = useChangeImageIsIntermediateMutation(); + const [addToBoard] = useAddImageToBoardMutation(); + const [removeFromBoard] = useRemoveImageFromBoardMutation(); + const handleResetControlImage = useCallback(() => { + dispatch(controlAdapterImageChanged({ id, controlImage: null })); + }, [id, dispatch]); + + const handleSaveControlImage = useCallback(async () => { + if (!processedControlImage) { + return; + } + + await changeIsIntermediate({ + imageDTO: processedControlImage, + is_intermediate: false, + }).unwrap(); + + if (autoAddBoardId !== 'none') { + addToBoard({ + imageDTO: processedControlImage, + board_id: autoAddBoardId, + }); + } else { + removeFromBoard({ imageDTO: processedControlImage }); + } + }, [processedControlImage, changeIsIntermediate, autoAddBoardId, addToBoard, removeFromBoard]); + + const handleSetControlImageToDimensions = useCallback(() => { + if (!controlImage) { + return; + } + + if (activeTabName === 'unifiedCanvas') { + dispatch(setBoundingBoxDimensions({ width: controlImage.width, height: controlImage.height }, optimalDimension)); + } else { + const { width, height } = calculateNewSize( + controlImage.width / controlImage.height, + optimalDimension * optimalDimension + ); + dispatch(widthChanged({ width: controlImage.width, updateAspectRatio: true })); + dispatch(heightChanged({ height: controlImage.height, updateAspectRatio: true })); + } + }, [controlImage, activeTabName, dispatch, optimalDimension]); + + const handleMouseEnter = useCallback(() => { + setIsMouseOverImage(true); + }, []); + + const handleMouseLeave = useCallback(() => { + setIsMouseOverImage(false); + }, []); + + const draggableData = useMemo(() => { + if (controlImage) { + return { + id, + payloadType: 'IMAGE_DTO', + payload: { imageDTO: controlImage }, + }; + } + }, [controlImage, id]); + + const droppableData = useMemo( + () => ({ + id, + actionType: 'SET_CONTROL_ADAPTER_IMAGE', + context: { id }, + }), + [id] + ); + + const postUploadAction = useMemo(() => ({ type: 'SET_CONTROL_ADAPTER_IMAGE', id }), [id]); + + const shouldShowProcessedImage = + controlImage && + processedControlImage && + !isMouseOverImage && + !pendingControlImages.includes(id) && + processorType !== 'none'; + + useEffect(() => { + if (isConnected && (isErrorControlImage || isErrorProcessedControlImage)) { + handleResetControlImage(); + } + }, [handleResetControlImage, isConnected, isErrorControlImage, isErrorProcessedControlImage]); + + return ( + + + + + + + + <> + : undefined} + tooltip={t('controlnet.resetControlImage')} + /> + : undefined} + tooltip={t('controlnet.saveControlImage')} + styleOverrides={saveControlImageStyleOverrides} + /> + : undefined} + tooltip={t('controlnet.setControlImageDimensions')} + styleOverrides={setControlImageDimensionsStyleOverrides} + /> + + + {pendingControlImages.includes(id) && ( + + + + )} + + ); +}; + +export default memo(ControlAdapterImagePreview); + +const saveControlImageStyleOverrides: SystemStyleObject = { mt: 6 }; +const setControlImageDimensionsStyleOverrides: SystemStyleObject = { mt: 12 }; diff --git a/invokeai/frontend/web/src/features/regionalPrompts/components/controlAdapterOverrides/ControlAdapterLayerConfig.tsx b/invokeai/frontend/web/src/features/regionalPrompts/components/controlAdapterOverrides/ControlAdapterLayerConfig.tsx new file mode 100644 index 0000000000..8afd035c3d --- /dev/null +++ b/invokeai/frontend/web/src/features/regionalPrompts/components/controlAdapterOverrides/ControlAdapterLayerConfig.tsx @@ -0,0 +1,70 @@ +import { Box, Flex, Icon, IconButton } from '@invoke-ai/ui-library'; +import ControlAdapterProcessorComponent from 'features/controlAdapters/components/ControlAdapterProcessorComponent'; +import ControlAdapterShouldAutoConfig from 'features/controlAdapters/components/ControlAdapterShouldAutoConfig'; +import ParamControlAdapterIPMethod from 'features/controlAdapters/components/parameters/ParamControlAdapterIPMethod'; +import ParamControlAdapterProcessorSelect from 'features/controlAdapters/components/parameters/ParamControlAdapterProcessorSelect'; +import { useControlAdapterType } from 'features/controlAdapters/hooks/useControlAdapterType'; +import { memo } from 'react'; +import { useTranslation } from 'react-i18next'; +import { PiCaretUpBold } from 'react-icons/pi'; +import { useToggle } from 'react-use'; + +import ControlAdapterImagePreview from './ControlAdapterImagePreview'; +import { ParamControlAdapterBeginEnd } from './ParamControlAdapterBeginEnd'; +import ParamControlAdapterControlMode from './ParamControlAdapterControlMode'; +import ParamControlAdapterModel from './ParamControlAdapterModel'; +import ParamControlAdapterWeight from './ParamControlAdapterWeight'; + +const ControlAdapterLayerConfig = (props: { id: string }) => { + const { id } = props; + const controlAdapterType = useControlAdapterType(id); + const { t } = useTranslation(); + const [isExpanded, toggleIsExpanded] = useToggle(false); + + return ( + + + + {' '} + + + + } + /> + + + + {controlAdapterType === 'ip_adapter' && } + {controlAdapterType === 'controlnet' && } + + + + + + + + {isExpanded && ( + <> + + + + + )} + + ); +}; + +export default memo(ControlAdapterLayerConfig); diff --git a/invokeai/frontend/web/src/features/regionalPrompts/components/controlAdapterOverrides/ParamControlAdapterBeginEnd.tsx b/invokeai/frontend/web/src/features/regionalPrompts/components/controlAdapterOverrides/ParamControlAdapterBeginEnd.tsx new file mode 100644 index 0000000000..e4bc07e0b4 --- /dev/null +++ b/invokeai/frontend/web/src/features/regionalPrompts/components/controlAdapterOverrides/ParamControlAdapterBeginEnd.tsx @@ -0,0 +1,89 @@ +import { CompositeRangeSlider, FormControl, FormLabel } from '@invoke-ai/ui-library'; +import { useAppDispatch } from 'app/store/storeHooks'; +import { InformationalPopover } from 'common/components/InformationalPopover/InformationalPopover'; +import { useControlAdapterBeginEndStepPct } from 'features/controlAdapters/hooks/useControlAdapterBeginEndStepPct'; +import { useControlAdapterIsEnabled } from 'features/controlAdapters/hooks/useControlAdapterIsEnabled'; +import { + controlAdapterBeginStepPctChanged, + controlAdapterEndStepPctChanged, +} from 'features/controlAdapters/store/controlAdaptersSlice'; +import { memo, useCallback, useMemo } from 'react'; +import { useTranslation } from 'react-i18next'; + +type Props = { + id: string; +}; + +const formatPct = (v: number) => `${Math.round(v * 100)}%`; + +export const ParamControlAdapterBeginEnd = memo(({ id }: Props) => { + const isEnabled = useControlAdapterIsEnabled(id); + const stepPcts = useControlAdapterBeginEndStepPct(id); + const dispatch = useAppDispatch(); + const { t } = useTranslation(); + + const onChange = useCallback( + (v: [number, number]) => { + dispatch( + controlAdapterBeginStepPctChanged({ + id, + beginStepPct: v[0], + }) + ); + dispatch( + controlAdapterEndStepPctChanged({ + id, + endStepPct: v[1], + }) + ); + }, + [dispatch, id] + ); + + const onReset = useCallback(() => { + dispatch( + controlAdapterBeginStepPctChanged({ + id, + beginStepPct: 0, + }) + ); + dispatch( + controlAdapterEndStepPctChanged({ + id, + endStepPct: 1, + }) + ); + }, [dispatch, id]); + + const value = useMemo<[number, number]>(() => [stepPcts?.beginStepPct ?? 0, stepPcts?.endStepPct ?? 1], [stepPcts]); + + if (!stepPcts) { + return null; + } + + return ( + + + {t('controlnet.beginEndStepPercentShort')} + + + + ); +}); + +ParamControlAdapterBeginEnd.displayName = 'ParamControlAdapterBeginEnd'; + +const ariaLabel = ['Begin Step %', 'End Step %']; diff --git a/invokeai/frontend/web/src/features/regionalPrompts/components/controlAdapterOverrides/ParamControlAdapterControlMode.tsx b/invokeai/frontend/web/src/features/regionalPrompts/components/controlAdapterOverrides/ParamControlAdapterControlMode.tsx new file mode 100644 index 0000000000..6b5d34c106 --- /dev/null +++ b/invokeai/frontend/web/src/features/regionalPrompts/components/controlAdapterOverrides/ParamControlAdapterControlMode.tsx @@ -0,0 +1,66 @@ +import type { ComboboxOnChange } from '@invoke-ai/ui-library'; +import { Combobox, FormControl, FormLabel } from '@invoke-ai/ui-library'; +import { useAppDispatch } from 'app/store/storeHooks'; +import { InformationalPopover } from 'common/components/InformationalPopover/InformationalPopover'; +import { useControlAdapterControlMode } from 'features/controlAdapters/hooks/useControlAdapterControlMode'; +import { useControlAdapterIsEnabled } from 'features/controlAdapters/hooks/useControlAdapterIsEnabled'; +import { controlAdapterControlModeChanged } from 'features/controlAdapters/store/controlAdaptersSlice'; +import type { ControlMode } from 'features/controlAdapters/store/types'; +import { memo, useCallback, useMemo } from 'react'; +import { useTranslation } from 'react-i18next'; + +type Props = { + id: string; +}; + +const ParamControlAdapterControlMode = ({ id }: Props) => { + const isEnabled = useControlAdapterIsEnabled(id); + const controlMode = useControlAdapterControlMode(id); + const dispatch = useAppDispatch(); + const { t } = useTranslation(); + + const CONTROL_MODE_DATA = useMemo( + () => [ + { label: t('controlnet.balanced'), value: 'balanced' }, + { label: t('controlnet.prompt'), value: 'more_prompt' }, + { label: t('controlnet.control'), value: 'more_control' }, + { label: t('controlnet.megaControl'), value: 'unbalanced' }, + ], + [t] + ); + + const handleControlModeChange = useCallback( + (v) => { + if (!v) { + return; + } + dispatch( + controlAdapterControlModeChanged({ + id, + controlMode: v.value as ControlMode, + }) + ); + }, + [id, dispatch] + ); + + const value = useMemo( + () => CONTROL_MODE_DATA.filter((o) => o.value === controlMode)[0], + [CONTROL_MODE_DATA, controlMode] + ); + + if (!controlMode) { + return null; + } + + return ( + + + {t('controlnet.control')} + + + + ); +}; + +export default memo(ParamControlAdapterControlMode); diff --git a/invokeai/frontend/web/src/features/regionalPrompts/components/controlAdapterOverrides/ParamControlAdapterModel.tsx b/invokeai/frontend/web/src/features/regionalPrompts/components/controlAdapterOverrides/ParamControlAdapterModel.tsx new file mode 100644 index 0000000000..73a7d695b3 --- /dev/null +++ b/invokeai/frontend/web/src/features/regionalPrompts/components/controlAdapterOverrides/ParamControlAdapterModel.tsx @@ -0,0 +1,136 @@ +import type { ComboboxOnChange, ComboboxOption } from '@invoke-ai/ui-library'; +import { Combobox, Flex, FormControl, Tooltip } from '@invoke-ai/ui-library'; +import { createMemoizedSelector } from 'app/store/createMemoizedSelector'; +import { useAppDispatch, useAppSelector } from 'app/store/storeHooks'; +import { useGroupedModelCombobox } from 'common/hooks/useGroupedModelCombobox'; +import { useControlAdapterCLIPVisionModel } from 'features/controlAdapters/hooks/useControlAdapterCLIPVisionModel'; +import { useControlAdapterIsEnabled } from 'features/controlAdapters/hooks/useControlAdapterIsEnabled'; +import { useControlAdapterModel } from 'features/controlAdapters/hooks/useControlAdapterModel'; +import { useControlAdapterModels } from 'features/controlAdapters/hooks/useControlAdapterModels'; +import { useControlAdapterType } from 'features/controlAdapters/hooks/useControlAdapterType'; +import { + controlAdapterCLIPVisionModelChanged, + controlAdapterModelChanged, +} from 'features/controlAdapters/store/controlAdaptersSlice'; +import type { CLIPVisionModel } from 'features/controlAdapters/store/types'; +import { selectGenerationSlice } from 'features/parameters/store/generationSlice'; +import { memo, useCallback, useMemo } from 'react'; +import { useTranslation } from 'react-i18next'; +import type { + AnyModelConfig, + ControlNetModelConfig, + IPAdapterModelConfig, + T2IAdapterModelConfig, +} from 'services/api/types'; + +type ParamControlAdapterModelProps = { + id: string; +}; + +const selectMainModel = createMemoizedSelector(selectGenerationSlice, (generation) => generation.model); + +const ParamControlAdapterModel = ({ id }: ParamControlAdapterModelProps) => { + const isEnabled = useControlAdapterIsEnabled(id); + const controlAdapterType = useControlAdapterType(id); + const { modelConfig } = useControlAdapterModel(id); + const dispatch = useAppDispatch(); + const currentBaseModel = useAppSelector((s) => s.generation.model?.base); + const currentCLIPVisionModel = useControlAdapterCLIPVisionModel(id); + const mainModel = useAppSelector(selectMainModel); + const { t } = useTranslation(); + + const [modelConfigs, { isLoading }] = useControlAdapterModels(controlAdapterType); + + const _onChange = useCallback( + (modelConfig: ControlNetModelConfig | IPAdapterModelConfig | T2IAdapterModelConfig | null) => { + if (!modelConfig) { + return; + } + dispatch( + controlAdapterModelChanged({ + id, + modelConfig, + }) + ); + }, + [dispatch, id] + ); + + const onCLIPVisionModelChange = useCallback( + (v) => { + if (!v?.value) { + return; + } + dispatch(controlAdapterCLIPVisionModelChanged({ id, clipVisionModel: v.value as CLIPVisionModel })); + }, + [dispatch, id] + ); + + const selectedModel = useMemo( + () => (modelConfig && controlAdapterType ? { ...modelConfig, model_type: controlAdapterType } : null), + [controlAdapterType, modelConfig] + ); + + const getIsDisabled = useCallback( + (model: AnyModelConfig): boolean => { + const isCompatible = currentBaseModel === model.base; + const hasMainModel = Boolean(currentBaseModel); + return !hasMainModel || !isCompatible; + }, + [currentBaseModel] + ); + + const { options, value, onChange, noOptionsMessage } = useGroupedModelCombobox({ + modelConfigs, + onChange: _onChange, + selectedModel, + getIsDisabled, + isLoading, + }); + + const clipVisionOptions = useMemo( + () => [ + { label: 'ViT-H', value: 'ViT-H' }, + { label: 'ViT-G', value: 'ViT-G' }, + ], + [] + ); + + const clipVisionModel = useMemo( + () => clipVisionOptions.find((o) => o.value === currentCLIPVisionModel), + [clipVisionOptions, currentCLIPVisionModel] + ); + + return ( + + + + + + + {modelConfig?.type === 'ip_adapter' && modelConfig.format === 'checkpoint' && ( + + + + )} + + ); +}; + +export default memo(ParamControlAdapterModel); diff --git a/invokeai/frontend/web/src/features/regionalPrompts/components/controlAdapterOverrides/ParamControlAdapterWeight.tsx b/invokeai/frontend/web/src/features/regionalPrompts/components/controlAdapterOverrides/ParamControlAdapterWeight.tsx new file mode 100644 index 0000000000..5e456fc792 --- /dev/null +++ b/invokeai/frontend/web/src/features/regionalPrompts/components/controlAdapterOverrides/ParamControlAdapterWeight.tsx @@ -0,0 +1,74 @@ +import { CompositeNumberInput, CompositeSlider, FormControl, FormLabel } from '@invoke-ai/ui-library'; +import { useAppDispatch, useAppSelector } from 'app/store/storeHooks'; +import { InformationalPopover } from 'common/components/InformationalPopover/InformationalPopover'; +import { useControlAdapterIsEnabled } from 'features/controlAdapters/hooks/useControlAdapterIsEnabled'; +import { useControlAdapterWeight } from 'features/controlAdapters/hooks/useControlAdapterWeight'; +import { controlAdapterWeightChanged } from 'features/controlAdapters/store/controlAdaptersSlice'; +import { isNil } from 'lodash-es'; +import { memo, useCallback } from 'react'; +import { useTranslation } from 'react-i18next'; + +type ParamControlAdapterWeightProps = { + id: string; +}; + +const formatValue = (v: number) => v.toFixed(2); + +const ParamControlAdapterWeight = ({ id }: ParamControlAdapterWeightProps) => { + const { t } = useTranslation(); + const dispatch = useAppDispatch(); + const isEnabled = useControlAdapterIsEnabled(id); + const weight = useControlAdapterWeight(id); + const initial = useAppSelector((s) => s.config.sd.ca.weight.initial); + const sliderMin = useAppSelector((s) => s.config.sd.ca.weight.sliderMin); + const sliderMax = useAppSelector((s) => s.config.sd.ca.weight.sliderMax); + const numberInputMin = useAppSelector((s) => s.config.sd.ca.weight.numberInputMin); + const numberInputMax = useAppSelector((s) => s.config.sd.ca.weight.numberInputMax); + const coarseStep = useAppSelector((s) => s.config.sd.ca.weight.coarseStep); + const fineStep = useAppSelector((s) => s.config.sd.ca.weight.fineStep); + + const onChange = useCallback( + (weight: number) => { + dispatch(controlAdapterWeightChanged({ id, weight })); + }, + [dispatch, id] + ); + + if (isNil(weight)) { + // should never happen + return null; + } + + return ( + + + {t('controlnet.weight')} + + + + + ); +}; + +export default memo(ParamControlAdapterWeight); + +const marks = [0, 1, 2]; diff --git a/invokeai/frontend/web/src/features/regionalPrompts/hooks/layerStateHooks.ts b/invokeai/frontend/web/src/features/regionalPrompts/hooks/layerStateHooks.ts index a2ee3bd320..5ef1014dcc 100644 --- a/invokeai/frontend/web/src/features/regionalPrompts/hooks/layerStateHooks.ts +++ b/invokeai/frontend/web/src/features/regionalPrompts/hooks/layerStateHooks.ts @@ -1,6 +1,9 @@ import { createSelector } from '@reduxjs/toolkit'; import { useAppSelector } from 'app/store/storeHooks'; -import { isMaskedGuidanceLayer, selectRegionalPromptsSlice } from 'features/regionalPrompts/store/regionalPromptsSlice'; +import { + isMaskedGuidanceLayer, + selectRegionalPromptsSlice, +} from 'features/regionalPrompts/store/regionalPromptsSlice'; import { useMemo } from 'react'; import { assert } from 'tsafe'; @@ -39,8 +42,8 @@ export const useLayerIsVisible = (layerId: string) => { () => createSelector(selectRegionalPromptsSlice, (regionalPrompts) => { const layer = regionalPrompts.present.layers.find((l) => l.id === layerId); - assert(isMaskedGuidanceLayer(layer), `Layer ${layerId} not found or not an RP layer`); - return layer.isVisible; + assert(layer, `Layer ${layerId} not found`); + return layer.isEnabled; }), [layerId] ); diff --git a/invokeai/frontend/web/src/features/regionalPrompts/hooks/useRegionalControlTitle.ts b/invokeai/frontend/web/src/features/regionalPrompts/hooks/useRegionalControlTitle.ts index bb9fd4f5e2..536e03bedc 100644 --- a/invokeai/frontend/web/src/features/regionalPrompts/hooks/useRegionalControlTitle.ts +++ b/invokeai/frontend/web/src/features/regionalPrompts/hooks/useRegionalControlTitle.ts @@ -10,7 +10,7 @@ const selectValidLayerCount = createSelector(selectRegionalPromptsSlice, (region } const validLayers = regionalPrompts.present.layers .filter(isMaskedGuidanceLayer) - .filter((l) => l.isVisible) + .filter((l) => l.isEnabled) .filter((l) => { const hasTextPrompt = Boolean(l.positivePrompt || l.negativePrompt); const hasAtLeastOneImagePrompt = l.ipAdapterIds.length > 0; diff --git a/invokeai/frontend/web/src/features/regionalPrompts/store/actions.ts b/invokeai/frontend/web/src/features/regionalPrompts/store/actions.ts new file mode 100644 index 0000000000..b28b04f643 --- /dev/null +++ b/invokeai/frontend/web/src/features/regionalPrompts/store/actions.ts @@ -0,0 +1,3 @@ + + + diff --git a/invokeai/frontend/web/src/features/regionalPrompts/store/regionalPromptsSlice.ts b/invokeai/frontend/web/src/features/regionalPrompts/store/regionalPromptsSlice.ts index bdc0f263b9..4128cad637 100644 --- a/invokeai/frontend/web/src/features/regionalPrompts/store/regionalPromptsSlice.ts +++ b/invokeai/frontend/web/src/features/regionalPrompts/store/regionalPromptsSlice.ts @@ -4,20 +4,16 @@ import type { PersistConfig, RootState } from 'app/store/store'; import { moveBackward, moveForward, moveToBack, moveToFront } from 'common/util/arrayUtils'; import { deepClone } from 'common/util/deepClone'; import { roundToMultiple } from 'common/util/roundDownToMultiple'; -import { controlAdapterRemoved, isAnyControlAdapterAdded } from 'features/controlAdapters/store/controlAdaptersSlice'; +import { + controlAdapterImageChanged, + controlAdapterProcessedImageChanged, + isAnyControlAdapterAdded, +} from 'features/controlAdapters/store/controlAdaptersSlice'; import { calculateNewSize } from 'features/parameters/components/ImageSize/calculateNewSize'; import { initialAspectRatioState } from 'features/parameters/components/ImageSize/constants'; import type { AspectRatioState } from 'features/parameters/components/ImageSize/types'; import { modelChanged } from 'features/parameters/store/generationSlice'; -import type { - ParameterAutoNegative, - ParameterHeight, - ParameterNegativePrompt, - ParameterNegativeStylePromptSDXL, - ParameterPositivePrompt, - ParameterPositiveStylePromptSDXL, - ParameterWidth, -} from 'features/parameters/types/parameterSchemas'; +import type { ParameterAutoNegative } from 'features/parameters/types/parameterSchemas'; import { getIsSizeOptimal, getOptimalDimension } from 'features/parameters/util/optimalDimension'; import type { IRect, Vector2d } from 'konva/lib/types'; import { isEqual } from 'lodash-es'; @@ -27,81 +23,17 @@ import type { UndoableOptions } from 'redux-undo'; import { assert } from 'tsafe'; import { v4 as uuidv4 } from 'uuid'; -type DrawingTool = 'brush' | 'eraser'; - -export type Tool = DrawingTool | 'move' | 'rect'; - -export type VectorMaskLine = { - id: string; - type: 'vector_mask_line'; - tool: DrawingTool; - strokeWidth: number; - points: number[]; -}; - -export type VectorMaskRect = { - id: string; - type: 'vector_mask_rect'; - x: number; - y: number; - width: number; - height: number; -}; - -type LayerBase = { - id: string; - isVisible: boolean; -}; - -type RenderableLayerBase = LayerBase & { - x: number; - y: number; - bbox: IRect | null; - bboxNeedsUpdate: boolean; -}; - -type ControlAdapterLayer = RenderableLayerBase & { - type: 'controlnet_layer'; // technically, also t2i adapter layer - controlAdapterId: string; -}; - -type IPAdapterLayer = LayerBase & { - type: 'ip_adapter_layer'; // technically, also t2i adapter layer - ipAdapterId: string; -}; - -export type MaskedGuidanceLayer = RenderableLayerBase & { - type: 'masked_guidance_layer'; - maskObjects: (VectorMaskLine | VectorMaskRect)[]; - positivePrompt: ParameterPositivePrompt | null; - negativePrompt: ParameterNegativePrompt | null; // Up to one text prompt per mask - ipAdapterIds: string[]; // Any number of image prompts - previewColor: RgbColor; - autoNegative: ParameterAutoNegative; - 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; - -type RegionalPromptsState = { - _version: 1; - selectedLayerId: string | null; - layers: Layer[]; - brushSize: number; - globalMaskLayerOpacity: number; - isEnabled: boolean; - positivePrompt: ParameterPositivePrompt; - negativePrompt: ParameterNegativePrompt; - positivePrompt2: ParameterPositiveStylePromptSDXL; - negativePrompt2: ParameterNegativeStylePromptSDXL; - shouldConcatPrompts: boolean; - initialImage: string | null; - size: { - width: ParameterWidth; - height: ParameterHeight; - aspectRatio: AspectRatioState; - }; -}; +import type { + ControlAdapterLayer, + DrawingTool, + IPAdapterLayer, + Layer, + MaskedGuidanceLayer, + RegionalPromptsState, + Tool, + VectorMaskLine, + VectorMaskRect, +} from './types'; export const initialRegionalPromptsState: RegionalPromptsState = { _version: 1, @@ -126,19 +58,22 @@ export const initialRegionalPromptsState: RegionalPromptsState = { const isLine = (obj: VectorMaskLine | VectorMaskRect): obj is VectorMaskLine => obj.type === 'vector_mask_line'; export const isMaskedGuidanceLayer = (layer?: Layer): layer is MaskedGuidanceLayer => layer?.type === 'masked_guidance_layer'; -export const isRenderableLayer = (layer?: Layer): layer is MaskedGuidanceLayer => - layer?.type === 'masked_guidance_layer' || layer?.type === 'controlnet_layer'; +export const isControlAdapterLayer = (layer?: Layer): layer is ControlAdapterLayer => + layer?.type === 'control_adapter_layer'; +export const isIPAdapterLayer = (layer?: Layer): layer is IPAdapterLayer => layer?.type === 'ip_adapter_layer'; +export const isRenderableLayer = (layer?: Layer): layer is MaskedGuidanceLayer | ControlAdapterLayer => + layer?.type === 'masked_guidance_layer' || layer?.type === 'control_adapter_layer'; const resetLayer = (layer: Layer) => { if (layer.type === 'masked_guidance_layer') { layer.maskObjects = []; layer.bbox = null; - layer.isVisible = true; + layer.isEnabled = true; layer.needsPixelBbox = false; layer.bboxNeedsUpdate = false; return; } - if (layer.type === 'controlnet_layer') { + if (layer.type === 'control_adapter_layer') { // TODO } }; @@ -153,59 +88,71 @@ export const regionalPromptsSlice = createSlice({ initialState: initialRegionalPromptsState, reducers: { //#region All Layers - layerAdded: { - reducer: (state, action: PayloadAction) => { - const type = action.payload; - if (type === 'masked_guidance_layer') { - const layer: MaskedGuidanceLayer = { - id: getMaskedGuidanceLayerId(action.meta.uuid), - type: 'masked_guidance_layer', - isVisible: true, - bbox: null, - bboxNeedsUpdate: false, - maskObjects: [], - previewColor: getVectorMaskPreviewColor(state), - x: 0, - y: 0, - autoNegative: 'invert', - needsPixelBbox: false, - positivePrompt: '', - negativePrompt: null, - ipAdapterIds: [], - }; - state.layers.push(layer); - state.selectedLayerId = layer.id; - return; - } - - if (type === 'controlnet_layer') { - const layer: ControlAdapterLayer = { - id: getControlLayerId(action.meta.uuid), - type: 'controlnet_layer', - controlAdapterId: action.meta.uuid, - x: 0, - y: 0, - bbox: null, - bboxNeedsUpdate: false, - isVisible: true, - }; - state.layers.push(layer); - state.selectedLayerId = layer.id; - return; - } - }, - prepare: (payload: Layer['type']) => ({ payload, meta: { uuid: uuidv4() } }), + maskedGuidanceLayerAdded: (state, action: PayloadAction<{ layerId: string }>) => { + const { layerId } = action.payload; + const layer: MaskedGuidanceLayer = { + id: getMaskedGuidanceLayerId(layerId), + type: 'masked_guidance_layer', + isEnabled: true, + bbox: null, + bboxNeedsUpdate: false, + maskObjects: [], + previewColor: getVectorMaskPreviewColor(state), + x: 0, + y: 0, + autoNegative: 'invert', + needsPixelBbox: false, + positivePrompt: '', + negativePrompt: null, + ipAdapterIds: [], + isSelected: true, + }; + state.layers.push(layer); + state.selectedLayerId = layer.id; + return; + }, + ipAdapterLayerAdded: (state, action: PayloadAction<{ layerId: string; ipAdapterId: string }>) => { + const { layerId, ipAdapterId } = action.payload; + const layer: IPAdapterLayer = { + id: getIPAdapterLayerId(layerId), + type: 'ip_adapter_layer', + isEnabled: true, + ipAdapterId, + }; + state.layers.push(layer); + return; + }, + controlAdapterLayerAdded: (state, action: PayloadAction<{ layerId: string; controlNetId: string }>) => { + const { layerId, controlNetId } = action.payload; + const layer: ControlAdapterLayer = { + id: getControlNetLayerId(layerId), + type: 'control_adapter_layer', + controlNetId, + x: 0, + y: 0, + bbox: null, + bboxNeedsUpdate: false, + isEnabled: true, + imageName: null, + opacity: 1, + isSelected: true, + }; + state.layers.push(layer); + state.selectedLayerId = layer.id; + return; }, layerSelected: (state, action: PayloadAction) => { - const layer = state.layers.find((l) => l.id === action.payload); - if (layer) { - state.selectedLayerId = layer.id; + for (const layer of state.layers) { + if (isRenderableLayer(layer) && layer.id === action.payload) { + layer.isSelected = true; + state.selectedLayerId = action.payload; + } } }, layerVisibilityToggled: (state, action: PayloadAction) => { const layer = state.layers.find((l) => l.id === action.payload); if (layer) { - layer.isVisible = !layer.isVisible; + layer.isEnabled = !layer.isEnabled; } }, layerTranslated: (state, action: PayloadAction<{ layerId: string; x: number; y: number }>) => { @@ -252,10 +199,6 @@ export const regionalPromptsSlice = createSlice({ // Because the layers are in reverse order, moving to the back is equivalent to moving to the front moveToFront(state.layers, cb); }, - allLayersDeleted: (state) => { - state.layers = []; - state.selectedLayerId = null; - }, selectedLayerReset: (state) => { const layer = state.layers.find((l) => l.id === state.selectedLayerId); if (layer) { @@ -283,14 +226,19 @@ export const regionalPromptsSlice = createSlice({ layer.negativePrompt = prompt; } }, - maskLayerIPAdapterAdded: { - reducer: (state, action: PayloadAction) => { - const layer = state.layers.find((l) => l.id === action.payload); - if (layer?.type === 'masked_guidance_layer') { - layer.ipAdapterIds.push(action.meta.uuid); - } - }, - prepare: (payload: string) => ({ payload, meta: { uuid: uuidv4() } }), + maskLayerIPAdapterAdded: (state, action: PayloadAction<{ layerId: string; ipAdapterId: string }>) => { + const { layerId, ipAdapterId } = action.payload; + const layer = state.layers.find((l) => l.id === layerId); + if (layer?.type === 'masked_guidance_layer') { + layer.ipAdapterIds.push(ipAdapterId); + } + }, + maskLayerIPAdapterDeleted: (state, action: PayloadAction<{ layerId: string; ipAdapterId: string }>) => { + const { layerId, ipAdapterId } = action.payload; + const layer = state.layers.find((l) => l.id === layerId); + if (layer?.type === 'masked_guidance_layer') { + layer.ipAdapterIds = layer.ipAdapterIds.filter((id) => id !== ipAdapterId); + } }, maskLayerPreviewColorChanged: (state, action: PayloadAction<{ layerId: string; color: RgbColor }>) => { const { layerId, color } = action.payload; @@ -422,10 +370,13 @@ export const regionalPromptsSlice = createSlice({ //#region General brushSizeChanged: (state, action: PayloadAction) => { - state.brushSize = action.payload; + state.brushSize = Math.round(action.payload); }, globalMaskLayerOpacityChanged: (state, action: PayloadAction) => { state.globalMaskLayerOpacity = action.payload; + state.layers.filter(isControlAdapterLayer).forEach((l) => { + l.opacity = action.payload; + }); }, isEnabledChanged: (state, action: PayloadAction) => { state.isEnabled = action.payload; @@ -445,12 +396,6 @@ export const regionalPromptsSlice = createSlice({ //#endregion }, extraReducers(builder) { - builder.addCase(controlAdapterRemoved, (state, action) => { - state.layers.filter(isMaskedGuidanceLayer).forEach((layer) => { - layer.ipAdapterIds = layer.ipAdapterIds.filter((id) => id !== action.payload.id); - }); - }); - builder.addCase(modelChanged, (state, action) => { const newModel = action.payload; if (!newModel || action.meta.previousModel?.base === newModel.base) { @@ -466,6 +411,28 @@ export const regionalPromptsSlice = createSlice({ state.size.height = height; }); + builder.addCase(controlAdapterImageChanged, (state, action) => { + const { id, controlImage } = action.payload; + const layer = state.layers.filter(isControlAdapterLayer).find((l) => l.controlNetId === id); + if (layer) { + layer.bbox = null; + layer.bboxNeedsUpdate = true; + layer.isEnabled = true; + layer.imageName = controlImage?.image_name ?? null; + } + }); + + builder.addCase(controlAdapterProcessedImageChanged, (state, action) => { + const { id, processedControlImage } = action.payload; + const layer = state.layers.filter(isControlAdapterLayer).find((l) => l.controlNetId === id); + if (layer) { + layer.bbox = null; + layer.bboxNeedsUpdate = true; + layer.isEnabled = true; + layer.imageName = processedControlImage?.image_name ?? null; + } + }); + // TODO: This is a temp fix to reduce issues with T2I adapter having a different downscaling // factor than the UNet. Hopefully we get an upstream fix in diffusers. builder.addMatcher(isAnyControlAdapterAdded, (state, action) => { @@ -510,7 +477,6 @@ class LayerColors { export const { // All layer actions - layerAdded, layerDeleted, layerMovedBackward, layerMovedForward, @@ -521,9 +487,11 @@ export const { layerTranslated, layerBboxChanged, layerVisibilityToggled, - allLayersDeleted, selectedLayerReset, selectedLayerDeleted, + maskedGuidanceLayerAdded, + ipAdapterLayerAdded, + controlAdapterLayerAdded, // Mask layer actions maskLayerLineAdded, maskLayerPointsAdded, @@ -531,6 +499,7 @@ export const { maskLayerNegativePromptChanged, maskLayerPositivePromptChanged, maskLayerIPAdapterAdded, + maskLayerIPAdapterDeleted, maskLayerAutoNegativeChanged, maskLayerPreviewColorChanged, // Base layer actions @@ -549,6 +518,20 @@ export const { redo, } = regionalPromptsSlice.actions; +export const selectAllControlAdapterIds = (regionalPrompts: RegionalPromptsState) => + regionalPrompts.layers.flatMap((l) => { + if (l.type === 'control_adapter_layer') { + return [l.controlNetId]; + } + if (l.type === 'ip_adapter_layer') { + return [l.ipAdapterId]; + } + if (l.type === 'masked_guidance_layer') { + return l.ipAdapterIds; + } + return []; + }); + export const selectRegionalPromptsSlice = (state: RootState) => state.regionalPrompts; /* eslint-disable-next-line @typescript-eslint/no-explicit-any */ @@ -571,8 +554,11 @@ export const TOOL_PREVIEW_BRUSH_BORDER_OUTER_ID = 'tool_preview_layer.brush_bord export const TOOL_PREVIEW_RECT_ID = 'tool_preview_layer.rect'; export const BACKGROUND_LAYER_ID = 'background_layer'; export const BACKGROUND_RECT_ID = 'background_layer.rect'; +export const CONTROLNET_LAYER_TRANSFORMER_ID = 'control_adapter_layer.transformer'; // Names (aka classes) for Konva layers and objects +export const CONTROLNET_LAYER_NAME = 'control_adapter_layer'; +export const CONTROLNET_LAYER_IMAGE_NAME = 'control_adapter_layer.image'; export const MASKED_GUIDANCE_LAYER_NAME = 'masked_guidance_layer'; export const MASKED_GUIDANCE_LAYER_LINE_NAME = 'masked_guidance_layer.line'; export const MASKED_GUIDANCE_LAYER_OBJECT_GROUP_NAME = 'masked_guidance_layer.object_group'; @@ -586,7 +572,9 @@ const getMaskedGuidnaceLayerRectId = (layerId: string, lineId: string) => `${lay export const getMaskedGuidanceLayerObjectGroupId = (layerId: string, groupId: string) => `${layerId}.objectGroup_${groupId}`; export const getLayerBboxId = (layerId: string) => `${layerId}.bbox`; -const getControlLayerId = (layerId: string) => `control_layer_${layerId}`; +const getControlNetLayerId = (layerId: string) => `control_adapter_layer_${layerId}`; +export const getControlNetLayerImageId = (layerId: string, imageName: string) => `${layerId}.image_${imageName}`; +const getIPAdapterLayerId = (layerId: string) => `ip_adapter_layer_${layerId}`; export const regionalPromptsPersistConfig: PersistConfig = { name: regionalPromptsSlice.name, diff --git a/invokeai/frontend/web/src/features/regionalPrompts/store/types.ts b/invokeai/frontend/web/src/features/regionalPrompts/store/types.ts new file mode 100644 index 0000000000..af2d2aeaf4 --- /dev/null +++ b/invokeai/frontend/web/src/features/regionalPrompts/store/types.ts @@ -0,0 +1,91 @@ +import type { AspectRatioState } from 'features/parameters/components/ImageSize/types'; +import type { + ParameterAutoNegative, + ParameterHeight, + ParameterNegativePrompt, + ParameterNegativeStylePromptSDXL, + ParameterPositivePrompt, + ParameterPositiveStylePromptSDXL, + ParameterWidth, +} from 'features/parameters/types/parameterSchemas'; +import type { IRect } from 'konva/lib/types'; +import type { RgbColor } from 'react-colorful'; + +export type DrawingTool = 'brush' | 'eraser'; + +export type Tool = DrawingTool | 'move' | 'rect'; + +export type VectorMaskLine = { + id: string; + type: 'vector_mask_line'; + tool: DrawingTool; + strokeWidth: number; + points: number[]; +}; + +export type VectorMaskRect = { + id: string; + type: 'vector_mask_rect'; + x: number; + y: number; + width: number; + height: number; +}; + +export type LayerBase = { + id: string; + isEnabled: boolean; +}; + +export type RenderableLayerBase = LayerBase & { + x: number; + y: number; + bbox: IRect | null; + bboxNeedsUpdate: boolean; + isSelected: boolean; +}; + +export type ControlAdapterLayer = RenderableLayerBase & { + type: 'control_adapter_layer'; // technically, also t2i adapter layer + controlNetId: string; + imageName: string | null; + opacity: number; +}; + +export type IPAdapterLayer = LayerBase & { + type: 'ip_adapter_layer'; // technically, also t2i adapter layer + ipAdapterId: string; +}; + +export type MaskedGuidanceLayer = RenderableLayerBase & { + type: 'masked_guidance_layer'; + maskObjects: (VectorMaskLine | VectorMaskRect)[]; + positivePrompt: ParameterPositivePrompt | null; + negativePrompt: ParameterNegativePrompt | null; // Up to one text prompt per mask + ipAdapterIds: string[]; // Any number of image prompts + previewColor: RgbColor; + autoNegative: ParameterAutoNegative; + 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 RegionalPromptsState = { + _version: 1; + selectedLayerId: string | null; + layers: Layer[]; + brushSize: number; + globalMaskLayerOpacity: number; + isEnabled: boolean; + positivePrompt: ParameterPositivePrompt; + negativePrompt: ParameterNegativePrompt; + positivePrompt2: ParameterPositiveStylePromptSDXL; + negativePrompt2: ParameterNegativeStylePromptSDXL; + shouldConcatPrompts: boolean; + initialImage: string | null; + size: { + width: ParameterWidth; + height: ParameterHeight; + aspectRatio: AspectRatioState; + }; +}; diff --git a/invokeai/frontend/web/src/features/regionalPrompts/util/renderers.ts b/invokeai/frontend/web/src/features/regionalPrompts/util/renderers.ts index 903482b362..83bf903430 100644 --- a/invokeai/frontend/web/src/features/regionalPrompts/util/renderers.ts +++ b/invokeai/frontend/web/src/features/regionalPrompts/util/renderers.ts @@ -1,20 +1,18 @@ import { getStore } from 'app/store/nanostores/store'; import { rgbaColorToString, rgbColorToString } from 'features/canvas/util/colorToString'; import { getScaledFlooredCursorPosition } from 'features/regionalPrompts/hooks/mouseEventHooks'; -import type { - Layer, - MaskedGuidanceLayer, - Tool, - VectorMaskLine, - VectorMaskRect, -} from 'features/regionalPrompts/store/regionalPromptsSlice'; import { $tool, BACKGROUND_LAYER_ID, BACKGROUND_RECT_ID, + CONTROLNET_LAYER_IMAGE_NAME, + CONTROLNET_LAYER_NAME, + getControlNetLayerImageId, getLayerBboxId, getMaskedGuidanceLayerObjectGroupId, + isControlAdapterLayer, isMaskedGuidanceLayer, + isRenderableLayer, LAYER_BBOX_NAME, MASKED_GUIDANCE_LAYER_LINE_NAME, MASKED_GUIDANCE_LAYER_NAME, @@ -27,11 +25,20 @@ import { TOOL_PREVIEW_LAYER_ID, TOOL_PREVIEW_RECT_ID, } from 'features/regionalPrompts/store/regionalPromptsSlice'; +import type { + ControlAdapterLayer, + Layer, + MaskedGuidanceLayer, + Tool, + VectorMaskLine, + VectorMaskRect, +} from 'features/regionalPrompts/store/types'; import { getLayerBboxFast, getLayerBboxPixels } from 'features/regionalPrompts/util/bbox'; import Konva from 'konva'; import type { IRect, Vector2d } from 'konva/lib/types'; import { debounce } from 'lodash-es'; import type { RgbColor } from 'react-colorful'; +import { imagesApi } from 'services/api/endpoints/images'; import { assert } from 'tsafe'; import { v4 as uuidv4 } from 'uuid'; @@ -53,6 +60,9 @@ const getIsSelected = (layerId?: string | null) => { return layerId === getStore().getState().regionalPrompts.present.selectedLayerId; }; +const selectRenderableLayers = (n: Konva.Node) => + n.name() === MASKED_GUIDANCE_LAYER_NAME || n.name() === CONTROLNET_LAYER_NAME; + const selectVectorMaskObjects = (node: Konva.Node) => { return node.name() === MASKED_GUIDANCE_LAYER_LINE_NAME || node.name() === MASKED_GUIDANCE_LAYER_RECT_NAME; }; @@ -219,7 +229,7 @@ const renderToolPreview = ( * @param reduxLayer The redux layer to create the konva layer from. * @param onLayerPosChanged Callback for when the layer's position changes. */ -const createVectorMaskLayer = ( +const createMaskedGuidanceLayer = ( stage: Konva.Stage, reduxLayer: MaskedGuidanceLayer, onLayerPosChanged?: (layerId: string, x: number, y: number) => void @@ -320,7 +330,7 @@ const createVectorMaskRect = (reduxObject: VectorMaskRect, konvaGroup: Konva.Gro * @param globalMaskLayerOpacity The opacity of the global mask layer. * @param tool The current tool. */ -const renderVectorMaskLayer = ( +const renderMaskedGuidanceLayer = ( stage: Konva.Stage, reduxLayer: MaskedGuidanceLayer, globalMaskLayerOpacity: number, @@ -328,7 +338,7 @@ const renderVectorMaskLayer = ( onLayerPosChanged?: (layerId: string, x: number, y: number) => void ): void => { const konvaLayer = - stage.findOne(`#${reduxLayer.id}`) ?? createVectorMaskLayer(stage, reduxLayer, onLayerPosChanged); + stage.findOne(`#${reduxLayer.id}`) ?? createMaskedGuidanceLayer(stage, reduxLayer, onLayerPosChanged); // Update the layer's position and listening state konvaLayer.setAttrs({ @@ -383,8 +393,8 @@ const renderVectorMaskLayer = ( } // Only update layer visibility if it has changed. - if (konvaLayer.visible() !== reduxLayer.isVisible) { - konvaLayer.visible(reduxLayer.isVisible); + if (konvaLayer.visible() !== reduxLayer.isEnabled) { + konvaLayer.visible(reduxLayer.isEnabled); groupNeedsCache = true; } @@ -401,6 +411,101 @@ const renderVectorMaskLayer = ( } }; +const createControlNetLayer = (stage: Konva.Stage, reduxLayer: ControlAdapterLayer): Konva.Layer => { + const konvaLayer = new Konva.Layer({ + id: reduxLayer.id, + name: CONTROLNET_LAYER_NAME, + imageSmoothingEnabled: false, + }); + stage.add(konvaLayer); + return konvaLayer; +}; + +const createControlNetLayerImage = (konvaLayer: Konva.Layer, image: HTMLImageElement): Konva.Image => { + const konvaImage = new Konva.Image({ + name: CONTROLNET_LAYER_IMAGE_NAME, + image, + filters: [LightnessToAlphaFilter], + }); + konvaLayer.add(konvaImage); + return konvaImage; +}; + +const updateControlNetLayerImageSource = async ( + stage: Konva.Stage, + konvaLayer: Konva.Layer, + reduxLayer: ControlAdapterLayer +) => { + if (reduxLayer.imageName) { + const imageName = reduxLayer.imageName; + const req = getStore().dispatch(imagesApi.endpoints.getImageDTO.initiate(reduxLayer.imageName)); + const imageDTO = await req.unwrap(); + req.unsubscribe(); + const image = new Image(); + const imageId = getControlNetLayerImageId(reduxLayer.id, imageName); + image.onload = () => { + // Find the existing image or create a new one - must find using the name, bc the id may have just changed + const konvaImage = + konvaLayer.findOne(`.${CONTROLNET_LAYER_IMAGE_NAME}`) ?? + createControlNetLayerImage(konvaLayer, image); + + // Update the image's attributes + konvaImage.setAttrs({ + id: imageId, + image, + }); + updateControlNetLayerImageAttrs(stage, konvaImage, reduxLayer); + // Must cache after this to apply the filters + konvaImage.cache(); + image.id = imageId; + }; + image.src = imageDTO.image_url; + } else { + konvaLayer.findOne(`.${CONTROLNET_LAYER_IMAGE_NAME}`)?.destroy(); + } +}; + +const updateControlNetLayerImageAttrs = ( + stage: Konva.Stage, + konvaImage: Konva.Image, + reduxLayer: ControlAdapterLayer +) => { + konvaImage.setAttrs({ + opacity: reduxLayer.opacity, + scaleX: 1, + scaleY: 1, + width: stage.width() / stage.scaleX(), + height: stage.height() / stage.scaleY(), + visible: reduxLayer.isEnabled, + }); + konvaImage.cache(); +}; + +const renderControlNetLayer = (stage: Konva.Stage, reduxLayer: ControlAdapterLayer) => { + const konvaLayer = stage.findOne(`#${reduxLayer.id}`) ?? createControlNetLayer(stage, reduxLayer); + const konvaImage = konvaLayer.findOne(`.${CONTROLNET_LAYER_IMAGE_NAME}`); + const canvasImageSource = konvaImage?.image(); + let imageSourceNeedsUpdate = false; + if (canvasImageSource instanceof HTMLImageElement) { + if ( + reduxLayer.imageName && + canvasImageSource.id !== getControlNetLayerImageId(reduxLayer.id, reduxLayer.imageName) + ) { + imageSourceNeedsUpdate = true; + } else if (!reduxLayer.imageName) { + imageSourceNeedsUpdate = true; + } + } else if (!canvasImageSource) { + imageSourceNeedsUpdate = true; + } + + if (imageSourceNeedsUpdate) { + updateControlNetLayerImageSource(stage, konvaLayer, reduxLayer); + } else if (konvaImage) { + updateControlNetLayerImageAttrs(stage, konvaImage, reduxLayer); + } +}; + /** * Renders the layers on the stage. * @param stage The konva stage to render on. @@ -416,10 +521,9 @@ const renderLayers = ( tool: Tool, onLayerPosChanged?: (layerId: string, x: number, y: number) => void ) => { - const reduxLayerIds = reduxLayers.map(mapId); - + const reduxLayerIds = reduxLayers.filter(isRenderableLayer).map(mapId); // Remove un-rendered layers - for (const konvaLayer of stage.find(`.${MASKED_GUIDANCE_LAYER_NAME}`)) { + for (const konvaLayer of stage.find(selectRenderableLayers)) { if (!reduxLayerIds.includes(konvaLayer.id())) { konvaLayer.destroy(); } @@ -427,7 +531,10 @@ const renderLayers = ( for (const reduxLayer of reduxLayers) { if (isMaskedGuidanceLayer(reduxLayer)) { - renderVectorMaskLayer(stage, reduxLayer, globalMaskLayerOpacity, tool, onLayerPosChanged); + renderMaskedGuidanceLayer(stage, reduxLayer, globalMaskLayerOpacity, tool, onLayerPosChanged); + } + if (isControlAdapterLayer(reduxLayer)) { + renderControlNetLayer(stage, reduxLayer); } } }; @@ -620,3 +727,20 @@ export const debouncedRenderers = { renderBackground: debounce(renderBackground, DEBOUNCE_MS), arrangeLayers: debounce(arrangeLayers, DEBOUNCE_MS), }; + +/** + * Calculates the lightness (HSL) of a given pixel and sets the alpha channel to that value. + * This is useful for edge maps and other masks, to make the black areas transparent. + * @param imageData The image data to apply the filter to + */ +const LightnessToAlphaFilter = (imageData: ImageData) => { + const len = imageData.data.length / 4; + for (let i = 0; i < len; i++) { + const r = imageData.data[i * 4 + 0] as number; + const g = imageData.data[i * 4 + 1] as number; + const b = imageData.data[i * 4 + 2] as number; + const cMin = Math.min(r, g, b); + const cMax = Math.max(r, g, b); + imageData.data[i * 4 + 3] = (cMin + cMax) / 2; + } +}; diff --git a/invokeai/frontend/web/src/features/settingsAccordions/components/ControlSettingsAccordion/ControlSettingsAccordion.tsx b/invokeai/frontend/web/src/features/settingsAccordions/components/ControlSettingsAccordion/ControlSettingsAccordion.tsx index 843ed3a4b7..b6e1b179b3 100644 --- a/invokeai/frontend/web/src/features/settingsAccordions/components/ControlSettingsAccordion/ControlSettingsAccordion.tsx +++ b/invokeai/frontend/web/src/features/settingsAccordions/components/ControlSettingsAccordion/ControlSettingsAccordion.tsx @@ -13,7 +13,10 @@ import { selectValidIPAdapters, selectValidT2IAdapters, } from 'features/controlAdapters/store/controlAdaptersSlice'; -import { isMaskedGuidanceLayer, selectRegionalPromptsSlice } from 'features/regionalPrompts/store/regionalPromptsSlice'; +import { + selectAllControlAdapterIds, + selectRegionalPromptsSlice, +} from 'features/regionalPrompts/store/regionalPromptsSlice'; import { useStandaloneAccordionToggle } from 'features/settingsAccordions/hooks/useStandaloneAccordionToggle'; import { useFeatureStatus } from 'features/system/hooks/useFeatureStatus'; import { Fragment, memo } from 'react'; @@ -26,10 +29,10 @@ const selector = createMemoizedSelector( const badges: string[] = []; let isError = false; + const regionalControlAdapterIds = selectAllControlAdapterIds(regionalPrompts.present); + const enabledNonRegionalIPAdapterCount = selectAllIPAdapters(controlAdapters) - .filter( - (ca) => !regionalPrompts.present.layers.filter(isMaskedGuidanceLayer).some((l) => l.ipAdapterIds.includes(ca.id)) - ) + .filter((ca) => !regionalControlAdapterIds.includes(ca.id)) .filter((ca) => ca.isEnabled).length; const validIPAdapterCount = selectValidIPAdapters(controlAdapters).length; @@ -40,7 +43,9 @@ const selector = createMemoizedSelector( isError = true; } - const enabledControlNetCount = selectAllControlNets(controlAdapters).filter((ca) => ca.isEnabled).length; + const enabledControlNetCount = selectAllControlNets(controlAdapters) + .filter((ca) => !regionalControlAdapterIds.includes(ca.id)) + .filter((ca) => ca.isEnabled).length; const validControlNetCount = selectValidControlNets(controlAdapters).length; if (enabledControlNetCount > 0) { badges.push(`${enabledControlNetCount} ControlNet`); @@ -49,7 +54,9 @@ const selector = createMemoizedSelector( isError = true; } - const enabledT2IAdapterCount = selectAllT2IAdapters(controlAdapters).filter((ca) => ca.isEnabled).length; + const enabledT2IAdapterCount = selectAllT2IAdapters(controlAdapters) + .filter((ca) => !regionalControlAdapterIds.includes(ca.id)) + .filter((ca) => ca.isEnabled).length; const validT2IAdapterCount = selectValidT2IAdapters(controlAdapters).length; if (enabledT2IAdapterCount > 0) { badges.push(`${enabledT2IAdapterCount} T2I`); @@ -59,7 +66,7 @@ const selector = createMemoizedSelector( } const controlAdapterIds = selectControlAdapterIds(controlAdapters).filter( - (id) => !regionalPrompts.present.layers.filter(isMaskedGuidanceLayer).some((l) => l.ipAdapterIds.includes(id)) + (id) => !regionalControlAdapterIds.includes(id) ); return { diff --git a/invokeai/frontend/web/src/features/ui/components/ParametersPanel.tsx b/invokeai/frontend/web/src/features/ui/components/ParametersPanel.tsx index a026a95196..bd76f5c924 100644 --- a/invokeai/frontend/web/src/features/ui/components/ParametersPanel.tsx +++ b/invokeai/frontend/web/src/features/ui/components/ParametersPanel.tsx @@ -48,7 +48,7 @@ const ParametersPanel = () => { - + {activeTabName !== 'txt2img' && } {activeTabName === 'unifiedCanvas' && } {isSDXL && } From c354470cd11370ad64f9906fd7cd9f6d08e70e72 Mon Sep 17 00:00:00 2001 From: psychedelicious <4822129+psychedelicious@users.noreply.github.com> Date: Tue, 30 Apr 2024 00:00:26 +1000 Subject: [PATCH 12/52] perf(ui): do not cache controlnet images unless required --- .../regionalPrompts/util/renderers.ts | 29 +++++++++++++------ 1 file changed, 20 insertions(+), 9 deletions(-) diff --git a/invokeai/frontend/web/src/features/regionalPrompts/util/renderers.ts b/invokeai/frontend/web/src/features/regionalPrompts/util/renderers.ts index 83bf903430..5687f249e7 100644 --- a/invokeai/frontend/web/src/features/regionalPrompts/util/renderers.ts +++ b/invokeai/frontend/web/src/features/regionalPrompts/util/renderers.ts @@ -470,15 +470,26 @@ const updateControlNetLayerImageAttrs = ( konvaImage: Konva.Image, reduxLayer: ControlAdapterLayer ) => { - konvaImage.setAttrs({ - opacity: reduxLayer.opacity, - scaleX: 1, - scaleY: 1, - width: stage.width() / stage.scaleX(), - height: stage.height() / stage.scaleY(), - visible: reduxLayer.isEnabled, - }); - konvaImage.cache(); + let needsCache = false; + const newWidth = stage.width() / stage.scaleX(); + const newHeight = stage.height() / stage.scaleY(); + if (konvaImage.width() !== newWidth || konvaImage.height() !== newHeight) { + konvaImage.setAttrs({ + opacity: reduxLayer.opacity, + scaleX: 1, + scaleY: 1, + width: stage.width() / stage.scaleX(), + height: stage.height() / stage.scaleY(), + visible: reduxLayer.isEnabled, + }); + needsCache = true; + } + if (konvaImage.opacity() !== reduxLayer.opacity) { + konvaImage.opacity(reduxLayer.opacity); + } + if (needsCache) { + konvaImage.cache(); + } }; const renderControlNetLayer = (stage: Konva.Stage, reduxLayer: ControlAdapterLayer) => { From 56050f788752f2b7196cfc422a1b16d9e3c116e9 Mon Sep 17 00:00:00 2001 From: psychedelicious <4822129+psychedelicious@users.noreply.github.com> Date: Tue, 30 Apr 2024 09:07:06 +1000 Subject: [PATCH 13/52] fix(ui): fix canvas scaling when window is zoomed Konva doesn't react to changes to window zoom/scale. If you open the tab at, say, 90%, then bump to 100%, the pixel ratio of the canvas doesn't change. This results in lower-quality renders on the canvas (generation is unaffected). --- invokeai/frontend/web/package.json | 1 + invokeai/frontend/web/pnpm-lock.yaml | 11 +++++++++++ .../regionalPrompts/components/StageComponent.tsx | 6 ++++++ .../src/features/regionalPrompts/util/renderers.ts | 2 +- 4 files changed, 19 insertions(+), 1 deletion(-) diff --git a/invokeai/frontend/web/package.json b/invokeai/frontend/web/package.json index a591e654a7..9e661e0737 100644 --- a/invokeai/frontend/web/package.json +++ b/invokeai/frontend/web/package.json @@ -101,6 +101,7 @@ "serialize-error": "^11.0.3", "socket.io-client": "^4.7.5", "use-debounce": "^10.0.0", + "use-device-pixel-ratio": "^1.1.2", "use-image": "^1.1.1", "uuid": "^9.0.1", "zod": "^3.22.4", diff --git a/invokeai/frontend/web/pnpm-lock.yaml b/invokeai/frontend/web/pnpm-lock.yaml index c0cbc59ad2..9910e32391 100644 --- a/invokeai/frontend/web/pnpm-lock.yaml +++ b/invokeai/frontend/web/pnpm-lock.yaml @@ -158,6 +158,9 @@ dependencies: use-debounce: specifier: ^10.0.0 version: 10.0.0(react@18.2.0) + use-device-pixel-ratio: + specifier: ^1.1.2 + version: 1.1.2(react@18.2.0) use-image: specifier: ^1.1.1 version: 1.1.1(react-dom@18.2.0)(react@18.2.0) @@ -13324,6 +13327,14 @@ packages: react: 18.2.0 dev: false + /use-device-pixel-ratio@1.1.2(react@18.2.0): + resolution: {integrity: sha512-nFxV0HwLdRUt20kvIgqHYZe6PK/v4mU1X8/eLsT1ti5ck0l2ob0HDRziaJPx+YWzBo6dMm4cTac3mcyk68Gh+A==} + peerDependencies: + react: '>=16.8.0' + dependencies: + react: 18.2.0 + dev: false + /use-image@1.1.1(react-dom@18.2.0)(react@18.2.0): resolution: {integrity: sha512-n4YO2k8AJG/BcDtxmBx8Aa+47kxY5m335dJiCQA5tTeVU4XdhrhqR6wT0WISRXwdMEOv5CSjqekDZkEMiiWaYQ==} peerDependencies: diff --git a/invokeai/frontend/web/src/features/regionalPrompts/components/StageComponent.tsx b/invokeai/frontend/web/src/features/regionalPrompts/components/StageComponent.tsx index e3018d3da9..6f6d828c5c 100644 --- a/invokeai/frontend/web/src/features/regionalPrompts/components/StageComponent.tsx +++ b/invokeai/frontend/web/src/features/regionalPrompts/components/StageComponent.tsx @@ -19,6 +19,7 @@ import { debouncedRenderers, renderers as normalRenderers } from 'features/regio import Konva from 'konva'; import type { IRect } from 'konva/lib/types'; import { memo, useCallback, useLayoutEffect, useMemo, useState } from 'react'; +import { useDevicePixelRatio } from 'use-device-pixel-ratio'; import { v4 as uuidv4 } from 'uuid'; // This will log warnings when layers > 5 - maybe use `import.meta.env.MODE === 'development'` instead? @@ -49,6 +50,7 @@ const useStageRenderer = ( const selectedLayerIdColor = useAppSelector(selectSelectedLayerColor); const layerIds = useMemo(() => state.layers.map((l) => l.id), [state.layers]); const renderers = useMemo(() => (asPreview ? debouncedRenderers : normalRenderers), [asPreview]); + const dpr = useDevicePixelRatio({ round: false }); const onLayerPosChanged = useCallback( (layerId: string, x: number, y: number) => { @@ -196,6 +198,10 @@ const useStageRenderer = ( log.trace('Arranging layers'); renderers.arrangeLayers(stage, layerIds); }, [stage, layerIds, renderers]); + + useLayoutEffect(() => { + Konva.pixelRatio = dpr; + }, [dpr]); }; type Props = { diff --git a/invokeai/frontend/web/src/features/regionalPrompts/util/renderers.ts b/invokeai/frontend/web/src/features/regionalPrompts/util/renderers.ts index 5687f249e7..beb9fcf7b3 100644 --- a/invokeai/frontend/web/src/features/regionalPrompts/util/renderers.ts +++ b/invokeai/frontend/web/src/features/regionalPrompts/util/renderers.ts @@ -415,7 +415,7 @@ const createControlNetLayer = (stage: Konva.Stage, reduxLayer: ControlAdapterLay const konvaLayer = new Konva.Layer({ id: reduxLayer.id, name: CONTROLNET_LAYER_NAME, - imageSmoothingEnabled: false, + imageSmoothingEnabled: true, }); stage.add(konvaLayer); return konvaLayer; From 387ab9cee76d11b8e9a7441afc01e8d17331d63e Mon Sep 17 00:00:00 2001 From: psychedelicious <4822129+psychedelicious@users.noreply.github.com> Date: Tue, 30 Apr 2024 09:15:38 +1000 Subject: [PATCH 14/52] feat(ui): reset controlnet model to null instead of disabling when base model changes --- .../listenerMiddleware/listeners/modelSelected.ts | 4 ++-- .../features/controlAdapters/store/controlAdaptersSlice.ts | 7 ++++++- .../services/api/hooks/useGetModelConfigWithTypeGuard.ts | 2 +- 3 files changed, 9 insertions(+), 4 deletions(-) diff --git a/invokeai/frontend/web/src/app/store/middleware/listenerMiddleware/listeners/modelSelected.ts b/invokeai/frontend/web/src/app/store/middleware/listenerMiddleware/listeners/modelSelected.ts index bc049cf498..b69e56e84a 100644 --- a/invokeai/frontend/web/src/app/store/middleware/listenerMiddleware/listeners/modelSelected.ts +++ b/invokeai/frontend/web/src/app/store/middleware/listenerMiddleware/listeners/modelSelected.ts @@ -1,7 +1,7 @@ import { logger } from 'app/logging/logger'; import type { AppStartListening } from 'app/store/middleware/listenerMiddleware'; import { - controlAdapterIsEnabledChanged, + controlAdapterModelChanged, selectControlAdapterAll, } from 'features/controlAdapters/store/controlAdaptersSlice'; import { loraRemoved } from 'features/lora/store/loraSlice'; @@ -54,7 +54,7 @@ export const addModelSelectedListener = (startAppListening: AppStartListening) = // handle incompatible controlnets selectControlAdapterAll(state.controlAdapters).forEach((ca) => { if (ca.model?.base !== newBaseModel) { - dispatch(controlAdapterIsEnabledChanged({ id: ca.id, isEnabled: false })); + dispatch(controlAdapterModelChanged({ id: ca.id, modelConfig: null })); modelsCleared += 1; } }); diff --git a/invokeai/frontend/web/src/features/controlAdapters/store/controlAdaptersSlice.ts b/invokeai/frontend/web/src/features/controlAdapters/store/controlAdaptersSlice.ts index 4c5a6fe85b..a5d6a185db 100644 --- a/invokeai/frontend/web/src/features/controlAdapters/store/controlAdaptersSlice.ts +++ b/invokeai/frontend/web/src/features/controlAdapters/store/controlAdaptersSlice.ts @@ -227,7 +227,7 @@ export const controlAdaptersSlice = createSlice({ state, action: PayloadAction<{ id: string; - modelConfig: ControlNetModelConfig | T2IAdapterModelConfig | IPAdapterModelConfig; + modelConfig: ControlNetModelConfig | T2IAdapterModelConfig | IPAdapterModelConfig | null; }> ) => { const { id, modelConfig } = action.payload; @@ -236,6 +236,11 @@ export const controlAdaptersSlice = createSlice({ return; } + if (modelConfig === null) { + caAdapter.updateOne(state, { id, changes: { model: null } }); + return; + } + const model = zModelIdentifierField.parse(modelConfig); if (!isControlNetOrT2IAdapter(cn)) { diff --git a/invokeai/frontend/web/src/services/api/hooks/useGetModelConfigWithTypeGuard.ts b/invokeai/frontend/web/src/services/api/hooks/useGetModelConfigWithTypeGuard.ts index 6de2941403..8ff4db1acc 100644 --- a/invokeai/frontend/web/src/services/api/hooks/useGetModelConfigWithTypeGuard.ts +++ b/invokeai/frontend/web/src/services/api/hooks/useGetModelConfigWithTypeGuard.ts @@ -8,7 +8,7 @@ export const useGetModelConfigWithTypeGuard = ( ) => { const result = useGetModelConfigQuery(key ?? skipToken, { selectFromResult: (result) => { - const modelConfig = result.data; + const modelConfig = result.currentData; return { ...result, modelConfig: modelConfig && typeGuard(modelConfig) ? modelConfig : undefined, From 5bf4d3794983c8daeab8f2b7df420fc154709266 Mon Sep 17 00:00:00 2001 From: psychedelicious <4822129+psychedelicious@users.noreply.github.com> Date: Tue, 30 Apr 2024 09:49:01 +1000 Subject: [PATCH 15/52] perf(ui): reduce control image processing to when it is needed Only should reprocess if the processor settings or the image has changed. --- .../listeners/controlNetAutoProcess.ts | 6 ++++++ .../controlAdapters/store/controlAdaptersSlice.ts | 15 ++++++++------- 2 files changed, 14 insertions(+), 7 deletions(-) diff --git a/invokeai/frontend/web/src/app/store/middleware/listenerMiddleware/listeners/controlNetAutoProcess.ts b/invokeai/frontend/web/src/app/store/middleware/listenerMiddleware/listeners/controlNetAutoProcess.ts index e52df30681..14af0246a2 100644 --- a/invokeai/frontend/web/src/app/store/middleware/listenerMiddleware/listeners/controlNetAutoProcess.ts +++ b/invokeai/frontend/web/src/app/store/middleware/listenerMiddleware/listeners/controlNetAutoProcess.ts @@ -12,6 +12,7 @@ import { selectControlAdapterById, } from 'features/controlAdapters/store/controlAdaptersSlice'; import { isControlNetOrT2IAdapter } from 'features/controlAdapters/store/types'; +import { isEqual } from 'lodash-es'; type AnyControlAdapterParamChangeAction = | ReturnType @@ -52,6 +53,11 @@ const predicate: AnyListenerPredicate = (action, state, prevState) => return false; } + if (prevCA.controlImage === ca.controlImage && isEqual(prevCA.processorNode, ca.processorNode)) { + // Don't re-process if the processor hasn't changed + return false; + } + const isProcessorSelected = processorType !== 'none'; const hasControlImage = Boolean(controlImage); diff --git a/invokeai/frontend/web/src/features/controlAdapters/store/controlAdaptersSlice.ts b/invokeai/frontend/web/src/features/controlAdapters/store/controlAdaptersSlice.ts index a5d6a185db..0c1ac20200 100644 --- a/invokeai/frontend/web/src/features/controlAdapters/store/controlAdaptersSlice.ts +++ b/invokeai/frontend/web/src/features/controlAdapters/store/controlAdaptersSlice.ts @@ -248,22 +248,23 @@ export const controlAdaptersSlice = createSlice({ return; } - const update: Update = { - id, - changes: { model, shouldAutoConfig: true }, - }; - - update.changes.processedControlImage = null; - if (modelConfig.type === 'ip_adapter') { // should never happen... return; } + // We always update the model + const update: Update = { id, changes: { model } }; + + // Build the default processor for this model const processor = buildControlAdapterProcessor(modelConfig); if (processor.processorType !== cn.processorNode.type) { + // If the processor type has changed, update the processor node + update.changes.shouldAutoConfig = true; + update.changes.processedControlImage = null; update.changes.processorType = processor.processorType; update.changes.processorNode = processor.processorNode; + if (cn.controlImageDimensions) { const minDim = Math.min(cn.controlImageDimensions.width, cn.controlImageDimensions.height); if ('detect_resolution' in update.changes.processorNode) { From 9d67ec9efec647b51751fa656acd3f57faca406d Mon Sep 17 00:00:00 2001 From: psychedelicious <4822129+psychedelicious@users.noreply.github.com> Date: Tue, 30 Apr 2024 09:50:59 +1000 Subject: [PATCH 16/52] fix(ui): toggle control adapter layer vis --- .../web/src/features/regionalPrompts/util/renderers.ts | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/invokeai/frontend/web/src/features/regionalPrompts/util/renderers.ts b/invokeai/frontend/web/src/features/regionalPrompts/util/renderers.ts index beb9fcf7b3..3d2da4ecea 100644 --- a/invokeai/frontend/web/src/features/regionalPrompts/util/renderers.ts +++ b/invokeai/frontend/web/src/features/regionalPrompts/util/renderers.ts @@ -473,7 +473,11 @@ const updateControlNetLayerImageAttrs = ( let needsCache = false; const newWidth = stage.width() / stage.scaleX(); const newHeight = stage.height() / stage.scaleY(); - if (konvaImage.width() !== newWidth || konvaImage.height() !== newHeight) { + if ( + konvaImage.width() !== newWidth || + konvaImage.height() !== newHeight || + konvaImage.visible() !== reduxLayer.isEnabled + ) { konvaImage.setAttrs({ opacity: reduxLayer.opacity, scaleX: 1, From fe459295ea1ff1fb8421df336398c6ae182b24ac Mon Sep 17 00:00:00 2001 From: psychedelicious <4822129+psychedelicious@users.noreply.github.com> Date: Tue, 30 Apr 2024 10:00:57 +1000 Subject: [PATCH 17/52] fix(ui): exclude disabled control adapters on control layers --- .../nodes/util/graph/addControlNetToLinearGraph.ts | 14 +++++++++----- .../nodes/util/graph/addIPAdapterToLinearGraph.ts | 10 +++++++--- .../nodes/util/graph/addT2IAdapterToLinearGraph.ts | 13 ++++++++----- 3 files changed, 24 insertions(+), 13 deletions(-) diff --git a/invokeai/frontend/web/src/features/nodes/util/graph/addControlNetToLinearGraph.ts b/invokeai/frontend/web/src/features/nodes/util/graph/addControlNetToLinearGraph.ts index bc58af7525..ac3c7ba670 100644 --- a/invokeai/frontend/web/src/features/nodes/util/graph/addControlNetToLinearGraph.ts +++ b/invokeai/frontend/web/src/features/nodes/util/graph/addControlNetToLinearGraph.ts @@ -33,16 +33,20 @@ const getControlNets = (state: RootState) => { // accordion. We need to filter the list of valid T2I adapters according to the tab. const activeTabName = activeTabNameSelector(state); - // Collect all ControlNet ids for ControlNet layers - const layerControlNetIds = state.regionalPrompts.present.layers - .filter(isControlAdapterLayer) - .map((l) => l.controlNetId); - if (activeTabName === 'txt2img') { // Add only the cnets that are used in control layers + // Collect all ControlNet ids for enabled ControlNet layers + const layerControlNetIds = state.regionalPrompts.present.layers + .filter(isControlAdapterLayer) + .filter((l) => l.isEnabled) + .map((l) => l.controlNetId); return intersectionWith(validControlNets, layerControlNetIds, (a, b) => a.id === b); } else { // Else, we want to exclude the cnets that are used in control layers + // Collect all ControlNet ids for all ControlNet layers + const layerControlNetIds = state.regionalPrompts.present.layers + .filter(isControlAdapterLayer) + .map((l) => l.controlNetId); return differenceWith(validControlNets, layerControlNetIds, (a, b) => a.id === b); } }; diff --git a/invokeai/frontend/web/src/features/nodes/util/graph/addIPAdapterToLinearGraph.ts b/invokeai/frontend/web/src/features/nodes/util/graph/addIPAdapterToLinearGraph.ts index 746e2da81d..12e942ed59 100644 --- a/invokeai/frontend/web/src/features/nodes/util/graph/addIPAdapterToLinearGraph.ts +++ b/invokeai/frontend/web/src/features/nodes/util/graph/addIPAdapterToLinearGraph.ts @@ -37,14 +37,18 @@ const getIPAdapters = (state: RootState) => { // accordion. We need to filter the list of valid IP adapters according to the tab. const activeTabName = activeTabNameSelector(state); - // Collect all IP Adapter ids for IP adapter layers - const layerIPAdapterIds = state.regionalPrompts.present.layers.filter(isIPAdapterLayer).map((l) => l.ipAdapterId); - 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 + // Collect all IP Adapter ids for enabled IP adapter layers + const layerIPAdapterIds = state.regionalPrompts.present.layers + .filter(isIPAdapterLayer) + .filter((l) => l.isEnabled) + .map((l) => l.ipAdapterId); return intersectionWith(nonMaskedIPAdapters, layerIPAdapterIds, (a, b) => a.id === b); } else { // 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 + const layerIPAdapterIds = state.regionalPrompts.present.layers.filter(isIPAdapterLayer).map((l) => l.ipAdapterId); return differenceWith(nonMaskedIPAdapters, layerIPAdapterIds, (a, b) => a.id === b); } }; diff --git a/invokeai/frontend/web/src/features/nodes/util/graph/addT2IAdapterToLinearGraph.ts b/invokeai/frontend/web/src/features/nodes/util/graph/addT2IAdapterToLinearGraph.ts index 0ac4affd1b..5fd168205f 100644 --- a/invokeai/frontend/web/src/features/nodes/util/graph/addT2IAdapterToLinearGraph.ts +++ b/invokeai/frontend/web/src/features/nodes/util/graph/addT2IAdapterToLinearGraph.ts @@ -33,16 +33,19 @@ const getT2IAdapters = (state: RootState) => { // accordion. We need to filter the list of valid T2I adapters according to the tab. const activeTabName = activeTabNameSelector(state); - // Collect all ids for control adapter layers - const layerControlAdapterIds = state.regionalPrompts.present.layers - .filter(isControlAdapterLayer) - .map((l) => l.controlNetId); - if (activeTabName === 'txt2img') { // Add only the T2Is that are used in control layers + // Collect all ids for enabled control adapter layers + const layerControlAdapterIds = state.regionalPrompts.present.layers + .filter(isControlAdapterLayer) + .filter((l) => l.isEnabled) + .map((l) => l.controlNetId); return intersectionWith(validT2IAdapters, layerControlAdapterIds, (a, b) => a.id === b); } else { // Else, we want to exclude the T2Is that are used in control layers + const layerControlAdapterIds = state.regionalPrompts.present.layers + .filter(isControlAdapterLayer) + .map((l) => l.controlNetId); return differenceWith(validT2IAdapters, layerControlAdapterIds, (a, b) => a.id === b); } }; From d14b315bc6a0b9e7ea7d40e24db528eab9f9e7dd Mon Sep 17 00:00:00 2001 From: psychedelicious <4822129+psychedelicious@users.noreply.github.com> Date: Tue, 30 Apr 2024 10:09:53 +1000 Subject: [PATCH 18/52] fix(ui): use optimal size when using control image dims --- .../controlAdapterOverrides/ControlAdapterImagePreview.tsx | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/invokeai/frontend/web/src/features/regionalPrompts/components/controlAdapterOverrides/ControlAdapterImagePreview.tsx b/invokeai/frontend/web/src/features/regionalPrompts/components/controlAdapterOverrides/ControlAdapterImagePreview.tsx index 3c6c32838a..8db8371cec 100644 --- a/invokeai/frontend/web/src/features/regionalPrompts/components/controlAdapterOverrides/ControlAdapterImagePreview.tsx +++ b/invokeai/frontend/web/src/features/regionalPrompts/components/controlAdapterOverrides/ControlAdapterImagePreview.tsx @@ -100,8 +100,8 @@ const ControlAdapterImagePreview = ({ isSmall, id }: Props) => { controlImage.width / controlImage.height, optimalDimension * optimalDimension ); - dispatch(widthChanged({ width: controlImage.width, updateAspectRatio: true })); - dispatch(heightChanged({ height: controlImage.height, updateAspectRatio: true })); + dispatch(widthChanged({ width, updateAspectRatio: true })); + dispatch(heightChanged({ height, updateAspectRatio: true })); } }, [controlImage, activeTabName, dispatch, optimalDimension]); From c1666a8b5a785c87c7a047f1385b7d4bddc9059b Mon Sep 17 00:00:00 2001 From: psychedelicious <4822129+psychedelicious@users.noreply.github.com> Date: Tue, 30 Apr 2024 10:37:45 +1000 Subject: [PATCH 19/52] fix(ui): select default control/ip adapter models in control layers --- .../regionalControlToControlAdapterBridge.ts | 67 ++++++++++++++++--- 1 file changed, 59 insertions(+), 8 deletions(-) diff --git a/invokeai/frontend/web/src/app/store/middleware/listenerMiddleware/listeners/regionalControlToControlAdapterBridge.ts b/invokeai/frontend/web/src/app/store/middleware/listenerMiddleware/listeners/regionalControlToControlAdapterBridge.ts index 2d80902550..6f53159d4b 100644 --- a/invokeai/frontend/web/src/app/store/middleware/listenerMiddleware/listeners/regionalControlToControlAdapterBridge.ts +++ b/invokeai/frontend/web/src/app/store/middleware/listenerMiddleware/listeners/regionalControlToControlAdapterBridge.ts @@ -1,6 +1,9 @@ import { createAction } from '@reduxjs/toolkit'; import type { AppStartListening } from 'app/store/middleware/listenerMiddleware'; +import { CONTROLNET_PROCESSORS } from 'features/controlAdapters/store/constants'; import { controlAdapterAdded, controlAdapterRemoved } from 'features/controlAdapters/store/controlAdaptersSlice'; +import type { ControlNetConfig, IPAdapterConfig } from 'features/controlAdapters/store/types'; +import { isControlAdapterProcessorType } from 'features/controlAdapters/store/types'; import { controlAdapterLayerAdded, ipAdapterLayerAdded, @@ -10,6 +13,8 @@ import { maskLayerIPAdapterDeleted, } from 'features/regionalPrompts/store/regionalPromptsSlice'; import type { Layer } from 'features/regionalPrompts/store/types'; +import { modelConfigsAdapterSelectors, modelsApi } from 'services/api/endpoints/models'; +import { isControlNetModelConfig, isIPAdapterModelConfig } from 'services/api/types'; import { assert } from 'tsafe'; import { v4 as uuidv4 } from 'uuid'; @@ -24,19 +29,52 @@ export const guidanceLayerIPAdapterDeleted = createAction<{ layerId: string; ipA export const addRegionalControlToControlAdapterBridge = (startAppListening: AppStartListening) => { startAppListening({ actionCreator: guidanceLayerAdded, - effect: (action, { dispatch }) => { + effect: (action, { dispatch, getState }) => { const type = action.payload; const layerId = uuidv4(); + if (type === 'masked_guidance_layer') { + dispatch(maskedGuidanceLayerAdded({ layerId })); + return; + } + + const state = getState(); + const baseModel = state.generation.model?.base; + const modelConfigs = modelsApi.endpoints.getModelConfigs.select(undefined)(state).data; + if (type === 'ip_adapter_layer') { const ipAdapterId = uuidv4(); - dispatch(controlAdapterAdded({ type: 'ip_adapter', overrides: { id: ipAdapterId } })); + const overrides: Partial = { + id: ipAdapterId, + }; + + // Find and select the first matching model + if (modelConfigs) { + const models = modelConfigsAdapterSelectors.selectAll(modelConfigs).filter(isIPAdapterModelConfig); + overrides.model = models.find((m) => m.base === baseModel) ?? null; + } + dispatch(controlAdapterAdded({ type: 'ip_adapter', overrides })); dispatch(ipAdapterLayerAdded({ layerId, ipAdapterId })); - } else if (type === 'control_adapter_layer') { + return; + } + + if (type === 'control_adapter_layer') { const controlNetId = uuidv4(); - dispatch(controlAdapterAdded({ type: 'controlnet', overrides: { id: controlNetId } })); + const overrides: Partial = { + id: controlNetId, + }; + + // Find and select the first matching model + if (modelConfigs) { + const models = modelConfigsAdapterSelectors.selectAll(modelConfigs).filter(isControlNetModelConfig); + const model = models.find((m) => m.base === baseModel) ?? null; + overrides.model = model; + const defaultPreprocessor = model?.default_settings?.preprocessor; + overrides.processorType = isControlAdapterProcessorType(defaultPreprocessor) ? defaultPreprocessor : 'none'; + overrides.processorNode = CONTROLNET_PROCESSORS[overrides.processorType].buildDefaults(baseModel); + } + dispatch(controlAdapterAdded({ type: 'controlnet', overrides })); dispatch(controlAdapterLayerAdded({ layerId, controlNetId })); - } else if (type === 'masked_guidance_layer') { - dispatch(maskedGuidanceLayerAdded({ layerId })); + return; } }, }); @@ -74,10 +112,23 @@ export const addRegionalControlToControlAdapterBridge = (startAppListening: AppS startAppListening({ actionCreator: guidanceLayerIPAdapterAdded, - effect: (action, { dispatch }) => { + effect: (action, { dispatch, getState }) => { const layerId = action.payload; const ipAdapterId = uuidv4(); - dispatch(controlAdapterAdded({ type: 'ip_adapter', overrides: { id: ipAdapterId } })); + const overrides: Partial = { + id: ipAdapterId, + }; + + // Find and select the first matching model + const state = getState(); + const baseModel = state.generation.model?.base; + const modelConfigs = modelsApi.endpoints.getModelConfigs.select(undefined)(state).data; + if (modelConfigs) { + const models = modelConfigsAdapterSelectors.selectAll(modelConfigs).filter(isIPAdapterModelConfig); + overrides.model = models.find((m) => m.base === baseModel) ?? null; + } + + dispatch(controlAdapterAdded({ type: 'ip_adapter', overrides })); dispatch(maskLayerIPAdapterAdded({ layerId, ipAdapterId })); }, }); From 629110784dda2037aa8aea238a7644c4ebc722e4 Mon Sep 17 00:00:00 2001 From: psychedelicious <4822129+psychedelicious@users.noreply.github.com> Date: Tue, 30 Apr 2024 10:40:23 +1000 Subject: [PATCH 20/52] fix(ui): delete control layers correctly --- .../regionalPrompts/components/RPLayerDeleteButton.tsx | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/invokeai/frontend/web/src/features/regionalPrompts/components/RPLayerDeleteButton.tsx b/invokeai/frontend/web/src/features/regionalPrompts/components/RPLayerDeleteButton.tsx index 237b710062..96aba546bc 100644 --- a/invokeai/frontend/web/src/features/regionalPrompts/components/RPLayerDeleteButton.tsx +++ b/invokeai/frontend/web/src/features/regionalPrompts/components/RPLayerDeleteButton.tsx @@ -1,6 +1,6 @@ import { IconButton } from '@invoke-ai/ui-library'; +import { guidanceLayerDeleted } from 'app/store/middleware/listenerMiddleware/listeners/regionalControlToControlAdapterBridge'; import { useAppDispatch } from 'app/store/storeHooks'; -import { layerDeleted } from 'features/regionalPrompts/store/regionalPromptsSlice'; import { memo, useCallback } from 'react'; import { useTranslation } from 'react-i18next'; import { PiTrashSimpleBold } from 'react-icons/pi'; @@ -11,7 +11,7 @@ export const RPLayerDeleteButton = memo(({ layerId }: Props) => { const { t } = useTranslation(); const dispatch = useAppDispatch(); const deleteLayer = useCallback(() => { - dispatch(layerDeleted(layerId)); + dispatch(guidanceLayerDeleted(layerId)); }, [dispatch, layerId]); return ( Date: Tue, 30 Apr 2024 10:48:35 +1000 Subject: [PATCH 21/52] feat(ui): make control layer ui exclusive to txt2img tab --- .../src/features/ui/components/InvokeTabs.tsx | 16 ++++- .../ui/components/ParametersPanel.tsx | 35 ++-------- .../components/ParametersPanelTextToImage.tsx | 70 +++++++++++++++++++ .../ui/components/tabs/ImageToImageTab.tsx | 10 ++- .../ui/components/tabs/TextToImageTab.tsx | 2 +- 5 files changed, 100 insertions(+), 33 deletions(-) create mode 100644 invokeai/frontend/web/src/features/ui/components/ParametersPanelTextToImage.tsx diff --git a/invokeai/frontend/web/src/features/ui/components/InvokeTabs.tsx b/invokeai/frontend/web/src/features/ui/components/InvokeTabs.tsx index cb49696dbf..9ac5324d41 100644 --- a/invokeai/frontend/web/src/features/ui/components/InvokeTabs.tsx +++ b/invokeai/frontend/web/src/features/ui/components/InvokeTabs.tsx @@ -11,6 +11,7 @@ import StatusIndicator from 'features/system/components/StatusIndicator'; import { selectConfigSlice } from 'features/system/store/configSlice'; import FloatingGalleryButton from 'features/ui/components/FloatingGalleryButton'; import FloatingParametersPanelButtons from 'features/ui/components/FloatingParametersPanelButtons'; +import ParametersPanelTextToImage from 'features/ui/components/ParametersPanelTextToImage'; import type { UsePanelOptions } from 'features/ui/hooks/usePanel'; import { usePanel } from 'features/ui/hooks/usePanel'; import { usePanelStorage } from 'features/ui/hooks/usePanelStorage'; @@ -249,7 +250,7 @@ const InvokeTabs = () => { onExpand={optionsPanel.onExpand} collapsible > - {activeTabName === 'nodes' ? : } + { }; export default memo(InvokeTabs); + +const ParametersPanelComponent = memo(() => { + const activeTabName = useAppSelector(activeTabNameSelector); + + if (activeTabName === 'nodes') { + return ; + } + if (activeTabName === 'txt2img') { + return ; + } + return ; +}); +ParametersPanelComponent.displayName = 'ParametersPanelComponent'; diff --git a/invokeai/frontend/web/src/features/ui/components/ParametersPanel.tsx b/invokeai/frontend/web/src/features/ui/components/ParametersPanel.tsx index bd76f5c924..b8d35976e3 100644 --- a/invokeai/frontend/web/src/features/ui/components/ParametersPanel.tsx +++ b/invokeai/frontend/web/src/features/ui/components/ParametersPanel.tsx @@ -1,10 +1,8 @@ -import { Box, Flex, Tab, TabList, TabPanel, TabPanels, Tabs } from '@invoke-ai/ui-library'; +import { Box, Flex } from '@invoke-ai/ui-library'; import { useAppSelector } from 'app/store/storeHooks'; import { overlayScrollbarsParams } from 'common/components/OverlayScrollbars/constants'; import { Prompts } from 'features/parameters/components/Prompts/Prompts'; import QueueControls from 'features/queue/components/QueueControls'; -import { RegionalPromptsPanelContent } from 'features/regionalPrompts/components/RegionalPromptsPanelContent'; -import { useRegionalControlTitle } from 'features/regionalPrompts/hooks/useRegionalControlTitle'; import { SDXLPrompts } from 'features/sdxl/components/SDXLPrompts/SDXLPrompts'; import { AdvancedSettingsAccordion } from 'features/settingsAccordions/components/AdvancedSettingsAccordion/AdvancedSettingsAccordion'; import { CompositingSettingsAccordion } from 'features/settingsAccordions/components/CompositingSettingsAccordion/CompositingSettingsAccordion'; @@ -16,7 +14,6 @@ import { activeTabNameSelector } from 'features/ui/store/uiSelectors'; import { OverlayScrollbarsComponent } from 'overlayscrollbars-react'; import type { CSSProperties } from 'react'; import { memo } from 'react'; -import { useTranslation } from 'react-i18next'; const overlayScrollbarsStyles: CSSProperties = { height: '100%', @@ -24,9 +21,7 @@ const overlayScrollbarsStyles: CSSProperties = { }; const ParametersPanel = () => { - const { t } = useTranslation(); const activeTabName = useAppSelector(activeTabNameSelector); - const regionalControlTitle = useRegionalControlTitle(); const isSDXL = useAppSelector((s) => s.generation.model?.base === 'sdxl'); return ( @@ -37,28 +32,12 @@ const ParametersPanel = () => { {isSDXL ? : } - - - {t('parameters.globalSettings')} - {regionalControlTitle} - - - - - - - - {activeTabName !== 'txt2img' && } - {activeTabName === 'unifiedCanvas' && } - {isSDXL && } - - - - - - - - + + + {activeTabName !== 'txt2img' && } + {activeTabName === 'unifiedCanvas' && } + {isSDXL && } + diff --git a/invokeai/frontend/web/src/features/ui/components/ParametersPanelTextToImage.tsx b/invokeai/frontend/web/src/features/ui/components/ParametersPanelTextToImage.tsx new file mode 100644 index 0000000000..d8a4a6fec4 --- /dev/null +++ b/invokeai/frontend/web/src/features/ui/components/ParametersPanelTextToImage.tsx @@ -0,0 +1,70 @@ +import { Box, Flex, Tab, TabList, TabPanel, TabPanels, Tabs } from '@invoke-ai/ui-library'; +import { useAppSelector } from 'app/store/storeHooks'; +import { overlayScrollbarsParams } from 'common/components/OverlayScrollbars/constants'; +import { Prompts } from 'features/parameters/components/Prompts/Prompts'; +import QueueControls from 'features/queue/components/QueueControls'; +import { RegionalPromptsPanelContent } from 'features/regionalPrompts/components/RegionalPromptsPanelContent'; +import { useRegionalControlTitle } from 'features/regionalPrompts/hooks/useRegionalControlTitle'; +import { SDXLPrompts } from 'features/sdxl/components/SDXLPrompts/SDXLPrompts'; +import { AdvancedSettingsAccordion } from 'features/settingsAccordions/components/AdvancedSettingsAccordion/AdvancedSettingsAccordion'; +import { CompositingSettingsAccordion } from 'features/settingsAccordions/components/CompositingSettingsAccordion/CompositingSettingsAccordion'; +import { ControlSettingsAccordion } from 'features/settingsAccordions/components/ControlSettingsAccordion/ControlSettingsAccordion'; +import { GenerationSettingsAccordion } from 'features/settingsAccordions/components/GenerationSettingsAccordion/GenerationSettingsAccordion'; +import { ImageSettingsAccordion } from 'features/settingsAccordions/components/ImageSettingsAccordion/ImageSettingsAccordion'; +import { RefinerSettingsAccordion } from 'features/settingsAccordions/components/RefinerSettingsAccordion/RefinerSettingsAccordion'; +import { activeTabNameSelector } from 'features/ui/store/uiSelectors'; +import { OverlayScrollbarsComponent } from 'overlayscrollbars-react'; +import type { CSSProperties } from 'react'; +import { memo } from 'react'; +import { useTranslation } from 'react-i18next'; + +const overlayScrollbarsStyles: CSSProperties = { + height: '100%', + width: '100%', +}; + +const ParametersPanelTextToImage = () => { + const { t } = useTranslation(); + const activeTabName = useAppSelector(activeTabNameSelector); + const regionalControlTitle = useRegionalControlTitle(); + const isSDXL = useAppSelector((s) => s.generation.model?.base === 'sdxl'); + + return ( + + + + + + + {isSDXL ? : } + + + {t('parameters.globalSettings')} + {regionalControlTitle} + + + + + + + + {activeTabName !== 'txt2img' && } + {activeTabName === 'unifiedCanvas' && } + {isSDXL && } + + + + + + + + + + + + + + ); +}; + +export default memo(ParametersPanelTextToImage); diff --git a/invokeai/frontend/web/src/features/ui/components/tabs/ImageToImageTab.tsx b/invokeai/frontend/web/src/features/ui/components/tabs/ImageToImageTab.tsx index dcacdbdff4..07e87d202c 100644 --- a/invokeai/frontend/web/src/features/ui/components/tabs/ImageToImageTab.tsx +++ b/invokeai/frontend/web/src/features/ui/components/tabs/ImageToImageTab.tsx @@ -1,7 +1,7 @@ -import { Box } from '@invoke-ai/ui-library'; +import { Box, Flex } from '@invoke-ai/ui-library'; +import CurrentImageDisplay from 'features/gallery/components/CurrentImage/CurrentImageDisplay'; import InitialImageDisplay from 'features/parameters/components/ImageToImage/InitialImageDisplay'; import ResizeHandle from 'features/ui/components/tabs/ResizeHandle'; -import TextToImageTabMain from 'features/ui/components/tabs/TextToImageTab'; import { usePanelStorage } from 'features/ui/hooks/usePanelStorage'; import type { CSSProperties } from 'react'; import { memo, useCallback, useRef } from 'react'; @@ -42,7 +42,11 @@ const ImageToImageTab = () => { - + + + + + diff --git a/invokeai/frontend/web/src/features/ui/components/tabs/TextToImageTab.tsx b/invokeai/frontend/web/src/features/ui/components/tabs/TextToImageTab.tsx index 2a79c9b9d1..082445fbb3 100644 --- a/invokeai/frontend/web/src/features/ui/components/tabs/TextToImageTab.tsx +++ b/invokeai/frontend/web/src/features/ui/components/tabs/TextToImageTab.tsx @@ -10,7 +10,7 @@ const TextToImageTab = () => { const regionalControlTitle = useRegionalControlTitle(); return ( - + {t('common.viewer')} From d884c15d0cb6594e8070f5e83db5232885e1b47e Mon Sep 17 00:00:00 2001 From: psychedelicious <4822129+psychedelicious@users.noreply.github.com> Date: Tue, 30 Apr 2024 11:23:00 +1000 Subject: [PATCH 22/52] feat(ui): update layer menus --- .../ControlAdapterLayerListItem.tsx | 2 + .../components/IPAdapterLayerListItem.tsx | 2 + .../components/RPLayerMenu.tsx | 118 ++++-------------- .../components/RPLayerMenuArrangeActions.tsx | 74 +++++++++++ .../RPLayerMenuMaskedGuidanceActions.tsx | 58 +++++++++ 5 files changed, 161 insertions(+), 93 deletions(-) create mode 100644 invokeai/frontend/web/src/features/regionalPrompts/components/RPLayerMenuArrangeActions.tsx create mode 100644 invokeai/frontend/web/src/features/regionalPrompts/components/RPLayerMenuMaskedGuidanceActions.tsx diff --git a/invokeai/frontend/web/src/features/regionalPrompts/components/ControlAdapterLayerListItem.tsx b/invokeai/frontend/web/src/features/regionalPrompts/components/ControlAdapterLayerListItem.tsx index 5f78843aab..ef94b4c75a 100644 --- a/invokeai/frontend/web/src/features/regionalPrompts/components/ControlAdapterLayerListItem.tsx +++ b/invokeai/frontend/web/src/features/regionalPrompts/components/ControlAdapterLayerListItem.tsx @@ -4,6 +4,7 @@ import { useAppDispatch, useAppSelector } from 'app/store/storeHooks'; import ControlAdapterLayerConfig from 'features/regionalPrompts/components/controlAdapterOverrides/ControlAdapterLayerConfig'; import { LayerTitle } from 'features/regionalPrompts/components/LayerTitle'; import { RPLayerDeleteButton } from 'features/regionalPrompts/components/RPLayerDeleteButton'; +import { RPLayerMenu } from 'features/regionalPrompts/components/RPLayerMenu'; import { RPLayerVisibilityToggle } from 'features/regionalPrompts/components/RPLayerVisibilityToggle'; import { isControlAdapterLayer, @@ -51,6 +52,7 @@ export const ControlAdapterLayerListItem = memo(({ layerId }: Props) => { + diff --git a/invokeai/frontend/web/src/features/regionalPrompts/components/IPAdapterLayerListItem.tsx b/invokeai/frontend/web/src/features/regionalPrompts/components/IPAdapterLayerListItem.tsx index bf4245f26b..f94865c305 100644 --- a/invokeai/frontend/web/src/features/regionalPrompts/components/IPAdapterLayerListItem.tsx +++ b/invokeai/frontend/web/src/features/regionalPrompts/components/IPAdapterLayerListItem.tsx @@ -4,6 +4,7 @@ import { useAppDispatch, useAppSelector } from 'app/store/storeHooks'; import ControlAdapterLayerConfig from 'features/regionalPrompts/components/controlAdapterOverrides/ControlAdapterLayerConfig'; import { LayerTitle } from 'features/regionalPrompts/components/LayerTitle'; import { RPLayerDeleteButton } from 'features/regionalPrompts/components/RPLayerDeleteButton'; +import { RPLayerMenu } from 'features/regionalPrompts/components/RPLayerMenu'; import { RPLayerVisibilityToggle } from 'features/regionalPrompts/components/RPLayerVisibilityToggle'; import { isIPAdapterLayer, @@ -51,6 +52,7 @@ export const IPAdapterLayerListItem = memo(({ layerId }: Props) => { + diff --git a/invokeai/frontend/web/src/features/regionalPrompts/components/RPLayerMenu.tsx b/invokeai/frontend/web/src/features/regionalPrompts/components/RPLayerMenu.tsx index ebfa399227..16619e924e 100644 --- a/invokeai/frontend/web/src/features/regionalPrompts/components/RPLayerMenu.tsx +++ b/invokeai/frontend/web/src/features/regionalPrompts/components/RPLayerMenu.tsx @@ -1,78 +1,19 @@ import { IconButton, Menu, MenuButton, MenuDivider, MenuItem, MenuList } from '@invoke-ai/ui-library'; -import { createMemoizedSelector } from 'app/store/createMemoizedSelector'; -import { guidanceLayerIPAdapterAdded } from 'app/store/middleware/listenerMiddleware/listeners/regionalControlToControlAdapterBridge'; -import { useAppDispatch, useAppSelector } from 'app/store/storeHooks'; -import { - isMaskedGuidanceLayer, - layerDeleted, - layerMovedBackward, - layerMovedForward, - layerMovedToBack, - layerMovedToFront, - layerReset, - maskLayerNegativePromptChanged, - maskLayerPositivePromptChanged, - selectRegionalPromptsSlice, -} from 'features/regionalPrompts/store/regionalPromptsSlice'; -import { memo, useCallback, useMemo } from 'react'; +import { useAppDispatch } from 'app/store/storeHooks'; +import { RPLayerMenuArrangeActions } from 'features/regionalPrompts/components/RPLayerMenuArrangeActions'; +import { RPLayerMenuMaskedGuidanceActions } from 'features/regionalPrompts/components/RPLayerMenuMaskedGuidanceActions'; +import { useLayerType } from 'features/regionalPrompts/hooks/layerStateHooks'; +import { layerDeleted, layerReset } from 'features/regionalPrompts/store/regionalPromptsSlice'; +import { memo, useCallback } from 'react'; import { useTranslation } from 'react-i18next'; -import { - PiArrowCounterClockwiseBold, - PiArrowDownBold, - PiArrowLineDownBold, - PiArrowLineUpBold, - PiArrowUpBold, - PiDotsThreeVerticalBold, - PiPlusBold, - PiTrashSimpleBold, -} from 'react-icons/pi'; -import { assert } from 'tsafe'; +import { PiArrowCounterClockwiseBold, PiDotsThreeVerticalBold, PiTrashSimpleBold } from 'react-icons/pi'; type Props = { layerId: string }; export const RPLayerMenu = memo(({ layerId }: Props) => { const dispatch = useAppDispatch(); const { t } = useTranslation(); - const selectValidActions = useMemo( - () => - createMemoizedSelector(selectRegionalPromptsSlice, (regionalPrompts) => { - const layer = regionalPrompts.present.layers.find((l) => l.id === layerId); - assert(isMaskedGuidanceLayer(layer), `Layer ${layerId} not found or not an RP layer`); - const layerIndex = regionalPrompts.present.layers.findIndex((l) => l.id === layerId); - const layerCount = regionalPrompts.present.layers.length; - return { - canAddPositivePrompt: layer.positivePrompt === null, - canAddNegativePrompt: layer.negativePrompt === null, - canMoveForward: layerIndex < layerCount - 1, - canMoveBackward: layerIndex > 0, - canMoveToFront: layerIndex < layerCount - 1, - canMoveToBack: layerIndex > 0, - }; - }), - [layerId] - ); - const validActions = useAppSelector(selectValidActions); - const addPositivePrompt = useCallback(() => { - dispatch(maskLayerPositivePromptChanged({ layerId, prompt: '' })); - }, [dispatch, layerId]); - const addNegativePrompt = useCallback(() => { - dispatch(maskLayerNegativePromptChanged({ layerId, prompt: '' })); - }, [dispatch, layerId]); - const addIPAdapter = useCallback(() => { - dispatch(guidanceLayerIPAdapterAdded(layerId)); - }, [dispatch, layerId]); - const moveForward = useCallback(() => { - dispatch(layerMovedForward(layerId)); - }, [dispatch, layerId]); - const moveToFront = useCallback(() => { - dispatch(layerMovedToFront(layerId)); - }, [dispatch, layerId]); - const moveBackward = useCallback(() => { - dispatch(layerMovedBackward(layerId)); - }, [dispatch, layerId]); - const moveToBack = useCallback(() => { - dispatch(layerMovedToBack(layerId)); - }, [dispatch, layerId]); + const layerType = useLayerType(layerId); const resetLayer = useCallback(() => { dispatch(layerReset(layerId)); }, [dispatch, layerId]); @@ -83,32 +24,23 @@ export const RPLayerMenu = memo(({ layerId }: Props) => { } /> - }> - {t('regionalPrompts.addPositivePrompt')} - - }> - {t('regionalPrompts.addNegativePrompt')} - - }> - {t('regionalPrompts.addIPAdapter')} - - - }> - {t('regionalPrompts.moveToFront')} - - }> - {t('regionalPrompts.moveForward')} - - }> - {t('regionalPrompts.moveBackward')} - - }> - {t('regionalPrompts.moveToBack')} - - - }> - {t('accessibility.reset')} - + {layerType === 'masked_guidance_layer' && ( + <> + + + + )} + {(layerType === 'masked_guidance_layer' || layerType === 'control_adapter_layer') && ( + <> + + + + )} + {layerType === 'masked_guidance_layer' && ( + }> + {t('accessibility.reset')} + + )} } color="error.300"> {t('common.delete')} diff --git a/invokeai/frontend/web/src/features/regionalPrompts/components/RPLayerMenuArrangeActions.tsx b/invokeai/frontend/web/src/features/regionalPrompts/components/RPLayerMenuArrangeActions.tsx new file mode 100644 index 0000000000..e3fe918ff0 --- /dev/null +++ b/invokeai/frontend/web/src/features/regionalPrompts/components/RPLayerMenuArrangeActions.tsx @@ -0,0 +1,74 @@ +import { MenuItem } from '@invoke-ai/ui-library'; +import { createMemoizedSelector } from 'app/store/createMemoizedSelector'; +import { useAppDispatch, useAppSelector } from 'app/store/storeHooks'; +import { + isRenderableLayer, + layerMovedBackward, + layerMovedForward, + layerMovedToBack, + layerMovedToFront, + selectRegionalPromptsSlice, +} from 'features/regionalPrompts/store/regionalPromptsSlice'; +import { memo, useCallback, useMemo } from 'react'; +import { useTranslation } from 'react-i18next'; +import { + PiArrowDownBold, + PiArrowLineDownBold, + PiArrowLineUpBold, + PiArrowUpBold, +} from 'react-icons/pi'; +import { assert } from 'tsafe'; + +type Props = { layerId: string }; + +export const RPLayerMenuArrangeActions = memo(({ layerId }: Props) => { + const dispatch = useAppDispatch(); + const { t } = useTranslation(); + const selectValidActions = useMemo( + () => + createMemoizedSelector(selectRegionalPromptsSlice, (regionalPrompts) => { + const layer = regionalPrompts.present.layers.find((l) => l.id === layerId); + assert(isRenderableLayer(layer), `Layer ${layerId} not found or not an RP layer`); + const layerIndex = regionalPrompts.present.layers.findIndex((l) => l.id === layerId); + const layerCount = regionalPrompts.present.layers.length; + return { + canMoveForward: layerIndex < layerCount - 1, + canMoveBackward: layerIndex > 0, + canMoveToFront: layerIndex < layerCount - 1, + canMoveToBack: layerIndex > 0, + }; + }), + [layerId] + ); + const validActions = useAppSelector(selectValidActions); + const moveForward = useCallback(() => { + dispatch(layerMovedForward(layerId)); + }, [dispatch, layerId]); + const moveToFront = useCallback(() => { + dispatch(layerMovedToFront(layerId)); + }, [dispatch, layerId]); + const moveBackward = useCallback(() => { + dispatch(layerMovedBackward(layerId)); + }, [dispatch, layerId]); + const moveToBack = useCallback(() => { + dispatch(layerMovedToBack(layerId)); + }, [dispatch, layerId]); + return ( + <> + }> + {t('regionalPrompts.moveToFront')} + + }> + {t('regionalPrompts.moveForward')} + + }> + {t('regionalPrompts.moveBackward')} + + }> + {t('regionalPrompts.moveToBack')} + + + ); +}); + +RPLayerMenuArrangeActions.displayName = 'RPLayerMenuArrangeActions'; diff --git a/invokeai/frontend/web/src/features/regionalPrompts/components/RPLayerMenuMaskedGuidanceActions.tsx b/invokeai/frontend/web/src/features/regionalPrompts/components/RPLayerMenuMaskedGuidanceActions.tsx new file mode 100644 index 0000000000..542f08c379 --- /dev/null +++ b/invokeai/frontend/web/src/features/regionalPrompts/components/RPLayerMenuMaskedGuidanceActions.tsx @@ -0,0 +1,58 @@ +import { MenuItem } from '@invoke-ai/ui-library'; +import { createMemoizedSelector } from 'app/store/createMemoizedSelector'; +import { guidanceLayerIPAdapterAdded } from 'app/store/middleware/listenerMiddleware/listeners/regionalControlToControlAdapterBridge'; +import { useAppDispatch, useAppSelector } from 'app/store/storeHooks'; +import { + isMaskedGuidanceLayer, + maskLayerNegativePromptChanged, + maskLayerPositivePromptChanged, + selectRegionalPromptsSlice, +} from 'features/regionalPrompts/store/regionalPromptsSlice'; +import { memo, useCallback, useMemo } from 'react'; +import { useTranslation } from 'react-i18next'; +import { PiPlusBold } from 'react-icons/pi'; +import { assert } from 'tsafe'; + +type Props = { layerId: string }; + +export const RPLayerMenuMaskedGuidanceActions = memo(({ layerId }: Props) => { + const dispatch = useAppDispatch(); + const { t } = useTranslation(); + const selectValidActions = useMemo( + () => + createMemoizedSelector(selectRegionalPromptsSlice, (regionalPrompts) => { + const layer = regionalPrompts.present.layers.find((l) => l.id === layerId); + assert(isMaskedGuidanceLayer(layer), `Layer ${layerId} not found or not an RP layer`); + return { + canAddPositivePrompt: layer.positivePrompt === null, + canAddNegativePrompt: layer.negativePrompt === null, + }; + }), + [layerId] + ); + const validActions = useAppSelector(selectValidActions); + const addPositivePrompt = useCallback(() => { + dispatch(maskLayerPositivePromptChanged({ layerId, prompt: '' })); + }, [dispatch, layerId]); + const addNegativePrompt = useCallback(() => { + dispatch(maskLayerNegativePromptChanged({ layerId, prompt: '' })); + }, [dispatch, layerId]); + const addIPAdapter = useCallback(() => { + dispatch(guidanceLayerIPAdapterAdded(layerId)); + }, [dispatch, layerId]); + return ( + <> + }> + {t('regionalPrompts.addPositivePrompt')} + + }> + {t('regionalPrompts.addNegativePrompt')} + + }> + {t('regionalPrompts.addIPAdapter')} + + + ); +}); + +RPLayerMenuMaskedGuidanceActions.displayName = 'RPLayerMenuMaskedGuidanceActions'; From e5ec529f0fcabac342d4a982eccf284ab7d91ab2 Mon Sep 17 00:00:00 2001 From: psychedelicious <4822129+psychedelicious@users.noreply.github.com> Date: Tue, 30 Apr 2024 11:23:11 +1000 Subject: [PATCH 23/52] feat(ui): fix layer arranging --- .../RegionalPromptsPanelContent.tsx | 62 ++++++++----------- .../regionalPrompts/hooks/layerStateHooks.ts | 19 ++++-- .../store/regionalPromptsSlice.ts | 18 ++++-- 3 files changed, 54 insertions(+), 45 deletions(-) diff --git a/invokeai/frontend/web/src/features/regionalPrompts/components/RegionalPromptsPanelContent.tsx b/invokeai/frontend/web/src/features/regionalPrompts/components/RegionalPromptsPanelContent.tsx index df422a5336..1a73202986 100644 --- a/invokeai/frontend/web/src/features/regionalPrompts/components/RegionalPromptsPanelContent.tsx +++ b/invokeai/frontend/web/src/features/regionalPrompts/components/RegionalPromptsPanelContent.tsx @@ -8,39 +8,16 @@ import { ControlAdapterLayerListItem } from 'features/regionalPrompts/components import { DeleteAllLayersButton } from 'features/regionalPrompts/components/DeleteAllLayersButton'; import { IPAdapterLayerListItem } from 'features/regionalPrompts/components/IPAdapterLayerListItem'; import { MaskedGuidanceLayerListItem } from 'features/regionalPrompts/components/MaskedGuidanceLayerListItem'; -import { - isControlAdapterLayer, - isIPAdapterLayer, - isMaskedGuidanceLayer, - selectRegionalPromptsSlice, -} from 'features/regionalPrompts/store/regionalPromptsSlice'; +import { selectRegionalPromptsSlice } from 'features/regionalPrompts/store/regionalPromptsSlice'; +import type { Layer } from 'features/regionalPrompts/store/types'; import { memo } from 'react'; -const selectMaskedGuidanceLayerIds = createMemoizedSelector(selectRegionalPromptsSlice, (regionalPrompts) => - regionalPrompts.present.layers - .filter(isMaskedGuidanceLayer) - .map((l) => l.id) - .reverse() -); - -const selectControlNetLayerIds = createMemoizedSelector(selectRegionalPromptsSlice, (regionalPrompts) => - regionalPrompts.present.layers - .filter(isControlAdapterLayer) - .map((l) => l.id) - .reverse() -); - -const selectIPAdapterLayerIds = createMemoizedSelector(selectRegionalPromptsSlice, (regionalPrompts) => - regionalPrompts.present.layers - .filter(isIPAdapterLayer) - .map((l) => l.id) - .reverse() +const selectLayerIdTypePairs = createMemoizedSelector(selectRegionalPromptsSlice, (regionalPrompts) => + regionalPrompts.present.layers.map((l) => ({ id: l.id, type: l.type })).reverse() ); export const RegionalPromptsPanelContent = memo(() => { - const maskedGuidanceLayerIds = useAppSelector(selectMaskedGuidanceLayerIds); - const controlNetLayerIds = useAppSelector(selectControlNetLayerIds); - const ipAdapterLayerIds = useAppSelector(selectIPAdapterLayerIds); + const layerIdTypePairs = useAppSelector(selectLayerIdTypePairs); return ( @@ -49,14 +26,8 @@ export const RegionalPromptsPanelContent = memo(() => { - {maskedGuidanceLayerIds.map((id) => ( - - ))} - {controlNetLayerIds.map((id) => ( - - ))} - {ipAdapterLayerIds.map((id) => ( - + {layerIdTypePairs.map(({ id, type }) => ( + ))} @@ -65,3 +36,22 @@ export const RegionalPromptsPanelContent = memo(() => { }); RegionalPromptsPanelContent.displayName = 'RegionalPromptsPanelContent'; + +type LayerWrapperProps = { + id: string; + type: Layer['type']; +}; + +const LayerWrapper = memo(({ id, type }: LayerWrapperProps) => { + if (type === 'masked_guidance_layer') { + return ; + } + if (type === 'control_adapter_layer') { + return ; + } + if (type === 'ip_adapter_layer') { + return ; + } +}); + +LayerWrapper.displayName = 'LayerWrapper'; diff --git a/invokeai/frontend/web/src/features/regionalPrompts/hooks/layerStateHooks.ts b/invokeai/frontend/web/src/features/regionalPrompts/hooks/layerStateHooks.ts index 5ef1014dcc..a8fd34d50f 100644 --- a/invokeai/frontend/web/src/features/regionalPrompts/hooks/layerStateHooks.ts +++ b/invokeai/frontend/web/src/features/regionalPrompts/hooks/layerStateHooks.ts @@ -1,9 +1,6 @@ import { createSelector } from '@reduxjs/toolkit'; import { useAppSelector } from 'app/store/storeHooks'; -import { - isMaskedGuidanceLayer, - selectRegionalPromptsSlice, -} from 'features/regionalPrompts/store/regionalPromptsSlice'; +import { isMaskedGuidanceLayer, selectRegionalPromptsSlice } from 'features/regionalPrompts/store/regionalPromptsSlice'; import { useMemo } from 'react'; import { assert } from 'tsafe'; @@ -50,3 +47,17 @@ export const useLayerIsVisible = (layerId: string) => { const isVisible = useAppSelector(selectLayer); return isVisible; }; + +export const useLayerType = (layerId: string) => { + const selectLayer = useMemo( + () => + createSelector(selectRegionalPromptsSlice, (regionalPrompts) => { + const layer = regionalPrompts.present.layers.find((l) => l.id === layerId); + assert(layer, `Layer ${layerId} not found`); + return layer.type; + }), + [layerId] + ); + const type = useAppSelector(selectLayer); + return type; +}; diff --git a/invokeai/frontend/web/src/features/regionalPrompts/store/regionalPromptsSlice.ts b/invokeai/frontend/web/src/features/regionalPrompts/store/regionalPromptsSlice.ts index 4128cad637..2c982565f9 100644 --- a/invokeai/frontend/web/src/features/regionalPrompts/store/regionalPromptsSlice.ts +++ b/invokeai/frontend/web/src/features/regionalPrompts/store/regionalPromptsSlice.ts @@ -16,7 +16,7 @@ import { modelChanged } from 'features/parameters/store/generationSlice'; import type { ParameterAutoNegative } from 'features/parameters/types/parameterSchemas'; import { getIsSizeOptimal, getOptimalDimension } from 'features/parameters/util/optimalDimension'; import type { IRect, Vector2d } from 'konva/lib/types'; -import { isEqual } from 'lodash-es'; +import { isEqual, partition } from 'lodash-es'; import { atom } from 'nanostores'; import type { RgbColor } from 'react-colorful'; import type { UndoableOptions } from 'redux-undo'; @@ -183,21 +183,29 @@ export const regionalPromptsSlice = createSlice({ }, layerMovedForward: (state, action: PayloadAction) => { const cb = (l: Layer) => l.id === action.payload; - moveForward(state.layers, cb); + const [renderableLayers, ipAdapterLayers] = partition(state.layers, isRenderableLayer); + moveForward(renderableLayers, cb); + state.layers = [...ipAdapterLayers, ...renderableLayers]; }, layerMovedToFront: (state, action: PayloadAction) => { const cb = (l: Layer) => l.id === action.payload; + const [renderableLayers, ipAdapterLayers] = partition(state.layers, isRenderableLayer); // Because the layers are in reverse order, moving to the front is equivalent to moving to the back - moveToBack(state.layers, cb); + moveToBack(renderableLayers, cb); + state.layers = [...ipAdapterLayers, ...renderableLayers]; }, layerMovedBackward: (state, action: PayloadAction) => { const cb = (l: Layer) => l.id === action.payload; - moveBackward(state.layers, cb); + const [renderableLayers, ipAdapterLayers] = partition(state.layers, isRenderableLayer); + moveBackward(renderableLayers, cb); + state.layers = [...ipAdapterLayers, ...renderableLayers]; }, layerMovedToBack: (state, action: PayloadAction) => { const cb = (l: Layer) => l.id === action.payload; + const [renderableLayers, ipAdapterLayers] = partition(state.layers, isRenderableLayer); // Because the layers are in reverse order, moving to the back is equivalent to moving to the front - moveToFront(state.layers, cb); + moveToFront(renderableLayers, cb); + state.layers = [...ipAdapterLayers, ...renderableLayers]; }, selectedLayerReset: (state) => { const layer = state.layers.find((l) => l.id === state.selectedLayerId); From 048bd18e107c68304e07a78d7cce1135bf739f52 Mon Sep 17 00:00:00 2001 From: psychedelicious <4822129+psychedelicious@users.noreply.github.com> Date: Tue, 30 Apr 2024 11:57:16 +1000 Subject: [PATCH 24/52] feat(ui): separate ca layer opacity --- invokeai/frontend/web/public/locales/en.json | 3 +- .../components/CALayerOpacity.tsx | 81 +++++++++++++++++++ .../ControlAdapterLayerListItem.tsx | 2 + .../regionalPrompts/hooks/layerStateHooks.ts | 20 ++++- 4 files changed, 104 insertions(+), 2 deletions(-) create mode 100644 invokeai/frontend/web/src/features/regionalPrompts/components/CALayerOpacity.tsx diff --git a/invokeai/frontend/web/public/locales/en.json b/invokeai/frontend/web/public/locales/en.json index d901c4be74..3a6f88e404 100644 --- a/invokeai/frontend/web/public/locales/en.json +++ b/invokeai/frontend/web/public/locales/en.json @@ -1536,6 +1536,7 @@ "maskedGuidance": "Masked Guidance", "maskedGuidanceLayer": "$t(regionalPrompts.maskedGuidance) $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" } } diff --git a/invokeai/frontend/web/src/features/regionalPrompts/components/CALayerOpacity.tsx b/invokeai/frontend/web/src/features/regionalPrompts/components/CALayerOpacity.tsx new file mode 100644 index 0000000000..9d7649be2e --- /dev/null +++ b/invokeai/frontend/web/src/features/regionalPrompts/components/CALayerOpacity.tsx @@ -0,0 +1,81 @@ +import { + CompositeNumberInput, + CompositeSlider, + Flex, + FormControl, + FormLabel, + IconButton, + Popover, + PopoverArrow, + PopoverBody, + PopoverContent, + PopoverTrigger, +} from '@invoke-ai/ui-library'; +import { useAppDispatch } from 'app/store/storeHooks'; +import { useLayerOpacity } from 'features/regionalPrompts/hooks/layerStateHooks'; +import { layerOpacityChanged } from 'features/regionalPrompts/store/regionalPromptsSlice'; +import { memo, useCallback } from 'react'; +import { useTranslation } from 'react-i18next'; +import { PiDropHalfFill } from 'react-icons/pi'; + +type Props = { + layerId: string; +}; + +const marks = [0, 25, 50, 75, 100]; +const formatPct = (v: number | string) => `${v} %`; + +const CALayerOpacity = ({ layerId }: Props) => { + const { t } = useTranslation(); + const dispatch = useAppDispatch(); + const opacity = useLayerOpacity(layerId); + const onChange = useCallback( + (v: number) => { + dispatch(layerOpacityChanged({ layerId, opacity: v / 100 })); + }, + [dispatch, layerId] + ); + return ( + + + } + variant="ghost" + /> + + + + + + + {t('regionalPrompts.opacity')} + + + + + + + + ); +}; + +export default memo(CALayerOpacity); diff --git a/invokeai/frontend/web/src/features/regionalPrompts/components/ControlAdapterLayerListItem.tsx b/invokeai/frontend/web/src/features/regionalPrompts/components/ControlAdapterLayerListItem.tsx index ef94b4c75a..f9ec37e159 100644 --- a/invokeai/frontend/web/src/features/regionalPrompts/components/ControlAdapterLayerListItem.tsx +++ b/invokeai/frontend/web/src/features/regionalPrompts/components/ControlAdapterLayerListItem.tsx @@ -1,6 +1,7 @@ import { Flex, Spacer } from '@invoke-ai/ui-library'; import { createMemoizedSelector } from 'app/store/createMemoizedSelector'; import { useAppDispatch, useAppSelector } from 'app/store/storeHooks'; +import CALayerOpacity from 'features/regionalPrompts/components/CALayerOpacity'; import ControlAdapterLayerConfig from 'features/regionalPrompts/components/controlAdapterOverrides/ControlAdapterLayerConfig'; import { LayerTitle } from 'features/regionalPrompts/components/LayerTitle'; import { RPLayerDeleteButton } from 'features/regionalPrompts/components/RPLayerDeleteButton'; @@ -52,6 +53,7 @@ export const ControlAdapterLayerListItem = memo(({ layerId }: Props) => { + diff --git a/invokeai/frontend/web/src/features/regionalPrompts/hooks/layerStateHooks.ts b/invokeai/frontend/web/src/features/regionalPrompts/hooks/layerStateHooks.ts index a8fd34d50f..dd0042431c 100644 --- a/invokeai/frontend/web/src/features/regionalPrompts/hooks/layerStateHooks.ts +++ b/invokeai/frontend/web/src/features/regionalPrompts/hooks/layerStateHooks.ts @@ -1,6 +1,10 @@ import { createSelector } from '@reduxjs/toolkit'; import { useAppSelector } from 'app/store/storeHooks'; -import { isMaskedGuidanceLayer, selectRegionalPromptsSlice } from 'features/regionalPrompts/store/regionalPromptsSlice'; +import { + isControlAdapterLayer, + isMaskedGuidanceLayer, + selectRegionalPromptsSlice, +} from 'features/regionalPrompts/store/regionalPromptsSlice'; import { useMemo } from 'react'; import { assert } from 'tsafe'; @@ -61,3 +65,17 @@ export const useLayerType = (layerId: string) => { const type = useAppSelector(selectLayer); return type; }; + +export const useLayerOpacity = (layerId: string) => { + const selectLayer = useMemo( + () => + createSelector(selectRegionalPromptsSlice, (regionalPrompts) => { + const layer = regionalPrompts.present.layers.filter(isControlAdapterLayer).find((l) => l.id === layerId); + assert(layer, `Layer ${layerId} not found`); + return Math.round(layer.opacity * 100); + }), + [layerId] + ); + const opacity = useAppSelector(selectLayer); + return opacity; +}; From 1f68a60752940c77cd4312a0f716437c4216e9ed Mon Sep 17 00:00:00 2001 From: psychedelicious <4822129+psychedelicious@users.noreply.github.com> Date: Tue, 30 Apr 2024 12:02:24 +1000 Subject: [PATCH 25/52] feat(ui): hold shift to use control image size w/o model constraints --- invokeai/frontend/web/public/locales/en.json | 3 ++- .../ControlAdapterImagePreview.tsx | 25 ++++++++++++------- 2 files changed, 18 insertions(+), 10 deletions(-) diff --git a/invokeai/frontend/web/public/locales/en.json b/invokeai/frontend/web/public/locales/en.json index 3a6f88e404..9d93ebed8d 100644 --- a/invokeai/frontend/web/public/locales/en.json +++ b/invokeai/frontend/web/public/locales/en.json @@ -228,7 +228,8 @@ "scribble": "scribble", "selectModel": "Select a model", "selectCLIPVisionModel": "Select a CLIP Vision model", - "setControlImageDimensions": "Set Control Image Dimensions To W/H", + "setControlImageDimensions": "Copy size to W/H (optimize for model)", + "setControlImageDimensionsForce": "Copy size to W/H (ignore model)", "showAdvanced": "Show Advanced", "small": "Small", "toggleControlNet": "Toggle this ControlNet", diff --git a/invokeai/frontend/web/src/features/regionalPrompts/components/controlAdapterOverrides/ControlAdapterImagePreview.tsx b/invokeai/frontend/web/src/features/regionalPrompts/components/controlAdapterOverrides/ControlAdapterImagePreview.tsx index 8db8371cec..6238ec498d 100644 --- a/invokeai/frontend/web/src/features/regionalPrompts/components/controlAdapterOverrides/ControlAdapterImagePreview.tsx +++ b/invokeai/frontend/web/src/features/regionalPrompts/components/controlAdapterOverrides/ControlAdapterImagePreview.tsx @@ -1,5 +1,5 @@ import type { SystemStyleObject } from '@invoke-ai/ui-library'; -import { Box, Flex, Spinner } from '@invoke-ai/ui-library'; +import { Box, Flex, Spinner, useShiftModifier } from '@invoke-ai/ui-library'; import { skipToken } from '@reduxjs/toolkit/query'; import { createMemoizedSelector } from 'app/store/createMemoizedSelector'; import { useAppDispatch, useAppSelector } from 'app/store/storeHooks'; @@ -50,6 +50,7 @@ const ControlAdapterImagePreview = ({ isSmall, id }: Props) => { const activeTabName = useAppSelector(activeTabNameSelector); const optimalDimension = useAppSelector(selectOptimalDimension); const pendingControlImages = useAppSelector(selectPendingControlImages); + const shift = useShiftModifier(); const [isMouseOverImage, setIsMouseOverImage] = useState(false); @@ -96,14 +97,20 @@ const ControlAdapterImagePreview = ({ isSmall, id }: Props) => { if (activeTabName === 'unifiedCanvas') { dispatch(setBoundingBoxDimensions({ width: controlImage.width, height: controlImage.height }, optimalDimension)); } else { - const { width, height } = calculateNewSize( - controlImage.width / controlImage.height, - optimalDimension * optimalDimension - ); - dispatch(widthChanged({ width, updateAspectRatio: true })); - dispatch(heightChanged({ height, updateAspectRatio: true })); + if (shift) { + const { width, height } = controlImage; + dispatch(widthChanged({ width, updateAspectRatio: true })); + dispatch(heightChanged({ height, updateAspectRatio: true })); + } else { + const { width, height } = calculateNewSize( + controlImage.width / controlImage.height, + optimalDimension * optimalDimension + ); + dispatch(widthChanged({ width, updateAspectRatio: true })); + dispatch(heightChanged({ height, updateAspectRatio: true })); + } } - }, [controlImage, activeTabName, dispatch, optimalDimension]); + }, [controlImage, activeTabName, dispatch, optimalDimension, shift]); const handleMouseEnter = useCallback(() => { setIsMouseOverImage(true); @@ -199,7 +206,7 @@ const ControlAdapterImagePreview = ({ isSmall, id }: Props) => { : undefined} - tooltip={t('controlnet.setControlImageDimensions')} + tooltip={shift ? t('controlnet.setControlImageDimensionsForce') : t('controlnet.setControlImageDimensions')} styleOverrides={setControlImageDimensionsStyleOverrides} /> From fa637b5c59b79457858b0c2e1cfc05d7cf1fefbf Mon Sep 17 00:00:00 2001 From: psychedelicious <4822129+psychedelicious@users.noreply.github.com> Date: Tue, 30 Apr 2024 12:03:36 +1000 Subject: [PATCH 26/52] fix(ui): add missed ca layer opacity logic didn't stage the right changes a few commits back --- .../regionalPrompts/store/regionalPromptsSlice.ts | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/invokeai/frontend/web/src/features/regionalPrompts/store/regionalPromptsSlice.ts b/invokeai/frontend/web/src/features/regionalPrompts/store/regionalPromptsSlice.ts index 2c982565f9..08e98ad953 100644 --- a/invokeai/frontend/web/src/features/regionalPrompts/store/regionalPromptsSlice.ts +++ b/invokeai/frontend/web/src/features/regionalPrompts/store/regionalPromptsSlice.ts @@ -40,7 +40,7 @@ export const initialRegionalPromptsState: RegionalPromptsState = { selectedLayerId: null, brushSize: 100, layers: [], - globalMaskLayerOpacity: 0.5, // this globally changes all mask layers' opacity + globalMaskLayerOpacity: 0.3, // this globally changes all mask layers' opacity isEnabled: true, positivePrompt: '', negativePrompt: '', @@ -217,6 +217,13 @@ export const regionalPromptsSlice = createSlice({ state.layers = state.layers.filter((l) => l.id !== state.selectedLayerId); state.selectedLayerId = state.layers[0]?.id ?? null; }, + layerOpacityChanged: (state, action: PayloadAction<{ layerId: string; opacity: number }>) => { + const { layerId, opacity } = action.payload; + const layer = state.layers.filter(isControlAdapterLayer).find((l) => l.id === layerId); + if (layer) { + layer.opacity = opacity; + } + }, //#endregion //#region Mask Layers @@ -500,6 +507,7 @@ export const { maskedGuidanceLayerAdded, ipAdapterLayerAdded, controlAdapterLayerAdded, + layerOpacityChanged, // Mask layer actions maskLayerLineAdded, maskLayerPointsAdded, From 22f160bfccbc682774a5a9b808a927b8342edbf0 Mon Sep 17 00:00:00 2001 From: psychedelicious <4822129+psychedelicious@users.noreply.github.com> Date: Tue, 30 Apr 2024 12:07:39 +1000 Subject: [PATCH 27/52] fix(ui): unlink control adapter opaicty from global mask opacity --- .../src/features/regionalPrompts/store/regionalPromptsSlice.ts | 3 --- 1 file changed, 3 deletions(-) diff --git a/invokeai/frontend/web/src/features/regionalPrompts/store/regionalPromptsSlice.ts b/invokeai/frontend/web/src/features/regionalPrompts/store/regionalPromptsSlice.ts index 08e98ad953..d024c45ae3 100644 --- a/invokeai/frontend/web/src/features/regionalPrompts/store/regionalPromptsSlice.ts +++ b/invokeai/frontend/web/src/features/regionalPrompts/store/regionalPromptsSlice.ts @@ -389,9 +389,6 @@ export const regionalPromptsSlice = createSlice({ }, globalMaskLayerOpacityChanged: (state, action: PayloadAction) => { state.globalMaskLayerOpacity = action.payload; - state.layers.filter(isControlAdapterLayer).forEach((l) => { - l.opacity = action.payload; - }); }, isEnabledChanged: (state, action: PayloadAction) => { state.isEnabled = action.payload; From a357a1ac9d60c98629a722f217901f1f46da35e6 Mon Sep 17 00:00:00 2001 From: psychedelicious <4822129+psychedelicious@users.noreply.github.com> Date: Tue, 30 Apr 2024 12:18:54 +1000 Subject: [PATCH 28/52] feat(ui): remove select layer on click in canvas It's very easy to end up in a spot where you cannot select a layer at all to move it around. Too tricky to handle otherwise. --- .../components/StageComponent.tsx | 12 +----- .../store/regionalPromptsSlice.ts | 6 ++- .../regionalPrompts/util/renderers.ts | 40 +++---------------- 3 files changed, 11 insertions(+), 47 deletions(-) diff --git a/invokeai/frontend/web/src/features/regionalPrompts/components/StageComponent.tsx b/invokeai/frontend/web/src/features/regionalPrompts/components/StageComponent.tsx index 6f6d828c5c..721ddf9d8e 100644 --- a/invokeai/frontend/web/src/features/regionalPrompts/components/StageComponent.tsx +++ b/invokeai/frontend/web/src/features/regionalPrompts/components/StageComponent.tsx @@ -11,7 +11,6 @@ import { $tool, isMaskedGuidanceLayer, layerBboxChanged, - layerSelected, layerTranslated, selectRegionalPromptsSlice, } from 'features/regionalPrompts/store/regionalPromptsSlice'; @@ -66,13 +65,6 @@ const useStageRenderer = ( [dispatch] ); - const onBboxMouseDown = useCallback( - (layerId: string) => { - dispatch(layerSelected(layerId)); - }, - [dispatch] - ); - useLayoutEffect(() => { log.trace('Initializing stage'); if (!container) { @@ -182,8 +174,8 @@ const useStageRenderer = ( // Preview should not display bboxes return; } - renderers.renderBbox(stage, state.layers, state.selectedLayerId, tool, onBboxChanged, onBboxMouseDown); - }, [stage, asPreview, state.layers, state.selectedLayerId, tool, onBboxChanged, onBboxMouseDown, renderers]); + renderers.renderBbox(stage, state.layers, tool, onBboxChanged); + }, [stage, asPreview, state.layers, tool, onBboxChanged, renderers]); useLayoutEffect(() => { log.trace('Rendering background'); diff --git a/invokeai/frontend/web/src/features/regionalPrompts/store/regionalPromptsSlice.ts b/invokeai/frontend/web/src/features/regionalPrompts/store/regionalPromptsSlice.ts index d024c45ae3..88a025b559 100644 --- a/invokeai/frontend/web/src/features/regionalPrompts/store/regionalPromptsSlice.ts +++ b/invokeai/frontend/web/src/features/regionalPrompts/store/regionalPromptsSlice.ts @@ -142,10 +142,12 @@ export const regionalPromptsSlice = createSlice({ return; }, layerSelected: (state, action: PayloadAction) => { - for (const layer of state.layers) { - if (isRenderableLayer(layer) && layer.id === action.payload) { + for (const layer of state.layers.filter(isRenderableLayer)) { + if (layer.id === action.payload) { layer.isSelected = true; state.selectedLayerId = action.payload; + } else { + layer.isSelected = false; } } }, diff --git a/invokeai/frontend/web/src/features/regionalPrompts/util/renderers.ts b/invokeai/frontend/web/src/features/regionalPrompts/util/renderers.ts index 3d2da4ecea..944d996bff 100644 --- a/invokeai/frontend/web/src/features/regionalPrompts/util/renderers.ts +++ b/invokeai/frontend/web/src/features/regionalPrompts/util/renderers.ts @@ -43,8 +43,6 @@ import { assert } from 'tsafe'; import { v4 as uuidv4 } from 'uuid'; const BBOX_SELECTED_STROKE = 'rgba(78, 190, 255, 1)'; -const BBOX_NOT_SELECTED_STROKE = 'rgba(255, 255, 255, 0.353)'; -const BBOX_NOT_SELECTED_MOUSEOVER_STROKE = 'rgba(255, 255, 255, 0.661)'; const BRUSH_BORDER_INNER_COLOR = 'rgba(0,0,0,1)'; const BRUSH_BORDER_OUTER_COLOR = 'rgba(255,255,255,0.8)'; // This is invokeai/frontend/web/public/assets/images/transparent_bg.png as a dataURL @@ -53,13 +51,6 @@ const STAGE_BG_DATAURL = const mapId = (object: { id: string }) => object.id; -const getIsSelected = (layerId?: string | null) => { - if (!layerId) { - return false; - } - return layerId === getStore().getState().regionalPrompts.present.selectedLayerId; -}; - const selectRenderableLayers = (n: Konva.Node) => n.name() === MASKED_GUIDANCE_LAYER_NAME || n.name() === CONTROLNET_LAYER_NAME; @@ -560,29 +551,12 @@ const renderLayers = ( * @param konvaLayer The konva layer to attach the bounding box to. * @param onBboxMouseDown Callback for when the bounding box is clicked. */ -const createBboxRect = (reduxLayer: Layer, konvaLayer: Konva.Layer, onBboxMouseDown: (layerId: string) => void) => { +const createBboxRect = (reduxLayer: Layer, konvaLayer: Konva.Layer) => { const rect = new Konva.Rect({ id: getLayerBboxId(reduxLayer.id), name: LAYER_BBOX_NAME, strokeWidth: 1, }); - rect.on('mousedown', function () { - onBboxMouseDown(reduxLayer.id); - }); - rect.on('mouseover', function (e) { - if (getIsSelected(e.target.getLayer()?.id())) { - this.stroke(BBOX_SELECTED_STROKE); - } else { - this.stroke(BBOX_NOT_SELECTED_MOUSEOVER_STROKE); - } - }); - rect.on('mouseout', function (e) { - if (getIsSelected(e.target.getLayer()?.id())) { - this.stroke(BBOX_SELECTED_STROKE); - } else { - this.stroke(BBOX_NOT_SELECTED_STROKE); - } - }); konvaLayer.add(rect); return rect; }; @@ -600,10 +574,8 @@ const createBboxRect = (reduxLayer: Layer, konvaLayer: Konva.Layer, onBboxMouseD const renderBbox = ( stage: Konva.Stage, reduxLayers: Layer[], - selectedLayerId: string | null, tool: Tool, - onBboxChanged: (layerId: string, bbox: IRect | null) => void, - onBboxMouseDown: (layerId: string) => void + onBboxChanged: (layerId: string, bbox: IRect | null) => void ) => { // Hide all bboxes so they don't interfere with getClientRect for (const bboxRect of stage.find(`.${LAYER_BBOX_NAME}`)) { @@ -634,18 +606,16 @@ const renderBbox = ( continue; } - const rect = - konvaLayer.findOne(`.${LAYER_BBOX_NAME}`) ?? - createBboxRect(reduxLayer, konvaLayer, onBboxMouseDown); + const rect = konvaLayer.findOne(`.${LAYER_BBOX_NAME}`) ?? createBboxRect(reduxLayer, konvaLayer); rect.setAttrs({ visible: true, - listening: true, + listening: reduxLayer.isSelected, x: bbox.x, y: bbox.y, width: bbox.width, height: bbox.height, - stroke: reduxLayer.id === selectedLayerId ? BBOX_SELECTED_STROKE : BBOX_NOT_SELECTED_STROKE, + stroke: reduxLayer.isSelected ? BBOX_SELECTED_STROKE : '', }); } } From 720e16cea651365a1eaa89f36e06d309ed183b3b Mon Sep 17 00:00:00 2001 From: psychedelicious <4822129+psychedelicious@users.noreply.github.com> Date: Tue, 30 Apr 2024 12:21:38 +1000 Subject: [PATCH 29/52] feat(ui): tweak layer list styling to better indicate selectablility --- .../components/ControlAdapterLayerListItem.tsx | 3 +-- .../components/IPAdapterLayerListItem.tsx | 10 +--------- .../components/MaskedGuidanceLayerListItem.tsx | 3 +-- 3 files changed, 3 insertions(+), 13 deletions(-) diff --git a/invokeai/frontend/web/src/features/regionalPrompts/components/ControlAdapterLayerListItem.tsx b/invokeai/frontend/web/src/features/regionalPrompts/components/ControlAdapterLayerListItem.tsx index f9ec37e159..24be87f668 100644 --- a/invokeai/frontend/web/src/features/regionalPrompts/components/ControlAdapterLayerListItem.tsx +++ b/invokeai/frontend/web/src/features/regionalPrompts/components/ControlAdapterLayerListItem.tsx @@ -43,9 +43,8 @@ export const ControlAdapterLayerListItem = memo(({ layerId }: Props) => { gap={2} onClickCapture={onClickCapture} bg={isSelected ? 'base.400' : 'base.800'} - ps={2} + px={2} borderRadius="base" - pe="1px" py="1px" > diff --git a/invokeai/frontend/web/src/features/regionalPrompts/components/IPAdapterLayerListItem.tsx b/invokeai/frontend/web/src/features/regionalPrompts/components/IPAdapterLayerListItem.tsx index f94865c305..db4cbec6fc 100644 --- a/invokeai/frontend/web/src/features/regionalPrompts/components/IPAdapterLayerListItem.tsx +++ b/invokeai/frontend/web/src/features/regionalPrompts/components/IPAdapterLayerListItem.tsx @@ -38,15 +38,7 @@ export const IPAdapterLayerListItem = memo(({ layerId }: Props) => { dispatch(layerSelected(layerId)); }, [dispatch, layerId]); return ( - + diff --git a/invokeai/frontend/web/src/features/regionalPrompts/components/MaskedGuidanceLayerListItem.tsx b/invokeai/frontend/web/src/features/regionalPrompts/components/MaskedGuidanceLayerListItem.tsx index 2b570a519a..7ecbf53cbb 100644 --- a/invokeai/frontend/web/src/features/regionalPrompts/components/MaskedGuidanceLayerListItem.tsx +++ b/invokeai/frontend/web/src/features/regionalPrompts/components/MaskedGuidanceLayerListItem.tsx @@ -56,9 +56,8 @@ export const MaskedGuidanceLayerListItem = memo(({ layerId }: Props) => { gap={2} onClickCapture={onClickCapture} bg={isSelected ? color : 'base.800'} - ps={2} + px={2} borderRadius="base" - pe="1px" py="1px" > From ace3955760ae6372dbb4d86ce8c9de9f22ae759e Mon Sep 17 00:00:00 2001 From: psychedelicious <4822129+psychedelicious@users.noreply.github.com> Date: Tue, 30 Apr 2024 12:29:35 +1000 Subject: [PATCH 30/52] fix(ui): tool preview/cursor when non-interactable layer selected --- .../regionalPrompts/components/StageComponent.tsx | 9 +++++++++ .../regionalPrompts/components/ToolChooser.tsx | 15 +++++++++++++-- .../features/regionalPrompts/util/renderers.ts | 4 ++++ 3 files changed, 26 insertions(+), 2 deletions(-) diff --git a/invokeai/frontend/web/src/features/regionalPrompts/components/StageComponent.tsx b/invokeai/frontend/web/src/features/regionalPrompts/components/StageComponent.tsx index 721ddf9d8e..83f589a392 100644 --- a/invokeai/frontend/web/src/features/regionalPrompts/components/StageComponent.tsx +++ b/invokeai/frontend/web/src/features/regionalPrompts/components/StageComponent.tsx @@ -1,5 +1,6 @@ import { Flex } from '@invoke-ai/ui-library'; import { useStore } from '@nanostores/react'; +import { createSelector } from '@reduxjs/toolkit'; import { logger } from 'app/logging/logger'; import { createMemoizedSelector } from 'app/store/createMemoizedSelector'; import { useAppDispatch, useAppSelector } from 'app/store/storeHooks'; @@ -33,6 +34,11 @@ const selectSelectedLayerColor = createMemoizedSelector(selectRegionalPromptsSli return layer?.previewColor ?? null; }); +const selectSelectedLayerType = createSelector(selectRegionalPromptsSlice, (regionalPrompts) => { + const selectedLayer = regionalPrompts.present.layers.find((l) => l.id === regionalPrompts.present.selectedLayerId); + return selectedLayer?.type ?? null; +}); + const useStageRenderer = ( stage: Konva.Stage, container: HTMLDivElement | null, @@ -47,6 +53,7 @@ const useStageRenderer = ( const lastMouseDownPos = useStore($lastMouseDownPos); const isMouseOver = useStore($isMouseOver); const selectedLayerIdColor = useAppSelector(selectSelectedLayerColor); + const selectedLayerType = useAppSelector(selectSelectedLayerType); const layerIds = useMemo(() => state.layers.map((l) => l.id), [state.layers]); const renderers = useMemo(() => (asPreview ? debouncedRenderers : normalRenderers), [asPreview]); const dpr = useDevicePixelRatio({ round: false }); @@ -135,6 +142,7 @@ const useStageRenderer = ( stage, tool, selectedLayerIdColor, + selectedLayerType, state.globalMaskLayerOpacity, cursorPosition, lastMouseDownPos, @@ -146,6 +154,7 @@ const useStageRenderer = ( stage, tool, selectedLayerIdColor, + selectedLayerType, state.globalMaskLayerOpacity, cursorPosition, lastMouseDownPos, diff --git a/invokeai/frontend/web/src/features/regionalPrompts/components/ToolChooser.tsx b/invokeai/frontend/web/src/features/regionalPrompts/components/ToolChooser.tsx index 80ed271289..a3b556a7e5 100644 --- a/invokeai/frontend/web/src/features/regionalPrompts/components/ToolChooser.tsx +++ b/invokeai/frontend/web/src/features/regionalPrompts/components/ToolChooser.tsx @@ -1,16 +1,27 @@ import { ButtonGroup, IconButton } from '@invoke-ai/ui-library'; import { useStore } from '@nanostores/react'; +import { createSelector } from '@reduxjs/toolkit'; import { useAppDispatch, useAppSelector } from 'app/store/storeHooks'; -import { $tool, selectedLayerDeleted, selectedLayerReset } from 'features/regionalPrompts/store/regionalPromptsSlice'; +import { + $tool, + selectedLayerDeleted, + selectedLayerReset, + selectRegionalPromptsSlice, +} from 'features/regionalPrompts/store/regionalPromptsSlice'; import { useCallback } from 'react'; import { useHotkeys } from 'react-hotkeys-hook'; import { useTranslation } from 'react-i18next'; import { PiArrowsOutCardinalBold, PiEraserBold, PiPaintBrushBold, PiRectangleBold } from 'react-icons/pi'; +const selectIsDisabled = createSelector(selectRegionalPromptsSlice, (regionalPrompts) => { + const selectedLayer = regionalPrompts.present.layers.find((l) => l.id === regionalPrompts.present.selectedLayerId); + return selectedLayer?.type !== 'masked_guidance_layer'; +}); + export const ToolChooser: React.FC = () => { const { t } = useTranslation(); const dispatch = useAppDispatch(); - const isDisabled = useAppSelector((s) => s.regionalPrompts.present.layers.length === 0); + const isDisabled = useAppSelector(selectIsDisabled); const tool = useStore($tool); const setToolToBrush = useCallback(() => { diff --git a/invokeai/frontend/web/src/features/regionalPrompts/util/renderers.ts b/invokeai/frontend/web/src/features/regionalPrompts/util/renderers.ts index 944d996bff..2e437728b5 100644 --- a/invokeai/frontend/web/src/features/regionalPrompts/util/renderers.ts +++ b/invokeai/frontend/web/src/features/regionalPrompts/util/renderers.ts @@ -133,6 +133,7 @@ const renderToolPreview = ( stage: Konva.Stage, tool: Tool, color: RgbColor | null, + selectedLayerType: Layer['type'] | null, globalMaskLayerOpacity: number, cursorPos: Vector2d | null, lastMouseDownPos: Vector2d | null, @@ -144,6 +145,9 @@ const renderToolPreview = ( if (layerCount === 0) { // We have no layers, so we should not render any tool stage.container().style.cursor = 'default'; + } else if (selectedLayerType !== 'masked_guidance_layer') { + // Non-mask-guidance layers don't have tools + stage.container().style.cursor = 'not-allowed'; } else if (tool === 'move') { // Move tool gets a pointer stage.container().style.cursor = 'default'; From c2f8adf93ea238374f01932b3c8b82b3d7ee3758 Mon Sep 17 00:00:00 2001 From: psychedelicious <4822129+psychedelicious@users.noreply.github.com> Date: Tue, 30 Apr 2024 12:32:39 +1000 Subject: [PATCH 31/52] fix(ui): deselect other layers when new layer added --- .../regionalPrompts/store/regionalPromptsSlice.ts | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/invokeai/frontend/web/src/features/regionalPrompts/store/regionalPromptsSlice.ts b/invokeai/frontend/web/src/features/regionalPrompts/store/regionalPromptsSlice.ts index 88a025b559..1445f6cabd 100644 --- a/invokeai/frontend/web/src/features/regionalPrompts/store/regionalPromptsSlice.ts +++ b/invokeai/frontend/web/src/features/regionalPrompts/store/regionalPromptsSlice.ts @@ -109,6 +109,11 @@ export const regionalPromptsSlice = createSlice({ }; state.layers.push(layer); state.selectedLayerId = layer.id; + for (const layer of state.layers.filter(isRenderableLayer)) { + if (layer.id !== layerId) { + layer.isSelected = false; + } + } return; }, ipAdapterLayerAdded: (state, action: PayloadAction<{ layerId: string; ipAdapterId: string }>) => { @@ -139,6 +144,11 @@ export const regionalPromptsSlice = createSlice({ }; state.layers.push(layer); state.selectedLayerId = layer.id; + for (const layer of state.layers.filter(isRenderableLayer)) { + if (layer.id !== layerId) { + layer.isSelected = false; + } + } return; }, layerSelected: (state, action: PayloadAction) => { From 61957418142c24b027b0d1da6046874c3beab9e8 Mon Sep 17 00:00:00 2001 From: psychedelicious <4822129+psychedelicious@users.noreply.github.com> Date: Tue, 30 Apr 2024 12:39:43 +1000 Subject: [PATCH 32/52] feat(ui): move global mask opacity to settings popover --- .../ControlLayersSettingsPopover.tsx | 26 ++++++++ .../components/GlobalMaskLayerOpacity.tsx | 64 +++++++------------ .../components/RegionalPromptsToolbar.tsx | 6 +- 3 files changed, 53 insertions(+), 43 deletions(-) create mode 100644 invokeai/frontend/web/src/features/regionalPrompts/components/ControlLayersSettingsPopover.tsx diff --git a/invokeai/frontend/web/src/features/regionalPrompts/components/ControlLayersSettingsPopover.tsx b/invokeai/frontend/web/src/features/regionalPrompts/components/ControlLayersSettingsPopover.tsx new file mode 100644 index 0000000000..ea3f7af325 --- /dev/null +++ b/invokeai/frontend/web/src/features/regionalPrompts/components/ControlLayersSettingsPopover.tsx @@ -0,0 +1,26 @@ +import { Flex, IconButton, Popover, PopoverBody, PopoverContent, PopoverTrigger } from '@invoke-ai/ui-library'; +import { GlobalMaskLayerOpacity } from 'features/regionalPrompts/components/GlobalMaskLayerOpacity'; +import { memo } from 'react'; +import { useTranslation } from 'react-i18next'; +import { RiSettings4Fill } from 'react-icons/ri'; + +const ControlLayersSettingsPopover = () => { + const { t } = useTranslation(); + + return ( + + + } /> + + + + + + + + + + ); +}; + +export default memo(ControlLayersSettingsPopover); diff --git a/invokeai/frontend/web/src/features/regionalPrompts/components/GlobalMaskLayerOpacity.tsx b/invokeai/frontend/web/src/features/regionalPrompts/components/GlobalMaskLayerOpacity.tsx index 8386f522a2..c24e224a61 100644 --- a/invokeai/frontend/web/src/features/regionalPrompts/components/GlobalMaskLayerOpacity.tsx +++ b/invokeai/frontend/web/src/features/regionalPrompts/components/GlobalMaskLayerOpacity.tsx @@ -1,14 +1,4 @@ -import { - CompositeNumberInput, - CompositeSlider, - FormControl, - FormLabel, - Popover, - PopoverArrow, - PopoverBody, - PopoverContent, - PopoverTrigger, -} from '@invoke-ai/ui-library'; +import { CompositeNumberInput, CompositeSlider, Flex, FormControl, FormLabel } from '@invoke-ai/ui-library'; import { useAppDispatch, useAppSelector } from 'app/store/storeHooks'; import { globalMaskLayerOpacityChanged, @@ -33,36 +23,30 @@ export const GlobalMaskLayerOpacity = memo(() => { [dispatch] ); return ( - + {t('regionalPrompts.globalMaskOpacity')} - - - - - - - - - - - + + + + ); }); diff --git a/invokeai/frontend/web/src/features/regionalPrompts/components/RegionalPromptsToolbar.tsx b/invokeai/frontend/web/src/features/regionalPrompts/components/RegionalPromptsToolbar.tsx index 4a3b611efd..3ffeedd245 100644 --- a/invokeai/frontend/web/src/features/regionalPrompts/components/RegionalPromptsToolbar.tsx +++ b/invokeai/frontend/web/src/features/regionalPrompts/components/RegionalPromptsToolbar.tsx @@ -1,7 +1,7 @@ /* eslint-disable i18next/no-literal-string */ import { Flex } from '@invoke-ai/ui-library'; import { BrushSize } from 'features/regionalPrompts/components/BrushSize'; -import { GlobalMaskLayerOpacity } from 'features/regionalPrompts/components/GlobalMaskLayerOpacity'; +import ControlLayersSettingsPopover from 'features/regionalPrompts/components/ControlLayersSettingsPopover'; import { ToolChooser } from 'features/regionalPrompts/components/ToolChooser'; import { UndoRedoButtonGroup } from 'features/regionalPrompts/components/UndoRedoButtonGroup'; import { memo } from 'react'; @@ -10,9 +10,9 @@ export const RegionalPromptsToolbar = memo(() => { return ( - - + + ); }); From 9d042baf48df1dcbf8fb4d4b896c46d73e1320a5 Mon Sep 17 00:00:00 2001 From: psychedelicious <4822129+psychedelicious@users.noreply.github.com> Date: Tue, 30 Apr 2024 12:42:11 +1000 Subject: [PATCH 33/52] fix(ui): ip adapter layers always at bottom of list --- .../components/RegionalPromptsPanelContent.tsx | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/invokeai/frontend/web/src/features/regionalPrompts/components/RegionalPromptsPanelContent.tsx b/invokeai/frontend/web/src/features/regionalPrompts/components/RegionalPromptsPanelContent.tsx index 1a73202986..e975d33056 100644 --- a/invokeai/frontend/web/src/features/regionalPrompts/components/RegionalPromptsPanelContent.tsx +++ b/invokeai/frontend/web/src/features/regionalPrompts/components/RegionalPromptsPanelContent.tsx @@ -8,13 +8,15 @@ import { ControlAdapterLayerListItem } from 'features/regionalPrompts/components import { DeleteAllLayersButton } from 'features/regionalPrompts/components/DeleteAllLayersButton'; import { IPAdapterLayerListItem } from 'features/regionalPrompts/components/IPAdapterLayerListItem'; import { MaskedGuidanceLayerListItem } from 'features/regionalPrompts/components/MaskedGuidanceLayerListItem'; -import { selectRegionalPromptsSlice } from 'features/regionalPrompts/store/regionalPromptsSlice'; +import { isRenderableLayer, selectRegionalPromptsSlice } from 'features/regionalPrompts/store/regionalPromptsSlice'; import type { Layer } from 'features/regionalPrompts/store/types'; +import { partition } from 'lodash-es'; import { memo } from 'react'; -const selectLayerIdTypePairs = createMemoizedSelector(selectRegionalPromptsSlice, (regionalPrompts) => - regionalPrompts.present.layers.map((l) => ({ id: l.id, type: l.type })).reverse() -); +const selectLayerIdTypePairs = createMemoizedSelector(selectRegionalPromptsSlice, (regionalPrompts) => { + const [renderableLayers, ipAdapterLayers] = partition(regionalPrompts.present.layers, isRenderableLayer); + return [...ipAdapterLayers, ...renderableLayers].map((l) => ({ id: l.id, type: l.type })).reverse(); +}); export const RegionalPromptsPanelContent = memo(() => { const layerIdTypePairs = useAppSelector(selectLayerIdTypePairs); From a20faca20ff07cf9d4c0680e6b1d46bc6315478b Mon Sep 17 00:00:00 2001 From: psychedelicious <4822129+psychedelicious@users.noreply.github.com> Date: Tue, 30 Apr 2024 12:44:47 +1000 Subject: [PATCH 34/52] feat(ui): layer layout tweaks --- .../ControlAdapterLayerListItem.tsx | 2 +- .../components/IPAdapterLayerListItem.tsx | 2 -- .../MaskedGuidanceLayerListItem.tsx | 2 +- .../components/RPLayerIPAdapterList.tsx | 8 +++-- .../ControlAdapterLayerConfig.tsx | 34 ++++++++++--------- 5 files changed, 25 insertions(+), 23 deletions(-) diff --git a/invokeai/frontend/web/src/features/regionalPrompts/components/ControlAdapterLayerListItem.tsx b/invokeai/frontend/web/src/features/regionalPrompts/components/ControlAdapterLayerListItem.tsx index 24be87f668..95c2c5fbb1 100644 --- a/invokeai/frontend/web/src/features/regionalPrompts/components/ControlAdapterLayerListItem.tsx +++ b/invokeai/frontend/web/src/features/regionalPrompts/components/ControlAdapterLayerListItem.tsx @@ -48,7 +48,7 @@ export const ControlAdapterLayerListItem = memo(({ layerId }: Props) => { py="1px" > - + diff --git a/invokeai/frontend/web/src/features/regionalPrompts/components/IPAdapterLayerListItem.tsx b/invokeai/frontend/web/src/features/regionalPrompts/components/IPAdapterLayerListItem.tsx index db4cbec6fc..27a72a7e8a 100644 --- a/invokeai/frontend/web/src/features/regionalPrompts/components/IPAdapterLayerListItem.tsx +++ b/invokeai/frontend/web/src/features/regionalPrompts/components/IPAdapterLayerListItem.tsx @@ -4,7 +4,6 @@ import { useAppDispatch, useAppSelector } from 'app/store/storeHooks'; import ControlAdapterLayerConfig from 'features/regionalPrompts/components/controlAdapterOverrides/ControlAdapterLayerConfig'; import { LayerTitle } from 'features/regionalPrompts/components/LayerTitle'; import { RPLayerDeleteButton } from 'features/regionalPrompts/components/RPLayerDeleteButton'; -import { RPLayerMenu } from 'features/regionalPrompts/components/RPLayerMenu'; import { RPLayerVisibilityToggle } from 'features/regionalPrompts/components/RPLayerVisibilityToggle'; import { isIPAdapterLayer, @@ -44,7 +43,6 @@ export const IPAdapterLayerListItem = memo(({ layerId }: Props) => { - diff --git a/invokeai/frontend/web/src/features/regionalPrompts/components/MaskedGuidanceLayerListItem.tsx b/invokeai/frontend/web/src/features/regionalPrompts/components/MaskedGuidanceLayerListItem.tsx index 7ecbf53cbb..ce18daed0e 100644 --- a/invokeai/frontend/web/src/features/regionalPrompts/components/MaskedGuidanceLayerListItem.tsx +++ b/invokeai/frontend/web/src/features/regionalPrompts/components/MaskedGuidanceLayerListItem.tsx @@ -61,7 +61,7 @@ export const MaskedGuidanceLayerListItem = memo(({ layerId }: Props) => { py="1px" > - + diff --git a/invokeai/frontend/web/src/features/regionalPrompts/components/RPLayerIPAdapterList.tsx b/invokeai/frontend/web/src/features/regionalPrompts/components/RPLayerIPAdapterList.tsx index 5cab232baa..73f04cb23f 100644 --- a/invokeai/frontend/web/src/features/regionalPrompts/components/RPLayerIPAdapterList.tsx +++ b/invokeai/frontend/web/src/features/regionalPrompts/components/RPLayerIPAdapterList.tsx @@ -32,9 +32,11 @@ export const RPLayerIPAdapterList = memo(({ layerId }: Props) => { <> {ipAdapterIds.map((id, index) => ( - - - + {index > 0 && ( + + + + )} ))} diff --git a/invokeai/frontend/web/src/features/regionalPrompts/components/controlAdapterOverrides/ControlAdapterLayerConfig.tsx b/invokeai/frontend/web/src/features/regionalPrompts/components/controlAdapterOverrides/ControlAdapterLayerConfig.tsx index 8afd035c3d..2a8fed7a83 100644 --- a/invokeai/frontend/web/src/features/regionalPrompts/components/controlAdapterOverrides/ControlAdapterLayerConfig.tsx +++ b/invokeai/frontend/web/src/features/regionalPrompts/components/controlAdapterOverrides/ControlAdapterLayerConfig.tsx @@ -28,22 +28,24 @@ const ControlAdapterLayerConfig = (props: { id: string }) => { {' '} - - } - /> + {controlAdapterType !== 'ip_adapter' && ( + + } + /> + )} From aa2ca0305693f3043e2a20eb947665a5d1022832 Mon Sep 17 00:00:00 2001 From: psychedelicious <4822129+psychedelicious@users.noreply.github.com> Date: Tue, 30 Apr 2024 12:59:57 +1000 Subject: [PATCH 35/52] fix(ui): filter layers based on tab when disabling invoke button --- .../src/common/hooks/useIsReadyToEnqueue.ts | 36 ++++++++++++++++++- 1 file changed, 35 insertions(+), 1 deletion(-) diff --git a/invokeai/frontend/web/src/common/hooks/useIsReadyToEnqueue.ts b/invokeai/frontend/web/src/common/hooks/useIsReadyToEnqueue.ts index 24abcd631d..26ce9eaf58 100644 --- a/invokeai/frontend/web/src/common/hooks/useIsReadyToEnqueue.ts +++ b/invokeai/frontend/web/src/common/hooks/useIsReadyToEnqueue.ts @@ -97,7 +97,41 @@ const selector = createMemoizedSelector( reasons.push(i18n.t('parameters.invoke.noModelSelected')); } - selectControlAdapterAll(controlAdapters).forEach((ca, i) => { + let enabledControlAdapters = selectControlAdapterAll(controlAdapters).filter((ca) => ca.isEnabled); + + if (activeTabName === 'txt2img') { + // Special handling for control layers on txt2img + const enabledControlLayersAdapterIds = regionalPrompts.present.layers + .filter((l) => l.isEnabled) + .flatMap((layer) => { + if (layer.type === 'masked_guidance_layer') { + return layer.ipAdapterIds; + } + if (layer.type === 'control_adapter_layer') { + return [layer.controlNetId]; + } + if (layer.type === 'ip_adapter_layer') { + return [layer.ipAdapterId]; + } + }); + + enabledControlAdapters = enabledControlAdapters.filter((ca) => enabledControlLayersAdapterIds.includes(ca.id)); + } else { + const allControlLayerAdapterIds = regionalPrompts.present.layers.flatMap((layer) => { + if (layer.type === 'masked_guidance_layer') { + return layer.ipAdapterIds; + } + if (layer.type === 'control_adapter_layer') { + return [layer.controlNetId]; + } + if (layer.type === 'ip_adapter_layer') { + return [layer.ipAdapterId]; + } + }); + enabledControlAdapters = enabledControlAdapters.filter((ca) => !allControlLayerAdapterIds.includes(ca.id)); + } + + enabledControlAdapters.forEach((ca, i) => { if (!ca.isEnabled) { return; } From 415a41e21af0584dc986bc65d788a1ce8e51cc16 Mon Sep 17 00:00:00 2001 From: psychedelicious <4822129+psychedelicious@users.noreply.github.com> Date: Tue, 30 Apr 2024 13:07:45 +1000 Subject: [PATCH 36/52] perf(ui): reset maskobjects when layer has no bbox (all objects erased) --- .../features/regionalPrompts/store/regionalPromptsSlice.ts | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/invokeai/frontend/web/src/features/regionalPrompts/store/regionalPromptsSlice.ts b/invokeai/frontend/web/src/features/regionalPrompts/store/regionalPromptsSlice.ts index 1445f6cabd..da5fa8aef7 100644 --- a/invokeai/frontend/web/src/features/regionalPrompts/store/regionalPromptsSlice.ts +++ b/invokeai/frontend/web/src/features/regionalPrompts/store/regionalPromptsSlice.ts @@ -181,6 +181,11 @@ export const regionalPromptsSlice = createSlice({ if (isRenderableLayer(layer)) { layer.bbox = bbox; layer.bboxNeedsUpdate = false; + if (bbox === null && layer.type === 'masked_guidance_layer') { + // The layer was fully erased, empty its objects to prevent accumulation of invisible objects + layer.maskObjects = []; + layer.needsPixelBbox = false; + } } }, layerReset: (state, action: PayloadAction) => { From 2af5c4be9fc29ef4879f08c90a2a8de8ec0852dc Mon Sep 17 00:00:00 2001 From: psychedelicious <4822129+psychedelicious@users.noreply.github.com> Date: Tue, 30 Apr 2024 13:07:56 +1000 Subject: [PATCH 37/52] fix(ui): ip adapter layers are not selectable --- .../components/IPAdapterLayerListItem.tsx | 24 +++++-------------- 1 file changed, 6 insertions(+), 18 deletions(-) diff --git a/invokeai/frontend/web/src/features/regionalPrompts/components/IPAdapterLayerListItem.tsx b/invokeai/frontend/web/src/features/regionalPrompts/components/IPAdapterLayerListItem.tsx index 27a72a7e8a..9673a301d7 100644 --- a/invokeai/frontend/web/src/features/regionalPrompts/components/IPAdapterLayerListItem.tsx +++ b/invokeai/frontend/web/src/features/regionalPrompts/components/IPAdapterLayerListItem.tsx @@ -1,16 +1,12 @@ import { Flex, Spacer } from '@invoke-ai/ui-library'; import { createMemoizedSelector } from 'app/store/createMemoizedSelector'; -import { useAppDispatch, useAppSelector } from 'app/store/storeHooks'; +import { useAppSelector } from 'app/store/storeHooks'; import ControlAdapterLayerConfig from 'features/regionalPrompts/components/controlAdapterOverrides/ControlAdapterLayerConfig'; import { LayerTitle } from 'features/regionalPrompts/components/LayerTitle'; import { RPLayerDeleteButton } from 'features/regionalPrompts/components/RPLayerDeleteButton'; import { RPLayerVisibilityToggle } from 'features/regionalPrompts/components/RPLayerVisibilityToggle'; -import { - isIPAdapterLayer, - layerSelected, - selectRegionalPromptsSlice, -} from 'features/regionalPrompts/store/regionalPromptsSlice'; -import { memo, useCallback, useMemo } from 'react'; +import { isIPAdapterLayer, selectRegionalPromptsSlice } from 'features/regionalPrompts/store/regionalPromptsSlice'; +import { memo, useMemo } from 'react'; import { assert } from 'tsafe'; type Props = { @@ -18,26 +14,18 @@ type Props = { }; export const IPAdapterLayerListItem = memo(({ layerId }: Props) => { - const dispatch = useAppDispatch(); const selector = useMemo( () => createMemoizedSelector(selectRegionalPromptsSlice, (regionalPrompts) => { const layer = regionalPrompts.present.layers.find((l) => l.id === layerId); assert(isIPAdapterLayer(layer), `Layer ${layerId} not found or not an IP Adapter layer`); - return { - ipAdapterId: layer.ipAdapterId, - isSelected: layerId === regionalPrompts.present.selectedLayerId, - }; + return layer.ipAdapterId; }), [layerId] ); - const { ipAdapterId, isSelected } = useAppSelector(selector); - const onClickCapture = useCallback(() => { - // Must be capture so that the layer is selected before deleting/resetting/etc - dispatch(layerSelected(layerId)); - }, [dispatch, layerId]); + const ipAdapterId = useAppSelector(selector); return ( - + From 22bd33b7c681f14df2ec8aef944d0c2ea8ac0911 Mon Sep 17 00:00:00 2001 From: psychedelicious <4822129+psychedelicious@users.noreply.github.com> Date: Tue, 30 Apr 2024 13:11:07 +1000 Subject: [PATCH 38/52] chore(ui): lint --- .../features/regionalPrompts/components/AddLayerButton.tsx | 2 +- .../components/RPLayerMenuArrangeActions.tsx | 7 +------ .../web/src/features/regionalPrompts/store/actions.ts | 3 --- .../features/regionalPrompts/store/regionalPromptsSlice.ts | 1 - .../web/src/features/regionalPrompts/store/types.ts | 4 ++-- 5 files changed, 4 insertions(+), 13 deletions(-) delete mode 100644 invokeai/frontend/web/src/features/regionalPrompts/store/actions.ts diff --git a/invokeai/frontend/web/src/features/regionalPrompts/components/AddLayerButton.tsx b/invokeai/frontend/web/src/features/regionalPrompts/components/AddLayerButton.tsx index d0d10ac987..b112cebe1b 100644 --- a/invokeai/frontend/web/src/features/regionalPrompts/components/AddLayerButton.tsx +++ b/invokeai/frontend/web/src/features/regionalPrompts/components/AddLayerButton.tsx @@ -1,6 +1,6 @@ import { Button, Menu, MenuButton, MenuItem, MenuList } from '@invoke-ai/ui-library'; -import { useAppDispatch } from 'app/store/storeHooks'; import { guidanceLayerAdded } from 'app/store/middleware/listenerMiddleware/listeners/regionalControlToControlAdapterBridge'; +import { useAppDispatch } from 'app/store/storeHooks'; import { memo, useCallback } from 'react'; import { useTranslation } from 'react-i18next'; import { PiPlusBold } from 'react-icons/pi'; diff --git a/invokeai/frontend/web/src/features/regionalPrompts/components/RPLayerMenuArrangeActions.tsx b/invokeai/frontend/web/src/features/regionalPrompts/components/RPLayerMenuArrangeActions.tsx index e3fe918ff0..99878cb95c 100644 --- a/invokeai/frontend/web/src/features/regionalPrompts/components/RPLayerMenuArrangeActions.tsx +++ b/invokeai/frontend/web/src/features/regionalPrompts/components/RPLayerMenuArrangeActions.tsx @@ -11,12 +11,7 @@ import { } from 'features/regionalPrompts/store/regionalPromptsSlice'; import { memo, useCallback, useMemo } from 'react'; import { useTranslation } from 'react-i18next'; -import { - PiArrowDownBold, - PiArrowLineDownBold, - PiArrowLineUpBold, - PiArrowUpBold, -} from 'react-icons/pi'; +import { PiArrowDownBold, PiArrowLineDownBold, PiArrowLineUpBold, PiArrowUpBold } from 'react-icons/pi'; import { assert } from 'tsafe'; type Props = { layerId: string }; diff --git a/invokeai/frontend/web/src/features/regionalPrompts/store/actions.ts b/invokeai/frontend/web/src/features/regionalPrompts/store/actions.ts deleted file mode 100644 index b28b04f643..0000000000 --- a/invokeai/frontend/web/src/features/regionalPrompts/store/actions.ts +++ /dev/null @@ -1,3 +0,0 @@ - - - diff --git a/invokeai/frontend/web/src/features/regionalPrompts/store/regionalPromptsSlice.ts b/invokeai/frontend/web/src/features/regionalPrompts/store/regionalPromptsSlice.ts index da5fa8aef7..0d94b8624c 100644 --- a/invokeai/frontend/web/src/features/regionalPrompts/store/regionalPromptsSlice.ts +++ b/invokeai/frontend/web/src/features/regionalPrompts/store/regionalPromptsSlice.ts @@ -584,7 +584,6 @@ export const TOOL_PREVIEW_BRUSH_BORDER_OUTER_ID = 'tool_preview_layer.brush_bord export const TOOL_PREVIEW_RECT_ID = 'tool_preview_layer.rect'; export const BACKGROUND_LAYER_ID = 'background_layer'; export const BACKGROUND_RECT_ID = 'background_layer.rect'; -export const CONTROLNET_LAYER_TRANSFORMER_ID = 'control_adapter_layer.transformer'; // Names (aka classes) for Konva layers and objects export const CONTROLNET_LAYER_NAME = 'control_adapter_layer'; diff --git a/invokeai/frontend/web/src/features/regionalPrompts/store/types.ts b/invokeai/frontend/web/src/features/regionalPrompts/store/types.ts index af2d2aeaf4..ac66c013d6 100644 --- a/invokeai/frontend/web/src/features/regionalPrompts/store/types.ts +++ b/invokeai/frontend/web/src/features/regionalPrompts/store/types.ts @@ -32,12 +32,12 @@ export type VectorMaskRect = { height: number; }; -export type LayerBase = { +type LayerBase = { id: string; isEnabled: boolean; }; -export type RenderableLayerBase = LayerBase & { +type RenderableLayerBase = LayerBase & { x: number; y: number; bbox: IRect | null; From 8de56fd77caca3495caa28ca22c030e4b0f71697 Mon Sep 17 00:00:00 2001 From: psychedelicious <4822129+psychedelicious@users.noreply.github.com> Date: Tue, 30 Apr 2024 13:13:09 +1000 Subject: [PATCH 39/52] tidy(ui): move regionalPrompts files to controlLayers --- .../listeners/modelsLoaded.ts | 2 +- .../listeners/promptChanged.ts | 2 +- .../regionalControlToControlAdapterBridge.ts | 4 ++-- .../listeners/setDefaultSettings.ts | 2 +- invokeai/frontend/web/src/app/store/store.ts | 10 +++++----- .../src/common/hooks/useIsReadyToEnqueue.ts | 2 +- .../components/ControlAdapterImagePreview.tsx | 2 +- .../components/AddLayerButton.tsx | 0 .../components/AddPromptButtons.tsx | 2 +- .../components/BrushSize.tsx | 2 +- .../components/CALayerOpacity.tsx | 4 ++-- .../ControlAdapterLayerListItem.tsx | 14 ++++++------- .../ControlLayersSettingsPopover.tsx | 2 +- .../components/DeleteAllLayersButton.tsx | 0 .../components/GlobalMaskLayerOpacity.tsx | 2 +- .../components/IPAdapterLayerListItem.tsx | 10 +++++----- .../components/LayerTitle.tsx | 2 +- .../MaskedGuidanceLayerListItem.tsx | 20 +++++++++---------- .../RPLayerAutoNegativeCheckbox.tsx | 2 +- .../components/RPLayerColorPicker.tsx | 2 +- .../components/RPLayerDeleteButton.tsx | 0 .../components/RPLayerIPAdapterList.tsx | 4 ++-- .../components/RPLayerMenu.tsx | 8 ++++---- .../components/RPLayerMenuArrangeActions.tsx | 2 +- .../RPLayerMenuMaskedGuidanceActions.tsx | 2 +- .../components/RPLayerNegativePrompt.tsx | 6 +++--- .../components/RPLayerPositivePrompt.tsx | 6 +++--- .../components/RPLayerPromptDeleteButton.tsx | 2 +- .../components/RPLayerSettingsPopover.tsx | 2 +- .../components/RPLayerVisibilityToggle.tsx | 4 ++-- .../RegionalPromptingEditor.stories.tsx | 2 +- .../components/RegionalPromptsEditor.tsx | 4 ++-- .../RegionalPromptsPanelContent.tsx | 14 ++++++------- .../components/RegionalPromptsToolbar.tsx | 8 ++++---- .../components/StageComponent.tsx | 6 +++--- .../components/ToolChooser.tsx | 2 +- .../components/UndoRedoButtonGroup.tsx | 2 +- .../ControlAdapterImagePreview.tsx | 2 +- .../ControlAdapterLayerConfig.tsx | 0 .../ParamControlAdapterBeginEnd.tsx | 0 .../ParamControlAdapterControlMode.tsx | 0 .../ParamControlAdapterModel.tsx | 0 .../ParamControlAdapterWeight.tsx | 0 .../hooks/layerStateHooks.ts | 2 +- .../hooks/mouseEventHooks.ts | 2 +- .../hooks/useRegionalControlTitle.ts | 2 +- .../store/regionalPromptsSlice.ts | 0 .../store/types.ts | 0 .../util/bbox.ts | 2 +- .../util/getLayerBlobs.ts | 4 ++-- .../util/renderers.ts | 8 ++++---- .../src/features/metadata/util/recallers.ts | 16 +++++++-------- .../util/graph/addControlNetToLinearGraph.ts | 2 +- .../util/graph/addIPAdapterToLinearGraph.ts | 2 +- .../util/graph/addRegionalPromptsToGraph.ts | 4 ++-- .../util/graph/addT2IAdapterToLinearGraph.ts | 2 +- .../components/Core/ParamNegativePrompt.tsx | 2 +- .../components/Core/ParamPositivePrompt.tsx | 2 +- .../ImageSize/AspectRatioCanvasPreview.tsx | 2 +- .../queue/components/QueueButtonTooltip.tsx | 2 +- .../ParamSDXLNegativeStylePrompt.tsx | 2 +- .../ParamSDXLPositiveStylePrompt.tsx | 2 +- .../SDXLPrompts/SDXLConcatButton.tsx | 2 +- .../ControlSettingsAccordion.tsx | 2 +- .../ImageSettingsAccordion.tsx | 2 +- .../ImageSizeLinear.tsx | 2 +- .../components/ParametersPanelTextToImage.tsx | 4 ++-- .../ui/components/tabs/TextToImageTab.tsx | 4 ++-- 68 files changed, 118 insertions(+), 118 deletions(-) rename invokeai/frontend/web/src/features/{regionalPrompts => controlLayers}/components/AddLayerButton.tsx (100%) rename invokeai/frontend/web/src/features/{regionalPrompts => controlLayers}/components/AddPromptButtons.tsx (97%) rename invokeai/frontend/web/src/features/{regionalPrompts => controlLayers}/components/BrushSize.tsx (97%) rename invokeai/frontend/web/src/features/{regionalPrompts => controlLayers}/components/CALayerOpacity.tsx (92%) rename invokeai/frontend/web/src/features/{regionalPrompts => controlLayers}/components/ControlAdapterLayerListItem.tsx (76%) rename invokeai/frontend/web/src/features/{regionalPrompts => controlLayers}/components/ControlLayersSettingsPopover.tsx (88%) rename invokeai/frontend/web/src/features/{regionalPrompts => controlLayers}/components/DeleteAllLayersButton.tsx (100%) rename invokeai/frontend/web/src/features/{regionalPrompts => controlLayers}/components/GlobalMaskLayerOpacity.tsx (96%) rename invokeai/frontend/web/src/features/{regionalPrompts => controlLayers}/components/IPAdapterLayerListItem.tsx (75%) rename invokeai/frontend/web/src/features/{regionalPrompts => controlLayers}/components/LayerTitle.tsx (91%) rename invokeai/frontend/web/src/features/{regionalPrompts => controlLayers}/components/MaskedGuidanceLayerListItem.tsx (77%) rename invokeai/frontend/web/src/features/{regionalPrompts => controlLayers}/components/RPLayerAutoNegativeCheckbox.tsx (96%) rename invokeai/frontend/web/src/features/{regionalPrompts => controlLayers}/components/RPLayerColorPicker.tsx (97%) rename invokeai/frontend/web/src/features/{regionalPrompts => controlLayers}/components/RPLayerDeleteButton.tsx (100%) rename invokeai/frontend/web/src/features/{regionalPrompts => controlLayers}/components/RPLayerIPAdapterList.tsx (93%) rename invokeai/frontend/web/src/features/{regionalPrompts => controlLayers}/components/RPLayerMenu.tsx (81%) rename invokeai/frontend/web/src/features/{regionalPrompts => controlLayers}/components/RPLayerMenuArrangeActions.tsx (97%) rename invokeai/frontend/web/src/features/{regionalPrompts => controlLayers}/components/RPLayerMenuMaskedGuidanceActions.tsx (97%) rename invokeai/frontend/web/src/features/{regionalPrompts => controlLayers}/components/RPLayerNegativePrompt.tsx (86%) rename invokeai/frontend/web/src/features/{regionalPrompts => controlLayers}/components/RPLayerPositivePrompt.tsx (86%) rename invokeai/frontend/web/src/features/{regionalPrompts => controlLayers}/components/RPLayerPromptDeleteButton.tsx (95%) rename invokeai/frontend/web/src/features/{regionalPrompts => controlLayers}/components/RPLayerSettingsPopover.tsx (95%) rename invokeai/frontend/web/src/features/{regionalPrompts => controlLayers}/components/RPLayerVisibilityToggle.tsx (84%) rename invokeai/frontend/web/src/features/{regionalPrompts => controlLayers}/components/RegionalPromptingEditor.stories.tsx (83%) rename invokeai/frontend/web/src/features/{regionalPrompts => controlLayers}/components/RegionalPromptsEditor.tsx (73%) rename invokeai/frontend/web/src/features/{regionalPrompts => controlLayers}/components/RegionalPromptsPanelContent.tsx (75%) rename invokeai/frontend/web/src/features/{regionalPrompts => controlLayers}/components/RegionalPromptsToolbar.tsx (52%) rename invokeai/frontend/web/src/features/{regionalPrompts => controlLayers}/components/StageComponent.tsx (97%) rename invokeai/frontend/web/src/features/{regionalPrompts => controlLayers}/components/ToolChooser.tsx (98%) rename invokeai/frontend/web/src/features/{regionalPrompts => controlLayers}/components/UndoRedoButtonGroup.tsx (95%) rename invokeai/frontend/web/src/features/{regionalPrompts => controlLayers}/components/controlAdapterOverrides/ControlAdapterImagePreview.tsx (98%) rename invokeai/frontend/web/src/features/{regionalPrompts => controlLayers}/components/controlAdapterOverrides/ControlAdapterLayerConfig.tsx (100%) rename invokeai/frontend/web/src/features/{regionalPrompts => controlLayers}/components/controlAdapterOverrides/ParamControlAdapterBeginEnd.tsx (100%) rename invokeai/frontend/web/src/features/{regionalPrompts => controlLayers}/components/controlAdapterOverrides/ParamControlAdapterControlMode.tsx (100%) rename invokeai/frontend/web/src/features/{regionalPrompts => controlLayers}/components/controlAdapterOverrides/ParamControlAdapterModel.tsx (100%) rename invokeai/frontend/web/src/features/{regionalPrompts => controlLayers}/components/controlAdapterOverrides/ParamControlAdapterWeight.tsx (100%) rename invokeai/frontend/web/src/features/{regionalPrompts => controlLayers}/hooks/layerStateHooks.ts (97%) rename invokeai/frontend/web/src/features/{regionalPrompts => controlLayers}/hooks/mouseEventHooks.ts (99%) rename invokeai/frontend/web/src/features/{regionalPrompts => controlLayers}/hooks/useRegionalControlTitle.ts (95%) rename invokeai/frontend/web/src/features/{regionalPrompts => controlLayers}/store/regionalPromptsSlice.ts (100%) rename invokeai/frontend/web/src/features/{regionalPrompts => controlLayers}/store/types.ts (100%) rename invokeai/frontend/web/src/features/{regionalPrompts => controlLayers}/util/bbox.ts (98%) rename invokeai/frontend/web/src/features/{regionalPrompts => controlLayers}/util/getLayerBlobs.ts (94%) rename invokeai/frontend/web/src/features/{regionalPrompts => controlLayers}/util/renderers.ts (98%) diff --git a/invokeai/frontend/web/src/app/store/middleware/listenerMiddleware/listeners/modelsLoaded.ts b/invokeai/frontend/web/src/app/store/middleware/listenerMiddleware/listeners/modelsLoaded.ts index 587f06720f..42b1cd36b4 100644 --- a/invokeai/frontend/web/src/app/store/middleware/listenerMiddleware/listeners/modelsLoaded.ts +++ b/invokeai/frontend/web/src/app/store/middleware/listenerMiddleware/listeners/modelsLoaded.ts @@ -6,12 +6,12 @@ import { controlAdapterModelCleared, selectControlAdapterAll, } from 'features/controlAdapters/store/controlAdaptersSlice'; +import { heightChanged, widthChanged } from 'features/controlLayers/store/regionalPromptsSlice'; import { loraRemoved } from 'features/lora/store/loraSlice'; import { calculateNewSize } from 'features/parameters/components/ImageSize/calculateNewSize'; import { modelChanged, vaeSelected } from 'features/parameters/store/generationSlice'; import { zParameterModel, zParameterVAEModel } from 'features/parameters/types/parameterSchemas'; import { getIsSizeOptimal, getOptimalDimension } from 'features/parameters/util/optimalDimension'; -import { heightChanged, widthChanged } from 'features/regionalPrompts/store/regionalPromptsSlice'; import { refinerModelChanged } from 'features/sdxl/store/sdxlSlice'; import { forEach } from 'lodash-es'; import type { Logger } from 'roarr'; diff --git a/invokeai/frontend/web/src/app/store/middleware/listenerMiddleware/listeners/promptChanged.ts b/invokeai/frontend/web/src/app/store/middleware/listenerMiddleware/listeners/promptChanged.ts index db38a0aa2e..06df2e2b92 100644 --- a/invokeai/frontend/web/src/app/store/middleware/listenerMiddleware/listeners/promptChanged.ts +++ b/invokeai/frontend/web/src/app/store/middleware/listenerMiddleware/listeners/promptChanged.ts @@ -1,5 +1,6 @@ import { isAnyOf } from '@reduxjs/toolkit'; import type { AppStartListening } from 'app/store/middleware/listenerMiddleware'; +import { positivePromptChanged } from 'features/controlLayers/store/regionalPromptsSlice'; import { combinatorialToggled, isErrorChanged, @@ -10,7 +11,6 @@ import { promptsChanged, } from 'features/dynamicPrompts/store/dynamicPromptsSlice'; import { getShouldProcessPrompt } from 'features/dynamicPrompts/util/getShouldProcessPrompt'; -import { positivePromptChanged } from 'features/regionalPrompts/store/regionalPromptsSlice'; import { utilitiesApi } from 'services/api/endpoints/utilities'; import { socketConnected } from 'services/events/actions'; diff --git a/invokeai/frontend/web/src/app/store/middleware/listenerMiddleware/listeners/regionalControlToControlAdapterBridge.ts b/invokeai/frontend/web/src/app/store/middleware/listenerMiddleware/listeners/regionalControlToControlAdapterBridge.ts index 6f53159d4b..ae7913594a 100644 --- a/invokeai/frontend/web/src/app/store/middleware/listenerMiddleware/listeners/regionalControlToControlAdapterBridge.ts +++ b/invokeai/frontend/web/src/app/store/middleware/listenerMiddleware/listeners/regionalControlToControlAdapterBridge.ts @@ -11,8 +11,8 @@ import { maskedGuidanceLayerAdded, maskLayerIPAdapterAdded, maskLayerIPAdapterDeleted, -} from 'features/regionalPrompts/store/regionalPromptsSlice'; -import type { Layer } from 'features/regionalPrompts/store/types'; +} from 'features/controlLayers/store/regionalPromptsSlice'; +import type { Layer } from 'features/controlLayers/store/types'; import { modelConfigsAdapterSelectors, modelsApi } from 'services/api/endpoints/models'; import { isControlNetModelConfig, isIPAdapterModelConfig } from 'services/api/types'; import { assert } from 'tsafe'; diff --git a/invokeai/frontend/web/src/app/store/middleware/listenerMiddleware/listeners/setDefaultSettings.ts b/invokeai/frontend/web/src/app/store/middleware/listenerMiddleware/listeners/setDefaultSettings.ts index 83fadffb26..0644e40856 100644 --- a/invokeai/frontend/web/src/app/store/middleware/listenerMiddleware/listeners/setDefaultSettings.ts +++ b/invokeai/frontend/web/src/app/store/middleware/listenerMiddleware/listeners/setDefaultSettings.ts @@ -1,4 +1,5 @@ import type { AppStartListening } from 'app/store/middleware/listenerMiddleware'; +import { heightChanged, widthChanged } from 'features/controlLayers/store/regionalPromptsSlice'; import { setDefaultSettings } from 'features/parameters/store/actions'; import { setCfgRescaleMultiplier, @@ -18,7 +19,6 @@ import { isParameterWidth, zParameterVAEModel, } from 'features/parameters/types/parameterSchemas'; -import { heightChanged, widthChanged } from 'features/regionalPrompts/store/regionalPromptsSlice'; import { addToast } from 'features/system/store/systemSlice'; import { makeToast } from 'features/system/util/makeToast'; import { t } from 'i18next'; diff --git a/invokeai/frontend/web/src/app/store/store.ts b/invokeai/frontend/web/src/app/store/store.ts index a21879cfcb..054617f77c 100644 --- a/invokeai/frontend/web/src/app/store/store.ts +++ b/invokeai/frontend/web/src/app/store/store.ts @@ -10,6 +10,11 @@ import { controlAdaptersPersistConfig, controlAdaptersSlice, } from 'features/controlAdapters/store/controlAdaptersSlice'; +import { + regionalPromptsPersistConfig, + regionalPromptsSlice, + regionalPromptsUndoableConfig, +} from 'features/controlLayers/store/regionalPromptsSlice'; import { deleteImageModalSlice } from 'features/deleteImageModal/store/slice'; import { dynamicPromptsPersistConfig, dynamicPromptsSlice } from 'features/dynamicPrompts/store/dynamicPromptsSlice'; import { galleryPersistConfig, gallerySlice } from 'features/gallery/store/gallerySlice'; @@ -21,11 +26,6 @@ import { workflowPersistConfig, workflowSlice } from 'features/nodes/store/workf import { generationPersistConfig, generationSlice } from 'features/parameters/store/generationSlice'; import { postprocessingPersistConfig, postprocessingSlice } from 'features/parameters/store/postprocessingSlice'; import { queueSlice } from 'features/queue/store/queueSlice'; -import { - regionalPromptsPersistConfig, - regionalPromptsSlice, - regionalPromptsUndoableConfig, -} from 'features/regionalPrompts/store/regionalPromptsSlice'; import { sdxlPersistConfig, sdxlSlice } from 'features/sdxl/store/sdxlSlice'; import { configSlice } from 'features/system/store/configSlice'; import { systemPersistConfig, systemSlice } from 'features/system/store/systemSlice'; diff --git a/invokeai/frontend/web/src/common/hooks/useIsReadyToEnqueue.ts b/invokeai/frontend/web/src/common/hooks/useIsReadyToEnqueue.ts index 26ce9eaf58..461c3b3032 100644 --- a/invokeai/frontend/web/src/common/hooks/useIsReadyToEnqueue.ts +++ b/invokeai/frontend/web/src/common/hooks/useIsReadyToEnqueue.ts @@ -5,12 +5,12 @@ import { selectControlAdaptersSlice, } from 'features/controlAdapters/store/controlAdaptersSlice'; import { isControlNetOrT2IAdapter } from 'features/controlAdapters/store/types'; +import { selectRegionalPromptsSlice } from 'features/controlLayers/store/regionalPromptsSlice'; import { selectDynamicPromptsSlice } from 'features/dynamicPrompts/store/dynamicPromptsSlice'; import { getShouldProcessPrompt } from 'features/dynamicPrompts/util/getShouldProcessPrompt'; import { selectNodesSlice } from 'features/nodes/store/nodesSlice'; import { isInvocationNode } from 'features/nodes/types/invocation'; import { selectGenerationSlice } from 'features/parameters/store/generationSlice'; -import { selectRegionalPromptsSlice } from 'features/regionalPrompts/store/regionalPromptsSlice'; import { selectSystemSlice } from 'features/system/store/systemSlice'; import { activeTabNameSelector } from 'features/ui/store/uiSelectors'; import i18n from 'i18next'; diff --git a/invokeai/frontend/web/src/features/controlAdapters/components/ControlAdapterImagePreview.tsx b/invokeai/frontend/web/src/features/controlAdapters/components/ControlAdapterImagePreview.tsx index 33269656ad..017cbe4bf6 100644 --- a/invokeai/frontend/web/src/features/controlAdapters/components/ControlAdapterImagePreview.tsx +++ b/invokeai/frontend/web/src/features/controlAdapters/components/ControlAdapterImagePreview.tsx @@ -13,10 +13,10 @@ import { controlAdapterImageChanged, selectControlAdaptersSlice, } from 'features/controlAdapters/store/controlAdaptersSlice'; +import { heightChanged, widthChanged } from 'features/controlLayers/store/regionalPromptsSlice'; import type { TypesafeDraggableData, TypesafeDroppableData } from 'features/dnd/types'; import { calculateNewSize } from 'features/parameters/components/ImageSize/calculateNewSize'; import { selectOptimalDimension } from 'features/parameters/store/generationSlice'; -import { heightChanged, widthChanged } from 'features/regionalPrompts/store/regionalPromptsSlice'; import { activeTabNameSelector } from 'features/ui/store/uiSelectors'; import { memo, useCallback, useEffect, useMemo, useState } from 'react'; import { useTranslation } from 'react-i18next'; diff --git a/invokeai/frontend/web/src/features/regionalPrompts/components/AddLayerButton.tsx b/invokeai/frontend/web/src/features/controlLayers/components/AddLayerButton.tsx similarity index 100% rename from invokeai/frontend/web/src/features/regionalPrompts/components/AddLayerButton.tsx rename to invokeai/frontend/web/src/features/controlLayers/components/AddLayerButton.tsx diff --git a/invokeai/frontend/web/src/features/regionalPrompts/components/AddPromptButtons.tsx b/invokeai/frontend/web/src/features/controlLayers/components/AddPromptButtons.tsx similarity index 97% rename from invokeai/frontend/web/src/features/regionalPrompts/components/AddPromptButtons.tsx rename to invokeai/frontend/web/src/features/controlLayers/components/AddPromptButtons.tsx index ebb7ef217d..0ed7a479c4 100644 --- a/invokeai/frontend/web/src/features/regionalPrompts/components/AddPromptButtons.tsx +++ b/invokeai/frontend/web/src/features/controlLayers/components/AddPromptButtons.tsx @@ -7,7 +7,7 @@ import { maskLayerNegativePromptChanged, maskLayerPositivePromptChanged, selectRegionalPromptsSlice, -} from 'features/regionalPrompts/store/regionalPromptsSlice'; +} from 'features/controlLayers/store/regionalPromptsSlice'; import { useCallback, useMemo } from 'react'; import { useTranslation } from 'react-i18next'; import { PiPlusBold } from 'react-icons/pi'; diff --git a/invokeai/frontend/web/src/features/regionalPrompts/components/BrushSize.tsx b/invokeai/frontend/web/src/features/controlLayers/components/BrushSize.tsx similarity index 97% rename from invokeai/frontend/web/src/features/regionalPrompts/components/BrushSize.tsx rename to invokeai/frontend/web/src/features/controlLayers/components/BrushSize.tsx index ae042f848d..f145a47e75 100644 --- a/invokeai/frontend/web/src/features/regionalPrompts/components/BrushSize.tsx +++ b/invokeai/frontend/web/src/features/controlLayers/components/BrushSize.tsx @@ -10,7 +10,7 @@ import { PopoverTrigger, } from '@invoke-ai/ui-library'; import { useAppDispatch, useAppSelector } from 'app/store/storeHooks'; -import { brushSizeChanged, initialRegionalPromptsState } from 'features/regionalPrompts/store/regionalPromptsSlice'; +import { brushSizeChanged, initialRegionalPromptsState } from 'features/controlLayers/store/regionalPromptsSlice'; import { memo, useCallback } from 'react'; import { useTranslation } from 'react-i18next'; diff --git a/invokeai/frontend/web/src/features/regionalPrompts/components/CALayerOpacity.tsx b/invokeai/frontend/web/src/features/controlLayers/components/CALayerOpacity.tsx similarity index 92% rename from invokeai/frontend/web/src/features/regionalPrompts/components/CALayerOpacity.tsx rename to invokeai/frontend/web/src/features/controlLayers/components/CALayerOpacity.tsx index 9d7649be2e..a44ed56e1d 100644 --- a/invokeai/frontend/web/src/features/regionalPrompts/components/CALayerOpacity.tsx +++ b/invokeai/frontend/web/src/features/controlLayers/components/CALayerOpacity.tsx @@ -12,8 +12,8 @@ import { PopoverTrigger, } from '@invoke-ai/ui-library'; import { useAppDispatch } from 'app/store/storeHooks'; -import { useLayerOpacity } from 'features/regionalPrompts/hooks/layerStateHooks'; -import { layerOpacityChanged } from 'features/regionalPrompts/store/regionalPromptsSlice'; +import { useLayerOpacity } from 'features/controlLayers/hooks/layerStateHooks'; +import { layerOpacityChanged } from 'features/controlLayers/store/regionalPromptsSlice'; import { memo, useCallback } from 'react'; import { useTranslation } from 'react-i18next'; import { PiDropHalfFill } from 'react-icons/pi'; diff --git a/invokeai/frontend/web/src/features/regionalPrompts/components/ControlAdapterLayerListItem.tsx b/invokeai/frontend/web/src/features/controlLayers/components/ControlAdapterLayerListItem.tsx similarity index 76% rename from invokeai/frontend/web/src/features/regionalPrompts/components/ControlAdapterLayerListItem.tsx rename to invokeai/frontend/web/src/features/controlLayers/components/ControlAdapterLayerListItem.tsx index 95c2c5fbb1..0c501e5e45 100644 --- a/invokeai/frontend/web/src/features/regionalPrompts/components/ControlAdapterLayerListItem.tsx +++ b/invokeai/frontend/web/src/features/controlLayers/components/ControlAdapterLayerListItem.tsx @@ -1,17 +1,17 @@ import { Flex, Spacer } from '@invoke-ai/ui-library'; import { createMemoizedSelector } from 'app/store/createMemoizedSelector'; import { useAppDispatch, useAppSelector } from 'app/store/storeHooks'; -import CALayerOpacity from 'features/regionalPrompts/components/CALayerOpacity'; -import ControlAdapterLayerConfig from 'features/regionalPrompts/components/controlAdapterOverrides/ControlAdapterLayerConfig'; -import { LayerTitle } from 'features/regionalPrompts/components/LayerTitle'; -import { RPLayerDeleteButton } from 'features/regionalPrompts/components/RPLayerDeleteButton'; -import { RPLayerMenu } from 'features/regionalPrompts/components/RPLayerMenu'; -import { RPLayerVisibilityToggle } from 'features/regionalPrompts/components/RPLayerVisibilityToggle'; +import CALayerOpacity from 'features/controlLayers/components/CALayerOpacity'; +import ControlAdapterLayerConfig from 'features/controlLayers/components/controlAdapterOverrides/ControlAdapterLayerConfig'; +import { LayerTitle } from 'features/controlLayers/components/LayerTitle'; +import { RPLayerDeleteButton } from 'features/controlLayers/components/RPLayerDeleteButton'; +import { RPLayerMenu } from 'features/controlLayers/components/RPLayerMenu'; +import { RPLayerVisibilityToggle } from 'features/controlLayers/components/RPLayerVisibilityToggle'; import { isControlAdapterLayer, layerSelected, selectRegionalPromptsSlice, -} from 'features/regionalPrompts/store/regionalPromptsSlice'; +} from 'features/controlLayers/store/regionalPromptsSlice'; import { memo, useCallback, useMemo } from 'react'; import { assert } from 'tsafe'; diff --git a/invokeai/frontend/web/src/features/regionalPrompts/components/ControlLayersSettingsPopover.tsx b/invokeai/frontend/web/src/features/controlLayers/components/ControlLayersSettingsPopover.tsx similarity index 88% rename from invokeai/frontend/web/src/features/regionalPrompts/components/ControlLayersSettingsPopover.tsx rename to invokeai/frontend/web/src/features/controlLayers/components/ControlLayersSettingsPopover.tsx index ea3f7af325..89032b7c76 100644 --- a/invokeai/frontend/web/src/features/regionalPrompts/components/ControlLayersSettingsPopover.tsx +++ b/invokeai/frontend/web/src/features/controlLayers/components/ControlLayersSettingsPopover.tsx @@ -1,5 +1,5 @@ import { Flex, IconButton, Popover, PopoverBody, PopoverContent, PopoverTrigger } from '@invoke-ai/ui-library'; -import { GlobalMaskLayerOpacity } from 'features/regionalPrompts/components/GlobalMaskLayerOpacity'; +import { GlobalMaskLayerOpacity } from 'features/controlLayers/components/GlobalMaskLayerOpacity'; import { memo } from 'react'; import { useTranslation } from 'react-i18next'; import { RiSettings4Fill } from 'react-icons/ri'; diff --git a/invokeai/frontend/web/src/features/regionalPrompts/components/DeleteAllLayersButton.tsx b/invokeai/frontend/web/src/features/controlLayers/components/DeleteAllLayersButton.tsx similarity index 100% rename from invokeai/frontend/web/src/features/regionalPrompts/components/DeleteAllLayersButton.tsx rename to invokeai/frontend/web/src/features/controlLayers/components/DeleteAllLayersButton.tsx diff --git a/invokeai/frontend/web/src/features/regionalPrompts/components/GlobalMaskLayerOpacity.tsx b/invokeai/frontend/web/src/features/controlLayers/components/GlobalMaskLayerOpacity.tsx similarity index 96% rename from invokeai/frontend/web/src/features/regionalPrompts/components/GlobalMaskLayerOpacity.tsx rename to invokeai/frontend/web/src/features/controlLayers/components/GlobalMaskLayerOpacity.tsx index c24e224a61..3769b40bc7 100644 --- a/invokeai/frontend/web/src/features/regionalPrompts/components/GlobalMaskLayerOpacity.tsx +++ b/invokeai/frontend/web/src/features/controlLayers/components/GlobalMaskLayerOpacity.tsx @@ -3,7 +3,7 @@ import { useAppDispatch, useAppSelector } from 'app/store/storeHooks'; import { globalMaskLayerOpacityChanged, initialRegionalPromptsState, -} from 'features/regionalPrompts/store/regionalPromptsSlice'; +} from 'features/controlLayers/store/regionalPromptsSlice'; import { memo, useCallback } from 'react'; import { useTranslation } from 'react-i18next'; diff --git a/invokeai/frontend/web/src/features/regionalPrompts/components/IPAdapterLayerListItem.tsx b/invokeai/frontend/web/src/features/controlLayers/components/IPAdapterLayerListItem.tsx similarity index 75% rename from invokeai/frontend/web/src/features/regionalPrompts/components/IPAdapterLayerListItem.tsx rename to invokeai/frontend/web/src/features/controlLayers/components/IPAdapterLayerListItem.tsx index 9673a301d7..0a07f29e82 100644 --- a/invokeai/frontend/web/src/features/regionalPrompts/components/IPAdapterLayerListItem.tsx +++ b/invokeai/frontend/web/src/features/controlLayers/components/IPAdapterLayerListItem.tsx @@ -1,11 +1,11 @@ import { Flex, Spacer } from '@invoke-ai/ui-library'; import { createMemoizedSelector } from 'app/store/createMemoizedSelector'; import { useAppSelector } from 'app/store/storeHooks'; -import ControlAdapterLayerConfig from 'features/regionalPrompts/components/controlAdapterOverrides/ControlAdapterLayerConfig'; -import { LayerTitle } from 'features/regionalPrompts/components/LayerTitle'; -import { RPLayerDeleteButton } from 'features/regionalPrompts/components/RPLayerDeleteButton'; -import { RPLayerVisibilityToggle } from 'features/regionalPrompts/components/RPLayerVisibilityToggle'; -import { isIPAdapterLayer, selectRegionalPromptsSlice } from 'features/regionalPrompts/store/regionalPromptsSlice'; +import ControlAdapterLayerConfig from 'features/controlLayers/components/controlAdapterOverrides/ControlAdapterLayerConfig'; +import { LayerTitle } from 'features/controlLayers/components/LayerTitle'; +import { RPLayerDeleteButton } from 'features/controlLayers/components/RPLayerDeleteButton'; +import { RPLayerVisibilityToggle } from 'features/controlLayers/components/RPLayerVisibilityToggle'; +import { isIPAdapterLayer, selectRegionalPromptsSlice } from 'features/controlLayers/store/regionalPromptsSlice'; import { memo, useMemo } from 'react'; import { assert } from 'tsafe'; diff --git a/invokeai/frontend/web/src/features/regionalPrompts/components/LayerTitle.tsx b/invokeai/frontend/web/src/features/controlLayers/components/LayerTitle.tsx similarity index 91% rename from invokeai/frontend/web/src/features/regionalPrompts/components/LayerTitle.tsx rename to invokeai/frontend/web/src/features/controlLayers/components/LayerTitle.tsx index c5eafe92c2..a14ce1fd96 100644 --- a/invokeai/frontend/web/src/features/regionalPrompts/components/LayerTitle.tsx +++ b/invokeai/frontend/web/src/features/controlLayers/components/LayerTitle.tsx @@ -1,5 +1,5 @@ import { Text } from '@invoke-ai/ui-library'; -import type { Layer } from 'features/regionalPrompts/store/types'; +import type { Layer } from 'features/controlLayers/store/types'; import { memo, useMemo } from 'react'; import { useTranslation } from 'react-i18next'; diff --git a/invokeai/frontend/web/src/features/regionalPrompts/components/MaskedGuidanceLayerListItem.tsx b/invokeai/frontend/web/src/features/controlLayers/components/MaskedGuidanceLayerListItem.tsx similarity index 77% rename from invokeai/frontend/web/src/features/regionalPrompts/components/MaskedGuidanceLayerListItem.tsx rename to invokeai/frontend/web/src/features/controlLayers/components/MaskedGuidanceLayerListItem.tsx index ce18daed0e..11b285dae0 100644 --- a/invokeai/frontend/web/src/features/regionalPrompts/components/MaskedGuidanceLayerListItem.tsx +++ b/invokeai/frontend/web/src/features/controlLayers/components/MaskedGuidanceLayerListItem.tsx @@ -2,20 +2,20 @@ import { Badge, Flex, Spacer } from '@invoke-ai/ui-library'; import { createMemoizedSelector } from 'app/store/createMemoizedSelector'; import { useAppDispatch, useAppSelector } from 'app/store/storeHooks'; import { rgbColorToString } from 'features/canvas/util/colorToString'; -import { LayerTitle } from 'features/regionalPrompts/components/LayerTitle'; -import { RPLayerColorPicker } from 'features/regionalPrompts/components/RPLayerColorPicker'; -import { RPLayerDeleteButton } from 'features/regionalPrompts/components/RPLayerDeleteButton'; -import { RPLayerIPAdapterList } from 'features/regionalPrompts/components/RPLayerIPAdapterList'; -import { RPLayerMenu } from 'features/regionalPrompts/components/RPLayerMenu'; -import { RPLayerNegativePrompt } from 'features/regionalPrompts/components/RPLayerNegativePrompt'; -import { RPLayerPositivePrompt } from 'features/regionalPrompts/components/RPLayerPositivePrompt'; -import RPLayerSettingsPopover from 'features/regionalPrompts/components/RPLayerSettingsPopover'; -import { RPLayerVisibilityToggle } from 'features/regionalPrompts/components/RPLayerVisibilityToggle'; +import { LayerTitle } from 'features/controlLayers/components/LayerTitle'; +import { RPLayerColorPicker } from 'features/controlLayers/components/RPLayerColorPicker'; +import { RPLayerDeleteButton } from 'features/controlLayers/components/RPLayerDeleteButton'; +import { RPLayerIPAdapterList } from 'features/controlLayers/components/RPLayerIPAdapterList'; +import { RPLayerMenu } from 'features/controlLayers/components/RPLayerMenu'; +import { RPLayerNegativePrompt } from 'features/controlLayers/components/RPLayerNegativePrompt'; +import { RPLayerPositivePrompt } from 'features/controlLayers/components/RPLayerPositivePrompt'; +import RPLayerSettingsPopover from 'features/controlLayers/components/RPLayerSettingsPopover'; +import { RPLayerVisibilityToggle } from 'features/controlLayers/components/RPLayerVisibilityToggle'; import { isMaskedGuidanceLayer, layerSelected, selectRegionalPromptsSlice, -} from 'features/regionalPrompts/store/regionalPromptsSlice'; +} from 'features/controlLayers/store/regionalPromptsSlice'; import { memo, useCallback, useMemo } from 'react'; import { useTranslation } from 'react-i18next'; import { assert } from 'tsafe'; diff --git a/invokeai/frontend/web/src/features/regionalPrompts/components/RPLayerAutoNegativeCheckbox.tsx b/invokeai/frontend/web/src/features/controlLayers/components/RPLayerAutoNegativeCheckbox.tsx similarity index 96% rename from invokeai/frontend/web/src/features/regionalPrompts/components/RPLayerAutoNegativeCheckbox.tsx rename to invokeai/frontend/web/src/features/controlLayers/components/RPLayerAutoNegativeCheckbox.tsx index 205b4b058e..ec9ac5b24e 100644 --- a/invokeai/frontend/web/src/features/regionalPrompts/components/RPLayerAutoNegativeCheckbox.tsx +++ b/invokeai/frontend/web/src/features/controlLayers/components/RPLayerAutoNegativeCheckbox.tsx @@ -5,7 +5,7 @@ import { isMaskedGuidanceLayer, maskLayerAutoNegativeChanged, selectRegionalPromptsSlice, -} from 'features/regionalPrompts/store/regionalPromptsSlice'; +} from 'features/controlLayers/store/regionalPromptsSlice'; import type { ChangeEvent } from 'react'; import { memo, useCallback, useMemo } from 'react'; import { useTranslation } from 'react-i18next'; diff --git a/invokeai/frontend/web/src/features/regionalPrompts/components/RPLayerColorPicker.tsx b/invokeai/frontend/web/src/features/controlLayers/components/RPLayerColorPicker.tsx similarity index 97% rename from invokeai/frontend/web/src/features/regionalPrompts/components/RPLayerColorPicker.tsx rename to invokeai/frontend/web/src/features/controlLayers/components/RPLayerColorPicker.tsx index 81c4b17fdd..550ff1a2bc 100644 --- a/invokeai/frontend/web/src/features/regionalPrompts/components/RPLayerColorPicker.tsx +++ b/invokeai/frontend/web/src/features/controlLayers/components/RPLayerColorPicker.tsx @@ -7,7 +7,7 @@ import { isMaskedGuidanceLayer, maskLayerPreviewColorChanged, selectRegionalPromptsSlice, -} from 'features/regionalPrompts/store/regionalPromptsSlice'; +} from 'features/controlLayers/store/regionalPromptsSlice'; import { memo, useCallback, useMemo } from 'react'; import type { RgbColor } from 'react-colorful'; import { useTranslation } from 'react-i18next'; diff --git a/invokeai/frontend/web/src/features/regionalPrompts/components/RPLayerDeleteButton.tsx b/invokeai/frontend/web/src/features/controlLayers/components/RPLayerDeleteButton.tsx similarity index 100% rename from invokeai/frontend/web/src/features/regionalPrompts/components/RPLayerDeleteButton.tsx rename to invokeai/frontend/web/src/features/controlLayers/components/RPLayerDeleteButton.tsx diff --git a/invokeai/frontend/web/src/features/regionalPrompts/components/RPLayerIPAdapterList.tsx b/invokeai/frontend/web/src/features/controlLayers/components/RPLayerIPAdapterList.tsx similarity index 93% rename from invokeai/frontend/web/src/features/regionalPrompts/components/RPLayerIPAdapterList.tsx rename to invokeai/frontend/web/src/features/controlLayers/components/RPLayerIPAdapterList.tsx index 73f04cb23f..7f13a6f281 100644 --- a/invokeai/frontend/web/src/features/regionalPrompts/components/RPLayerIPAdapterList.tsx +++ b/invokeai/frontend/web/src/features/controlLayers/components/RPLayerIPAdapterList.tsx @@ -2,8 +2,8 @@ import { Divider, Flex, IconButton, Spacer, Text } from '@invoke-ai/ui-library'; import { createMemoizedSelector } from 'app/store/createMemoizedSelector'; import { guidanceLayerIPAdapterDeleted } from 'app/store/middleware/listenerMiddleware/listeners/regionalControlToControlAdapterBridge'; import { useAppDispatch, useAppSelector } from 'app/store/storeHooks'; -import ControlAdapterLayerConfig from 'features/regionalPrompts/components/controlAdapterOverrides/ControlAdapterLayerConfig'; -import { isMaskedGuidanceLayer, selectRegionalPromptsSlice } from 'features/regionalPrompts/store/regionalPromptsSlice'; +import ControlAdapterLayerConfig from 'features/controlLayers/components/controlAdapterOverrides/ControlAdapterLayerConfig'; +import { isMaskedGuidanceLayer, selectRegionalPromptsSlice } from 'features/controlLayers/store/regionalPromptsSlice'; import { memo, useCallback, useMemo } from 'react'; import { PiTrashSimpleBold } from 'react-icons/pi'; import { assert } from 'tsafe'; diff --git a/invokeai/frontend/web/src/features/regionalPrompts/components/RPLayerMenu.tsx b/invokeai/frontend/web/src/features/controlLayers/components/RPLayerMenu.tsx similarity index 81% rename from invokeai/frontend/web/src/features/regionalPrompts/components/RPLayerMenu.tsx rename to invokeai/frontend/web/src/features/controlLayers/components/RPLayerMenu.tsx index 16619e924e..41e1fb3aa8 100644 --- a/invokeai/frontend/web/src/features/regionalPrompts/components/RPLayerMenu.tsx +++ b/invokeai/frontend/web/src/features/controlLayers/components/RPLayerMenu.tsx @@ -1,9 +1,9 @@ import { IconButton, Menu, MenuButton, MenuDivider, MenuItem, MenuList } from '@invoke-ai/ui-library'; import { useAppDispatch } from 'app/store/storeHooks'; -import { RPLayerMenuArrangeActions } from 'features/regionalPrompts/components/RPLayerMenuArrangeActions'; -import { RPLayerMenuMaskedGuidanceActions } from 'features/regionalPrompts/components/RPLayerMenuMaskedGuidanceActions'; -import { useLayerType } from 'features/regionalPrompts/hooks/layerStateHooks'; -import { layerDeleted, layerReset } from 'features/regionalPrompts/store/regionalPromptsSlice'; +import { RPLayerMenuArrangeActions } from 'features/controlLayers/components/RPLayerMenuArrangeActions'; +import { RPLayerMenuMaskedGuidanceActions } from 'features/controlLayers/components/RPLayerMenuMaskedGuidanceActions'; +import { useLayerType } from 'features/controlLayers/hooks/layerStateHooks'; +import { layerDeleted, layerReset } from 'features/controlLayers/store/regionalPromptsSlice'; import { memo, useCallback } from 'react'; import { useTranslation } from 'react-i18next'; import { PiArrowCounterClockwiseBold, PiDotsThreeVerticalBold, PiTrashSimpleBold } from 'react-icons/pi'; diff --git a/invokeai/frontend/web/src/features/regionalPrompts/components/RPLayerMenuArrangeActions.tsx b/invokeai/frontend/web/src/features/controlLayers/components/RPLayerMenuArrangeActions.tsx similarity index 97% rename from invokeai/frontend/web/src/features/regionalPrompts/components/RPLayerMenuArrangeActions.tsx rename to invokeai/frontend/web/src/features/controlLayers/components/RPLayerMenuArrangeActions.tsx index 99878cb95c..deb74d4cc5 100644 --- a/invokeai/frontend/web/src/features/regionalPrompts/components/RPLayerMenuArrangeActions.tsx +++ b/invokeai/frontend/web/src/features/controlLayers/components/RPLayerMenuArrangeActions.tsx @@ -8,7 +8,7 @@ import { layerMovedToBack, layerMovedToFront, selectRegionalPromptsSlice, -} from 'features/regionalPrompts/store/regionalPromptsSlice'; +} from 'features/controlLayers/store/regionalPromptsSlice'; import { memo, useCallback, useMemo } from 'react'; import { useTranslation } from 'react-i18next'; import { PiArrowDownBold, PiArrowLineDownBold, PiArrowLineUpBold, PiArrowUpBold } from 'react-icons/pi'; diff --git a/invokeai/frontend/web/src/features/regionalPrompts/components/RPLayerMenuMaskedGuidanceActions.tsx b/invokeai/frontend/web/src/features/controlLayers/components/RPLayerMenuMaskedGuidanceActions.tsx similarity index 97% rename from invokeai/frontend/web/src/features/regionalPrompts/components/RPLayerMenuMaskedGuidanceActions.tsx rename to invokeai/frontend/web/src/features/controlLayers/components/RPLayerMenuMaskedGuidanceActions.tsx index 542f08c379..674c9d3a26 100644 --- a/invokeai/frontend/web/src/features/regionalPrompts/components/RPLayerMenuMaskedGuidanceActions.tsx +++ b/invokeai/frontend/web/src/features/controlLayers/components/RPLayerMenuMaskedGuidanceActions.tsx @@ -7,7 +7,7 @@ import { maskLayerNegativePromptChanged, maskLayerPositivePromptChanged, selectRegionalPromptsSlice, -} from 'features/regionalPrompts/store/regionalPromptsSlice'; +} from 'features/controlLayers/store/regionalPromptsSlice'; import { memo, useCallback, useMemo } from 'react'; import { useTranslation } from 'react-i18next'; import { PiPlusBold } from 'react-icons/pi'; diff --git a/invokeai/frontend/web/src/features/regionalPrompts/components/RPLayerNegativePrompt.tsx b/invokeai/frontend/web/src/features/controlLayers/components/RPLayerNegativePrompt.tsx similarity index 86% rename from invokeai/frontend/web/src/features/regionalPrompts/components/RPLayerNegativePrompt.tsx rename to invokeai/frontend/web/src/features/controlLayers/components/RPLayerNegativePrompt.tsx index 382b698b8f..c81c7009fe 100644 --- a/invokeai/frontend/web/src/features/regionalPrompts/components/RPLayerNegativePrompt.tsx +++ b/invokeai/frontend/web/src/features/controlLayers/components/RPLayerNegativePrompt.tsx @@ -1,12 +1,12 @@ import { Box, Textarea } from '@invoke-ai/ui-library'; import { useAppDispatch } from 'app/store/storeHooks'; +import { RPLayerPromptDeleteButton } from 'features/controlLayers/components/RPLayerPromptDeleteButton'; +import { useLayerNegativePrompt } from 'features/controlLayers/hooks/layerStateHooks'; +import { maskLayerNegativePromptChanged } from 'features/controlLayers/store/regionalPromptsSlice'; import { PromptOverlayButtonWrapper } from 'features/parameters/components/Prompts/PromptOverlayButtonWrapper'; import { AddPromptTriggerButton } from 'features/prompt/AddPromptTriggerButton'; import { PromptPopover } from 'features/prompt/PromptPopover'; import { usePrompt } from 'features/prompt/usePrompt'; -import { RPLayerPromptDeleteButton } from 'features/regionalPrompts/components/RPLayerPromptDeleteButton'; -import { useLayerNegativePrompt } from 'features/regionalPrompts/hooks/layerStateHooks'; -import { maskLayerNegativePromptChanged } from 'features/regionalPrompts/store/regionalPromptsSlice'; import { memo, useCallback, useRef } from 'react'; import { useTranslation } from 'react-i18next'; diff --git a/invokeai/frontend/web/src/features/regionalPrompts/components/RPLayerPositivePrompt.tsx b/invokeai/frontend/web/src/features/controlLayers/components/RPLayerPositivePrompt.tsx similarity index 86% rename from invokeai/frontend/web/src/features/regionalPrompts/components/RPLayerPositivePrompt.tsx rename to invokeai/frontend/web/src/features/controlLayers/components/RPLayerPositivePrompt.tsx index 595a44e83e..b99c529e34 100644 --- a/invokeai/frontend/web/src/features/regionalPrompts/components/RPLayerPositivePrompt.tsx +++ b/invokeai/frontend/web/src/features/controlLayers/components/RPLayerPositivePrompt.tsx @@ -1,12 +1,12 @@ import { Box, Textarea } from '@invoke-ai/ui-library'; import { useAppDispatch } from 'app/store/storeHooks'; +import { RPLayerPromptDeleteButton } from 'features/controlLayers/components/RPLayerPromptDeleteButton'; +import { useLayerPositivePrompt } from 'features/controlLayers/hooks/layerStateHooks'; +import { maskLayerPositivePromptChanged } from 'features/controlLayers/store/regionalPromptsSlice'; import { PromptOverlayButtonWrapper } from 'features/parameters/components/Prompts/PromptOverlayButtonWrapper'; import { AddPromptTriggerButton } from 'features/prompt/AddPromptTriggerButton'; import { PromptPopover } from 'features/prompt/PromptPopover'; import { usePrompt } from 'features/prompt/usePrompt'; -import { RPLayerPromptDeleteButton } from 'features/regionalPrompts/components/RPLayerPromptDeleteButton'; -import { useLayerPositivePrompt } from 'features/regionalPrompts/hooks/layerStateHooks'; -import { maskLayerPositivePromptChanged } from 'features/regionalPrompts/store/regionalPromptsSlice'; import { memo, useCallback, useRef } from 'react'; import { useTranslation } from 'react-i18next'; diff --git a/invokeai/frontend/web/src/features/regionalPrompts/components/RPLayerPromptDeleteButton.tsx b/invokeai/frontend/web/src/features/controlLayers/components/RPLayerPromptDeleteButton.tsx similarity index 95% rename from invokeai/frontend/web/src/features/regionalPrompts/components/RPLayerPromptDeleteButton.tsx rename to invokeai/frontend/web/src/features/controlLayers/components/RPLayerPromptDeleteButton.tsx index 7448e3a035..ae3c9317fd 100644 --- a/invokeai/frontend/web/src/features/regionalPrompts/components/RPLayerPromptDeleteButton.tsx +++ b/invokeai/frontend/web/src/features/controlLayers/components/RPLayerPromptDeleteButton.tsx @@ -3,7 +3,7 @@ import { useAppDispatch } from 'app/store/storeHooks'; import { maskLayerNegativePromptChanged, maskLayerPositivePromptChanged, -} from 'features/regionalPrompts/store/regionalPromptsSlice'; +} from 'features/controlLayers/store/regionalPromptsSlice'; import { memo, useCallback } from 'react'; import { useTranslation } from 'react-i18next'; import { PiTrashSimpleBold } from 'react-icons/pi'; diff --git a/invokeai/frontend/web/src/features/regionalPrompts/components/RPLayerSettingsPopover.tsx b/invokeai/frontend/web/src/features/controlLayers/components/RPLayerSettingsPopover.tsx similarity index 95% rename from invokeai/frontend/web/src/features/regionalPrompts/components/RPLayerSettingsPopover.tsx rename to invokeai/frontend/web/src/features/controlLayers/components/RPLayerSettingsPopover.tsx index 10495f0900..cf6674db5d 100644 --- a/invokeai/frontend/web/src/features/regionalPrompts/components/RPLayerSettingsPopover.tsx +++ b/invokeai/frontend/web/src/features/controlLayers/components/RPLayerSettingsPopover.tsx @@ -9,7 +9,7 @@ import { PopoverContent, PopoverTrigger, } from '@invoke-ai/ui-library'; -import { MaskedGuidanceLayerAutoNegativeCheckbox } from 'features/regionalPrompts/components/RPLayerAutoNegativeCheckbox'; +import { MaskedGuidanceLayerAutoNegativeCheckbox } from 'features/controlLayers/components/RPLayerAutoNegativeCheckbox'; import { memo } from 'react'; import { useTranslation } from 'react-i18next'; import { PiGearSixBold } from 'react-icons/pi'; diff --git a/invokeai/frontend/web/src/features/regionalPrompts/components/RPLayerVisibilityToggle.tsx b/invokeai/frontend/web/src/features/controlLayers/components/RPLayerVisibilityToggle.tsx similarity index 84% rename from invokeai/frontend/web/src/features/regionalPrompts/components/RPLayerVisibilityToggle.tsx rename to invokeai/frontend/web/src/features/controlLayers/components/RPLayerVisibilityToggle.tsx index 4f9e5e84b4..67d249191c 100644 --- a/invokeai/frontend/web/src/features/regionalPrompts/components/RPLayerVisibilityToggle.tsx +++ b/invokeai/frontend/web/src/features/controlLayers/components/RPLayerVisibilityToggle.tsx @@ -1,7 +1,7 @@ import { IconButton } from '@invoke-ai/ui-library'; import { useAppDispatch } from 'app/store/storeHooks'; -import { useLayerIsVisible } from 'features/regionalPrompts/hooks/layerStateHooks'; -import { layerVisibilityToggled } from 'features/regionalPrompts/store/regionalPromptsSlice'; +import { useLayerIsVisible } from 'features/controlLayers/hooks/layerStateHooks'; +import { layerVisibilityToggled } from 'features/controlLayers/store/regionalPromptsSlice'; import { memo, useCallback } from 'react'; import { useTranslation } from 'react-i18next'; import { PiCheckBold } from 'react-icons/pi'; diff --git a/invokeai/frontend/web/src/features/regionalPrompts/components/RegionalPromptingEditor.stories.tsx b/invokeai/frontend/web/src/features/controlLayers/components/RegionalPromptingEditor.stories.tsx similarity index 83% rename from invokeai/frontend/web/src/features/regionalPrompts/components/RegionalPromptingEditor.stories.tsx rename to invokeai/frontend/web/src/features/controlLayers/components/RegionalPromptingEditor.stories.tsx index 943be227c8..8d822fb954 100644 --- a/invokeai/frontend/web/src/features/regionalPrompts/components/RegionalPromptingEditor.stories.tsx +++ b/invokeai/frontend/web/src/features/controlLayers/components/RegionalPromptingEditor.stories.tsx @@ -1,6 +1,6 @@ import { Flex } from '@invoke-ai/ui-library'; import type { Meta, StoryObj } from '@storybook/react'; -import { RegionalPromptsEditor } from 'features/regionalPrompts/components/RegionalPromptsEditor'; +import { RegionalPromptsEditor } from 'features/controlLayers/components/RegionalPromptsEditor'; const meta: Meta = { title: 'Feature/RegionalPrompts', diff --git a/invokeai/frontend/web/src/features/regionalPrompts/components/RegionalPromptsEditor.tsx b/invokeai/frontend/web/src/features/controlLayers/components/RegionalPromptsEditor.tsx similarity index 73% rename from invokeai/frontend/web/src/features/regionalPrompts/components/RegionalPromptsEditor.tsx rename to invokeai/frontend/web/src/features/controlLayers/components/RegionalPromptsEditor.tsx index dd2e797235..cf190a444c 100644 --- a/invokeai/frontend/web/src/features/regionalPrompts/components/RegionalPromptsEditor.tsx +++ b/invokeai/frontend/web/src/features/controlLayers/components/RegionalPromptsEditor.tsx @@ -1,7 +1,7 @@ /* eslint-disable i18next/no-literal-string */ import { Flex } from '@invoke-ai/ui-library'; -import { RegionalPromptsToolbar } from 'features/regionalPrompts/components/RegionalPromptsToolbar'; -import { StageComponent } from 'features/regionalPrompts/components/StageComponent'; +import { RegionalPromptsToolbar } from 'features/controlLayers/components/RegionalPromptsToolbar'; +import { StageComponent } from 'features/controlLayers/components/StageComponent'; import { memo } from 'react'; export const RegionalPromptsEditor = memo(() => { diff --git a/invokeai/frontend/web/src/features/regionalPrompts/components/RegionalPromptsPanelContent.tsx b/invokeai/frontend/web/src/features/controlLayers/components/RegionalPromptsPanelContent.tsx similarity index 75% rename from invokeai/frontend/web/src/features/regionalPrompts/components/RegionalPromptsPanelContent.tsx rename to invokeai/frontend/web/src/features/controlLayers/components/RegionalPromptsPanelContent.tsx index e975d33056..59853ddda3 100644 --- a/invokeai/frontend/web/src/features/regionalPrompts/components/RegionalPromptsPanelContent.tsx +++ b/invokeai/frontend/web/src/features/controlLayers/components/RegionalPromptsPanelContent.tsx @@ -3,13 +3,13 @@ import { Flex } from '@invoke-ai/ui-library'; import { createMemoizedSelector } from 'app/store/createMemoizedSelector'; import { useAppSelector } from 'app/store/storeHooks'; import ScrollableContent from 'common/components/OverlayScrollbars/ScrollableContent'; -import { AddLayerButton } from 'features/regionalPrompts/components/AddLayerButton'; -import { ControlAdapterLayerListItem } from 'features/regionalPrompts/components/ControlAdapterLayerListItem'; -import { DeleteAllLayersButton } from 'features/regionalPrompts/components/DeleteAllLayersButton'; -import { IPAdapterLayerListItem } from 'features/regionalPrompts/components/IPAdapterLayerListItem'; -import { MaskedGuidanceLayerListItem } from 'features/regionalPrompts/components/MaskedGuidanceLayerListItem'; -import { isRenderableLayer, selectRegionalPromptsSlice } from 'features/regionalPrompts/store/regionalPromptsSlice'; -import type { Layer } from 'features/regionalPrompts/store/types'; +import { AddLayerButton } from 'features/controlLayers/components/AddLayerButton'; +import { ControlAdapterLayerListItem } from 'features/controlLayers/components/ControlAdapterLayerListItem'; +import { DeleteAllLayersButton } from 'features/controlLayers/components/DeleteAllLayersButton'; +import { IPAdapterLayerListItem } from 'features/controlLayers/components/IPAdapterLayerListItem'; +import { MaskedGuidanceLayerListItem } from 'features/controlLayers/components/MaskedGuidanceLayerListItem'; +import { isRenderableLayer, selectRegionalPromptsSlice } from 'features/controlLayers/store/regionalPromptsSlice'; +import type { Layer } from 'features/controlLayers/store/types'; import { partition } from 'lodash-es'; import { memo } from 'react'; diff --git a/invokeai/frontend/web/src/features/regionalPrompts/components/RegionalPromptsToolbar.tsx b/invokeai/frontend/web/src/features/controlLayers/components/RegionalPromptsToolbar.tsx similarity index 52% rename from invokeai/frontend/web/src/features/regionalPrompts/components/RegionalPromptsToolbar.tsx rename to invokeai/frontend/web/src/features/controlLayers/components/RegionalPromptsToolbar.tsx index 3ffeedd245..978dddf717 100644 --- a/invokeai/frontend/web/src/features/regionalPrompts/components/RegionalPromptsToolbar.tsx +++ b/invokeai/frontend/web/src/features/controlLayers/components/RegionalPromptsToolbar.tsx @@ -1,9 +1,9 @@ /* eslint-disable i18next/no-literal-string */ import { Flex } from '@invoke-ai/ui-library'; -import { BrushSize } from 'features/regionalPrompts/components/BrushSize'; -import ControlLayersSettingsPopover from 'features/regionalPrompts/components/ControlLayersSettingsPopover'; -import { ToolChooser } from 'features/regionalPrompts/components/ToolChooser'; -import { UndoRedoButtonGroup } from 'features/regionalPrompts/components/UndoRedoButtonGroup'; +import { BrushSize } from 'features/controlLayers/components/BrushSize'; +import ControlLayersSettingsPopover from 'features/controlLayers/components/ControlLayersSettingsPopover'; +import { ToolChooser } from 'features/controlLayers/components/ToolChooser'; +import { UndoRedoButtonGroup } from 'features/controlLayers/components/UndoRedoButtonGroup'; import { memo } from 'react'; export const RegionalPromptsToolbar = memo(() => { diff --git a/invokeai/frontend/web/src/features/regionalPrompts/components/StageComponent.tsx b/invokeai/frontend/web/src/features/controlLayers/components/StageComponent.tsx similarity index 97% rename from invokeai/frontend/web/src/features/regionalPrompts/components/StageComponent.tsx rename to invokeai/frontend/web/src/features/controlLayers/components/StageComponent.tsx index 83f589a392..2cff657aad 100644 --- a/invokeai/frontend/web/src/features/regionalPrompts/components/StageComponent.tsx +++ b/invokeai/frontend/web/src/features/controlLayers/components/StageComponent.tsx @@ -4,7 +4,7 @@ import { createSelector } from '@reduxjs/toolkit'; import { logger } from 'app/logging/logger'; import { createMemoizedSelector } from 'app/store/createMemoizedSelector'; import { useAppDispatch, useAppSelector } from 'app/store/storeHooks'; -import { useMouseEvents } from 'features/regionalPrompts/hooks/mouseEventHooks'; +import { useMouseEvents } from 'features/controlLayers/hooks/mouseEventHooks'; import { $cursorPosition, $isMouseOver, @@ -14,8 +14,8 @@ import { layerBboxChanged, layerTranslated, selectRegionalPromptsSlice, -} from 'features/regionalPrompts/store/regionalPromptsSlice'; -import { debouncedRenderers, renderers as normalRenderers } from 'features/regionalPrompts/util/renderers'; +} from 'features/controlLayers/store/regionalPromptsSlice'; +import { debouncedRenderers, renderers as normalRenderers } from 'features/controlLayers/util/renderers'; import Konva from 'konva'; import type { IRect } from 'konva/lib/types'; import { memo, useCallback, useLayoutEffect, useMemo, useState } from 'react'; diff --git a/invokeai/frontend/web/src/features/regionalPrompts/components/ToolChooser.tsx b/invokeai/frontend/web/src/features/controlLayers/components/ToolChooser.tsx similarity index 98% rename from invokeai/frontend/web/src/features/regionalPrompts/components/ToolChooser.tsx rename to invokeai/frontend/web/src/features/controlLayers/components/ToolChooser.tsx index a3b556a7e5..bc93556f77 100644 --- a/invokeai/frontend/web/src/features/regionalPrompts/components/ToolChooser.tsx +++ b/invokeai/frontend/web/src/features/controlLayers/components/ToolChooser.tsx @@ -7,7 +7,7 @@ import { selectedLayerDeleted, selectedLayerReset, selectRegionalPromptsSlice, -} from 'features/regionalPrompts/store/regionalPromptsSlice'; +} from 'features/controlLayers/store/regionalPromptsSlice'; import { useCallback } from 'react'; import { useHotkeys } from 'react-hotkeys-hook'; import { useTranslation } from 'react-i18next'; diff --git a/invokeai/frontend/web/src/features/regionalPrompts/components/UndoRedoButtonGroup.tsx b/invokeai/frontend/web/src/features/controlLayers/components/UndoRedoButtonGroup.tsx similarity index 95% rename from invokeai/frontend/web/src/features/regionalPrompts/components/UndoRedoButtonGroup.tsx rename to invokeai/frontend/web/src/features/controlLayers/components/UndoRedoButtonGroup.tsx index bb8f9cfd6e..bfc5e0bd24 100644 --- a/invokeai/frontend/web/src/features/regionalPrompts/components/UndoRedoButtonGroup.tsx +++ b/invokeai/frontend/web/src/features/controlLayers/components/UndoRedoButtonGroup.tsx @@ -1,7 +1,7 @@ /* eslint-disable i18next/no-literal-string */ import { ButtonGroup, IconButton } from '@invoke-ai/ui-library'; import { useAppDispatch, useAppSelector } from 'app/store/storeHooks'; -import { redo, undo } from 'features/regionalPrompts/store/regionalPromptsSlice'; +import { redo, undo } from 'features/controlLayers/store/regionalPromptsSlice'; import { memo, useCallback } from 'react'; import { useHotkeys } from 'react-hotkeys-hook'; import { useTranslation } from 'react-i18next'; diff --git a/invokeai/frontend/web/src/features/regionalPrompts/components/controlAdapterOverrides/ControlAdapterImagePreview.tsx b/invokeai/frontend/web/src/features/controlLayers/components/controlAdapterOverrides/ControlAdapterImagePreview.tsx similarity index 98% rename from invokeai/frontend/web/src/features/regionalPrompts/components/controlAdapterOverrides/ControlAdapterImagePreview.tsx rename to invokeai/frontend/web/src/features/controlLayers/components/controlAdapterOverrides/ControlAdapterImagePreview.tsx index 6238ec498d..41339b0efa 100644 --- a/invokeai/frontend/web/src/features/regionalPrompts/components/controlAdapterOverrides/ControlAdapterImagePreview.tsx +++ b/invokeai/frontend/web/src/features/controlLayers/components/controlAdapterOverrides/ControlAdapterImagePreview.tsx @@ -13,10 +13,10 @@ import { controlAdapterImageChanged, selectControlAdaptersSlice, } from 'features/controlAdapters/store/controlAdaptersSlice'; +import { heightChanged, widthChanged } from 'features/controlLayers/store/regionalPromptsSlice'; import type { TypesafeDraggableData, TypesafeDroppableData } from 'features/dnd/types'; import { calculateNewSize } from 'features/parameters/components/ImageSize/calculateNewSize'; import { selectOptimalDimension } from 'features/parameters/store/generationSlice'; -import { heightChanged, widthChanged } from 'features/regionalPrompts/store/regionalPromptsSlice'; import { activeTabNameSelector } from 'features/ui/store/uiSelectors'; import { memo, useCallback, useEffect, useMemo, useState } from 'react'; import { useTranslation } from 'react-i18next'; diff --git a/invokeai/frontend/web/src/features/regionalPrompts/components/controlAdapterOverrides/ControlAdapterLayerConfig.tsx b/invokeai/frontend/web/src/features/controlLayers/components/controlAdapterOverrides/ControlAdapterLayerConfig.tsx similarity index 100% rename from invokeai/frontend/web/src/features/regionalPrompts/components/controlAdapterOverrides/ControlAdapterLayerConfig.tsx rename to invokeai/frontend/web/src/features/controlLayers/components/controlAdapterOverrides/ControlAdapterLayerConfig.tsx diff --git a/invokeai/frontend/web/src/features/regionalPrompts/components/controlAdapterOverrides/ParamControlAdapterBeginEnd.tsx b/invokeai/frontend/web/src/features/controlLayers/components/controlAdapterOverrides/ParamControlAdapterBeginEnd.tsx similarity index 100% rename from invokeai/frontend/web/src/features/regionalPrompts/components/controlAdapterOverrides/ParamControlAdapterBeginEnd.tsx rename to invokeai/frontend/web/src/features/controlLayers/components/controlAdapterOverrides/ParamControlAdapterBeginEnd.tsx diff --git a/invokeai/frontend/web/src/features/regionalPrompts/components/controlAdapterOverrides/ParamControlAdapterControlMode.tsx b/invokeai/frontend/web/src/features/controlLayers/components/controlAdapterOverrides/ParamControlAdapterControlMode.tsx similarity index 100% rename from invokeai/frontend/web/src/features/regionalPrompts/components/controlAdapterOverrides/ParamControlAdapterControlMode.tsx rename to invokeai/frontend/web/src/features/controlLayers/components/controlAdapterOverrides/ParamControlAdapterControlMode.tsx diff --git a/invokeai/frontend/web/src/features/regionalPrompts/components/controlAdapterOverrides/ParamControlAdapterModel.tsx b/invokeai/frontend/web/src/features/controlLayers/components/controlAdapterOverrides/ParamControlAdapterModel.tsx similarity index 100% rename from invokeai/frontend/web/src/features/regionalPrompts/components/controlAdapterOverrides/ParamControlAdapterModel.tsx rename to invokeai/frontend/web/src/features/controlLayers/components/controlAdapterOverrides/ParamControlAdapterModel.tsx diff --git a/invokeai/frontend/web/src/features/regionalPrompts/components/controlAdapterOverrides/ParamControlAdapterWeight.tsx b/invokeai/frontend/web/src/features/controlLayers/components/controlAdapterOverrides/ParamControlAdapterWeight.tsx similarity index 100% rename from invokeai/frontend/web/src/features/regionalPrompts/components/controlAdapterOverrides/ParamControlAdapterWeight.tsx rename to invokeai/frontend/web/src/features/controlLayers/components/controlAdapterOverrides/ParamControlAdapterWeight.tsx diff --git a/invokeai/frontend/web/src/features/regionalPrompts/hooks/layerStateHooks.ts b/invokeai/frontend/web/src/features/controlLayers/hooks/layerStateHooks.ts similarity index 97% rename from invokeai/frontend/web/src/features/regionalPrompts/hooks/layerStateHooks.ts rename to invokeai/frontend/web/src/features/controlLayers/hooks/layerStateHooks.ts index dd0042431c..5925322fc9 100644 --- a/invokeai/frontend/web/src/features/regionalPrompts/hooks/layerStateHooks.ts +++ b/invokeai/frontend/web/src/features/controlLayers/hooks/layerStateHooks.ts @@ -4,7 +4,7 @@ import { isControlAdapterLayer, isMaskedGuidanceLayer, selectRegionalPromptsSlice, -} from 'features/regionalPrompts/store/regionalPromptsSlice'; +} from 'features/controlLayers/store/regionalPromptsSlice'; import { useMemo } from 'react'; import { assert } from 'tsafe'; diff --git a/invokeai/frontend/web/src/features/regionalPrompts/hooks/mouseEventHooks.ts b/invokeai/frontend/web/src/features/controlLayers/hooks/mouseEventHooks.ts similarity index 99% rename from invokeai/frontend/web/src/features/regionalPrompts/hooks/mouseEventHooks.ts rename to invokeai/frontend/web/src/features/controlLayers/hooks/mouseEventHooks.ts index fc58de60ed..ca44480a2b 100644 --- a/invokeai/frontend/web/src/features/regionalPrompts/hooks/mouseEventHooks.ts +++ b/invokeai/frontend/web/src/features/controlLayers/hooks/mouseEventHooks.ts @@ -12,7 +12,7 @@ import { maskLayerLineAdded, maskLayerPointsAdded, maskLayerRectAdded, -} from 'features/regionalPrompts/store/regionalPromptsSlice'; +} from 'features/controlLayers/store/regionalPromptsSlice'; import type Konva from 'konva'; import type { KonvaEventObject } from 'konva/lib/Node'; import type { Vector2d } from 'konva/lib/types'; diff --git a/invokeai/frontend/web/src/features/regionalPrompts/hooks/useRegionalControlTitle.ts b/invokeai/frontend/web/src/features/controlLayers/hooks/useRegionalControlTitle.ts similarity index 95% rename from invokeai/frontend/web/src/features/regionalPrompts/hooks/useRegionalControlTitle.ts rename to invokeai/frontend/web/src/features/controlLayers/hooks/useRegionalControlTitle.ts index 536e03bedc..c5e74868d7 100644 --- a/invokeai/frontend/web/src/features/regionalPrompts/hooks/useRegionalControlTitle.ts +++ b/invokeai/frontend/web/src/features/controlLayers/hooks/useRegionalControlTitle.ts @@ -1,6 +1,6 @@ import { createSelector } from '@reduxjs/toolkit'; import { useAppSelector } from 'app/store/storeHooks'; -import { isMaskedGuidanceLayer, selectRegionalPromptsSlice } from 'features/regionalPrompts/store/regionalPromptsSlice'; +import { isMaskedGuidanceLayer, selectRegionalPromptsSlice } from 'features/controlLayers/store/regionalPromptsSlice'; import { useMemo } from 'react'; import { useTranslation } from 'react-i18next'; diff --git a/invokeai/frontend/web/src/features/regionalPrompts/store/regionalPromptsSlice.ts b/invokeai/frontend/web/src/features/controlLayers/store/regionalPromptsSlice.ts similarity index 100% rename from invokeai/frontend/web/src/features/regionalPrompts/store/regionalPromptsSlice.ts rename to invokeai/frontend/web/src/features/controlLayers/store/regionalPromptsSlice.ts diff --git a/invokeai/frontend/web/src/features/regionalPrompts/store/types.ts b/invokeai/frontend/web/src/features/controlLayers/store/types.ts similarity index 100% rename from invokeai/frontend/web/src/features/regionalPrompts/store/types.ts rename to invokeai/frontend/web/src/features/controlLayers/store/types.ts diff --git a/invokeai/frontend/web/src/features/regionalPrompts/util/bbox.ts b/invokeai/frontend/web/src/features/controlLayers/util/bbox.ts similarity index 98% rename from invokeai/frontend/web/src/features/regionalPrompts/util/bbox.ts rename to invokeai/frontend/web/src/features/controlLayers/util/bbox.ts index b17d805552..a75650fd0a 100644 --- a/invokeai/frontend/web/src/features/regionalPrompts/util/bbox.ts +++ b/invokeai/frontend/web/src/features/controlLayers/util/bbox.ts @@ -1,6 +1,6 @@ import openBase64ImageInTab from 'common/util/openBase64ImageInTab'; import { imageDataToDataURL } from 'features/canvas/util/blobToDataURL'; -import { MASKED_GUIDANCE_LAYER_OBJECT_GROUP_NAME } from 'features/regionalPrompts/store/regionalPromptsSlice'; +import { MASKED_GUIDANCE_LAYER_OBJECT_GROUP_NAME } from 'features/controlLayers/store/regionalPromptsSlice'; import Konva from 'konva'; import type { Layer as KonvaLayerType } from 'konva/lib/Layer'; import type { IRect } from 'konva/lib/types'; diff --git a/invokeai/frontend/web/src/features/regionalPrompts/util/getLayerBlobs.ts b/invokeai/frontend/web/src/features/controlLayers/util/getLayerBlobs.ts similarity index 94% rename from invokeai/frontend/web/src/features/regionalPrompts/util/getLayerBlobs.ts rename to invokeai/frontend/web/src/features/controlLayers/util/getLayerBlobs.ts index 723d2611fd..783fdee513 100644 --- a/invokeai/frontend/web/src/features/regionalPrompts/util/getLayerBlobs.ts +++ b/invokeai/frontend/web/src/features/controlLayers/util/getLayerBlobs.ts @@ -1,8 +1,8 @@ import { getStore } from 'app/store/nanostores/store'; import openBase64ImageInTab from 'common/util/openBase64ImageInTab'; import { blobToDataURL } from 'features/canvas/util/blobToDataURL'; -import { isMaskedGuidanceLayer, MASKED_GUIDANCE_LAYER_NAME } from 'features/regionalPrompts/store/regionalPromptsSlice'; -import { renderers } from 'features/regionalPrompts/util/renderers'; +import { isMaskedGuidanceLayer, MASKED_GUIDANCE_LAYER_NAME } from 'features/controlLayers/store/regionalPromptsSlice'; +import { renderers } from 'features/controlLayers/util/renderers'; import Konva from 'konva'; import { assert } from 'tsafe'; diff --git a/invokeai/frontend/web/src/features/regionalPrompts/util/renderers.ts b/invokeai/frontend/web/src/features/controlLayers/util/renderers.ts similarity index 98% rename from invokeai/frontend/web/src/features/regionalPrompts/util/renderers.ts rename to invokeai/frontend/web/src/features/controlLayers/util/renderers.ts index 2e437728b5..c64baa211c 100644 --- a/invokeai/frontend/web/src/features/regionalPrompts/util/renderers.ts +++ b/invokeai/frontend/web/src/features/controlLayers/util/renderers.ts @@ -1,6 +1,6 @@ import { getStore } from 'app/store/nanostores/store'; import { rgbaColorToString, rgbColorToString } from 'features/canvas/util/colorToString'; -import { getScaledFlooredCursorPosition } from 'features/regionalPrompts/hooks/mouseEventHooks'; +import { getScaledFlooredCursorPosition } from 'features/controlLayers/hooks/mouseEventHooks'; import { $tool, BACKGROUND_LAYER_ID, @@ -24,7 +24,7 @@ import { TOOL_PREVIEW_BRUSH_GROUP_ID, TOOL_PREVIEW_LAYER_ID, TOOL_PREVIEW_RECT_ID, -} from 'features/regionalPrompts/store/regionalPromptsSlice'; +} from 'features/controlLayers/store/regionalPromptsSlice'; import type { ControlAdapterLayer, Layer, @@ -32,8 +32,8 @@ import type { Tool, VectorMaskLine, VectorMaskRect, -} from 'features/regionalPrompts/store/types'; -import { getLayerBboxFast, getLayerBboxPixels } from 'features/regionalPrompts/util/bbox'; +} from 'features/controlLayers/store/types'; +import { getLayerBboxFast, getLayerBboxPixels } from 'features/controlLayers/util/bbox'; import Konva from 'konva'; import type { IRect, Vector2d } from 'konva/lib/types'; import { debounce } from 'lodash-es'; diff --git a/invokeai/frontend/web/src/features/metadata/util/recallers.ts b/invokeai/frontend/web/src/features/metadata/util/recallers.ts index fd0d8c9a33..fbf72ef98f 100644 --- a/invokeai/frontend/web/src/features/metadata/util/recallers.ts +++ b/invokeai/frontend/web/src/features/metadata/util/recallers.ts @@ -5,6 +5,14 @@ import { ipAdaptersReset, t2iAdaptersReset, } from 'features/controlAdapters/store/controlAdaptersSlice'; +import { + heightChanged, + negativePrompt2Changed, + negativePromptChanged, + positivePrompt2Changed, + positivePromptChanged, + widthChanged, +} from 'features/controlLayers/store/regionalPromptsSlice'; import { setHrfEnabled, setHrfMethod, setHrfStrength } from 'features/hrf/store/hrfSlice'; import type { LoRA } from 'features/lora/store/loraSlice'; import { loraRecalled, lorasReset } from 'features/lora/store/loraSlice'; @@ -47,14 +55,6 @@ import type { ParameterVAEModel, ParameterWidth, } from 'features/parameters/types/parameterSchemas'; -import { - heightChanged, - negativePrompt2Changed, - negativePromptChanged, - positivePrompt2Changed, - positivePromptChanged, - widthChanged, -} from 'features/regionalPrompts/store/regionalPromptsSlice'; import { refinerModelChanged, setRefinerCFGScale, diff --git a/invokeai/frontend/web/src/features/nodes/util/graph/addControlNetToLinearGraph.ts b/invokeai/frontend/web/src/features/nodes/util/graph/addControlNetToLinearGraph.ts index ac3c7ba670..90898a0852 100644 --- a/invokeai/frontend/web/src/features/nodes/util/graph/addControlNetToLinearGraph.ts +++ b/invokeai/frontend/web/src/features/nodes/util/graph/addControlNetToLinearGraph.ts @@ -1,8 +1,8 @@ import type { RootState } from 'app/store/store'; import { selectValidControlNets } from 'features/controlAdapters/store/controlAdaptersSlice'; import type { ControlAdapterProcessorType, ControlNetConfig } from 'features/controlAdapters/store/types'; +import { isControlAdapterLayer } from 'features/controlLayers/store/regionalPromptsSlice'; import type { ImageField } from 'features/nodes/types/common'; -import { isControlAdapterLayer } from 'features/regionalPrompts/store/regionalPromptsSlice'; import { activeTabNameSelector } from 'features/ui/store/uiSelectors'; import { differenceWith, intersectionWith } from 'lodash-es'; import type { diff --git a/invokeai/frontend/web/src/features/nodes/util/graph/addIPAdapterToLinearGraph.ts b/invokeai/frontend/web/src/features/nodes/util/graph/addIPAdapterToLinearGraph.ts index 12e942ed59..6373c87eb5 100644 --- a/invokeai/frontend/web/src/features/nodes/util/graph/addIPAdapterToLinearGraph.ts +++ b/invokeai/frontend/web/src/features/nodes/util/graph/addIPAdapterToLinearGraph.ts @@ -1,8 +1,8 @@ import type { RootState } from 'app/store/store'; import { selectValidIPAdapters } from 'features/controlAdapters/store/controlAdaptersSlice'; import type { IPAdapterConfig } from 'features/controlAdapters/store/types'; +import { isIPAdapterLayer, isMaskedGuidanceLayer } from 'features/controlLayers/store/regionalPromptsSlice'; import type { ImageField } from 'features/nodes/types/common'; -import { isIPAdapterLayer, isMaskedGuidanceLayer } from 'features/regionalPrompts/store/regionalPromptsSlice'; import { activeTabNameSelector } from 'features/ui/store/uiSelectors'; import { differenceWith, intersectionWith } from 'lodash-es'; import type { diff --git a/invokeai/frontend/web/src/features/nodes/util/graph/addRegionalPromptsToGraph.ts b/invokeai/frontend/web/src/features/nodes/util/graph/addRegionalPromptsToGraph.ts index 82725d8ace..5c581ad24e 100644 --- a/invokeai/frontend/web/src/features/nodes/util/graph/addRegionalPromptsToGraph.ts +++ b/invokeai/frontend/web/src/features/nodes/util/graph/addRegionalPromptsToGraph.ts @@ -1,6 +1,8 @@ import { getStore } from 'app/store/nanostores/store'; import type { RootState } from 'app/store/store'; import { selectAllIPAdapters } from 'features/controlAdapters/store/controlAdaptersSlice'; +import { isMaskedGuidanceLayer } from 'features/controlLayers/store/regionalPromptsSlice'; +import { getRegionalPromptLayerBlobs } from 'features/controlLayers/util/getLayerBlobs'; import { IP_ADAPTER_COLLECT, NEGATIVE_CONDITIONING, @@ -13,8 +15,6 @@ import { PROMPT_REGION_POSITIVE_COND_INVERTED_PREFIX, PROMPT_REGION_POSITIVE_COND_PREFIX, } from 'features/nodes/util/graph/constants'; -import { isMaskedGuidanceLayer } from 'features/regionalPrompts/store/regionalPromptsSlice'; -import { getRegionalPromptLayerBlobs } from 'features/regionalPrompts/util/getLayerBlobs'; import { size } from 'lodash-es'; import { imagesApi } from 'services/api/endpoints/images'; import type { CollectInvocation, Edge, IPAdapterInvocation, NonNullableGraph, S } from 'services/api/types'; diff --git a/invokeai/frontend/web/src/features/nodes/util/graph/addT2IAdapterToLinearGraph.ts b/invokeai/frontend/web/src/features/nodes/util/graph/addT2IAdapterToLinearGraph.ts index 5fd168205f..9006ed8b83 100644 --- a/invokeai/frontend/web/src/features/nodes/util/graph/addT2IAdapterToLinearGraph.ts +++ b/invokeai/frontend/web/src/features/nodes/util/graph/addT2IAdapterToLinearGraph.ts @@ -1,8 +1,8 @@ import type { RootState } from 'app/store/store'; import { selectValidT2IAdapters } from 'features/controlAdapters/store/controlAdaptersSlice'; import type { ControlAdapterProcessorType, T2IAdapterConfig } from 'features/controlAdapters/store/types'; +import { isControlAdapterLayer } from 'features/controlLayers/store/regionalPromptsSlice'; import type { ImageField } from 'features/nodes/types/common'; -import { isControlAdapterLayer } from 'features/regionalPrompts/store/regionalPromptsSlice'; import { activeTabNameSelector } from 'features/ui/store/uiSelectors'; import { differenceWith, intersectionWith } from 'lodash-es'; import type { diff --git a/invokeai/frontend/web/src/features/parameters/components/Core/ParamNegativePrompt.tsx b/invokeai/frontend/web/src/features/parameters/components/Core/ParamNegativePrompt.tsx index 077bdca108..ccfb6f7c72 100644 --- a/invokeai/frontend/web/src/features/parameters/components/Core/ParamNegativePrompt.tsx +++ b/invokeai/frontend/web/src/features/parameters/components/Core/ParamNegativePrompt.tsx @@ -1,10 +1,10 @@ import { Box, Textarea } from '@invoke-ai/ui-library'; import { useAppDispatch, useAppSelector } from 'app/store/storeHooks'; +import { negativePromptChanged } from 'features/controlLayers/store/regionalPromptsSlice'; import { PromptOverlayButtonWrapper } from 'features/parameters/components/Prompts/PromptOverlayButtonWrapper'; import { AddPromptTriggerButton } from 'features/prompt/AddPromptTriggerButton'; import { PromptPopover } from 'features/prompt/PromptPopover'; import { usePrompt } from 'features/prompt/usePrompt'; -import { negativePromptChanged } from 'features/regionalPrompts/store/regionalPromptsSlice'; import { memo, useCallback, useRef } from 'react'; import { useTranslation } from 'react-i18next'; diff --git a/invokeai/frontend/web/src/features/parameters/components/Core/ParamPositivePrompt.tsx b/invokeai/frontend/web/src/features/parameters/components/Core/ParamPositivePrompt.tsx index 90b6ae3093..4f6e66e50a 100644 --- a/invokeai/frontend/web/src/features/parameters/components/Core/ParamPositivePrompt.tsx +++ b/invokeai/frontend/web/src/features/parameters/components/Core/ParamPositivePrompt.tsx @@ -1,11 +1,11 @@ import { Box, Textarea } from '@invoke-ai/ui-library'; import { useAppDispatch, useAppSelector } from 'app/store/storeHooks'; +import { positivePromptChanged } from 'features/controlLayers/store/regionalPromptsSlice'; import { ShowDynamicPromptsPreviewButton } from 'features/dynamicPrompts/components/ShowDynamicPromptsPreviewButton'; import { PromptOverlayButtonWrapper } from 'features/parameters/components/Prompts/PromptOverlayButtonWrapper'; import { AddPromptTriggerButton } from 'features/prompt/AddPromptTriggerButton'; import { PromptPopover } from 'features/prompt/PromptPopover'; import { usePrompt } from 'features/prompt/usePrompt'; -import { positivePromptChanged } from 'features/regionalPrompts/store/regionalPromptsSlice'; import { SDXLConcatButton } from 'features/sdxl/components/SDXLPrompts/SDXLConcatButton'; import { memo, useCallback, useRef } from 'react'; import type { HotkeyCallback } from 'react-hotkeys-hook'; diff --git a/invokeai/frontend/web/src/features/parameters/components/ImageSize/AspectRatioCanvasPreview.tsx b/invokeai/frontend/web/src/features/parameters/components/ImageSize/AspectRatioCanvasPreview.tsx index 6584cb14c9..00fa10c0c5 100644 --- a/invokeai/frontend/web/src/features/parameters/components/ImageSize/AspectRatioCanvasPreview.tsx +++ b/invokeai/frontend/web/src/features/parameters/components/ImageSize/AspectRatioCanvasPreview.tsx @@ -1,5 +1,5 @@ import { Flex } from '@invoke-ai/ui-library'; -import { StageComponent } from 'features/regionalPrompts/components/StageComponent'; +import { StageComponent } from 'features/controlLayers/components/StageComponent'; import { memo } from 'react'; export const AspectRatioCanvasPreview = memo(() => { diff --git a/invokeai/frontend/web/src/features/queue/components/QueueButtonTooltip.tsx b/invokeai/frontend/web/src/features/queue/components/QueueButtonTooltip.tsx index e020782aa1..351589a949 100644 --- a/invokeai/frontend/web/src/features/queue/components/QueueButtonTooltip.tsx +++ b/invokeai/frontend/web/src/features/queue/components/QueueButtonTooltip.tsx @@ -2,9 +2,9 @@ import { Divider, Flex, ListItem, Text, UnorderedList } from '@invoke-ai/ui-libr import { createSelector } from '@reduxjs/toolkit'; import { useAppSelector } from 'app/store/storeHooks'; import { useIsReadyToEnqueue } from 'common/hooks/useIsReadyToEnqueue'; +import { selectRegionalPromptsSlice } from 'features/controlLayers/store/regionalPromptsSlice'; import { selectDynamicPromptsSlice } from 'features/dynamicPrompts/store/dynamicPromptsSlice'; import { getShouldProcessPrompt } from 'features/dynamicPrompts/util/getShouldProcessPrompt'; -import { selectRegionalPromptsSlice } from 'features/regionalPrompts/store/regionalPromptsSlice'; import { memo, useMemo } from 'react'; import { useTranslation } from 'react-i18next'; import { useEnqueueBatchMutation } from 'services/api/endpoints/queue'; diff --git a/invokeai/frontend/web/src/features/sdxl/components/SDXLPrompts/ParamSDXLNegativeStylePrompt.tsx b/invokeai/frontend/web/src/features/sdxl/components/SDXLPrompts/ParamSDXLNegativeStylePrompt.tsx index 51347c769d..6104642b62 100644 --- a/invokeai/frontend/web/src/features/sdxl/components/SDXLPrompts/ParamSDXLNegativeStylePrompt.tsx +++ b/invokeai/frontend/web/src/features/sdxl/components/SDXLPrompts/ParamSDXLNegativeStylePrompt.tsx @@ -1,10 +1,10 @@ import { Box, Textarea } from '@invoke-ai/ui-library'; import { useAppDispatch, useAppSelector } from 'app/store/storeHooks'; +import { negativePrompt2Changed } from 'features/controlLayers/store/regionalPromptsSlice'; import { PromptOverlayButtonWrapper } from 'features/parameters/components/Prompts/PromptOverlayButtonWrapper'; import { AddPromptTriggerButton } from 'features/prompt/AddPromptTriggerButton'; import { PromptPopover } from 'features/prompt/PromptPopover'; import { usePrompt } from 'features/prompt/usePrompt'; -import { negativePrompt2Changed } from 'features/regionalPrompts/store/regionalPromptsSlice'; import { memo, useCallback, useRef } from 'react'; import { useHotkeys } from 'react-hotkeys-hook'; import { useTranslation } from 'react-i18next'; diff --git a/invokeai/frontend/web/src/features/sdxl/components/SDXLPrompts/ParamSDXLPositiveStylePrompt.tsx b/invokeai/frontend/web/src/features/sdxl/components/SDXLPrompts/ParamSDXLPositiveStylePrompt.tsx index cdff420b47..d14d42a3ce 100644 --- a/invokeai/frontend/web/src/features/sdxl/components/SDXLPrompts/ParamSDXLPositiveStylePrompt.tsx +++ b/invokeai/frontend/web/src/features/sdxl/components/SDXLPrompts/ParamSDXLPositiveStylePrompt.tsx @@ -1,10 +1,10 @@ import { Box, Textarea } from '@invoke-ai/ui-library'; import { useAppDispatch, useAppSelector } from 'app/store/storeHooks'; +import { positivePrompt2Changed } from 'features/controlLayers/store/regionalPromptsSlice'; import { PromptOverlayButtonWrapper } from 'features/parameters/components/Prompts/PromptOverlayButtonWrapper'; import { AddPromptTriggerButton } from 'features/prompt/AddPromptTriggerButton'; import { PromptPopover } from 'features/prompt/PromptPopover'; import { usePrompt } from 'features/prompt/usePrompt'; -import { positivePrompt2Changed } from 'features/regionalPrompts/store/regionalPromptsSlice'; import { memo, useCallback, useRef } from 'react'; import { useTranslation } from 'react-i18next'; diff --git a/invokeai/frontend/web/src/features/sdxl/components/SDXLPrompts/SDXLConcatButton.tsx b/invokeai/frontend/web/src/features/sdxl/components/SDXLPrompts/SDXLConcatButton.tsx index b25698cc1d..436749428b 100644 --- a/invokeai/frontend/web/src/features/sdxl/components/SDXLPrompts/SDXLConcatButton.tsx +++ b/invokeai/frontend/web/src/features/sdxl/components/SDXLPrompts/SDXLConcatButton.tsx @@ -1,6 +1,6 @@ import { IconButton, Tooltip } from '@invoke-ai/ui-library'; import { useAppDispatch, useAppSelector } from 'app/store/storeHooks'; -import { shouldConcatPromptsChanged } from 'features/regionalPrompts/store/regionalPromptsSlice'; +import { shouldConcatPromptsChanged } from 'features/controlLayers/store/regionalPromptsSlice'; import { memo, useCallback, useMemo } from 'react'; import { useTranslation } from 'react-i18next'; import { PiLinkSimpleBold, PiLinkSimpleBreakBold } from 'react-icons/pi'; diff --git a/invokeai/frontend/web/src/features/settingsAccordions/components/ControlSettingsAccordion/ControlSettingsAccordion.tsx b/invokeai/frontend/web/src/features/settingsAccordions/components/ControlSettingsAccordion/ControlSettingsAccordion.tsx index b6e1b179b3..2de6d9de00 100644 --- a/invokeai/frontend/web/src/features/settingsAccordions/components/ControlSettingsAccordion/ControlSettingsAccordion.tsx +++ b/invokeai/frontend/web/src/features/settingsAccordions/components/ControlSettingsAccordion/ControlSettingsAccordion.tsx @@ -16,7 +16,7 @@ import { import { selectAllControlAdapterIds, selectRegionalPromptsSlice, -} from 'features/regionalPrompts/store/regionalPromptsSlice'; +} from 'features/controlLayers/store/regionalPromptsSlice'; import { useStandaloneAccordionToggle } from 'features/settingsAccordions/hooks/useStandaloneAccordionToggle'; import { useFeatureStatus } from 'features/system/hooks/useFeatureStatus'; import { Fragment, memo } from 'react'; diff --git a/invokeai/frontend/web/src/features/settingsAccordions/components/ImageSettingsAccordion/ImageSettingsAccordion.tsx b/invokeai/frontend/web/src/features/settingsAccordions/components/ImageSettingsAccordion/ImageSettingsAccordion.tsx index 30f7f4b0ac..6f30d5110a 100644 --- a/invokeai/frontend/web/src/features/settingsAccordions/components/ImageSettingsAccordion/ImageSettingsAccordion.tsx +++ b/invokeai/frontend/web/src/features/settingsAccordions/components/ImageSettingsAccordion/ImageSettingsAccordion.tsx @@ -3,6 +3,7 @@ import { Expander, Flex, FormControlGroup, StandaloneAccordion } from '@invoke-a import { createMemoizedSelector } from 'app/store/createMemoizedSelector'; import { useAppSelector } from 'app/store/storeHooks'; import { selectCanvasSlice } from 'features/canvas/store/canvasSlice'; +import { selectRegionalPromptsSlice } from 'features/controlLayers/store/regionalPromptsSlice'; import { HrfSettings } from 'features/hrf/components/HrfSettings'; import { selectHrfSlice } from 'features/hrf/store/hrfSlice'; import ParamScaleBeforeProcessing from 'features/parameters/components/Canvas/InfillAndScaling/ParamScaleBeforeProcessing'; @@ -14,7 +15,6 @@ import { ParamSeedNumberInput } from 'features/parameters/components/Seed/ParamS import { ParamSeedRandomize } from 'features/parameters/components/Seed/ParamSeedRandomize'; import { ParamSeedShuffle } from 'features/parameters/components/Seed/ParamSeedShuffle'; import { selectGenerationSlice } from 'features/parameters/store/generationSlice'; -import { selectRegionalPromptsSlice } from 'features/regionalPrompts/store/regionalPromptsSlice'; import { useExpanderToggle } from 'features/settingsAccordions/hooks/useExpanderToggle'; import { useStandaloneAccordionToggle } from 'features/settingsAccordions/hooks/useStandaloneAccordionToggle'; import { activeTabNameSelector } from 'features/ui/store/uiSelectors'; diff --git a/invokeai/frontend/web/src/features/settingsAccordions/components/ImageSettingsAccordion/ImageSizeLinear.tsx b/invokeai/frontend/web/src/features/settingsAccordions/components/ImageSettingsAccordion/ImageSizeLinear.tsx index 3a00496ec9..6ca9988f67 100644 --- a/invokeai/frontend/web/src/features/settingsAccordions/components/ImageSettingsAccordion/ImageSizeLinear.tsx +++ b/invokeai/frontend/web/src/features/settingsAccordions/components/ImageSettingsAccordion/ImageSizeLinear.tsx @@ -1,11 +1,11 @@ import { useAppDispatch, useAppSelector } from 'app/store/storeHooks'; +import { aspectRatioChanged, heightChanged, widthChanged } from 'features/controlLayers/store/regionalPromptsSlice'; import { ParamHeight } from 'features/parameters/components/Core/ParamHeight'; import { ParamWidth } from 'features/parameters/components/Core/ParamWidth'; import { AspectRatioCanvasPreview } from 'features/parameters/components/ImageSize/AspectRatioCanvasPreview'; import { AspectRatioIconPreview } from 'features/parameters/components/ImageSize/AspectRatioIconPreview'; import { ImageSize } from 'features/parameters/components/ImageSize/ImageSize'; import type { AspectRatioState } from 'features/parameters/components/ImageSize/types'; -import { aspectRatioChanged, heightChanged, widthChanged } from 'features/regionalPrompts/store/regionalPromptsSlice'; import { activeTabNameSelector } from 'features/ui/store/uiSelectors'; import { memo, useCallback } from 'react'; diff --git a/invokeai/frontend/web/src/features/ui/components/ParametersPanelTextToImage.tsx b/invokeai/frontend/web/src/features/ui/components/ParametersPanelTextToImage.tsx index d8a4a6fec4..0adf65d0bb 100644 --- a/invokeai/frontend/web/src/features/ui/components/ParametersPanelTextToImage.tsx +++ b/invokeai/frontend/web/src/features/ui/components/ParametersPanelTextToImage.tsx @@ -1,10 +1,10 @@ import { Box, Flex, Tab, TabList, TabPanel, TabPanels, Tabs } from '@invoke-ai/ui-library'; import { useAppSelector } from 'app/store/storeHooks'; import { overlayScrollbarsParams } from 'common/components/OverlayScrollbars/constants'; +import { RegionalPromptsPanelContent } from 'features/controlLayers/components/RegionalPromptsPanelContent'; +import { useRegionalControlTitle } from 'features/controlLayers/hooks/useRegionalControlTitle'; import { Prompts } from 'features/parameters/components/Prompts/Prompts'; import QueueControls from 'features/queue/components/QueueControls'; -import { RegionalPromptsPanelContent } from 'features/regionalPrompts/components/RegionalPromptsPanelContent'; -import { useRegionalControlTitle } from 'features/regionalPrompts/hooks/useRegionalControlTitle'; import { SDXLPrompts } from 'features/sdxl/components/SDXLPrompts/SDXLPrompts'; import { AdvancedSettingsAccordion } from 'features/settingsAccordions/components/AdvancedSettingsAccordion/AdvancedSettingsAccordion'; import { CompositingSettingsAccordion } from 'features/settingsAccordions/components/CompositingSettingsAccordion/CompositingSettingsAccordion'; diff --git a/invokeai/frontend/web/src/features/ui/components/tabs/TextToImageTab.tsx b/invokeai/frontend/web/src/features/ui/components/tabs/TextToImageTab.tsx index 082445fbb3..fc8694e06d 100644 --- a/invokeai/frontend/web/src/features/ui/components/tabs/TextToImageTab.tsx +++ b/invokeai/frontend/web/src/features/ui/components/tabs/TextToImageTab.tsx @@ -1,7 +1,7 @@ import { Box, Tab, TabList, TabPanel, TabPanels, Tabs } from '@invoke-ai/ui-library'; +import { RegionalPromptsEditor } from 'features/controlLayers/components/RegionalPromptsEditor'; +import { useRegionalControlTitle } from 'features/controlLayers/hooks/useRegionalControlTitle'; import CurrentImageDisplay from 'features/gallery/components/CurrentImage/CurrentImageDisplay'; -import { RegionalPromptsEditor } from 'features/regionalPrompts/components/RegionalPromptsEditor'; -import { useRegionalControlTitle } from 'features/regionalPrompts/hooks/useRegionalControlTitle'; import { memo } from 'react'; import { useTranslation } from 'react-i18next'; From 3441187c239b7c4c2e6ed7ba685774481a7a61d1 Mon Sep 17 00:00:00 2001 From: psychedelicious <4822129+psychedelicious@users.noreply.github.com> Date: Tue, 30 Apr 2024 13:28:03 +1000 Subject: [PATCH 40/52] tidy(ui): "regional prompts" -> "control layers" --- invokeai/frontend/web/public/locales/en.json | 7 +- .../frontend/web/src/app/logging/logger.ts | 2 +- .../listeners/modelsLoaded.ts | 8 +- .../listeners/promptChanged.ts | 4 +- .../regionalControlToControlAdapterBridge.ts | 20 ++--- .../listeners/setDefaultSettings.ts | 2 +- invokeai/frontend/web/src/app/store/store.ts | 12 +-- .../src/common/hooks/useIsReadyToEnqueue.ts | 16 ++-- .../components/ControlAdapterImagePreview.tsx | 2 +- .../components/AddLayerButton.tsx | 10 +-- .../components/AddPromptButtons.tsx | 8 +- .../controlLayers/components/BrushSize.tsx | 10 +-- .../components/CALayerOpacity.tsx | 6 +- .../ControlAdapterLayerListItem.tsx | 10 +-- .../ControlLayersEditor.stories.tsx | 24 ++++++ ...mptsEditor.tsx => ControlLayersEditor.tsx} | 8 +- ...tent.tsx => ControlLayersPanelContent.tsx} | 12 +-- ...tsToolbar.tsx => ControlLayersToolbar.tsx} | 4 +- .../components/DeleteAllLayersButton.tsx | 2 +- .../components/GlobalMaskLayerOpacity.tsx | 12 +-- .../components/IPAdapterLayerListItem.tsx | 6 +- .../controlLayers/components/LayerTitle.tsx | 4 +- .../MaskedGuidanceLayerListItem.tsx | 14 +-- .../RPLayerAutoNegativeCheckbox.tsx | 10 +-- .../components/RPLayerColorPicker.tsx | 12 +-- .../components/RPLayerIPAdapterList.tsx | 6 +- .../controlLayers/components/RPLayerMenu.tsx | 8 +- .../components/RPLayerMenuArrangeActions.tsx | 20 ++--- .../RPLayerMenuMaskedGuidanceActions.tsx | 14 +-- .../components/RPLayerNegativePrompt.tsx | 2 +- .../components/RPLayerPositivePrompt.tsx | 2 +- .../components/RPLayerPromptDeleteButton.tsx | 6 +- .../components/RPLayerVisibilityToggle.tsx | 6 +- .../RegionalPromptingEditor.stories.tsx | 24 ------ .../components/StageComponent.tsx | 18 ++-- .../controlLayers/components/ToolChooser.tsx | 14 +-- .../components/UndoRedoButtonGroup.tsx | 6 +- .../ControlAdapterImagePreview.tsx | 2 +- .../controlLayers/hooks/layerStateHooks.ts | 24 +++--- .../controlLayers/hooks/mouseEventHooks.ts | 6 +- .../hooks/useRegionalControlTitle.ts | 10 +-- ...lPromptsSlice.ts => controlLayersSlice.ts} | 86 +++++++++---------- .../src/features/controlLayers/store/types.ts | 8 +- .../src/features/controlLayers/util/bbox.ts | 4 +- .../controlLayers/util/getLayerBlobs.ts | 8 +- .../features/controlLayers/util/renderers.ts | 36 ++++---- .../src/features/metadata/util/recallers.ts | 2 +- ...sToGraph.ts => addControlLayersToGraph.ts} | 8 +- .../util/graph/addControlNetToLinearGraph.ts | 6 +- .../nodes/util/graph/addHrfToGraph.ts | 2 +- .../util/graph/addIPAdapterToLinearGraph.ts | 8 +- .../util/graph/addT2IAdapterToLinearGraph.ts | 6 +- .../graph/buildCanvasImageToImageGraph.ts | 2 +- .../util/graph/buildCanvasInpaintGraph.ts | 2 +- .../util/graph/buildCanvasOutpaintGraph.ts | 2 +- .../graph/buildCanvasSDXLImageToImageGraph.ts | 2 +- .../util/graph/buildCanvasSDXLInpaintGraph.ts | 2 +- .../graph/buildCanvasSDXLOutpaintGraph.ts | 2 +- .../graph/buildCanvasSDXLTextToImageGraph.ts | 2 +- .../util/graph/buildCanvasTextToImageGraph.ts | 2 +- .../util/graph/buildLinearBatchConfig.ts | 2 +- .../graph/buildLinearImageToImageGraph.ts | 4 +- .../graph/buildLinearSDXLImageToImageGraph.ts | 4 +- .../graph/buildLinearSDXLTextToImageGraph.ts | 8 +- .../util/graph/buildLinearTextToImageGraph.ts | 8 +- .../nodes/util/graph/graphBuilderUtils.ts | 2 +- .../components/Core/ParamNegativePrompt.tsx | 4 +- .../components/Core/ParamPositivePrompt.tsx | 4 +- .../queue/components/QueueButtonTooltip.tsx | 8 +- .../ParamSDXLNegativeStylePrompt.tsx | 4 +- .../ParamSDXLPositiveStylePrompt.tsx | 4 +- .../SDXLPrompts/SDXLConcatButton.tsx | 4 +- .../components/SDXLPrompts/SDXLPrompts.tsx | 2 +- .../ControlSettingsAccordion.tsx | 11 +-- .../ImageSettingsAccordion.tsx | 8 +- .../ImageSizeLinear.tsx | 8 +- .../components/ParametersPanelTextToImage.tsx | 4 +- .../ui/components/tabs/TextToImageTab.tsx | 4 +- 78 files changed, 336 insertions(+), 340 deletions(-) create mode 100644 invokeai/frontend/web/src/features/controlLayers/components/ControlLayersEditor.stories.tsx rename invokeai/frontend/web/src/features/controlLayers/components/{RegionalPromptsEditor.tsx => ControlLayersEditor.tsx} (64%) rename invokeai/frontend/web/src/features/controlLayers/components/{RegionalPromptsPanelContent.tsx => ControlLayersPanelContent.tsx} (80%) rename invokeai/frontend/web/src/features/controlLayers/components/{RegionalPromptsToolbar.tsx => ControlLayersToolbar.tsx} (84%) delete mode 100644 invokeai/frontend/web/src/features/controlLayers/components/RegionalPromptingEditor.stories.tsx rename invokeai/frontend/web/src/features/controlLayers/store/{regionalPromptsSlice.ts => controlLayersSlice.ts} (90%) rename invokeai/frontend/web/src/features/nodes/util/graph/{addRegionalPromptsToGraph.ts => addControlLayersToGraph.ts} (98%) diff --git a/invokeai/frontend/web/public/locales/en.json b/invokeai/frontend/web/public/locales/en.json index 9d93ebed8d..36e915c4fc 100644 --- a/invokeai/frontend/web/public/locales/en.json +++ b/invokeai/frontend/web/public/locales/en.json @@ -1513,7 +1513,7 @@ "app": { "storeNotInitialized": "Store is not initialized" }, - "regionalPrompts": { + "controlLayers": { "deleteAll": "Delete All", "addLayer": "Add Layer", "moveToFront": "Move to Front", @@ -1521,8 +1521,7 @@ "moveForward": "Move Forward", "moveBackward": "Move Backward", "brushSize": "Brush Size", - "regionalControl": "Regional Control (ALPHA)", - "enableRegionalPrompts": "Enable $t(regionalPrompts.regionalPrompts)", + "controlLayers": "Control Layers (BETA)", "globalMaskOpacity": "Global Mask Opacity", "autoNegative": "Auto Negative", "toggleVisibility": "Toggle Layer Visibility", @@ -1535,7 +1534,7 @@ "addNegativePrompt": "Add $t(common.negativePrompt)", "addIPAdapter": "Add $t(common.ipAdapter)", "maskedGuidance": "Masked Guidance", - "maskedGuidanceLayer": "$t(regionalPrompts.maskedGuidance) $t(unifiedCanvas.layer)", + "maskedGuidanceLayer": "$t(controlLayers.maskedGuidance) $t(unifiedCanvas.layer)", "controlNetLayer": "$t(common.controlNet) $t(unifiedCanvas.layer)", "ipAdapterLayer": "$t(common.ipAdapter) $t(unifiedCanvas.layer)", "opacity": "Opacity" diff --git a/invokeai/frontend/web/src/app/logging/logger.ts b/invokeai/frontend/web/src/app/logging/logger.ts index 00389d8c4f..ca7a24201a 100644 --- a/invokeai/frontend/web/src/app/logging/logger.ts +++ b/invokeai/frontend/web/src/app/logging/logger.ts @@ -28,7 +28,7 @@ export type LoggerNamespace = | 'session' | 'queue' | 'dnd' - | 'regionalPrompts'; + | 'controlLayers'; export const logger = (namespace: LoggerNamespace) => $logger.get().child({ namespace }); diff --git a/invokeai/frontend/web/src/app/store/middleware/listenerMiddleware/listeners/modelsLoaded.ts b/invokeai/frontend/web/src/app/store/middleware/listenerMiddleware/listeners/modelsLoaded.ts index 42b1cd36b4..eb86f54c84 100644 --- a/invokeai/frontend/web/src/app/store/middleware/listenerMiddleware/listeners/modelsLoaded.ts +++ b/invokeai/frontend/web/src/app/store/middleware/listenerMiddleware/listeners/modelsLoaded.ts @@ -6,7 +6,7 @@ import { controlAdapterModelCleared, selectControlAdapterAll, } 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 { calculateNewSize } from 'features/parameters/components/ImageSize/calculateNewSize'; import { modelChanged, vaeSelected } from 'features/parameters/store/generationSlice'; @@ -72,15 +72,15 @@ const handleMainModels: ModelHandler = (models, state, dispatch, log) => { const optimalDimension = getOptimalDimension(defaultModelInList); if ( getIsSizeOptimal( - state.regionalPrompts.present.size.width, - state.regionalPrompts.present.size.height, + state.controlLayers.present.size.width, + state.controlLayers.present.size.height, optimalDimension ) ) { return; } const { width, height } = calculateNewSize( - state.regionalPrompts.present.size.aspectRatio.value, + state.controlLayers.present.size.aspectRatio.value, optimalDimension * optimalDimension ); diff --git a/invokeai/frontend/web/src/app/store/middleware/listenerMiddleware/listeners/promptChanged.ts b/invokeai/frontend/web/src/app/store/middleware/listenerMiddleware/listeners/promptChanged.ts index 06df2e2b92..4633eb45a5 100644 --- a/invokeai/frontend/web/src/app/store/middleware/listenerMiddleware/listeners/promptChanged.ts +++ b/invokeai/frontend/web/src/app/store/middleware/listenerMiddleware/listeners/promptChanged.ts @@ -1,6 +1,6 @@ import { isAnyOf } from '@reduxjs/toolkit'; import type { AppStartListening } from 'app/store/middleware/listenerMiddleware'; -import { positivePromptChanged } from 'features/controlLayers/store/regionalPromptsSlice'; +import { positivePromptChanged } from 'features/controlLayers/store/controlLayersSlice'; import { combinatorialToggled, isErrorChanged, @@ -28,7 +28,7 @@ export const addDynamicPromptsListener = (startAppListening: AppStartListening) effect: async (action, { dispatch, getState, cancelActiveListeners, delay }) => { cancelActiveListeners(); const state = getState(); - const { positivePrompt } = state.regionalPrompts.present; + const { positivePrompt } = state.controlLayers.present; const { maxPrompts } = state.dynamicPrompts; if (state.config.disabledFeatures.includes('dynamicPrompting')) { diff --git a/invokeai/frontend/web/src/app/store/middleware/listenerMiddleware/listeners/regionalControlToControlAdapterBridge.ts b/invokeai/frontend/web/src/app/store/middleware/listenerMiddleware/listeners/regionalControlToControlAdapterBridge.ts index ae7913594a..d90dfb97bf 100644 --- a/invokeai/frontend/web/src/app/store/middleware/listenerMiddleware/listeners/regionalControlToControlAdapterBridge.ts +++ b/invokeai/frontend/web/src/app/store/middleware/listenerMiddleware/listeners/regionalControlToControlAdapterBridge.ts @@ -11,19 +11,19 @@ import { maskedGuidanceLayerAdded, maskLayerIPAdapterAdded, maskLayerIPAdapterDeleted, -} from 'features/controlLayers/store/regionalPromptsSlice'; +} from 'features/controlLayers/store/controlLayersSlice'; import type { Layer } from 'features/controlLayers/store/types'; import { modelConfigsAdapterSelectors, modelsApi } from 'services/api/endpoints/models'; import { isControlNetModelConfig, isIPAdapterModelConfig } from 'services/api/types'; import { assert } from 'tsafe'; import { v4 as uuidv4 } from 'uuid'; -export const guidanceLayerAdded = createAction('regionalPrompts/guidanceLayerAdded'); -export const guidanceLayerDeleted = createAction('regionalPrompts/guidanceLayerDeleted'); -export const allLayersDeleted = createAction('regionalPrompts/allLayersDeleted'); -export const guidanceLayerIPAdapterAdded = createAction('regionalPrompts/guidanceLayerIPAdapterAdded'); +export const guidanceLayerAdded = createAction('controlLayers/guidanceLayerAdded'); +export const guidanceLayerDeleted = createAction('controlLayers/guidanceLayerDeleted'); +export const allLayersDeleted = createAction('controlLayers/allLayersDeleted'); +export const guidanceLayerIPAdapterAdded = createAction('controlLayers/guidanceLayerIPAdapterAdded'); export const guidanceLayerIPAdapterDeleted = createAction<{ layerId: string; ipAdapterId: string }>( - 'regionalPrompts/guidanceLayerIPAdapterDeleted' + 'controlLayers/guidanceLayerIPAdapterDeleted' ); export const addRegionalControlToControlAdapterBridge = (startAppListening: AppStartListening) => { @@ -32,7 +32,7 @@ export const addRegionalControlToControlAdapterBridge = (startAppListening: AppS effect: (action, { dispatch, getState }) => { const type = action.payload; const layerId = uuidv4(); - if (type === 'masked_guidance_layer') { + if (type === 'regional_guidance_layer') { dispatch(maskedGuidanceLayerAdded({ layerId })); return; } @@ -84,14 +84,14 @@ export const addRegionalControlToControlAdapterBridge = (startAppListening: AppS effect: (action, { getState, dispatch }) => { const layerId = action.payload; 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`); if (layer.type === 'ip_adapter_layer') { dispatch(controlAdapterRemoved({ id: layer.ipAdapterId })); } else if (layer.type === 'control_adapter_layer') { 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) { dispatch(controlAdapterRemoved({ id: ipAdapterId })); } @@ -104,7 +104,7 @@ export const addRegionalControlToControlAdapterBridge = (startAppListening: AppS actionCreator: allLayersDeleted, effect: (action, { dispatch, getOriginalState }) => { const state = getOriginalState(); - for (const layer of state.regionalPrompts.present.layers) { + for (const layer of state.controlLayers.present.layers) { dispatch(guidanceLayerDeleted(layer.id)); } }, diff --git a/invokeai/frontend/web/src/app/store/middleware/listenerMiddleware/listeners/setDefaultSettings.ts b/invokeai/frontend/web/src/app/store/middleware/listenerMiddleware/listeners/setDefaultSettings.ts index 0644e40856..6f3aa9756a 100644 --- a/invokeai/frontend/web/src/app/store/middleware/listenerMiddleware/listeners/setDefaultSettings.ts +++ b/invokeai/frontend/web/src/app/store/middleware/listenerMiddleware/listeners/setDefaultSettings.ts @@ -1,5 +1,5 @@ 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 { setCfgRescaleMultiplier, diff --git a/invokeai/frontend/web/src/app/store/store.ts b/invokeai/frontend/web/src/app/store/store.ts index 054617f77c..9661f57f99 100644 --- a/invokeai/frontend/web/src/app/store/store.ts +++ b/invokeai/frontend/web/src/app/store/store.ts @@ -11,10 +11,10 @@ import { controlAdaptersSlice, } from 'features/controlAdapters/store/controlAdaptersSlice'; import { - regionalPromptsPersistConfig, - regionalPromptsSlice, - regionalPromptsUndoableConfig, -} from 'features/controlLayers/store/regionalPromptsSlice'; + controlLayersPersistConfig, + controlLayersSlice, + controlLayersUndoableConfig, +} from 'features/controlLayers/store/controlLayersSlice'; import { deleteImageModalSlice } from 'features/deleteImageModal/store/slice'; import { dynamicPromptsPersistConfig, dynamicPromptsSlice } from 'features/dynamicPrompts/store/dynamicPromptsSlice'; import { galleryPersistConfig, gallerySlice } from 'features/gallery/store/gallerySlice'; @@ -65,7 +65,7 @@ const allReducers = { [queueSlice.name]: queueSlice.reducer, [workflowSlice.name]: workflowSlice.reducer, [hrfSlice.name]: hrfSlice.reducer, - [regionalPromptsSlice.name]: undoable(regionalPromptsSlice.reducer, regionalPromptsUndoableConfig), + [controlLayersSlice.name]: undoable(controlLayersSlice.reducer, controlLayersUndoableConfig), [api.reducerPath]: api.reducer, }; @@ -110,7 +110,7 @@ const persistConfigs: { [key in keyof typeof allReducers]?: PersistConfig } = { [loraPersistConfig.name]: loraPersistConfig, [modelManagerV2PersistConfig.name]: modelManagerV2PersistConfig, [hrfPersistConfig.name]: hrfPersistConfig, - [regionalPromptsPersistConfig.name]: regionalPromptsPersistConfig, + [controlLayersPersistConfig.name]: controlLayersPersistConfig, }; const unserialize: UnserializeFunction = (data, key) => { diff --git a/invokeai/frontend/web/src/common/hooks/useIsReadyToEnqueue.ts b/invokeai/frontend/web/src/common/hooks/useIsReadyToEnqueue.ts index 461c3b3032..d765e987eb 100644 --- a/invokeai/frontend/web/src/common/hooks/useIsReadyToEnqueue.ts +++ b/invokeai/frontend/web/src/common/hooks/useIsReadyToEnqueue.ts @@ -5,7 +5,7 @@ import { selectControlAdaptersSlice, } from 'features/controlAdapters/store/controlAdaptersSlice'; 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 { getShouldProcessPrompt } from 'features/dynamicPrompts/util/getShouldProcessPrompt'; import { selectNodesSlice } from 'features/nodes/store/nodesSlice'; @@ -24,12 +24,12 @@ const selector = createMemoizedSelector( selectSystemSlice, selectNodesSlice, selectDynamicPromptsSlice, - selectRegionalPromptsSlice, + selectControlLayersSlice, activeTabNameSelector, ], - (controlAdapters, generation, system, nodes, dynamicPrompts, regionalPrompts, activeTabName) => { + (controlAdapters, generation, system, nodes, dynamicPrompts, controlLayers, activeTabName) => { const { initialImage, model } = generation; - const { positivePrompt } = regionalPrompts.present; + const { positivePrompt } = controlLayers.present; const { isConnected } = system; @@ -101,10 +101,10 @@ const selector = createMemoizedSelector( if (activeTabName === 'txt2img') { // Special handling for control layers on txt2img - const enabledControlLayersAdapterIds = regionalPrompts.present.layers + const enabledControlLayersAdapterIds = controlLayers.present.layers .filter((l) => l.isEnabled) .flatMap((layer) => { - if (layer.type === 'masked_guidance_layer') { + if (layer.type === 'regional_guidance_layer') { return layer.ipAdapterIds; } if (layer.type === 'control_adapter_layer') { @@ -117,8 +117,8 @@ const selector = createMemoizedSelector( enabledControlAdapters = enabledControlAdapters.filter((ca) => enabledControlLayersAdapterIds.includes(ca.id)); } else { - const allControlLayerAdapterIds = regionalPrompts.present.layers.flatMap((layer) => { - if (layer.type === 'masked_guidance_layer') { + const allControlLayerAdapterIds = controlLayers.present.layers.flatMap((layer) => { + if (layer.type === 'regional_guidance_layer') { return layer.ipAdapterIds; } if (layer.type === 'control_adapter_layer') { diff --git a/invokeai/frontend/web/src/features/controlAdapters/components/ControlAdapterImagePreview.tsx b/invokeai/frontend/web/src/features/controlAdapters/components/ControlAdapterImagePreview.tsx index 017cbe4bf6..56589fe613 100644 --- a/invokeai/frontend/web/src/features/controlAdapters/components/ControlAdapterImagePreview.tsx +++ b/invokeai/frontend/web/src/features/controlAdapters/components/ControlAdapterImagePreview.tsx @@ -13,7 +13,7 @@ import { controlAdapterImageChanged, selectControlAdaptersSlice, } 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 { calculateNewSize } from 'features/parameters/components/ImageSize/calculateNewSize'; import { selectOptimalDimension } from 'features/parameters/store/generationSlice'; diff --git a/invokeai/frontend/web/src/features/controlLayers/components/AddLayerButton.tsx b/invokeai/frontend/web/src/features/controlLayers/components/AddLayerButton.tsx index b112cebe1b..9c324b1f9e 100644 --- a/invokeai/frontend/web/src/features/controlLayers/components/AddLayerButton.tsx +++ b/invokeai/frontend/web/src/features/controlLayers/components/AddLayerButton.tsx @@ -9,7 +9,7 @@ export const AddLayerButton = memo(() => { const { t } = useTranslation(); const dispatch = useAppDispatch(); const addMaskedGuidanceLayer = useCallback(() => { - dispatch(guidanceLayerAdded('masked_guidance_layer')); + dispatch(guidanceLayerAdded('regional_guidance_layer')); }, [dispatch]); const addControlNetLayer = useCallback(() => { dispatch(guidanceLayerAdded('control_adapter_layer')); @@ -21,12 +21,12 @@ export const AddLayerButton = memo(() => { return ( } variant="ghost"> - {t('regionalPrompts.addLayer')} + {t('controlLayers.addLayer')} - {t('regionalPrompts.maskedGuidanceLayer')} - {t('regionalPrompts.controlNetLayer')} - {t('regionalPrompts.ipAdapterLayer')} + {t('controlLayers.maskedGuidanceLayer')} + {t('controlLayers.controlNetLayer')} + {t('controlLayers.ipAdapterLayer')} ); diff --git a/invokeai/frontend/web/src/features/controlLayers/components/AddPromptButtons.tsx b/invokeai/frontend/web/src/features/controlLayers/components/AddPromptButtons.tsx index 0ed7a479c4..81b119c7c3 100644 --- a/invokeai/frontend/web/src/features/controlLayers/components/AddPromptButtons.tsx +++ b/invokeai/frontend/web/src/features/controlLayers/components/AddPromptButtons.tsx @@ -6,8 +6,8 @@ import { isMaskedGuidanceLayer, maskLayerNegativePromptChanged, maskLayerPositivePromptChanged, - selectRegionalPromptsSlice, -} from 'features/controlLayers/store/regionalPromptsSlice'; + selectControlLayersSlice, +} from 'features/controlLayers/store/controlLayersSlice'; import { useCallback, useMemo } from 'react'; import { useTranslation } from 'react-i18next'; import { PiPlusBold } from 'react-icons/pi'; @@ -21,8 +21,8 @@ export const AddPromptButtons = ({ layerId }: AddPromptButtonProps) => { const dispatch = useAppDispatch(); const selectValidActions = useMemo( () => - createMemoizedSelector(selectRegionalPromptsSlice, (regionalPrompts) => { - const layer = regionalPrompts.present.layers.find((l) => l.id === layerId); + createMemoizedSelector(selectControlLayersSlice, (controlLayers) => { + const layer = controlLayers.present.layers.find((l) => l.id === layerId); assert(isMaskedGuidanceLayer(layer), `Layer ${layerId} not found or not an RP layer`); return { canAddPositivePrompt: layer.positivePrompt === null, diff --git a/invokeai/frontend/web/src/features/controlLayers/components/BrushSize.tsx b/invokeai/frontend/web/src/features/controlLayers/components/BrushSize.tsx index f145a47e75..a34250c29f 100644 --- a/invokeai/frontend/web/src/features/controlLayers/components/BrushSize.tsx +++ b/invokeai/frontend/web/src/features/controlLayers/components/BrushSize.tsx @@ -10,7 +10,7 @@ import { PopoverTrigger, } from '@invoke-ai/ui-library'; 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 { useTranslation } from 'react-i18next'; @@ -20,7 +20,7 @@ const formatPx = (v: number | string) => `${v} px`; export const BrushSize = memo(() => { const dispatch = useAppDispatch(); const { t } = useTranslation(); - const brushSize = useAppSelector((s) => s.regionalPrompts.present.brushSize); + const brushSize = useAppSelector((s) => s.controlLayers.present.brushSize); const onChange = useCallback( (v: number) => { dispatch(brushSizeChanged(Math.round(v))); @@ -29,13 +29,13 @@ export const BrushSize = memo(() => { ); return ( - {t('regionalPrompts.brushSize')} + {t('controlLayers.brushSize')} { { } variant="ghost" @@ -50,7 +50,7 @@ const CALayerOpacity = ({ layerId }: Props) => { - {t('regionalPrompts.opacity')} + {t('controlLayers.opacity')} { const dispatch = useAppDispatch(); const selector = useMemo( () => - createMemoizedSelector(selectRegionalPromptsSlice, (regionalPrompts) => { - const layer = regionalPrompts.present.layers.find((l) => l.id === layerId); + createMemoizedSelector(selectControlLayersSlice, (controlLayers) => { + const layer = controlLayers.present.layers.find((l) => l.id === layerId); assert(isControlAdapterLayer(layer), `Layer ${layerId} not found or not a ControlNet layer`); return { controlNetId: layer.controlNetId, - isSelected: layerId === regionalPrompts.present.selectedLayerId, + isSelected: layerId === controlLayers.present.selectedLayerId, }; }), [layerId] diff --git a/invokeai/frontend/web/src/features/controlLayers/components/ControlLayersEditor.stories.tsx b/invokeai/frontend/web/src/features/controlLayers/components/ControlLayersEditor.stories.tsx new file mode 100644 index 0000000000..c0fa306c6b --- /dev/null +++ b/invokeai/frontend/web/src/features/controlLayers/components/ControlLayersEditor.stories.tsx @@ -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 = { + title: 'Feature/ControlLayers', + tags: ['autodocs'], + component: ControlLayersEditor, +}; + +export default meta; +type Story = StoryObj; + +const Component = () => { + return ( + + + + ); +}; + +export const Default: Story = { + render: Component, +}; diff --git a/invokeai/frontend/web/src/features/controlLayers/components/RegionalPromptsEditor.tsx b/invokeai/frontend/web/src/features/controlLayers/components/ControlLayersEditor.tsx similarity index 64% rename from invokeai/frontend/web/src/features/controlLayers/components/RegionalPromptsEditor.tsx rename to invokeai/frontend/web/src/features/controlLayers/components/ControlLayersEditor.tsx index cf190a444c..e9275426fe 100644 --- a/invokeai/frontend/web/src/features/controlLayers/components/RegionalPromptsEditor.tsx +++ b/invokeai/frontend/web/src/features/controlLayers/components/ControlLayersEditor.tsx @@ -1,10 +1,10 @@ /* eslint-disable i18next/no-literal-string */ 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 { memo } from 'react'; -export const RegionalPromptsEditor = memo(() => { +export const ControlLayersEditor = memo(() => { return ( { alignItems="center" justifyContent="center" > - + ); }); -RegionalPromptsEditor.displayName = 'RegionalPromptsEditor'; +ControlLayersEditor.displayName = 'ControlLayersEditor'; diff --git a/invokeai/frontend/web/src/features/controlLayers/components/RegionalPromptsPanelContent.tsx b/invokeai/frontend/web/src/features/controlLayers/components/ControlLayersPanelContent.tsx similarity index 80% rename from invokeai/frontend/web/src/features/controlLayers/components/RegionalPromptsPanelContent.tsx rename to invokeai/frontend/web/src/features/controlLayers/components/ControlLayersPanelContent.tsx index 59853ddda3..4d1545badb 100644 --- a/invokeai/frontend/web/src/features/controlLayers/components/RegionalPromptsPanelContent.tsx +++ b/invokeai/frontend/web/src/features/controlLayers/components/ControlLayersPanelContent.tsx @@ -8,17 +8,17 @@ import { ControlAdapterLayerListItem } from 'features/controlLayers/components/C import { DeleteAllLayersButton } from 'features/controlLayers/components/DeleteAllLayersButton'; import { IPAdapterLayerListItem } from 'features/controlLayers/components/IPAdapterLayerListItem'; 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 { partition } from 'lodash-es'; import { memo } from 'react'; -const selectLayerIdTypePairs = createMemoizedSelector(selectRegionalPromptsSlice, (regionalPrompts) => { - const [renderableLayers, ipAdapterLayers] = partition(regionalPrompts.present.layers, isRenderableLayer); +const selectLayerIdTypePairs = createMemoizedSelector(selectControlLayersSlice, (controlLayers) => { + const [renderableLayers, ipAdapterLayers] = partition(controlLayers.present.layers, isRenderableLayer); return [...ipAdapterLayers, ...renderableLayers].map((l) => ({ id: l.id, type: l.type })).reverse(); }); -export const RegionalPromptsPanelContent = memo(() => { +export const ControlLayersPanelContent = memo(() => { const layerIdTypePairs = useAppSelector(selectLayerIdTypePairs); return ( @@ -37,7 +37,7 @@ export const RegionalPromptsPanelContent = memo(() => { ); }); -RegionalPromptsPanelContent.displayName = 'RegionalPromptsPanelContent'; +ControlLayersPanelContent.displayName = 'ControlLayersPanelContent'; type LayerWrapperProps = { id: string; @@ -45,7 +45,7 @@ type LayerWrapperProps = { }; const LayerWrapper = memo(({ id, type }: LayerWrapperProps) => { - if (type === 'masked_guidance_layer') { + if (type === 'regional_guidance_layer') { return ; } if (type === 'control_adapter_layer') { diff --git a/invokeai/frontend/web/src/features/controlLayers/components/RegionalPromptsToolbar.tsx b/invokeai/frontend/web/src/features/controlLayers/components/ControlLayersToolbar.tsx similarity index 84% rename from invokeai/frontend/web/src/features/controlLayers/components/RegionalPromptsToolbar.tsx rename to invokeai/frontend/web/src/features/controlLayers/components/ControlLayersToolbar.tsx index 978dddf717..15a74a332a 100644 --- a/invokeai/frontend/web/src/features/controlLayers/components/RegionalPromptsToolbar.tsx +++ b/invokeai/frontend/web/src/features/controlLayers/components/ControlLayersToolbar.tsx @@ -6,7 +6,7 @@ import { ToolChooser } from 'features/controlLayers/components/ToolChooser'; import { UndoRedoButtonGroup } from 'features/controlLayers/components/UndoRedoButtonGroup'; import { memo } from 'react'; -export const RegionalPromptsToolbar = memo(() => { +export const ControlLayersToolbar = memo(() => { return ( @@ -17,4 +17,4 @@ export const RegionalPromptsToolbar = memo(() => { ); }); -RegionalPromptsToolbar.displayName = 'RegionalPromptsToolbar'; +ControlLayersToolbar.displayName = 'ControlLayersToolbar'; diff --git a/invokeai/frontend/web/src/features/controlLayers/components/DeleteAllLayersButton.tsx b/invokeai/frontend/web/src/features/controlLayers/components/DeleteAllLayersButton.tsx index 83e464b844..110a018a8e 100644 --- a/invokeai/frontend/web/src/features/controlLayers/components/DeleteAllLayersButton.tsx +++ b/invokeai/frontend/web/src/features/controlLayers/components/DeleteAllLayersButton.tsx @@ -14,7 +14,7 @@ export const DeleteAllLayersButton = memo(() => { return ( ); }); diff --git a/invokeai/frontend/web/src/features/controlLayers/components/GlobalMaskLayerOpacity.tsx b/invokeai/frontend/web/src/features/controlLayers/components/GlobalMaskLayerOpacity.tsx index 3769b40bc7..40985499db 100644 --- a/invokeai/frontend/web/src/features/controlLayers/components/GlobalMaskLayerOpacity.tsx +++ b/invokeai/frontend/web/src/features/controlLayers/components/GlobalMaskLayerOpacity.tsx @@ -2,8 +2,8 @@ import { CompositeNumberInput, CompositeSlider, Flex, FormControl, FormLabel } f import { useAppDispatch, useAppSelector } from 'app/store/storeHooks'; import { globalMaskLayerOpacityChanged, - initialRegionalPromptsState, -} from 'features/controlLayers/store/regionalPromptsSlice'; + initialControlLayersState, +} from 'features/controlLayers/store/controlLayersSlice'; import { memo, useCallback } from 'react'; import { useTranslation } from 'react-i18next'; @@ -14,7 +14,7 @@ export const GlobalMaskLayerOpacity = memo(() => { const dispatch = useAppDispatch(); const { t } = useTranslation(); const globalMaskLayerOpacity = useAppSelector((s) => - Math.round(s.regionalPrompts.present.globalMaskLayerOpacity * 100) + Math.round(s.controlLayers.present.globalMaskLayerOpacity * 100) ); const onChange = useCallback( (v: number) => { @@ -24,14 +24,14 @@ export const GlobalMaskLayerOpacity = memo(() => { ); return ( - {t('regionalPrompts.globalMaskOpacity')} + {t('controlLayers.globalMaskOpacity')} { max={100} step={1} value={globalMaskLayerOpacity} - defaultValue={initialRegionalPromptsState.globalMaskLayerOpacity * 100} + defaultValue={initialControlLayersState.globalMaskLayerOpacity * 100} onChange={onChange} w={28} format={formatPct} diff --git a/invokeai/frontend/web/src/features/controlLayers/components/IPAdapterLayerListItem.tsx b/invokeai/frontend/web/src/features/controlLayers/components/IPAdapterLayerListItem.tsx index 0a07f29e82..188a7c1f4f 100644 --- a/invokeai/frontend/web/src/features/controlLayers/components/IPAdapterLayerListItem.tsx +++ b/invokeai/frontend/web/src/features/controlLayers/components/IPAdapterLayerListItem.tsx @@ -5,7 +5,7 @@ import ControlAdapterLayerConfig from 'features/controlLayers/components/control import { LayerTitle } from 'features/controlLayers/components/LayerTitle'; import { RPLayerDeleteButton } from 'features/controlLayers/components/RPLayerDeleteButton'; 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 { assert } from 'tsafe'; @@ -16,8 +16,8 @@ type Props = { export const IPAdapterLayerListItem = memo(({ layerId }: Props) => { const selector = useMemo( () => - createMemoizedSelector(selectRegionalPromptsSlice, (regionalPrompts) => { - const layer = regionalPrompts.present.layers.find((l) => l.id === layerId); + createMemoizedSelector(selectControlLayersSlice, (controlLayers) => { + const layer = controlLayers.present.layers.find((l) => l.id === layerId); assert(isIPAdapterLayer(layer), `Layer ${layerId} not found or not an IP Adapter layer`); return layer.ipAdapterId; }), diff --git a/invokeai/frontend/web/src/features/controlLayers/components/LayerTitle.tsx b/invokeai/frontend/web/src/features/controlLayers/components/LayerTitle.tsx index a14ce1fd96..a192b9e865 100644 --- a/invokeai/frontend/web/src/features/controlLayers/components/LayerTitle.tsx +++ b/invokeai/frontend/web/src/features/controlLayers/components/LayerTitle.tsx @@ -10,8 +10,8 @@ type Props = { export const LayerTitle = memo(({ type }: Props) => { const { t } = useTranslation(); const title = useMemo(() => { - if (type === 'masked_guidance_layer') { - return t('regionalPrompts.maskedGuidance'); + if (type === 'regional_guidance_layer') { + return t('controlLayers.maskedGuidance'); } else if (type === 'control_adapter_layer') { return t('common.controlNet'); } else if (type === 'ip_adapter_layer') { diff --git a/invokeai/frontend/web/src/features/controlLayers/components/MaskedGuidanceLayerListItem.tsx b/invokeai/frontend/web/src/features/controlLayers/components/MaskedGuidanceLayerListItem.tsx index 11b285dae0..2a48417f8c 100644 --- a/invokeai/frontend/web/src/features/controlLayers/components/MaskedGuidanceLayerListItem.tsx +++ b/invokeai/frontend/web/src/features/controlLayers/components/MaskedGuidanceLayerListItem.tsx @@ -14,8 +14,8 @@ import { RPLayerVisibilityToggle } from 'features/controlLayers/components/RPLay import { isMaskedGuidanceLayer, layerSelected, - selectRegionalPromptsSlice, -} from 'features/controlLayers/store/regionalPromptsSlice'; + selectControlLayersSlice, +} from 'features/controlLayers/store/controlLayersSlice'; import { memo, useCallback, useMemo } from 'react'; import { useTranslation } from 'react-i18next'; import { assert } from 'tsafe'; @@ -31,15 +31,15 @@ export const MaskedGuidanceLayerListItem = memo(({ layerId }: Props) => { const dispatch = useAppDispatch(); const selector = useMemo( () => - createMemoizedSelector(selectRegionalPromptsSlice, (regionalPrompts) => { - const layer = regionalPrompts.present.layers.find((l) => l.id === layerId); + createMemoizedSelector(selectControlLayersSlice, (controlLayers) => { + const layer = controlLayers.present.layers.find((l) => l.id === layerId); assert(isMaskedGuidanceLayer(layer), `Layer ${layerId} not found or not an RP layer`); return { color: rgbColorToString(layer.previewColor), hasPositivePrompt: layer.positivePrompt !== null, hasNegativePrompt: layer.negativePrompt !== null, hasIPAdapters: layer.ipAdapterIds.length > 0, - isSelected: layerId === regionalPrompts.present.selectedLayerId, + isSelected: layerId === controlLayers.present.selectedLayerId, autoNegative: layer.autoNegative, }; }), @@ -63,11 +63,11 @@ export const MaskedGuidanceLayerListItem = memo(({ layerId }: Props) => { - + {autoNegative === 'invert' && ( - {t('regionalPrompts.autoNegative')} + {t('controlLayers.autoNegative')} )} diff --git a/invokeai/frontend/web/src/features/controlLayers/components/RPLayerAutoNegativeCheckbox.tsx b/invokeai/frontend/web/src/features/controlLayers/components/RPLayerAutoNegativeCheckbox.tsx index ec9ac5b24e..32a60039f6 100644 --- a/invokeai/frontend/web/src/features/controlLayers/components/RPLayerAutoNegativeCheckbox.tsx +++ b/invokeai/frontend/web/src/features/controlLayers/components/RPLayerAutoNegativeCheckbox.tsx @@ -4,8 +4,8 @@ import { useAppDispatch, useAppSelector } from 'app/store/storeHooks'; import { isMaskedGuidanceLayer, maskLayerAutoNegativeChanged, - selectRegionalPromptsSlice, -} from 'features/controlLayers/store/regionalPromptsSlice'; + selectControlLayersSlice, +} from 'features/controlLayers/store/controlLayersSlice'; import type { ChangeEvent } from 'react'; import { memo, useCallback, useMemo } from 'react'; import { useTranslation } from 'react-i18next'; @@ -18,8 +18,8 @@ type Props = { const useAutoNegative = (layerId: string) => { const selectAutoNegative = useMemo( () => - createSelector(selectRegionalPromptsSlice, (regionalPrompts) => { - const layer = regionalPrompts.present.layers.find((l) => l.id === layerId); + createSelector(selectControlLayersSlice, (controlLayers) => { + const layer = controlLayers.present.layers.find((l) => l.id === layerId); assert(isMaskedGuidanceLayer(layer), `Layer ${layerId} not found or not an RP layer`); return layer.autoNegative; }), @@ -42,7 +42,7 @@ export const MaskedGuidanceLayerAutoNegativeCheckbox = memo(({ layerId }: Props) return ( - {t('regionalPrompts.autoNegative')} + {t('controlLayers.autoNegative')} ); diff --git a/invokeai/frontend/web/src/features/controlLayers/components/RPLayerColorPicker.tsx b/invokeai/frontend/web/src/features/controlLayers/components/RPLayerColorPicker.tsx index 550ff1a2bc..e1a20d0210 100644 --- a/invokeai/frontend/web/src/features/controlLayers/components/RPLayerColorPicker.tsx +++ b/invokeai/frontend/web/src/features/controlLayers/components/RPLayerColorPicker.tsx @@ -6,8 +6,8 @@ import { rgbColorToString } from 'features/canvas/util/colorToString'; import { isMaskedGuidanceLayer, maskLayerPreviewColorChanged, - selectRegionalPromptsSlice, -} from 'features/controlLayers/store/regionalPromptsSlice'; + selectControlLayersSlice, +} from 'features/controlLayers/store/controlLayersSlice'; import { memo, useCallback, useMemo } from 'react'; import type { RgbColor } from 'react-colorful'; import { useTranslation } from 'react-i18next'; @@ -21,8 +21,8 @@ export const RPLayerColorPicker = memo(({ layerId }: Props) => { const { t } = useTranslation(); const selectColor = useMemo( () => - createMemoizedSelector(selectRegionalPromptsSlice, (regionalPrompts) => { - const layer = regionalPrompts.present.layers.find((l) => l.id === layerId); + createMemoizedSelector(selectControlLayersSlice, (controlLayers) => { + const layer = controlLayers.present.layers.find((l) => l.id === layerId); assert(isMaskedGuidanceLayer(layer), `Layer ${layerId} not found or not an vector mask layer`); return layer.previewColor; }), @@ -40,10 +40,10 @@ export const RPLayerColorPicker = memo(({ layerId }: Props) => { - + { const selectIPAdapterIds = useMemo( () => - createMemoizedSelector(selectRegionalPromptsSlice, (regionalPrompts) => { - const layer = regionalPrompts.present.layers.filter(isMaskedGuidanceLayer).find((l) => l.id === layerId); + createMemoizedSelector(selectControlLayersSlice, (controlLayers) => { + const layer = controlLayers.present.layers.filter(isMaskedGuidanceLayer).find((l) => l.id === layerId); assert(layer, `Layer ${layerId} not found`); return layer.ipAdapterIds; }), diff --git a/invokeai/frontend/web/src/features/controlLayers/components/RPLayerMenu.tsx b/invokeai/frontend/web/src/features/controlLayers/components/RPLayerMenu.tsx index 41e1fb3aa8..95879701c8 100644 --- a/invokeai/frontend/web/src/features/controlLayers/components/RPLayerMenu.tsx +++ b/invokeai/frontend/web/src/features/controlLayers/components/RPLayerMenu.tsx @@ -3,7 +3,7 @@ import { useAppDispatch } from 'app/store/storeHooks'; import { RPLayerMenuArrangeActions } from 'features/controlLayers/components/RPLayerMenuArrangeActions'; import { RPLayerMenuMaskedGuidanceActions } from 'features/controlLayers/components/RPLayerMenuMaskedGuidanceActions'; 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 { useTranslation } from 'react-i18next'; import { PiArrowCounterClockwiseBold, PiDotsThreeVerticalBold, PiTrashSimpleBold } from 'react-icons/pi'; @@ -24,19 +24,19 @@ export const RPLayerMenu = memo(({ layerId }: Props) => { } /> - {layerType === 'masked_guidance_layer' && ( + {layerType === 'regional_guidance_layer' && ( <> )} - {(layerType === 'masked_guidance_layer' || layerType === 'control_adapter_layer') && ( + {(layerType === 'regional_guidance_layer' || layerType === 'control_adapter_layer') && ( <> )} - {layerType === 'masked_guidance_layer' && ( + {layerType === 'regional_guidance_layer' && ( }> {t('accessibility.reset')} diff --git a/invokeai/frontend/web/src/features/controlLayers/components/RPLayerMenuArrangeActions.tsx b/invokeai/frontend/web/src/features/controlLayers/components/RPLayerMenuArrangeActions.tsx index deb74d4cc5..fab3a47adb 100644 --- a/invokeai/frontend/web/src/features/controlLayers/components/RPLayerMenuArrangeActions.tsx +++ b/invokeai/frontend/web/src/features/controlLayers/components/RPLayerMenuArrangeActions.tsx @@ -7,8 +7,8 @@ import { layerMovedForward, layerMovedToBack, layerMovedToFront, - selectRegionalPromptsSlice, -} from 'features/controlLayers/store/regionalPromptsSlice'; + selectControlLayersSlice, +} from 'features/controlLayers/store/controlLayersSlice'; import { memo, useCallback, useMemo } from 'react'; import { useTranslation } from 'react-i18next'; import { PiArrowDownBold, PiArrowLineDownBold, PiArrowLineUpBold, PiArrowUpBold } from 'react-icons/pi'; @@ -21,11 +21,11 @@ export const RPLayerMenuArrangeActions = memo(({ layerId }: Props) => { const { t } = useTranslation(); const selectValidActions = useMemo( () => - createMemoizedSelector(selectRegionalPromptsSlice, (regionalPrompts) => { - const layer = regionalPrompts.present.layers.find((l) => l.id === layerId); + createMemoizedSelector(selectControlLayersSlice, (controlLayers) => { + const layer = controlLayers.present.layers.find((l) => l.id === layerId); assert(isRenderableLayer(layer), `Layer ${layerId} not found or not an RP layer`); - const layerIndex = regionalPrompts.present.layers.findIndex((l) => l.id === layerId); - const layerCount = regionalPrompts.present.layers.length; + const layerIndex = controlLayers.present.layers.findIndex((l) => l.id === layerId); + const layerCount = controlLayers.present.layers.length; return { canMoveForward: layerIndex < layerCount - 1, canMoveBackward: layerIndex > 0, @@ -51,16 +51,16 @@ export const RPLayerMenuArrangeActions = memo(({ layerId }: Props) => { return ( <> }> - {t('regionalPrompts.moveToFront')} + {t('controlLayers.moveToFront')} }> - {t('regionalPrompts.moveForward')} + {t('controlLayers.moveForward')} }> - {t('regionalPrompts.moveBackward')} + {t('controlLayers.moveBackward')} }> - {t('regionalPrompts.moveToBack')} + {t('controlLayers.moveToBack')} ); diff --git a/invokeai/frontend/web/src/features/controlLayers/components/RPLayerMenuMaskedGuidanceActions.tsx b/invokeai/frontend/web/src/features/controlLayers/components/RPLayerMenuMaskedGuidanceActions.tsx index 674c9d3a26..a3da006d0b 100644 --- a/invokeai/frontend/web/src/features/controlLayers/components/RPLayerMenuMaskedGuidanceActions.tsx +++ b/invokeai/frontend/web/src/features/controlLayers/components/RPLayerMenuMaskedGuidanceActions.tsx @@ -6,8 +6,8 @@ import { isMaskedGuidanceLayer, maskLayerNegativePromptChanged, maskLayerPositivePromptChanged, - selectRegionalPromptsSlice, -} from 'features/controlLayers/store/regionalPromptsSlice'; + selectControlLayersSlice, +} from 'features/controlLayers/store/controlLayersSlice'; import { memo, useCallback, useMemo } from 'react'; import { useTranslation } from 'react-i18next'; import { PiPlusBold } from 'react-icons/pi'; @@ -20,8 +20,8 @@ export const RPLayerMenuMaskedGuidanceActions = memo(({ layerId }: Props) => { const { t } = useTranslation(); const selectValidActions = useMemo( () => - createMemoizedSelector(selectRegionalPromptsSlice, (regionalPrompts) => { - const layer = regionalPrompts.present.layers.find((l) => l.id === layerId); + createMemoizedSelector(selectControlLayersSlice, (controlLayers) => { + const layer = controlLayers.present.layers.find((l) => l.id === layerId); assert(isMaskedGuidanceLayer(layer), `Layer ${layerId} not found or not an RP layer`); return { canAddPositivePrompt: layer.positivePrompt === null, @@ -43,13 +43,13 @@ export const RPLayerMenuMaskedGuidanceActions = memo(({ layerId }: Props) => { return ( <> }> - {t('regionalPrompts.addPositivePrompt')} + {t('controlLayers.addPositivePrompt')} }> - {t('regionalPrompts.addNegativePrompt')} + {t('controlLayers.addNegativePrompt')} }> - {t('regionalPrompts.addIPAdapter')} + {t('controlLayers.addIPAdapter')} ); diff --git a/invokeai/frontend/web/src/features/controlLayers/components/RPLayerNegativePrompt.tsx b/invokeai/frontend/web/src/features/controlLayers/components/RPLayerNegativePrompt.tsx index c81c7009fe..d396de4634 100644 --- a/invokeai/frontend/web/src/features/controlLayers/components/RPLayerNegativePrompt.tsx +++ b/invokeai/frontend/web/src/features/controlLayers/components/RPLayerNegativePrompt.tsx @@ -2,7 +2,7 @@ import { Box, Textarea } from '@invoke-ai/ui-library'; import { useAppDispatch } from 'app/store/storeHooks'; import { RPLayerPromptDeleteButton } from 'features/controlLayers/components/RPLayerPromptDeleteButton'; 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 { AddPromptTriggerButton } from 'features/prompt/AddPromptTriggerButton'; import { PromptPopover } from 'features/prompt/PromptPopover'; diff --git a/invokeai/frontend/web/src/features/controlLayers/components/RPLayerPositivePrompt.tsx b/invokeai/frontend/web/src/features/controlLayers/components/RPLayerPositivePrompt.tsx index b99c529e34..054c75958c 100644 --- a/invokeai/frontend/web/src/features/controlLayers/components/RPLayerPositivePrompt.tsx +++ b/invokeai/frontend/web/src/features/controlLayers/components/RPLayerPositivePrompt.tsx @@ -2,7 +2,7 @@ import { Box, Textarea } from '@invoke-ai/ui-library'; import { useAppDispatch } from 'app/store/storeHooks'; import { RPLayerPromptDeleteButton } from 'features/controlLayers/components/RPLayerPromptDeleteButton'; 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 { AddPromptTriggerButton } from 'features/prompt/AddPromptTriggerButton'; import { PromptPopover } from 'features/prompt/PromptPopover'; diff --git a/invokeai/frontend/web/src/features/controlLayers/components/RPLayerPromptDeleteButton.tsx b/invokeai/frontend/web/src/features/controlLayers/components/RPLayerPromptDeleteButton.tsx index ae3c9317fd..b2778d0997 100644 --- a/invokeai/frontend/web/src/features/controlLayers/components/RPLayerPromptDeleteButton.tsx +++ b/invokeai/frontend/web/src/features/controlLayers/components/RPLayerPromptDeleteButton.tsx @@ -3,7 +3,7 @@ import { useAppDispatch } from 'app/store/storeHooks'; import { maskLayerNegativePromptChanged, maskLayerPositivePromptChanged, -} from 'features/controlLayers/store/regionalPromptsSlice'; +} from 'features/controlLayers/store/controlLayersSlice'; import { memo, useCallback } from 'react'; import { useTranslation } from 'react-i18next'; import { PiTrashSimpleBold } from 'react-icons/pi'; @@ -24,10 +24,10 @@ export const RPLayerPromptDeleteButton = memo(({ layerId, polarity }: Props) => } }, [dispatch, layerId, polarity]); return ( - + } onClick={onClick} /> diff --git a/invokeai/frontend/web/src/features/controlLayers/components/RPLayerVisibilityToggle.tsx b/invokeai/frontend/web/src/features/controlLayers/components/RPLayerVisibilityToggle.tsx index 67d249191c..7a39f5d95e 100644 --- a/invokeai/frontend/web/src/features/controlLayers/components/RPLayerVisibilityToggle.tsx +++ b/invokeai/frontend/web/src/features/controlLayers/components/RPLayerVisibilityToggle.tsx @@ -1,7 +1,7 @@ import { IconButton } from '@invoke-ai/ui-library'; import { useAppDispatch } from 'app/store/storeHooks'; 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 { useTranslation } from 'react-i18next'; import { PiCheckBold } from 'react-icons/pi'; @@ -21,8 +21,8 @@ export const RPLayerVisibilityToggle = memo(({ layerId }: Props) => { return ( : undefined} onClick={onClick} diff --git a/invokeai/frontend/web/src/features/controlLayers/components/RegionalPromptingEditor.stories.tsx b/invokeai/frontend/web/src/features/controlLayers/components/RegionalPromptingEditor.stories.tsx deleted file mode 100644 index 8d822fb954..0000000000 --- a/invokeai/frontend/web/src/features/controlLayers/components/RegionalPromptingEditor.stories.tsx +++ /dev/null @@ -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 = { - title: 'Feature/RegionalPrompts', - tags: ['autodocs'], - component: RegionalPromptsEditor, -}; - -export default meta; -type Story = StoryObj; - -const Component = () => { - return ( - - - - ); -}; - -export const Default: Story = { - render: Component, -}; diff --git a/invokeai/frontend/web/src/features/controlLayers/components/StageComponent.tsx b/invokeai/frontend/web/src/features/controlLayers/components/StageComponent.tsx index 2cff657aad..be7f584627 100644 --- a/invokeai/frontend/web/src/features/controlLayers/components/StageComponent.tsx +++ b/invokeai/frontend/web/src/features/controlLayers/components/StageComponent.tsx @@ -13,8 +13,8 @@ import { isMaskedGuidanceLayer, layerBboxChanged, layerTranslated, - selectRegionalPromptsSlice, -} from 'features/controlLayers/store/regionalPromptsSlice'; + selectControlLayersSlice, +} from 'features/controlLayers/store/controlLayersSlice'; import { debouncedRenderers, renderers as normalRenderers } from 'features/controlLayers/util/renderers'; import Konva from 'konva'; 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? Konva.showWarnings = false; -const log = logger('regionalPrompts'); +const log = logger('controlLayers'); -const selectSelectedLayerColor = createMemoizedSelector(selectRegionalPromptsSlice, (regionalPrompts) => { - const layer = regionalPrompts.present.layers +const selectSelectedLayerColor = createMemoizedSelector(selectControlLayersSlice, (controlLayers) => { + const layer = controlLayers.present.layers .filter(isMaskedGuidanceLayer) - .find((l) => l.id === regionalPrompts.present.selectedLayerId); + .find((l) => l.id === controlLayers.present.selectedLayerId); return layer?.previewColor ?? null; }); -const selectSelectedLayerType = createSelector(selectRegionalPromptsSlice, (regionalPrompts) => { - const selectedLayer = regionalPrompts.present.layers.find((l) => l.id === regionalPrompts.present.selectedLayerId); +const selectSelectedLayerType = createSelector(selectControlLayersSlice, (controlLayers) => { + const selectedLayer = controlLayers.present.layers.find((l) => l.id === controlLayers.present.selectedLayerId); return selectedLayer?.type ?? null; }); @@ -46,7 +46,7 @@ const useStageRenderer = ( asPreview: boolean ) => { const dispatch = useAppDispatch(); - const state = useAppSelector((s) => s.regionalPrompts.present); + const state = useAppSelector((s) => s.controlLayers.present); const tool = useStore($tool); const { onMouseDown, onMouseUp, onMouseMove, onMouseEnter, onMouseLeave, onMouseWheel } = useMouseEvents(); const cursorPosition = useStore($cursorPosition); diff --git a/invokeai/frontend/web/src/features/controlLayers/components/ToolChooser.tsx b/invokeai/frontend/web/src/features/controlLayers/components/ToolChooser.tsx index bc93556f77..53535b4248 100644 --- a/invokeai/frontend/web/src/features/controlLayers/components/ToolChooser.tsx +++ b/invokeai/frontend/web/src/features/controlLayers/components/ToolChooser.tsx @@ -4,18 +4,18 @@ import { createSelector } from '@reduxjs/toolkit'; import { useAppDispatch, useAppSelector } from 'app/store/storeHooks'; import { $tool, + selectControlLayersSlice, selectedLayerDeleted, selectedLayerReset, - selectRegionalPromptsSlice, -} from 'features/controlLayers/store/regionalPromptsSlice'; +} from 'features/controlLayers/store/controlLayersSlice'; import { useCallback } from 'react'; import { useHotkeys } from 'react-hotkeys-hook'; import { useTranslation } from 'react-i18next'; import { PiArrowsOutCardinalBold, PiEraserBold, PiPaintBrushBold, PiRectangleBold } from 'react-icons/pi'; -const selectIsDisabled = createSelector(selectRegionalPromptsSlice, (regionalPrompts) => { - const selectedLayer = regionalPrompts.present.layers.find((l) => l.id === regionalPrompts.present.selectedLayerId); - return selectedLayer?.type !== 'masked_guidance_layer'; +const selectIsDisabled = createSelector(selectControlLayersSlice, (controlLayers) => { + const selectedLayer = controlLayers.present.layers.find((l) => l.id === controlLayers.present.selectedLayerId); + return selectedLayer?.type !== 'regional_guidance_layer'; }); export const ToolChooser: React.FC = () => { @@ -70,8 +70,8 @@ export const ToolChooser: React.FC = () => { isDisabled={isDisabled} /> } variant={tool === 'rect' ? 'solid' : 'outline'} onClick={setToolToRect} diff --git a/invokeai/frontend/web/src/features/controlLayers/components/UndoRedoButtonGroup.tsx b/invokeai/frontend/web/src/features/controlLayers/components/UndoRedoButtonGroup.tsx index bfc5e0bd24..8babae7fcc 100644 --- a/invokeai/frontend/web/src/features/controlLayers/components/UndoRedoButtonGroup.tsx +++ b/invokeai/frontend/web/src/features/controlLayers/components/UndoRedoButtonGroup.tsx @@ -1,7 +1,7 @@ /* eslint-disable i18next/no-literal-string */ import { ButtonGroup, IconButton } from '@invoke-ai/ui-library'; 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 { useHotkeys } from 'react-hotkeys-hook'; import { useTranslation } from 'react-i18next'; @@ -11,13 +11,13 @@ export const UndoRedoButtonGroup = memo(() => { const { t } = useTranslation(); const dispatch = useAppDispatch(); - const mayUndo = useAppSelector((s) => s.regionalPrompts.past.length > 0); + const mayUndo = useAppSelector((s) => s.controlLayers.past.length > 0); const handleUndo = useCallback(() => { dispatch(undo()); }, [dispatch]); 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(() => { dispatch(redo()); }, [dispatch]); diff --git a/invokeai/frontend/web/src/features/controlLayers/components/controlAdapterOverrides/ControlAdapterImagePreview.tsx b/invokeai/frontend/web/src/features/controlLayers/components/controlAdapterOverrides/ControlAdapterImagePreview.tsx index 41339b0efa..b3094e5599 100644 --- a/invokeai/frontend/web/src/features/controlLayers/components/controlAdapterOverrides/ControlAdapterImagePreview.tsx +++ b/invokeai/frontend/web/src/features/controlLayers/components/controlAdapterOverrides/ControlAdapterImagePreview.tsx @@ -13,7 +13,7 @@ import { controlAdapterImageChanged, selectControlAdaptersSlice, } 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 { calculateNewSize } from 'features/parameters/components/ImageSize/calculateNewSize'; import { selectOptimalDimension } from 'features/parameters/store/generationSlice'; diff --git a/invokeai/frontend/web/src/features/controlLayers/hooks/layerStateHooks.ts b/invokeai/frontend/web/src/features/controlLayers/hooks/layerStateHooks.ts index 5925322fc9..346f553e32 100644 --- a/invokeai/frontend/web/src/features/controlLayers/hooks/layerStateHooks.ts +++ b/invokeai/frontend/web/src/features/controlLayers/hooks/layerStateHooks.ts @@ -3,16 +3,16 @@ import { useAppSelector } from 'app/store/storeHooks'; import { isControlAdapterLayer, isMaskedGuidanceLayer, - selectRegionalPromptsSlice, -} from 'features/controlLayers/store/regionalPromptsSlice'; + selectControlLayersSlice, +} from 'features/controlLayers/store/controlLayersSlice'; import { useMemo } from 'react'; import { assert } from 'tsafe'; export const useLayerPositivePrompt = (layerId: string) => { const selectLayer = useMemo( () => - createSelector(selectRegionalPromptsSlice, (regionalPrompts) => { - const layer = regionalPrompts.present.layers.find((l) => l.id === layerId); + createSelector(selectControlLayersSlice, (controlLayers) => { + const layer = controlLayers.present.layers.find((l) => l.id === layerId); assert(isMaskedGuidanceLayer(layer), `Layer ${layerId} not found or not an RP layer`); assert(layer.positivePrompt !== null, `Layer ${layerId} does not have a positive prompt`); return layer.positivePrompt; @@ -26,8 +26,8 @@ export const useLayerPositivePrompt = (layerId: string) => { export const useLayerNegativePrompt = (layerId: string) => { const selectLayer = useMemo( () => - createSelector(selectRegionalPromptsSlice, (regionalPrompts) => { - const layer = regionalPrompts.present.layers.find((l) => l.id === layerId); + createSelector(selectControlLayersSlice, (controlLayers) => { + const layer = controlLayers.present.layers.find((l) => l.id === layerId); assert(isMaskedGuidanceLayer(layer), `Layer ${layerId} not found or not an RP layer`); assert(layer.negativePrompt !== null, `Layer ${layerId} does not have a negative prompt`); return layer.negativePrompt; @@ -41,8 +41,8 @@ export const useLayerNegativePrompt = (layerId: string) => { export const useLayerIsVisible = (layerId: string) => { const selectLayer = useMemo( () => - createSelector(selectRegionalPromptsSlice, (regionalPrompts) => { - const layer = regionalPrompts.present.layers.find((l) => l.id === layerId); + createSelector(selectControlLayersSlice, (controlLayers) => { + const layer = controlLayers.present.layers.find((l) => l.id === layerId); assert(layer, `Layer ${layerId} not found`); return layer.isEnabled; }), @@ -55,8 +55,8 @@ export const useLayerIsVisible = (layerId: string) => { export const useLayerType = (layerId: string) => { const selectLayer = useMemo( () => - createSelector(selectRegionalPromptsSlice, (regionalPrompts) => { - const layer = regionalPrompts.present.layers.find((l) => l.id === layerId); + createSelector(selectControlLayersSlice, (controlLayers) => { + const layer = controlLayers.present.layers.find((l) => l.id === layerId); assert(layer, `Layer ${layerId} not found`); return layer.type; }), @@ -69,8 +69,8 @@ export const useLayerType = (layerId: string) => { export const useLayerOpacity = (layerId: string) => { const selectLayer = useMemo( () => - createSelector(selectRegionalPromptsSlice, (regionalPrompts) => { - const layer = regionalPrompts.present.layers.filter(isControlAdapterLayer).find((l) => l.id === layerId); + createSelector(selectControlLayersSlice, (controlLayers) => { + const layer = controlLayers.present.layers.filter(isControlAdapterLayer).find((l) => l.id === layerId); assert(layer, `Layer ${layerId} not found`); return Math.round(layer.opacity * 100); }), diff --git a/invokeai/frontend/web/src/features/controlLayers/hooks/mouseEventHooks.ts b/invokeai/frontend/web/src/features/controlLayers/hooks/mouseEventHooks.ts index ca44480a2b..bab7ef263f 100644 --- a/invokeai/frontend/web/src/features/controlLayers/hooks/mouseEventHooks.ts +++ b/invokeai/frontend/web/src/features/controlLayers/hooks/mouseEventHooks.ts @@ -12,7 +12,7 @@ import { maskLayerLineAdded, maskLayerPointsAdded, maskLayerRectAdded, -} from 'features/controlLayers/store/regionalPromptsSlice'; +} from 'features/controlLayers/store/controlLayersSlice'; import type Konva from 'konva'; import type { KonvaEventObject } from 'konva/lib/Node'; import type { Vector2d } from 'konva/lib/types'; @@ -48,11 +48,11 @@ const BRUSH_SPACING = 20; export const useMouseEvents = () => { const dispatch = useAppDispatch(); - const selectedLayerId = useAppSelector((s) => s.regionalPrompts.present.selectedLayerId); + const selectedLayerId = useAppSelector((s) => s.controlLayers.present.selectedLayerId); const tool = useStore($tool); const lastCursorPosRef = useRef<[number, number] | null>(null); 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( (e: KonvaEventObject) => { diff --git a/invokeai/frontend/web/src/features/controlLayers/hooks/useRegionalControlTitle.ts b/invokeai/frontend/web/src/features/controlLayers/hooks/useRegionalControlTitle.ts index c5e74868d7..f755b3e6f2 100644 --- a/invokeai/frontend/web/src/features/controlLayers/hooks/useRegionalControlTitle.ts +++ b/invokeai/frontend/web/src/features/controlLayers/hooks/useRegionalControlTitle.ts @@ -1,14 +1,14 @@ import { createSelector } from '@reduxjs/toolkit'; 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 { useTranslation } from 'react-i18next'; -const selectValidLayerCount = createSelector(selectRegionalPromptsSlice, (regionalPrompts) => { - if (!regionalPrompts.present.isEnabled) { +const selectValidLayerCount = createSelector(selectControlLayersSlice, (controlLayers) => { + if (!controlLayers.present.isEnabled) { return 0; } - const validLayers = regionalPrompts.present.layers + const validLayers = controlLayers.present.layers .filter(isMaskedGuidanceLayer) .filter((l) => l.isEnabled) .filter((l) => { @@ -25,7 +25,7 @@ export const useRegionalControlTitle = () => { const validLayerCount = useAppSelector(selectValidLayerCount); const title = useMemo(() => { const suffix = validLayerCount > 0 ? ` (${validLayerCount})` : ''; - return `${t('regionalPrompts.regionalControl')}${suffix}`; + return `${t('controlLayers.regionalControl')}${suffix}`; }, [t, validLayerCount]); return title; }; diff --git a/invokeai/frontend/web/src/features/controlLayers/store/regionalPromptsSlice.ts b/invokeai/frontend/web/src/features/controlLayers/store/controlLayersSlice.ts similarity index 90% rename from invokeai/frontend/web/src/features/controlLayers/store/regionalPromptsSlice.ts rename to invokeai/frontend/web/src/features/controlLayers/store/controlLayersSlice.ts index 0d94b8624c..f5e0fe616a 100644 --- a/invokeai/frontend/web/src/features/controlLayers/store/regionalPromptsSlice.ts +++ b/invokeai/frontend/web/src/features/controlLayers/store/controlLayersSlice.ts @@ -25,17 +25,17 @@ import { v4 as uuidv4 } from 'uuid'; import type { ControlAdapterLayer, + ControlLayersState, DrawingTool, IPAdapterLayer, Layer, - MaskedGuidanceLayer, - RegionalPromptsState, + RegionalGuidanceLayer, Tool, VectorMaskLine, VectorMaskRect, } from './types'; -export const initialRegionalPromptsState: RegionalPromptsState = { +export const initialControlLayersState: ControlLayersState = { _version: 1, selectedLayerId: null, brushSize: 100, @@ -56,15 +56,15 @@ export const initialRegionalPromptsState: RegionalPromptsState = { }; const isLine = (obj: VectorMaskLine | VectorMaskRect): obj is VectorMaskLine => obj.type === 'vector_mask_line'; -export const isMaskedGuidanceLayer = (layer?: Layer): layer is MaskedGuidanceLayer => - layer?.type === 'masked_guidance_layer'; +export const isMaskedGuidanceLayer = (layer?: Layer): layer is RegionalGuidanceLayer => + layer?.type === 'regional_guidance_layer'; export const isControlAdapterLayer = (layer?: Layer): layer is ControlAdapterLayer => layer?.type === 'control_adapter_layer'; export const isIPAdapterLayer = (layer?: Layer): layer is IPAdapterLayer => layer?.type === 'ip_adapter_layer'; -export const isRenderableLayer = (layer?: Layer): layer is MaskedGuidanceLayer | ControlAdapterLayer => - layer?.type === 'masked_guidance_layer' || layer?.type === 'control_adapter_layer'; +export const isRenderableLayer = (layer?: Layer): layer is RegionalGuidanceLayer | ControlAdapterLayer => + layer?.type === 'regional_guidance_layer' || layer?.type === 'control_adapter_layer'; const resetLayer = (layer: Layer) => { - if (layer.type === 'masked_guidance_layer') { + if (layer.type === 'regional_guidance_layer') { layer.maskObjects = []; layer.bbox = null; layer.isEnabled = true; @@ -77,22 +77,22 @@ const resetLayer = (layer: Layer) => { // TODO } }; -const getVectorMaskPreviewColor = (state: RegionalPromptsState): RgbColor => { +const getVectorMaskPreviewColor = (state: ControlLayersState): RgbColor => { const vmLayers = state.layers.filter(isMaskedGuidanceLayer); const lastColor = vmLayers[vmLayers.length - 1]?.previewColor; return LayerColors.next(lastColor); }; -export const regionalPromptsSlice = createSlice({ - name: 'regionalPrompts', - initialState: initialRegionalPromptsState, +export const controlLayersSlice = createSlice({ + name: 'controlLayers', + initialState: initialControlLayersState, reducers: { //#region All Layers maskedGuidanceLayerAdded: (state, action: PayloadAction<{ layerId: string }>) => { const { layerId } = action.payload; - const layer: MaskedGuidanceLayer = { + const layer: RegionalGuidanceLayer = { id: getMaskedGuidanceLayerId(layerId), - type: 'masked_guidance_layer', + type: 'regional_guidance_layer', isEnabled: true, bbox: null, bboxNeedsUpdate: false, @@ -181,7 +181,7 @@ export const regionalPromptsSlice = createSlice({ if (isRenderableLayer(layer)) { layer.bbox = bbox; 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 layer.maskObjects = []; layer.needsPixelBbox = false; @@ -247,35 +247,35 @@ export const regionalPromptsSlice = createSlice({ maskLayerPositivePromptChanged: (state, action: PayloadAction<{ layerId: string; prompt: string | null }>) => { const { layerId, prompt } = action.payload; const layer = state.layers.find((l) => l.id === layerId); - if (layer?.type === 'masked_guidance_layer') { + if (layer?.type === 'regional_guidance_layer') { layer.positivePrompt = prompt; } }, maskLayerNegativePromptChanged: (state, action: PayloadAction<{ layerId: string; prompt: string | null }>) => { const { layerId, prompt } = action.payload; const layer = state.layers.find((l) => l.id === layerId); - if (layer?.type === 'masked_guidance_layer') { + if (layer?.type === 'regional_guidance_layer') { layer.negativePrompt = prompt; } }, maskLayerIPAdapterAdded: (state, action: PayloadAction<{ layerId: string; ipAdapterId: string }>) => { const { layerId, ipAdapterId } = action.payload; 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); } }, maskLayerIPAdapterDeleted: (state, action: PayloadAction<{ layerId: string; ipAdapterId: string }>) => { const { layerId, ipAdapterId } = action.payload; 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); } }, maskLayerPreviewColorChanged: (state, action: PayloadAction<{ layerId: string; color: RgbColor }>) => { const { layerId, color } = action.payload; const layer = state.layers.find((l) => l.id === layerId); - if (layer?.type === 'masked_guidance_layer') { + if (layer?.type === 'regional_guidance_layer') { layer.previewColor = color; } }, @@ -290,7 +290,7 @@ export const regionalPromptsSlice = createSlice({ ) => { const { layerId, points, tool } = action.payload; 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); layer.maskObjects.push({ type: 'vector_mask_line', @@ -315,7 +315,7 @@ export const regionalPromptsSlice = createSlice({ maskLayerPointsAdded: (state, action: PayloadAction<{ layerId: string; point: [number, number] }>) => { const { layerId, point } = action.payload; 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); if (!lastLine) { return; @@ -334,7 +334,7 @@ export const regionalPromptsSlice = createSlice({ return; } 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); layer.maskObjects.push({ type: 'vector_mask_rect', @@ -355,7 +355,7 @@ export const regionalPromptsSlice = createSlice({ ) => { const { layerId, autoNegative } = action.payload; const layer = state.layers.find((l) => l.id === layerId); - if (layer?.type === 'masked_guidance_layer') { + if (layer?.type === 'regional_guidance_layer') { layer.autoNegative = autoNegative; } }, @@ -546,26 +546,26 @@ export const { globalMaskLayerOpacityChanged, undo, redo, -} = regionalPromptsSlice.actions; +} = controlLayersSlice.actions; -export const selectAllControlAdapterIds = (regionalPrompts: RegionalPromptsState) => - regionalPrompts.layers.flatMap((l) => { +export const selectAllControlAdapterIds = (controlLayers: ControlLayersState) => + controlLayers.layers.flatMap((l) => { if (l.type === 'control_adapter_layer') { return [l.controlNetId]; } if (l.type === 'ip_adapter_layer') { return [l.ipAdapterId]; } - if (l.type === 'masked_guidance_layer') { + if (l.type === 'regional_guidance_layer') { return l.ipAdapterIds; } return []; }); -export const selectRegionalPromptsSlice = (state: RootState) => state.regionalPrompts; +export const selectControlLayersSlice = (state: RootState) => state.controlLayers; /* eslint-disable-next-line @typescript-eslint/no-explicit-any */ -const migrateRegionalPromptsState = (state: any): any => { +const migrateControlLayersState = (state: any): any => { return state; }; @@ -588,14 +588,14 @@ export const BACKGROUND_RECT_ID = 'background_layer.rect'; // Names (aka classes) for Konva layers and objects export const CONTROLNET_LAYER_NAME = 'control_adapter_layer'; export const CONTROLNET_LAYER_IMAGE_NAME = 'control_adapter_layer.image'; -export const MASKED_GUIDANCE_LAYER_NAME = 'masked_guidance_layer'; -export const MASKED_GUIDANCE_LAYER_LINE_NAME = 'masked_guidance_layer.line'; -export const MASKED_GUIDANCE_LAYER_OBJECT_GROUP_NAME = 'masked_guidance_layer.object_group'; -export const MASKED_GUIDANCE_LAYER_RECT_NAME = 'masked_guidance_layer.rect'; +export const regional_guidance_layer_NAME = 'regional_guidance_layer'; +export const regional_guidance_layer_LINE_NAME = 'regional_guidance_layer.line'; +export const regional_guidance_layer_OBJECT_GROUP_NAME = 'regional_guidance_layer.object_group'; +export const regional_guidance_layer_RECT_NAME = 'regional_guidance_layer.rect'; export const LAYER_BBOX_NAME = 'layer.bbox'; // 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 getMaskedGuidnaceLayerRectId = (layerId: string, lineId: string) => `${layerId}.rect_${lineId}`; 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}`; const getIPAdapterLayerId = (layerId: string) => `ip_adapter_layer_${layerId}`; -export const regionalPromptsPersistConfig: PersistConfig = { - name: regionalPromptsSlice.name, - initialState: initialRegionalPromptsState, - migrate: migrateRegionalPromptsState, +export const controlLayersPersistConfig: PersistConfig = { + name: controlLayersSlice.name, + initialState: initialControlLayersState, + migrate: migrateControlLayersState, persistDenylist: [], }; @@ -626,10 +626,10 @@ const undoableGroupByMatcher = isAnyOf( const LINE_1 = 'LINE_1'; const LINE_2 = 'LINE_2'; -export const regionalPromptsUndoableConfig: UndoableOptions = { +export const controlLayersUndoableConfig: UndoableOptions = { limit: 64, - undoType: regionalPromptsSlice.actions.undo.type, - redoType: regionalPromptsSlice.actions.redo.type, + undoType: controlLayersSlice.actions.undo.type, + redoType: controlLayersSlice.actions.redo.type, groupBy: (action, state, history) => { // 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 @@ -649,7 +649,7 @@ export const regionalPromptsUndoableConfig: UndoableOptions { // Ignore all actions from other slices - if (!action.type.startsWith(regionalPromptsSlice.name)) { + if (!action.type.startsWith(controlLayersSlice.name)) { return false; } // This action is triggered on state changes, including when we undo. If we do not ignore this action, when we diff --git a/invokeai/frontend/web/src/features/controlLayers/store/types.ts b/invokeai/frontend/web/src/features/controlLayers/store/types.ts index ac66c013d6..5f3d11c765 100644 --- a/invokeai/frontend/web/src/features/controlLayers/store/types.ts +++ b/invokeai/frontend/web/src/features/controlLayers/store/types.ts @@ -57,8 +57,8 @@ export type IPAdapterLayer = LayerBase & { ipAdapterId: string; }; -export type MaskedGuidanceLayer = RenderableLayerBase & { - type: 'masked_guidance_layer'; +export type RegionalGuidanceLayer = RenderableLayerBase & { + type: 'regional_guidance_layer'; maskObjects: (VectorMaskLine | VectorMaskRect)[]; positivePrompt: ParameterPositivePrompt | null; 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 }; -export type Layer = MaskedGuidanceLayer | ControlAdapterLayer | IPAdapterLayer; +export type Layer = RegionalGuidanceLayer | ControlAdapterLayer | IPAdapterLayer; -export type RegionalPromptsState = { +export type ControlLayersState = { _version: 1; selectedLayerId: string | null; layers: Layer[]; diff --git a/invokeai/frontend/web/src/features/controlLayers/util/bbox.ts b/invokeai/frontend/web/src/features/controlLayers/util/bbox.ts index a75650fd0a..3c2915e0ab 100644 --- a/invokeai/frontend/web/src/features/controlLayers/util/bbox.ts +++ b/invokeai/frontend/web/src/features/controlLayers/util/bbox.ts @@ -1,6 +1,6 @@ import openBase64ImageInTab from 'common/util/openBase64ImageInTab'; 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 type { Layer as KonvaLayerType } from 'konva/lib/Layer'; import type { IRect } from 'konva/lib/types'; @@ -81,7 +81,7 @@ export const getLayerBboxPixels = (layer: KonvaLayerType, preview: boolean = fal offscreenStage.add(layerClone); 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 child.opacity(1); child.cache(); diff --git a/invokeai/frontend/web/src/features/controlLayers/util/getLayerBlobs.ts b/invokeai/frontend/web/src/features/controlLayers/util/getLayerBlobs.ts index 783fdee513..c7616e4300 100644 --- a/invokeai/frontend/web/src/features/controlLayers/util/getLayerBlobs.ts +++ b/invokeai/frontend/web/src/features/controlLayers/util/getLayerBlobs.ts @@ -1,7 +1,7 @@ import { getStore } from 'app/store/nanostores/store'; import openBase64ImageInTab from 'common/util/openBase64ImageInTab'; 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 Konva from 'konva'; import { assert } from 'tsafe'; @@ -17,14 +17,14 @@ export const getRegionalPromptLayerBlobs = async ( preview: boolean = false ): Promise> => { const state = getStore().getState(); - const { layers } = state.regionalPrompts.present; - const { width, height } = state.regionalPrompts.present.size; + const { layers } = state.controlLayers.present; + const { width, height } = state.controlLayers.present.size; const reduxLayers = layers.filter(isMaskedGuidanceLayer); const container = document.createElement('div'); const stage = new Konva.Stage({ container, width, height }); renderers.renderLayers(stage, reduxLayers, 1, 'brush'); - const konvaLayers = stage.find(`.${MASKED_GUIDANCE_LAYER_NAME}`); + const konvaLayers = stage.find(`.${regional_guidance_layer_NAME}`); const blobs: Record = {}; // First remove all layers diff --git a/invokeai/frontend/web/src/features/controlLayers/util/renderers.ts b/invokeai/frontend/web/src/features/controlLayers/util/renderers.ts index c64baa211c..d61e6d1b96 100644 --- a/invokeai/frontend/web/src/features/controlLayers/util/renderers.ts +++ b/invokeai/frontend/web/src/features/controlLayers/util/renderers.ts @@ -14,21 +14,21 @@ import { isMaskedGuidanceLayer, isRenderableLayer, LAYER_BBOX_NAME, - MASKED_GUIDANCE_LAYER_LINE_NAME, - MASKED_GUIDANCE_LAYER_NAME, - MASKED_GUIDANCE_LAYER_OBJECT_GROUP_NAME, - MASKED_GUIDANCE_LAYER_RECT_NAME, + regional_guidance_layer_LINE_NAME, + regional_guidance_layer_NAME, + regional_guidance_layer_OBJECT_GROUP_NAME, + regional_guidance_layer_RECT_NAME, TOOL_PREVIEW_BRUSH_BORDER_INNER_ID, TOOL_PREVIEW_BRUSH_BORDER_OUTER_ID, TOOL_PREVIEW_BRUSH_FILL_ID, TOOL_PREVIEW_BRUSH_GROUP_ID, TOOL_PREVIEW_LAYER_ID, TOOL_PREVIEW_RECT_ID, -} from 'features/controlLayers/store/regionalPromptsSlice'; +} from 'features/controlLayers/store/controlLayersSlice'; import type { ControlAdapterLayer, Layer, - MaskedGuidanceLayer, + RegionalGuidanceLayer, Tool, VectorMaskLine, VectorMaskRect, @@ -52,10 +52,10 @@ const STAGE_BG_DATAURL = const mapId = (object: { id: string }) => object.id; 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) => { - 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, 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 if (layerCount === 0) { // We have no layers, so we should not render any tool 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 stage.container().style.cursor = 'not-allowed'; } else if (tool === 'move') { @@ -226,13 +226,13 @@ const renderToolPreview = ( */ const createMaskedGuidanceLayer = ( stage: Konva.Stage, - reduxLayer: MaskedGuidanceLayer, + reduxLayer: RegionalGuidanceLayer, onLayerPosChanged?: (layerId: string, x: number, y: number) => void ) => { // This layer hasn't been added to the konva state yet const konvaLayer = new Konva.Layer({ id: reduxLayer.id, - name: MASKED_GUIDANCE_LAYER_NAME, + name: regional_guidance_layer_NAME, draggable: true, dragDistance: 0, }); @@ -265,7 +265,7 @@ const createMaskedGuidanceLayer = ( // The object group holds all of the layer's objects (e.g. lines and rects) const konvaObjectGroup = new Konva.Group({ id: getMaskedGuidanceLayerObjectGroupId(reduxLayer.id, uuidv4()), - name: MASKED_GUIDANCE_LAYER_OBJECT_GROUP_NAME, + name: regional_guidance_layer_OBJECT_GROUP_NAME, listening: false, }); konvaLayer.add(konvaObjectGroup); @@ -284,7 +284,7 @@ const createVectorMaskLine = (reduxObject: VectorMaskLine, konvaGroup: Konva.Gro const vectorMaskLine = new Konva.Line({ id: reduxObject.id, key: reduxObject.id, - name: MASKED_GUIDANCE_LAYER_LINE_NAME, + name: regional_guidance_layer_LINE_NAME, strokeWidth: reduxObject.strokeWidth, tension: 0, lineCap: 'round', @@ -306,7 +306,7 @@ const createVectorMaskRect = (reduxObject: VectorMaskRect, konvaGroup: Konva.Gro const vectorMaskRect = new Konva.Rect({ id: reduxObject.id, key: reduxObject.id, - name: MASKED_GUIDANCE_LAYER_RECT_NAME, + name: regional_guidance_layer_RECT_NAME, x: reduxObject.x, y: reduxObject.y, width: reduxObject.width, @@ -327,7 +327,7 @@ const createVectorMaskRect = (reduxObject: VectorMaskRect, konvaGroup: Konva.Gro */ const renderMaskedGuidanceLayer = ( stage: Konva.Stage, - reduxLayer: MaskedGuidanceLayer, + reduxLayer: RegionalGuidanceLayer, globalMaskLayerOpacity: number, tool: Tool, 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. const rgbColor = rgbColorToString(reduxLayer.previewColor); - const konvaObjectGroup = konvaLayer.findOne(`.${MASKED_GUIDANCE_LAYER_OBJECT_GROUP_NAME}`); + const konvaObjectGroup = konvaLayer.findOne(`.${regional_guidance_layer_OBJECT_GROUP_NAME}`); 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. @@ -592,7 +592,7 @@ const renderBbox = ( } for (const reduxLayer of reduxLayers) { - if (reduxLayer.type === 'masked_guidance_layer') { + if (reduxLayer.type === 'regional_guidance_layer') { const konvaLayer = stage.findOne(`#${reduxLayer.id}`); assert(konvaLayer, `Layer ${reduxLayer.id} not found in stage`); diff --git a/invokeai/frontend/web/src/features/metadata/util/recallers.ts b/invokeai/frontend/web/src/features/metadata/util/recallers.ts index fbf72ef98f..f07b2ab8b6 100644 --- a/invokeai/frontend/web/src/features/metadata/util/recallers.ts +++ b/invokeai/frontend/web/src/features/metadata/util/recallers.ts @@ -12,7 +12,7 @@ import { positivePrompt2Changed, positivePromptChanged, widthChanged, -} from 'features/controlLayers/store/regionalPromptsSlice'; +} from 'features/controlLayers/store/controlLayersSlice'; import { setHrfEnabled, setHrfMethod, setHrfStrength } from 'features/hrf/store/hrfSlice'; import type { LoRA } from 'features/lora/store/loraSlice'; import { loraRecalled, lorasReset } from 'features/lora/store/loraSlice'; diff --git a/invokeai/frontend/web/src/features/nodes/util/graph/addRegionalPromptsToGraph.ts b/invokeai/frontend/web/src/features/nodes/util/graph/addControlLayersToGraph.ts similarity index 98% rename from invokeai/frontend/web/src/features/nodes/util/graph/addRegionalPromptsToGraph.ts rename to invokeai/frontend/web/src/features/nodes/util/graph/addControlLayersToGraph.ts index 5c581ad24e..b16fe806b6 100644 --- a/invokeai/frontend/web/src/features/nodes/util/graph/addRegionalPromptsToGraph.ts +++ b/invokeai/frontend/web/src/features/nodes/util/graph/addControlLayersToGraph.ts @@ -1,7 +1,7 @@ import { getStore } from 'app/store/nanostores/store'; import type { RootState } from 'app/store/store'; 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 { 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 { assert } from 'tsafe'; -export const addRegionalPromptsToGraph = async (state: RootState, graph: NonNullableGraph, denoiseNodeId: string) => { - if (!state.regionalPrompts.present.isEnabled) { +export const addControlLayersToGraph = async (state: RootState, graph: NonNullableGraph, denoiseNodeId: string) => { + if (!state.controlLayers.present.isEnabled) { return; } const { dispatch } = getStore(); 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 // TODO: Image masks .filter(isMaskedGuidanceLayer) diff --git a/invokeai/frontend/web/src/features/nodes/util/graph/addControlNetToLinearGraph.ts b/invokeai/frontend/web/src/features/nodes/util/graph/addControlNetToLinearGraph.ts index 90898a0852..fb912d0be2 100644 --- a/invokeai/frontend/web/src/features/nodes/util/graph/addControlNetToLinearGraph.ts +++ b/invokeai/frontend/web/src/features/nodes/util/graph/addControlNetToLinearGraph.ts @@ -1,7 +1,7 @@ import type { RootState } from 'app/store/store'; import { selectValidControlNets } from 'features/controlAdapters/store/controlAdaptersSlice'; 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 { activeTabNameSelector } from 'features/ui/store/uiSelectors'; import { differenceWith, intersectionWith } from 'lodash-es'; @@ -36,7 +36,7 @@ const getControlNets = (state: RootState) => { if (activeTabName === 'txt2img') { // Add only the cnets that are used in control layers // Collect all ControlNet ids for enabled ControlNet layers - const layerControlNetIds = state.regionalPrompts.present.layers + const layerControlNetIds = state.controlLayers.present.layers .filter(isControlAdapterLayer) .filter((l) => l.isEnabled) .map((l) => l.controlNetId); @@ -44,7 +44,7 @@ const getControlNets = (state: RootState) => { } else { // Else, we want to exclude the cnets that are used in control layers // Collect all ControlNet ids for all ControlNet layers - const layerControlNetIds = state.regionalPrompts.present.layers + const layerControlNetIds = state.controlLayers.present.layers .filter(isControlAdapterLayer) .map((l) => l.controlNetId); return differenceWith(validControlNets, layerControlNetIds, (a, b) => a.id === b); diff --git a/invokeai/frontend/web/src/features/nodes/util/graph/addHrfToGraph.ts b/invokeai/frontend/web/src/features/nodes/util/graph/addHrfToGraph.ts index d7c512728e..5abf07740a 100644 --- a/invokeai/frontend/web/src/features/nodes/util/graph/addHrfToGraph.ts +++ b/invokeai/frontend/web/src/features/nodes/util/graph/addHrfToGraph.ts @@ -110,7 +110,7 @@ export const addHrfToGraph = (state: RootState, graph: NonNullableGraph): void = const { vae, seamlessXAxis, seamlessYAxis } = state.generation; const { hrfStrength, hrfEnabled, hrfMethod } = state.hrf; - const { width, height } = state.regionalPrompts.present.size; + const { width, height } = state.controlLayers.present.size; const isAutoVae = !vae; const isSeamlessEnabled = seamlessXAxis || seamlessYAxis; const optimalDimension = selectOptimalDimension(state); diff --git a/invokeai/frontend/web/src/features/nodes/util/graph/addIPAdapterToLinearGraph.ts b/invokeai/frontend/web/src/features/nodes/util/graph/addIPAdapterToLinearGraph.ts index 6373c87eb5..1fd0ea76d7 100644 --- a/invokeai/frontend/web/src/features/nodes/util/graph/addIPAdapterToLinearGraph.ts +++ b/invokeai/frontend/web/src/features/nodes/util/graph/addIPAdapterToLinearGraph.ts @@ -1,7 +1,7 @@ import type { RootState } from 'app/store/store'; import { selectValidIPAdapters } from 'features/controlAdapters/store/controlAdaptersSlice'; 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 { activeTabNameSelector } from 'features/ui/store/uiSelectors'; 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 - const maskedIPAdapterIds = state.regionalPrompts.present.layers + const maskedIPAdapterIds = state.controlLayers.present.layers .filter(isMaskedGuidanceLayer) .map((l) => l.ipAdapterIds) .flat(); @@ -40,7 +40,7 @@ const getIPAdapters = (state: RootState) => { 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 // 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((l) => l.isEnabled) .map((l) => l.ipAdapterId); @@ -48,7 +48,7 @@ const getIPAdapters = (state: RootState) => { } else { // 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 - 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); } }; diff --git a/invokeai/frontend/web/src/features/nodes/util/graph/addT2IAdapterToLinearGraph.ts b/invokeai/frontend/web/src/features/nodes/util/graph/addT2IAdapterToLinearGraph.ts index 9006ed8b83..1632449724 100644 --- a/invokeai/frontend/web/src/features/nodes/util/graph/addT2IAdapterToLinearGraph.ts +++ b/invokeai/frontend/web/src/features/nodes/util/graph/addT2IAdapterToLinearGraph.ts @@ -1,7 +1,7 @@ import type { RootState } from 'app/store/store'; import { selectValidT2IAdapters } from 'features/controlAdapters/store/controlAdaptersSlice'; 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 { activeTabNameSelector } from 'features/ui/store/uiSelectors'; import { differenceWith, intersectionWith } from 'lodash-es'; @@ -36,14 +36,14 @@ const getT2IAdapters = (state: RootState) => { if (activeTabName === 'txt2img') { // Add only the T2Is that are used in control layers // Collect all ids for enabled control adapter layers - const layerControlAdapterIds = state.regionalPrompts.present.layers + const layerControlAdapterIds = state.controlLayers.present.layers .filter(isControlAdapterLayer) .filter((l) => l.isEnabled) .map((l) => l.controlNetId); return intersectionWith(validT2IAdapters, layerControlAdapterIds, (a, b) => a.id === b); } else { // 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) .map((l) => l.controlNetId); return differenceWith(validT2IAdapters, layerControlAdapterIds, (a, b) => a.id === b); diff --git a/invokeai/frontend/web/src/features/nodes/util/graph/buildCanvasImageToImageGraph.ts b/invokeai/frontend/web/src/features/nodes/util/graph/buildCanvasImageToImageGraph.ts index 144db94f3c..f2c9957edc 100644 --- a/invokeai/frontend/web/src/features/nodes/util/graph/buildCanvasImageToImageGraph.ts +++ b/invokeai/frontend/web/src/features/nodes/util/graph/buildCanvasImageToImageGraph.ts @@ -55,7 +55,7 @@ export const buildCanvasImageToImageGraph = async ( seamlessXAxis, seamlessYAxis, } = 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 const { width, height } = state.canvas.boundingBoxDimensions; diff --git a/invokeai/frontend/web/src/features/nodes/util/graph/buildCanvasInpaintGraph.ts b/invokeai/frontend/web/src/features/nodes/util/graph/buildCanvasInpaintGraph.ts index 49cd590780..ab73953008 100644 --- a/invokeai/frontend/web/src/features/nodes/util/graph/buildCanvasInpaintGraph.ts +++ b/invokeai/frontend/web/src/features/nodes/util/graph/buildCanvasInpaintGraph.ts @@ -64,7 +64,7 @@ export const buildCanvasInpaintGraph = async ( canvasCoherenceEdgeSize, maskBlur, } = state.generation; - const { positivePrompt, negativePrompt } = state.regionalPrompts.present; + const { positivePrompt, negativePrompt } = state.controlLayers.present; if (!model) { log.error('No model found in state'); diff --git a/invokeai/frontend/web/src/features/nodes/util/graph/buildCanvasOutpaintGraph.ts b/invokeai/frontend/web/src/features/nodes/util/graph/buildCanvasOutpaintGraph.ts index c94b07d395..6b564f464e 100644 --- a/invokeai/frontend/web/src/features/nodes/util/graph/buildCanvasOutpaintGraph.ts +++ b/invokeai/frontend/web/src/features/nodes/util/graph/buildCanvasOutpaintGraph.ts @@ -76,7 +76,7 @@ export const buildCanvasOutpaintGraph = async ( canvasCoherenceEdgeSize, maskBlur, } = state.generation; - const { positivePrompt, negativePrompt } = state.regionalPrompts.present; + const { positivePrompt, negativePrompt } = state.controlLayers.present; if (!model) { log.error('No model found in state'); diff --git a/invokeai/frontend/web/src/features/nodes/util/graph/buildCanvasSDXLImageToImageGraph.ts b/invokeai/frontend/web/src/features/nodes/util/graph/buildCanvasSDXLImageToImageGraph.ts index 7b29d0a8fb..ee918d1470 100644 --- a/invokeai/frontend/web/src/features/nodes/util/graph/buildCanvasSDXLImageToImageGraph.ts +++ b/invokeai/frontend/web/src/features/nodes/util/graph/buildCanvasSDXLImageToImageGraph.ts @@ -55,7 +55,7 @@ export const buildCanvasSDXLImageToImageGraph = async ( seamlessYAxis, img2imgStrength: strength, } = state.generation; - const { positivePrompt, negativePrompt } = state.regionalPrompts.present; + const { positivePrompt, negativePrompt } = state.controlLayers.present; const { refinerModel, refinerStart } = state.sdxl; diff --git a/invokeai/frontend/web/src/features/nodes/util/graph/buildCanvasSDXLInpaintGraph.ts b/invokeai/frontend/web/src/features/nodes/util/graph/buildCanvasSDXLInpaintGraph.ts index 4b1fd4575a..68b948a44a 100644 --- a/invokeai/frontend/web/src/features/nodes/util/graph/buildCanvasSDXLInpaintGraph.ts +++ b/invokeai/frontend/web/src/features/nodes/util/graph/buildCanvasSDXLInpaintGraph.ts @@ -64,7 +64,7 @@ export const buildCanvasSDXLInpaintGraph = async ( canvasCoherenceEdgeSize, maskBlur, } = state.generation; - const { positivePrompt, negativePrompt } = state.regionalPrompts.present; + const { positivePrompt, negativePrompt } = state.controlLayers.present; const { refinerModel, refinerStart } = state.sdxl; diff --git a/invokeai/frontend/web/src/features/nodes/util/graph/buildCanvasSDXLOutpaintGraph.ts b/invokeai/frontend/web/src/features/nodes/util/graph/buildCanvasSDXLOutpaintGraph.ts index 0561976edb..c5c40b695a 100644 --- a/invokeai/frontend/web/src/features/nodes/util/graph/buildCanvasSDXLOutpaintGraph.ts +++ b/invokeai/frontend/web/src/features/nodes/util/graph/buildCanvasSDXLOutpaintGraph.ts @@ -76,7 +76,7 @@ export const buildCanvasSDXLOutpaintGraph = async ( canvasCoherenceEdgeSize, maskBlur, } = state.generation; - const { positivePrompt, negativePrompt } = state.regionalPrompts.present; + const { positivePrompt, negativePrompt } = state.controlLayers.present; const { refinerModel, refinerStart } = state.sdxl; diff --git a/invokeai/frontend/web/src/features/nodes/util/graph/buildCanvasSDXLTextToImageGraph.ts b/invokeai/frontend/web/src/features/nodes/util/graph/buildCanvasSDXLTextToImageGraph.ts index 3f890e02ef..f6ac645580 100644 --- a/invokeai/frontend/web/src/features/nodes/util/graph/buildCanvasSDXLTextToImageGraph.ts +++ b/invokeai/frontend/web/src/features/nodes/util/graph/buildCanvasSDXLTextToImageGraph.ts @@ -44,7 +44,7 @@ export const buildCanvasSDXLTextToImageGraph = async (state: RootState): Promise seamlessXAxis, seamlessYAxis, } = 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 const { width, height } = state.canvas.boundingBoxDimensions; diff --git a/invokeai/frontend/web/src/features/nodes/util/graph/buildCanvasTextToImageGraph.ts b/invokeai/frontend/web/src/features/nodes/util/graph/buildCanvasTextToImageGraph.ts index 4458720be7..0749308fb8 100644 --- a/invokeai/frontend/web/src/features/nodes/util/graph/buildCanvasTextToImageGraph.ts +++ b/invokeai/frontend/web/src/features/nodes/util/graph/buildCanvasTextToImageGraph.ts @@ -44,7 +44,7 @@ export const buildCanvasTextToImageGraph = async (state: RootState): Promise { const { iterations, model, shouldRandomizeSeed, seed } = state.generation; - const { shouldConcatPrompts } = state.regionalPrompts.present; + const { shouldConcatPrompts } = state.controlLayers.present; const { prompts, seedBehaviour } = state.dynamicPrompts; const data: Batch['data'] = []; diff --git a/invokeai/frontend/web/src/features/nodes/util/graph/buildLinearImageToImageGraph.ts b/invokeai/frontend/web/src/features/nodes/util/graph/buildLinearImageToImageGraph.ts index 1a890ed57a..0ca121b667 100644 --- a/invokeai/frontend/web/src/features/nodes/util/graph/buildLinearImageToImageGraph.ts +++ b/invokeai/frontend/web/src/features/nodes/util/graph/buildLinearImageToImageGraph.ts @@ -53,8 +53,8 @@ export const buildLinearImageToImageGraph = async (state: RootState): Promise { */ export const getSDXLStylePrompts = (state: RootState): { positiveStylePrompt: string; negativeStylePrompt: string } => { const { positivePrompt, negativePrompt, positivePrompt2, negativePrompt2, shouldConcatPrompts } = - state.regionalPrompts.present; + state.controlLayers.present; return { positiveStylePrompt: shouldConcatPrompts ? positivePrompt : positivePrompt2, diff --git a/invokeai/frontend/web/src/features/parameters/components/Core/ParamNegativePrompt.tsx b/invokeai/frontend/web/src/features/parameters/components/Core/ParamNegativePrompt.tsx index ccfb6f7c72..5702cf8bd7 100644 --- a/invokeai/frontend/web/src/features/parameters/components/Core/ParamNegativePrompt.tsx +++ b/invokeai/frontend/web/src/features/parameters/components/Core/ParamNegativePrompt.tsx @@ -1,6 +1,6 @@ import { Box, Textarea } from '@invoke-ai/ui-library'; 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 { AddPromptTriggerButton } from 'features/prompt/AddPromptTriggerButton'; import { PromptPopover } from 'features/prompt/PromptPopover'; @@ -10,7 +10,7 @@ import { useTranslation } from 'react-i18next'; export const ParamNegativePrompt = memo(() => { const dispatch = useAppDispatch(); - const prompt = useAppSelector((s) => s.regionalPrompts.present.negativePrompt); + const prompt = useAppSelector((s) => s.controlLayers.present.negativePrompt); const textareaRef = useRef(null); const { t } = useTranslation(); const _onChange = useCallback( diff --git a/invokeai/frontend/web/src/features/parameters/components/Core/ParamPositivePrompt.tsx b/invokeai/frontend/web/src/features/parameters/components/Core/ParamPositivePrompt.tsx index 4f6e66e50a..0b2890875e 100644 --- a/invokeai/frontend/web/src/features/parameters/components/Core/ParamPositivePrompt.tsx +++ b/invokeai/frontend/web/src/features/parameters/components/Core/ParamPositivePrompt.tsx @@ -1,6 +1,6 @@ import { Box, Textarea } from '@invoke-ai/ui-library'; 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 { PromptOverlayButtonWrapper } from 'features/parameters/components/Prompts/PromptOverlayButtonWrapper'; import { AddPromptTriggerButton } from 'features/prompt/AddPromptTriggerButton'; @@ -14,7 +14,7 @@ import { useTranslation } from 'react-i18next'; export const ParamPositivePrompt = memo(() => { 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 textareaRef = useRef(null); diff --git a/invokeai/frontend/web/src/features/queue/components/QueueButtonTooltip.tsx b/invokeai/frontend/web/src/features/queue/components/QueueButtonTooltip.tsx index 351589a949..f63e96c45f 100644 --- a/invokeai/frontend/web/src/features/queue/components/QueueButtonTooltip.tsx +++ b/invokeai/frontend/web/src/features/queue/components/QueueButtonTooltip.tsx @@ -2,7 +2,7 @@ import { Divider, Flex, ListItem, Text, UnorderedList } from '@invoke-ai/ui-libr import { createSelector } from '@reduxjs/toolkit'; import { useAppSelector } from 'app/store/storeHooks'; 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 { getShouldProcessPrompt } from 'features/dynamicPrompts/util/getShouldProcessPrompt'; import { memo, useMemo } from 'react'; @@ -11,10 +11,10 @@ import { useEnqueueBatchMutation } from 'services/api/endpoints/queue'; import { useBoardName } from 'services/api/hooks/useBoardName'; const selectPromptsCount = createSelector( - selectRegionalPromptsSlice, + selectControlLayersSlice, selectDynamicPromptsSlice, - (regionalPrompts, dynamicPrompts) => - getShouldProcessPrompt(regionalPrompts.present.positivePrompt) ? dynamicPrompts.prompts.length : 1 + (controlLayers, dynamicPrompts) => + getShouldProcessPrompt(controlLayers.present.positivePrompt) ? dynamicPrompts.prompts.length : 1 ); type Props = { diff --git a/invokeai/frontend/web/src/features/sdxl/components/SDXLPrompts/ParamSDXLNegativeStylePrompt.tsx b/invokeai/frontend/web/src/features/sdxl/components/SDXLPrompts/ParamSDXLNegativeStylePrompt.tsx index 6104642b62..bba9e0b32d 100644 --- a/invokeai/frontend/web/src/features/sdxl/components/SDXLPrompts/ParamSDXLNegativeStylePrompt.tsx +++ b/invokeai/frontend/web/src/features/sdxl/components/SDXLPrompts/ParamSDXLNegativeStylePrompt.tsx @@ -1,6 +1,6 @@ import { Box, Textarea } from '@invoke-ai/ui-library'; 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 { AddPromptTriggerButton } from 'features/prompt/AddPromptTriggerButton'; import { PromptPopover } from 'features/prompt/PromptPopover'; @@ -11,7 +11,7 @@ import { useTranslation } from 'react-i18next'; export const ParamSDXLNegativeStylePrompt = memo(() => { const dispatch = useAppDispatch(); - const prompt = useAppSelector((s) => s.regionalPrompts.present.negativePrompt2); + const prompt = useAppSelector((s) => s.controlLayers.present.negativePrompt2); const textareaRef = useRef(null); const { t } = useTranslation(); const handleChange = useCallback( diff --git a/invokeai/frontend/web/src/features/sdxl/components/SDXLPrompts/ParamSDXLPositiveStylePrompt.tsx b/invokeai/frontend/web/src/features/sdxl/components/SDXLPrompts/ParamSDXLPositiveStylePrompt.tsx index d14d42a3ce..3828136c74 100644 --- a/invokeai/frontend/web/src/features/sdxl/components/SDXLPrompts/ParamSDXLPositiveStylePrompt.tsx +++ b/invokeai/frontend/web/src/features/sdxl/components/SDXLPrompts/ParamSDXLPositiveStylePrompt.tsx @@ -1,6 +1,6 @@ import { Box, Textarea } from '@invoke-ai/ui-library'; 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 { AddPromptTriggerButton } from 'features/prompt/AddPromptTriggerButton'; import { PromptPopover } from 'features/prompt/PromptPopover'; @@ -10,7 +10,7 @@ import { useTranslation } from 'react-i18next'; export const ParamSDXLPositiveStylePrompt = memo(() => { const dispatch = useAppDispatch(); - const prompt = useAppSelector((s) => s.regionalPrompts.present.positivePrompt2); + const prompt = useAppSelector((s) => s.controlLayers.present.positivePrompt2); const textareaRef = useRef(null); const { t } = useTranslation(); const handleChange = useCallback( diff --git a/invokeai/frontend/web/src/features/sdxl/components/SDXLPrompts/SDXLConcatButton.tsx b/invokeai/frontend/web/src/features/sdxl/components/SDXLPrompts/SDXLConcatButton.tsx index 436749428b..0af3dfcee4 100644 --- a/invokeai/frontend/web/src/features/sdxl/components/SDXLPrompts/SDXLConcatButton.tsx +++ b/invokeai/frontend/web/src/features/sdxl/components/SDXLPrompts/SDXLConcatButton.tsx @@ -1,12 +1,12 @@ import { IconButton, Tooltip } from '@invoke-ai/ui-library'; 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 { useTranslation } from 'react-i18next'; import { PiLinkSimpleBold, PiLinkSimpleBreakBold } from 'react-icons/pi'; export const SDXLConcatButton = memo(() => { - const shouldConcatPrompts = useAppSelector((s) => s.regionalPrompts.present.shouldConcatPrompts); + const shouldConcatPrompts = useAppSelector((s) => s.controlLayers.present.shouldConcatPrompts); const dispatch = useAppDispatch(); const { t } = useTranslation(); diff --git a/invokeai/frontend/web/src/features/sdxl/components/SDXLPrompts/SDXLPrompts.tsx b/invokeai/frontend/web/src/features/sdxl/components/SDXLPrompts/SDXLPrompts.tsx index edfd6302a4..b585e92a5f 100644 --- a/invokeai/frontend/web/src/features/sdxl/components/SDXLPrompts/SDXLPrompts.tsx +++ b/invokeai/frontend/web/src/features/sdxl/components/SDXLPrompts/SDXLPrompts.tsx @@ -8,7 +8,7 @@ import { ParamSDXLNegativeStylePrompt } from './ParamSDXLNegativeStylePrompt'; import { ParamSDXLPositiveStylePrompt } from './ParamSDXLPositiveStylePrompt'; export const SDXLPrompts = memo(() => { - const shouldConcatPrompts = useAppSelector((s) => s.regionalPrompts.present.shouldConcatPrompts); + const shouldConcatPrompts = useAppSelector((s) => s.controlLayers.present.shouldConcatPrompts); return ( diff --git a/invokeai/frontend/web/src/features/settingsAccordions/components/ControlSettingsAccordion/ControlSettingsAccordion.tsx b/invokeai/frontend/web/src/features/settingsAccordions/components/ControlSettingsAccordion/ControlSettingsAccordion.tsx index 2de6d9de00..eb3f932bbf 100644 --- a/invokeai/frontend/web/src/features/settingsAccordions/components/ControlSettingsAccordion/ControlSettingsAccordion.tsx +++ b/invokeai/frontend/web/src/features/settingsAccordions/components/ControlSettingsAccordion/ControlSettingsAccordion.tsx @@ -13,10 +13,7 @@ import { selectValidIPAdapters, selectValidT2IAdapters, } from 'features/controlAdapters/store/controlAdaptersSlice'; -import { - selectAllControlAdapterIds, - selectRegionalPromptsSlice, -} from 'features/controlLayers/store/regionalPromptsSlice'; +import { selectAllControlAdapterIds, selectControlLayersSlice } from 'features/controlLayers/store/controlLayersSlice'; import { useStandaloneAccordionToggle } from 'features/settingsAccordions/hooks/useStandaloneAccordionToggle'; import { useFeatureStatus } from 'features/system/hooks/useFeatureStatus'; import { Fragment, memo } from 'react'; @@ -24,12 +21,12 @@ import { useTranslation } from 'react-i18next'; import { PiPlusBold } from 'react-icons/pi'; const selector = createMemoizedSelector( - [selectControlAdaptersSlice, selectRegionalPromptsSlice], - (controlAdapters, regionalPrompts) => { + [selectControlAdaptersSlice, selectControlLayersSlice], + (controlAdapters, controlLayers) => { const badges: string[] = []; let isError = false; - const regionalControlAdapterIds = selectAllControlAdapterIds(regionalPrompts.present); + const regionalControlAdapterIds = selectAllControlAdapterIds(controlLayers.present); const enabledNonRegionalIPAdapterCount = selectAllIPAdapters(controlAdapters) .filter((ca) => !regionalControlAdapterIds.includes(ca.id)) diff --git a/invokeai/frontend/web/src/features/settingsAccordions/components/ImageSettingsAccordion/ImageSettingsAccordion.tsx b/invokeai/frontend/web/src/features/settingsAccordions/components/ImageSettingsAccordion/ImageSettingsAccordion.tsx index 6f30d5110a..bb9cfd36ce 100644 --- a/invokeai/frontend/web/src/features/settingsAccordions/components/ImageSettingsAccordion/ImageSettingsAccordion.tsx +++ b/invokeai/frontend/web/src/features/settingsAccordions/components/ImageSettingsAccordion/ImageSettingsAccordion.tsx @@ -3,7 +3,7 @@ import { Expander, Flex, FormControlGroup, StandaloneAccordion } from '@invoke-a import { createMemoizedSelector } from 'app/store/createMemoizedSelector'; import { useAppSelector } from 'app/store/storeHooks'; 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 { selectHrfSlice } from 'features/hrf/store/hrfSlice'; import ParamScaleBeforeProcessing from 'features/parameters/components/Canvas/InfillAndScaling/ParamScaleBeforeProcessing'; @@ -25,8 +25,8 @@ import { ImageSizeCanvas } from './ImageSizeCanvas'; import { ImageSizeLinear } from './ImageSizeLinear'; const selector = createMemoizedSelector( - [selectGenerationSlice, selectCanvasSlice, selectHrfSlice, selectRegionalPromptsSlice, activeTabNameSelector], - (generation, canvas, hrf, regionalPrompts, activeTabName) => { + [selectGenerationSlice, selectCanvasSlice, selectHrfSlice, selectControlLayersSlice, activeTabNameSelector], + (generation, canvas, hrf, controlLayers, activeTabName) => { const { shouldRandomizeSeed, model } = generation; const { hrfEnabled } = hrf; const badges: string[] = []; @@ -43,7 +43,7 @@ const selector = createMemoizedSelector( badges.push('locked'); } } else { - const { aspectRatio, width, height } = regionalPrompts.present.size; + const { aspectRatio, width, height } = controlLayers.present.size; badges.push(`${width}×${height}`); badges.push(aspectRatio.id); if (aspectRatio.isLocked) { diff --git a/invokeai/frontend/web/src/features/settingsAccordions/components/ImageSettingsAccordion/ImageSizeLinear.tsx b/invokeai/frontend/web/src/features/settingsAccordions/components/ImageSettingsAccordion/ImageSizeLinear.tsx index 6ca9988f67..7e436556da 100644 --- a/invokeai/frontend/web/src/features/settingsAccordions/components/ImageSettingsAccordion/ImageSizeLinear.tsx +++ b/invokeai/frontend/web/src/features/settingsAccordions/components/ImageSettingsAccordion/ImageSizeLinear.tsx @@ -1,5 +1,5 @@ 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 { ParamWidth } from 'features/parameters/components/Core/ParamWidth'; import { AspectRatioCanvasPreview } from 'features/parameters/components/ImageSize/AspectRatioCanvasPreview'; @@ -12,9 +12,9 @@ import { memo, useCallback } from 'react'; export const ImageSizeLinear = memo(() => { const dispatch = useAppDispatch(); const tab = useAppSelector(activeTabNameSelector); - const width = useAppSelector((s) => s.regionalPrompts.present.size.width); - const height = useAppSelector((s) => s.regionalPrompts.present.size.height); - const aspectRatioState = useAppSelector((s) => s.regionalPrompts.present.size.aspectRatio); + const width = useAppSelector((s) => s.controlLayers.present.size.width); + const height = useAppSelector((s) => s.controlLayers.present.size.height); + const aspectRatioState = useAppSelector((s) => s.controlLayers.present.size.aspectRatio); const onChangeWidth = useCallback( (width: number) => { diff --git a/invokeai/frontend/web/src/features/ui/components/ParametersPanelTextToImage.tsx b/invokeai/frontend/web/src/features/ui/components/ParametersPanelTextToImage.tsx index 0adf65d0bb..82243867d7 100644 --- a/invokeai/frontend/web/src/features/ui/components/ParametersPanelTextToImage.tsx +++ b/invokeai/frontend/web/src/features/ui/components/ParametersPanelTextToImage.tsx @@ -1,7 +1,7 @@ import { Box, Flex, Tab, TabList, TabPanel, TabPanels, Tabs } from '@invoke-ai/ui-library'; import { useAppSelector } from 'app/store/storeHooks'; 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 { Prompts } from 'features/parameters/components/Prompts/Prompts'; import QueueControls from 'features/queue/components/QueueControls'; @@ -55,7 +55,7 @@ const ParametersPanelTextToImage = () => { - + diff --git a/invokeai/frontend/web/src/features/ui/components/tabs/TextToImageTab.tsx b/invokeai/frontend/web/src/features/ui/components/tabs/TextToImageTab.tsx index fc8694e06d..1ff5dd6462 100644 --- a/invokeai/frontend/web/src/features/ui/components/tabs/TextToImageTab.tsx +++ b/invokeai/frontend/web/src/features/ui/components/tabs/TextToImageTab.tsx @@ -1,5 +1,5 @@ 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 CurrentImageDisplay from 'features/gallery/components/CurrentImage/CurrentImageDisplay'; import { memo } from 'react'; @@ -22,7 +22,7 @@ const TextToImageTab = () => { - + From b3dbfdaa02b6fc81fb0899cfd3a317d0ec8b8ff4 Mon Sep 17 00:00:00 2001 From: psychedelicious <4822129+psychedelicious@users.noreply.github.com> Date: Tue, 30 Apr 2024 13:41:29 +1000 Subject: [PATCH 41/52] tidy(ui): more renaming of components --- invokeai/frontend/web/public/locales/en.json | 4 +- .../regionalControlToControlAdapterBridge.ts | 4 +- .../components/AddLayerButton.tsx | 4 +- .../components/AddPromptButtons.tsx | 4 +- ...rLayerListItem.tsx => CALayerListItem.tsx} | 16 ++++---- .../components/ControlLayersPanelContent.tsx | 12 +++--- ...rLayerListItem.tsx => IPLayerListItem.tsx} | 12 +++--- ...DeleteButton.tsx => LayerDeleteButton.tsx} | 4 +- .../{RPLayerMenu.tsx => LayerMenu.tsx} | 12 +++--- ...ctions.tsx => LayerMenuArrangeActions.tsx} | 4 +- ...anceActions.tsx => LayerMenuRGActions.tsx} | 8 ++-- .../controlLayers/components/LayerTitle.tsx | 2 +- ...tyToggle.tsx => LayerVisibilityToggle.tsx} | 4 +- ...ox.tsx => RGLayerAutoNegativeCheckbox.tsx} | 8 ++-- ...ColorPicker.tsx => RGLayerColorPicker.tsx} | 8 ++-- ...apterList.tsx => RGLayerIPAdapterList.tsx} | 14 +++---- ...eLayerListItem.tsx => RGLayerListItem.tsx} | 40 +++++++++---------- ...vePrompt.tsx => RGLayerNegativePrompt.tsx} | 8 ++-- ...vePrompt.tsx => RGLayerPositivePrompt.tsx} | 8 ++-- ...tton.tsx => RGLayerPromptDeleteButton.tsx} | 4 +- ...Popover.tsx => RGLayerSettingsPopover.tsx} | 8 ++-- .../components/StageComponent.tsx | 4 +- .../controlLayers/hooks/layerStateHooks.ts | 6 +-- .../hooks/useRegionalControlTitle.ts | 4 +- .../controlLayers/store/controlLayersSlice.ts | 18 ++++----- .../controlLayers/util/getLayerBlobs.ts | 4 +- .../features/controlLayers/util/renderers.ts | 17 ++++---- .../util/graph/addControlLayersToGraph.ts | 4 +- .../util/graph/addIPAdapterToLinearGraph.ts | 4 +- 29 files changed, 125 insertions(+), 124 deletions(-) rename invokeai/frontend/web/src/features/controlLayers/components/{ControlAdapterLayerListItem.tsx => CALayerListItem.tsx} (78%) rename invokeai/frontend/web/src/features/controlLayers/components/{IPAdapterLayerListItem.tsx => IPLayerListItem.tsx} (76%) rename invokeai/frontend/web/src/features/controlLayers/components/{RPLayerDeleteButton.tsx => LayerDeleteButton.tsx} (86%) rename invokeai/frontend/web/src/features/controlLayers/components/{RPLayerMenu.tsx => LayerMenu.tsx} (79%) rename invokeai/frontend/web/src/features/controlLayers/components/{RPLayerMenuArrangeActions.tsx => LayerMenuArrangeActions.tsx} (94%) rename invokeai/frontend/web/src/features/controlLayers/components/{RPLayerMenuMaskedGuidanceActions.tsx => LayerMenuRGActions.tsx} (88%) rename invokeai/frontend/web/src/features/controlLayers/components/{RPLayerVisibilityToggle.tsx => LayerVisibilityToggle.tsx} (87%) rename invokeai/frontend/web/src/features/controlLayers/components/{RPLayerAutoNegativeCheckbox.tsx => RGLayerAutoNegativeCheckbox.tsx} (83%) rename invokeai/frontend/web/src/features/controlLayers/components/{RPLayerColorPicker.tsx => RGLayerColorPicker.tsx} (88%) rename invokeai/frontend/web/src/features/controlLayers/components/{RPLayerIPAdapterList.tsx => RGLayerIPAdapterList.tsx} (76%) rename invokeai/frontend/web/src/features/controlLayers/components/{MaskedGuidanceLayerListItem.tsx => RGLayerListItem.tsx} (62%) rename invokeai/frontend/web/src/features/controlLayers/components/{RPLayerNegativePrompt.tsx => RGLayerNegativePrompt.tsx} (86%) rename invokeai/frontend/web/src/features/controlLayers/components/{RPLayerPositivePrompt.tsx => RGLayerPositivePrompt.tsx} (86%) rename invokeai/frontend/web/src/features/controlLayers/components/{RPLayerPromptDeleteButton.tsx => RGLayerPromptDeleteButton.tsx} (89%) rename invokeai/frontend/web/src/features/controlLayers/components/{RPLayerSettingsPopover.tsx => RGLayerSettingsPopover.tsx} (77%) diff --git a/invokeai/frontend/web/public/locales/en.json b/invokeai/frontend/web/public/locales/en.json index 36e915c4fc..814ad4de0d 100644 --- a/invokeai/frontend/web/public/locales/en.json +++ b/invokeai/frontend/web/public/locales/en.json @@ -1533,8 +1533,8 @@ "addPositivePrompt": "Add $t(common.positivePrompt)", "addNegativePrompt": "Add $t(common.negativePrompt)", "addIPAdapter": "Add $t(common.ipAdapter)", - "maskedGuidance": "Masked Guidance", - "maskedGuidanceLayer": "$t(controlLayers.maskedGuidance) $t(unifiedCanvas.layer)", + "regionalGuidance": "Regional Guidance", + "regionalGuidanceLayer": "$t(controlLayers.regionalGuidance) $t(unifiedCanvas.layer)", "controlNetLayer": "$t(common.controlNet) $t(unifiedCanvas.layer)", "ipAdapterLayer": "$t(common.ipAdapter) $t(unifiedCanvas.layer)", "opacity": "Opacity" diff --git a/invokeai/frontend/web/src/app/store/middleware/listenerMiddleware/listeners/regionalControlToControlAdapterBridge.ts b/invokeai/frontend/web/src/app/store/middleware/listenerMiddleware/listeners/regionalControlToControlAdapterBridge.ts index d90dfb97bf..8a9191f90b 100644 --- a/invokeai/frontend/web/src/app/store/middleware/listenerMiddleware/listeners/regionalControlToControlAdapterBridge.ts +++ b/invokeai/frontend/web/src/app/store/middleware/listenerMiddleware/listeners/regionalControlToControlAdapterBridge.ts @@ -8,9 +8,9 @@ import { controlAdapterLayerAdded, ipAdapterLayerAdded, layerDeleted, - maskedGuidanceLayerAdded, maskLayerIPAdapterAdded, maskLayerIPAdapterDeleted, + regionalGuidanceLayerAdded, } from 'features/controlLayers/store/controlLayersSlice'; import type { Layer } from 'features/controlLayers/store/types'; import { modelConfigsAdapterSelectors, modelsApi } from 'services/api/endpoints/models'; @@ -33,7 +33,7 @@ export const addRegionalControlToControlAdapterBridge = (startAppListening: AppS const type = action.payload; const layerId = uuidv4(); if (type === 'regional_guidance_layer') { - dispatch(maskedGuidanceLayerAdded({ layerId })); + dispatch(regionalGuidanceLayerAdded({ layerId })); return; } diff --git a/invokeai/frontend/web/src/features/controlLayers/components/AddLayerButton.tsx b/invokeai/frontend/web/src/features/controlLayers/components/AddLayerButton.tsx index 9c324b1f9e..c1d95be10f 100644 --- a/invokeai/frontend/web/src/features/controlLayers/components/AddLayerButton.tsx +++ b/invokeai/frontend/web/src/features/controlLayers/components/AddLayerButton.tsx @@ -8,7 +8,7 @@ import { PiPlusBold } from 'react-icons/pi'; export const AddLayerButton = memo(() => { const { t } = useTranslation(); const dispatch = useAppDispatch(); - const addMaskedGuidanceLayer = useCallback(() => { + const addRegionalGuidanceLayer = useCallback(() => { dispatch(guidanceLayerAdded('regional_guidance_layer')); }, [dispatch]); const addControlNetLayer = useCallback(() => { @@ -24,7 +24,7 @@ export const AddLayerButton = memo(() => { {t('controlLayers.addLayer')} - {t('controlLayers.maskedGuidanceLayer')} + {t('controlLayers.regionalGuidanceLayer')} {t('controlLayers.controlNetLayer')} {t('controlLayers.ipAdapterLayer')} diff --git a/invokeai/frontend/web/src/features/controlLayers/components/AddPromptButtons.tsx b/invokeai/frontend/web/src/features/controlLayers/components/AddPromptButtons.tsx index 81b119c7c3..51e5f43b24 100644 --- a/invokeai/frontend/web/src/features/controlLayers/components/AddPromptButtons.tsx +++ b/invokeai/frontend/web/src/features/controlLayers/components/AddPromptButtons.tsx @@ -3,7 +3,7 @@ import { createMemoizedSelector } from 'app/store/createMemoizedSelector'; import { guidanceLayerIPAdapterAdded } from 'app/store/middleware/listenerMiddleware/listeners/regionalControlToControlAdapterBridge'; import { useAppDispatch, useAppSelector } from 'app/store/storeHooks'; import { - isMaskedGuidanceLayer, + isRegionalGuidanceLayer, maskLayerNegativePromptChanged, maskLayerPositivePromptChanged, selectControlLayersSlice, @@ -23,7 +23,7 @@ export const AddPromptButtons = ({ layerId }: AddPromptButtonProps) => { () => createMemoizedSelector(selectControlLayersSlice, (controlLayers) => { const layer = controlLayers.present.layers.find((l) => l.id === layerId); - assert(isMaskedGuidanceLayer(layer), `Layer ${layerId} not found or not an RP layer`); + assert(isRegionalGuidanceLayer(layer), `Layer ${layerId} not found or not an RP layer`); return { canAddPositivePrompt: layer.positivePrompt === null, canAddNegativePrompt: layer.negativePrompt === null, diff --git a/invokeai/frontend/web/src/features/controlLayers/components/ControlAdapterLayerListItem.tsx b/invokeai/frontend/web/src/features/controlLayers/components/CALayerListItem.tsx similarity index 78% rename from invokeai/frontend/web/src/features/controlLayers/components/ControlAdapterLayerListItem.tsx rename to invokeai/frontend/web/src/features/controlLayers/components/CALayerListItem.tsx index 5b0859a174..28ef0866fb 100644 --- a/invokeai/frontend/web/src/features/controlLayers/components/ControlAdapterLayerListItem.tsx +++ b/invokeai/frontend/web/src/features/controlLayers/components/CALayerListItem.tsx @@ -3,10 +3,10 @@ import { createMemoizedSelector } from 'app/store/createMemoizedSelector'; import { useAppDispatch, useAppSelector } from 'app/store/storeHooks'; import CALayerOpacity from 'features/controlLayers/components/CALayerOpacity'; import ControlAdapterLayerConfig from 'features/controlLayers/components/controlAdapterOverrides/ControlAdapterLayerConfig'; +import { LayerDeleteButton } from 'features/controlLayers/components/LayerDeleteButton'; +import { LayerMenu } from 'features/controlLayers/components/LayerMenu'; import { LayerTitle } from 'features/controlLayers/components/LayerTitle'; -import { RPLayerDeleteButton } from 'features/controlLayers/components/RPLayerDeleteButton'; -import { RPLayerMenu } from 'features/controlLayers/components/RPLayerMenu'; -import { RPLayerVisibilityToggle } from 'features/controlLayers/components/RPLayerVisibilityToggle'; +import { LayerVisibilityToggle } from 'features/controlLayers/components/LayerVisibilityToggle'; import { isControlAdapterLayer, layerSelected, @@ -19,7 +19,7 @@ type Props = { layerId: string; }; -export const ControlAdapterLayerListItem = memo(({ layerId }: Props) => { +export const CALayerListItem = memo(({ layerId }: Props) => { const dispatch = useAppDispatch(); const selector = useMemo( () => @@ -49,12 +49,12 @@ export const ControlAdapterLayerListItem = memo(({ layerId }: Props) => { > - + - - + + @@ -62,4 +62,4 @@ export const ControlAdapterLayerListItem = memo(({ layerId }: Props) => { ); }); -ControlAdapterLayerListItem.displayName = 'ControlAdapterLayerListItem'; +CALayerListItem.displayName = 'CALayerListItem'; diff --git a/invokeai/frontend/web/src/features/controlLayers/components/ControlLayersPanelContent.tsx b/invokeai/frontend/web/src/features/controlLayers/components/ControlLayersPanelContent.tsx index 4d1545badb..e2865be356 100644 --- a/invokeai/frontend/web/src/features/controlLayers/components/ControlLayersPanelContent.tsx +++ b/invokeai/frontend/web/src/features/controlLayers/components/ControlLayersPanelContent.tsx @@ -4,10 +4,10 @@ import { createMemoizedSelector } from 'app/store/createMemoizedSelector'; import { useAppSelector } from 'app/store/storeHooks'; import ScrollableContent from 'common/components/OverlayScrollbars/ScrollableContent'; import { AddLayerButton } from 'features/controlLayers/components/AddLayerButton'; -import { ControlAdapterLayerListItem } from 'features/controlLayers/components/ControlAdapterLayerListItem'; +import { CALayerListItem } from 'features/controlLayers/components/CALayerListItem'; import { DeleteAllLayersButton } from 'features/controlLayers/components/DeleteAllLayersButton'; -import { IPAdapterLayerListItem } from 'features/controlLayers/components/IPAdapterLayerListItem'; -import { MaskedGuidanceLayerListItem } from 'features/controlLayers/components/MaskedGuidanceLayerListItem'; +import { IPLayerListItem } from 'features/controlLayers/components/IPLayerListItem'; +import { RGLayerListItem } from 'features/controlLayers/components/RGLayerListItem'; import { isRenderableLayer, selectControlLayersSlice } from 'features/controlLayers/store/controlLayersSlice'; import type { Layer } from 'features/controlLayers/store/types'; import { partition } from 'lodash-es'; @@ -46,13 +46,13 @@ type LayerWrapperProps = { const LayerWrapper = memo(({ id, type }: LayerWrapperProps) => { if (type === 'regional_guidance_layer') { - return ; + return ; } if (type === 'control_adapter_layer') { - return ; + return ; } if (type === 'ip_adapter_layer') { - return ; + return ; } }); diff --git a/invokeai/frontend/web/src/features/controlLayers/components/IPAdapterLayerListItem.tsx b/invokeai/frontend/web/src/features/controlLayers/components/IPLayerListItem.tsx similarity index 76% rename from invokeai/frontend/web/src/features/controlLayers/components/IPAdapterLayerListItem.tsx rename to invokeai/frontend/web/src/features/controlLayers/components/IPLayerListItem.tsx index 188a7c1f4f..efb4156137 100644 --- a/invokeai/frontend/web/src/features/controlLayers/components/IPAdapterLayerListItem.tsx +++ b/invokeai/frontend/web/src/features/controlLayers/components/IPLayerListItem.tsx @@ -2,9 +2,9 @@ import { Flex, Spacer } from '@invoke-ai/ui-library'; import { createMemoizedSelector } from 'app/store/createMemoizedSelector'; import { useAppSelector } from 'app/store/storeHooks'; import ControlAdapterLayerConfig from 'features/controlLayers/components/controlAdapterOverrides/ControlAdapterLayerConfig'; +import { LayerDeleteButton } from 'features/controlLayers/components/LayerDeleteButton'; import { LayerTitle } from 'features/controlLayers/components/LayerTitle'; -import { RPLayerDeleteButton } from 'features/controlLayers/components/RPLayerDeleteButton'; -import { RPLayerVisibilityToggle } from 'features/controlLayers/components/RPLayerVisibilityToggle'; +import { LayerVisibilityToggle } from 'features/controlLayers/components/LayerVisibilityToggle'; import { isIPAdapterLayer, selectControlLayersSlice } from 'features/controlLayers/store/controlLayersSlice'; import { memo, useMemo } from 'react'; import { assert } from 'tsafe'; @@ -13,7 +13,7 @@ type Props = { layerId: string; }; -export const IPAdapterLayerListItem = memo(({ layerId }: Props) => { +export const IPLayerListItem = memo(({ layerId }: Props) => { const selector = useMemo( () => createMemoizedSelector(selectControlLayersSlice, (controlLayers) => { @@ -28,10 +28,10 @@ export const IPAdapterLayerListItem = memo(({ layerId }: Props) => { - + - + @@ -39,4 +39,4 @@ export const IPAdapterLayerListItem = memo(({ layerId }: Props) => { ); }); -IPAdapterLayerListItem.displayName = 'IPAdapterLayerListItem'; +IPLayerListItem.displayName = 'IPLayerListItem'; diff --git a/invokeai/frontend/web/src/features/controlLayers/components/RPLayerDeleteButton.tsx b/invokeai/frontend/web/src/features/controlLayers/components/LayerDeleteButton.tsx similarity index 86% rename from invokeai/frontend/web/src/features/controlLayers/components/RPLayerDeleteButton.tsx rename to invokeai/frontend/web/src/features/controlLayers/components/LayerDeleteButton.tsx index 96aba546bc..fe9c9ecc2a 100644 --- a/invokeai/frontend/web/src/features/controlLayers/components/RPLayerDeleteButton.tsx +++ b/invokeai/frontend/web/src/features/controlLayers/components/LayerDeleteButton.tsx @@ -7,7 +7,7 @@ import { PiTrashSimpleBold } from 'react-icons/pi'; type Props = { layerId: string }; -export const RPLayerDeleteButton = memo(({ layerId }: Props) => { +export const LayerDeleteButton = memo(({ layerId }: Props) => { const { t } = useTranslation(); const dispatch = useAppDispatch(); const deleteLayer = useCallback(() => { @@ -25,4 +25,4 @@ export const RPLayerDeleteButton = memo(({ layerId }: Props) => { ); }); -RPLayerDeleteButton.displayName = 'RPLayerDeleteButton'; +LayerDeleteButton.displayName = 'LayerDeleteButton'; diff --git a/invokeai/frontend/web/src/features/controlLayers/components/RPLayerMenu.tsx b/invokeai/frontend/web/src/features/controlLayers/components/LayerMenu.tsx similarity index 79% rename from invokeai/frontend/web/src/features/controlLayers/components/RPLayerMenu.tsx rename to invokeai/frontend/web/src/features/controlLayers/components/LayerMenu.tsx index 95879701c8..cd86fb8cd1 100644 --- a/invokeai/frontend/web/src/features/controlLayers/components/RPLayerMenu.tsx +++ b/invokeai/frontend/web/src/features/controlLayers/components/LayerMenu.tsx @@ -1,7 +1,7 @@ import { IconButton, Menu, MenuButton, MenuDivider, MenuItem, MenuList } from '@invoke-ai/ui-library'; import { useAppDispatch } from 'app/store/storeHooks'; -import { RPLayerMenuArrangeActions } from 'features/controlLayers/components/RPLayerMenuArrangeActions'; -import { RPLayerMenuMaskedGuidanceActions } from 'features/controlLayers/components/RPLayerMenuMaskedGuidanceActions'; +import { LayerMenuArrangeActions } from 'features/controlLayers/components/LayerMenuArrangeActions'; +import { LayerMenuRGActions } from 'features/controlLayers/components/LayerMenuRGActions'; import { useLayerType } from 'features/controlLayers/hooks/layerStateHooks'; import { layerDeleted, layerReset } from 'features/controlLayers/store/controlLayersSlice'; import { memo, useCallback } from 'react'; @@ -10,7 +10,7 @@ import { PiArrowCounterClockwiseBold, PiDotsThreeVerticalBold, PiTrashSimpleBold type Props = { layerId: string }; -export const RPLayerMenu = memo(({ layerId }: Props) => { +export const LayerMenu = memo(({ layerId }: Props) => { const dispatch = useAppDispatch(); const { t } = useTranslation(); const layerType = useLayerType(layerId); @@ -26,13 +26,13 @@ export const RPLayerMenu = memo(({ layerId }: Props) => { {layerType === 'regional_guidance_layer' && ( <> - + )} {(layerType === 'regional_guidance_layer' || layerType === 'control_adapter_layer') && ( <> - + )} @@ -49,4 +49,4 @@ export const RPLayerMenu = memo(({ layerId }: Props) => { ); }); -RPLayerMenu.displayName = 'RPLayerMenu'; +LayerMenu.displayName = 'LayerMenu'; diff --git a/invokeai/frontend/web/src/features/controlLayers/components/RPLayerMenuArrangeActions.tsx b/invokeai/frontend/web/src/features/controlLayers/components/LayerMenuArrangeActions.tsx similarity index 94% rename from invokeai/frontend/web/src/features/controlLayers/components/RPLayerMenuArrangeActions.tsx rename to invokeai/frontend/web/src/features/controlLayers/components/LayerMenuArrangeActions.tsx index fab3a47adb..9c51671a39 100644 --- a/invokeai/frontend/web/src/features/controlLayers/components/RPLayerMenuArrangeActions.tsx +++ b/invokeai/frontend/web/src/features/controlLayers/components/LayerMenuArrangeActions.tsx @@ -16,7 +16,7 @@ import { assert } from 'tsafe'; type Props = { layerId: string }; -export const RPLayerMenuArrangeActions = memo(({ layerId }: Props) => { +export const LayerMenuArrangeActions = memo(({ layerId }: Props) => { const dispatch = useAppDispatch(); const { t } = useTranslation(); const selectValidActions = useMemo( @@ -66,4 +66,4 @@ export const RPLayerMenuArrangeActions = memo(({ layerId }: Props) => { ); }); -RPLayerMenuArrangeActions.displayName = 'RPLayerMenuArrangeActions'; +LayerMenuArrangeActions.displayName = 'LayerMenuArrangeActions'; diff --git a/invokeai/frontend/web/src/features/controlLayers/components/RPLayerMenuMaskedGuidanceActions.tsx b/invokeai/frontend/web/src/features/controlLayers/components/LayerMenuRGActions.tsx similarity index 88% rename from invokeai/frontend/web/src/features/controlLayers/components/RPLayerMenuMaskedGuidanceActions.tsx rename to invokeai/frontend/web/src/features/controlLayers/components/LayerMenuRGActions.tsx index a3da006d0b..cdcb4d99b3 100644 --- a/invokeai/frontend/web/src/features/controlLayers/components/RPLayerMenuMaskedGuidanceActions.tsx +++ b/invokeai/frontend/web/src/features/controlLayers/components/LayerMenuRGActions.tsx @@ -3,7 +3,7 @@ import { createMemoizedSelector } from 'app/store/createMemoizedSelector'; import { guidanceLayerIPAdapterAdded } from 'app/store/middleware/listenerMiddleware/listeners/regionalControlToControlAdapterBridge'; import { useAppDispatch, useAppSelector } from 'app/store/storeHooks'; import { - isMaskedGuidanceLayer, + isRegionalGuidanceLayer, maskLayerNegativePromptChanged, maskLayerPositivePromptChanged, selectControlLayersSlice, @@ -15,14 +15,14 @@ import { assert } from 'tsafe'; type Props = { layerId: string }; -export const RPLayerMenuMaskedGuidanceActions = memo(({ layerId }: Props) => { +export const LayerMenuRGActions = memo(({ layerId }: Props) => { const dispatch = useAppDispatch(); const { t } = useTranslation(); const selectValidActions = useMemo( () => createMemoizedSelector(selectControlLayersSlice, (controlLayers) => { const layer = controlLayers.present.layers.find((l) => l.id === layerId); - assert(isMaskedGuidanceLayer(layer), `Layer ${layerId} not found or not an RP layer`); + assert(isRegionalGuidanceLayer(layer), `Layer ${layerId} not found or not an RP layer`); return { canAddPositivePrompt: layer.positivePrompt === null, canAddNegativePrompt: layer.negativePrompt === null, @@ -55,4 +55,4 @@ export const RPLayerMenuMaskedGuidanceActions = memo(({ layerId }: Props) => { ); }); -RPLayerMenuMaskedGuidanceActions.displayName = 'RPLayerMenuMaskedGuidanceActions'; +LayerMenuRGActions.displayName = 'LayerMenuRGActions'; diff --git a/invokeai/frontend/web/src/features/controlLayers/components/LayerTitle.tsx b/invokeai/frontend/web/src/features/controlLayers/components/LayerTitle.tsx index a192b9e865..523bae0490 100644 --- a/invokeai/frontend/web/src/features/controlLayers/components/LayerTitle.tsx +++ b/invokeai/frontend/web/src/features/controlLayers/components/LayerTitle.tsx @@ -11,7 +11,7 @@ export const LayerTitle = memo(({ type }: Props) => { const { t } = useTranslation(); const title = useMemo(() => { if (type === 'regional_guidance_layer') { - return t('controlLayers.maskedGuidance'); + return t('controlLayers.regionalGuidance'); } else if (type === 'control_adapter_layer') { return t('common.controlNet'); } else if (type === 'ip_adapter_layer') { diff --git a/invokeai/frontend/web/src/features/controlLayers/components/RPLayerVisibilityToggle.tsx b/invokeai/frontend/web/src/features/controlLayers/components/LayerVisibilityToggle.tsx similarity index 87% rename from invokeai/frontend/web/src/features/controlLayers/components/RPLayerVisibilityToggle.tsx rename to invokeai/frontend/web/src/features/controlLayers/components/LayerVisibilityToggle.tsx index 7a39f5d95e..77fa8cf8e8 100644 --- a/invokeai/frontend/web/src/features/controlLayers/components/RPLayerVisibilityToggle.tsx +++ b/invokeai/frontend/web/src/features/controlLayers/components/LayerVisibilityToggle.tsx @@ -10,7 +10,7 @@ type Props = { layerId: string; }; -export const RPLayerVisibilityToggle = memo(({ layerId }: Props) => { +export const LayerVisibilityToggle = memo(({ layerId }: Props) => { const { t } = useTranslation(); const dispatch = useAppDispatch(); const isVisible = useLayerIsVisible(layerId); @@ -31,4 +31,4 @@ export const RPLayerVisibilityToggle = memo(({ layerId }: Props) => { ); }); -RPLayerVisibilityToggle.displayName = 'RPLayerVisibilityToggle'; +LayerVisibilityToggle.displayName = 'LayerVisibilityToggle'; diff --git a/invokeai/frontend/web/src/features/controlLayers/components/RPLayerAutoNegativeCheckbox.tsx b/invokeai/frontend/web/src/features/controlLayers/components/RGLayerAutoNegativeCheckbox.tsx similarity index 83% rename from invokeai/frontend/web/src/features/controlLayers/components/RPLayerAutoNegativeCheckbox.tsx rename to invokeai/frontend/web/src/features/controlLayers/components/RGLayerAutoNegativeCheckbox.tsx index 32a60039f6..6f03d4b28d 100644 --- a/invokeai/frontend/web/src/features/controlLayers/components/RPLayerAutoNegativeCheckbox.tsx +++ b/invokeai/frontend/web/src/features/controlLayers/components/RGLayerAutoNegativeCheckbox.tsx @@ -2,7 +2,7 @@ import { Checkbox, FormControl, FormLabel } from '@invoke-ai/ui-library'; import { createSelector } from '@reduxjs/toolkit'; import { useAppDispatch, useAppSelector } from 'app/store/storeHooks'; import { - isMaskedGuidanceLayer, + isRegionalGuidanceLayer, maskLayerAutoNegativeChanged, selectControlLayersSlice, } from 'features/controlLayers/store/controlLayersSlice'; @@ -20,7 +20,7 @@ const useAutoNegative = (layerId: string) => { () => createSelector(selectControlLayersSlice, (controlLayers) => { const layer = controlLayers.present.layers.find((l) => l.id === layerId); - assert(isMaskedGuidanceLayer(layer), `Layer ${layerId} not found or not an RP layer`); + assert(isRegionalGuidanceLayer(layer), `Layer ${layerId} not found or not an RP layer`); return layer.autoNegative; }), [layerId] @@ -29,7 +29,7 @@ const useAutoNegative = (layerId: string) => { return autoNegative; }; -export const MaskedGuidanceLayerAutoNegativeCheckbox = memo(({ layerId }: Props) => { +export const RGLayerAutoNegativeCheckbox = memo(({ layerId }: Props) => { const { t } = useTranslation(); const dispatch = useAppDispatch(); const autoNegative = useAutoNegative(layerId); @@ -48,4 +48,4 @@ export const MaskedGuidanceLayerAutoNegativeCheckbox = memo(({ layerId }: Props) ); }); -MaskedGuidanceLayerAutoNegativeCheckbox.displayName = 'MaskedGuidanceLayerAutoNegativeCheckbox'; +RGLayerAutoNegativeCheckbox.displayName = 'RGLayerAutoNegativeCheckbox'; diff --git a/invokeai/frontend/web/src/features/controlLayers/components/RPLayerColorPicker.tsx b/invokeai/frontend/web/src/features/controlLayers/components/RGLayerColorPicker.tsx similarity index 88% rename from invokeai/frontend/web/src/features/controlLayers/components/RPLayerColorPicker.tsx rename to invokeai/frontend/web/src/features/controlLayers/components/RGLayerColorPicker.tsx index e1a20d0210..04c6fbf0a2 100644 --- a/invokeai/frontend/web/src/features/controlLayers/components/RPLayerColorPicker.tsx +++ b/invokeai/frontend/web/src/features/controlLayers/components/RGLayerColorPicker.tsx @@ -4,7 +4,7 @@ import { useAppDispatch, useAppSelector } from 'app/store/storeHooks'; import RgbColorPicker from 'common/components/RgbColorPicker'; import { rgbColorToString } from 'features/canvas/util/colorToString'; import { - isMaskedGuidanceLayer, + isRegionalGuidanceLayer, maskLayerPreviewColorChanged, selectControlLayersSlice, } from 'features/controlLayers/store/controlLayersSlice'; @@ -17,13 +17,13 @@ type Props = { layerId: string; }; -export const RPLayerColorPicker = memo(({ layerId }: Props) => { +export const RGLayerColorPicker = memo(({ layerId }: Props) => { const { t } = useTranslation(); const selectColor = useMemo( () => createMemoizedSelector(selectControlLayersSlice, (controlLayers) => { const layer = controlLayers.present.layers.find((l) => l.id === layerId); - assert(isMaskedGuidanceLayer(layer), `Layer ${layerId} not found or not an vector mask layer`); + assert(isRegionalGuidanceLayer(layer), `Layer ${layerId} not found or not an vector mask layer`); return layer.previewColor; }), [layerId] @@ -64,4 +64,4 @@ export const RPLayerColorPicker = memo(({ layerId }: Props) => { ); }); -RPLayerColorPicker.displayName = 'RPLayerColorPicker'; +RGLayerColorPicker.displayName = 'RGLayerColorPicker'; diff --git a/invokeai/frontend/web/src/features/controlLayers/components/RPLayerIPAdapterList.tsx b/invokeai/frontend/web/src/features/controlLayers/components/RGLayerIPAdapterList.tsx similarity index 76% rename from invokeai/frontend/web/src/features/controlLayers/components/RPLayerIPAdapterList.tsx rename to invokeai/frontend/web/src/features/controlLayers/components/RGLayerIPAdapterList.tsx index 4b37bbef7a..07a8e0e334 100644 --- a/invokeai/frontend/web/src/features/controlLayers/components/RPLayerIPAdapterList.tsx +++ b/invokeai/frontend/web/src/features/controlLayers/components/RGLayerIPAdapterList.tsx @@ -3,7 +3,7 @@ import { createMemoizedSelector } from 'app/store/createMemoizedSelector'; import { guidanceLayerIPAdapterDeleted } from 'app/store/middleware/listenerMiddleware/listeners/regionalControlToControlAdapterBridge'; import { useAppDispatch, useAppSelector } from 'app/store/storeHooks'; import ControlAdapterLayerConfig from 'features/controlLayers/components/controlAdapterOverrides/ControlAdapterLayerConfig'; -import { isMaskedGuidanceLayer, selectControlLayersSlice } from 'features/controlLayers/store/controlLayersSlice'; +import { isRegionalGuidanceLayer, selectControlLayersSlice } from 'features/controlLayers/store/controlLayersSlice'; import { memo, useCallback, useMemo } from 'react'; import { PiTrashSimpleBold } from 'react-icons/pi'; import { assert } from 'tsafe'; @@ -12,11 +12,11 @@ type Props = { layerId: string; }; -export const RPLayerIPAdapterList = memo(({ layerId }: Props) => { +export const RGLayerIPAdapterList = memo(({ layerId }: Props) => { const selectIPAdapterIds = useMemo( () => createMemoizedSelector(selectControlLayersSlice, (controlLayers) => { - const layer = controlLayers.present.layers.filter(isMaskedGuidanceLayer).find((l) => l.id === layerId); + const layer = controlLayers.present.layers.filter(isRegionalGuidanceLayer).find((l) => l.id === layerId); assert(layer, `Layer ${layerId} not found`); return layer.ipAdapterIds; }), @@ -37,14 +37,14 @@ export const RPLayerIPAdapterList = memo(({ layerId }: Props) => { )} - + ))} ); }); -RPLayerIPAdapterList.displayName = 'RPLayerIPAdapterList'; +RGLayerIPAdapterList.displayName = 'RGLayerIPAdapterList'; type IPAdapterListItemProps = { layerId: string; @@ -52,7 +52,7 @@ type IPAdapterListItemProps = { ipAdapterNumber: number; }; -const IPAdapterListItem = memo(({ layerId, ipAdapterId, ipAdapterNumber }: IPAdapterListItemProps) => { +const RGLayerIPAdapterListItem = memo(({ layerId, ipAdapterId, ipAdapterNumber }: IPAdapterListItemProps) => { const dispatch = useAppDispatch(); const onDeleteIPAdapter = useCallback(() => { dispatch(guidanceLayerIPAdapterDeleted({ layerId, ipAdapterId })); @@ -77,4 +77,4 @@ const IPAdapterListItem = memo(({ layerId, ipAdapterId, ipAdapterNumber }: IPAda ); }); -IPAdapterListItem.displayName = 'IPAdapterListItem'; +RGLayerIPAdapterListItem.displayName = 'RGLayerIPAdapterListItem'; diff --git a/invokeai/frontend/web/src/features/controlLayers/components/MaskedGuidanceLayerListItem.tsx b/invokeai/frontend/web/src/features/controlLayers/components/RGLayerListItem.tsx similarity index 62% rename from invokeai/frontend/web/src/features/controlLayers/components/MaskedGuidanceLayerListItem.tsx rename to invokeai/frontend/web/src/features/controlLayers/components/RGLayerListItem.tsx index 2a48417f8c..96acfffa50 100644 --- a/invokeai/frontend/web/src/features/controlLayers/components/MaskedGuidanceLayerListItem.tsx +++ b/invokeai/frontend/web/src/features/controlLayers/components/RGLayerListItem.tsx @@ -2,17 +2,17 @@ import { Badge, Flex, Spacer } from '@invoke-ai/ui-library'; import { createMemoizedSelector } from 'app/store/createMemoizedSelector'; import { useAppDispatch, useAppSelector } from 'app/store/storeHooks'; import { rgbColorToString } from 'features/canvas/util/colorToString'; +import { LayerDeleteButton } from 'features/controlLayers/components/LayerDeleteButton'; +import { LayerMenu } from 'features/controlLayers/components/LayerMenu'; import { LayerTitle } from 'features/controlLayers/components/LayerTitle'; -import { RPLayerColorPicker } from 'features/controlLayers/components/RPLayerColorPicker'; -import { RPLayerDeleteButton } from 'features/controlLayers/components/RPLayerDeleteButton'; -import { RPLayerIPAdapterList } from 'features/controlLayers/components/RPLayerIPAdapterList'; -import { RPLayerMenu } from 'features/controlLayers/components/RPLayerMenu'; -import { RPLayerNegativePrompt } from 'features/controlLayers/components/RPLayerNegativePrompt'; -import { RPLayerPositivePrompt } from 'features/controlLayers/components/RPLayerPositivePrompt'; -import RPLayerSettingsPopover from 'features/controlLayers/components/RPLayerSettingsPopover'; -import { RPLayerVisibilityToggle } from 'features/controlLayers/components/RPLayerVisibilityToggle'; +import { LayerVisibilityToggle } from 'features/controlLayers/components/LayerVisibilityToggle'; +import { RGLayerColorPicker } from 'features/controlLayers/components/RGLayerColorPicker'; +import { RGLayerIPAdapterList } from 'features/controlLayers/components/RGLayerIPAdapterList'; +import { RGLayerNegativePrompt } from 'features/controlLayers/components/RGLayerNegativePrompt'; +import { RGLayerPositivePrompt } from 'features/controlLayers/components/RGLayerPositivePrompt'; +import RGLayerSettingsPopover from 'features/controlLayers/components/RGLayerSettingsPopover'; import { - isMaskedGuidanceLayer, + isRegionalGuidanceLayer, layerSelected, selectControlLayersSlice, } from 'features/controlLayers/store/controlLayersSlice'; @@ -26,14 +26,14 @@ type Props = { layerId: string; }; -export const MaskedGuidanceLayerListItem = memo(({ layerId }: Props) => { +export const RGLayerListItem = memo(({ layerId }: Props) => { const { t } = useTranslation(); const dispatch = useAppDispatch(); const selector = useMemo( () => createMemoizedSelector(selectControlLayersSlice, (controlLayers) => { const layer = controlLayers.present.layers.find((l) => l.id === layerId); - assert(isMaskedGuidanceLayer(layer), `Layer ${layerId} not found or not an RP layer`); + assert(isRegionalGuidanceLayer(layer), `Layer ${layerId} not found or not an RP layer`); return { color: rgbColorToString(layer.previewColor), hasPositivePrompt: layer.positivePrompt !== null, @@ -62,7 +62,7 @@ export const MaskedGuidanceLayerListItem = memo(({ layerId }: Props) => { > - + {autoNegative === 'invert' && ( @@ -70,18 +70,18 @@ export const MaskedGuidanceLayerListItem = memo(({ layerId }: Props) => { {t('controlLayers.autoNegative')} )} - - - - + + + + {!hasPositivePrompt && !hasNegativePrompt && !hasIPAdapters && } - {hasPositivePrompt && } - {hasNegativePrompt && } - {hasIPAdapters && } + {hasPositivePrompt && } + {hasNegativePrompt && } + {hasIPAdapters && } ); }); -MaskedGuidanceLayerListItem.displayName = 'MaskedGuidanceLayerListItem'; +RGLayerListItem.displayName = 'RGLayerListItem'; diff --git a/invokeai/frontend/web/src/features/controlLayers/components/RPLayerNegativePrompt.tsx b/invokeai/frontend/web/src/features/controlLayers/components/RGLayerNegativePrompt.tsx similarity index 86% rename from invokeai/frontend/web/src/features/controlLayers/components/RPLayerNegativePrompt.tsx rename to invokeai/frontend/web/src/features/controlLayers/components/RGLayerNegativePrompt.tsx index d396de4634..e869c8809a 100644 --- a/invokeai/frontend/web/src/features/controlLayers/components/RPLayerNegativePrompt.tsx +++ b/invokeai/frontend/web/src/features/controlLayers/components/RGLayerNegativePrompt.tsx @@ -1,6 +1,6 @@ import { Box, Textarea } from '@invoke-ai/ui-library'; import { useAppDispatch } from 'app/store/storeHooks'; -import { RPLayerPromptDeleteButton } from 'features/controlLayers/components/RPLayerPromptDeleteButton'; +import { RGLayerPromptDeleteButton } from 'features/controlLayers/components/RGLayerPromptDeleteButton'; import { useLayerNegativePrompt } from 'features/controlLayers/hooks/layerStateHooks'; import { maskLayerNegativePromptChanged } from 'features/controlLayers/store/controlLayersSlice'; import { PromptOverlayButtonWrapper } from 'features/parameters/components/Prompts/PromptOverlayButtonWrapper'; @@ -14,7 +14,7 @@ type Props = { layerId: string; }; -export const RPLayerNegativePrompt = memo(({ layerId }: Props) => { +export const RGLayerNegativePrompt = memo(({ layerId }: Props) => { const prompt = useLayerNegativePrompt(layerId); const dispatch = useAppDispatch(); const textareaRef = useRef(null); @@ -47,7 +47,7 @@ export const RPLayerNegativePrompt = memo(({ layerId }: Props) => { fontSize="sm" /> - + @@ -55,4 +55,4 @@ export const RPLayerNegativePrompt = memo(({ layerId }: Props) => { ); }); -RPLayerNegativePrompt.displayName = 'RPLayerNegativePrompt'; +RGLayerNegativePrompt.displayName = 'RGLayerNegativePrompt'; diff --git a/invokeai/frontend/web/src/features/controlLayers/components/RPLayerPositivePrompt.tsx b/invokeai/frontend/web/src/features/controlLayers/components/RGLayerPositivePrompt.tsx similarity index 86% rename from invokeai/frontend/web/src/features/controlLayers/components/RPLayerPositivePrompt.tsx rename to invokeai/frontend/web/src/features/controlLayers/components/RGLayerPositivePrompt.tsx index 054c75958c..6d508338c1 100644 --- a/invokeai/frontend/web/src/features/controlLayers/components/RPLayerPositivePrompt.tsx +++ b/invokeai/frontend/web/src/features/controlLayers/components/RGLayerPositivePrompt.tsx @@ -1,6 +1,6 @@ import { Box, Textarea } from '@invoke-ai/ui-library'; import { useAppDispatch } from 'app/store/storeHooks'; -import { RPLayerPromptDeleteButton } from 'features/controlLayers/components/RPLayerPromptDeleteButton'; +import { RGLayerPromptDeleteButton } from 'features/controlLayers/components/RGLayerPromptDeleteButton'; import { useLayerPositivePrompt } from 'features/controlLayers/hooks/layerStateHooks'; import { maskLayerPositivePromptChanged } from 'features/controlLayers/store/controlLayersSlice'; import { PromptOverlayButtonWrapper } from 'features/parameters/components/Prompts/PromptOverlayButtonWrapper'; @@ -14,7 +14,7 @@ type Props = { layerId: string; }; -export const RPLayerPositivePrompt = memo(({ layerId }: Props) => { +export const RGLayerPositivePrompt = memo(({ layerId }: Props) => { const prompt = useLayerPositivePrompt(layerId); const dispatch = useAppDispatch(); const textareaRef = useRef(null); @@ -47,7 +47,7 @@ export const RPLayerPositivePrompt = memo(({ layerId }: Props) => { minH={28} /> - + @@ -55,4 +55,4 @@ export const RPLayerPositivePrompt = memo(({ layerId }: Props) => { ); }); -RPLayerPositivePrompt.displayName = 'RPLayerPositivePrompt'; +RGLayerPositivePrompt.displayName = 'RGLayerPositivePrompt'; diff --git a/invokeai/frontend/web/src/features/controlLayers/components/RPLayerPromptDeleteButton.tsx b/invokeai/frontend/web/src/features/controlLayers/components/RGLayerPromptDeleteButton.tsx similarity index 89% rename from invokeai/frontend/web/src/features/controlLayers/components/RPLayerPromptDeleteButton.tsx rename to invokeai/frontend/web/src/features/controlLayers/components/RGLayerPromptDeleteButton.tsx index b2778d0997..9a32bb68ad 100644 --- a/invokeai/frontend/web/src/features/controlLayers/components/RPLayerPromptDeleteButton.tsx +++ b/invokeai/frontend/web/src/features/controlLayers/components/RGLayerPromptDeleteButton.tsx @@ -13,7 +13,7 @@ type Props = { polarity: 'positive' | 'negative'; }; -export const RPLayerPromptDeleteButton = memo(({ layerId, polarity }: Props) => { +export const RGLayerPromptDeleteButton = memo(({ layerId, polarity }: Props) => { const { t } = useTranslation(); const dispatch = useAppDispatch(); const onClick = useCallback(() => { @@ -35,4 +35,4 @@ export const RPLayerPromptDeleteButton = memo(({ layerId, polarity }: Props) => ); }); -RPLayerPromptDeleteButton.displayName = 'RPLayerPromptDeleteButton'; +RGLayerPromptDeleteButton.displayName = 'RGLayerPromptDeleteButton'; diff --git a/invokeai/frontend/web/src/features/controlLayers/components/RPLayerSettingsPopover.tsx b/invokeai/frontend/web/src/features/controlLayers/components/RGLayerSettingsPopover.tsx similarity index 77% rename from invokeai/frontend/web/src/features/controlLayers/components/RPLayerSettingsPopover.tsx rename to invokeai/frontend/web/src/features/controlLayers/components/RGLayerSettingsPopover.tsx index cf6674db5d..acb64e16b8 100644 --- a/invokeai/frontend/web/src/features/controlLayers/components/RPLayerSettingsPopover.tsx +++ b/invokeai/frontend/web/src/features/controlLayers/components/RGLayerSettingsPopover.tsx @@ -9,7 +9,7 @@ import { PopoverContent, PopoverTrigger, } from '@invoke-ai/ui-library'; -import { MaskedGuidanceLayerAutoNegativeCheckbox } from 'features/controlLayers/components/RPLayerAutoNegativeCheckbox'; +import { RGLayerAutoNegativeCheckbox } from 'features/controlLayers/components/RGLayerAutoNegativeCheckbox'; import { memo } from 'react'; import { useTranslation } from 'react-i18next'; import { PiGearSixBold } from 'react-icons/pi'; @@ -23,7 +23,7 @@ const formLabelProps: FormLabelProps = { minW: 32, }; -const RPLayerSettingsPopover = ({ layerId }: Props) => { +const RGLayerSettingsPopover = ({ layerId }: Props) => { const { t } = useTranslation(); return ( @@ -41,7 +41,7 @@ const RPLayerSettingsPopover = ({ layerId }: Props) => { - + @@ -50,4 +50,4 @@ const RPLayerSettingsPopover = ({ layerId }: Props) => { ); }; -export default memo(RPLayerSettingsPopover); +export default memo(RGLayerSettingsPopover); diff --git a/invokeai/frontend/web/src/features/controlLayers/components/StageComponent.tsx b/invokeai/frontend/web/src/features/controlLayers/components/StageComponent.tsx index be7f584627..00368a059f 100644 --- a/invokeai/frontend/web/src/features/controlLayers/components/StageComponent.tsx +++ b/invokeai/frontend/web/src/features/controlLayers/components/StageComponent.tsx @@ -10,7 +10,7 @@ import { $isMouseOver, $lastMouseDownPos, $tool, - isMaskedGuidanceLayer, + isRegionalGuidanceLayer, layerBboxChanged, layerTranslated, selectControlLayersSlice, @@ -29,7 +29,7 @@ const log = logger('controlLayers'); const selectSelectedLayerColor = createMemoizedSelector(selectControlLayersSlice, (controlLayers) => { const layer = controlLayers.present.layers - .filter(isMaskedGuidanceLayer) + .filter(isRegionalGuidanceLayer) .find((l) => l.id === controlLayers.present.selectedLayerId); return layer?.previewColor ?? null; }); diff --git a/invokeai/frontend/web/src/features/controlLayers/hooks/layerStateHooks.ts b/invokeai/frontend/web/src/features/controlLayers/hooks/layerStateHooks.ts index 346f553e32..abeb7b801d 100644 --- a/invokeai/frontend/web/src/features/controlLayers/hooks/layerStateHooks.ts +++ b/invokeai/frontend/web/src/features/controlLayers/hooks/layerStateHooks.ts @@ -2,7 +2,7 @@ import { createSelector } from '@reduxjs/toolkit'; import { useAppSelector } from 'app/store/storeHooks'; import { isControlAdapterLayer, - isMaskedGuidanceLayer, + isRegionalGuidanceLayer, selectControlLayersSlice, } from 'features/controlLayers/store/controlLayersSlice'; import { useMemo } from 'react'; @@ -13,7 +13,7 @@ export const useLayerPositivePrompt = (layerId: string) => { () => createSelector(selectControlLayersSlice, (controlLayers) => { const layer = controlLayers.present.layers.find((l) => l.id === layerId); - assert(isMaskedGuidanceLayer(layer), `Layer ${layerId} not found or not an RP layer`); + assert(isRegionalGuidanceLayer(layer), `Layer ${layerId} not found or not an RP layer`); assert(layer.positivePrompt !== null, `Layer ${layerId} does not have a positive prompt`); return layer.positivePrompt; }), @@ -28,7 +28,7 @@ export const useLayerNegativePrompt = (layerId: string) => { () => createSelector(selectControlLayersSlice, (controlLayers) => { const layer = controlLayers.present.layers.find((l) => l.id === layerId); - assert(isMaskedGuidanceLayer(layer), `Layer ${layerId} not found or not an RP layer`); + assert(isRegionalGuidanceLayer(layer), `Layer ${layerId} not found or not an RP layer`); assert(layer.negativePrompt !== null, `Layer ${layerId} does not have a negative prompt`); return layer.negativePrompt; }), diff --git a/invokeai/frontend/web/src/features/controlLayers/hooks/useRegionalControlTitle.ts b/invokeai/frontend/web/src/features/controlLayers/hooks/useRegionalControlTitle.ts index f755b3e6f2..2957bed522 100644 --- a/invokeai/frontend/web/src/features/controlLayers/hooks/useRegionalControlTitle.ts +++ b/invokeai/frontend/web/src/features/controlLayers/hooks/useRegionalControlTitle.ts @@ -1,6 +1,6 @@ import { createSelector } from '@reduxjs/toolkit'; import { useAppSelector } from 'app/store/storeHooks'; -import { isMaskedGuidanceLayer, selectControlLayersSlice } from 'features/controlLayers/store/controlLayersSlice'; +import { isRegionalGuidanceLayer, selectControlLayersSlice } from 'features/controlLayers/store/controlLayersSlice'; import { useMemo } from 'react'; import { useTranslation } from 'react-i18next'; @@ -9,7 +9,7 @@ const selectValidLayerCount = createSelector(selectControlLayersSlice, (controlL return 0; } const validLayers = controlLayers.present.layers - .filter(isMaskedGuidanceLayer) + .filter(isRegionalGuidanceLayer) .filter((l) => l.isEnabled) .filter((l) => { const hasTextPrompt = Boolean(l.positivePrompt || l.negativePrompt); diff --git a/invokeai/frontend/web/src/features/controlLayers/store/controlLayersSlice.ts b/invokeai/frontend/web/src/features/controlLayers/store/controlLayersSlice.ts index f5e0fe616a..6e3fba453a 100644 --- a/invokeai/frontend/web/src/features/controlLayers/store/controlLayersSlice.ts +++ b/invokeai/frontend/web/src/features/controlLayers/store/controlLayersSlice.ts @@ -56,7 +56,7 @@ export const initialControlLayersState: ControlLayersState = { }; const isLine = (obj: VectorMaskLine | VectorMaskRect): obj is VectorMaskLine => obj.type === 'vector_mask_line'; -export const isMaskedGuidanceLayer = (layer?: Layer): layer is RegionalGuidanceLayer => +export const isRegionalGuidanceLayer = (layer?: Layer): layer is RegionalGuidanceLayer => layer?.type === 'regional_guidance_layer'; export const isControlAdapterLayer = (layer?: Layer): layer is ControlAdapterLayer => layer?.type === 'control_adapter_layer'; @@ -78,7 +78,7 @@ const resetLayer = (layer: Layer) => { } }; const getVectorMaskPreviewColor = (state: ControlLayersState): RgbColor => { - const vmLayers = state.layers.filter(isMaskedGuidanceLayer); + const vmLayers = state.layers.filter(isRegionalGuidanceLayer); const lastColor = vmLayers[vmLayers.length - 1]?.previewColor; return LayerColors.next(lastColor); }; @@ -88,10 +88,10 @@ export const controlLayersSlice = createSlice({ initialState: initialControlLayersState, reducers: { //#region All Layers - maskedGuidanceLayerAdded: (state, action: PayloadAction<{ layerId: string }>) => { + regionalGuidanceLayerAdded: (state, action: PayloadAction<{ layerId: string }>) => { const { layerId } = action.payload; const layer: RegionalGuidanceLayer = { - id: getMaskedGuidanceLayerId(layerId), + id: getRegionalGuidanceLayerId(layerId), type: 'regional_guidance_layer', isEnabled: true, bbox: null, @@ -291,7 +291,7 @@ export const controlLayersSlice = createSlice({ const { layerId, points, tool } = action.payload; const layer = state.layers.find((l) => l.id === layerId); if (layer?.type === 'regional_guidance_layer') { - const lineId = getMaskedGuidanceLayerLineId(layer.id, action.meta.uuid); + const lineId = getRegionalGuidanceLayerLineId(layer.id, action.meta.uuid); layer.maskObjects.push({ type: 'vector_mask_line', tool: tool, @@ -518,7 +518,7 @@ export const { layerVisibilityToggled, selectedLayerReset, selectedLayerDeleted, - maskedGuidanceLayerAdded, + regionalGuidanceLayerAdded, ipAdapterLayerAdded, controlAdapterLayerAdded, layerOpacityChanged, @@ -595,10 +595,10 @@ export const regional_guidance_layer_RECT_NAME = 'regional_guidance_layer.rect'; export const LAYER_BBOX_NAME = 'layer.bbox'; // Getters for non-singleton layer and object IDs -const getMaskedGuidanceLayerId = (layerId: string) => `${regional_guidance_layer_NAME}_${layerId}`; -const getMaskedGuidanceLayerLineId = (layerId: string, lineId: string) => `${layerId}.line_${lineId}`; +const getRegionalGuidanceLayerId = (layerId: string) => `${regional_guidance_layer_NAME}_${layerId}`; +const getRegionalGuidanceLayerLineId = (layerId: string, lineId: string) => `${layerId}.line_${lineId}`; const getMaskedGuidnaceLayerRectId = (layerId: string, lineId: string) => `${layerId}.rect_${lineId}`; -export const getMaskedGuidanceLayerObjectGroupId = (layerId: string, groupId: string) => +export const getRegionalGuidanceLayerObjectGroupId = (layerId: string, groupId: string) => `${layerId}.objectGroup_${groupId}`; export const getLayerBboxId = (layerId: string) => `${layerId}.bbox`; const getControlNetLayerId = (layerId: string) => `control_adapter_layer_${layerId}`; diff --git a/invokeai/frontend/web/src/features/controlLayers/util/getLayerBlobs.ts b/invokeai/frontend/web/src/features/controlLayers/util/getLayerBlobs.ts index c7616e4300..1b0808c5f1 100644 --- a/invokeai/frontend/web/src/features/controlLayers/util/getLayerBlobs.ts +++ b/invokeai/frontend/web/src/features/controlLayers/util/getLayerBlobs.ts @@ -1,7 +1,7 @@ import { getStore } from 'app/store/nanostores/store'; import openBase64ImageInTab from 'common/util/openBase64ImageInTab'; import { blobToDataURL } from 'features/canvas/util/blobToDataURL'; -import { isMaskedGuidanceLayer, regional_guidance_layer_NAME } from 'features/controlLayers/store/controlLayersSlice'; +import { isRegionalGuidanceLayer, regional_guidance_layer_NAME } from 'features/controlLayers/store/controlLayersSlice'; import { renderers } from 'features/controlLayers/util/renderers'; import Konva from 'konva'; import { assert } from 'tsafe'; @@ -19,7 +19,7 @@ export const getRegionalPromptLayerBlobs = async ( const state = getStore().getState(); const { layers } = state.controlLayers.present; const { width, height } = state.controlLayers.present.size; - const reduxLayers = layers.filter(isMaskedGuidanceLayer); + const reduxLayers = layers.filter(isRegionalGuidanceLayer); const container = document.createElement('div'); const stage = new Konva.Stage({ container, width, height }); renderers.renderLayers(stage, reduxLayers, 1, 'brush'); diff --git a/invokeai/frontend/web/src/features/controlLayers/util/renderers.ts b/invokeai/frontend/web/src/features/controlLayers/util/renderers.ts index d61e6d1b96..f2245b1faf 100644 --- a/invokeai/frontend/web/src/features/controlLayers/util/renderers.ts +++ b/invokeai/frontend/web/src/features/controlLayers/util/renderers.ts @@ -9,9 +9,9 @@ import { CONTROLNET_LAYER_NAME, getControlNetLayerImageId, getLayerBboxId, - getMaskedGuidanceLayerObjectGroupId, + getRegionalGuidanceLayerObjectGroupId, isControlAdapterLayer, - isMaskedGuidanceLayer, + isRegionalGuidanceLayer, isRenderableLayer, LAYER_BBOX_NAME, regional_guidance_layer_LINE_NAME, @@ -224,7 +224,7 @@ const renderToolPreview = ( * @param reduxLayer The redux layer to create the konva layer from. * @param onLayerPosChanged Callback for when the layer's position changes. */ -const createMaskedGuidanceLayer = ( +const createRegionalGuidanceLayer = ( stage: Konva.Stage, reduxLayer: RegionalGuidanceLayer, onLayerPosChanged?: (layerId: string, x: number, y: number) => void @@ -264,7 +264,7 @@ const createMaskedGuidanceLayer = ( // The object group holds all of the layer's objects (e.g. lines and rects) const konvaObjectGroup = new Konva.Group({ - id: getMaskedGuidanceLayerObjectGroupId(reduxLayer.id, uuidv4()), + id: getRegionalGuidanceLayerObjectGroupId(reduxLayer.id, uuidv4()), name: regional_guidance_layer_OBJECT_GROUP_NAME, listening: false, }); @@ -325,7 +325,7 @@ const createVectorMaskRect = (reduxObject: VectorMaskRect, konvaGroup: Konva.Gro * @param globalMaskLayerOpacity The opacity of the global mask layer. * @param tool The current tool. */ -const renderMaskedGuidanceLayer = ( +const renderRegionalGuidanceLayer = ( stage: Konva.Stage, reduxLayer: RegionalGuidanceLayer, globalMaskLayerOpacity: number, @@ -333,7 +333,8 @@ const renderMaskedGuidanceLayer = ( onLayerPosChanged?: (layerId: string, x: number, y: number) => void ): void => { const konvaLayer = - stage.findOne(`#${reduxLayer.id}`) ?? createMaskedGuidanceLayer(stage, reduxLayer, onLayerPosChanged); + stage.findOne(`#${reduxLayer.id}`) ?? + createRegionalGuidanceLayer(stage, reduxLayer, onLayerPosChanged); // Update the layer's position and listening state konvaLayer.setAttrs({ @@ -540,8 +541,8 @@ const renderLayers = ( } for (const reduxLayer of reduxLayers) { - if (isMaskedGuidanceLayer(reduxLayer)) { - renderMaskedGuidanceLayer(stage, reduxLayer, globalMaskLayerOpacity, tool, onLayerPosChanged); + if (isRegionalGuidanceLayer(reduxLayer)) { + renderRegionalGuidanceLayer(stage, reduxLayer, globalMaskLayerOpacity, tool, onLayerPosChanged); } if (isControlAdapterLayer(reduxLayer)) { renderControlNetLayer(stage, reduxLayer); diff --git a/invokeai/frontend/web/src/features/nodes/util/graph/addControlLayersToGraph.ts b/invokeai/frontend/web/src/features/nodes/util/graph/addControlLayersToGraph.ts index b16fe806b6..a7236af3cc 100644 --- a/invokeai/frontend/web/src/features/nodes/util/graph/addControlLayersToGraph.ts +++ b/invokeai/frontend/web/src/features/nodes/util/graph/addControlLayersToGraph.ts @@ -1,7 +1,7 @@ import { getStore } from 'app/store/nanostores/store'; import type { RootState } from 'app/store/store'; import { selectAllIPAdapters } from 'features/controlAdapters/store/controlAdaptersSlice'; -import { isMaskedGuidanceLayer } from 'features/controlLayers/store/controlLayersSlice'; +import { isRegionalGuidanceLayer } from 'features/controlLayers/store/controlLayersSlice'; import { getRegionalPromptLayerBlobs } from 'features/controlLayers/util/getLayerBlobs'; import { IP_ADAPTER_COLLECT, @@ -29,7 +29,7 @@ export const addControlLayersToGraph = async (state: RootState, graph: NonNullab const layers = state.controlLayers.present.layers // Only support vector mask layers now // TODO: Image masks - .filter(isMaskedGuidanceLayer) + .filter(isRegionalGuidanceLayer) // Only visible layers are rendered on the canvas .filter((l) => l.isEnabled) // Only layers with prompts get added to the graph diff --git a/invokeai/frontend/web/src/features/nodes/util/graph/addIPAdapterToLinearGraph.ts b/invokeai/frontend/web/src/features/nodes/util/graph/addIPAdapterToLinearGraph.ts index 1fd0ea76d7..2c53fb3827 100644 --- a/invokeai/frontend/web/src/features/nodes/util/graph/addIPAdapterToLinearGraph.ts +++ b/invokeai/frontend/web/src/features/nodes/util/graph/addIPAdapterToLinearGraph.ts @@ -1,7 +1,7 @@ import type { RootState } from 'app/store/store'; import { selectValidIPAdapters } from 'features/controlAdapters/store/controlAdaptersSlice'; import type { IPAdapterConfig } from 'features/controlAdapters/store/types'; -import { isIPAdapterLayer, isMaskedGuidanceLayer } from 'features/controlLayers/store/controlLayersSlice'; +import { isIPAdapterLayer, isRegionalGuidanceLayer } from 'features/controlLayers/store/controlLayersSlice'; import type { ImageField } from 'features/nodes/types/common'; import { activeTabNameSelector } from 'features/ui/store/uiSelectors'; import { differenceWith, intersectionWith } from 'lodash-es'; @@ -28,7 +28,7 @@ const getIPAdapters = (state: RootState) => { // Masked IP adapters are handled in the graph helper for regional control - skip them here const maskedIPAdapterIds = state.controlLayers.present.layers - .filter(isMaskedGuidanceLayer) + .filter(isRegionalGuidanceLayer) .map((l) => l.ipAdapterIds) .flat(); const nonMaskedIPAdapters = differenceWith(validIPAdapters, maskedIPAdapterIds, (a, b) => a.id === b); From ba6db33b39c626b93483887058006b7bbee5b822 Mon Sep 17 00:00:00 2001 From: psychedelicious <4822129+psychedelicious@users.noreply.github.com> Date: Tue, 30 Apr 2024 13:45:31 +1000 Subject: [PATCH 42/52] tidy(ui): more renaming of components --- .../app/store/middleware/listenerMiddleware/index.ts | 4 ++-- ...ridge.ts => controlLayersToControlAdapterBridge.ts} | 2 +- .../controlLayers/components/AddLayerButton.tsx | 2 +- .../controlLayers/components/AddPromptButtons.tsx | 2 +- .../controlLayers/components/DeleteAllLayersButton.tsx | 2 +- .../controlLayers/components/LayerDeleteButton.tsx | 2 +- .../controlLayers/components/LayerMenuRGActions.tsx | 2 +- .../controlLayers/components/RGLayerIPAdapterList.tsx | 2 +- ...egionalControlTitle.ts => useControlLayersTitle.ts} | 4 ++-- .../ControlSettingsAccordion.tsx | 10 +++++----- .../ui/components/ParametersPanelTextToImage.tsx | 6 +++--- .../src/features/ui/components/tabs/TextToImageTab.tsx | 6 +++--- 12 files changed, 22 insertions(+), 22 deletions(-) rename invokeai/frontend/web/src/app/store/middleware/listenerMiddleware/listeners/{regionalControlToControlAdapterBridge.ts => controlLayersToControlAdapterBridge.ts} (98%) rename invokeai/frontend/web/src/features/controlLayers/hooks/{useRegionalControlTitle.ts => useControlLayersTitle.ts} (90%) diff --git a/invokeai/frontend/web/src/app/store/middleware/listenerMiddleware/index.ts b/invokeai/frontend/web/src/app/store/middleware/listenerMiddleware/index.ts index 7474a79f18..ac039c2df6 100644 --- a/invokeai/frontend/web/src/app/store/middleware/listenerMiddleware/index.ts +++ b/invokeai/frontend/web/src/app/store/middleware/listenerMiddleware/index.ts @@ -16,6 +16,7 @@ import { addCanvasMaskSavedToGalleryListener } from 'app/store/middleware/listen import { addCanvasMaskToControlNetListener } from 'app/store/middleware/listenerMiddleware/listeners/canvasMaskToControlNet'; import { addCanvasMergedListener } from 'app/store/middleware/listenerMiddleware/listeners/canvasMerged'; import { addCanvasSavedToGalleryListener } from 'app/store/middleware/listenerMiddleware/listeners/canvasSavedToGallery'; +import { addControlLayersToControlAdapterBridge } from 'app/store/middleware/listenerMiddleware/listeners/controlLayersToControlAdapterBridge'; import { addControlNetAutoProcessListener } from 'app/store/middleware/listenerMiddleware/listeners/controlNetAutoProcess'; import { addControlNetImageProcessedListener } from 'app/store/middleware/listenerMiddleware/listeners/controlNetImageProcessed'; import { addEnqueueRequestedCanvasListener } from 'app/store/middleware/listenerMiddleware/listeners/enqueueRequestedCanvas'; @@ -35,7 +36,6 @@ import { addInitialImageSelectedListener } from 'app/store/middleware/listenerMi import { addModelSelectedListener } from 'app/store/middleware/listenerMiddleware/listeners/modelSelected'; import { addModelsLoadedListener } from 'app/store/middleware/listenerMiddleware/listeners/modelsLoaded'; import { addDynamicPromptsListener } from 'app/store/middleware/listenerMiddleware/listeners/promptChanged'; -import { addRegionalControlToControlAdapterBridge } from 'app/store/middleware/listenerMiddleware/listeners/regionalControlToControlAdapterBridge'; import { addSocketConnectedEventListener } from 'app/store/middleware/listenerMiddleware/listeners/socketio/socketConnected'; import { addSocketDisconnectedEventListener } from 'app/store/middleware/listenerMiddleware/listeners/socketio/socketDisconnected'; import { addGeneratorProgressEventListener } from 'app/store/middleware/listenerMiddleware/listeners/socketio/socketGeneratorProgress'; @@ -159,4 +159,4 @@ addDynamicPromptsListener(startAppListening); addSetDefaultSettingsListener(startAppListening); -addRegionalControlToControlAdapterBridge(startAppListening); +addControlLayersToControlAdapterBridge(startAppListening); diff --git a/invokeai/frontend/web/src/app/store/middleware/listenerMiddleware/listeners/regionalControlToControlAdapterBridge.ts b/invokeai/frontend/web/src/app/store/middleware/listenerMiddleware/listeners/controlLayersToControlAdapterBridge.ts similarity index 98% rename from invokeai/frontend/web/src/app/store/middleware/listenerMiddleware/listeners/regionalControlToControlAdapterBridge.ts rename to invokeai/frontend/web/src/app/store/middleware/listenerMiddleware/listeners/controlLayersToControlAdapterBridge.ts index 8a9191f90b..bc14277f88 100644 --- a/invokeai/frontend/web/src/app/store/middleware/listenerMiddleware/listeners/regionalControlToControlAdapterBridge.ts +++ b/invokeai/frontend/web/src/app/store/middleware/listenerMiddleware/listeners/controlLayersToControlAdapterBridge.ts @@ -26,7 +26,7 @@ export const guidanceLayerIPAdapterDeleted = createAction<{ layerId: string; ipA 'controlLayers/guidanceLayerIPAdapterDeleted' ); -export const addRegionalControlToControlAdapterBridge = (startAppListening: AppStartListening) => { +export const addControlLayersToControlAdapterBridge = (startAppListening: AppStartListening) => { startAppListening({ actionCreator: guidanceLayerAdded, effect: (action, { dispatch, getState }) => { diff --git a/invokeai/frontend/web/src/features/controlLayers/components/AddLayerButton.tsx b/invokeai/frontend/web/src/features/controlLayers/components/AddLayerButton.tsx index c1d95be10f..6aeebe3f08 100644 --- a/invokeai/frontend/web/src/features/controlLayers/components/AddLayerButton.tsx +++ b/invokeai/frontend/web/src/features/controlLayers/components/AddLayerButton.tsx @@ -1,5 +1,5 @@ import { Button, Menu, MenuButton, MenuItem, MenuList } from '@invoke-ai/ui-library'; -import { guidanceLayerAdded } from 'app/store/middleware/listenerMiddleware/listeners/regionalControlToControlAdapterBridge'; +import { guidanceLayerAdded } from 'app/store/middleware/listenerMiddleware/listeners/controlLayersToControlAdapterBridge'; import { useAppDispatch } from 'app/store/storeHooks'; import { memo, useCallback } from 'react'; import { useTranslation } from 'react-i18next'; diff --git a/invokeai/frontend/web/src/features/controlLayers/components/AddPromptButtons.tsx b/invokeai/frontend/web/src/features/controlLayers/components/AddPromptButtons.tsx index 51e5f43b24..88eac207b2 100644 --- a/invokeai/frontend/web/src/features/controlLayers/components/AddPromptButtons.tsx +++ b/invokeai/frontend/web/src/features/controlLayers/components/AddPromptButtons.tsx @@ -1,6 +1,6 @@ import { Button, Flex } from '@invoke-ai/ui-library'; import { createMemoizedSelector } from 'app/store/createMemoizedSelector'; -import { guidanceLayerIPAdapterAdded } from 'app/store/middleware/listenerMiddleware/listeners/regionalControlToControlAdapterBridge'; +import { guidanceLayerIPAdapterAdded } from 'app/store/middleware/listenerMiddleware/listeners/controlLayersToControlAdapterBridge'; import { useAppDispatch, useAppSelector } from 'app/store/storeHooks'; import { isRegionalGuidanceLayer, diff --git a/invokeai/frontend/web/src/features/controlLayers/components/DeleteAllLayersButton.tsx b/invokeai/frontend/web/src/features/controlLayers/components/DeleteAllLayersButton.tsx index 110a018a8e..c55864afa5 100644 --- a/invokeai/frontend/web/src/features/controlLayers/components/DeleteAllLayersButton.tsx +++ b/invokeai/frontend/web/src/features/controlLayers/components/DeleteAllLayersButton.tsx @@ -1,5 +1,5 @@ import { Button } from '@invoke-ai/ui-library'; -import { allLayersDeleted } from 'app/store/middleware/listenerMiddleware/listeners/regionalControlToControlAdapterBridge'; +import { allLayersDeleted } from 'app/store/middleware/listenerMiddleware/listeners/controlLayersToControlAdapterBridge'; import { useAppDispatch } from 'app/store/storeHooks'; import { memo, useCallback } from 'react'; import { useTranslation } from 'react-i18next'; diff --git a/invokeai/frontend/web/src/features/controlLayers/components/LayerDeleteButton.tsx b/invokeai/frontend/web/src/features/controlLayers/components/LayerDeleteButton.tsx index fe9c9ecc2a..5d8937db66 100644 --- a/invokeai/frontend/web/src/features/controlLayers/components/LayerDeleteButton.tsx +++ b/invokeai/frontend/web/src/features/controlLayers/components/LayerDeleteButton.tsx @@ -1,5 +1,5 @@ import { IconButton } from '@invoke-ai/ui-library'; -import { guidanceLayerDeleted } from 'app/store/middleware/listenerMiddleware/listeners/regionalControlToControlAdapterBridge'; +import { guidanceLayerDeleted } from 'app/store/middleware/listenerMiddleware/listeners/controlLayersToControlAdapterBridge'; import { useAppDispatch } from 'app/store/storeHooks'; import { memo, useCallback } from 'react'; import { useTranslation } from 'react-i18next'; diff --git a/invokeai/frontend/web/src/features/controlLayers/components/LayerMenuRGActions.tsx b/invokeai/frontend/web/src/features/controlLayers/components/LayerMenuRGActions.tsx index cdcb4d99b3..6c2bb4c26b 100644 --- a/invokeai/frontend/web/src/features/controlLayers/components/LayerMenuRGActions.tsx +++ b/invokeai/frontend/web/src/features/controlLayers/components/LayerMenuRGActions.tsx @@ -1,6 +1,6 @@ import { MenuItem } from '@invoke-ai/ui-library'; import { createMemoizedSelector } from 'app/store/createMemoizedSelector'; -import { guidanceLayerIPAdapterAdded } from 'app/store/middleware/listenerMiddleware/listeners/regionalControlToControlAdapterBridge'; +import { guidanceLayerIPAdapterAdded } from 'app/store/middleware/listenerMiddleware/listeners/controlLayersToControlAdapterBridge'; import { useAppDispatch, useAppSelector } from 'app/store/storeHooks'; import { isRegionalGuidanceLayer, diff --git a/invokeai/frontend/web/src/features/controlLayers/components/RGLayerIPAdapterList.tsx b/invokeai/frontend/web/src/features/controlLayers/components/RGLayerIPAdapterList.tsx index 07a8e0e334..464bd41897 100644 --- a/invokeai/frontend/web/src/features/controlLayers/components/RGLayerIPAdapterList.tsx +++ b/invokeai/frontend/web/src/features/controlLayers/components/RGLayerIPAdapterList.tsx @@ -1,6 +1,6 @@ import { Divider, Flex, IconButton, Spacer, Text } from '@invoke-ai/ui-library'; import { createMemoizedSelector } from 'app/store/createMemoizedSelector'; -import { guidanceLayerIPAdapterDeleted } from 'app/store/middleware/listenerMiddleware/listeners/regionalControlToControlAdapterBridge'; +import { guidanceLayerIPAdapterDeleted } from 'app/store/middleware/listenerMiddleware/listeners/controlLayersToControlAdapterBridge'; import { useAppDispatch, useAppSelector } from 'app/store/storeHooks'; import ControlAdapterLayerConfig from 'features/controlLayers/components/controlAdapterOverrides/ControlAdapterLayerConfig'; import { isRegionalGuidanceLayer, selectControlLayersSlice } from 'features/controlLayers/store/controlLayersSlice'; diff --git a/invokeai/frontend/web/src/features/controlLayers/hooks/useRegionalControlTitle.ts b/invokeai/frontend/web/src/features/controlLayers/hooks/useControlLayersTitle.ts similarity index 90% rename from invokeai/frontend/web/src/features/controlLayers/hooks/useRegionalControlTitle.ts rename to invokeai/frontend/web/src/features/controlLayers/hooks/useControlLayersTitle.ts index 2957bed522..93c8bec8a6 100644 --- a/invokeai/frontend/web/src/features/controlLayers/hooks/useRegionalControlTitle.ts +++ b/invokeai/frontend/web/src/features/controlLayers/hooks/useControlLayersTitle.ts @@ -20,12 +20,12 @@ const selectValidLayerCount = createSelector(selectControlLayersSlice, (controlL return validLayers.length; }); -export const useRegionalControlTitle = () => { +export const useControlLayersTitle = () => { const { t } = useTranslation(); const validLayerCount = useAppSelector(selectValidLayerCount); const title = useMemo(() => { const suffix = validLayerCount > 0 ? ` (${validLayerCount})` : ''; - return `${t('controlLayers.regionalControl')}${suffix}`; + return `${t('controlLayers.controlLayers')}${suffix}`; }, [t, validLayerCount]); return title; }; diff --git a/invokeai/frontend/web/src/features/settingsAccordions/components/ControlSettingsAccordion/ControlSettingsAccordion.tsx b/invokeai/frontend/web/src/features/settingsAccordions/components/ControlSettingsAccordion/ControlSettingsAccordion.tsx index eb3f932bbf..d072cfde0f 100644 --- a/invokeai/frontend/web/src/features/settingsAccordions/components/ControlSettingsAccordion/ControlSettingsAccordion.tsx +++ b/invokeai/frontend/web/src/features/settingsAccordions/components/ControlSettingsAccordion/ControlSettingsAccordion.tsx @@ -26,10 +26,10 @@ const selector = createMemoizedSelector( const badges: string[] = []; let isError = false; - const regionalControlAdapterIds = selectAllControlAdapterIds(controlLayers.present); + const controlLayersAdapterIds = selectAllControlAdapterIds(controlLayers.present); const enabledNonRegionalIPAdapterCount = selectAllIPAdapters(controlAdapters) - .filter((ca) => !regionalControlAdapterIds.includes(ca.id)) + .filter((ca) => !controlLayersAdapterIds.includes(ca.id)) .filter((ca) => ca.isEnabled).length; const validIPAdapterCount = selectValidIPAdapters(controlAdapters).length; @@ -41,7 +41,7 @@ const selector = createMemoizedSelector( } const enabledControlNetCount = selectAllControlNets(controlAdapters) - .filter((ca) => !regionalControlAdapterIds.includes(ca.id)) + .filter((ca) => !controlLayersAdapterIds.includes(ca.id)) .filter((ca) => ca.isEnabled).length; const validControlNetCount = selectValidControlNets(controlAdapters).length; if (enabledControlNetCount > 0) { @@ -52,7 +52,7 @@ const selector = createMemoizedSelector( } const enabledT2IAdapterCount = selectAllT2IAdapters(controlAdapters) - .filter((ca) => !regionalControlAdapterIds.includes(ca.id)) + .filter((ca) => !controlLayersAdapterIds.includes(ca.id)) .filter((ca) => ca.isEnabled).length; const validT2IAdapterCount = selectValidT2IAdapters(controlAdapters).length; if (enabledT2IAdapterCount > 0) { @@ -63,7 +63,7 @@ const selector = createMemoizedSelector( } const controlAdapterIds = selectControlAdapterIds(controlAdapters).filter( - (id) => !regionalControlAdapterIds.includes(id) + (id) => !controlLayersAdapterIds.includes(id) ); return { diff --git a/invokeai/frontend/web/src/features/ui/components/ParametersPanelTextToImage.tsx b/invokeai/frontend/web/src/features/ui/components/ParametersPanelTextToImage.tsx index 82243867d7..b809ef999f 100644 --- a/invokeai/frontend/web/src/features/ui/components/ParametersPanelTextToImage.tsx +++ b/invokeai/frontend/web/src/features/ui/components/ParametersPanelTextToImage.tsx @@ -2,7 +2,7 @@ import { Box, Flex, Tab, TabList, TabPanel, TabPanels, Tabs } from '@invoke-ai/u import { useAppSelector } from 'app/store/storeHooks'; import { overlayScrollbarsParams } from 'common/components/OverlayScrollbars/constants'; import { ControlLayersPanelContent } from 'features/controlLayers/components/ControlLayersPanelContent'; -import { useRegionalControlTitle } from 'features/controlLayers/hooks/useRegionalControlTitle'; +import { useControlLayersTitle } from 'features/controlLayers/hooks/useControlLayersTitle'; import { Prompts } from 'features/parameters/components/Prompts/Prompts'; import QueueControls from 'features/queue/components/QueueControls'; import { SDXLPrompts } from 'features/sdxl/components/SDXLPrompts/SDXLPrompts'; @@ -26,7 +26,7 @@ const overlayScrollbarsStyles: CSSProperties = { const ParametersPanelTextToImage = () => { const { t } = useTranslation(); const activeTabName = useAppSelector(activeTabNameSelector); - const regionalControlTitle = useRegionalControlTitle(); + const controlLayersTitle = useControlLayersTitle(); const isSDXL = useAppSelector((s) => s.generation.model?.base === 'sdxl'); return ( @@ -40,7 +40,7 @@ const ParametersPanelTextToImage = () => { {t('parameters.globalSettings')} - {regionalControlTitle} + {controlLayersTitle} diff --git a/invokeai/frontend/web/src/features/ui/components/tabs/TextToImageTab.tsx b/invokeai/frontend/web/src/features/ui/components/tabs/TextToImageTab.tsx index 1ff5dd6462..f9b760bcd5 100644 --- a/invokeai/frontend/web/src/features/ui/components/tabs/TextToImageTab.tsx +++ b/invokeai/frontend/web/src/features/ui/components/tabs/TextToImageTab.tsx @@ -1,20 +1,20 @@ import { Box, Tab, TabList, TabPanel, TabPanels, Tabs } from '@invoke-ai/ui-library'; import { ControlLayersEditor } from 'features/controlLayers/components/ControlLayersEditor'; -import { useRegionalControlTitle } from 'features/controlLayers/hooks/useRegionalControlTitle'; +import { useControlLayersTitle } from 'features/controlLayers/hooks/useControlLayersTitle'; import CurrentImageDisplay from 'features/gallery/components/CurrentImage/CurrentImageDisplay'; import { memo } from 'react'; import { useTranslation } from 'react-i18next'; const TextToImageTab = () => { const { t } = useTranslation(); - const regionalControlTitle = useRegionalControlTitle(); + const controlLayersTitle = useControlLayersTitle(); return ( {t('common.viewer')} - {regionalControlTitle} + {controlLayersTitle} From 1212698059d38c7428c9b508818fd8db202a338c Mon Sep 17 00:00:00 2001 From: psychedelicious <4822129+psychedelicious@users.noreply.github.com> Date: Tue, 30 Apr 2024 13:51:07 +1000 Subject: [PATCH 43/52] tidy(ui): more renaming of components --- invokeai/frontend/web/public/locales/en.json | 6 +++++- .../controlLayers/components/AddLayerButton.tsx | 14 ++++++++++---- .../controlLayers/components/LayerTitle.tsx | 4 ++-- 3 files changed, 17 insertions(+), 7 deletions(-) diff --git a/invokeai/frontend/web/public/locales/en.json b/invokeai/frontend/web/public/locales/en.json index 814ad4de0d..dbce62c8a4 100644 --- a/invokeai/frontend/web/public/locales/en.json +++ b/invokeai/frontend/web/public/locales/en.json @@ -1537,6 +1537,10 @@ "regionalGuidanceLayer": "$t(controlLayers.regionalGuidance) $t(unifiedCanvas.layer)", "controlNetLayer": "$t(common.controlNet) $t(unifiedCanvas.layer)", "ipAdapterLayer": "$t(common.ipAdapter) $t(unifiedCanvas.layer)", - "opacity": "Opacity" + "opacity": "Opacity", + "globalControlAdapter": "Global $t(controlnet.controlAdapter_one)", + "globalControlAdapterLayer": "Global $t(controlnet.controlAdapter_one) $t(unifiedCanvas.layer)", + "globalIPAdapter": "Global $t(common.ipAdapter)", + "globalIPAdapterLayer": "Global $t(common.ipAdapter) $t(unifiedCanvas.layer)" } } diff --git a/invokeai/frontend/web/src/features/controlLayers/components/AddLayerButton.tsx b/invokeai/frontend/web/src/features/controlLayers/components/AddLayerButton.tsx index 6aeebe3f08..b521153239 100644 --- a/invokeai/frontend/web/src/features/controlLayers/components/AddLayerButton.tsx +++ b/invokeai/frontend/web/src/features/controlLayers/components/AddLayerButton.tsx @@ -11,7 +11,7 @@ export const AddLayerButton = memo(() => { const addRegionalGuidanceLayer = useCallback(() => { dispatch(guidanceLayerAdded('regional_guidance_layer')); }, [dispatch]); - const addControlNetLayer = useCallback(() => { + const addControlAdapterLayer = useCallback(() => { dispatch(guidanceLayerAdded('control_adapter_layer')); }, [dispatch]); const addIPAdapterLayer = useCallback(() => { @@ -24,9 +24,15 @@ export const AddLayerButton = memo(() => { {t('controlLayers.addLayer')} - {t('controlLayers.regionalGuidanceLayer')} - {t('controlLayers.controlNetLayer')} - {t('controlLayers.ipAdapterLayer')} + } onClick={addRegionalGuidanceLayer}> + {t('controlLayers.regionalGuidanceLayer')} + + } onClick={addControlAdapterLayer}> + {t('controlLayers.globalControlAdapterLayer')} + + } onClick={addIPAdapterLayer}> + {t('controlLayers.globalIPAdapterLayer')} + ); diff --git a/invokeai/frontend/web/src/features/controlLayers/components/LayerTitle.tsx b/invokeai/frontend/web/src/features/controlLayers/components/LayerTitle.tsx index 523bae0490..ba3e01f799 100644 --- a/invokeai/frontend/web/src/features/controlLayers/components/LayerTitle.tsx +++ b/invokeai/frontend/web/src/features/controlLayers/components/LayerTitle.tsx @@ -13,9 +13,9 @@ export const LayerTitle = memo(({ type }: Props) => { if (type === 'regional_guidance_layer') { return t('controlLayers.regionalGuidance'); } else if (type === 'control_adapter_layer') { - return t('common.controlNet'); + return t('controlLayers.globalControlAdapter'); } else if (type === 'ip_adapter_layer') { - return t('common.ipAdapter'); + return t('controlLayers.globalIPAdapter'); } }, [t, type]); From 8a791d4f16a23b8172895fafc885f7e31e11a94b Mon Sep 17 00:00:00 2001 From: psychedelicious <4822129+psychedelicious@users.noreply.github.com> Date: Tue, 30 Apr 2024 18:50:29 +1000 Subject: [PATCH 44/52] feat(ui): make control image opacity filter toggleable --- invokeai/frontend/web/public/locales/en.json | 3 +- .../components/CALayerOpacity.tsx | 29 ++++++++++++++----- .../controlLayers/hooks/layerStateHooks.ts | 2 +- .../controlLayers/store/controlLayersSlice.ts | 16 ++++++++++ .../src/features/controlLayers/store/types.ts | 1 + .../features/controlLayers/util/renderers.ts | 6 ++-- 6 files changed, 46 insertions(+), 11 deletions(-) diff --git a/invokeai/frontend/web/public/locales/en.json b/invokeai/frontend/web/public/locales/en.json index dbce62c8a4..885a937de3 100644 --- a/invokeai/frontend/web/public/locales/en.json +++ b/invokeai/frontend/web/public/locales/en.json @@ -1541,6 +1541,7 @@ "globalControlAdapter": "Global $t(controlnet.controlAdapter_one)", "globalControlAdapterLayer": "Global $t(controlnet.controlAdapter_one) $t(unifiedCanvas.layer)", "globalIPAdapter": "Global $t(common.ipAdapter)", - "globalIPAdapterLayer": "Global $t(common.ipAdapter) $t(unifiedCanvas.layer)" + "globalIPAdapterLayer": "Global $t(common.ipAdapter) $t(unifiedCanvas.layer)", + "opacityFilter": "Opacity Filter" } } diff --git a/invokeai/frontend/web/src/features/controlLayers/components/CALayerOpacity.tsx b/invokeai/frontend/web/src/features/controlLayers/components/CALayerOpacity.tsx index 02e63dc8d4..eb414f5369 100644 --- a/invokeai/frontend/web/src/features/controlLayers/components/CALayerOpacity.tsx +++ b/invokeai/frontend/web/src/features/controlLayers/components/CALayerOpacity.tsx @@ -10,10 +10,12 @@ import { PopoverBody, PopoverContent, PopoverTrigger, + Switch, } from '@invoke-ai/ui-library'; import { useAppDispatch } from 'app/store/storeHooks'; import { useLayerOpacity } from 'features/controlLayers/hooks/layerStateHooks'; -import { layerOpacityChanged } from 'features/controlLayers/store/controlLayersSlice'; +import { isFilterEnabledChanged, layerOpacityChanged } from 'features/controlLayers/store/controlLayersSlice'; +import type { ChangeEvent } from 'react'; import { memo, useCallback } from 'react'; import { useTranslation } from 'react-i18next'; import { PiDropHalfFill } from 'react-icons/pi'; @@ -28,13 +30,19 @@ const formatPct = (v: number | string) => `${v} %`; const CALayerOpacity = ({ layerId }: Props) => { const { t } = useTranslation(); const dispatch = useAppDispatch(); - const opacity = useLayerOpacity(layerId); - const onChange = useCallback( + const { opacity, isFilterEnabled } = useLayerOpacity(layerId); + const onChangeOpacity = useCallback( (v: number) => { dispatch(layerOpacityChanged({ layerId, opacity: v / 100 })); }, [dispatch, layerId] ); + const onChangeFilter = useCallback( + (e: ChangeEvent) => { + dispatch(isFilterEnabledChanged({ layerId, isFilterEnabled: e.target.checked })); + }, + [dispatch, layerId] + ); return ( @@ -49,7 +57,13 @@ const CALayerOpacity = ({ layerId }: Props) => { - + + + {t('controlLayers.opacityFilter')} + + + + {t('controlLayers.opacity')} { step={1} value={opacity} defaultValue={100} - onChange={onChange} + onChange={onChangeOpacity} marks={marks} + w={48} /> { step={1} value={opacity} defaultValue={100} - onChange={onChange} - minW={24} + onChange={onChangeOpacity} + w={24} format={formatPct} /> diff --git a/invokeai/frontend/web/src/features/controlLayers/hooks/layerStateHooks.ts b/invokeai/frontend/web/src/features/controlLayers/hooks/layerStateHooks.ts index abeb7b801d..b4880d1dc6 100644 --- a/invokeai/frontend/web/src/features/controlLayers/hooks/layerStateHooks.ts +++ b/invokeai/frontend/web/src/features/controlLayers/hooks/layerStateHooks.ts @@ -72,7 +72,7 @@ export const useLayerOpacity = (layerId: string) => { createSelector(selectControlLayersSlice, (controlLayers) => { const layer = controlLayers.present.layers.filter(isControlAdapterLayer).find((l) => l.id === layerId); assert(layer, `Layer ${layerId} not found`); - return Math.round(layer.opacity * 100); + return { opacity: Math.round(layer.opacity * 100), isFilterEnabled: layer.isFilterEnabled }; }), [layerId] ); diff --git a/invokeai/frontend/web/src/features/controlLayers/store/controlLayersSlice.ts b/invokeai/frontend/web/src/features/controlLayers/store/controlLayersSlice.ts index 6e3fba453a..3c7371e596 100644 --- a/invokeai/frontend/web/src/features/controlLayers/store/controlLayersSlice.ts +++ b/invokeai/frontend/web/src/features/controlLayers/store/controlLayersSlice.ts @@ -141,6 +141,7 @@ export const controlLayersSlice = createSlice({ imageName: null, opacity: 1, isSelected: true, + isFilterEnabled: true, }; state.layers.push(layer); state.selectedLayerId = layer.id; @@ -243,6 +244,19 @@ export const controlLayersSlice = createSlice({ }, //#endregion + //#region CA Layers + isFilterEnabledChanged: ( + state, + action: PayloadAction<{ layerId: string; isFilterEnabled: boolean }> + ) => { + const { layerId, isFilterEnabled } = action.payload; + const layer = state.layers.filter(isControlAdapterLayer).find((l) => l.id === layerId); + if (layer) { + layer.isFilterEnabled = isFilterEnabled; + } + }, + //#endregion + //#region Mask Layers maskLayerPositivePromptChanged: (state, action: PayloadAction<{ layerId: string; prompt: string | null }>) => { const { layerId, prompt } = action.payload; @@ -522,6 +536,8 @@ export const { ipAdapterLayerAdded, controlAdapterLayerAdded, layerOpacityChanged, + // CA layer actions + isFilterEnabledChanged, // Mask layer actions maskLayerLineAdded, maskLayerPointsAdded, diff --git a/invokeai/frontend/web/src/features/controlLayers/store/types.ts b/invokeai/frontend/web/src/features/controlLayers/store/types.ts index 5f3d11c765..58b25f967b 100644 --- a/invokeai/frontend/web/src/features/controlLayers/store/types.ts +++ b/invokeai/frontend/web/src/features/controlLayers/store/types.ts @@ -50,6 +50,7 @@ export type ControlAdapterLayer = RenderableLayerBase & { controlNetId: string; imageName: string | null; opacity: number; + isFilterEnabled: boolean; }; export type IPAdapterLayer = LayerBase & { diff --git a/invokeai/frontend/web/src/features/controlLayers/util/renderers.ts b/invokeai/frontend/web/src/features/controlLayers/util/renderers.ts index f2245b1faf..da835655cc 100644 --- a/invokeai/frontend/web/src/features/controlLayers/util/renderers.ts +++ b/invokeai/frontend/web/src/features/controlLayers/util/renderers.ts @@ -421,7 +421,6 @@ const createControlNetLayerImage = (konvaLayer: Konva.Layer, image: HTMLImageEle const konvaImage = new Konva.Image({ name: CONTROLNET_LAYER_IMAGE_NAME, image, - filters: [LightnessToAlphaFilter], }); konvaLayer.add(konvaImage); return konvaImage; @@ -469,10 +468,12 @@ const updateControlNetLayerImageAttrs = ( let needsCache = false; const newWidth = stage.width() / stage.scaleX(); const newHeight = stage.height() / stage.scaleY(); + const hasFilter = konvaImage.filters() !== null && konvaImage.filters().length > 0; if ( konvaImage.width() !== newWidth || konvaImage.height() !== newHeight || - konvaImage.visible() !== reduxLayer.isEnabled + konvaImage.visible() !== reduxLayer.isEnabled || + hasFilter !== reduxLayer.isFilterEnabled ) { konvaImage.setAttrs({ opacity: reduxLayer.opacity, @@ -481,6 +482,7 @@ const updateControlNetLayerImageAttrs = ( width: stage.width() / stage.scaleX(), height: stage.height() / stage.scaleY(), visible: reduxLayer.isEnabled, + filters: reduxLayer.isFilterEnabled ? [LightnessToAlphaFilter] : [], }); needsCache = true; } From cf1883585d86e822fb2917ca91f34be928d6102a Mon Sep 17 00:00:00 2001 From: psychedelicious <4822129+psychedelicious@users.noreply.github.com> Date: Tue, 30 Apr 2024 19:44:57 +1000 Subject: [PATCH 45/52] chore(ui): lint --- .../src/features/controlLayers/store/controlLayersSlice.ts | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/invokeai/frontend/web/src/features/controlLayers/store/controlLayersSlice.ts b/invokeai/frontend/web/src/features/controlLayers/store/controlLayersSlice.ts index 3c7371e596..9d5e32fa23 100644 --- a/invokeai/frontend/web/src/features/controlLayers/store/controlLayersSlice.ts +++ b/invokeai/frontend/web/src/features/controlLayers/store/controlLayersSlice.ts @@ -245,10 +245,7 @@ export const controlLayersSlice = createSlice({ //#endregion //#region CA Layers - isFilterEnabledChanged: ( - state, - action: PayloadAction<{ layerId: string; isFilterEnabled: boolean }> - ) => { + isFilterEnabledChanged: (state, action: PayloadAction<{ layerId: string; isFilterEnabled: boolean }>) => { const { layerId, isFilterEnabled } = action.payload; const layer = state.layers.filter(isControlAdapterLayer).find((l) => l.id === layerId); if (layer) { From d74cd12aa67a964a52ede856cfa2e6ffa1741ce8 Mon Sep 17 00:00:00 2001 From: psychedelicious <4822129+psychedelicious@users.noreply.github.com> Date: Tue, 30 Apr 2024 19:45:09 +1000 Subject: [PATCH 46/52] feat(ui): collapsible layers --- .../web/src/common/util/stopPropagation.ts | 3 ++ .../components/CALayerListItem.tsx | 14 +++++--- .../components/CALayerOpacity.tsx | 2 ++ .../components/IPLayerListItem.tsx | 15 ++++++--- .../components/LayerDeleteButton.tsx | 2 ++ .../controlLayers/components/LayerMenu.tsx | 9 ++++- .../controlLayers/components/LayerTitle.tsx | 2 +- .../components/LayerVisibilityToggle.tsx | 2 ++ .../components/RGLayerColorPicker.tsx | 2 ++ .../components/RGLayerListItem.tsx | 33 +++++++++---------- .../components/RGLayerSettingsPopover.tsx | 2 ++ .../ControlAdapterLayerConfig.tsx | 2 +- 12 files changed, 58 insertions(+), 30 deletions(-) create mode 100644 invokeai/frontend/web/src/common/util/stopPropagation.ts diff --git a/invokeai/frontend/web/src/common/util/stopPropagation.ts b/invokeai/frontend/web/src/common/util/stopPropagation.ts new file mode 100644 index 0000000000..b3481b7c0e --- /dev/null +++ b/invokeai/frontend/web/src/common/util/stopPropagation.ts @@ -0,0 +1,3 @@ +export const stopPropagation = (e: React.MouseEvent) => { + e.stopPropagation(); +}; diff --git a/invokeai/frontend/web/src/features/controlLayers/components/CALayerListItem.tsx b/invokeai/frontend/web/src/features/controlLayers/components/CALayerListItem.tsx index 28ef0866fb..a55c71fad6 100644 --- a/invokeai/frontend/web/src/features/controlLayers/components/CALayerListItem.tsx +++ b/invokeai/frontend/web/src/features/controlLayers/components/CALayerListItem.tsx @@ -1,4 +1,4 @@ -import { Flex, Spacer } from '@invoke-ai/ui-library'; +import { Flex, Spacer, useDisclosure } from '@invoke-ai/ui-library'; import { createMemoizedSelector } from 'app/store/createMemoizedSelector'; import { useAppDispatch, useAppSelector } from 'app/store/storeHooks'; import CALayerOpacity from 'features/controlLayers/components/CALayerOpacity'; @@ -38,6 +38,8 @@ export const CALayerListItem = memo(({ layerId }: Props) => { // Must be capture so that the layer is selected before deleting/resetting/etc dispatch(layerSelected(layerId)); }, [dispatch, layerId]); + const { isOpen, onToggle } = useDisclosure(); + return ( { borderRadius="base" py="1px" > - - + + @@ -56,7 +58,11 @@ export const CALayerListItem = memo(({ layerId }: Props) => { - + {isOpen && ( + + + + )} ); diff --git a/invokeai/frontend/web/src/features/controlLayers/components/CALayerOpacity.tsx b/invokeai/frontend/web/src/features/controlLayers/components/CALayerOpacity.tsx index eb414f5369..a6107da1ec 100644 --- a/invokeai/frontend/web/src/features/controlLayers/components/CALayerOpacity.tsx +++ b/invokeai/frontend/web/src/features/controlLayers/components/CALayerOpacity.tsx @@ -13,6 +13,7 @@ import { Switch, } from '@invoke-ai/ui-library'; import { useAppDispatch } from 'app/store/storeHooks'; +import { stopPropagation } from 'common/util/stopPropagation'; import { useLayerOpacity } from 'features/controlLayers/hooks/layerStateHooks'; import { isFilterEnabledChanged, layerOpacityChanged } from 'features/controlLayers/store/controlLayersSlice'; import type { ChangeEvent } from 'react'; @@ -51,6 +52,7 @@ const CALayerOpacity = ({ layerId }: Props) => { size="sm" icon={} variant="ghost" + onDoubleClick={stopPropagation} /> diff --git a/invokeai/frontend/web/src/features/controlLayers/components/IPLayerListItem.tsx b/invokeai/frontend/web/src/features/controlLayers/components/IPLayerListItem.tsx index efb4156137..d9b3121dab 100644 --- a/invokeai/frontend/web/src/features/controlLayers/components/IPLayerListItem.tsx +++ b/invokeai/frontend/web/src/features/controlLayers/components/IPLayerListItem.tsx @@ -1,4 +1,4 @@ -import { Flex, Spacer } from '@invoke-ai/ui-library'; +import { Flex, Spacer, useDisclosure } from '@invoke-ai/ui-library'; import { createMemoizedSelector } from 'app/store/createMemoizedSelector'; import { useAppSelector } from 'app/store/storeHooks'; import ControlAdapterLayerConfig from 'features/controlLayers/components/controlAdapterOverrides/ControlAdapterLayerConfig'; @@ -24,16 +24,21 @@ export const IPLayerListItem = memo(({ layerId }: Props) => { [layerId] ); const ipAdapterId = useAppSelector(selector); + const { isOpen, onToggle } = useDisclosure(); return ( - - - + + + - + {isOpen && ( + + + + )} ); diff --git a/invokeai/frontend/web/src/features/controlLayers/components/LayerDeleteButton.tsx b/invokeai/frontend/web/src/features/controlLayers/components/LayerDeleteButton.tsx index 5d8937db66..0c74b2a9ea 100644 --- a/invokeai/frontend/web/src/features/controlLayers/components/LayerDeleteButton.tsx +++ b/invokeai/frontend/web/src/features/controlLayers/components/LayerDeleteButton.tsx @@ -1,6 +1,7 @@ import { IconButton } from '@invoke-ai/ui-library'; import { guidanceLayerDeleted } from 'app/store/middleware/listenerMiddleware/listeners/controlLayersToControlAdapterBridge'; import { useAppDispatch } from 'app/store/storeHooks'; +import { stopPropagation } from 'common/util/stopPropagation'; import { memo, useCallback } from 'react'; import { useTranslation } from 'react-i18next'; import { PiTrashSimpleBold } from 'react-icons/pi'; @@ -21,6 +22,7 @@ export const LayerDeleteButton = memo(({ layerId }: Props) => { tooltip={t('common.delete')} icon={} onClick={deleteLayer} + onDoubleClick={stopPropagation} // double click expands the layer /> ); }); diff --git a/invokeai/frontend/web/src/features/controlLayers/components/LayerMenu.tsx b/invokeai/frontend/web/src/features/controlLayers/components/LayerMenu.tsx index cd86fb8cd1..e5c8cc0aac 100644 --- a/invokeai/frontend/web/src/features/controlLayers/components/LayerMenu.tsx +++ b/invokeai/frontend/web/src/features/controlLayers/components/LayerMenu.tsx @@ -1,5 +1,6 @@ import { IconButton, Menu, MenuButton, MenuDivider, MenuItem, MenuList } from '@invoke-ai/ui-library'; import { useAppDispatch } from 'app/store/storeHooks'; +import { stopPropagation } from 'common/util/stopPropagation'; import { LayerMenuArrangeActions } from 'features/controlLayers/components/LayerMenuArrangeActions'; import { LayerMenuRGActions } from 'features/controlLayers/components/LayerMenuRGActions'; import { useLayerType } from 'features/controlLayers/hooks/layerStateHooks'; @@ -22,7 +23,13 @@ export const LayerMenu = memo(({ layerId }: Props) => { }, [dispatch, layerId]); return ( - } /> + } + onDoubleClick={stopPropagation} // double click expands the layer + /> {layerType === 'regional_guidance_layer' && ( <> diff --git a/invokeai/frontend/web/src/features/controlLayers/components/LayerTitle.tsx b/invokeai/frontend/web/src/features/controlLayers/components/LayerTitle.tsx index ba3e01f799..ec13ff7bcc 100644 --- a/invokeai/frontend/web/src/features/controlLayers/components/LayerTitle.tsx +++ b/invokeai/frontend/web/src/features/controlLayers/components/LayerTitle.tsx @@ -20,7 +20,7 @@ export const LayerTitle = memo(({ type }: Props) => { }, [t, type]); return ( - + {title} ); diff --git a/invokeai/frontend/web/src/features/controlLayers/components/LayerVisibilityToggle.tsx b/invokeai/frontend/web/src/features/controlLayers/components/LayerVisibilityToggle.tsx index 77fa8cf8e8..d2dab39e36 100644 --- a/invokeai/frontend/web/src/features/controlLayers/components/LayerVisibilityToggle.tsx +++ b/invokeai/frontend/web/src/features/controlLayers/components/LayerVisibilityToggle.tsx @@ -1,5 +1,6 @@ import { IconButton } from '@invoke-ai/ui-library'; import { useAppDispatch } from 'app/store/storeHooks'; +import { stopPropagation } from 'common/util/stopPropagation'; import { useLayerIsVisible } from 'features/controlLayers/hooks/layerStateHooks'; import { layerVisibilityToggled } from 'features/controlLayers/store/controlLayersSlice'; import { memo, useCallback } from 'react'; @@ -27,6 +28,7 @@ export const LayerVisibilityToggle = memo(({ layerId }: Props) => { icon={isVisible ? : undefined} onClick={onClick} colorScheme="base" + onDoubleClick={stopPropagation} // double click expands the layer /> ); }); diff --git a/invokeai/frontend/web/src/features/controlLayers/components/RGLayerColorPicker.tsx b/invokeai/frontend/web/src/features/controlLayers/components/RGLayerColorPicker.tsx index 04c6fbf0a2..e76ab57a51 100644 --- a/invokeai/frontend/web/src/features/controlLayers/components/RGLayerColorPicker.tsx +++ b/invokeai/frontend/web/src/features/controlLayers/components/RGLayerColorPicker.tsx @@ -2,6 +2,7 @@ import { Flex, Popover, PopoverBody, PopoverContent, PopoverTrigger, Tooltip } f import { createMemoizedSelector } from 'app/store/createMemoizedSelector'; import { useAppDispatch, useAppSelector } from 'app/store/storeHooks'; import RgbColorPicker from 'common/components/RgbColorPicker'; +import { stopPropagation } from 'common/util/stopPropagation'; import { rgbColorToString } from 'features/canvas/util/colorToString'; import { isRegionalGuidanceLayer, @@ -51,6 +52,7 @@ export const RGLayerColorPicker = memo(({ layerId }: Props) => { h={8} cursor="pointer" tabIndex={-1} + onDoubleClick={stopPropagation} // double click expands the layer /> diff --git a/invokeai/frontend/web/src/features/controlLayers/components/RGLayerListItem.tsx b/invokeai/frontend/web/src/features/controlLayers/components/RGLayerListItem.tsx index 96acfffa50..7c34158edb 100644 --- a/invokeai/frontend/web/src/features/controlLayers/components/RGLayerListItem.tsx +++ b/invokeai/frontend/web/src/features/controlLayers/components/RGLayerListItem.tsx @@ -1,4 +1,4 @@ -import { Badge, Flex, Spacer } from '@invoke-ai/ui-library'; +import { Badge, Flex, Spacer, useDisclosure } from '@invoke-ai/ui-library'; import { createMemoizedSelector } from 'app/store/createMemoizedSelector'; import { useAppDispatch, useAppSelector } from 'app/store/storeHooks'; import { rgbColorToString } from 'features/canvas/util/colorToString'; @@ -47,26 +47,19 @@ export const RGLayerListItem = memo(({ layerId }: Props) => { ); const { autoNegative, color, hasPositivePrompt, hasNegativePrompt, hasIPAdapters, isSelected } = useAppSelector(selector); - const onClickCapture = useCallback(() => { - // Must be capture so that the layer is selected before deleting/resetting/etc + const { isOpen, onToggle } = useDisclosure(); + const onClick = useCallback(() => { dispatch(layerSelected(layerId)); }, [dispatch, layerId]); return ( - - - + + + {autoNegative === 'invert' && ( - + {t('controlLayers.autoNegative')} )} @@ -75,10 +68,14 @@ export const RGLayerListItem = memo(({ layerId }: Props) => { - {!hasPositivePrompt && !hasNegativePrompt && !hasIPAdapters && } - {hasPositivePrompt && } - {hasNegativePrompt && } - {hasIPAdapters && } + {isOpen && ( + + {!hasPositivePrompt && !hasNegativePrompt && !hasIPAdapters && } + {hasPositivePrompt && } + {hasNegativePrompt && } + {hasIPAdapters && } + + )} ); diff --git a/invokeai/frontend/web/src/features/controlLayers/components/RGLayerSettingsPopover.tsx b/invokeai/frontend/web/src/features/controlLayers/components/RGLayerSettingsPopover.tsx index acb64e16b8..e270748b9b 100644 --- a/invokeai/frontend/web/src/features/controlLayers/components/RGLayerSettingsPopover.tsx +++ b/invokeai/frontend/web/src/features/controlLayers/components/RGLayerSettingsPopover.tsx @@ -9,6 +9,7 @@ import { PopoverContent, PopoverTrigger, } from '@invoke-ai/ui-library'; +import { stopPropagation } from 'common/util/stopPropagation'; import { RGLayerAutoNegativeCheckbox } from 'features/controlLayers/components/RGLayerAutoNegativeCheckbox'; import { memo } from 'react'; import { useTranslation } from 'react-i18next'; @@ -34,6 +35,7 @@ const RGLayerSettingsPopover = ({ layerId }: Props) => { aria-label={t('common.settingsLabel')} size="sm" icon={} + onDoubleClick={stopPropagation} // double click expands the layer /> diff --git a/invokeai/frontend/web/src/features/controlLayers/components/controlAdapterOverrides/ControlAdapterLayerConfig.tsx b/invokeai/frontend/web/src/features/controlLayers/components/controlAdapterOverrides/ControlAdapterLayerConfig.tsx index 2a8fed7a83..29a3502d37 100644 --- a/invokeai/frontend/web/src/features/controlLayers/components/controlAdapterOverrides/ControlAdapterLayerConfig.tsx +++ b/invokeai/frontend/web/src/features/controlLayers/components/controlAdapterOverrides/ControlAdapterLayerConfig.tsx @@ -22,7 +22,7 @@ const ControlAdapterLayerConfig = (props: { id: string }) => { const [isExpanded, toggleIsExpanded] = useToggle(false); return ( - + {' '} From 21cf1004db5c13f42890ed6dd43acc23abc5b2c1 Mon Sep 17 00:00:00 2001 From: psychedelicious <4822129+psychedelicious@users.noreply.github.com> Date: Tue, 30 Apr 2024 19:47:22 +1000 Subject: [PATCH 47/52] fix(ui): layers default to expanded --- .../src/features/controlLayers/components/CALayerListItem.tsx | 2 +- .../src/features/controlLayers/components/IPLayerListItem.tsx | 2 +- .../src/features/controlLayers/components/RGLayerListItem.tsx | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/invokeai/frontend/web/src/features/controlLayers/components/CALayerListItem.tsx b/invokeai/frontend/web/src/features/controlLayers/components/CALayerListItem.tsx index a55c71fad6..440fad05dd 100644 --- a/invokeai/frontend/web/src/features/controlLayers/components/CALayerListItem.tsx +++ b/invokeai/frontend/web/src/features/controlLayers/components/CALayerListItem.tsx @@ -38,7 +38,7 @@ export const CALayerListItem = memo(({ layerId }: Props) => { // Must be capture so that the layer is selected before deleting/resetting/etc dispatch(layerSelected(layerId)); }, [dispatch, layerId]); - const { isOpen, onToggle } = useDisclosure(); + const { isOpen, onToggle } = useDisclosure({ defaultIsOpen: true }); return ( { [layerId] ); const ipAdapterId = useAppSelector(selector); - const { isOpen, onToggle } = useDisclosure(); + const { isOpen, onToggle } = useDisclosure({ defaultIsOpen: true }); return ( diff --git a/invokeai/frontend/web/src/features/controlLayers/components/RGLayerListItem.tsx b/invokeai/frontend/web/src/features/controlLayers/components/RGLayerListItem.tsx index 7c34158edb..f4ff58f5de 100644 --- a/invokeai/frontend/web/src/features/controlLayers/components/RGLayerListItem.tsx +++ b/invokeai/frontend/web/src/features/controlLayers/components/RGLayerListItem.tsx @@ -47,7 +47,7 @@ export const RGLayerListItem = memo(({ layerId }: Props) => { ); const { autoNegative, color, hasPositivePrompt, hasNegativePrompt, hasIPAdapters, isSelected } = useAppSelector(selector); - const { isOpen, onToggle } = useDisclosure(); + const { isOpen, onToggle } = useDisclosure({ defaultIsOpen: true }); const onClick = useCallback(() => { dispatch(layerSelected(layerId)); }, [dispatch, layerId]); From 3f268804938284a1a26042164c66ca82116dda95 Mon Sep 17 00:00:00 2001 From: psychedelicious <4822129+psychedelicious@users.noreply.github.com> Date: Tue, 30 Apr 2024 20:00:47 +1000 Subject: [PATCH 48/52] fix(ui): "Global Settings" -> "Settings" --- .../src/features/ui/components/ParametersPanelTextToImage.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/invokeai/frontend/web/src/features/ui/components/ParametersPanelTextToImage.tsx b/invokeai/frontend/web/src/features/ui/components/ParametersPanelTextToImage.tsx index b809ef999f..2d14a50856 100644 --- a/invokeai/frontend/web/src/features/ui/components/ParametersPanelTextToImage.tsx +++ b/invokeai/frontend/web/src/features/ui/components/ParametersPanelTextToImage.tsx @@ -39,7 +39,7 @@ const ParametersPanelTextToImage = () => { {isSDXL ? : } - {t('parameters.globalSettings')} + {t('common.settingsLabel')} {controlLayersTitle} From e90775731d327726bbb6e45802b226a438e4142c Mon Sep 17 00:00:00 2001 From: psychedelicious <4822129+psychedelicious@users.noreply.github.com> Date: Tue, 30 Apr 2024 20:00:59 +1000 Subject: [PATCH 49/52] fix(ui): layer layout orientation --- .../src/features/controlLayers/components/CALayerListItem.tsx | 2 +- .../src/features/controlLayers/components/IPLayerListItem.tsx | 2 +- .../src/features/controlLayers/components/RGLayerListItem.tsx | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/invokeai/frontend/web/src/features/controlLayers/components/CALayerListItem.tsx b/invokeai/frontend/web/src/features/controlLayers/components/CALayerListItem.tsx index 440fad05dd..f97546c4fe 100644 --- a/invokeai/frontend/web/src/features/controlLayers/components/CALayerListItem.tsx +++ b/invokeai/frontend/web/src/features/controlLayers/components/CALayerListItem.tsx @@ -59,7 +59,7 @@ export const CALayerListItem = memo(({ layerId }: Props) => { {isOpen && ( - + )} diff --git a/invokeai/frontend/web/src/features/controlLayers/components/IPLayerListItem.tsx b/invokeai/frontend/web/src/features/controlLayers/components/IPLayerListItem.tsx index cd6d352c8b..bdc54373a0 100644 --- a/invokeai/frontend/web/src/features/controlLayers/components/IPLayerListItem.tsx +++ b/invokeai/frontend/web/src/features/controlLayers/components/IPLayerListItem.tsx @@ -35,7 +35,7 @@ export const IPLayerListItem = memo(({ layerId }: Props) => { {isOpen && ( - + )} diff --git a/invokeai/frontend/web/src/features/controlLayers/components/RGLayerListItem.tsx b/invokeai/frontend/web/src/features/controlLayers/components/RGLayerListItem.tsx index f4ff58f5de..3c126cabaa 100644 --- a/invokeai/frontend/web/src/features/controlLayers/components/RGLayerListItem.tsx +++ b/invokeai/frontend/web/src/features/controlLayers/components/RGLayerListItem.tsx @@ -69,7 +69,7 @@ export const RGLayerListItem = memo(({ layerId }: Props) => { {isOpen && ( - + {!hasPositivePrompt && !hasNegativePrompt && !hasIPAdapters && } {hasPositivePrompt && } {hasNegativePrompt && } From 7a5399e83c47e410079a0f5e2dbad52fcf2e57c9 Mon Sep 17 00:00:00 2001 From: psychedelicious <4822129+psychedelicious@users.noreply.github.com> Date: Tue, 30 Apr 2024 20:24:51 +1000 Subject: [PATCH 50/52] feat(ui): display message when no layers are added --- .../components/StageComponent.tsx | 10 +++++ .../controlLayers/store/controlLayersSlice.ts | 1 + .../features/controlLayers/util/renderers.ts | 38 +++++++++++++++++++ 3 files changed, 49 insertions(+) diff --git a/invokeai/frontend/web/src/features/controlLayers/components/StageComponent.tsx b/invokeai/frontend/web/src/features/controlLayers/components/StageComponent.tsx index 00368a059f..3b91c793be 100644 --- a/invokeai/frontend/web/src/features/controlLayers/components/StageComponent.tsx +++ b/invokeai/frontend/web/src/features/controlLayers/components/StageComponent.tsx @@ -55,6 +55,7 @@ const useStageRenderer = ( const selectedLayerIdColor = useAppSelector(selectSelectedLayerColor); const selectedLayerType = useAppSelector(selectSelectedLayerType); const layerIds = useMemo(() => state.layers.map((l) => l.id), [state.layers]); + const layerCount = useMemo(() => state.layers.length, [state.layers]); const renderers = useMemo(() => (asPreview ? debouncedRenderers : normalRenderers), [asPreview]); const dpr = useDevicePixelRatio({ round: false }); @@ -200,6 +201,15 @@ const useStageRenderer = ( renderers.arrangeLayers(stage, layerIds); }, [stage, layerIds, renderers]); + useLayoutEffect(() => { + log.trace('Rendering no layers message'); + if (asPreview) { + // The preview should not display the no layers message + return; + } + renderers.renderNoLayersMessage(stage, layerCount, state.size.width, state.size.height); + }, [stage, layerCount, renderers, asPreview, state.size.width, state.size.height]); + useLayoutEffect(() => { Konva.pixelRatio = dpr; }, [dpr]); diff --git a/invokeai/frontend/web/src/features/controlLayers/store/controlLayersSlice.ts b/invokeai/frontend/web/src/features/controlLayers/store/controlLayersSlice.ts index 9d5e32fa23..6d351d4d0d 100644 --- a/invokeai/frontend/web/src/features/controlLayers/store/controlLayersSlice.ts +++ b/invokeai/frontend/web/src/features/controlLayers/store/controlLayersSlice.ts @@ -597,6 +597,7 @@ export const TOOL_PREVIEW_BRUSH_BORDER_OUTER_ID = 'tool_preview_layer.brush_bord export const TOOL_PREVIEW_RECT_ID = 'tool_preview_layer.rect'; export const BACKGROUND_LAYER_ID = 'background_layer'; export const BACKGROUND_RECT_ID = 'background_layer.rect'; +export const NO_LAYERS_MESSAGE_LAYER_ID = 'no_layers_message'; // Names (aka classes) for Konva layers and objects export const CONTROLNET_LAYER_NAME = 'control_adapter_layer'; diff --git a/invokeai/frontend/web/src/features/controlLayers/util/renderers.ts b/invokeai/frontend/web/src/features/controlLayers/util/renderers.ts index da835655cc..b2f04a88c1 100644 --- a/invokeai/frontend/web/src/features/controlLayers/util/renderers.ts +++ b/invokeai/frontend/web/src/features/controlLayers/util/renderers.ts @@ -14,6 +14,7 @@ import { isRegionalGuidanceLayer, isRenderableLayer, LAYER_BBOX_NAME, + NO_LAYERS_MESSAGE_LAYER_ID, regional_guidance_layer_LINE_NAME, regional_guidance_layer_NAME, regional_guidance_layer_OBJECT_GROUP_NAME, @@ -702,11 +703,47 @@ const arrangeLayers = (stage: Konva.Stage, layerIds: string[]): void => { stage.findOne(`#${TOOL_PREVIEW_LAYER_ID}`)?.zIndex(nextZIndex++); }; +const createNoLayersMessageLayer = (stage: Konva.Stage): Konva.Layer => { + const noLayersMessageLayer = new Konva.Layer({ + id: NO_LAYERS_MESSAGE_LAYER_ID, + opacity: 0.7, + listening: false, + }); + const text = new Konva.Text({ + x: 0, + y: 0, + align: 'center', + verticalAlign: 'middle', + text: 'No Layers Added', + fontFamily: '"Inter Variable", sans-serif', + fontStyle: '600', + fill: 'white', + }); + noLayersMessageLayer.add(text); + stage.add(noLayersMessageLayer); + return noLayersMessageLayer; +}; + +const renderNoLayersMessage = (stage: Konva.Stage, layerCount: number, width: number, height: number) => { + const noLayersMessageLayer = + stage.findOne(`#${NO_LAYERS_MESSAGE_LAYER_ID}`) ?? createNoLayersMessageLayer(stage); + if (layerCount === 0) { + noLayersMessageLayer.findOne('Text')?.setAttrs({ + width, + height, + fontSize: 32 / stage.scaleX(), + }); + } else { + noLayersMessageLayer?.destroy(); + } +}; + export const renderers = { renderToolPreview, renderLayers, renderBbox, renderBackground, + renderNoLayersMessage, arrangeLayers, }; @@ -717,6 +754,7 @@ export const debouncedRenderers = { renderLayers: debounce(renderLayers, DEBOUNCE_MS), renderBbox: debounce(renderBbox, DEBOUNCE_MS), renderBackground: debounce(renderBackground, DEBOUNCE_MS), + renderNoLayersMessage: debounce(renderNoLayersMessage, DEBOUNCE_MS), arrangeLayers: debounce(arrangeLayers, DEBOUNCE_MS), }; From 631878b212a077ce877e991874b3f1d1d515e66f Mon Sep 17 00:00:00 2001 From: psychedelicious <4822129+psychedelicious@users.noreply.github.com> Date: Tue, 30 Apr 2024 20:29:01 +1000 Subject: [PATCH 51/52] feat(ui): border radius on canvas --- .../src/features/controlLayers/components/StageComponent.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/invokeai/frontend/web/src/features/controlLayers/components/StageComponent.tsx b/invokeai/frontend/web/src/features/controlLayers/components/StageComponent.tsx index 3b91c793be..ecf1121b41 100644 --- a/invokeai/frontend/web/src/features/controlLayers/components/StageComponent.tsx +++ b/invokeai/frontend/web/src/features/controlLayers/components/StageComponent.tsx @@ -239,7 +239,7 @@ export const StageComponent = memo(({ asPreview = false }: Props) => { return ( - + ); From abb3bb9f7ee9f470b313165fdfd5bc48793385cb Mon Sep 17 00:00:00 2001 From: psychedelicious <4822129+psychedelicious@users.noreply.github.com> Date: Wed, 1 May 2024 06:21:45 +1000 Subject: [PATCH 52/52] Update invokeai_version.py --- invokeai/version/invokeai_version.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/invokeai/version/invokeai_version.py b/invokeai/version/invokeai_version.py index 0c1d77bc3d..7c223b74a7 100644 --- a/invokeai/version/invokeai_version.py +++ b/invokeai/version/invokeai_version.py @@ -1 +1 @@ -__version__ = "4.2.0a3" +__version__ = "4.2.0a4"