This commit is contained in:
Jennifer Player 2024-03-06 14:05:27 -05:00 committed by Kent Keirsey
parent aa88fadc30
commit 8df02623f2
10 changed files with 98 additions and 96 deletions

View File

@ -3,6 +3,7 @@ from pathlib import Path
from PIL.Image import Image as PILImageType
class ModelImagesBase(ABC):
"""Low-level service responsible for storing and retrieving image files."""

View File

@ -9,7 +9,12 @@ from invokeai.app.services.invoker import Invoker
from invokeai.app.util.thumbnails import make_thumbnail
from .model_images_base import ModelImagesBase
from .model_images_common import ModelImageFileDeleteException, ModelImageFileNotFoundException, ModelImageFileSaveException
from .model_images_common import (
ModelImageFileDeleteException,
ModelImageFileNotFoundException,
ModelImageFileSaveException,
)
class ModelImagesService(ModelImagesBase):
"""Stores images on disk"""
@ -29,7 +34,7 @@ class ModelImagesService(ModelImagesBase):
def get(self, model_key: str) -> PILImageType:
try:
path = self.get_path(model_key)
if not self.validate_path(path):
raise ModelImageFileNotFoundException
@ -57,14 +62,14 @@ class ModelImagesService(ModelImagesBase):
path = self.__model_images_folder / (model_key + '.webp')
return path
def get_url(self, model_key: str) -> str | None:
path = self.get_path(model_key)
if not self.validate_path(path):
return
return self.__invoker.services.urls.get_model_image_url(model_key)
def delete(self, model_key: str) -> None:
try:
path = self.get_path(model_key)
@ -76,7 +81,7 @@ class ModelImagesService(ModelImagesBase):
except Exception as e:
raise ModelImageFileDeleteException from e
def validate_path(self, path: Union[str, Path]) -> bool:
"""Validates the path given for an image."""
path = path if isinstance(path, Path) else Path(path)

View File

@ -18,4 +18,4 @@ class LocalUrlService(UrlServiceBase):
return f"{self._base_url}/images/i/{image_basename}/full"
def get_model_image_url(self, model_key: str) -> str:
return f"{self._base_url_v2}/models/i/{model_key}/image"
return f"{self._base_url_v2}/models/i/{model_key}/image"

View File

@ -858,7 +858,7 @@
"triggerPhrases": "Trigger Phrases",
"typePhraseHere": "Type phrase here",
"upcastAttention": "Upcast Attention",
"uploadImage":"Upload Image",
"uploadImage": "Upload Image",
"updateModel": "Update Model",
"useCustomConfig": "Use Custom Config",
"useDefaultSettings": "Use Default Settings",

View File

@ -1,28 +1,27 @@
import { Box, Image } from '@invoke-ai/ui-library';
import { typedMemo } from 'common/util/typedMemo';
import { useGetModelConfigQuery } from 'services/api/endpoints/models';
type Props = {
image_url?: string;
};
const ModelImage = ({ image_url }: Props) => {
if (!image_url) {
return <Box height="50px" minWidth="50px" />;
}
if (!image_url) return <Box height="50px" minWidth="50px" />;
return (
<Image
src={image_url}
objectFit="cover"
objectPosition="50% 50%"
height="50px"
width="50px"
minHeight="50px"
minWidth="50px"
borderRadius="base"
/>
);
return (
<Image
src={image_url}
objectFit="cover"
objectPosition="50% 50%"
height="50px"
width="50px"
minHeight="50px"
minWidth="50px"
borderRadius="base"
/>
);
};
export default typedMemo(ModelImage);

View File

@ -21,6 +21,7 @@ import { IoWarning } from 'react-icons/io5';
import { PiTrashSimpleBold } from 'react-icons/pi';
import { useDeleteModelsMutation } from 'services/api/endpoints/models';
import type { AnyModelConfig } from 'services/api/types';
import ModelImage from './ModelImage';
type ModelListItemProps = {

View File

@ -1,24 +1,22 @@
import { Box, IconButton, Image } from '@invoke-ai/ui-library';
import { typedMemo } from 'common/util/typedMemo';
import { useCallback, useState } from 'react';
import { Box, Button, IconButton, Image } from '@invoke-ai/ui-library';
import { useAppDispatch } from 'app/store/storeHooks';
import { Button } from '@invoke-ai/ui-library';
import { useDropzone } from 'react-dropzone';
import { PiArrowCounterClockwiseBold, PiUploadSimpleBold } from 'react-icons/pi';
import { useUpdateModelImageMutation, useDeleteModelImageMutation } from 'services/api/endpoints/models';
import { useTranslation } from 'react-i18next';
import { typedMemo } from 'common/util/typedMemo';
import { addToast } from 'features/system/store/systemSlice';
import { makeToast } from 'features/system/util/makeToast';
import { useCallback, useState } from 'react';
import { useDropzone } from 'react-dropzone';
import { useTranslation } from 'react-i18next';
import { PiArrowCounterClockwiseBold, PiUploadSimpleBold } from 'react-icons/pi';
import { useDeleteModelImageMutation, useUpdateModelImageMutation } from 'services/api/endpoints/models';
type Props = {
model_key: string | null;
model_image: string | null;
};
const ModelImageUpload = ({ model_key, model_image }: Props) => {
model_key: string | null;
model_image?: string | null;
};
const ModelImageUpload = ({ model_key, model_image }: Props) => {
const dispatch = useAppDispatch();
const [image, setImage] = useState<string | null>(model_image);
const [image, setImage] = useState<string | null>(model_image || null);
const { t } = useTranslation();
const [updateModelImage] = useUpdateModelImageMutation();
@ -28,19 +26,50 @@ type Props = {
(files: File[]) => {
const file = files[0];
if (!file) {
if (!file || !model_key) {
return;
}
setImage(URL.createObjectURL(file));
updateModelImage({ key: model_key, image: file })
.unwrap()
.then(() => {
dispatch(
addToast(
makeToast({
title: t('modelManager.modelImageUpdated'),
status: 'success',
})
)
);
})
.catch((_) => {
dispatch(
addToast(
makeToast({
title: t('modelManager.modelImageUpdateFailed'),
status: 'error',
})
)
);
});
},
[dispatch, model_key, t, updateModelImage]
);
const handleResetImage = useCallback(() => {
if (!model_key) {
return;
}
setImage(null);
deleteModelImage(model_key)
.unwrap()
.then(() => {
dispatch(
addToast(
makeToast({
title: t('modelManager.modelImageUpdated'),
title: t('modelManager.modelImageDeleted'),
status: 'success',
})
)
@ -50,44 +79,13 @@ type Props = {
dispatch(
addToast(
makeToast({
title: t('modelManager.modelImageUpdateFailed'),
title: t('modelManager.modelImageDeleteFailed'),
status: 'error',
})
)
);
});
},
[]
);
const handleResetImage = useCallback(() => {
if (!model_key) {
return;
}
setImage(null);
deleteModelImage(model_key)
.unwrap()
.then(() => {
dispatch(
addToast(
makeToast({
title: t('modelManager.modelImageDeleted'),
status: 'success',
})
)
);
})
.catch((_) => {
dispatch(
addToast(
makeToast({
title: t('modelManager.modelImageDeleteFailed'),
status: 'error',
})
)
);
});
}, []);
}, [dispatch, model_key, t, deleteModelImage]);
const { getInputProps, getRootProps } = useDropzone({
accept: { 'image/png': ['.png'], 'image/jpeg': ['.jpg', '.jpeg', '.png'] },
@ -98,9 +96,7 @@ type Props = {
if (image) {
return (
<Box
position="relative"
>
<Box position="relative">
<Image
src={image}
objectFit="cover"

View File

@ -4,11 +4,11 @@ import { useAppSelector } from 'app/store/storeHooks';
import { useTranslation } from 'react-i18next';
import { useGetModelConfigQuery } from 'services/api/endpoints/models';
import ModelImageUpload from './Fields/ModelImageUpload';
import { ModelMetadata } from './Metadata/ModelMetadata';
import { ModelAttrView } from './ModelAttrView';
import { ModelEdit } from './ModelEdit';
import { ModelView } from './ModelView';
import ModelImageUpload from './Fields/ModelImageUpload';
export const Model = () => {
const { t } = useTranslation();
@ -26,22 +26,22 @@ export const Model = () => {
return (
<>
<Flex alignItems="center" justifyContent="space-between" gap="4" paddingRight="5">
<Flex flexDir="column" gap={1} p={2}>
<Heading as="h2" fontSize="lg">
{data.name}
</Heading>
<Flex alignItems="center" justifyContent="space-between" gap="4" paddingRight="5">
<Flex flexDir="column" gap={1} p={2}>
<Heading as="h2" fontSize="lg">
{data.name}
</Heading>
{data.source && (
<Text variant="subtext">
{t('modelManager.source')}: {data?.source}
</Text>
)}
<Box mt="4">
<ModelAttrView label="Description" value={data.description} />
</Box>
</Flex>
<ModelImageUpload model_key={selectedModelKey} model_image={data.cover_image} />
{data.source && (
<Text variant="subtext">
{t('modelManager.source')}: {data?.source}
</Text>
)}
<Box mt="4">
<ModelAttrView label="Description" value={data.description} />
</Box>
</Flex>
<ModelImageUpload model_key={selectedModelKey} model_image={data.cover_image} />
</Flex>
<Tabs mt="4" h="100%">

View File

@ -193,7 +193,7 @@ export const modelsApi = api.injectEndpoints({
invalidatesTags: ['Model'],
}),
getModelImage: build.query<string, string>({
query: (key) => buildModelsUrl(`i/${key}/image`)
query: (key) => buildModelsUrl(`i/${key}/image`),
}),
convertModel: build.mutation<ConvertMainModelResponse, string>({
query: (key) => {

File diff suppressed because one or more lines are too long