From 239b1e8cc7de1b9784bdb7443be47a00e5aec5c8 Mon Sep 17 00:00:00 2001 From: Jennifer Player Date: Wed, 6 Mar 2024 09:58:39 -0500 Subject: [PATCH] moved upload image field and added delete image functionality --- invokeai/app/api/routers/model_manager.py | 39 +++++---- .../model_images/model_images_base.py | 5 -- .../ModelPanel/Fields/ModelImageUpload.tsx | 81 ++++++++++++++----- .../subpanels/ModelPanel/Model.tsx | 4 + .../subpanels/ModelPanel/ModelEdit.tsx | 43 +--------- .../web/src/services/api/endpoints/models.ts | 14 +++- 6 files changed, 105 insertions(+), 81 deletions(-) diff --git a/invokeai/app/api/routers/model_manager.py b/invokeai/app/api/routers/model_manager.py index 41ee9767a8..13fbd65007 100644 --- a/invokeai/app/api/routers/model_manager.py +++ b/invokeai/app/api/routers/model_manager.py @@ -292,6 +292,7 @@ async def get_model_image( """Gets a full-resolution image file""" try: + # still need to handle this gracefully when path doesnt exist instead of throwing error path = ApiDependencies.invoker.services.model_images.get_path(key + ".png") if not path: @@ -308,22 +309,6 @@ async def get_model_image( except Exception: raise HTTPException(status_code=404) - -# async def get_model_image( -# key: Annotated[str, Path(description="Unique key of model")], -# ) -> Optional[str]: -# model_images = ApiDependencies.invoker.services.model_images -# try: -# url = model_images.get_url(key) - -# if not url: -# return None - -# return url -# except Exception: -# raise HTTPException(status_code=404) - - @model_manager_router.patch( "/i/{key}/image", operation_id="update_model_image", @@ -390,6 +375,28 @@ async def delete_model( logger.error(str(e)) raise HTTPException(status_code=404, detail=str(e)) +@model_manager_router.delete( + "/i/{key}/image", + operation_id="delete_model_image", + responses={ + 204: {"description": "Model image deleted successfully"}, + 404: {"description": "Model image not found"}, + }, + status_code=204, +) +async def delete_model_image( + key: str = Path(description="Unique key of model image to remove from model_images directory."), +) -> None: + logger = ApiDependencies.invoker.services.logger + model_images = ApiDependencies.invoker.services.model_images + try: + model_images.delete(key) + logger.info(f"Deleted model image: {key}") + return + except UnknownModelException as e: + logger.error(str(e)) + raise HTTPException(status_code=404, detail=str(e)) + # @model_manager_router.post( # "/i/", diff --git a/invokeai/app/services/model_images/model_images_base.py b/invokeai/app/services/model_images/model_images_base.py index 890ff2b864..03c0a12a7d 100644 --- a/invokeai/app/services/model_images/model_images_base.py +++ b/invokeai/app/services/model_images/model_images_base.py @@ -16,11 +16,6 @@ class ModelImagesBase(ABC): """Gets the internal path to a model image.""" pass - @abstractmethod - def get_url(self, model_key: str) -> str: - """Gets the url for a model image.""" - pass - @abstractmethod def save( self, diff --git a/invokeai/frontend/web/src/features/modelManagerV2/subpanels/ModelPanel/Fields/ModelImageUpload.tsx b/invokeai/frontend/web/src/features/modelManagerV2/subpanels/ModelPanel/Fields/ModelImageUpload.tsx index e9f446f57f..ebf87c03fc 100644 --- a/invokeai/frontend/web/src/features/modelManagerV2/subpanels/ModelPanel/Fields/ModelImageUpload.tsx +++ b/invokeai/frontend/web/src/features/modelManagerV2/subpanels/ModelPanel/Fields/ModelImageUpload.tsx @@ -1,25 +1,28 @@ import { Box, IconButton, Image } from '@invoke-ai/ui-library'; import { typedMemo } from 'common/util/typedMemo'; import { useCallback, useState } from 'react'; -import type { Control } from 'react-hook-form'; -import { useController, useWatch } from 'react-hook-form'; +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 { UpdateModelArg, buildModelsUrl } from 'services/api/endpoints/models'; +import { buildModelsUrl, useUpdateModelImageMutation, useDeleteModelImageMutation } from 'services/api/endpoints/models'; import { useTranslation } from 'react-i18next'; +import { addToast } from 'features/system/store/systemSlice'; +import { makeToast } from 'features/system/util/makeToast'; type Props = { - control: Control; -}; - -const ModelImageUpload = ({ control }: Props) => { - const key = useWatch({ control, name: 'key' }); - const [image, setImage] = useState(buildModelsUrl(`i/${key}/image`)); - const { field } = useController({ control, name: 'image' }); + model_key: string; + }; + + const ModelImageUpload = ({ model_key }: Props) => { + const dispatch = useAppDispatch(); + const [image, setImage] = useState(buildModelsUrl(`i/${model_key}/image`)); const { t } = useTranslation(); + const [updateModelImage] = useUpdateModelImageMutation(); + const [deleteModelImage] = useDeleteModelImageMutation(); + const onDropAccepted = useCallback( (files: File[]) => { const file = files[0]; @@ -28,16 +31,59 @@ const ModelImageUpload = ({ control }: Props) => { return; } - field.onChange(file); setImage(URL.createObjectURL(file)); + + updateModelImage({ key: model_key, image: image }) + .unwrap() + .then(() => { + dispatch( + addToast( + makeToast({ + title: t('modelManager.modelImageUpdated'), + status: 'success', + }) + ) + ); + }) + .catch((_) => { + dispatch( + addToast( + makeToast({ + title: t('modelManager.modelImageUpdateFailed'), + status: 'error', + }) + ) + ); + }); }, - [field] + [] ); const handleResetImage = useCallback(() => { - field.onChange(undefined); setImage(undefined); - }, [field]); + deleteModelImage(model_key) + .unwrap() + .then(() => { + dispatch( + addToast( + makeToast({ + title: t('modelManager.modelImageDeleted'), + status: 'success', + }) + ) + ); + }) + .catch((_) => { + dispatch( + addToast( + makeToast({ + title: t('modelManager.modelImageDeleteFailed'), + status: 'error', + }) + ) + ); + }); + }, []); const { getInputProps, getRootProps } = useDropzone({ accept: { 'image/png': ['.png'], 'image/jpeg': ['.jpg', '.jpeg', '.png'] }, @@ -50,8 +96,6 @@ const ModelImageUpload = ({ control }: Props) => { return ( setImage(undefined)} @@ -66,11 +110,12 @@ const ModelImageUpload = ({ control }: Props) => { top="1" right="1" onClick={handleResetImage} - aria-label={t('modelManager.resetImage')} - tooltip={t('modelManager.resetImage')} + aria-label={t('modelManager.deleteModelImage')} + tooltip={t('modelManager.deleteModelImage')} icon={} size="sm" variant="link" + _hover={{ color: 'base.100' }} /> ); diff --git a/invokeai/frontend/web/src/features/modelManagerV2/subpanels/ModelPanel/Model.tsx b/invokeai/frontend/web/src/features/modelManagerV2/subpanels/ModelPanel/Model.tsx index 96e2629443..3a4ef11d24 100644 --- a/invokeai/frontend/web/src/features/modelManagerV2/subpanels/ModelPanel/Model.tsx +++ b/invokeai/frontend/web/src/features/modelManagerV2/subpanels/ModelPanel/Model.tsx @@ -8,6 +8,7 @@ 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(); @@ -25,6 +26,7 @@ export const Model = () => { return ( <> + {data.name} @@ -39,6 +41,8 @@ export const Model = () => { + + diff --git a/invokeai/frontend/web/src/features/modelManagerV2/subpanels/ModelPanel/ModelEdit.tsx b/invokeai/frontend/web/src/features/modelManagerV2/subpanels/ModelPanel/ModelEdit.tsx index f04e3dee37..ee0a4df10b 100644 --- a/invokeai/frontend/web/src/features/modelManagerV2/subpanels/ModelPanel/ModelEdit.tsx +++ b/invokeai/frontend/web/src/features/modelManagerV2/subpanels/ModelPanel/ModelEdit.tsx @@ -20,14 +20,9 @@ import type { SubmitHandler } from 'react-hook-form'; import { useForm } from 'react-hook-form'; import { useTranslation } from 'react-i18next'; import type { UpdateModelArg } from 'services/api/endpoints/models'; -import { - useGetModelConfigQuery, - useUpdateModelImageMutation, - useUpdateModelsMutation, -} from 'services/api/endpoints/models'; +import { useGetModelConfigQuery, useUpdateModelMutation } from 'services/api/endpoints/models'; import BaseModelSelect from './Fields/BaseModelSelect'; -import ModelImageUpload from './Fields/ModelImageUpload'; import ModelVariantSelect from './Fields/ModelVariantSelect'; import PredictionTypeSelect from './Fields/PredictionTypeSelect'; @@ -36,8 +31,7 @@ export const ModelEdit = () => { const selectedModelKey = useAppSelector((s) => s.modelmanagerV2.selectedModelKey); const { data, isLoading } = useGetModelConfigQuery(selectedModelKey ?? skipToken); - const [updateModel, { isLoading: isSubmitting }] = useUpdateModelsMutation(); - const [updateModelImage] = useUpdateModelImageMutation(); + const [updateModel, { isLoading: isSubmitting }] = useUpdateModelMutation(); const { t } = useTranslation(); @@ -60,11 +54,6 @@ export const ModelEdit = () => { return; } - // remove image from body - const image = values.image; - if (values.image) { - delete values.image; - } const responseBody: UpdateModelArg = { key: data.key, body: values, @@ -95,33 +84,6 @@ export const ModelEdit = () => { ) ); }); - if (image) { - updateModelImage({ key: data.key, image: image }) - .unwrap() - .then((payload) => { - reset(payload, { keepDefaultValues: true }); - dispatch(setSelectedModelMode('view')); - dispatch( - addToast( - makeToast({ - title: t('modelManager.modelUpdated'), - status: 'success', - }) - ) - ); - }) - .catch((_) => { - reset(); - dispatch( - addToast( - makeToast({ - title: t('modelManager.modelUpdateFailed'), - status: 'error', - }) - ) - ); - }); - } }, [dispatch, data?.key, reset, t, updateModel] ); @@ -168,7 +130,6 @@ export const ModelEdit = () => { - {t('modelManager.description')}