mirror of
https://github.com/invoke-ai/InvokeAI
synced 2024-08-30 20:32:17 +00:00
fix path for style_preset_images, fix png type when converting blobs to files, built view mode components
This commit is contained in:
parent
0b0abfbe8f
commit
9a4d075074
@ -47,11 +47,11 @@ async def get_style_preset(
|
|||||||
},
|
},
|
||||||
)
|
)
|
||||||
async def update_style_preset(
|
async def update_style_preset(
|
||||||
|
image: Optional[UploadFile] = File(description="The image file to upload", default=None),
|
||||||
style_preset_id: str = Path(description="The id of the style preset to update"),
|
style_preset_id: str = Path(description="The id of the style preset to update"),
|
||||||
name: str = Form(description="The name of the style preset to create"),
|
name: str = Form(description="The name of the style preset to create"),
|
||||||
positive_prompt: str = Form(description="The positive prompt of the style preset"),
|
positive_prompt: str = Form(description="The positive prompt of the style preset"),
|
||||||
negative_prompt: str = Form(description="The negative prompt of the style preset"),
|
negative_prompt: str = Form(description="The negative prompt of the style preset"),
|
||||||
image: Optional[UploadFile] = File(description="The image file to upload", default=None),
|
|
||||||
) -> StylePresetRecordWithImage:
|
) -> StylePresetRecordWithImage:
|
||||||
"""Updates a style preset"""
|
"""Updates a style preset"""
|
||||||
if image is not None:
|
if image is not None:
|
||||||
@ -73,8 +73,8 @@ async def update_style_preset(
|
|||||||
else:
|
else:
|
||||||
try:
|
try:
|
||||||
ApiDependencies.invoker.services.style_preset_images_service.delete(style_preset_id)
|
ApiDependencies.invoker.services.style_preset_images_service.delete(style_preset_id)
|
||||||
except ValueError as e:
|
except StylePresetImageFileNotFoundException:
|
||||||
raise HTTPException(status_code=409, detail=str(e))
|
pass
|
||||||
|
|
||||||
preset_data = PresetData(positive_prompt=positive_prompt, negative_prompt=negative_prompt)
|
preset_data = PresetData(positive_prompt=positive_prompt, negative_prompt=negative_prompt)
|
||||||
changes = StylePresetChanges(name=name, preset_data=preset_data)
|
changes = StylePresetChanges(name=name, preset_data=preset_data)
|
||||||
|
@ -153,7 +153,7 @@ class InvokeAIAppConfig(BaseSettings):
|
|||||||
db_dir: Path = Field(default=Path("databases"), description="Path to InvokeAI databases directory.")
|
db_dir: Path = Field(default=Path("databases"), description="Path to InvokeAI databases directory.")
|
||||||
outputs_dir: Path = Field(default=Path("outputs"), description="Path to directory for outputs.")
|
outputs_dir: Path = Field(default=Path("outputs"), description="Path to directory for outputs.")
|
||||||
custom_nodes_dir: Path = Field(default=Path("nodes"), description="Path to directory for custom nodes.")
|
custom_nodes_dir: Path = Field(default=Path("nodes"), description="Path to directory for custom nodes.")
|
||||||
style_preset_images_path: Path = Field(default=Path("style_preset_images"), description="Path to directory for style preset images.")
|
style_preset_images_dir: Path = Field(default=Path("style_preset_images"), description="Path to directory for style preset images.")
|
||||||
|
|
||||||
# LOGGING
|
# LOGGING
|
||||||
log_handlers: list[str] = Field(default=["console"], description='Log handler. Valid options are "console", "file=<path>", "syslog=path|address:host:port", "http=<url>".')
|
log_handlers: list[str] = Field(default=["console"], description='Log handler. Valid options are "console", "file=<path>", "syslog=path|address:host:port", "http=<url>".')
|
||||||
@ -301,6 +301,11 @@ class InvokeAIAppConfig(BaseSettings):
|
|||||||
"""Path to the models directory, resolved to an absolute path.."""
|
"""Path to the models directory, resolved to an absolute path.."""
|
||||||
return self._resolve(self.models_dir)
|
return self._resolve(self.models_dir)
|
||||||
|
|
||||||
|
@property
|
||||||
|
def style_preset_images_path(self) -> Path:
|
||||||
|
"""Path to the style preset images directory, resolved to an absolute path.."""
|
||||||
|
return self._resolve(self.style_preset_images_dir)
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def convert_cache_path(self) -> Path:
|
def convert_cache_path(self) -> Path:
|
||||||
"""Path to the converted cache models directory, resolved to an absolute path.."""
|
"""Path to the converted cache models directory, resolved to an absolute path.."""
|
||||||
|
@ -13,12 +13,14 @@ export const useImageUrlToBlob = () => {
|
|||||||
new Promise<Blob | null>((resolve) => {
|
new Promise<Blob | null>((resolve) => {
|
||||||
const img = new Image();
|
const img = new Image();
|
||||||
img.onload = () => {
|
img.onload = () => {
|
||||||
|
console.log("on load")
|
||||||
const canvas = document.createElement('canvas');
|
const canvas = document.createElement('canvas');
|
||||||
canvas.width = img.width;
|
canvas.width = img.width;
|
||||||
canvas.height = img.height;
|
canvas.height = img.height;
|
||||||
|
|
||||||
const context = canvas.getContext('2d');
|
const context = canvas.getContext('2d');
|
||||||
if (!context) {
|
if (!context) {
|
||||||
|
console.log("no context")
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
context.drawImage(img, 0, 0);
|
context.drawImage(img, 0, 0);
|
||||||
|
@ -6,6 +6,7 @@ import { useCopyImageToClipboard } from 'common/hooks/useCopyImageToClipboard';
|
|||||||
import { useDownloadImage } from 'common/hooks/useDownloadImage';
|
import { useDownloadImage } from 'common/hooks/useDownloadImage';
|
||||||
import { setInitialCanvasImage } from 'features/canvas/store/canvasSlice';
|
import { setInitialCanvasImage } from 'features/canvas/store/canvasSlice';
|
||||||
import { imagesToChangeSelected, isModalOpenChanged } from 'features/changeBoardModal/store/slice';
|
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 { iiLayerAdded } from 'features/controlLayers/store/controlLayersSlice';
|
||||||
import { imagesToDeleteSelected } from 'features/deleteImageModal/store/slice';
|
import { imagesToDeleteSelected } from 'features/deleteImageModal/store/slice';
|
||||||
import { useImageActions } from 'features/gallery/hooks/useImageActions';
|
import { useImageActions } from 'features/gallery/hooks/useImageActions';
|
||||||
@ -58,8 +59,17 @@ const SingleSelectionMenuItems = (props: SingleSelectionMenuItemsProps) => {
|
|||||||
const { downloadImage } = useDownloadImage();
|
const { downloadImage } = useDownloadImage();
|
||||||
const templates = useStore($templates);
|
const templates = useStore($templates);
|
||||||
|
|
||||||
const { recallAll, remix, recallSeed, recallPrompts, hasMetadata, hasSeed, hasPrompts, isLoadingMetadata } =
|
const {
|
||||||
useImageActions(imageDTO?.image_name);
|
recallAll,
|
||||||
|
remix,
|
||||||
|
recallSeed,
|
||||||
|
recallPrompts,
|
||||||
|
hasMetadata,
|
||||||
|
hasSeed,
|
||||||
|
hasPrompts,
|
||||||
|
isLoadingMetadata,
|
||||||
|
createAsPreset,
|
||||||
|
} = useImageActions(imageDTO?.image_name);
|
||||||
|
|
||||||
const { getAndLoadEmbeddedWorkflow, getAndLoadEmbeddedWorkflowResult } = useGetAndLoadEmbeddedWorkflow({});
|
const { getAndLoadEmbeddedWorkflow, getAndLoadEmbeddedWorkflowResult } = useGetAndLoadEmbeddedWorkflow({});
|
||||||
|
|
||||||
@ -133,11 +143,6 @@ const SingleSelectionMenuItems = (props: SingleSelectionMenuItemsProps) => {
|
|||||||
dispatch(setActiveTab('upscaling'));
|
dispatch(setActiveTab('upscaling'));
|
||||||
}, [dispatch, imageDTO]);
|
}, [dispatch, imageDTO]);
|
||||||
|
|
||||||
const handleCreatePreset = useCallback(() => {
|
|
||||||
dispatch(createPresetFromImageChanged(imageDTO));
|
|
||||||
dispatch(isMenuOpenChanged(true));
|
|
||||||
}, [dispatch, imageDTO]);
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<MenuItem as="a" href={imageDTO.image_url} target="_blank" icon={<PiShareFatBold />}>
|
<MenuItem as="a" href={imageDTO.image_url} target="_blank" icon={<PiShareFatBold />}>
|
||||||
@ -192,7 +197,7 @@ const SingleSelectionMenuItems = (props: SingleSelectionMenuItemsProps) => {
|
|||||||
</MenuItem>
|
</MenuItem>
|
||||||
<MenuItem
|
<MenuItem
|
||||||
icon={isLoadingMetadata ? <SpinnerIcon /> : <PiPaintBrushBold />}
|
icon={isLoadingMetadata ? <SpinnerIcon /> : <PiPaintBrushBold />}
|
||||||
onClickCapture={handleCreatePreset}
|
onClickCapture={createAsPreset}
|
||||||
isDisabled={isLoadingMetadata || !hasPrompts}
|
isDisabled={isLoadingMetadata || !hasPrompts}
|
||||||
>
|
>
|
||||||
Create Preset
|
Create Preset
|
||||||
|
@ -1,8 +1,12 @@
|
|||||||
import { useAppSelector } from 'app/store/storeHooks';
|
import { useAppDispatch, useAppSelector } from 'app/store/storeHooks';
|
||||||
import { handlers, parseAndRecallAllMetadata, parseAndRecallPrompts } from 'features/metadata/util/handlers';
|
import { handlers, parseAndRecallAllMetadata, parseAndRecallPrompts } from 'features/metadata/util/handlers';
|
||||||
import { activeTabNameSelector } from 'features/ui/store/uiSelectors';
|
import { activeTabNameSelector } from 'features/ui/store/uiSelectors';
|
||||||
import { useCallback, useEffect, useState } from 'react';
|
import { useCallback, useEffect, useState } from 'react';
|
||||||
import { useDebouncedMetadata } from 'services/api/hooks/useDebouncedMetadata';
|
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) => {
|
export const useImageActions = (image_name?: string) => {
|
||||||
const activeTabName = useAppSelector(activeTabNameSelector);
|
const activeTabName = useAppSelector(activeTabNameSelector);
|
||||||
@ -10,6 +14,9 @@ export const useImageActions = (image_name?: string) => {
|
|||||||
const [hasMetadata, setHasMetadata] = useState(false);
|
const [hasMetadata, setHasMetadata] = useState(false);
|
||||||
const [hasSeed, setHasSeed] = useState(false);
|
const [hasSeed, setHasSeed] = useState(false);
|
||||||
const [hasPrompts, setHasPrompts] = useState(false);
|
const [hasPrompts, setHasPrompts] = useState(false);
|
||||||
|
const imageUrlToBlob = useImageUrlToBlob();
|
||||||
|
const dispatch = useAppDispatch()
|
||||||
|
const { data: imageDTO } = useGetImageDTOQuery(image_name ?? skipToken)
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const parseMetadata = async () => {
|
const parseMetadata = async () => {
|
||||||
@ -61,5 +68,17 @@ export const useImageActions = (image_name?: string) => {
|
|||||||
parseAndRecallPrompts(metadata);
|
parseAndRecallPrompts(metadata);
|
||||||
}, [metadata]);
|
}, [metadata]);
|
||||||
|
|
||||||
return { recallAll, remix, recallSeed, recallPrompts, hasMetadata, hasSeed, hasPrompts, isLoadingMetadata };
|
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)
|
||||||
|
|
||||||
|
dispatch(prefilledFormDataChanged({ name: "", positivePrompt, negativePrompt, image: imageBlob ? new File([imageBlob], "stylePreset.png", { type: 'image/png', }) : null }))
|
||||||
|
dispatch(isModalOpenChanged(true))
|
||||||
|
}
|
||||||
|
|
||||||
|
}, [image_name, metadata, dispatch, imageDTO])
|
||||||
|
|
||||||
|
return { recallAll, remix, recallSeed, recallPrompts, hasMetadata, hasSeed, hasPrompts, isLoadingMetadata, createAsPreset };
|
||||||
};
|
};
|
||||||
|
@ -125,7 +125,7 @@ const parseCreatedBy: MetadataParseFunc<string> = (metadata) => getProperty(meta
|
|||||||
|
|
||||||
const parseGenerationMode: MetadataParseFunc<string> = (metadata) => getProperty(metadata, 'generation_mode', isString);
|
const parseGenerationMode: MetadataParseFunc<string> = (metadata) => getProperty(metadata, 'generation_mode', isString);
|
||||||
|
|
||||||
const parsePositivePrompt: MetadataParseFunc<ParameterPositivePrompt> = (metadata) =>
|
export const parsePositivePrompt: MetadataParseFunc<ParameterPositivePrompt> = (metadata) =>
|
||||||
getProperty(metadata, 'positive_prompt', isParameterPositivePrompt);
|
getProperty(metadata, 'positive_prompt', isParameterPositivePrompt);
|
||||||
|
|
||||||
const parseNegativePrompt: MetadataParseFunc<ParameterNegativePrompt> = (metadata) =>
|
const parseNegativePrompt: MetadataParseFunc<ParameterNegativePrompt> = (metadata) =>
|
||||||
|
@ -7,10 +7,15 @@ import { PromptPopover } from 'features/prompt/PromptPopover';
|
|||||||
import { usePrompt } from 'features/prompt/usePrompt';
|
import { usePrompt } from 'features/prompt/usePrompt';
|
||||||
import { memo, useCallback, useRef } from 'react';
|
import { memo, useCallback, useRef } from 'react';
|
||||||
import { useTranslation } from 'react-i18next';
|
import { useTranslation } from 'react-i18next';
|
||||||
|
import { ViewModePrompt } from '../Prompts/ViewModePrompt';
|
||||||
|
|
||||||
|
const DEFAULT_HEIGHT = 20;
|
||||||
|
|
||||||
export const ParamNegativePrompt = memo(() => {
|
export const ParamNegativePrompt = memo(() => {
|
||||||
const dispatch = useAppDispatch();
|
const dispatch = useAppDispatch();
|
||||||
const prompt = useAppSelector((s) => s.controlLayers.present.negativePrompt);
|
const prompt = useAppSelector((s) => s.controlLayers.present.negativePrompt);
|
||||||
|
const viewMode = useAppSelector((s) => s.stylePreset.viewMode);
|
||||||
|
const activeStylePreset = useAppSelector((s) => s.stylePreset.activeStylePreset);
|
||||||
const textareaRef = useRef<HTMLTextAreaElement>(null);
|
const textareaRef = useRef<HTMLTextAreaElement>(null);
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
const _onChange = useCallback(
|
const _onChange = useCallback(
|
||||||
@ -25,6 +30,16 @@ export const ParamNegativePrompt = memo(() => {
|
|||||||
onChange: _onChange,
|
onChange: _onChange,
|
||||||
});
|
});
|
||||||
|
|
||||||
|
if (viewMode) {
|
||||||
|
return (
|
||||||
|
<ViewModePrompt
|
||||||
|
prompt={prompt}
|
||||||
|
presetPrompt={activeStylePreset?.preset_data.negative_prompt || ''}
|
||||||
|
height={DEFAULT_HEIGHT}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<PromptPopover isOpen={isOpen} onClose={onClose} onSelect={onSelect} width={textareaRef.current?.clientWidth}>
|
<PromptPopover isOpen={isOpen} onClose={onClose} onSelect={onSelect} width={textareaRef.current?.clientWidth}>
|
||||||
<Box pos="relative">
|
<Box pos="relative">
|
||||||
@ -39,6 +54,7 @@ export const ParamNegativePrompt = memo(() => {
|
|||||||
fontSize="sm"
|
fontSize="sm"
|
||||||
variant="darkFilled"
|
variant="darkFilled"
|
||||||
paddingRight={30}
|
paddingRight={30}
|
||||||
|
minH={DEFAULT_HEIGHT}
|
||||||
/>
|
/>
|
||||||
<PromptOverlayButtonWrapper>
|
<PromptOverlayButtonWrapper>
|
||||||
<AddPromptTriggerButton isOpen={isOpen} onOpen={onOpen} />
|
<AddPromptTriggerButton isOpen={isOpen} onOpen={onOpen} />
|
||||||
|
@ -11,11 +11,16 @@ import { memo, useCallback, useRef } from 'react';
|
|||||||
import type { HotkeyCallback } from 'react-hotkeys-hook';
|
import type { HotkeyCallback } from 'react-hotkeys-hook';
|
||||||
import { useHotkeys } from 'react-hotkeys-hook';
|
import { useHotkeys } from 'react-hotkeys-hook';
|
||||||
import { useTranslation } from 'react-i18next';
|
import { useTranslation } from 'react-i18next';
|
||||||
|
import { ViewModePrompt } from '../Prompts/ViewModePrompt';
|
||||||
|
|
||||||
|
const DEFAULT_HEIGHT = 28;
|
||||||
|
|
||||||
export const ParamPositivePrompt = memo(() => {
|
export const ParamPositivePrompt = memo(() => {
|
||||||
const dispatch = useAppDispatch();
|
const dispatch = useAppDispatch();
|
||||||
const prompt = useAppSelector((s) => s.controlLayers.present.positivePrompt);
|
const prompt = useAppSelector((s) => s.controlLayers.present.positivePrompt);
|
||||||
const baseModel = useAppSelector((s) => s.generation.model)?.base;
|
const baseModel = useAppSelector((s) => s.generation.model)?.base;
|
||||||
|
const viewMode = useAppSelector((s) => s.stylePreset.viewMode);
|
||||||
|
const activeStylePreset = useAppSelector((s) => s.stylePreset.activeStylePreset);
|
||||||
|
|
||||||
const textareaRef = useRef<HTMLTextAreaElement>(null);
|
const textareaRef = useRef<HTMLTextAreaElement>(null);
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
@ -41,6 +46,16 @@ export const ParamPositivePrompt = memo(() => {
|
|||||||
|
|
||||||
useHotkeys('alt+a', focus, []);
|
useHotkeys('alt+a', focus, []);
|
||||||
|
|
||||||
|
if (viewMode) {
|
||||||
|
return (
|
||||||
|
<ViewModePrompt
|
||||||
|
prompt={prompt}
|
||||||
|
presetPrompt={activeStylePreset?.preset_data.positive_prompt || ''}
|
||||||
|
height={DEFAULT_HEIGHT}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<PromptPopover isOpen={isOpen} onClose={onClose} onSelect={onSelect} width={textareaRef.current?.clientWidth}>
|
<PromptPopover isOpen={isOpen} onClose={onClose} onSelect={onSelect} width={textareaRef.current?.clientWidth}>
|
||||||
<Box pos="relative">
|
<Box pos="relative">
|
||||||
@ -51,7 +66,7 @@ export const ParamPositivePrompt = memo(() => {
|
|||||||
value={prompt}
|
value={prompt}
|
||||||
placeholder={t('parameters.globalPositivePromptPlaceholder')}
|
placeholder={t('parameters.globalPositivePromptPlaceholder')}
|
||||||
onChange={onChange}
|
onChange={onChange}
|
||||||
minH={28}
|
minH={DEFAULT_HEIGHT}
|
||||||
onKeyDown={onKeyDown}
|
onKeyDown={onKeyDown}
|
||||||
variant="darkFilled"
|
variant="darkFilled"
|
||||||
paddingRight={30}
|
paddingRight={30}
|
||||||
|
@ -7,7 +7,6 @@ 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 { usePresetModifiedPrompts } from 'features/stylePresets/hooks/usePresetModifiedPrompts';
|
|
||||||
import { memo } from 'react';
|
import { memo } from 'react';
|
||||||
|
|
||||||
const concatPromptsSelector = createSelector(
|
const concatPromptsSelector = createSelector(
|
||||||
@ -19,14 +18,11 @@ const concatPromptsSelector = createSelector(
|
|||||||
|
|
||||||
export const Prompts = memo(() => {
|
export const Prompts = memo(() => {
|
||||||
const shouldConcatPrompts = useAppSelector(concatPromptsSelector);
|
const shouldConcatPrompts = useAppSelector(concatPromptsSelector);
|
||||||
const { presetModifiedPositivePrompt, presetModifiedNegativePrompt } = usePresetModifiedPrompts();
|
|
||||||
return (
|
return (
|
||||||
<Flex flexDir="column" gap={2}>
|
<Flex flexDir="column" gap={2}>
|
||||||
<ParamPositivePrompt />
|
<ParamPositivePrompt />
|
||||||
<Flex>{presetModifiedPositivePrompt}</Flex>
|
|
||||||
{!shouldConcatPrompts && <ParamSDXLPositiveStylePrompt />}
|
{!shouldConcatPrompts && <ParamSDXLPositiveStylePrompt />}
|
||||||
<ParamNegativePrompt />
|
<ParamNegativePrompt />
|
||||||
<Flex>{presetModifiedNegativePrompt}</Flex>
|
|
||||||
{!shouldConcatPrompts && <ParamSDXLNegativeStylePrompt />}
|
{!shouldConcatPrompts && <ParamSDXLNegativeStylePrompt />}
|
||||||
</Flex>
|
</Flex>
|
||||||
);
|
);
|
||||||
|
@ -0,0 +1,67 @@
|
|||||||
|
import { Flex, Icon, Text, Tooltip, Box } from '@invoke-ai/ui-library';
|
||||||
|
import { useAppDispatch, useAppSelector } from '../../../../app/store/storeHooks';
|
||||||
|
import { useCallback, useMemo } from 'react';
|
||||||
|
import { PiEyeBold, PiQuestionBold } from 'react-icons/pi';
|
||||||
|
import { viewModeChanged } from '../../../stylePresets/store/stylePresetSlice';
|
||||||
|
import { getViewModeChunks } from '../../../stylePresets/util/getViewModeChunks';
|
||||||
|
|
||||||
|
export const ViewModePrompt = ({
|
||||||
|
presetPrompt,
|
||||||
|
prompt,
|
||||||
|
height,
|
||||||
|
}: {
|
||||||
|
presetPrompt: string;
|
||||||
|
prompt: string;
|
||||||
|
height: number;
|
||||||
|
}) => {
|
||||||
|
const dispatch = useAppDispatch();
|
||||||
|
|
||||||
|
const presetChunks = useMemo(() => {
|
||||||
|
return getViewModeChunks(prompt, presetPrompt);
|
||||||
|
}, [presetPrompt, prompt]);
|
||||||
|
|
||||||
|
const handleExitViewMode = useCallback(() => {
|
||||||
|
dispatch(viewModeChanged(false));
|
||||||
|
}, [dispatch]);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Flex
|
||||||
|
flexDir="column"
|
||||||
|
layerStyle="second"
|
||||||
|
padding="8px 10px"
|
||||||
|
borderRadius="base"
|
||||||
|
height={height}
|
||||||
|
onClick={handleExitViewMode}
|
||||||
|
justifyContent="space-between"
|
||||||
|
position="relative"
|
||||||
|
>
|
||||||
|
<Flex overflow="scroll">
|
||||||
|
<Text fontSize="sm" lineHeight="1rem">
|
||||||
|
{presetChunks.map((chunk, index) => {
|
||||||
|
return (
|
||||||
|
chunk && (
|
||||||
|
<Text as="span" color={index === 1 ? 'white' : 'base.300'}>
|
||||||
|
{chunk.trim()}{' '}
|
||||||
|
</Text>
|
||||||
|
)
|
||||||
|
);
|
||||||
|
})}
|
||||||
|
</Text>
|
||||||
|
</Flex>
|
||||||
|
|
||||||
|
<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.'
|
||||||
|
}
|
||||||
|
>
|
||||||
|
<Flex>
|
||||||
|
<Icon as={PiEyeBold} color="base.500" boxSize="12px" />
|
||||||
|
</Flex>
|
||||||
|
</Tooltip>
|
||||||
|
</Flex>
|
||||||
|
</Box>
|
||||||
|
</Flex>
|
||||||
|
);
|
||||||
|
};
|
@ -0,0 +1,98 @@
|
|||||||
|
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,
|
||||||
|
};
|
||||||
|
};
|
@ -2,14 +2,15 @@ import { Flex, IconButton, Text, Box, ButtonGroup } from '@invoke-ai/ui-library'
|
|||||||
import { useAppDispatch, useAppSelector } from 'app/store/storeHooks';
|
import { useAppDispatch, useAppSelector } from 'app/store/storeHooks';
|
||||||
import { negativePromptChanged, positivePromptChanged } from 'features/controlLayers/store/controlLayersSlice';
|
import { negativePromptChanged, positivePromptChanged } from 'features/controlLayers/store/controlLayersSlice';
|
||||||
import { usePresetModifiedPrompts } from 'features/stylePresets/hooks/usePresetModifiedPrompts';
|
import { usePresetModifiedPrompts } from 'features/stylePresets/hooks/usePresetModifiedPrompts';
|
||||||
import { activeStylePresetChanged } from 'features/stylePresets/store/stylePresetSlice';
|
import { activeStylePresetChanged, viewModeChanged } from 'features/stylePresets/store/stylePresetSlice';
|
||||||
import type { MouseEventHandler } from 'react';
|
import type { MouseEventHandler } from 'react';
|
||||||
import { useCallback } from 'react';
|
import { useCallback } from 'react';
|
||||||
import { PiStackSimpleBold, PiXBold } from 'react-icons/pi';
|
import { PiEyeBold, PiStackSimpleBold, PiXBold } from 'react-icons/pi';
|
||||||
import StylePresetImage from './StylePresetImage';
|
import StylePresetImage from './StylePresetImage';
|
||||||
|
|
||||||
export const ActiveStylePreset = () => {
|
export const ActiveStylePreset = () => {
|
||||||
const { activeStylePreset } = useAppSelector((s) => s.stylePreset);
|
const { activeStylePreset, viewMode } = useAppSelector((s) => s.stylePreset);
|
||||||
|
|
||||||
const dispatch = useAppDispatch();
|
const dispatch = useAppDispatch();
|
||||||
|
|
||||||
const { presetModifiedPositivePrompt, presetModifiedNegativePrompt } = usePresetModifiedPrompts();
|
const { presetModifiedPositivePrompt, presetModifiedNegativePrompt } = usePresetModifiedPrompts();
|
||||||
@ -17,6 +18,7 @@ export const ActiveStylePreset = () => {
|
|||||||
const handleClearActiveStylePreset = useCallback<MouseEventHandler<HTMLButtonElement>>(
|
const handleClearActiveStylePreset = useCallback<MouseEventHandler<HTMLButtonElement>>(
|
||||||
(e) => {
|
(e) => {
|
||||||
e.stopPropagation();
|
e.stopPropagation();
|
||||||
|
dispatch(viewModeChanged(false));
|
||||||
dispatch(activeStylePresetChanged(null));
|
dispatch(activeStylePresetChanged(null));
|
||||||
},
|
},
|
||||||
[dispatch]
|
[dispatch]
|
||||||
@ -27,11 +29,20 @@ export const ActiveStylePreset = () => {
|
|||||||
e.stopPropagation();
|
e.stopPropagation();
|
||||||
dispatch(positivePromptChanged(presetModifiedPositivePrompt));
|
dispatch(positivePromptChanged(presetModifiedPositivePrompt));
|
||||||
dispatch(negativePromptChanged(presetModifiedNegativePrompt));
|
dispatch(negativePromptChanged(presetModifiedNegativePrompt));
|
||||||
|
dispatch(viewModeChanged(false));
|
||||||
dispatch(activeStylePresetChanged(null));
|
dispatch(activeStylePresetChanged(null));
|
||||||
},
|
},
|
||||||
[dispatch, presetModifiedPositivePrompt, presetModifiedNegativePrompt]
|
[dispatch, presetModifiedPositivePrompt, presetModifiedNegativePrompt]
|
||||||
);
|
);
|
||||||
|
|
||||||
|
const handleToggleViewMode = useCallback<MouseEventHandler<HTMLButtonElement>>(
|
||||||
|
(e) => {
|
||||||
|
e.stopPropagation();
|
||||||
|
dispatch(viewModeChanged(!viewMode));
|
||||||
|
},
|
||||||
|
[dispatch, viewMode]
|
||||||
|
);
|
||||||
|
|
||||||
if (!activeStylePreset) {
|
if (!activeStylePreset) {
|
||||||
return (
|
return (
|
||||||
<Flex h="25px" alignItems="center">
|
<Flex h="25px" alignItems="center">
|
||||||
@ -47,12 +58,20 @@ export const ActiveStylePreset = () => {
|
|||||||
<Flex gap="2" alignItems="center">
|
<Flex gap="2" alignItems="center">
|
||||||
<StylePresetImage imageWidth={25} presetImageUrl={activeStylePreset.image} />
|
<StylePresetImage imageWidth={25} presetImageUrl={activeStylePreset.image} />
|
||||||
<Flex flexDir="column">
|
<Flex flexDir="column">
|
||||||
<Text fontSize="sm" fontWeight="semibold" color="base.300">
|
<Text fontSize="sm" fontWeight="semibold" color="base.300" noOfLines={1}>
|
||||||
{activeStylePreset.name}
|
{activeStylePreset.name}
|
||||||
</Text>
|
</Text>
|
||||||
</Flex>
|
</Flex>
|
||||||
</Flex>
|
</Flex>
|
||||||
<Flex gap="1">
|
<Flex gap="1">
|
||||||
|
<IconButton
|
||||||
|
onClick={handleToggleViewMode}
|
||||||
|
variant="outline"
|
||||||
|
size="sm"
|
||||||
|
aria-label="View"
|
||||||
|
colorScheme={viewMode ? 'invokeBlue' : 'base'}
|
||||||
|
icon={<PiEyeBold />}
|
||||||
|
/>
|
||||||
<IconButton
|
<IconButton
|
||||||
onClick={handleFlattenPrompts}
|
onClick={handleFlattenPrompts}
|
||||||
variant="outline"
|
variant="outline"
|
||||||
|
@ -1,13 +1,11 @@
|
|||||||
import { Button, Flex, FormControl, FormLabel, Icon, Input, Text } from '@invoke-ai/ui-library';
|
import { Button, Flex, FormControl, FormLabel, Icon, Input, Text } from '@invoke-ai/ui-library';
|
||||||
import { useAppDispatch } from 'app/store/storeHooks';
|
import { useAppDispatch, useAppSelector } from 'app/store/storeHooks';
|
||||||
import { useStylePresetFields } from 'features/stylePresets/hooks/useStylePresetFields';
|
import { isModalOpenChanged, updatingStylePresetIdChanged } from 'features/stylePresets/store/stylePresetModalSlice';
|
||||||
import { isModalOpenChanged, updatingStylePresetChanged } from 'features/stylePresets/store/stylePresetModalSlice';
|
|
||||||
import { toast } from 'features/toast/toast';
|
import { toast } from 'features/toast/toast';
|
||||||
import { useCallback } from 'react';
|
import { useCallback } from 'react';
|
||||||
import type { SubmitHandler } from 'react-hook-form';
|
import type { SubmitHandler } from 'react-hook-form';
|
||||||
import { useForm } from 'react-hook-form';
|
import { useForm } from 'react-hook-form';
|
||||||
import { PiBracketsCurlyBold } from 'react-icons/pi';
|
import { PiBracketsCurlyBold } from 'react-icons/pi';
|
||||||
import type { StylePresetRecordWithImage } from 'services/api/endpoints/stylePresets';
|
|
||||||
import { useCreateStylePresetMutation, useUpdateStylePresetMutation } from 'services/api/endpoints/stylePresets';
|
import { useCreateStylePresetMutation, useUpdateStylePresetMutation } from 'services/api/endpoints/stylePresets';
|
||||||
|
|
||||||
import { StylePresetPromptField } from './StylePresetPromptField';
|
import { StylePresetPromptField } from './StylePresetPromptField';
|
||||||
@ -20,15 +18,20 @@ export type StylePresetFormData = {
|
|||||||
image: File | null;
|
image: File | null;
|
||||||
};
|
};
|
||||||
|
|
||||||
export const StylePresetForm = ({ updatingPreset }: { updatingPreset: StylePresetRecordWithImage | null }) => {
|
export const StylePresetForm = ({ updatingStylePresetId }: { updatingStylePresetId: string | null }) => {
|
||||||
const [createStylePreset] = useCreateStylePresetMutation();
|
const [createStylePreset] = useCreateStylePresetMutation();
|
||||||
const [updateStylePreset] = useUpdateStylePresetMutation();
|
const [updateStylePreset] = useUpdateStylePresetMutation();
|
||||||
const dispatch = useAppDispatch();
|
const dispatch = useAppDispatch();
|
||||||
|
|
||||||
const stylePresetFieldDefaults = useStylePresetFields(updatingPreset);
|
const defaultValues = useAppSelector((s) => s.stylePresetModal.prefilledFormData);
|
||||||
|
|
||||||
const { handleSubmit, control, formState, reset, register } = useForm<StylePresetFormData>({
|
const { handleSubmit, control, formState, reset, register } = useForm<StylePresetFormData>({
|
||||||
defaultValues: stylePresetFieldDefaults,
|
defaultValues: defaultValues || {
|
||||||
|
name: '',
|
||||||
|
positivePrompt: '',
|
||||||
|
negativePrompt: '',
|
||||||
|
image: null,
|
||||||
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
const handleClickSave = useCallback<SubmitHandler<StylePresetFormData>>(
|
const handleClickSave = useCallback<SubmitHandler<StylePresetFormData>>(
|
||||||
@ -41,9 +44,9 @@ export const StylePresetForm = ({ updatingPreset }: { updatingPreset: StylePrese
|
|||||||
};
|
};
|
||||||
|
|
||||||
try {
|
try {
|
||||||
if (updatingPreset) {
|
if (updatingStylePresetId) {
|
||||||
await updateStylePreset({
|
await updateStylePreset({
|
||||||
id: updatingPreset.id,
|
id: updatingStylePresetId,
|
||||||
...payload,
|
...payload,
|
||||||
}).unwrap();
|
}).unwrap();
|
||||||
} else {
|
} else {
|
||||||
@ -56,10 +59,10 @@ export const StylePresetForm = ({ updatingPreset }: { updatingPreset: StylePrese
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
dispatch(updatingStylePresetChanged(null));
|
dispatch(updatingStylePresetIdChanged(null));
|
||||||
dispatch(isModalOpenChanged(false));
|
dispatch(isModalOpenChanged(false));
|
||||||
},
|
},
|
||||||
[dispatch, updatingPreset, updateStylePreset, createStylePreset]
|
[dispatch, updatingStylePresetId, updateStylePreset, createStylePreset]
|
||||||
);
|
);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
@ -1,31 +1,52 @@
|
|||||||
import { Badge, ConfirmationAlertDialog, Flex, IconButton, Text, useDisclosure } from '@invoke-ai/ui-library';
|
import { Badge, ConfirmationAlertDialog, Flex, IconButton, Text, useDisclosure } from '@invoke-ai/ui-library';
|
||||||
import type { MouseEvent } from 'react';
|
import type { MouseEvent } from 'react';
|
||||||
import { useAppDispatch, useAppSelector } from 'app/store/storeHooks';
|
import { useAppDispatch, useAppSelector } from 'app/store/storeHooks';
|
||||||
import ModelImage from 'features/modelManagerV2/subpanels/ModelManagerPanel/ModelImage';
|
import {
|
||||||
import { isModalOpenChanged, updatingStylePresetChanged } from 'features/stylePresets/store/stylePresetModalSlice';
|
isModalOpenChanged,
|
||||||
|
prefilledFormDataChanged,
|
||||||
|
updatingStylePresetIdChanged,
|
||||||
|
} from 'features/stylePresets/store/stylePresetModalSlice';
|
||||||
import { activeStylePresetChanged, isMenuOpenChanged } from 'features/stylePresets/store/stylePresetSlice';
|
import { activeStylePresetChanged, isMenuOpenChanged } from 'features/stylePresets/store/stylePresetSlice';
|
||||||
import { useCallback } from 'react';
|
import { useCallback } from 'react';
|
||||||
import { PiPencilBold, PiTrashBold } from 'react-icons/pi';
|
import { PiPencilBold, PiTrashBold } from 'react-icons/pi';
|
||||||
import type { StylePresetRecordWithImage } from 'services/api/endpoints/stylePresets';
|
import type { StylePresetRecordWithImage } from 'services/api/endpoints/stylePresets';
|
||||||
import { useDeleteStylePresetMutation } from 'services/api/endpoints/stylePresets';
|
import { useDeleteStylePresetMutation } from 'services/api/endpoints/stylePresets';
|
||||||
import StylePresetImage from './StylePresetImage';
|
import StylePresetImage from './StylePresetImage';
|
||||||
|
import { useImageUrlToBlob } from 'common/hooks/useImageUrlToBlob';
|
||||||
|
|
||||||
export const StylePresetListItem = ({ preset }: { preset: StylePresetRecordWithImage }) => {
|
export const StylePresetListItem = ({ preset }: { preset: StylePresetRecordWithImage }) => {
|
||||||
const dispatch = useAppDispatch();
|
const dispatch = useAppDispatch();
|
||||||
const [deleteStylePreset] = useDeleteStylePresetMutation();
|
const [deleteStylePreset] = useDeleteStylePresetMutation();
|
||||||
const activeStylePreset = useAppSelector((s) => s.stylePreset.activeStylePreset);
|
const activeStylePreset = useAppSelector((s) => s.stylePreset.activeStylePreset);
|
||||||
const { isOpen, onOpen, onClose } = useDisclosure();
|
const { isOpen, onOpen, onClose } = useDisclosure();
|
||||||
|
const imageUrlToBlob = useImageUrlToBlob();
|
||||||
|
|
||||||
const handleClickEdit = useCallback(
|
const handleClickEdit = useCallback(
|
||||||
(e: MouseEvent<HTMLButtonElement>) => {
|
async (e: MouseEvent<HTMLButtonElement>) => {
|
||||||
e.stopPropagation();
|
e.stopPropagation();
|
||||||
dispatch(updatingStylePresetChanged(preset));
|
const { name, preset_data } = preset;
|
||||||
|
const { positive_prompt, negative_prompt } = preset_data;
|
||||||
|
let imageBlob = null;
|
||||||
|
if (preset.image) {
|
||||||
|
imageBlob = await imageUrlToBlob(preset.image);
|
||||||
|
}
|
||||||
|
|
||||||
|
dispatch(
|
||||||
|
prefilledFormDataChanged({
|
||||||
|
name,
|
||||||
|
positivePrompt: positive_prompt,
|
||||||
|
negativePrompt: negative_prompt,
|
||||||
|
image: imageBlob ? new File([imageBlob], `style_preset_${preset.id}.png`, { type: 'image/png' }) : null,
|
||||||
|
})
|
||||||
|
);
|
||||||
|
|
||||||
|
dispatch(updatingStylePresetIdChanged(preset.id));
|
||||||
dispatch(isModalOpenChanged(true));
|
dispatch(isModalOpenChanged(true));
|
||||||
},
|
},
|
||||||
[dispatch, preset]
|
[dispatch, preset]
|
||||||
);
|
);
|
||||||
|
|
||||||
const handleClickApply = useCallback(() => {
|
const handleClickApply = useCallback(async () => {
|
||||||
dispatch(activeStylePresetChanged(preset));
|
dispatch(activeStylePresetChanged(preset));
|
||||||
dispatch(isMenuOpenChanged(false));
|
dispatch(isMenuOpenChanged(false));
|
||||||
}, [dispatch, preset]);
|
}, [dispatch, preset]);
|
||||||
@ -53,14 +74,16 @@ export const StylePresetListItem = ({ preset }: { preset: StylePresetRecordWithI
|
|||||||
_hover={{ backgroundColor: 'base.750' }}
|
_hover={{ backgroundColor: 'base.750' }}
|
||||||
padding="10px"
|
padding="10px"
|
||||||
borderRadius="base"
|
borderRadius="base"
|
||||||
alignItems="center"
|
alignItems="flex-start"
|
||||||
w="full"
|
w="full"
|
||||||
>
|
>
|
||||||
<StylePresetImage presetImageUrl={preset.image} />
|
<StylePresetImage presetImageUrl={preset.image} />
|
||||||
<Flex flexDir="column" w="full">
|
<Flex flexDir="column" w="full">
|
||||||
<Flex w="full" justifyContent="space-between">
|
<Flex w="full" justifyContent="space-between" alignItems="flex-start">
|
||||||
<Flex alignItems="center" gap="2">
|
<Flex alignItems="center" gap="2">
|
||||||
<Text fontSize="md">{preset.name}</Text>
|
<Text fontSize="md" noOfLines={2}>
|
||||||
|
{preset.name}
|
||||||
|
</Text>
|
||||||
{activeStylePreset && activeStylePreset.id === preset.id && (
|
{activeStylePreset && activeStylePreset.id === preset.id && (
|
||||||
<Badge
|
<Badge
|
||||||
color="invokeBlue.400"
|
color="invokeBlue.400"
|
||||||
@ -87,6 +110,7 @@ export const StylePresetListItem = ({ preset }: { preset: StylePresetRecordWithI
|
|||||||
variant="ghost"
|
variant="ghost"
|
||||||
aria-label="Delete"
|
aria-label="Delete"
|
||||||
onClick={handleClickDelete}
|
onClick={handleClickDelete}
|
||||||
|
colorScheme="error"
|
||||||
icon={<PiTrashBold />}
|
icon={<PiTrashBold />}
|
||||||
/>
|
/>
|
||||||
</Flex>
|
</Flex>
|
||||||
|
@ -1,7 +1,11 @@
|
|||||||
import { Flex, IconButton, Text } from '@invoke-ai/ui-library';
|
import { Flex, IconButton, Text } from '@invoke-ai/ui-library';
|
||||||
import { EMPTY_ARRAY } from 'app/store/constants';
|
import { EMPTY_ARRAY } from 'app/store/constants';
|
||||||
import { useAppDispatch, useAppSelector } from 'app/store/storeHooks';
|
import { useAppDispatch, useAppSelector } from 'app/store/storeHooks';
|
||||||
import { isModalOpenChanged, updatingStylePresetChanged } from 'features/stylePresets/store/stylePresetModalSlice';
|
import {
|
||||||
|
isModalOpenChanged,
|
||||||
|
prefilledFormDataChanged,
|
||||||
|
updatingStylePresetIdChanged,
|
||||||
|
} from 'features/stylePresets/store/stylePresetModalSlice';
|
||||||
import { useCallback } from 'react';
|
import { useCallback } from 'react';
|
||||||
import { PiPlusBold } from 'react-icons/pi';
|
import { PiPlusBold } from 'react-icons/pi';
|
||||||
import type { StylePresetRecordWithImage } from 'services/api/endpoints/stylePresets';
|
import type { StylePresetRecordWithImage } from 'services/api/endpoints/stylePresets';
|
||||||
@ -40,12 +44,13 @@ export const StylePresetMenu = () => {
|
|||||||
const dispatch = useAppDispatch();
|
const dispatch = useAppDispatch();
|
||||||
|
|
||||||
const handleClickAddNew = useCallback(() => {
|
const handleClickAddNew = useCallback(() => {
|
||||||
dispatch(updatingStylePresetChanged(null));
|
dispatch(prefilledFormDataChanged(null));
|
||||||
|
dispatch(updatingStylePresetIdChanged(null));
|
||||||
dispatch(isModalOpenChanged(true));
|
dispatch(isModalOpenChanged(true));
|
||||||
}, [dispatch]);
|
}, [dispatch]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Flex flexDir="column" gap="2" padding="10px">
|
<Flex flexDir="column" gap="2" padding="10px" layerStyle="second">
|
||||||
<Flex alignItems="center" gap="10" w="full" justifyContent="space-between">
|
<Flex alignItems="center" gap="10" w="full" justifyContent="space-between">
|
||||||
<StylePresetSearch />
|
<StylePresetSearch />
|
||||||
<IconButton
|
<IconButton
|
||||||
|
@ -8,6 +8,7 @@ import { ActiveStylePreset } from './ActiveStylePreset';
|
|||||||
|
|
||||||
export const StylePresetMenuTrigger = () => {
|
export const StylePresetMenuTrigger = () => {
|
||||||
const isMenuOpen = useAppSelector((s) => s.stylePreset.isMenuOpen);
|
const isMenuOpen = useAppSelector((s) => s.stylePreset.isMenuOpen);
|
||||||
|
const activeStylePreset = useAppSelector((s) => s.stylePreset.activeStylePreset);
|
||||||
const dispatch = useAppDispatch();
|
const dispatch = useAppDispatch();
|
||||||
|
|
||||||
const handleToggle = useCallback(() => {
|
const handleToggle = useCallback(() => {
|
||||||
@ -16,7 +17,7 @@ export const StylePresetMenuTrigger = () => {
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<Flex
|
<Flex
|
||||||
as="button"
|
// as="button"
|
||||||
onClick={handleToggle}
|
onClick={handleToggle}
|
||||||
backgroundColor="base.800"
|
backgroundColor="base.800"
|
||||||
justifyContent="space-between"
|
justifyContent="space-between"
|
||||||
@ -24,6 +25,8 @@ export const StylePresetMenuTrigger = () => {
|
|||||||
padding="5px 10px"
|
padding="5px 10px"
|
||||||
borderRadius="base"
|
borderRadius="base"
|
||||||
gap="2"
|
gap="2"
|
||||||
|
borderTop="2px solid transparent"
|
||||||
|
borderColor={activeStylePreset ? 'invokeBlue.200' : 'transparent'}
|
||||||
>
|
>
|
||||||
<ActiveStylePreset />
|
<ActiveStylePreset />
|
||||||
|
|
||||||
|
@ -8,7 +8,7 @@ import {
|
|||||||
ModalOverlay,
|
ModalOverlay,
|
||||||
} from '@invoke-ai/ui-library';
|
} from '@invoke-ai/ui-library';
|
||||||
import { useAppDispatch, useAppSelector } from 'app/store/storeHooks';
|
import { useAppDispatch, useAppSelector } from 'app/store/storeHooks';
|
||||||
import { isModalOpenChanged, updatingStylePresetChanged } from 'features/stylePresets/store/stylePresetModalSlice';
|
import { isModalOpenChanged, updatingStylePresetIdChanged } from 'features/stylePresets/store/stylePresetModalSlice';
|
||||||
import { useCallback, useMemo } from 'react';
|
import { useCallback, useMemo } from 'react';
|
||||||
|
|
||||||
import { StylePresetForm } from './StylePresetForm';
|
import { StylePresetForm } from './StylePresetForm';
|
||||||
@ -16,14 +16,14 @@ import { StylePresetForm } from './StylePresetForm';
|
|||||||
export const StylePresetModal = () => {
|
export const StylePresetModal = () => {
|
||||||
const dispatch = useAppDispatch();
|
const dispatch = useAppDispatch();
|
||||||
const isModalOpen = useAppSelector((s) => s.stylePresetModal.isModalOpen);
|
const isModalOpen = useAppSelector((s) => s.stylePresetModal.isModalOpen);
|
||||||
const updatingStylePreset = useAppSelector((s) => s.stylePresetModal.updatingStylePreset);
|
const updatingStylePresetId = useAppSelector((s) => s.stylePresetModal.updatingStylePresetId);
|
||||||
|
|
||||||
const modalTitle = useMemo(() => {
|
const modalTitle = useMemo(() => {
|
||||||
return updatingStylePreset ? `Update Style Preset` : `Create Style Preset`;
|
return updatingStylePresetId ? `Update Style Preset` : `Create Style Preset`;
|
||||||
}, [updatingStylePreset]);
|
}, [updatingStylePresetId]);
|
||||||
|
|
||||||
const handleCloseModal = useCallback(() => {
|
const handleCloseModal = useCallback(() => {
|
||||||
dispatch(updatingStylePresetChanged(null));
|
dispatch(updatingStylePresetIdChanged(null));
|
||||||
dispatch(isModalOpenChanged(false));
|
dispatch(isModalOpenChanged(false));
|
||||||
}, [dispatch]);
|
}, [dispatch]);
|
||||||
|
|
||||||
@ -34,7 +34,7 @@ export const StylePresetModal = () => {
|
|||||||
<ModalHeader>{modalTitle}</ModalHeader>
|
<ModalHeader>{modalTitle}</ModalHeader>
|
||||||
<ModalCloseButton />
|
<ModalCloseButton />
|
||||||
<ModalBody display="flex" flexDir="column" gap={4}>
|
<ModalBody display="flex" flexDir="column" gap={4}>
|
||||||
<StylePresetForm updatingPreset={updatingStylePreset} />
|
<StylePresetForm updatingStylePresetId={updatingStylePresetId} />
|
||||||
</ModalBody>
|
</ModalBody>
|
||||||
<ModalFooter />
|
<ModalFooter />
|
||||||
</ModalContent>
|
</ModalContent>
|
||||||
|
@ -1,45 +0,0 @@
|
|||||||
import { useCallback, useMemo } from 'react';
|
|
||||||
import type { StylePresetRecordWithImage } from 'services/api/endpoints/stylePresets';
|
|
||||||
import { useAppSelector } from '../../../app/store/storeHooks';
|
|
||||||
import { useDebouncedMetadata } from '../../../services/api/hooks/useDebouncedMetadata';
|
|
||||||
import { handlers } from '../../metadata/util/handlers';
|
|
||||||
import { useImageUrlToBlob } from '../../../common/hooks/useImageUrlToBlob';
|
|
||||||
|
|
||||||
|
|
||||||
export const useStylePresetFields = (preset: StylePresetRecordWithImage | null) => {
|
|
||||||
const createPresetFromImage = useAppSelector(s => s.stylePresetModal.createPresetFromImage)
|
|
||||||
|
|
||||||
const imageUrlToBlob = useImageUrlToBlob();
|
|
||||||
|
|
||||||
const getStylePresetFieldDefaults = useCallback(async () => {
|
|
||||||
if (preset) {
|
|
||||||
let file: File | null = null;
|
|
||||||
if (preset.image) {
|
|
||||||
const blob = await imageUrlToBlob(preset.image);
|
|
||||||
if (blob) {
|
|
||||||
file = new File([blob], "name");
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
return {
|
|
||||||
name: preset.name,
|
|
||||||
positivePrompt: preset.preset_data.positive_prompt || "",
|
|
||||||
negativePrompt: preset.preset_data.negative_prompt || "",
|
|
||||||
image: file
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
return {
|
|
||||||
name: "",
|
|
||||||
positivePrompt: "",
|
|
||||||
negativePrompt: "",
|
|
||||||
image: null
|
|
||||||
};
|
|
||||||
}, [
|
|
||||||
preset
|
|
||||||
]);
|
|
||||||
|
|
||||||
return getStylePresetFieldDefaults;
|
|
||||||
};
|
|
@ -1,16 +1,15 @@
|
|||||||
import type { PayloadAction } from '@reduxjs/toolkit';
|
import type { PayloadAction } from '@reduxjs/toolkit';
|
||||||
import { createSlice } from '@reduxjs/toolkit';
|
import { createSlice } from '@reduxjs/toolkit';
|
||||||
import type { RootState } from 'app/store/store';
|
import type { RootState } from 'app/store/store';
|
||||||
import type { StylePresetRecordWithImage } from 'services/api/endpoints/stylePresets';
|
|
||||||
|
|
||||||
import type { StylePresetModalState, StylePresetPrefillOptions } from './types';
|
import type { StylePresetModalState } from './types';
|
||||||
import { ImageDTO } from '../../../services/api/types';
|
import { StylePresetFormData } from '../components/StylePresetForm';
|
||||||
|
|
||||||
|
|
||||||
export const initialState: StylePresetModalState = {
|
export const initialState: StylePresetModalState = {
|
||||||
isModalOpen: false,
|
isModalOpen: false,
|
||||||
updatingStylePreset: null,
|
updatingStylePresetId: null,
|
||||||
createPresetFromImage: null
|
prefilledFormData: null
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
@ -21,15 +20,15 @@ export const stylePresetModalSlice = createSlice({
|
|||||||
isModalOpenChanged: (state, action: PayloadAction<boolean>) => {
|
isModalOpenChanged: (state, action: PayloadAction<boolean>) => {
|
||||||
state.isModalOpen = action.payload;
|
state.isModalOpen = action.payload;
|
||||||
},
|
},
|
||||||
updatingStylePresetChanged: (state, action: PayloadAction<StylePresetRecordWithImage | null>) => {
|
updatingStylePresetIdChanged: (state, action: PayloadAction<string | null>) => {
|
||||||
state.updatingStylePreset = action.payload;
|
state.updatingStylePresetId = action.payload;
|
||||||
},
|
},
|
||||||
createPresetFromImageChanged: (state, action: PayloadAction<ImageDTO | null>) => {
|
prefilledFormDataChanged: (state, action: PayloadAction<StylePresetFormData | null>) => {
|
||||||
state.createPresetFromImage = action.payload;
|
state.prefilledFormData = action.payload;
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
export const { isModalOpenChanged, updatingStylePresetChanged, createPresetFromImageChanged } = stylePresetModalSlice.actions;
|
export const { isModalOpenChanged, updatingStylePresetIdChanged, prefilledFormDataChanged } = stylePresetModalSlice.actions;
|
||||||
|
|
||||||
export const selectStylePresetModalSlice = (state: RootState) => state.stylePresetModal;
|
export const selectStylePresetModalSlice = (state: RootState) => state.stylePresetModal;
|
||||||
|
@ -9,7 +9,8 @@ import type { StylePresetState } from './types';
|
|||||||
export const initialState: StylePresetState = {
|
export const initialState: StylePresetState = {
|
||||||
isMenuOpen: false,
|
isMenuOpen: false,
|
||||||
activeStylePreset: null,
|
activeStylePreset: null,
|
||||||
searchTerm: ""
|
searchTerm: "",
|
||||||
|
viewMode: false
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
@ -26,9 +27,12 @@ export const stylePresetSlice = createSlice({
|
|||||||
searchTermChanged: (state, action: PayloadAction<string>) => {
|
searchTermChanged: (state, action: PayloadAction<string>) => {
|
||||||
state.searchTerm = action.payload;
|
state.searchTerm = action.payload;
|
||||||
},
|
},
|
||||||
|
viewModeChanged: (state, action: PayloadAction<boolean>) => {
|
||||||
|
state.viewMode = action.payload;
|
||||||
|
},
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
export const { isMenuOpenChanged, activeStylePresetChanged, searchTermChanged } = stylePresetSlice.actions;
|
export const { isMenuOpenChanged, activeStylePresetChanged, searchTermChanged, viewModeChanged } = stylePresetSlice.actions;
|
||||||
|
|
||||||
export const selectStylePresetSlice = (state: RootState) => state.stylePreset;
|
export const selectStylePresetSlice = (state: RootState) => state.stylePreset;
|
||||||
|
@ -1,21 +1,17 @@
|
|||||||
import type { StylePresetRecordWithImage } from "services/api/endpoints/stylePresets";
|
import type { StylePresetRecordWithImage } from "services/api/endpoints/stylePresets";
|
||||||
import { ImageDTO } from "../../../services/api/types";
|
import { StylePresetFormData } from "../components/StylePresetForm";
|
||||||
|
|
||||||
export type StylePresetModalState = {
|
export type StylePresetModalState = {
|
||||||
isModalOpen: boolean;
|
isModalOpen: boolean;
|
||||||
updatingStylePreset: StylePresetRecordWithImage | null;
|
updatingStylePresetId: string | null;
|
||||||
createPresetFromImage: ImageDTO | null
|
prefilledFormData: StylePresetFormData | null
|
||||||
};
|
};
|
||||||
|
|
||||||
export type StylePresetPrefillOptions = {
|
|
||||||
positivePrompt: string;
|
|
||||||
negativePrompt: string;
|
|
||||||
image: File;
|
|
||||||
}
|
|
||||||
|
|
||||||
export type StylePresetState = {
|
export type StylePresetState = {
|
||||||
isMenuOpen: boolean;
|
isMenuOpen: boolean;
|
||||||
activeStylePreset: StylePresetRecordWithImage | null;
|
activeStylePreset: StylePresetRecordWithImage | null;
|
||||||
searchTerm: string
|
searchTerm: string;
|
||||||
|
viewMode: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -0,0 +1,15 @@
|
|||||||
|
import { PRESET_PLACEHOLDER } from '../hooks/usePresetModifiedPrompts';
|
||||||
|
|
||||||
|
export const getViewModeChunks = (currentPrompt: string, presetPrompt?: string) => {
|
||||||
|
if (!presetPrompt || !presetPrompt.length) {
|
||||||
|
return ['', currentPrompt, ''];
|
||||||
|
}
|
||||||
|
|
||||||
|
const chunks = presetPrompt.split(PRESET_PLACEHOLDER);
|
||||||
|
|
||||||
|
if (chunks.length === 1) {
|
||||||
|
return ['', currentPrompt, chunks[0]];
|
||||||
|
} else {
|
||||||
|
return [chunks[0], currentPrompt, chunks[1]];
|
||||||
|
}
|
||||||
|
};
|
@ -12,6 +12,8 @@ import { RefinerSettingsAccordion } from 'features/settingsAccordions/components
|
|||||||
import { OverlayScrollbarsComponent } from 'overlayscrollbars-react';
|
import { OverlayScrollbarsComponent } from 'overlayscrollbars-react';
|
||||||
import type { CSSProperties } from 'react';
|
import type { CSSProperties } from 'react';
|
||||||
import { memo } from 'react';
|
import { memo } from 'react';
|
||||||
|
import { StylePresetMenu } from '../../../stylePresets/components/StylePresetMenu';
|
||||||
|
import { StylePresetMenuTrigger } from '../../../stylePresets/components/StylePresetMenuTrigger';
|
||||||
|
|
||||||
const overlayScrollbarsStyles: CSSProperties = {
|
const overlayScrollbarsStyles: CSSProperties = {
|
||||||
height: '100%',
|
height: '100%',
|
||||||
@ -20,12 +22,21 @@ const overlayScrollbarsStyles: CSSProperties = {
|
|||||||
|
|
||||||
const ParametersPanelCanvas = () => {
|
const ParametersPanelCanvas = () => {
|
||||||
const isSDXL = useAppSelector((s) => s.generation.model?.base === 'sdxl');
|
const isSDXL = useAppSelector((s) => s.generation.model?.base === 'sdxl');
|
||||||
|
const isMenuOpen = useAppSelector((s) => s.stylePreset.isMenuOpen);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Flex w="full" h="full" flexDir="column" gap={2}>
|
<Flex w="full" h="full" flexDir="column" gap={2}>
|
||||||
<QueueControls />
|
<QueueControls />
|
||||||
|
<StylePresetMenuTrigger />
|
||||||
<Flex w="full" h="full" position="relative">
|
<Flex w="full" h="full" position="relative">
|
||||||
<Box position="absolute" top={0} left={0} right={0} bottom={0}>
|
<Box position="absolute" top={0} left={0} right={0} bottom={0}>
|
||||||
|
{isMenuOpen ? (
|
||||||
|
<OverlayScrollbarsComponent defer style={overlayScrollbarsStyles} options={overlayScrollbarsParams.options}>
|
||||||
|
<Flex gap={2} flexDirection="column" h="full" w="full">
|
||||||
|
<StylePresetMenu />
|
||||||
|
</Flex>
|
||||||
|
</OverlayScrollbarsComponent>
|
||||||
|
) : (
|
||||||
<OverlayScrollbarsComponent defer style={overlayScrollbarsStyles} options={overlayScrollbarsParams.options}>
|
<OverlayScrollbarsComponent defer style={overlayScrollbarsStyles} options={overlayScrollbarsParams.options}>
|
||||||
<Flex gap={2} flexDirection="column" h="full" w="full">
|
<Flex gap={2} flexDirection="column" h="full" w="full">
|
||||||
<Prompts />
|
<Prompts />
|
||||||
@ -37,6 +48,7 @@ const ParametersPanelCanvas = () => {
|
|||||||
<AdvancedSettingsAccordion />
|
<AdvancedSettingsAccordion />
|
||||||
</Flex>
|
</Flex>
|
||||||
</OverlayScrollbarsComponent>
|
</OverlayScrollbarsComponent>
|
||||||
|
)}
|
||||||
</Box>
|
</Box>
|
||||||
</Flex>
|
</Flex>
|
||||||
</Flex>
|
</Flex>
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
import type { ChakraProps } from '@invoke-ai/ui-library';
|
import type { ChakraProps } from '@invoke-ai/ui-library';
|
||||||
import { Box, Flex, Portal, Tab, TabList, TabPanel, TabPanels, Tabs } from '@invoke-ai/ui-library';
|
import { Box, Flex, Tab, TabList, TabPanel, TabPanels, Tabs } from '@invoke-ai/ui-library';
|
||||||
import { useAppDispatch, useAppSelector } from 'app/store/storeHooks';
|
import { useAppDispatch, useAppSelector } from 'app/store/storeHooks';
|
||||||
import { overlayScrollbarsParams } from 'common/components/OverlayScrollbars/constants';
|
import { overlayScrollbarsParams } from 'common/components/OverlayScrollbars/constants';
|
||||||
import { ControlLayersPanelContent } from 'features/controlLayers/components/ControlLayersPanelContent';
|
import { ControlLayersPanelContent } from 'features/controlLayers/components/ControlLayersPanelContent';
|
||||||
@ -69,22 +69,13 @@ const ParametersPanelTextToImage = () => {
|
|||||||
<StylePresetMenuTrigger />
|
<StylePresetMenuTrigger />
|
||||||
<Flex w="full" h="full" position="relative">
|
<Flex w="full" h="full" position="relative">
|
||||||
<Box position="absolute" top={0} left={0} right={0} bottom={0} ref={ref}>
|
<Box position="absolute" top={0} left={0} right={0} bottom={0} ref={ref}>
|
||||||
<Portal containerRef={ref}>
|
{isMenuOpen ? (
|
||||||
{isMenuOpen && (
|
<OverlayScrollbarsComponent defer style={overlayScrollbarsStyles} options={overlayScrollbarsParams.options}>
|
||||||
<Box position="absolute" top={0} left={0} right={0} bottom={0} layerStyle="second">
|
|
||||||
<OverlayScrollbarsComponent
|
|
||||||
defer
|
|
||||||
style={overlayScrollbarsStyles}
|
|
||||||
options={overlayScrollbarsParams.options}
|
|
||||||
>
|
|
||||||
<Flex gap={2} flexDirection="column" h="full" w="full">
|
<Flex gap={2} flexDirection="column" h="full" w="full">
|
||||||
<StylePresetMenu />
|
<StylePresetMenu />
|
||||||
</Flex>
|
</Flex>
|
||||||
</OverlayScrollbarsComponent>
|
</OverlayScrollbarsComponent>
|
||||||
</Box>
|
) : (
|
||||||
)}
|
|
||||||
</Portal>
|
|
||||||
{!isMenuOpen && (
|
|
||||||
<OverlayScrollbarsComponent defer style={overlayScrollbarsStyles} options={overlayScrollbarsParams.options}>
|
<OverlayScrollbarsComponent defer style={overlayScrollbarsStyles} options={overlayScrollbarsParams.options}>
|
||||||
<Flex gap={2} flexDirection="column" h="full" w="full">
|
<Flex gap={2} flexDirection="column" h="full" w="full">
|
||||||
<Prompts />
|
<Prompts />
|
||||||
|
@ -8,6 +8,9 @@ import { UpscaleSettingsAccordion } from 'features/settingsAccordions/components
|
|||||||
import { OverlayScrollbarsComponent } from 'overlayscrollbars-react';
|
import { OverlayScrollbarsComponent } from 'overlayscrollbars-react';
|
||||||
import type { CSSProperties } from 'react';
|
import type { CSSProperties } from 'react';
|
||||||
import { memo } 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 = {
|
const overlayScrollbarsStyles: CSSProperties = {
|
||||||
height: '100%',
|
height: '100%',
|
||||||
@ -15,11 +18,20 @@ const overlayScrollbarsStyles: CSSProperties = {
|
|||||||
};
|
};
|
||||||
|
|
||||||
const ParametersPanelUpscale = () => {
|
const ParametersPanelUpscale = () => {
|
||||||
|
const isMenuOpen = useAppSelector((s) => s.stylePreset.isMenuOpen);
|
||||||
return (
|
return (
|
||||||
<Flex w="full" h="full" flexDir="column" gap={2}>
|
<Flex w="full" h="full" flexDir="column" gap={2}>
|
||||||
<QueueControls />
|
<QueueControls />
|
||||||
|
<StylePresetMenuTrigger />
|
||||||
<Flex w="full" h="full" position="relative">
|
<Flex w="full" h="full" position="relative">
|
||||||
<Box position="absolute" top={0} left={0} right={0} bottom={0}>
|
<Box position="absolute" top={0} left={0} right={0} bottom={0}>
|
||||||
|
{isMenuOpen ? (
|
||||||
|
<OverlayScrollbarsComponent defer style={overlayScrollbarsStyles} options={overlayScrollbarsParams.options}>
|
||||||
|
<Flex gap={2} flexDirection="column" h="full" w="full">
|
||||||
|
<StylePresetMenu />
|
||||||
|
</Flex>
|
||||||
|
</OverlayScrollbarsComponent>
|
||||||
|
) : (
|
||||||
<OverlayScrollbarsComponent defer style={overlayScrollbarsStyles} options={overlayScrollbarsParams.options}>
|
<OverlayScrollbarsComponent defer style={overlayScrollbarsStyles} options={overlayScrollbarsParams.options}>
|
||||||
<Flex gap={2} flexDirection="column" h="full" w="full">
|
<Flex gap={2} flexDirection="column" h="full" w="full">
|
||||||
<Prompts />
|
<Prompts />
|
||||||
@ -28,6 +40,7 @@ const ParametersPanelUpscale = () => {
|
|||||||
<AdvancedSettingsAccordion />
|
<AdvancedSettingsAccordion />
|
||||||
</Flex>
|
</Flex>
|
||||||
</OverlayScrollbarsComponent>
|
</OverlayScrollbarsComponent>
|
||||||
|
)}
|
||||||
</Box>
|
</Box>
|
||||||
</Flex>
|
</Flex>
|
||||||
</Flex>
|
</Flex>
|
||||||
|
Loading…
Reference in New Issue
Block a user