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 a1ce52b407..15420bf768 100644
--- a/invokeai/frontend/web/src/app/store/middleware/listenerMiddleware/index.ts
+++ b/invokeai/frontend/web/src/app/store/middleware/listenerMiddleware/index.ts
@@ -53,6 +53,7 @@ import type { AppDispatch, RootState } from 'app/store/store';
import { addArchivedOrDeletedBoardListener } from './listeners/addArchivedOrDeletedBoardListener';
import { addEnqueueRequestedUpscale } from './listeners/enqueueRequestedUpscale';
+import { addActiveStylePresetChanged } from './listeners/activeStylePresetChanged';
export const listenerMiddleware = createListenerMiddleware();
@@ -146,6 +147,7 @@ addAdHocPostProcessingRequestedListener(startAppListening);
// Prompts
addDynamicPromptsListener(startAppListening);
+addActiveStylePresetChanged(startAppListening)
addSetDefaultSettingsListener(startAppListening);
addControlAdapterPreprocessor(startAppListening);
diff --git a/invokeai/frontend/web/src/app/store/middleware/listenerMiddleware/listeners/activeStylePresetChanged.ts b/invokeai/frontend/web/src/app/store/middleware/listenerMiddleware/listeners/activeStylePresetChanged.ts
new file mode 100644
index 0000000000..aa146bcc29
--- /dev/null
+++ b/invokeai/frontend/web/src/app/store/middleware/listenerMiddleware/listeners/activeStylePresetChanged.ts
@@ -0,0 +1,31 @@
+import type { AppStartListening } from 'app/store/middleware/listenerMiddleware';
+import { negativePromptChanged, positivePromptChanged, } from 'features/controlLayers/store/controlLayersSlice';
+import { activeStylePresetChanged, calculatedNegPromptChanged, calculatedPosPromptChanged } from '../../../../../features/stylePresets/store/stylePresetSlice';
+import { isAnyOf } from '@reduxjs/toolkit';
+
+export const addActiveStylePresetChanged = (startAppListening: AppStartListening) => {
+ startAppListening({
+ matcher: isAnyOf(activeStylePresetChanged, positivePromptChanged, negativePromptChanged),
+ effect: async (action, { dispatch, getState }) => {
+ const state = getState();
+
+ const activeStylePreset = state.stylePreset.activeStylePreset;
+ const positivePrompt = state.controlLayers.present.positivePrompt
+ const negativePrompt = state.controlLayers.present.negativePrompt
+
+ if (!activeStylePreset) {
+ return;
+ }
+
+ const { positive_prompt: presetPositivePrompt, negative_prompt: presetNegativePrompt } = activeStylePreset.preset_data;
+
+ const calculatedPosPrompt = presetPositivePrompt.includes('{prompt}') ? presetPositivePrompt.replace('{prompt}', positivePrompt) : `${positivePrompt} ${presetPositivePrompt}`
+
+ const calculatedNegPrompt = presetNegativePrompt.includes('{prompt}') ? presetNegativePrompt.replace('{prompt}', negativePrompt) : `${negativePrompt} ${presetNegativePrompt}`
+
+ dispatch(calculatedPosPromptChanged(calculatedPosPrompt))
+
+ dispatch(calculatedNegPromptChanged(calculatedNegPrompt))
+ },
+ });
+};
diff --git a/invokeai/frontend/web/src/app/store/store.ts b/invokeai/frontend/web/src/app/store/store.ts
index f061d0e59f..70326ea5fd 100644
--- a/invokeai/frontend/web/src/app/store/store.ts
+++ b/invokeai/frontend/web/src/app/store/store.ts
@@ -28,7 +28,7 @@ import { generationPersistConfig, generationSlice } from 'features/parameters/st
import { upscalePersistConfig, upscaleSlice } from 'features/parameters/store/upscaleSlice';
import { queueSlice } from 'features/queue/store/queueSlice';
import { sdxlPersistConfig, sdxlSlice } from 'features/sdxl/store/sdxlSlice';
-import { stylePresetModalSlice } from 'features/stylePresets/store/slice';
+import { stylePresetModalSlice } from 'features/stylePresets/store/stylePresetModalSlice';
import { configSlice } from 'features/system/store/configSlice';
import { systemPersistConfig, systemSlice } from 'features/system/store/systemSlice';
import { uiPersistConfig, uiSlice } from 'features/ui/store/uiSlice';
@@ -47,6 +47,7 @@ import { actionSanitizer } from './middleware/devtools/actionSanitizer';
import { actionsDenylist } from './middleware/devtools/actionsDenylist';
import { stateSanitizer } from './middleware/devtools/stateSanitizer';
import { listenerMiddleware } from './middleware/listenerMiddleware';
+import { stylePresetSlice } from '../../features/stylePresets/store/stylePresetSlice';
const allReducers = {
[canvasSlice.name]: canvasSlice.reducer,
@@ -70,7 +71,8 @@ const allReducers = {
[workflowSettingsSlice.name]: workflowSettingsSlice.reducer,
[api.reducerPath]: api.reducer,
[upscaleSlice.name]: upscaleSlice.reducer,
- [stylePresetModalSlice.name]: stylePresetModalSlice.reducer
+ [stylePresetModalSlice.name]: stylePresetModalSlice.reducer,
+ [stylePresetSlice.name]: stylePresetSlice.reducer
};
const rootReducer = combineReducers(allReducers);
diff --git a/invokeai/frontend/web/src/features/parameters/components/Prompts/Prompts.tsx b/invokeai/frontend/web/src/features/parameters/components/Prompts/Prompts.tsx
index 39746c9ba6..aefe15efe7 100644
--- a/invokeai/frontend/web/src/features/parameters/components/Prompts/Prompts.tsx
+++ b/invokeai/frontend/web/src/features/parameters/components/Prompts/Prompts.tsx
@@ -19,12 +19,16 @@ const concatPromptsSelector = createSelector(
export const Prompts = memo(() => {
const shouldConcatPrompts = useAppSelector(concatPromptsSelector);
+ const calculatedPosPrompt = useAppSelector((s) => s.stylePreset.calculatedPosPrompt);
+ const calculatedNegPrompt = useAppSelector((s) => s.stylePreset.calculatedNegPrompt);
return (
+ {calculatedPosPrompt}
{!shouldConcatPrompts && }
+ {calculatedNegPrompt}
{!shouldConcatPrompts && }
);
diff --git a/invokeai/frontend/web/src/features/stylePresets/components/StylePresetForm.tsx b/invokeai/frontend/web/src/features/stylePresets/components/StylePresetForm.tsx
index 201c1e27cc..c8faf9fc2e 100644
--- a/invokeai/frontend/web/src/features/stylePresets/components/StylePresetForm.tsx
+++ b/invokeai/frontend/web/src/features/stylePresets/components/StylePresetForm.tsx
@@ -1,15 +1,11 @@
import { Button, Flex, FormControl, FormLabel, Input, Textarea } from '@invoke-ai/ui-library';
import { useAppDispatch } from 'app/store/storeHooks';
-import { isModalOpenChanged,updatingStylePresetChanged } from 'features/stylePresets/store/slice';
+import { isModalOpenChanged, updatingStylePresetChanged } from 'features/stylePresets/store/stylePresetModalSlice';
import { toast } from 'features/toast/toast';
-import type { ChangeEventHandler} from 'react';
+import type { ChangeEventHandler } from 'react';
import { useCallback, useEffect, useState } from 'react';
-import type {
- StylePresetRecordDTO} from 'services/api/endpoints/stylePresets';
-import {
- useCreateStylePresetMutation,
- useUpdateStylePresetMutation,
-} from 'services/api/endpoints/stylePresets';
+import type { StylePresetRecordDTO } from 'services/api/endpoints/stylePresets';
+import { useCreateStylePresetMutation, useUpdateStylePresetMutation } from 'services/api/endpoints/stylePresets';
export const StylePresetForm = ({ updatingPreset }: { updatingPreset: StylePresetRecordDTO | null }) => {
const [createStylePreset] = useCreateStylePresetMutation();
diff --git a/invokeai/frontend/web/src/features/stylePresets/components/StylePresetListItem.tsx b/invokeai/frontend/web/src/features/stylePresets/components/StylePresetListItem.tsx
index 86a5b78b04..321c5a09bb 100644
--- a/invokeai/frontend/web/src/features/stylePresets/components/StylePresetListItem.tsx
+++ b/invokeai/frontend/web/src/features/stylePresets/components/StylePresetListItem.tsx
@@ -1,9 +1,10 @@
import { Button, Flex, Text } from '@invoke-ai/ui-library';
import { useAppDispatch } from 'app/store/storeHooks';
-import { isModalOpenChanged, updatingStylePresetChanged } from 'features/stylePresets/store/slice';
+import { isModalOpenChanged, updatingStylePresetChanged } from 'features/stylePresets/store/stylePresetModalSlice';
import { useCallback } from 'react';
-import type { StylePresetRecordDTO} from 'services/api/endpoints/stylePresets';
+import type { StylePresetRecordDTO } from 'services/api/endpoints/stylePresets';
import { useDeleteStylePresetMutation } from 'services/api/endpoints/stylePresets';
+import { activeStylePresetChanged, isMenuOpenChanged } from '../store/stylePresetSlice';
export const StylePresetListItem = ({ preset }: { preset: StylePresetRecordDTO }) => {
const dispatch = useAppDispatch();
@@ -14,6 +15,11 @@ export const StylePresetListItem = ({ preset }: { preset: StylePresetRecordDTO }
dispatch(isModalOpenChanged(true));
}, [dispatch, preset]);
+ const handleClickApply = useCallback(() => {
+ dispatch(activeStylePresetChanged(preset));
+ dispatch(isMenuOpenChanged(false));
+ }, [dispatch, preset]);
+
const handleDeletePreset = useCallback(async () => {
try {
await deleteStylePreset(preset.id);
@@ -39,6 +45,7 @@ export const StylePresetListItem = ({ preset }: { preset: StylePresetRecordDTO }
+
>
diff --git a/invokeai/frontend/web/src/features/stylePresets/components/StylePresetMenu.tsx b/invokeai/frontend/web/src/features/stylePresets/components/StylePresetMenu.tsx
index e4fd240820..374aa37be3 100644
--- a/invokeai/frontend/web/src/features/stylePresets/components/StylePresetMenu.tsx
+++ b/invokeai/frontend/web/src/features/stylePresets/components/StylePresetMenu.tsx
@@ -1,6 +1,6 @@
import { Button, Flex, Text } from '@invoke-ai/ui-library';
import { useAppDispatch } from 'app/store/storeHooks';
-import { isModalOpenChanged, updatingStylePresetChanged } from 'features/stylePresets/store/slice';
+import { isModalOpenChanged, updatingStylePresetChanged } from 'features/stylePresets/store/stylePresetModalSlice';
import { useCallback } from 'react';
import { useListStylePresetsQuery } from 'services/api/endpoints/stylePresets';
diff --git a/invokeai/frontend/web/src/features/stylePresets/components/StylePresetMenuTrigger.tsx b/invokeai/frontend/web/src/features/stylePresets/components/StylePresetMenuTrigger.tsx
index 0b5fe7b9be..889695d5ee 100644
--- a/invokeai/frontend/web/src/features/stylePresets/components/StylePresetMenuTrigger.tsx
+++ b/invokeai/frontend/web/src/features/stylePresets/components/StylePresetMenuTrigger.tsx
@@ -1,18 +1,28 @@
-import {
- Button,
- Popover,
- PopoverBody,
- PopoverContent,
- PopoverTrigger,
-} from '@invoke-ai/ui-library';
+import { Button, Popover, PopoverBody, PopoverContent, PopoverTrigger } from '@invoke-ai/ui-library';
import { StylePresetMenu } from './StylePresetMenu';
+import { useAppDispatch, useAppSelector } from '../../../app/store/storeHooks';
+import { useCallback } from 'react';
+import { isMenuOpenChanged } from '../store/stylePresetSlice';
export const StylePresetMenuTrigger = () => {
+ const isMenuOpen = useAppSelector((s) => s.stylePreset.isMenuOpen);
+ const dispatch = useAppDispatch();
+
+ const handleClose = useCallback(() => {
+ dispatch(isMenuOpenChanged(false));
+ }, [dispatch]);
+
+ const handleToggle = useCallback(() => {
+ dispatch(isMenuOpenChanged(!isMenuOpen));
+ }, [dispatch, isMenuOpen]);
+
return (
-
+
-
+
diff --git a/invokeai/frontend/web/src/features/stylePresets/components/StylePresetModal.tsx b/invokeai/frontend/web/src/features/stylePresets/components/StylePresetModal.tsx
index a783d93db2..02be01e31f 100644
--- a/invokeai/frontend/web/src/features/stylePresets/components/StylePresetModal.tsx
+++ b/invokeai/frontend/web/src/features/stylePresets/components/StylePresetModal.tsx
@@ -8,7 +8,7 @@ import {
ModalOverlay,
} from '@invoke-ai/ui-library';
import { useAppDispatch, useAppSelector } from 'app/store/storeHooks';
-import { isModalOpenChanged, updatingStylePresetChanged } from 'features/stylePresets/store/slice';
+import { isModalOpenChanged, updatingStylePresetChanged } from 'features/stylePresets/store/stylePresetModalSlice';
import { useCallback, useMemo } from 'react';
import { StylePresetForm } from './StylePresetForm';
diff --git a/invokeai/frontend/web/src/features/stylePresets/store/slice.ts b/invokeai/frontend/web/src/features/stylePresets/store/stylePresetModalSlice.ts
similarity index 89%
rename from invokeai/frontend/web/src/features/stylePresets/store/slice.ts
rename to invokeai/frontend/web/src/features/stylePresets/store/stylePresetModalSlice.ts
index 4ba8b881c3..b0dc894037 100644
--- a/invokeai/frontend/web/src/features/stylePresets/store/slice.ts
+++ b/invokeai/frontend/web/src/features/stylePresets/store/stylePresetModalSlice.ts
@@ -3,10 +3,10 @@ import { createSlice } from '@reduxjs/toolkit';
import type { RootState } from 'app/store/store';
import type { StylePresetRecordDTO } from 'services/api/endpoints/stylePresets';
-import type { StylePresetState } from './types';
+import type { StylePresetModalState } from './types';
-export const initialState: StylePresetState = {
+export const initialState: StylePresetModalState = {
isModalOpen: false,
updatingStylePreset: null,
};
diff --git a/invokeai/frontend/web/src/features/stylePresets/store/stylePresetSlice.ts b/invokeai/frontend/web/src/features/stylePresets/store/stylePresetSlice.ts
new file mode 100644
index 0000000000..39cfd30487
--- /dev/null
+++ b/invokeai/frontend/web/src/features/stylePresets/store/stylePresetSlice.ts
@@ -0,0 +1,38 @@
+import type { PayloadAction } from '@reduxjs/toolkit';
+import { createSlice } from '@reduxjs/toolkit';
+import type { RootState } from 'app/store/store';
+import type { StylePresetRecordDTO } from 'services/api/endpoints/stylePresets';
+
+import type { StylePresetState } from './types';
+
+
+export const initialState: StylePresetState = {
+ isMenuOpen: false,
+ activeStylePreset: null,
+ calculatedPosPrompt: undefined,
+ calculatedNegPrompt: undefined
+};
+
+
+export const stylePresetSlice = createSlice({
+ name: 'stylePreset',
+ initialState: initialState,
+ reducers: {
+ isMenuOpenChanged: (state, action: PayloadAction) => {
+ state.isMenuOpen = action.payload;
+ },
+ activeStylePresetChanged: (state, action: PayloadAction) => {
+ state.activeStylePreset = action.payload;
+ },
+ calculatedPosPromptChanged: (state, action: PayloadAction) => {
+ state.calculatedPosPrompt = action.payload;
+ },
+ calculatedNegPromptChanged: (state, action: PayloadAction) => {
+ state.calculatedNegPrompt = action.payload;
+ },
+ },
+});
+
+export const { isMenuOpenChanged, activeStylePresetChanged, calculatedPosPromptChanged, calculatedNegPromptChanged } = stylePresetSlice.actions;
+
+export const selectStylePresetSlice = (state: RootState) => state.stylePreset;
diff --git a/invokeai/frontend/web/src/features/stylePresets/store/types.ts b/invokeai/frontend/web/src/features/stylePresets/store/types.ts
index 0ea2f8e71c..45af35a4b5 100644
--- a/invokeai/frontend/web/src/features/stylePresets/store/types.ts
+++ b/invokeai/frontend/web/src/features/stylePresets/store/types.ts
@@ -1,8 +1,14 @@
import type { StylePresetRecordDTO } from "services/api/endpoints/stylePresets";
-export type StylePresetState = {
+export type StylePresetModalState = {
isModalOpen: boolean;
updatingStylePreset: StylePresetRecordDTO | null;
};
+export type StylePresetState = {
+ isMenuOpen: boolean;
+ activeStylePreset: StylePresetRecordDTO | null;
+ calculatedPosPrompt?: string
+ calculatedNegPrompt?: string
+}