mirror of
https://github.com/invoke-ai/InvokeAI
synced 2024-08-30 20:32:17 +00:00
moved upload image field and added delete image functionality
This commit is contained in:
parent
8a68355926
commit
239b1e8cc7
@ -292,6 +292,7 @@ async def get_model_image(
|
|||||||
"""Gets a full-resolution image file"""
|
"""Gets a full-resolution image file"""
|
||||||
|
|
||||||
try:
|
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")
|
path = ApiDependencies.invoker.services.model_images.get_path(key + ".png")
|
||||||
|
|
||||||
if not path:
|
if not path:
|
||||||
@ -308,22 +309,6 @@ async def get_model_image(
|
|||||||
except Exception:
|
except Exception:
|
||||||
raise HTTPException(status_code=404)
|
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(
|
@model_manager_router.patch(
|
||||||
"/i/{key}/image",
|
"/i/{key}/image",
|
||||||
operation_id="update_model_image",
|
operation_id="update_model_image",
|
||||||
@ -390,6 +375,28 @@ async def delete_model(
|
|||||||
logger.error(str(e))
|
logger.error(str(e))
|
||||||
raise HTTPException(status_code=404, detail=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(
|
# @model_manager_router.post(
|
||||||
# "/i/",
|
# "/i/",
|
||||||
|
@ -16,11 +16,6 @@ class ModelImagesBase(ABC):
|
|||||||
"""Gets the internal path to a model image."""
|
"""Gets the internal path to a model image."""
|
||||||
pass
|
pass
|
||||||
|
|
||||||
@abstractmethod
|
|
||||||
def get_url(self, model_key: str) -> str:
|
|
||||||
"""Gets the url for a model image."""
|
|
||||||
pass
|
|
||||||
|
|
||||||
@abstractmethod
|
@abstractmethod
|
||||||
def save(
|
def save(
|
||||||
self,
|
self,
|
||||||
|
@ -1,25 +1,28 @@
|
|||||||
import { Box, IconButton, Image } from '@invoke-ai/ui-library';
|
import { Box, IconButton, Image } from '@invoke-ai/ui-library';
|
||||||
import { typedMemo } from 'common/util/typedMemo';
|
import { typedMemo } from 'common/util/typedMemo';
|
||||||
import { useCallback, useState } from 'react';
|
import { useCallback, useState } from 'react';
|
||||||
import type { Control } from 'react-hook-form';
|
import { useAppDispatch } from 'app/store/storeHooks';
|
||||||
import { useController, useWatch } from 'react-hook-form';
|
|
||||||
|
|
||||||
import { Button } from '@invoke-ai/ui-library';
|
import { Button } from '@invoke-ai/ui-library';
|
||||||
import { useDropzone } from 'react-dropzone';
|
import { useDropzone } from 'react-dropzone';
|
||||||
import { PiArrowCounterClockwiseBold, PiUploadSimpleBold } from 'react-icons/pi';
|
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 { useTranslation } from 'react-i18next';
|
||||||
|
import { addToast } from 'features/system/store/systemSlice';
|
||||||
|
import { makeToast } from 'features/system/util/makeToast';
|
||||||
|
|
||||||
type Props = {
|
type Props = {
|
||||||
control: Control<UpdateModelArg['body']>;
|
model_key: string;
|
||||||
};
|
};
|
||||||
|
|
||||||
const ModelImageUpload = ({ control }: Props) => {
|
const ModelImageUpload = ({ model_key }: Props) => {
|
||||||
const key = useWatch({ control, name: 'key' });
|
const dispatch = useAppDispatch();
|
||||||
const [image, setImage] = useState<string | undefined>(buildModelsUrl(`i/${key}/image`));
|
const [image, setImage] = useState<string | undefined>(buildModelsUrl(`i/${model_key}/image`));
|
||||||
const { field } = useController({ control, name: 'image' });
|
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
|
|
||||||
|
const [updateModelImage] = useUpdateModelImageMutation();
|
||||||
|
const [deleteModelImage] = useDeleteModelImageMutation();
|
||||||
|
|
||||||
const onDropAccepted = useCallback(
|
const onDropAccepted = useCallback(
|
||||||
(files: File[]) => {
|
(files: File[]) => {
|
||||||
const file = files[0];
|
const file = files[0];
|
||||||
@ -28,16 +31,59 @@ const ModelImageUpload = ({ control }: Props) => {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
field.onChange(file);
|
|
||||||
setImage(URL.createObjectURL(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(() => {
|
const handleResetImage = useCallback(() => {
|
||||||
field.onChange(undefined);
|
|
||||||
setImage(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({
|
const { getInputProps, getRootProps } = useDropzone({
|
||||||
accept: { 'image/png': ['.png'], 'image/jpeg': ['.jpg', '.jpeg', '.png'] },
|
accept: { 'image/png': ['.png'], 'image/jpeg': ['.jpg', '.jpeg', '.png'] },
|
||||||
@ -50,8 +96,6 @@ const ModelImageUpload = ({ control }: Props) => {
|
|||||||
return (
|
return (
|
||||||
<Box
|
<Box
|
||||||
position="relative"
|
position="relative"
|
||||||
_hover={{ filter: 'brightness(50%)' }}
|
|
||||||
transition="filter ease 0.2s"
|
|
||||||
>
|
>
|
||||||
<Image
|
<Image
|
||||||
onError={() => setImage(undefined)}
|
onError={() => setImage(undefined)}
|
||||||
@ -66,11 +110,12 @@ const ModelImageUpload = ({ control }: Props) => {
|
|||||||
top="1"
|
top="1"
|
||||||
right="1"
|
right="1"
|
||||||
onClick={handleResetImage}
|
onClick={handleResetImage}
|
||||||
aria-label={t('modelManager.resetImage')}
|
aria-label={t('modelManager.deleteModelImage')}
|
||||||
tooltip={t('modelManager.resetImage')}
|
tooltip={t('modelManager.deleteModelImage')}
|
||||||
icon={<PiArrowCounterClockwiseBold size={16} />}
|
icon={<PiArrowCounterClockwiseBold size={16} />}
|
||||||
size="sm"
|
size="sm"
|
||||||
variant="link"
|
variant="link"
|
||||||
|
_hover={{ color: 'base.100' }}
|
||||||
/>
|
/>
|
||||||
</Box>
|
</Box>
|
||||||
);
|
);
|
||||||
|
@ -8,6 +8,7 @@ import { ModelMetadata } from './Metadata/ModelMetadata';
|
|||||||
import { ModelAttrView } from './ModelAttrView';
|
import { ModelAttrView } from './ModelAttrView';
|
||||||
import { ModelEdit } from './ModelEdit';
|
import { ModelEdit } from './ModelEdit';
|
||||||
import { ModelView } from './ModelView';
|
import { ModelView } from './ModelView';
|
||||||
|
import ModelImageUpload from './Fields/ModelImageUpload';
|
||||||
|
|
||||||
export const Model = () => {
|
export const Model = () => {
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
@ -25,6 +26,7 @@ export const Model = () => {
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
|
<Flex alignItems="center" justifyContent="space-between" gap="4" paddingRight="5">
|
||||||
<Flex flexDir="column" gap={1} p={2}>
|
<Flex flexDir="column" gap={1} p={2}>
|
||||||
<Heading as="h2" fontSize="lg">
|
<Heading as="h2" fontSize="lg">
|
||||||
{data.name}
|
{data.name}
|
||||||
@ -39,6 +41,8 @@ export const Model = () => {
|
|||||||
<ModelAttrView label="Description" value={data.description} />
|
<ModelAttrView label="Description" value={data.description} />
|
||||||
</Box>
|
</Box>
|
||||||
</Flex>
|
</Flex>
|
||||||
|
<ModelImageUpload model_key={selectedModelKey || ''} />
|
||||||
|
</Flex>
|
||||||
|
|
||||||
<Tabs mt="4" h="100%">
|
<Tabs mt="4" h="100%">
|
||||||
<TabList>
|
<TabList>
|
||||||
|
@ -20,14 +20,9 @@ import type { SubmitHandler } from 'react-hook-form';
|
|||||||
import { useForm } from 'react-hook-form';
|
import { useForm } from 'react-hook-form';
|
||||||
import { useTranslation } from 'react-i18next';
|
import { useTranslation } from 'react-i18next';
|
||||||
import type { UpdateModelArg } from 'services/api/endpoints/models';
|
import type { UpdateModelArg } from 'services/api/endpoints/models';
|
||||||
import {
|
import { useGetModelConfigQuery, useUpdateModelMutation } from 'services/api/endpoints/models';
|
||||||
useGetModelConfigQuery,
|
|
||||||
useUpdateModelImageMutation,
|
|
||||||
useUpdateModelsMutation,
|
|
||||||
} from 'services/api/endpoints/models';
|
|
||||||
|
|
||||||
import BaseModelSelect from './Fields/BaseModelSelect';
|
import BaseModelSelect from './Fields/BaseModelSelect';
|
||||||
import ModelImageUpload from './Fields/ModelImageUpload';
|
|
||||||
import ModelVariantSelect from './Fields/ModelVariantSelect';
|
import ModelVariantSelect from './Fields/ModelVariantSelect';
|
||||||
import PredictionTypeSelect from './Fields/PredictionTypeSelect';
|
import PredictionTypeSelect from './Fields/PredictionTypeSelect';
|
||||||
|
|
||||||
@ -36,8 +31,7 @@ export const ModelEdit = () => {
|
|||||||
const selectedModelKey = useAppSelector((s) => s.modelmanagerV2.selectedModelKey);
|
const selectedModelKey = useAppSelector((s) => s.modelmanagerV2.selectedModelKey);
|
||||||
const { data, isLoading } = useGetModelConfigQuery(selectedModelKey ?? skipToken);
|
const { data, isLoading } = useGetModelConfigQuery(selectedModelKey ?? skipToken);
|
||||||
|
|
||||||
const [updateModel, { isLoading: isSubmitting }] = useUpdateModelsMutation();
|
const [updateModel, { isLoading: isSubmitting }] = useUpdateModelMutation();
|
||||||
const [updateModelImage] = useUpdateModelImageMutation();
|
|
||||||
|
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
|
|
||||||
@ -60,11 +54,6 @@ export const ModelEdit = () => {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// remove image from body
|
|
||||||
const image = values.image;
|
|
||||||
if (values.image) {
|
|
||||||
delete values.image;
|
|
||||||
}
|
|
||||||
const responseBody: UpdateModelArg = {
|
const responseBody: UpdateModelArg = {
|
||||||
key: data.key,
|
key: data.key,
|
||||||
body: values,
|
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]
|
[dispatch, data?.key, reset, t, updateModel]
|
||||||
);
|
);
|
||||||
@ -168,7 +130,6 @@ export const ModelEdit = () => {
|
|||||||
|
|
||||||
<Flex flexDir="column" gap={3} mt="4">
|
<Flex flexDir="column" gap={3} mt="4">
|
||||||
<Flex gap="4" alignItems="center">
|
<Flex gap="4" alignItems="center">
|
||||||
<ModelImageUpload control={control} />
|
|
||||||
<FormControl flexDir="column" alignItems="flex-start" gap={1}>
|
<FormControl flexDir="column" alignItems="flex-start" gap={1}>
|
||||||
<FormLabel>{t('modelManager.description')}</FormLabel>
|
<FormLabel>{t('modelManager.description')}</FormLabel>
|
||||||
<Textarea fontSize="md" resize="none" {...register('description')} />
|
<Textarea fontSize="md" resize="none" {...register('description')} />
|
||||||
|
@ -40,6 +40,7 @@ type DeleteModelArg = {
|
|||||||
key: string;
|
key: string;
|
||||||
};
|
};
|
||||||
type DeleteModelResponse = void;
|
type DeleteModelResponse = void;
|
||||||
|
type DeleteModelImageResponse = void;
|
||||||
|
|
||||||
type ConvertMainModelResponse =
|
type ConvertMainModelResponse =
|
||||||
paths['/api/v2/models/convert/{key}']['put']['responses']['200']['content']['application/json'];
|
paths['/api/v2/models/convert/{key}']['put']['responses']['200']['content']['application/json'];
|
||||||
@ -161,7 +162,6 @@ export const modelsApi = api.injectEndpoints({
|
|||||||
body: formData,
|
body: formData,
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
invalidatesTags: ['Model'],
|
|
||||||
}),
|
}),
|
||||||
installModel: build.mutation<InstallModelResponse, InstallModelArg>({
|
installModel: build.mutation<InstallModelResponse, InstallModelArg>({
|
||||||
query: ({ source }) => {
|
query: ({ source }) => {
|
||||||
@ -182,6 +182,17 @@ export const modelsApi = api.injectEndpoints({
|
|||||||
},
|
},
|
||||||
invalidatesTags: ['Model'],
|
invalidatesTags: ['Model'],
|
||||||
}),
|
}),
|
||||||
|
deleteModelImage: build.mutation<DeleteModelImageResponse, string>({
|
||||||
|
query: (key) => {
|
||||||
|
return {
|
||||||
|
url: buildModelsUrl(`i/${key}/image`),
|
||||||
|
method: 'DELETE',
|
||||||
|
};
|
||||||
|
},
|
||||||
|
}),
|
||||||
|
getModelImage: build.query<string, string>({
|
||||||
|
query: (key) => buildModelsUrl(`i/${key}/image`)
|
||||||
|
}),
|
||||||
convertModel: build.mutation<ConvertMainModelResponse, string>({
|
convertModel: build.mutation<ConvertMainModelResponse, string>({
|
||||||
query: (key) => {
|
query: (key) => {
|
||||||
return {
|
return {
|
||||||
@ -349,6 +360,7 @@ export const {
|
|||||||
useGetTextualInversionModelsQuery,
|
useGetTextualInversionModelsQuery,
|
||||||
useGetVaeModelsQuery,
|
useGetVaeModelsQuery,
|
||||||
useDeleteModelsMutation,
|
useDeleteModelsMutation,
|
||||||
|
useDeleteModelImageMutation,
|
||||||
useUpdateModelMutation,
|
useUpdateModelMutation,
|
||||||
useUpdateModelImageMutation,
|
useUpdateModelImageMutation,
|
||||||
useInstallModelMutation,
|
useInstallModelMutation,
|
||||||
|
Loading…
Reference in New Issue
Block a user