mirror of
https://github.com/invoke-ai/InvokeAI
synced 2024-08-30 20:32:17 +00:00
(ui) the most basic crud ui: view list of presets, create a new preset, edit/delete existing presets
This commit is contained in:
parent
af9110e964
commit
fd7a635777
@ -13,6 +13,7 @@ import ChangeBoardModal from 'features/changeBoardModal/components/ChangeBoardMo
|
|||||||
import DeleteImageModal from 'features/deleteImageModal/components/DeleteImageModal';
|
import DeleteImageModal from 'features/deleteImageModal/components/DeleteImageModal';
|
||||||
import { DynamicPromptsModal } from 'features/dynamicPrompts/components/DynamicPromptsPreviewModal';
|
import { DynamicPromptsModal } from 'features/dynamicPrompts/components/DynamicPromptsPreviewModal';
|
||||||
import { useStarterModelsToast } from 'features/modelManagerV2/hooks/useStarterModelsToast';
|
import { useStarterModelsToast } from 'features/modelManagerV2/hooks/useStarterModelsToast';
|
||||||
|
import { StylePresetModal } from 'features/stylePresets/components/StylePresetModal';
|
||||||
import { configChanged } from 'features/system/store/configSlice';
|
import { configChanged } from 'features/system/store/configSlice';
|
||||||
import { languageSelector } from 'features/system/store/systemSelectors';
|
import { languageSelector } from 'features/system/store/systemSelectors';
|
||||||
import InvokeTabs from 'features/ui/components/InvokeTabs';
|
import InvokeTabs from 'features/ui/components/InvokeTabs';
|
||||||
@ -104,6 +105,7 @@ const App = ({ config = DEFAULT_CONFIG, selectedImage, destination }: Props) =>
|
|||||||
<DeleteImageModal />
|
<DeleteImageModal />
|
||||||
<ChangeBoardModal />
|
<ChangeBoardModal />
|
||||||
<DynamicPromptsModal />
|
<DynamicPromptsModal />
|
||||||
|
<StylePresetModal />
|
||||||
<PreselectedImage selectedImage={selectedImage} />
|
<PreselectedImage selectedImage={selectedImage} />
|
||||||
</ErrorBoundary>
|
</ErrorBoundary>
|
||||||
);
|
);
|
||||||
|
@ -28,6 +28,7 @@ import { generationPersistConfig, generationSlice } from 'features/parameters/st
|
|||||||
import { upscalePersistConfig, upscaleSlice } from 'features/parameters/store/upscaleSlice';
|
import { upscalePersistConfig, upscaleSlice } from 'features/parameters/store/upscaleSlice';
|
||||||
import { queueSlice } from 'features/queue/store/queueSlice';
|
import { queueSlice } from 'features/queue/store/queueSlice';
|
||||||
import { sdxlPersistConfig, sdxlSlice } from 'features/sdxl/store/sdxlSlice';
|
import { sdxlPersistConfig, sdxlSlice } from 'features/sdxl/store/sdxlSlice';
|
||||||
|
import { stylePresetModalSlice } from 'features/stylePresets/store/slice';
|
||||||
import { configSlice } from 'features/system/store/configSlice';
|
import { configSlice } from 'features/system/store/configSlice';
|
||||||
import { systemPersistConfig, systemSlice } from 'features/system/store/systemSlice';
|
import { systemPersistConfig, systemSlice } from 'features/system/store/systemSlice';
|
||||||
import { uiPersistConfig, uiSlice } from 'features/ui/store/uiSlice';
|
import { uiPersistConfig, uiSlice } from 'features/ui/store/uiSlice';
|
||||||
@ -69,6 +70,7 @@ const allReducers = {
|
|||||||
[workflowSettingsSlice.name]: workflowSettingsSlice.reducer,
|
[workflowSettingsSlice.name]: workflowSettingsSlice.reducer,
|
||||||
[api.reducerPath]: api.reducer,
|
[api.reducerPath]: api.reducer,
|
||||||
[upscaleSlice.name]: upscaleSlice.reducer,
|
[upscaleSlice.name]: upscaleSlice.reducer,
|
||||||
|
[stylePresetModalSlice.name]: stylePresetModalSlice.reducer
|
||||||
};
|
};
|
||||||
|
|
||||||
const rootReducer = combineReducers(allReducers);
|
const rootReducer = combineReducers(allReducers);
|
||||||
|
@ -7,6 +7,7 @@ import { ParamPositivePrompt } from 'features/parameters/components/Core/ParamPo
|
|||||||
import { selectGenerationSlice } from 'features/parameters/store/generationSlice';
|
import { selectGenerationSlice } from 'features/parameters/store/generationSlice';
|
||||||
import { ParamSDXLNegativeStylePrompt } from 'features/sdxl/components/SDXLPrompts/ParamSDXLNegativeStylePrompt';
|
import { ParamSDXLNegativeStylePrompt } from 'features/sdxl/components/SDXLPrompts/ParamSDXLNegativeStylePrompt';
|
||||||
import { ParamSDXLPositiveStylePrompt } from 'features/sdxl/components/SDXLPrompts/ParamSDXLPositiveStylePrompt';
|
import { ParamSDXLPositiveStylePrompt } from 'features/sdxl/components/SDXLPrompts/ParamSDXLPositiveStylePrompt';
|
||||||
|
import { StylePresetMenuTrigger } from 'features/stylePresets/components/StylePresetMenuTrigger';
|
||||||
import { memo } from 'react';
|
import { memo } from 'react';
|
||||||
|
|
||||||
const concatPromptsSelector = createSelector(
|
const concatPromptsSelector = createSelector(
|
||||||
@ -20,6 +21,7 @@ export const Prompts = memo(() => {
|
|||||||
const shouldConcatPrompts = useAppSelector(concatPromptsSelector);
|
const shouldConcatPrompts = useAppSelector(concatPromptsSelector);
|
||||||
return (
|
return (
|
||||||
<Flex flexDir="column" gap={2}>
|
<Flex flexDir="column" gap={2}>
|
||||||
|
<StylePresetMenuTrigger />
|
||||||
<ParamPositivePrompt />
|
<ParamPositivePrompt />
|
||||||
{!shouldConcatPrompts && <ParamSDXLPositiveStylePrompt />}
|
{!shouldConcatPrompts && <ParamSDXLPositiveStylePrompt />}
|
||||||
<ParamNegativePrompt />
|
<ParamNegativePrompt />
|
||||||
|
@ -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<ChangeEventHandler<HTMLInputElement>>((e) => {
|
||||||
|
setName(e.target.value);
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
const handleChangePosPrompt = useCallback<ChangeEventHandler<HTMLTextAreaElement>>((e) => {
|
||||||
|
setPosPrompt(e.target.value);
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
const handleChangeNegPrompt = useCallback<ChangeEventHandler<HTMLTextAreaElement>>((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 (
|
||||||
|
<Flex flexDir="column" gap="4">
|
||||||
|
<FormControl>
|
||||||
|
<FormLabel>Name</FormLabel>
|
||||||
|
<Input value={name} onChange={handleChangeName} />
|
||||||
|
</FormControl>
|
||||||
|
<FormControl>
|
||||||
|
<FormLabel>Positive Prompt</FormLabel>
|
||||||
|
<Textarea value={posPrompt} onChange={handleChangePosPrompt} />
|
||||||
|
</FormControl>
|
||||||
|
<FormControl>
|
||||||
|
<FormLabel>Negative Prompt</FormLabel>
|
||||||
|
<Textarea value={negPrompt} onChange={handleChangeNegPrompt} />
|
||||||
|
</FormControl>
|
||||||
|
<Button onClick={handleClickSave}>Save</Button>
|
||||||
|
</Flex>
|
||||||
|
);
|
||||||
|
};
|
@ -0,0 +1,46 @@
|
|||||||
|
import { Button, Flex, Text } from '@invoke-ai/ui-library';
|
||||||
|
import { useAppDispatch } from 'app/store/storeHooks';
|
||||||
|
import { isModalOpenChanged, updatingStylePresetChanged } from 'features/stylePresets/store/slice';
|
||||||
|
import { useCallback } from 'react';
|
||||||
|
import type { StylePresetRecordDTO} from 'services/api/endpoints/stylePresets';
|
||||||
|
import { useDeleteStylePresetMutation } from 'services/api/endpoints/stylePresets';
|
||||||
|
|
||||||
|
export const StylePresetListItem = ({ preset }: { preset: StylePresetRecordDTO }) => {
|
||||||
|
const dispatch = useAppDispatch();
|
||||||
|
const [deleteStylePreset] = useDeleteStylePresetMutation();
|
||||||
|
|
||||||
|
const handleClickEdit = useCallback(() => {
|
||||||
|
dispatch(updatingStylePresetChanged(preset));
|
||||||
|
dispatch(isModalOpenChanged(true));
|
||||||
|
}, [dispatch, preset]);
|
||||||
|
|
||||||
|
const handleDeletePreset = useCallback(async () => {
|
||||||
|
try {
|
||||||
|
await deleteStylePreset(preset.id);
|
||||||
|
} catch (error) {}
|
||||||
|
}, [preset]);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<Flex flexDir="column" gap="2">
|
||||||
|
<Text fontSize="md">{preset.name}</Text>
|
||||||
|
<Flex flexDir="column" layerStyle="third" borderRadius="base" padding="10px">
|
||||||
|
<Text fontSize="xs">
|
||||||
|
<Text as="span" fontWeight="semibold">
|
||||||
|
Positive prompt:
|
||||||
|
</Text>{' '}
|
||||||
|
{preset.preset_data.positive_prompt}
|
||||||
|
</Text>
|
||||||
|
<Text fontSize="xs">
|
||||||
|
<Text as="span" fontWeight="semibold">
|
||||||
|
Negative prompt:
|
||||||
|
</Text>{' '}
|
||||||
|
{preset.preset_data.negative_prompt}
|
||||||
|
</Text>
|
||||||
|
<Button onClick={handleClickEdit}>Edit</Button>
|
||||||
|
<Button onClick={handleDeletePreset}>Delete</Button>
|
||||||
|
</Flex>
|
||||||
|
</Flex>
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
};
|
@ -0,0 +1,34 @@
|
|||||||
|
import { Button, Flex, Text } from '@invoke-ai/ui-library';
|
||||||
|
import { useAppDispatch } from 'app/store/storeHooks';
|
||||||
|
import { isModalOpenChanged, updatingStylePresetChanged } from 'features/stylePresets/store/slice';
|
||||||
|
import { useCallback } from 'react';
|
||||||
|
import { useListStylePresetsQuery } from 'services/api/endpoints/stylePresets';
|
||||||
|
|
||||||
|
import { StylePresetListItem } from './StylePresetListItem';
|
||||||
|
|
||||||
|
export const StylePresetMenu = () => {
|
||||||
|
const { data } = useListStylePresetsQuery({});
|
||||||
|
const dispatch = useAppDispatch();
|
||||||
|
|
||||||
|
const handleClickAddNew = useCallback(() => {
|
||||||
|
dispatch(updatingStylePresetChanged(null));
|
||||||
|
dispatch(isModalOpenChanged(true));
|
||||||
|
}, [dispatch]);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<Flex flexDir="column" gap="2">
|
||||||
|
<Flex alignItems="center" gap="10" w="full" justifyContent="space-between">
|
||||||
|
<Text fontSize="sm" fontWeight="semibold" userSelect="none" color="base.500">
|
||||||
|
Style Presets
|
||||||
|
</Text>
|
||||||
|
<Button size="sm" onClick={handleClickAddNew}>
|
||||||
|
Add New
|
||||||
|
</Button>
|
||||||
|
</Flex>
|
||||||
|
|
||||||
|
{data?.items.map((preset) => <StylePresetListItem preset={preset} key={preset.id} />)}
|
||||||
|
</Flex>
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
};
|
@ -0,0 +1,24 @@
|
|||||||
|
import {
|
||||||
|
Button,
|
||||||
|
Popover,
|
||||||
|
PopoverBody,
|
||||||
|
PopoverContent,
|
||||||
|
PopoverTrigger,
|
||||||
|
} from '@invoke-ai/ui-library';
|
||||||
|
|
||||||
|
import { StylePresetMenu } from './StylePresetMenu';
|
||||||
|
|
||||||
|
export const StylePresetMenuTrigger = () => {
|
||||||
|
return (
|
||||||
|
<Popover isLazy>
|
||||||
|
<PopoverTrigger>
|
||||||
|
<Button size="sm">Style Presets</Button>
|
||||||
|
</PopoverTrigger>
|
||||||
|
<PopoverContent>
|
||||||
|
<PopoverBody>
|
||||||
|
<StylePresetMenu />
|
||||||
|
</PopoverBody>
|
||||||
|
</PopoverContent>
|
||||||
|
</Popover>
|
||||||
|
);
|
||||||
|
};
|
@ -0,0 +1,43 @@
|
|||||||
|
import {
|
||||||
|
Modal,
|
||||||
|
ModalBody,
|
||||||
|
ModalCloseButton,
|
||||||
|
ModalContent,
|
||||||
|
ModalFooter,
|
||||||
|
ModalHeader,
|
||||||
|
ModalOverlay,
|
||||||
|
} from '@invoke-ai/ui-library';
|
||||||
|
import { useAppDispatch, useAppSelector } from 'app/store/storeHooks';
|
||||||
|
import { isModalOpenChanged, updatingStylePresetChanged } from 'features/stylePresets/store/slice';
|
||||||
|
import { useCallback, useMemo } from 'react';
|
||||||
|
|
||||||
|
import { StylePresetForm } from './StylePresetForm';
|
||||||
|
|
||||||
|
export const StylePresetModal = () => {
|
||||||
|
const dispatch = useAppDispatch();
|
||||||
|
const isModalOpen = useAppSelector((s) => s.stylePresetModal.isModalOpen);
|
||||||
|
const updatingStylePreset = useAppSelector((s) => s.stylePresetModal.updatingStylePreset);
|
||||||
|
|
||||||
|
const modalTitle = useMemo(() => {
|
||||||
|
return updatingStylePreset ? `Update Style Preset` : `Create Style Preset`;
|
||||||
|
}, [updatingStylePreset]);
|
||||||
|
|
||||||
|
const handleCloseModal = useCallback(() => {
|
||||||
|
dispatch(updatingStylePresetChanged(null));
|
||||||
|
dispatch(isModalOpenChanged(false));
|
||||||
|
}, [dispatch]);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Modal isOpen={isModalOpen} onClose={handleCloseModal} isCentered size="2xl">
|
||||||
|
<ModalOverlay />
|
||||||
|
<ModalContent>
|
||||||
|
<ModalHeader>{modalTitle}</ModalHeader>
|
||||||
|
<ModalCloseButton />
|
||||||
|
<ModalBody display="flex" flexDir="column" gap={4}>
|
||||||
|
<StylePresetForm updatingPreset={updatingStylePreset} />
|
||||||
|
</ModalBody>
|
||||||
|
<ModalFooter />
|
||||||
|
</ModalContent>
|
||||||
|
</Modal>
|
||||||
|
);
|
||||||
|
};
|
@ -0,0 +1,30 @@
|
|||||||
|
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 = {
|
||||||
|
isModalOpen: false,
|
||||||
|
updatingStylePreset: null,
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
export const stylePresetModalSlice = createSlice({
|
||||||
|
name: 'stylePresetModal',
|
||||||
|
initialState: initialState,
|
||||||
|
reducers: {
|
||||||
|
isModalOpenChanged: (state, action: PayloadAction<boolean>) => {
|
||||||
|
state.isModalOpen = action.payload;
|
||||||
|
},
|
||||||
|
updatingStylePresetChanged: (state, action: PayloadAction<StylePresetRecordDTO | null>) => {
|
||||||
|
state.updatingStylePreset = action.payload;
|
||||||
|
},
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
export const { isModalOpenChanged, updatingStylePresetChanged } = stylePresetModalSlice.actions;
|
||||||
|
|
||||||
|
export const selectStylePresetModalSlice = (state: RootState) => state.stylePresetModal;
|
@ -0,0 +1,8 @@
|
|||||||
|
import type { StylePresetRecordDTO } from "services/api/endpoints/stylePresets";
|
||||||
|
|
||||||
|
export type StylePresetState = {
|
||||||
|
isModalOpen: boolean;
|
||||||
|
updatingStylePreset: StylePresetRecordDTO | null;
|
||||||
|
};
|
||||||
|
|
||||||
|
|
@ -2,6 +2,8 @@ import type { paths } from 'services/api/schema';
|
|||||||
|
|
||||||
import { api, buildV1Url, LIST_TAG } from '..';
|
import { api, buildV1Url, LIST_TAG } from '..';
|
||||||
|
|
||||||
|
export type StylePresetRecordDTO = paths['/api/v1/style_presets/i/{style_preset_id}']['get']['responses']['200']['content']['application/json']
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Builds an endpoint URL for the style_presets router
|
* Builds an endpoint URL for the style_presets router
|
||||||
* @example
|
* @example
|
||||||
@ -17,7 +19,10 @@ export const stylePresetsApi = api.injectEndpoints({
|
|||||||
string
|
string
|
||||||
>({
|
>({
|
||||||
query: (style_preset_id) => buildStylePresetsUrl(`i/${style_preset_id}`),
|
query: (style_preset_id) => buildStylePresetsUrl(`i/${style_preset_id}`),
|
||||||
providesTags: (result, error, style_preset_id) => [{ type: 'StylePreset', id: style_preset_id }, 'FetchOnReconnect'],
|
providesTags: (result, error, style_preset_id) => [
|
||||||
|
{ type: 'StylePreset', id: style_preset_id },
|
||||||
|
'FetchOnReconnect',
|
||||||
|
],
|
||||||
}),
|
}),
|
||||||
deleteStylePreset: build.mutation<void, string>({
|
deleteStylePreset: build.mutation<void, string>({
|
||||||
query: (style_preset_id) => ({
|
query: (style_preset_id) => ({
|
||||||
@ -45,14 +50,17 @@ export const stylePresetsApi = api.injectEndpoints({
|
|||||||
}),
|
}),
|
||||||
updateStylePreset: build.mutation<
|
updateStylePreset: build.mutation<
|
||||||
paths['/api/v1/style_presets/i/{style_preset_id}']['patch']['responses']['200']['content']['application/json'],
|
paths['/api/v1/style_presets/i/{style_preset_id}']['patch']['responses']['200']['content']['application/json'],
|
||||||
{ id: string, changes: paths['/api/v1/style_presets/i/{style_preset_id}']['patch']['requestBody']['content']['application/json']['changes'] }
|
{
|
||||||
|
id: string;
|
||||||
|
changes: paths['/api/v1/style_presets/i/{style_preset_id}']['patch']['requestBody']['content']['application/json']['changes'];
|
||||||
|
}
|
||||||
>({
|
>({
|
||||||
query: ({ id, changes }) => ({
|
query: ({ id, changes }) => ({
|
||||||
url: buildStylePresetsUrl(`i/${id}`),
|
url: buildStylePresetsUrl(`i/${id}`),
|
||||||
method: 'PATCH',
|
method: 'PATCH',
|
||||||
body: { changes },
|
body: { changes },
|
||||||
}),
|
}),
|
||||||
invalidatesTags: (response, error, { id, changes }) => [
|
invalidatesTags: (response, error, { id }) => [
|
||||||
{ type: 'StylePreset', id: LIST_TAG },
|
{ type: 'StylePreset', id: LIST_TAG },
|
||||||
{ type: 'StylePreset', id: id },
|
{ type: 'StylePreset', id: id },
|
||||||
],
|
],
|
||||||
|
Loading…
Reference in New Issue
Block a user