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 03a042a6cf..dd3f40da85 100644 --- a/invokeai/frontend/web/src/features/nodes/util/graph/addRegionalPromptsToGraph.ts +++ b/invokeai/frontend/web/src/features/nodes/util/graph/addRegionalPromptsToGraph.ts @@ -22,7 +22,6 @@ export const addRegionalPromptsToGraph = async (state: RootState, graph: NonNull const { dispatch } = getStore(); // TODO: Handle non-SDXL // const isSDXL = state.generation.model?.base === 'sdxl'; - const { autoNegative } = state.regionalPrompts.present; const layers = state.regionalPrompts.present.layers .filter((l) => l.kind === 'promptRegionLayer') // We only want the prompt region layers .filter((l) => l.isVisible); // Only visible layers are rendered on the canvas @@ -205,7 +204,7 @@ export const addRegionalPromptsToGraph = async (state: RootState, graph: NonNull } // If we are using the "invert" auto-negative setting, we need to add an additional negative conditioning node - if (autoNegative === 'invert') { + if (layer.autoNegative === 'invert') { // We re-use the mask image, but invert it when converting to tensor // TODO: Probably faster to invert the tensor from the earlier mask rather than read the mask image and convert... const invertedMaskToTensorNode: S['AlphaMaskToTensorInvocation'] = { diff --git a/invokeai/frontend/web/src/features/regionalPrompts/components/LayerAutoNegativeCombobox.tsx b/invokeai/frontend/web/src/features/regionalPrompts/components/LayerAutoNegativeCombobox.tsx new file mode 100644 index 0000000000..0f29b41287 --- /dev/null +++ b/invokeai/frontend/web/src/features/regionalPrompts/components/LayerAutoNegativeCombobox.tsx @@ -0,0 +1,62 @@ +import type { ComboboxOnChange, ComboboxOption } from '@invoke-ai/ui-library'; +import { Combobox, FormControl, FormLabel } from '@invoke-ai/ui-library'; +import { createSelector } from '@reduxjs/toolkit'; +import { useAppDispatch, useAppSelector } from 'app/store/storeHooks'; +import { isParameterAutoNegative } from 'features/parameters/types/parameterSchemas'; +import { + layerAutoNegativeChanged, + selectRegionalPromptsSlice, +} from 'features/regionalPrompts/store/regionalPromptsSlice'; +import { memo, useCallback, useMemo } from 'react'; +import { useTranslation } from 'react-i18next'; +import { assert } from 'tsafe'; + +const options: ComboboxOption[] = [ + { label: 'Off', value: 'off' }, + { label: 'Invert', value: 'invert' }, +]; + +type Props = { + layerId: string; +}; + +const useAutoNegative = (layerId: string) => { + const selectAutoNegative = useMemo( + () => + createSelector(selectRegionalPromptsSlice, (regionalPrompts) => { + const layer = regionalPrompts.present.layers.find((l) => l.id === layerId); + assert(layer, `Layer ${layerId} not found`); + return layer.autoNegative; + }), + [layerId] + ); + const autoNegative = useAppSelector(selectAutoNegative); + return autoNegative; +}; + +const AutoNegativeCombobox = ({ layerId }: Props) => { + const dispatch = useAppDispatch(); + const { t } = useTranslation(); + const autoNegative = useAutoNegative(layerId); + + const onChange = useCallback( + (v) => { + if (!isParameterAutoNegative(v?.value)) { + return; + } + dispatch(layerAutoNegativeChanged({ layerId, autoNegative: v.value })); + }, + [dispatch, layerId] + ); + + const value = useMemo(() => options.find((o) => o.value === autoNegative), [autoNegative]); + + return ( + + AutoNegative + + + ); +}; + +export default memo(AutoNegativeCombobox); diff --git a/invokeai/frontend/web/src/features/regionalPrompts/components/LayerListItem.tsx b/invokeai/frontend/web/src/features/regionalPrompts/components/LayerListItem.tsx index 0d84885010..e9ae02bd34 100644 --- a/invokeai/frontend/web/src/features/regionalPrompts/components/LayerListItem.tsx +++ b/invokeai/frontend/web/src/features/regionalPrompts/components/LayerListItem.tsx @@ -1,6 +1,7 @@ -import { Flex, Spacer, Text } from '@invoke-ai/ui-library'; +import { Flex, Spacer } from '@invoke-ai/ui-library'; import { useAppDispatch, useAppSelector } from 'app/store/storeHooks'; import { rgbaColorToString } from 'features/canvas/util/colorToString'; +import LayerAutoNegativeCombobox from 'features/regionalPrompts/components/LayerAutoNegativeCombobox'; import { LayerColorPicker } from 'features/regionalPrompts/components/LayerColorPicker'; import { LayerMenu } from 'features/regionalPrompts/components/LayerMenu'; import { LayerVisibilityToggle } from 'features/regionalPrompts/components/LayerVisibilityToggle'; @@ -8,7 +9,6 @@ import { RegionalPromptsNegativePrompt } from 'features/regionalPrompts/componen import { RegionalPromptsPositivePrompt } from 'features/regionalPrompts/components/RegionalPromptsPositivePrompt'; import { layerSelected } from 'features/regionalPrompts/store/regionalPromptsSlice'; import { memo, useCallback } from 'react'; -import { useTranslation } from 'react-i18next'; type Props = { id: string; @@ -16,7 +16,6 @@ type Props = { export const LayerListItem = memo(({ id }: Props) => { const dispatch = useAppDispatch(); - const { t } = useTranslation(); const selectedLayer = useAppSelector((s) => s.regionalPrompts.present.selectedLayer); const color = useAppSelector((s) => { const color = s.regionalPrompts.present.layers.find((l) => l.id === id)?.color; @@ -36,11 +35,7 @@ export const LayerListItem = memo(({ id }: Props) => { - {selectedLayer === id && ( - - {t('common.selected')} - - )} + diff --git a/invokeai/frontend/web/src/features/regionalPrompts/components/NegativeModeCombobox.tsx b/invokeai/frontend/web/src/features/regionalPrompts/components/NegativeModeCombobox.tsx deleted file mode 100644 index 8c818ff930..0000000000 --- a/invokeai/frontend/web/src/features/regionalPrompts/components/NegativeModeCombobox.tsx +++ /dev/null @@ -1,39 +0,0 @@ -import type { ComboboxOnChange, ComboboxOption } from '@invoke-ai/ui-library'; -import { Combobox, FormControl, FormLabel } from '@invoke-ai/ui-library'; -import { useAppDispatch, useAppSelector } from 'app/store/storeHooks'; -import { isParameterAutoNegative } from 'features/parameters/types/parameterSchemas'; -import { autoNegativeChanged } from 'features/regionalPrompts/store/regionalPromptsSlice'; -import { memo, useCallback, useMemo } from 'react'; -import { useTranslation } from 'react-i18next'; - -const options: ComboboxOption[] = [ - { label: 'Off', value: 'off' }, - { label: 'Invert', value: 'invert' }, -]; - -const AutoNegativeCombobox = () => { - const dispatch = useAppDispatch(); - const { t } = useTranslation(); - const autoNegative = useAppSelector((s) => s.regionalPrompts.present.autoNegative); - - const onChange = useCallback( - (v) => { - if (!isParameterAutoNegative(v?.value)) { - return; - } - dispatch(autoNegativeChanged(v.value)); - }, - [dispatch] - ); - - const value = useMemo(() => options.find((o) => o.value === autoNegative), [autoNegative]); - - return ( - - Negative Mode - - - ); -}; - -export default memo(AutoNegativeCombobox); diff --git a/invokeai/frontend/web/src/features/regionalPrompts/components/RegionalPromptsEditor.tsx b/invokeai/frontend/web/src/features/regionalPrompts/components/RegionalPromptsEditor.tsx index 26a10c4526..02e10f6b8c 100644 --- a/invokeai/frontend/web/src/features/regionalPrompts/components/RegionalPromptsEditor.tsx +++ b/invokeai/frontend/web/src/features/regionalPrompts/components/RegionalPromptsEditor.tsx @@ -6,7 +6,6 @@ import { AddLayerButton } from 'features/regionalPrompts/components/AddLayerButt import { BrushSize } from 'features/regionalPrompts/components/BrushSize'; import { DeleteAllLayersButton } from 'features/regionalPrompts/components/DeleteAllLayersButton'; import { LayerListItem } from 'features/regionalPrompts/components/LayerListItem'; -import AutoNegativeCombobox from 'features/regionalPrompts/components/NegativeModeCombobox'; import { PromptLayerOpacity } from 'features/regionalPrompts/components/PromptLayerOpacity'; import { StageComponent } from 'features/regionalPrompts/components/StageComponent'; import { ToolChooser } from 'features/regionalPrompts/components/ToolChooser'; @@ -39,7 +38,6 @@ export const RegionalPromptsEditor = memo(() => { - {layerIdsReversed.map((id) => ( ))} diff --git a/invokeai/frontend/web/src/features/regionalPrompts/store/regionalPromptsSlice.ts b/invokeai/frontend/web/src/features/regionalPrompts/store/regionalPromptsSlice.ts index ad487d83e8..5612d093e3 100644 --- a/invokeai/frontend/web/src/features/regionalPrompts/store/regionalPromptsSlice.ts +++ b/invokeai/frontend/web/src/features/regionalPrompts/store/regionalPromptsSlice.ts @@ -56,6 +56,7 @@ type PromptRegionLayer = LayerBase & { positivePrompt: string; negativePrompt: string; color: RgbColor; + autoNegative: ParameterAutoNegative; }; export type Layer = PromptRegionLayer; @@ -67,7 +68,6 @@ type RegionalPromptsState = { layers: PromptRegionLayer[]; brushSize: number; promptLayerOpacity: number; - autoNegative: ParameterAutoNegative; }; export const initialRegionalPromptsState: RegionalPromptsState = { @@ -77,7 +77,6 @@ export const initialRegionalPromptsState: RegionalPromptsState = { brushSize: 40, layers: [], promptLayerOpacity: 0.5, // This currently doesn't work - autoNegative: 'off', }; const isLine = (obj: LayerObject): obj is LineObject => obj.kind === 'line'; @@ -99,6 +98,7 @@ export const regionalPromptsSlice = createSlice({ color: action.meta.color, x: 0, y: 0, + autoNegative: 'off', }; state.layers.push(layer); state.selectedLayer = layer.id; @@ -233,8 +233,16 @@ export const regionalPromptsSlice = createSlice({ promptLayerOpacityChanged: (state, action: PayloadAction) => { state.promptLayerOpacity = action.payload; }, - autoNegativeChanged: (state, action: PayloadAction) => { - state.autoNegative = action.payload; + layerAutoNegativeChanged: ( + state, + action: PayloadAction<{ layerId: string; autoNegative: ParameterAutoNegative }> + ) => { + const { layerId, autoNegative } = action.payload; + const layer = state.layers.find((l) => l.id === layerId); + if (!layer || layer.kind !== 'promptRegionLayer') { + return; + } + layer.autoNegative = autoNegative; }, lineFinished: (state) => { console.log('lineFinished'); @@ -288,7 +296,7 @@ export const { layerBboxChanged, promptLayerOpacityChanged, allLayersDeleted, - autoNegativeChanged, + layerAutoNegativeChanged, } = regionalPromptsSlice.actions; export const selectRegionalPromptsSlice = (state: RootState) => state.regionalPrompts;