diff --git a/invokeai/frontend/web/src/features/modelManagerV2/components/SyncModels/SyncModelsButton.tsx b/invokeai/frontend/web/src/features/modelManagerV2/components/SyncModels/SyncModelsButton.tsx index ba2fd302a6..e8d1c0117b 100644 --- a/invokeai/frontend/web/src/features/modelManagerV2/components/SyncModels/SyncModelsButton.tsx +++ b/invokeai/frontend/web/src/features/modelManagerV2/components/SyncModels/SyncModelsButton.tsx @@ -21,6 +21,7 @@ export const SyncModelsButton = memo((props: Omit) => leftIcon={} isLoading={isLoading} onClick={syncModels} + size="sm" variant="ghost" {...props} > diff --git a/invokeai/frontend/web/src/features/modelManagerV2/subpanels/ModelManager.tsx b/invokeai/frontend/web/src/features/modelManagerV2/subpanels/ModelManager.tsx index 19bd8ab4eb..ed75f86078 100644 --- a/invokeai/frontend/web/src/features/modelManagerV2/subpanels/ModelManager.tsx +++ b/invokeai/frontend/web/src/features/modelManagerV2/subpanels/ModelManager.tsx @@ -21,8 +21,8 @@ export const ModelManager = () => { {t('common.modelManager')} - - diff --git a/invokeai/frontend/web/src/features/modelManagerV2/subpanels/ModelManagerPanel/ModelTypeFilter.tsx b/invokeai/frontend/web/src/features/modelManagerV2/subpanels/ModelManagerPanel/ModelTypeFilter.tsx index 4208366a5b..b5b4aadbe9 100644 --- a/invokeai/frontend/web/src/features/modelManagerV2/subpanels/ModelManagerPanel/ModelTypeFilter.tsx +++ b/invokeai/frontend/web/src/features/modelManagerV2/subpanels/ModelManagerPanel/ModelTypeFilter.tsx @@ -34,7 +34,7 @@ export const ModelTypeFilter = () => { return ( - }> + }> {filteredModelType ? MODEL_TYPE_LABELS[filteredModelType] : t('modelManager.allModels')} 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 2e5695f34d..6c5583ade9 100644 --- a/invokeai/frontend/web/src/features/modelManagerV2/subpanels/ModelPanel/Model.tsx +++ b/invokeai/frontend/web/src/features/modelManagerV2/subpanels/ModelPanel/Model.tsx @@ -1,10 +1,18 @@ -import { Flex, Heading, Spacer, Text } from '@invoke-ai/ui-library'; +import { Button, Flex, Heading, Spacer, Text } from '@invoke-ai/ui-library'; import { skipToken } from '@reduxjs/toolkit/query'; -import { useAppSelector } from 'app/store/storeHooks'; +import { useAppDispatch, useAppSelector } from 'app/store/storeHooks'; +import { setSelectedModelMode } from 'features/modelManagerV2/store/modelManagerV2Slice'; import { ModelConvertButton } from 'features/modelManagerV2/subpanels/ModelPanel/ModelConvertButton'; import { ModelEditButton } from 'features/modelManagerV2/subpanels/ModelPanel/ModelEditButton'; +import { addToast } from 'features/system/store/systemSlice'; +import { makeToast } from 'features/system/util/makeToast'; +import { useCallback } from 'react'; +import type { SubmitHandler } from 'react-hook-form'; +import { useForm } from 'react-hook-form'; import { useTranslation } from 'react-i18next'; -import { useGetModelConfigQuery } from 'services/api/endpoints/models'; +import { PiCheckBold, PiXBold } from 'react-icons/pi'; +import type { UpdateModelArg } from 'services/api/endpoints/models'; +import { useGetModelConfigQuery, useUpdateModelMutation } from 'services/api/endpoints/models'; import ModelImageUpload from './Fields/ModelImageUpload'; import { ModelEdit } from './ModelEdit'; @@ -15,6 +23,57 @@ export const Model = () => { const selectedModelMode = useAppSelector((s) => s.modelmanagerV2.selectedModelMode); const selectedModelKey = useAppSelector((s) => s.modelmanagerV2.selectedModelKey); const { data, isLoading } = useGetModelConfigQuery(selectedModelKey ?? skipToken); + const [updateModel, { isLoading: isSubmitting }] = useUpdateModelMutation(); + const dispatch = useAppDispatch(); + + const form = useForm({ + defaultValues: data, + mode: 'onChange', + }); + + const onSubmit = useCallback>( + (values) => { + if (!data?.key) { + return; + } + + const responseBody: UpdateModelArg = { + key: data.key, + body: values, + }; + + updateModel(responseBody) + .unwrap() + .then((payload) => { + form.reset(payload, { keepDefaultValues: true }); + dispatch(setSelectedModelMode('view')); + dispatch( + addToast( + makeToast({ + title: t('modelManager.modelUpdated'), + status: 'success', + }) + ) + ); + }) + .catch((_) => { + form.reset(); + dispatch( + addToast( + makeToast({ + title: t('modelManager.modelUpdateFailed'), + status: 'error', + }) + ) + ); + }); + }, + [dispatch, data?.key, form, t, updateModel] + ); + + const handleClickCancel = useCallback(() => { + dispatch(setSelectedModelMode('view')); + }, [dispatch]); if (isLoading) { return {t('common.loading')}; @@ -30,12 +89,29 @@ export const Model = () => { - + {data.name} - - + {selectedModelMode === 'view' && } + {selectedModelMode === 'view' && } + {selectedModelMode === 'edit' && ( + + )} + {selectedModelMode === 'edit' && ( + + )} {data.source && ( @@ -45,7 +121,7 @@ export const Model = () => { {data.description} - {selectedModelMode === 'view' ? : } + {selectedModelMode === 'view' ? : } ); }; diff --git a/invokeai/frontend/web/src/features/modelManagerV2/subpanels/ModelPanel/ModelConvertButton.tsx b/invokeai/frontend/web/src/features/modelManagerV2/subpanels/ModelPanel/ModelConvertButton.tsx index 53977434d9..61d30c3744 100644 --- a/invokeai/frontend/web/src/features/modelManagerV2/subpanels/ModelPanel/ModelConvertButton.tsx +++ b/invokeai/frontend/web/src/features/modelManagerV2/subpanels/ModelPanel/ModelConvertButton.tsx @@ -76,7 +76,7 @@ export const ModelConvertButton = (props: ModelConvertProps) => { isLoading={isLoading} flexShrink={0} > - 🧨 {t('modelManager.convertToDiffusers')} + 🧨 {t('modelManager.convert')} { - const dispatch = useAppDispatch(); +type Props = { + form: UseFormReturn; + onSubmit: SubmitHandler; +}; + +const stringFieldOptions = { + validate: (value?: string | null) => (value && value.trim().length > 3) || 'Must be at least 3 characters', +}; + +export const ModelEdit = ({ form }: Props) => { const selectedModelKey = useAppSelector((s) => s.modelmanagerV2.selectedModelKey); const { data, isLoading } = useGetModelConfigQuery(selectedModelKey ?? skipToken); - - const [updateModel, { isLoading: isSubmitting }] = useUpdateModelMutation(); - const { t } = useTranslation(); - const { - register, - handleSubmit, - control, - formState: { errors }, - reset, - } = useForm({ - defaultValues: { - ...data, - }, - mode: 'onChange', - }); - - const onSubmit = useCallback>( - (values) => { - if (!data?.key) { - return; - } - - const responseBody: UpdateModelArg = { - key: data.key, - body: values, - }; - - updateModel(responseBody) - .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] - ); - - const handleClickCancel = useCallback(() => { - dispatch(setSelectedModelMode('view')); - }, [dispatch]); - if (isLoading) { return {t('common.loading')}; } @@ -99,40 +41,26 @@ export const ModelEdit = () => { if (!data) { return {t('common.somethingWentWrong')}; } + return ( -
+ - + {t('modelManager.modelName')} - (value && value.trim().length > 3) || 'Must be at least 3 characters', - })} - size="lg" - /> + - {errors.name?.message && {errors.name?.message}} + {form.formState.errors.name?.message && ( + {form.formState.errors.name?.message} + )} - - {t('modelManager.description')} -