diff --git a/invokeai/frontend/web/src/app/components/App.tsx b/invokeai/frontend/web/src/app/components/App.tsx index 760eddbee8..07b959e684 100644 --- a/invokeai/frontend/web/src/app/components/App.tsx +++ b/invokeai/frontend/web/src/app/components/App.tsx @@ -13,6 +13,7 @@ import ChangeBoardModal from 'features/changeBoardModal/components/ChangeBoardMo import DeleteImageModal from 'features/deleteImageModal/components/DeleteImageModal'; import { DynamicPromptsModal } from 'features/dynamicPrompts/components/DynamicPromptsPreviewModal'; import { useStarterModelsToast } from 'features/modelManagerV2/hooks/useStarterModelsToast'; +import { StylePresetModal } from 'features/stylePresets/components/StylePresetModal'; import { configChanged } from 'features/system/store/configSlice'; import { languageSelector } from 'features/system/store/systemSelectors'; import InvokeTabs from 'features/ui/components/InvokeTabs'; @@ -104,6 +105,7 @@ const App = ({ config = DEFAULT_CONFIG, selectedImage, destination }: Props) => + ); diff --git a/invokeai/frontend/web/src/app/store/store.ts b/invokeai/frontend/web/src/app/store/store.ts index 6ae2011355..f061d0e59f 100644 --- a/invokeai/frontend/web/src/app/store/store.ts +++ b/invokeai/frontend/web/src/app/store/store.ts @@ -28,6 +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 { configSlice } from 'features/system/store/configSlice'; import { systemPersistConfig, systemSlice } from 'features/system/store/systemSlice'; import { uiPersistConfig, uiSlice } from 'features/ui/store/uiSlice'; @@ -69,6 +70,7 @@ const allReducers = { [workflowSettingsSlice.name]: workflowSettingsSlice.reducer, [api.reducerPath]: api.reducer, [upscaleSlice.name]: upscaleSlice.reducer, + [stylePresetModalSlice.name]: stylePresetModalSlice.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 c41f929ae9..39746c9ba6 100644 --- a/invokeai/frontend/web/src/features/parameters/components/Prompts/Prompts.tsx +++ b/invokeai/frontend/web/src/features/parameters/components/Prompts/Prompts.tsx @@ -7,6 +7,7 @@ import { ParamPositivePrompt } from 'features/parameters/components/Core/ParamPo import { selectGenerationSlice } from 'features/parameters/store/generationSlice'; import { ParamSDXLNegativeStylePrompt } from 'features/sdxl/components/SDXLPrompts/ParamSDXLNegativeStylePrompt'; import { ParamSDXLPositiveStylePrompt } from 'features/sdxl/components/SDXLPrompts/ParamSDXLPositiveStylePrompt'; +import { StylePresetMenuTrigger } from 'features/stylePresets/components/StylePresetMenuTrigger'; import { memo } from 'react'; const concatPromptsSelector = createSelector( @@ -20,6 +21,7 @@ export const Prompts = memo(() => { const shouldConcatPrompts = useAppSelector(concatPromptsSelector); return ( + {!shouldConcatPrompts && } diff --git a/invokeai/frontend/web/src/features/stylePresets/components/StylePresetForm.tsx b/invokeai/frontend/web/src/features/stylePresets/components/StylePresetForm.tsx new file mode 100644 index 0000000000..201c1e27cc --- /dev/null +++ b/invokeai/frontend/web/src/features/stylePresets/components/StylePresetForm.tsx @@ -0,0 +1,88 @@ +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 { toast } from 'features/toast/toast'; +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'; + +export const StylePresetForm = ({ updatingPreset }: { updatingPreset: StylePresetRecordDTO | null }) => { + const [createStylePreset] = useCreateStylePresetMutation(); + const [updateStylePreset] = useUpdateStylePresetMutation(); + const dispatch = useAppDispatch(); + + const [name, setName] = useState(updatingPreset ? updatingPreset.name : ''); + const [posPrompt, setPosPrompt] = useState(updatingPreset ? updatingPreset.preset_data.positive_prompt : ''); + const [negPrompt, setNegPrompt] = useState(updatingPreset ? updatingPreset.preset_data.negative_prompt : ''); + + const handleChangeName = useCallback>((e) => { + setName(e.target.value); + }, []); + + const handleChangePosPrompt = useCallback>((e) => { + setPosPrompt(e.target.value); + }, []); + + const handleChangeNegPrompt = useCallback>((e) => { + setNegPrompt(e.target.value); + }, []); + + useEffect(() => { + if (updatingPreset) { + setName(updatingPreset.name); + setPosPrompt(updatingPreset.preset_data.positive_prompt); + setNegPrompt(updatingPreset.preset_data.negative_prompt); + } else { + setName(''); + setPosPrompt(''); + setNegPrompt(''); + } + }, [updatingPreset]); + + const handleClickSave = useCallback(async () => { + try { + if (updatingPreset) { + await updateStylePreset({ + id: updatingPreset.id, + changes: { name, preset_data: { positive_prompt: posPrompt, negative_prompt: negPrompt } }, + }).unwrap(); + } else { + await createStylePreset({ + name: name, + preset_data: { positive_prompt: posPrompt, negative_prompt: negPrompt }, + }).unwrap(); + } + } catch (error) { + toast({ + status: 'error', + title: 'Failed to save style preset', + }); + } + + dispatch(updatingStylePresetChanged(null)); + dispatch(isModalOpenChanged(false)); + }, [dispatch, updatingPreset, name, posPrompt, negPrompt, updateStylePreset, createStylePreset]); + + return ( + + + Name + + + + Positive Prompt +