clean up image implementation

This commit is contained in:
Mary Hipp 2024-08-07 10:36:38 -04:00
parent cc96dcf0ed
commit 0b0abfbe8f
8 changed files with 167 additions and 132 deletions

View File

@ -8,6 +8,7 @@ from PIL import Image
from invokeai.app.api.dependencies import ApiDependencies from invokeai.app.api.dependencies import ApiDependencies
from invokeai.app.api.routers.model_manager import IMAGE_MAX_AGE from invokeai.app.api.routers.model_manager import IMAGE_MAX_AGE
from invokeai.app.services.style_preset_images.style_preset_images_common import StylePresetImageFileNotFoundException
from invokeai.app.services.style_preset_records.style_preset_records_common import ( from invokeai.app.services.style_preset_records.style_preset_records_common import (
PresetData, PresetData,
StylePresetChanges, StylePresetChanges,
@ -91,7 +92,11 @@ async def delete_style_preset(
style_preset_id: str = Path(description="The style preset to delete"), style_preset_id: str = Path(description="The style preset to delete"),
) -> None: ) -> None:
"""Deletes a style preset""" """Deletes a style preset"""
ApiDependencies.invoker.services.style_preset_images_service.delete(style_preset_id) try:
ApiDependencies.invoker.services.style_preset_images_service.delete(style_preset_id)
except StylePresetImageFileNotFoundException:
pass
ApiDependencies.invoker.services.style_preset_records.delete(style_preset_id) ApiDependencies.invoker.services.style_preset_records.delete(style_preset_id)

View File

@ -72,6 +72,8 @@ class StylePresetImageFileStorageDisk(StylePresetImageFileStorageBase):
send2trash(path) send2trash(path)
except StylePresetImageFileNotFoundException as e:
raise StylePresetImageFileNotFoundException from e
except Exception as e: except Exception as e:
raise StylePresetImageFileDeleteException from e raise StylePresetImageFileDeleteException from e

View File

@ -1,12 +1,11 @@
import { Flex, IconButton, Text } from '@invoke-ai/ui-library'; 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 } from 'features/stylePresets/store/stylePresetSlice';
import type { MouseEventHandler } from 'react'; import type { MouseEventHandler } from 'react';
import { useCallback } from 'react'; import { useCallback } from 'react';
import { CgPushDown } from 'react-icons/cg'; import { PiStackSimpleBold, PiXBold } from 'react-icons/pi';
import { PiXBold } from 'react-icons/pi';
import StylePresetImage from './StylePresetImage'; import StylePresetImage from './StylePresetImage';
export const ActiveStylePreset = () => { export const ActiveStylePreset = () => {
@ -34,18 +33,21 @@ export const ActiveStylePreset = () => {
); );
if (!activeStylePreset) { if (!activeStylePreset) {
return <>Choose Preset</>; return (
<Flex h="25px" alignItems="center">
<Text fontSize="sm" fontWeight="semibold" color="base.300">
Choose Preset
</Text>
</Flex>
);
} }
return ( return (
<> <>
<Flex justifyContent="space-between" w="full" alignItems="center"> <Flex justifyContent="space-between" w="full" alignItems="center">
<Flex gap="2"> <Flex gap="2" alignItems="center">
<StylePresetImage presetImageUrl={activeStylePreset.image} /> <StylePresetImage imageWidth={25} presetImageUrl={activeStylePreset.image} />
<Flex flexDir="column"> <Flex flexDir="column">
<Text variant="subtext" fontSize="xs"> <Text fontSize="sm" fontWeight="semibold" color="base.300">
Prompt Style
</Text>
<Text fontSize="md" fontWeight="semibold">
{activeStylePreset.name} {activeStylePreset.name}
</Text> </Text>
</Flex> </Flex>
@ -53,15 +55,15 @@ export const ActiveStylePreset = () => {
<Flex gap="1"> <Flex gap="1">
<IconButton <IconButton
onClick={handleFlattenPrompts} onClick={handleFlattenPrompts}
variant="ghost" variant="outline"
size="md" size="sm"
aria-label="Flatten" aria-label="Flatten"
icon={<CgPushDown />} icon={<PiStackSimpleBold />}
/> />
<IconButton <IconButton
onClick={handleClearActiveStylePreset} onClick={handleClearActiveStylePreset}
variant="ghost" variant="outline"
size="md" size="sm"
aria-label="Clear" aria-label="Clear"
icon={<PiXBold />} icon={<PiXBold />}
/> />

View File

@ -5,29 +5,29 @@ import { PiImage } from 'react-icons/pi';
const IMAGE_THUMBNAIL_SIZE = '40px'; const IMAGE_THUMBNAIL_SIZE = '40px';
const FALLBACK_ICON_SIZE = '24px'; const FALLBACK_ICON_SIZE = '24px';
const StylePresetImage = ({ presetImageUrl }: { presetImageUrl: string | null }) => { const StylePresetImage = ({ presetImageUrl, imageWidth }: { presetImageUrl: string | null; imageWidth?: number }) => {
return ( return (
<Image <Image
src={presetImageUrl || ''} src={presetImageUrl || ''}
fallbackStrategy="beforeLoadOrError" fallbackStrategy="beforeLoadOrError"
fallback={ fallback={
<Flex <Flex
height={IMAGE_THUMBNAIL_SIZE} height={imageWidth || IMAGE_THUMBNAIL_SIZE}
minWidth={IMAGE_THUMBNAIL_SIZE} minWidth={imageWidth || IMAGE_THUMBNAIL_SIZE}
bg="base.650" bg="base.650"
borderRadius="base" borderRadius="base"
alignItems="center" alignItems="center"
justifyContent="center" justifyContent="center"
> >
<Icon color="base.500" as={PiImage} boxSize={FALLBACK_ICON_SIZE} /> <Icon color="base.500" as={PiImage} boxSize={imageWidth ? imageWidth / 2 : FALLBACK_ICON_SIZE} />
</Flex> </Flex>
} }
objectFit="cover" objectFit="cover"
objectPosition="50% 50%" objectPosition="50% 50%"
height={IMAGE_THUMBNAIL_SIZE} height={imageWidth || IMAGE_THUMBNAIL_SIZE}
width={IMAGE_THUMBNAIL_SIZE} width={imageWidth || IMAGE_THUMBNAIL_SIZE}
minHeight={IMAGE_THUMBNAIL_SIZE} minHeight={imageWidth || IMAGE_THUMBNAIL_SIZE}
minWidth={IMAGE_THUMBNAIL_SIZE} minWidth={imageWidth || IMAGE_THUMBNAIL_SIZE}
borderRadius="base" borderRadius="base"
/> />
); );

View File

@ -1,4 +1,4 @@
import { Badge, Flex, IconButton, Text } 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 ModelImage from 'features/modelManagerV2/subpanels/ModelManagerPanel/ModelImage';
@ -14,6 +14,7 @@ export const StylePresetListItem = ({ preset }: { preset: StylePresetRecordWithI
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 handleClickEdit = useCallback( const handleClickEdit = useCallback(
(e: MouseEvent<HTMLButtonElement>) => { (e: MouseEvent<HTMLButtonElement>) => {
@ -29,72 +30,94 @@ export const StylePresetListItem = ({ preset }: { preset: StylePresetRecordWithI
dispatch(isMenuOpenChanged(false)); dispatch(isMenuOpenChanged(false));
}, [dispatch, preset]); }, [dispatch, preset]);
const handleDeletePreset = useCallback( const handleClickDelete = useCallback(
async (e: MouseEvent<HTMLButtonElement>) => { (e: MouseEvent<HTMLButtonElement>) => {
e.stopPropagation(); e.stopPropagation();
try { onOpen();
await deleteStylePreset(preset.id);
} catch (error) {}
}, },
[preset] [dispatch, preset]
); );
const handleDeletePreset = useCallback(async () => {
try {
await deleteStylePreset(preset.id);
} catch (error) {}
}, [preset]);
return ( return (
<Flex <>
gap="4" <Flex
onClick={handleClickApply} gap="4"
cursor="pointer" onClick={handleClickApply}
_hover={{ backgroundColor: 'base.750' }} cursor="pointer"
padding="10px" _hover={{ backgroundColor: 'base.750' }}
borderRadius="base" padding="10px"
alignItems="center" borderRadius="base"
w="full" alignItems="center"
> w="full"
<StylePresetImage presetImageUrl={preset.image} /> >
<Flex flexDir="column" w="full"> <StylePresetImage presetImageUrl={preset.image} />
<Flex w="full" justifyContent="space-between"> <Flex flexDir="column" w="full">
<Flex alignItems="center" gap="2"> <Flex w="full" justifyContent="space-between">
<Text fontSize="md">{preset.name}</Text> <Flex alignItems="center" gap="2">
{activeStylePreset && activeStylePreset.id === preset.id && ( <Text fontSize="md">{preset.name}</Text>
<Badge {activeStylePreset && activeStylePreset.id === preset.id && (
color="invokeBlue.400" <Badge
borderColor="invokeBlue.700" color="invokeBlue.400"
borderWidth={1} borderColor="invokeBlue.700"
bg="transparent" borderWidth={1}
flexShrink={0} bg="transparent"
> flexShrink={0}
Active >
</Badge> Active
)} </Badge>
)}
</Flex>
<Flex alignItems="center" gap="1">
<IconButton
size="sm"
variant="ghost"
aria-label="Edit"
onClick={handleClickEdit}
icon={<PiPencilBold />}
/>
<IconButton
size="sm"
variant="ghost"
aria-label="Delete"
onClick={handleClickDelete}
icon={<PiTrashBold />}
/>
</Flex>
</Flex> </Flex>
<Flex alignItems="center" gap="1"> <Flex flexDir="column">
<IconButton size="sm" variant="ghost" aria-label="Edit" onClick={handleClickEdit} icon={<PiPencilBold />} /> <Text fontSize="xs">
<IconButton <Text as="span" fontWeight="semibold">
size="sm" Positive prompt:
variant="ghost" </Text>{' '}
aria-label="Delete" {preset.preset_data.positive_prompt}
onClick={handleDeletePreset} </Text>
icon={<PiTrashBold />} <Text fontSize="xs">
/> <Text as="span" fontWeight="semibold">
Negative prompt:
</Text>{' '}
{preset.preset_data.negative_prompt}
</Text>
</Flex> </Flex>
</Flex> </Flex>
<Flex flexDir="column">
<Text fontSize="xs">
<Text as="span" fontWeight="semibold">
Positive prompt:
</Text>{' '}
{preset.preset_data.positive_prompt}
</Text>
<Text fontSize="xs">
<Text as="span" fontWeight="semibold">
Negative prompt:
</Text>{' '}
{preset.preset_data.negative_prompt}
</Text>
</Flex>
</Flex> </Flex>
</Flex> <ConfirmationAlertDialog
isOpen={isOpen}
onClose={onClose}
title={'Delete preset'}
acceptCallback={handleDeletePreset}
acceptButtonText={'Delete'}
>
<p>{'Delete Preset?'}</p>
<br />
</ConfirmationAlertDialog>
</>
); );
}; };

View File

@ -16,16 +16,18 @@ export const StylePresetMenuTrigger = () => {
return ( return (
<Flex <Flex
as="button"
onClick={handleToggle} onClick={handleToggle}
backgroundColor="base.800" backgroundColor="base.800"
justifyContent="space-between" justifyContent="space-between"
alignItems="center" alignItems="center"
padding="5px 10px" padding="5px 10px"
borderRadius="base" borderRadius="base"
gap="2"
> >
<ActiveStylePreset /> <ActiveStylePreset />
<Icon as={PiCaretDownBold} /> <Icon boxSize="15px" as={PiCaretDownBold} color="base.300" />
</Flex> </Flex>
); );
}; };

View File

@ -24,8 +24,8 @@ export const useStylePresetFields = (preset: StylePresetRecordWithImage | null)
return { return {
name: preset.name, name: preset.name,
positivePrompt: preset.preset_data.positive_prompt, positivePrompt: preset.preset_data.positive_prompt || "",
negativePrompt: preset.preset_data.negative_prompt, negativePrompt: preset.preset_data.negative_prompt || "",
image: file image: file
}; };
} }

View File

@ -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, Portal, 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';
@ -71,63 +71,64 @@ const ParametersPanelTextToImage = () => {
<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}> <Portal containerRef={ref}>
{isMenuOpen && ( {isMenuOpen && (
<Box position="absolute" top={0} left={0} right={0} bottom={0}> <Box position="absolute" top={0} left={0} right={0} bottom={0} layerStyle="second">
<OverlayScrollbarsComponent <OverlayScrollbarsComponent
defer defer
style={overlayScrollbarsStyles} style={overlayScrollbarsStyles}
options={overlayScrollbarsParams.options} options={overlayScrollbarsParams.options}
// backgroundColor="rgba(0,0,0,0.5)"
> >
<Flex gap={2} flexDirection="column" h="full" w="full" layerStyle="second"> <Flex gap={2} flexDirection="column" h="full" w="full">
<StylePresetMenu /> <StylePresetMenu />
</Flex> </Flex>
</OverlayScrollbarsComponent> </OverlayScrollbarsComponent>
</Box> </Box>
)} )}
</Portal> </Portal>
<OverlayScrollbarsComponent defer style={overlayScrollbarsStyles} options={overlayScrollbarsParams.options}> {!isMenuOpen && (
<Flex gap={2} flexDirection="column" h="full" w="full"> <OverlayScrollbarsComponent defer style={overlayScrollbarsStyles} options={overlayScrollbarsParams.options}>
<Prompts /> <Flex gap={2} flexDirection="column" h="full" w="full">
<Tabs <Prompts />
defaultIndex={0} <Tabs
variant="enclosed" defaultIndex={0}
display="flex" variant="enclosed"
flexDir="column" display="flex"
w="full" flexDir="column"
h="full" w="full"
gap={2} h="full"
onChange={onChangeTabs} gap={2}
> onChange={onChangeTabs}
<TabList gap={2} fontSize="sm" borderColor="base.800"> >
<Tab sx={baseStyles} _selected={selectedStyles} data-testid="generation-tab-settings-tab-button"> <TabList gap={2} fontSize="sm" borderColor="base.800">
{t('common.settingsLabel')} <Tab sx={baseStyles} _selected={selectedStyles} data-testid="generation-tab-settings-tab-button">
</Tab> {t('common.settingsLabel')}
<Tab </Tab>
sx={baseStyles} <Tab
_selected={selectedStyles} sx={baseStyles}
data-testid="generation-tab-control-layers-tab-button" _selected={selectedStyles}
> data-testid="generation-tab-control-layers-tab-button"
{controlLayersTitle} >
</Tab> {controlLayersTitle}
</TabList> </Tab>
<TabPanels w="full" h="full"> </TabList>
<TabPanel p={0} w="full" h="full"> <TabPanels w="full" h="full">
<Flex gap={2} flexDirection="column" h="full" w="full"> <TabPanel p={0} w="full" h="full">
<ImageSettingsAccordion /> <Flex gap={2} flexDirection="column" h="full" w="full">
<GenerationSettingsAccordion /> <ImageSettingsAccordion />
{activeTabName !== 'generation' && <ControlSettingsAccordion />} <GenerationSettingsAccordion />
{activeTabName === 'canvas' && <CompositingSettingsAccordion />} {activeTabName !== 'generation' && <ControlSettingsAccordion />}
{isSDXL && <RefinerSettingsAccordion />} {activeTabName === 'canvas' && <CompositingSettingsAccordion />}
<AdvancedSettingsAccordion /> {isSDXL && <RefinerSettingsAccordion />}
</Flex> <AdvancedSettingsAccordion />
</TabPanel> </Flex>
<TabPanel p={0} w="full" h="full"> </TabPanel>
<ControlLayersPanelContent /> <TabPanel p={0} w="full" h="full">
</TabPanel> <ControlLayersPanelContent />
</TabPanels> </TabPanel>
</Tabs> </TabPanels>
</Flex> </Tabs>
</OverlayScrollbarsComponent> </Flex>
</OverlayScrollbarsComponent>
)}
</Box> </Box>
</Flex> </Flex>
</Flex> </Flex>