translations and lint fix

This commit is contained in:
Mary Hipp 2024-08-08 13:56:37 -04:00
parent 442fc02429
commit 4cc41e0188
35 changed files with 269 additions and 275 deletions

View File

@ -1691,6 +1691,27 @@
"missingUpscaleModel": "Missing upscale model",
"missingTileControlNetModel": "No valid tile ControlNet models installed"
},
"stylePresets": {
"active": "Active",
"choosePromptTemplate": "Choose Prompt Template",
"createPromptTemplate": "Create Prompt Template",
"defaultTemplates": "Default Templates",
"deleteImage": "Delete Image",
"deleteTemplate": "Delete Template",
"deleteTemplate2": "Are you sure you want to delete this template? This cannot be undone.",
"editTemplate": "Edit Template",
"myTemplates": "My Templates",
"name": "Name",
"negativePrompt": "Negative Prompt",
"noMatchingTemplates": "No matching templates",
"placeholderDirections": "Use the { } button to specify where your manual prompt should be included in the template. If you do not provide one, the template will be appended to your prompt.",
"positivePrompt": "Positive Prompt",
"templateDeleted": "Prompt template deleted",
"unableToDeleteTemplate": "Unable to delete prompt template",
"updatePromptTemplate": "Update Prompt Template",
"uploadImage": "Upload Image",
"useForTemplate": "Use For Prompt Template"
},
"upsell": {
"inviteTeammates": "Invite Teammates",
"professional": "Professional",

View File

@ -72,7 +72,7 @@ const allReducers = {
[api.reducerPath]: api.reducer,
[upscaleSlice.name]: upscaleSlice.reducer,
[stylePresetModalSlice.name]: stylePresetModalSlice.reducer,
[stylePresetSlice.name]: stylePresetSlice.reducer
[stylePresetSlice.name]: stylePresetSlice.reducer,
};
const rootReducer = combineReducers(allReducers);

View File

@ -6,7 +6,6 @@ import { useCopyImageToClipboard } from 'common/hooks/useCopyImageToClipboard';
import { useDownloadImage } from 'common/hooks/useDownloadImage';
import { setInitialCanvasImage } from 'features/canvas/store/canvasSlice';
import { imagesToChangeSelected, isModalOpenChanged } from 'features/changeBoardModal/store/slice';
import { isModalOpenChanged as isStylePresetModalOpenChanged } from 'features/stylePresets/store/stylePresetModalSlice';
import { iiLayerAdded } from 'features/controlLayers/store/controlLayersSlice';
import { imagesToDeleteSelected } from 'features/deleteImageModal/store/slice';
import { useImageActions } from 'features/gallery/hooks/useImageActions';
@ -41,8 +40,6 @@ import {
} from 'react-icons/pi';
import { useStarImagesMutation, useUnstarImagesMutation } from 'services/api/endpoints/images';
import type { ImageDTO } from 'services/api/types';
import { isMenuOpenChanged } from '../../../stylePresets/store/stylePresetSlice';
import { createPresetFromImageChanged } from '../../../stylePresets/store/stylePresetModalSlice';
type SingleSelectionMenuItemsProps = {
imageDTO: ImageDTO;
@ -200,7 +197,7 @@ const SingleSelectionMenuItems = (props: SingleSelectionMenuItemsProps) => {
onClickCapture={createAsPreset}
isDisabled={isLoadingMetadata || !hasPrompts}
>
Create Preset
{t('stylePresets.useForTemplate')}
</MenuItem>
<MenuDivider />
<MenuItem icon={<PiShareFatBold />} onClickCapture={handleSendToImageToImage} id="send-to-img2img">

View File

@ -1,12 +1,12 @@
import { skipToken } from '@reduxjs/toolkit/query';
import { useAppDispatch, useAppSelector } from 'app/store/storeHooks';
import { useImageUrlToBlob } from 'common/hooks/useImageUrlToBlob';
import { handlers, parseAndRecallAllMetadata, parseAndRecallPrompts } from 'features/metadata/util/handlers';
import { isModalOpenChanged, prefilledFormDataChanged } from 'features/stylePresets/store/stylePresetModalSlice';
import { activeTabNameSelector } from 'features/ui/store/uiSelectors';
import { useCallback, useEffect, useState } from 'react';
import { useGetImageDTOQuery } from 'services/api/endpoints/images';
import { useDebouncedMetadata } from 'services/api/hooks/useDebouncedMetadata';
import { useImageUrlToBlob } from '../../../common/hooks/useImageUrlToBlob';
import { prefilledFormDataChanged, isModalOpenChanged } from '../../stylePresets/store/stylePresetModalSlice';
import { useGetImageDTOQuery } from '../../../services/api/endpoints/images';
import { skipToken } from '@reduxjs/toolkit/query';
export const useImageActions = (image_name?: string) => {
const activeTabName = useAppSelector(activeTabNameSelector);
@ -15,8 +15,8 @@ export const useImageActions = (image_name?: string) => {
const [hasSeed, setHasSeed] = useState(false);
const [hasPrompts, setHasPrompts] = useState(false);
const imageUrlToBlob = useImageUrlToBlob();
const dispatch = useAppDispatch()
const { data: imageDTO } = useGetImageDTOQuery(image_name ?? skipToken)
const dispatch = useAppDispatch();
const { data: imageDTO } = useGetImageDTOQuery(image_name ?? skipToken);
useEffect(() => {
const parseMetadata = async () => {
@ -70,15 +70,31 @@ export const useImageActions = (image_name?: string) => {
const createAsPreset = useCallback(async () => {
if (image_name && metadata && imageDTO) {
const positivePrompt = await handlers.positivePrompt.parse(metadata)
const negativePrompt = await handlers.negativePrompt.parse(metadata)
const imageBlob = await imageUrlToBlob(imageDTO.image_url, 100)
const positivePrompt = await handlers.positivePrompt.parse(metadata);
const negativePrompt = await handlers.negativePrompt.parse(metadata);
const imageBlob = await imageUrlToBlob(imageDTO.image_url, 100);
dispatch(prefilledFormDataChanged({ name: "", positivePrompt, negativePrompt, image: imageBlob ? new File([imageBlob], "stylePreset.png", { type: 'image/png', }) : null }))
dispatch(isModalOpenChanged(true))
dispatch(
prefilledFormDataChanged({
name: '',
positivePrompt,
negativePrompt,
image: imageBlob ? new File([imageBlob], 'stylePreset.png', { type: 'image/png' }) : null,
})
);
dispatch(isModalOpenChanged(true));
}
}, [image_name, metadata, dispatch, imageDTO, imageUrlToBlob]);
}, [image_name, metadata, dispatch, imageDTO])
return { recallAll, remix, recallSeed, recallPrompts, hasMetadata, hasSeed, hasPrompts, isLoadingMetadata, createAsPreset };
return {
recallAll,
remix,
recallSeed,
recallPrompts,
hasMetadata,
hasSeed,
hasPrompts,
isLoadingMetadata,
createAsPreset,
};
};

View File

@ -98,7 +98,8 @@ export const buildMultidiffusionUpscaleGraph = async (state: RootState): Promise
let modelNode;
if (model.base === 'sdxl') {
const { positivePrompt, negativePrompt, positiveStylePrompt, negativeStylePrompt } = getPresetModifiedPrompts(state);
const { positivePrompt, negativePrompt, positiveStylePrompt, negativeStylePrompt } =
getPresetModifiedPrompts(state);
posCondNode = g.addNode({
type: 'sdxl_compel_prompt',

View File

@ -16,7 +16,11 @@ import {
POSITIVE_CONDITIONING,
SEAMLESS,
} from 'features/nodes/util/graph/constants';
import { getBoardField, getIsIntermediate, getPresetModifiedPrompts } from 'features/nodes/util/graph/graphBuilderUtils';
import {
getBoardField,
getIsIntermediate,
getPresetModifiedPrompts,
} from 'features/nodes/util/graph/graphBuilderUtils';
import type { ImageDTO, Invocation, NonNullableGraph } from 'services/api/types';
import { isNonRefinerMainModelConfig } from 'services/api/types';

View File

@ -19,7 +19,11 @@ import {
POSITIVE_CONDITIONING,
SEAMLESS,
} from 'features/nodes/util/graph/constants';
import { getBoardField, getIsIntermediate, getPresetModifiedPrompts } from 'features/nodes/util/graph/graphBuilderUtils';
import {
getBoardField,
getIsIntermediate,
getPresetModifiedPrompts,
} from 'features/nodes/util/graph/graphBuilderUtils';
import type { ImageDTO, Invocation, NonNullableGraph } from 'services/api/types';
import { addControlNetToLinearGraph } from './addControlNetToLinearGraph';

View File

@ -23,7 +23,11 @@ import {
POSITIVE_CONDITIONING,
SEAMLESS,
} from 'features/nodes/util/graph/constants';
import { getBoardField, getIsIntermediate, getPresetModifiedPrompts } from 'features/nodes/util/graph/graphBuilderUtils';
import {
getBoardField,
getIsIntermediate,
getPresetModifiedPrompts,
} from 'features/nodes/util/graph/graphBuilderUtils';
import type { ImageDTO, Invocation, NonNullableGraph } from 'services/api/types';
import { addControlNetToLinearGraph } from './addControlNetToLinearGraph';

View File

@ -16,7 +16,11 @@ import {
SDXL_REFINER_SEAMLESS,
SEAMLESS,
} from 'features/nodes/util/graph/constants';
import { getBoardField, getIsIntermediate, getPresetModifiedPrompts } from 'features/nodes/util/graph/graphBuilderUtils';
import {
getBoardField,
getIsIntermediate,
getPresetModifiedPrompts,
} from 'features/nodes/util/graph/graphBuilderUtils';
import type { ImageDTO, Invocation, NonNullableGraph } from 'services/api/types';
import { isNonRefinerMainModelConfig } from 'services/api/types';

View File

@ -19,7 +19,11 @@ import {
SDXL_REFINER_SEAMLESS,
SEAMLESS,
} from 'features/nodes/util/graph/constants';
import { getBoardField, getIsIntermediate, getPresetModifiedPrompts } from 'features/nodes/util/graph/graphBuilderUtils';
import {
getBoardField,
getIsIntermediate,
getPresetModifiedPrompts,
} from 'features/nodes/util/graph/graphBuilderUtils';
import type { ImageDTO, Invocation, NonNullableGraph } from 'services/api/types';
import { addControlNetToLinearGraph } from './addControlNetToLinearGraph';

View File

@ -23,7 +23,11 @@ import {
SDXL_REFINER_SEAMLESS,
SEAMLESS,
} from 'features/nodes/util/graph/constants';
import { getBoardField, getIsIntermediate, getPresetModifiedPrompts } from 'features/nodes/util/graph/graphBuilderUtils';
import {
getBoardField,
getIsIntermediate,
getPresetModifiedPrompts,
} from 'features/nodes/util/graph/graphBuilderUtils';
import type { ImageDTO, Invocation, NonNullableGraph } from 'services/api/types';
import { addControlNetToLinearGraph } from './addControlNetToLinearGraph';

View File

@ -14,7 +14,11 @@ import {
SDXL_REFINER_SEAMLESS,
SEAMLESS,
} from 'features/nodes/util/graph/constants';
import { getBoardField, getIsIntermediate, getPresetModifiedPrompts } from 'features/nodes/util/graph/graphBuilderUtils';
import {
getBoardField,
getIsIntermediate,
getPresetModifiedPrompts,
} from 'features/nodes/util/graph/graphBuilderUtils';
import { isNonRefinerMainModelConfig, type NonNullableGraph } from 'services/api/types';
import { addControlNetToLinearGraph } from './addControlNetToLinearGraph';

View File

@ -14,7 +14,11 @@ import {
POSITIVE_CONDITIONING,
SEAMLESS,
} from 'features/nodes/util/graph/constants';
import { getBoardField, getIsIntermediate, getPresetModifiedPrompts } from 'features/nodes/util/graph/graphBuilderUtils';
import {
getBoardField,
getIsIntermediate,
getPresetModifiedPrompts,
} from 'features/nodes/util/graph/graphBuilderUtils';
import { isNonRefinerMainModelConfig, type NonNullableGraph } from 'services/api/types';
import { addControlNetToLinearGraph } from './addControlNetToLinearGraph';

View File

@ -105,10 +105,10 @@ export const buildGenerationTabGraph = async (state: RootState): Promise<GraphTy
const vaeLoader =
vae?.base === model.base
? g.addNode({
type: 'vae_loader',
id: VAE_LOADER,
vae_model: vae,
})
type: 'vae_loader',
id: VAE_LOADER,
vae_model: vae,
})
: null;
let imageOutput: Invocation<'l2i'> | Invocation<'img_nsfw'> | Invocation<'img_watermark'> = l2i;

View File

@ -93,10 +93,10 @@ export const buildGenerationTabSDXLGraph = async (state: RootState): Promise<Non
const vaeLoader =
vae?.base === model.base
? g.addNode({
type: 'vae_loader',
id: VAE_LOADER,
vae_model: vae,
})
type: 'vae_loader',
id: VAE_LOADER,
vae_model: vae,
})
: null;
let imageOutput: Invocation<'l2i'> | Invocation<'img_nsfw'> | Invocation<'img_watermark'> = l2i;

View File

@ -17,22 +17,29 @@ export const getBoardField = (state: RootState): BoardField | undefined => {
/**
* Gets the prompts, modified for the active style preset.
*/
export const getPresetModifiedPrompts = (state: RootState): { positivePrompt: string; negativePrompt: string, positiveStylePrompt?: string; negativeStylePrompt?: string } => {
export const getPresetModifiedPrompts = (
state: RootState
): { positivePrompt: string; negativePrompt: string; positiveStylePrompt?: string; negativeStylePrompt?: string } => {
const { positivePrompt, negativePrompt, positivePrompt2, negativePrompt2, shouldConcatPrompts } =
state.controlLayers.present;
const { activeStylePreset } = state.stylePreset
const { activeStylePreset } = state.stylePreset;
if (activeStylePreset) {
const presetModifiedPositivePrompt = buildPresetModifiedPrompt(activeStylePreset.preset_data.positive_prompt, positivePrompt)
const presetModifiedPositivePrompt = buildPresetModifiedPrompt(
activeStylePreset.preset_data.positive_prompt,
positivePrompt
);
const presetModifiedNegativePrompt = buildPresetModifiedPrompt(activeStylePreset.preset_data.negative_prompt, negativePrompt)
const presetModifiedNegativePrompt = buildPresetModifiedPrompt(
activeStylePreset.preset_data.negative_prompt,
negativePrompt
);
return {
positivePrompt: presetModifiedPositivePrompt,
negativePrompt: presetModifiedNegativePrompt,
positiveStylePrompt: shouldConcatPrompts ? presetModifiedPositivePrompt : positivePrompt2,
negativeStylePrompt: shouldConcatPrompts ? presetModifiedNegativePrompt : negativePrompt2,
};
}

View File

@ -2,12 +2,12 @@ import { Box, Textarea } from '@invoke-ai/ui-library';
import { useAppDispatch, useAppSelector } from 'app/store/storeHooks';
import { negativePromptChanged } from 'features/controlLayers/store/controlLayersSlice';
import { PromptOverlayButtonWrapper } from 'features/parameters/components/Prompts/PromptOverlayButtonWrapper';
import { ViewModePrompt } from 'features/parameters/components/Prompts/ViewModePrompt';
import { AddPromptTriggerButton } from 'features/prompt/AddPromptTriggerButton';
import { PromptPopover } from 'features/prompt/PromptPopover';
import { usePrompt } from 'features/prompt/usePrompt';
import { memo, useCallback, useRef } from 'react';
import { useTranslation } from 'react-i18next';
import { ViewModePrompt } from '../Prompts/ViewModePrompt';
const DEFAULT_HEIGHT = 20;
@ -42,7 +42,7 @@ export const ParamNegativePrompt = memo(() => {
return (
<PromptPopover isOpen={isOpen} onClose={onClose} onSelect={onSelect} width={textareaRef.current?.clientWidth}>
<Box pos="relative">
<Box pos="relative" w="full">
<Textarea
id="negativePrompt"
name="negativePrompt"

View File

@ -3,6 +3,7 @@ import { useAppDispatch, useAppSelector } from 'app/store/storeHooks';
import { positivePromptChanged } from 'features/controlLayers/store/controlLayersSlice';
import { ShowDynamicPromptsPreviewButton } from 'features/dynamicPrompts/components/ShowDynamicPromptsPreviewButton';
import { PromptOverlayButtonWrapper } from 'features/parameters/components/Prompts/PromptOverlayButtonWrapper';
import { ViewModePrompt } from 'features/parameters/components/Prompts/ViewModePrompt';
import { AddPromptTriggerButton } from 'features/prompt/AddPromptTriggerButton';
import { PromptPopover } from 'features/prompt/PromptPopover';
import { usePrompt } from 'features/prompt/usePrompt';
@ -11,7 +12,6 @@ import { memo, useCallback, useRef } from 'react';
import type { HotkeyCallback } from 'react-hotkeys-hook';
import { useHotkeys } from 'react-hotkeys-hook';
import { useTranslation } from 'react-i18next';
import { ViewModePrompt } from '../Prompts/ViewModePrompt';
const DEFAULT_HEIGHT = 28;

View File

@ -1,9 +1,9 @@
import { Flex, Icon, Text, Tooltip, Box } from '@invoke-ai/ui-library';
import { useAppDispatch, useAppSelector } from '../../../../app/store/storeHooks';
import { Box, Flex, Icon, Text, Tooltip } from '@invoke-ai/ui-library';
import { useAppDispatch } from 'app/store/storeHooks';
import { viewModeChanged } from 'features/stylePresets/store/stylePresetSlice';
import { getViewModeChunks } from 'features/stylePresets/util/getViewModeChunks';
import { useCallback, useMemo } from 'react';
import { PiEyeBold, PiQuestionBold } from 'react-icons/pi';
import { viewModeChanged } from '../../../stylePresets/store/stylePresetSlice';
import { getViewModeChunks } from '../../../stylePresets/util/getViewModeChunks';
import { PiEyeBold } from 'react-icons/pi';
export const ViewModePrompt = ({
presetPrompt,
@ -51,11 +51,7 @@ export const ViewModePrompt = ({
<Box position="absolute" top={0} right={0} backgroundColor="rgba(0,0,0,0.75)" padding="2px 5px">
<Flex alignItems="center" gap="1">
<Tooltip
label={
'This is how your prompt will look with your currently selected preset. To edit your prompt, click anywhere in the text box.'
}
>
<Tooltip label="This is how your prompt will look with your currently selected preset. To edit your prompt, click anywhere in the text box.">
<Flex>
<Icon as={PiEyeBold} color="base.500" boxSize="12px" />
</Flex>

View File

@ -1,98 +0,0 @@
import { useDisclosure } from '@invoke-ai/ui-library';
import { isNil } from 'lodash-es';
import type { ChangeEventHandler, FormEvent, FormEventHandler, KeyboardEventHandler, RefObject, SyntheticEvent } from 'react';
import { useCallback } from 'react';
import { flushSync } from 'react-dom';
type UseInsertTriggerArg = {
prompt: string;
paragraphRef: RefObject<HTMLParagraphElement>;
onChange: (v: string) => void;
};
export const usePromptContentEditable = ({ prompt, paragraphRef, onChange: _onChange }: UseInsertTriggerArg) => {
const { isOpen, onClose, onOpen } = useDisclosure();
const onChange = useCallback(
(e: any) => {
e.preventDefault();
_onChange(e.data)
},
[_onChange]
);
const insertTrigger = useCallback(
(v: string) => {
const element = paragraphRef.current;
if (!element) {
return;
}
const selection = window.getSelection();
if (!selection || selection.rangeCount === 0) {
// Insert at the end if no selection found
const newPrompt = prompt + v;
flushSync(() => {
_onChange(newPrompt);
});
return;
}
const range = selection.getRangeAt(0);
const cursorPosition = range.startOffset;
console.log({ cursorPosition })
const updatedPrompt = prompt.slice(0, cursorPosition) + v + prompt.slice(cursorPosition);
console.log({ updatedPrompt })
flushSync(() => {
_onChange(updatedPrompt);
});
},
[paragraphRef, _onChange, prompt]
);
const onFocus = useCallback(() => {
paragraphRef.current?.focus();
}, [paragraphRef]);
const handleClosePopover = useCallback(() => {
onClose();
onFocus();
}, [onFocus, onClose]);
const onSelect = useCallback(
(v: string) => {
insertTrigger(v)
handleClosePopover();
},
[handleClosePopover, insertTrigger]
);
const onKeyDown: KeyboardEventHandler<HTMLParagraphElement> = useCallback(
(e) => {
if (e.key === '<') {
onOpen();
e.preventDefault();
}
},
[onOpen]
);
return {
onChange,
isOpen,
onClose,
onOpen,
onSelect,
onKeyDown,
onFocus,
};
};

View File

@ -1,17 +1,20 @@
import { Flex, IconButton, Text, Box, ButtonGroup } from '@invoke-ai/ui-library';
import { Flex, IconButton, Text } from '@invoke-ai/ui-library';
import { useAppDispatch, useAppSelector } from 'app/store/storeHooks';
import { negativePromptChanged, positivePromptChanged } from 'features/controlLayers/store/controlLayersSlice';
import { usePresetModifiedPrompts } from 'features/stylePresets/hooks/usePresetModifiedPrompts';
import { activeStylePresetChanged, viewModeChanged } from 'features/stylePresets/store/stylePresetSlice';
import type { MouseEventHandler } from 'react';
import { useCallback } from 'react';
import { useTranslation } from 'react-i18next';
import { PiEyeBold, PiStackSimpleBold, PiXBold } from 'react-icons/pi';
import StylePresetImage from './StylePresetImage';
export const ActiveStylePreset = () => {
const { activeStylePreset, viewMode } = useAppSelector((s) => s.stylePreset);
const dispatch = useAppDispatch();
const { t } = useTranslation();
const { presetModifiedPositivePrompt, presetModifiedNegativePrompt } = usePresetModifiedPrompts();
@ -47,7 +50,7 @@ export const ActiveStylePreset = () => {
return (
<Flex h="25px" alignItems="center">
<Text fontSize="sm" fontWeight="semibold" color="base.300">
Choose Preset
{t('stylePresets.choosePromptTemplate')}
</Text>
</Flex>
);

View File

@ -1,15 +1,15 @@
import { Button, Flex, FormControl, FormLabel, Icon, Input, Text } from '@invoke-ai/ui-library';
import { Button, Flex, FormControl, FormLabel, Input, Text } from '@invoke-ai/ui-library';
import { useAppDispatch, useAppSelector } from 'app/store/storeHooks';
import { isModalOpenChanged, updatingStylePresetIdChanged } from 'features/stylePresets/store/stylePresetModalSlice';
import { toast } from 'features/toast/toast';
import { useCallback } from 'react';
import type { SubmitHandler } from 'react-hook-form';
import { useForm } from 'react-hook-form';
import { PiBracketsCurlyBold } from 'react-icons/pi';
import { useTranslation } from 'react-i18next';
import { useCreateStylePresetMutation, useUpdateStylePresetMutation } from 'services/api/endpoints/stylePresets';
import { StylePresetPromptField } from './StylePresetPromptField';
import { StylePresetImageField } from './StylePresetImageField';
import { StylePresetPromptField } from './StylePresetPromptField';
export type StylePresetFormData = {
name: string;
@ -22,10 +22,11 @@ export const StylePresetForm = ({ updatingStylePresetId }: { updatingStylePreset
const [createStylePreset] = useCreateStylePresetMutation();
const [updateStylePreset] = useUpdateStylePresetMutation();
const dispatch = useAppDispatch();
const { t } = useTranslation();
const defaultValues = useAppSelector((s) => s.stylePresetModal.prefilledFormData);
const { handleSubmit, control, formState, reset, register } = useForm<StylePresetFormData>({
const { handleSubmit, control, register } = useForm<StylePresetFormData>({
defaultValues: defaultValues || {
name: '',
positivePrompt: '',
@ -70,19 +71,18 @@ export const StylePresetForm = ({ updatingStylePresetId }: { updatingStylePreset
<Flex alignItems="center" gap="4">
<StylePresetImageField control={control} name="image" />
<FormControl orientation="vertical">
<FormLabel>Name</FormLabel>
<FormLabel>{t('stylePresets.name')}</FormLabel>
<Input size="md" {...register('name')} />
</FormControl>
</Flex>
<StylePresetPromptField label="Positive Prompt" control={control} name="positivePrompt" />
<StylePresetPromptField label="Negative Prompt" control={control} name="negativePrompt" />
<Text variant="subtext">
Use the <Icon as={PiBracketsCurlyBold} /> button to specify where your manual prompt should be included in the
template. If you do not provide one, the template will be appended to your prompt.
</Text>
<Text variant="subtext">{t('stylePresets.placeholderDirections')}</Text>
<Button onClick={handleSubmit(handleClickSave)}>Save</Button>
<Flex justifyContent="flex-end">
<Button onClick={handleSubmit(handleClickSave)}>{t('common.save')}</Button>
</Flex>
</Flex>
);
};

View File

@ -1,13 +1,16 @@
import { Tooltip, Flex, Button, Icon, Box, Image, IconButton } from '@invoke-ai/ui-library';
import { t } from 'i18next';
import { useCallback, useState } from 'react';
import { Box, Button, Flex, Icon, IconButton, Image, Tooltip } from '@invoke-ai/ui-library';
import { useCallback } from 'react';
import { useDropzone } from 'react-dropzone';
import type { UseControllerProps } from 'react-hook-form';
import { useController } from 'react-hook-form';
import { useTranslation } from 'react-i18next';
import { PiArrowCounterClockwiseBold, PiUploadSimpleBold } from 'react-icons/pi';
import { useController, UseControllerProps } from 'react-hook-form';
import { StylePresetFormData } from './StylePresetForm';
import type { StylePresetFormData } from './StylePresetForm';
export const StylePresetImageField = (props: UseControllerProps<StylePresetFormData>) => {
const { field } = useController(props);
const { t } = useTranslation();
const onDropAccepted = useCallback(
(files: File[]) => {
const file = files[0];
@ -15,12 +18,12 @@ export const StylePresetImageField = (props: UseControllerProps<StylePresetFormD
field.onChange(file);
}
},
[field, t]
[field]
);
const handleResetImage = useCallback(() => {
field.onChange(null);
}, []);
}, [field]);
const { getInputProps, getRootProps } = useDropzone({
accept: { 'image/png': ['.png'], 'image/jpeg': ['.jpg', '.jpeg', '.png'] },
@ -46,8 +49,8 @@ export const StylePresetImageField = (props: UseControllerProps<StylePresetFormD
insetInlineEnd={0}
insetBlockStart={0}
onClick={handleResetImage}
aria-label={t('modelManager.deleteModelImage')}
tooltip={t('modelManager.deleteModelImage')}
aria-label={t('stylePresets.deleteImage')}
tooltip={t('stylePresets.deleteImage')}
icon={<PiArrowCounterClockwiseBold />}
size="md"
variant="ghost"
@ -58,7 +61,7 @@ export const StylePresetImageField = (props: UseControllerProps<StylePresetFormD
return (
<>
<Tooltip label={t('modelManager.uploadImage')}>
<Tooltip label={t('stylePresets.uploadImage')}>
<Flex
as={Button}
w={65}

View File

@ -1,18 +1,21 @@
import { Badge, ConfirmationAlertDialog, Flex, IconButton, Text, useDisclosure } from '@invoke-ai/ui-library';
import type { MouseEvent } from 'react';
import { useAppDispatch, useAppSelector } from 'app/store/storeHooks';
import { useImageUrlToBlob } from 'common/hooks/useImageUrlToBlob';
import {
isModalOpenChanged,
prefilledFormDataChanged,
updatingStylePresetIdChanged,
} from 'features/stylePresets/store/stylePresetModalSlice';
import { activeStylePresetChanged, isMenuOpenChanged } from 'features/stylePresets/store/stylePresetSlice';
import { toast } from 'features/toast/toast';
import type { MouseEvent } from 'react';
import { useCallback } from 'react';
import { useTranslation } from 'react-i18next';
import { PiPencilBold, PiTrashBold } from 'react-icons/pi';
import type { StylePresetRecordWithImage } from 'services/api/endpoints/stylePresets';
import { useDeleteStylePresetMutation } from 'services/api/endpoints/stylePresets';
import StylePresetImage from './StylePresetImage';
import { useImageUrlToBlob } from 'common/hooks/useImageUrlToBlob';
export const StylePresetListItem = ({ preset }: { preset: StylePresetRecordWithImage }) => {
const dispatch = useAppDispatch();
@ -20,6 +23,7 @@ export const StylePresetListItem = ({ preset }: { preset: StylePresetRecordWithI
const activeStylePreset = useAppSelector((s) => s.stylePreset.activeStylePreset);
const { isOpen, onOpen, onClose } = useDisclosure();
const imageUrlToBlob = useImageUrlToBlob();
const { t } = useTranslation();
const handleClickEdit = useCallback(
async (e: MouseEvent<HTMLButtonElement>) => {
@ -43,7 +47,7 @@ export const StylePresetListItem = ({ preset }: { preset: StylePresetRecordWithI
dispatch(updatingStylePresetIdChanged(preset.id));
dispatch(isModalOpenChanged(true));
},
[dispatch, preset]
[dispatch, preset, imageUrlToBlob]
);
const handleClickApply = useCallback(async () => {
@ -56,14 +60,23 @@ export const StylePresetListItem = ({ preset }: { preset: StylePresetRecordWithI
e.stopPropagation();
onOpen();
},
[dispatch, preset]
[onOpen]
);
const handleDeletePreset = useCallback(async () => {
try {
await deleteStylePreset(preset.id);
} catch (error) {}
}, [preset]);
toast({
status: 'success',
title: t('stylePresets.templateDeleted'),
});
} catch (error) {
toast({
status: 'error',
title: t('stylePresets.unableToDeleteTemplate'),
});
}
}, [preset, t, deleteStylePreset]);
return (
<>
@ -92,7 +105,7 @@ export const StylePresetListItem = ({ preset }: { preset: StylePresetRecordWithI
bg="transparent"
flexShrink={0}
>
Active
{t('stylePresets.active')}
</Badge>
)}
</Flex>
@ -101,14 +114,14 @@ export const StylePresetListItem = ({ preset }: { preset: StylePresetRecordWithI
<IconButton
size="sm"
variant="ghost"
aria-label="Edit"
aria-label={t('stylePresets.editTemplate')}
onClick={handleClickEdit}
icon={<PiPencilBold />}
/>
<IconButton
size="sm"
variant="ghost"
aria-label="Delete"
aria-label={t('stylePresets.deleteTemplate')}
onClick={handleClickDelete}
colorScheme="error"
icon={<PiTrashBold />}
@ -116,16 +129,16 @@ export const StylePresetListItem = ({ preset }: { preset: StylePresetRecordWithI
</Flex>
</Flex>
<Flex flexDir="column">
<Flex flexDir="column" gap="1">
<Text fontSize="xs">
<Text as="span" fontWeight="semibold">
Positive prompt:
{t('stylePresets.positivePrompt')}:
</Text>{' '}
{preset.preset_data.positive_prompt}
</Text>
<Text fontSize="xs">
<Text as="span" fontWeight="semibold">
Negative prompt:
{t('stylePresets.negativePrompt')}:
</Text>{' '}
{preset.preset_data.negative_prompt}
</Text>
@ -135,12 +148,11 @@ export const StylePresetListItem = ({ preset }: { preset: StylePresetRecordWithI
<ConfirmationAlertDialog
isOpen={isOpen}
onClose={onClose}
title={'Delete preset'}
title={t('stylePresets.deleteTemplate')}
acceptCallback={handleDeletePreset}
acceptButtonText={'Delete'}
acceptButtonText="Delete"
>
<p>{'Delete Preset?'}</p>
<br />
<p>{t('stylePresets.deleteTemplate2')}</p>
</ConfirmationAlertDialog>
</>
);

View File

@ -7,6 +7,7 @@ import {
updatingStylePresetIdChanged,
} from 'features/stylePresets/store/stylePresetModalSlice';
import { useCallback } from 'react';
import { useTranslation } from 'react-i18next';
import { PiPlusBold } from 'react-icons/pi';
import type { StylePresetRecordWithImage } from 'services/api/endpoints/stylePresets';
import { useListStylePresetsQuery } from 'services/api/endpoints/stylePresets';
@ -42,6 +43,7 @@ export const StylePresetMenu = () => {
});
const dispatch = useAppDispatch();
const { t } = useTranslation();
const handleClickAddNew = useCallback(() => {
dispatch(prefilledFormDataChanged(null));
@ -67,13 +69,13 @@ export const StylePresetMenu = () => {
{data.presets.length === 0 && data.defaultPresets.length === 0 && (
<Text m="20px" textAlign="center">
No matching presets
{t('stylePrests.noMatchingTemplates')}
</Text>
)}
<StylePresetList title="My Presets" data={data.presets} />
<StylePresetList title={t('stylePresets.myTemplates')} data={data.presets} />
<StylePresetList title="Default Presets" data={data.defaultPresets} />
<StylePresetList title={t('stylePresets.defaultTemplates')} data={data.defaultPresets} />
</Flex>
);
};

View File

@ -10,17 +10,19 @@ import {
import { useAppDispatch, useAppSelector } from 'app/store/storeHooks';
import { isModalOpenChanged, updatingStylePresetIdChanged } from 'features/stylePresets/store/stylePresetModalSlice';
import { useCallback, useMemo } from 'react';
import { useTranslation } from 'react-i18next';
import { StylePresetForm } from './StylePresetForm';
export const StylePresetModal = () => {
const dispatch = useAppDispatch();
const { t } = useTranslation();
const isModalOpen = useAppSelector((s) => s.stylePresetModal.isModalOpen);
const updatingStylePresetId = useAppSelector((s) => s.stylePresetModal.updatingStylePresetId);
const modalTitle = useMemo(() => {
return updatingStylePresetId ? `Update Style Preset` : `Create Style Preset`;
}, [updatingStylePresetId]);
return updatingStylePresetId ? t('stylePresets.updatePromptTemplate') : t('stylePresets.createPromptTemplate');
}, [updatingStylePresetId, t]);
const handleCloseModal = useCallback(() => {
dispatch(updatingStylePresetIdChanged(null));

View File

@ -1,4 +1,5 @@
import { Flex, FormControl, FormLabel, IconButton, Textarea } from '@invoke-ai/ui-library';
import { PRESET_PLACEHOLDER } from 'features/stylePresets/hooks/usePresetModifiedPrompts';
import type { ChangeEventHandler } from 'react';
import { useCallback, useMemo, useRef } from 'react';
import type { UseControllerProps } from 'react-hook-form';
@ -6,7 +7,6 @@ import { useController } from 'react-hook-form';
import { PiBracketsCurlyBold } from 'react-icons/pi';
import type { StylePresetFormData } from './StylePresetForm';
import { PRESET_PLACEHOLDER } from '../hooks/usePresetModifiedPrompts';
interface Props extends UseControllerProps<StylePresetFormData> {
label: string;

View File

@ -1,25 +1,27 @@
import { useAppSelector } from "app/store/storeHooks"
import { useAppSelector } from 'app/store/storeHooks';
export const PRESET_PLACEHOLDER = `{prompt}`
export const PRESET_PLACEHOLDER = `{prompt}`;
export const buildPresetModifiedPrompt = (presetPrompt: string, currentPrompt: string) => {
return presetPrompt.includes(PRESET_PLACEHOLDER) ? presetPrompt.replace(new RegExp(PRESET_PLACEHOLDER), currentPrompt) : `${currentPrompt} ${presetPrompt}`
}
return presetPrompt.includes(PRESET_PLACEHOLDER)
? presetPrompt.replace(new RegExp(PRESET_PLACEHOLDER), currentPrompt)
: `${currentPrompt} ${presetPrompt}`;
};
export const usePresetModifiedPrompts = () => {
const activeStylePreset = useAppSelector(s => s.stylePreset.activeStylePreset)
const { positivePrompt, negativePrompt } = useAppSelector(s => s.controlLayers.present)
const activeStylePreset = useAppSelector((s) => s.stylePreset.activeStylePreset);
const { positivePrompt, negativePrompt } = useAppSelector((s) => s.controlLayers.present);
if (!activeStylePreset) {
return { presetModifiedPositivePrompt: positivePrompt, presetModifiedNegativePrompt: negativePrompt }
}
if (!activeStylePreset) {
return { presetModifiedPositivePrompt: positivePrompt, presetModifiedNegativePrompt: negativePrompt };
}
const { positive_prompt: presetPositivePrompt, negative_prompt: presetNegativePrompt } = activeStylePreset.preset_data;
const { positive_prompt: presetPositivePrompt, negative_prompt: presetNegativePrompt } =
activeStylePreset.preset_data;
const presetModifiedPositivePrompt = buildPresetModifiedPrompt(presetPositivePrompt, positivePrompt)
const presetModifiedPositivePrompt = buildPresetModifiedPrompt(presetPositivePrompt, positivePrompt);
const presetModifiedNegativePrompt = buildPresetModifiedPrompt(presetNegativePrompt, negativePrompt)
const presetModifiedNegativePrompt = buildPresetModifiedPrompt(presetNegativePrompt, negativePrompt);
return { presetModifiedPositivePrompt, presetModifiedNegativePrompt }
}
return { presetModifiedPositivePrompt, presetModifiedNegativePrompt };
};

View File

@ -1,34 +1,33 @@
import type { PayloadAction } from '@reduxjs/toolkit';
import { createSlice } from '@reduxjs/toolkit';
import type { RootState } from 'app/store/store';
import type { StylePresetFormData } from 'features/stylePresets/components/StylePresetForm';
import type { StylePresetModalState } from './types';
import { StylePresetFormData } from '../components/StylePresetForm';
export const initialState: StylePresetModalState = {
isModalOpen: false,
updatingStylePresetId: null,
prefilledFormData: null
isModalOpen: false,
updatingStylePresetId: null,
prefilledFormData: null,
};
export const stylePresetModalSlice = createSlice({
name: 'stylePresetModal',
initialState: initialState,
reducers: {
isModalOpenChanged: (state, action: PayloadAction<boolean>) => {
state.isModalOpen = action.payload;
},
updatingStylePresetIdChanged: (state, action: PayloadAction<string | null>) => {
state.updatingStylePresetId = action.payload;
},
prefilledFormDataChanged: (state, action: PayloadAction<StylePresetFormData | null>) => {
state.prefilledFormData = action.payload;
},
name: 'stylePresetModal',
initialState: initialState,
reducers: {
isModalOpenChanged: (state, action: PayloadAction<boolean>) => {
state.isModalOpen = action.payload;
},
updatingStylePresetIdChanged: (state, action: PayloadAction<string | null>) => {
state.updatingStylePresetId = action.payload;
},
prefilledFormDataChanged: (state, action: PayloadAction<StylePresetFormData | null>) => {
state.prefilledFormData = action.payload;
},
},
});
export const { isModalOpenChanged, updatingStylePresetIdChanged, prefilledFormDataChanged } = stylePresetModalSlice.actions;
export const { isModalOpenChanged, updatingStylePresetIdChanged, prefilledFormDataChanged } =
stylePresetModalSlice.actions;
export const selectStylePresetModalSlice = (state: RootState) => state.stylePresetModal;

View File

@ -5,34 +5,33 @@ import type { StylePresetRecordWithImage } from 'services/api/endpoints/stylePre
import type { StylePresetState } from './types';
export const initialState: StylePresetState = {
isMenuOpen: false,
activeStylePreset: null,
searchTerm: "",
viewMode: false
isMenuOpen: false,
activeStylePreset: null,
searchTerm: '',
viewMode: false,
};
export const stylePresetSlice = createSlice({
name: 'stylePreset',
initialState: initialState,
reducers: {
isMenuOpenChanged: (state, action: PayloadAction<boolean>) => {
state.isMenuOpen = action.payload;
},
activeStylePresetChanged: (state, action: PayloadAction<StylePresetRecordWithImage | null>) => {
state.activeStylePreset = action.payload;
},
searchTermChanged: (state, action: PayloadAction<string>) => {
state.searchTerm = action.payload;
},
viewModeChanged: (state, action: PayloadAction<boolean>) => {
state.viewMode = action.payload;
},
name: 'stylePreset',
initialState: initialState,
reducers: {
isMenuOpenChanged: (state, action: PayloadAction<boolean>) => {
state.isMenuOpen = action.payload;
},
activeStylePresetChanged: (state, action: PayloadAction<StylePresetRecordWithImage | null>) => {
state.activeStylePreset = action.payload;
},
searchTermChanged: (state, action: PayloadAction<string>) => {
state.searchTerm = action.payload;
},
viewModeChanged: (state, action: PayloadAction<boolean>) => {
state.viewMode = action.payload;
},
},
});
export const { isMenuOpenChanged, activeStylePresetChanged, searchTermChanged, viewModeChanged } = stylePresetSlice.actions;
export const { isMenuOpenChanged, activeStylePresetChanged, searchTermChanged, viewModeChanged } =
stylePresetSlice.actions;
export const selectStylePresetSlice = (state: RootState) => state.stylePreset;

View File

@ -1,17 +1,15 @@
import type { StylePresetRecordWithImage } from "services/api/endpoints/stylePresets";
import { StylePresetFormData } from "../components/StylePresetForm";
import type { StylePresetFormData } from 'features/stylePresets/components/StylePresetForm';
import type { StylePresetRecordWithImage } from 'services/api/endpoints/stylePresets';
export type StylePresetModalState = {
isModalOpen: boolean;
updatingStylePresetId: string | null;
prefilledFormData: StylePresetFormData | null
isModalOpen: boolean;
updatingStylePresetId: string | null;
prefilledFormData: StylePresetFormData | null;
};
export type StylePresetState = {
isMenuOpen: boolean;
activeStylePreset: StylePresetRecordWithImage | null;
searchTerm: string;
viewMode: boolean;
}
isMenuOpen: boolean;
activeStylePreset: StylePresetRecordWithImage | null;
searchTerm: string;
viewMode: boolean;
};

View File

@ -1,4 +1,4 @@
import { PRESET_PLACEHOLDER } from '../hooks/usePresetModifiedPrompts';
import { PRESET_PLACEHOLDER } from 'features/stylePresets/hooks/usePresetModifiedPrompts';
export const getViewModeChunks = (currentPrompt: string, presetPrompt?: string) => {
if (!presetPrompt || !presetPrompt.length) {

View File

@ -9,11 +9,11 @@ import { ControlSettingsAccordion } from 'features/settingsAccordions/components
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 { StylePresetMenu } from 'features/stylePresets/components/StylePresetMenu';
import { StylePresetMenuTrigger } from 'features/stylePresets/components/StylePresetMenuTrigger';
import { OverlayScrollbarsComponent } from 'overlayscrollbars-react';
import type { CSSProperties } from 'react';
import { memo } from 'react';
import { StylePresetMenu } from '../../../stylePresets/components/StylePresetMenu';
import { StylePresetMenuTrigger } from '../../../stylePresets/components/StylePresetMenuTrigger';
const overlayScrollbarsStyles: CSSProperties = {
height: '100%',

View File

@ -1,16 +1,16 @@
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 { AdvancedSettingsAccordion } from 'features/settingsAccordions/components/AdvancedSettingsAccordion/AdvancedSettingsAccordion';
import { GenerationSettingsAccordion } from 'features/settingsAccordions/components/GenerationSettingsAccordion/GenerationSettingsAccordion';
import { UpscaleSettingsAccordion } from 'features/settingsAccordions/components/UpscaleSettingsAccordion/UpscaleSettingsAccordion';
import { StylePresetMenu } from 'features/stylePresets/components/StylePresetMenu';
import { StylePresetMenuTrigger } from 'features/stylePresets/components/StylePresetMenuTrigger';
import { OverlayScrollbarsComponent } from 'overlayscrollbars-react';
import type { CSSProperties } from 'react';
import { memo } from 'react';
import { useAppSelector } from '../../../../app/store/storeHooks';
import { StylePresetMenu } from '../../../stylePresets/components/StylePresetMenu';
import { StylePresetMenuTrigger } from '../../../stylePresets/components/StylePresetMenuTrigger';
const overlayScrollbarsStyles: CSSProperties = {
height: '100%',

View File

@ -2,7 +2,8 @@ import type { paths } from 'services/api/schema';
import { api, buildV1Url, LIST_TAG } from '..';
export type StylePresetRecordWithImage = paths['/api/v1/style_presets/i/{style_preset_id}']['get']['responses']['200']['content']['application/json']
export type StylePresetRecordWithImage =
paths['/api/v1/style_presets/i/{style_preset_id}']['get']['responses']['200']['content']['application/json'];
/**
* Builds an endpoint URL for the style_presets router
@ -60,7 +61,9 @@ export const stylePresetsApi = api.injectEndpoints({
}),
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']['requestBody']['content']['multipart/form-data'] & { id: string }
paths['/api/v1/style_presets/i/{style_preset_id}']['patch']['requestBody']['content']['multipart/form-data'] & {
id: string;
}
>({
query: ({ id, name, positive_prompt, negative_prompt, image }) => {
const formData = new FormData();
@ -72,12 +75,11 @@ export const stylePresetsApi = api.injectEndpoints({
formData.append('positive_prompt', positive_prompt);
formData.append('negative_prompt', negative_prompt);
return {
url: buildStylePresetsUrl(`i/${id}`),
method: 'PATCH',
body: formData
}
body: formData,
};
},
invalidatesTags: (response, error, { id }) => [
{ type: 'StylePreset', id: LIST_TAG },