From af9a62d2240a6380804e2a9dfac6f96cebb8bee6 Mon Sep 17 00:00:00 2001 From: Mary Hipp Date: Fri, 23 Feb 2024 16:03:36 -0500 Subject: [PATCH] add model convert to checkpoint main models --- .../SyncModels/SyncModelsButton.tsx | 32 ++++++ .../SyncModels/SyncModelsIconButton.tsx | 33 ++++++ .../components/SyncModels/useSyncModels.ts | 40 +++++++ .../store/modelManagerV2Slice.ts | 2 +- .../AddModelPanel/CheckpointConfigsSelect.tsx | 32 ------ .../modelManagerV2/subpanels/ModelManager.tsx | 11 +- .../subpanels/ModelPanel/ModelConvert.tsx | 104 ++++++++++++++++++ .../subpanels/ModelPanel/ModelEdit.tsx | 2 +- .../subpanels/ModelPanel/ModelView.tsx | 14 ++- 9 files changed, 224 insertions(+), 46 deletions(-) create mode 100644 invokeai/frontend/web/src/features/modelManagerV2/components/SyncModels/SyncModelsButton.tsx create mode 100644 invokeai/frontend/web/src/features/modelManagerV2/components/SyncModels/SyncModelsIconButton.tsx create mode 100644 invokeai/frontend/web/src/features/modelManagerV2/components/SyncModels/useSyncModels.ts delete mode 100644 invokeai/frontend/web/src/features/modelManagerV2/subpanels/AddModelPanel/CheckpointConfigsSelect.tsx create mode 100644 invokeai/frontend/web/src/features/modelManagerV2/subpanels/ModelPanel/ModelConvert.tsx diff --git a/invokeai/frontend/web/src/features/modelManagerV2/components/SyncModels/SyncModelsButton.tsx b/invokeai/frontend/web/src/features/modelManagerV2/components/SyncModels/SyncModelsButton.tsx new file mode 100644 index 0000000000..8a49bc2585 --- /dev/null +++ b/invokeai/frontend/web/src/features/modelManagerV2/components/SyncModels/SyncModelsButton.tsx @@ -0,0 +1,32 @@ +import type { ButtonProps } from '@invoke-ai/ui-library'; +import { Button } from '@invoke-ai/ui-library'; +import { useFeatureStatus } from 'features/system/hooks/useFeatureStatus'; +import { memo } from 'react'; +import { useTranslation } from 'react-i18next'; +import { PiArrowsClockwiseBold } from 'react-icons/pi'; + +import { useSyncModels } from './useSyncModels'; + +export const SyncModelsButton = memo((props: Omit) => { + const { t } = useTranslation(); + const { syncModels, isLoading } = useSyncModels(); + const isSyncModelEnabled = useFeatureStatus('syncModels').isFeatureEnabled; + + if (!isSyncModelEnabled) { + return null; + } + + return ( + + ); +}); + +SyncModelsButton.displayName = 'SyncModelsButton'; diff --git a/invokeai/frontend/web/src/features/modelManagerV2/components/SyncModels/SyncModelsIconButton.tsx b/invokeai/frontend/web/src/features/modelManagerV2/components/SyncModels/SyncModelsIconButton.tsx new file mode 100644 index 0000000000..986a99bd0b --- /dev/null +++ b/invokeai/frontend/web/src/features/modelManagerV2/components/SyncModels/SyncModelsIconButton.tsx @@ -0,0 +1,33 @@ +import type { IconButtonProps } from '@invoke-ai/ui-library'; +import { IconButton } from '@invoke-ai/ui-library'; +import { useFeatureStatus } from 'features/system/hooks/useFeatureStatus'; +import { memo } from 'react'; +import { useTranslation } from 'react-i18next'; +import { PiArrowsClockwiseBold } from 'react-icons/pi'; + +import { useSyncModels } from './useSyncModels'; + +export const SyncModelsIconButton = memo((props: Omit) => { + const { t } = useTranslation(); + const { syncModels, isLoading } = useSyncModels(); + const isSyncModelEnabled = useFeatureStatus('syncModels').isFeatureEnabled; + + if (!isSyncModelEnabled) { + return null; + } + + return ( + } + tooltip={t('modelManager.syncModels')} + aria-label={t('modelManager.syncModels')} + isLoading={isLoading} + onClick={syncModels} + size="sm" + variant="ghost" + {...props} + /> + ); +}); + +SyncModelsIconButton.displayName = 'SyncModelsIconButton'; diff --git a/invokeai/frontend/web/src/features/modelManagerV2/components/SyncModels/useSyncModels.ts b/invokeai/frontend/web/src/features/modelManagerV2/components/SyncModels/useSyncModels.ts new file mode 100644 index 0000000000..3ccaba66a5 --- /dev/null +++ b/invokeai/frontend/web/src/features/modelManagerV2/components/SyncModels/useSyncModels.ts @@ -0,0 +1,40 @@ +import { useAppDispatch } from 'app/store/storeHooks'; +import { addToast } from 'features/system/store/systemSlice'; +import { makeToast } from 'features/system/util/makeToast'; +import { useCallback } from 'react'; +import { useTranslation } from 'react-i18next'; +import { useSyncModelsMutation } from 'services/api/endpoints/models'; + +export const useSyncModels = () => { + const dispatch = useAppDispatch(); + const { t } = useTranslation(); + const [_syncModels, { isLoading }] = useSyncModelsMutation(); + const syncModels = useCallback(() => { + _syncModels() + .unwrap() + .then((_) => { + dispatch( + addToast( + makeToast({ + title: `${t('modelManager.modelsSynced')}`, + status: 'success', + }) + ) + ); + }) + .catch((error) => { + if (error) { + dispatch( + addToast( + makeToast({ + title: `${t('modelManager.modelSyncFailed')}`, + status: 'error', + }) + ) + ); + } + }); + }, [dispatch, _syncModels, t]); + + return { syncModels, isLoading }; +}; diff --git a/invokeai/frontend/web/src/features/modelManagerV2/store/modelManagerV2Slice.ts b/invokeai/frontend/web/src/features/modelManagerV2/store/modelManagerV2Slice.ts index 83bd0cf8d5..f4760bad36 100644 --- a/invokeai/frontend/web/src/features/modelManagerV2/store/modelManagerV2Slice.ts +++ b/invokeai/frontend/web/src/features/modelManagerV2/store/modelManagerV2Slice.ts @@ -52,7 +52,7 @@ export const migrateModelManagerState = (state: any): any => { return state; }; -export const modelManagerPersistConfig: PersistConfig = { +export const modelManagerV2PersistConfig: PersistConfig = { name: modelManagerV2Slice.name, initialState: initialModelManagerState, migrate: migrateModelManagerState, diff --git a/invokeai/frontend/web/src/features/modelManagerV2/subpanels/AddModelPanel/CheckpointConfigsSelect.tsx b/invokeai/frontend/web/src/features/modelManagerV2/subpanels/AddModelPanel/CheckpointConfigsSelect.tsx deleted file mode 100644 index 569f1abbba..0000000000 --- a/invokeai/frontend/web/src/features/modelManagerV2/subpanels/AddModelPanel/CheckpointConfigsSelect.tsx +++ /dev/null @@ -1,32 +0,0 @@ -import type { ChakraProps, ComboboxOnChange, ComboboxOption } from '@invoke-ai/ui-library'; -import { Combobox, FormControl, FormLabel } from '@invoke-ai/ui-library'; -import { memo, useCallback, useMemo } from 'react'; -import { useController, type UseControllerProps } from 'react-hook-form'; -import { useTranslation } from 'react-i18next'; -import { useGetCheckpointConfigsQuery } from 'services/api/endpoints/models'; -import type { CheckpointModelConfig } from 'services/api/types'; - -const sx: ChakraProps['sx'] = { w: 'full' }; - -const CheckpointConfigsSelect = (props: UseControllerProps) => { - const { data } = useGetCheckpointConfigsQuery(); - const { t } = useTranslation(); - const options = useMemo(() => (data ? data.map((i) => ({ label: i, value: i })) : []), [data]); - const { field } = useController(props); - const value = useMemo(() => options.find((o) => o.value === field.value), [field.value, options]); - const onChange = useCallback( - (v) => { - field.onChange(v?.value); - }, - [field] - ); - - return ( - - {t('modelManager.configFile')} - - - ); -}; - -export default memo(CheckpointConfigsSelect); diff --git a/invokeai/frontend/web/src/features/modelManagerV2/subpanels/ModelManager.tsx b/invokeai/frontend/web/src/features/modelManagerV2/subpanels/ModelManager.tsx index bb35ab7ffd..012b2ef2e6 100644 --- a/invokeai/frontend/web/src/features/modelManagerV2/subpanels/ModelManager.tsx +++ b/invokeai/frontend/web/src/features/modelManagerV2/subpanels/ModelManager.tsx @@ -1,6 +1,6 @@ import { Box, Button, Flex, Heading } from '@invoke-ai/ui-library'; import { useAppDispatch } from 'app/store/storeHooks'; -import { SyncModelsIconButton } from 'features/modelManager/components/SyncModels/SyncModelsIconButton'; +import { SyncModelsIconButton } from 'features/modelManagerV2/components/SyncModels/SyncModelsIconButton'; import { setSelectedModelKey } from 'features/modelManagerV2/store/modelManagerV2Slice'; import { useCallback } from 'react'; @@ -20,12 +20,9 @@ export const ModelManager = () => { Model Manager - - - - + diff --git a/invokeai/frontend/web/src/features/modelManagerV2/subpanels/ModelPanel/ModelConvert.tsx b/invokeai/frontend/web/src/features/modelManagerV2/subpanels/ModelPanel/ModelConvert.tsx new file mode 100644 index 0000000000..2d1989ca3f --- /dev/null +++ b/invokeai/frontend/web/src/features/modelManagerV2/subpanels/ModelPanel/ModelConvert.tsx @@ -0,0 +1,104 @@ +import { + Button, + ConfirmationAlertDialog, + Divider, + Flex, + ListItem, + Text, + UnorderedList, + useDisclosure, +} from '@invoke-ai/ui-library'; +import { useAppDispatch } from 'app/store/storeHooks'; +import { addToast } from 'features/system/store/systemSlice'; +import { makeToast } from 'features/system/util/makeToast'; +import { useCallback } from 'react'; +import { useTranslation } from 'react-i18next'; +import { useConvertMainModelsMutation } from 'services/api/endpoints/models'; +import type { CheckpointModelConfig } from 'services/api/types'; + +interface ModelConvertProps { + model: CheckpointModelConfig; +} + +export const ModelConvert = (props: ModelConvertProps) => { + const { model } = props; + const dispatch = useAppDispatch(); + const { t } = useTranslation(); + const [convertModel, { isLoading }] = useConvertMainModelsMutation(); + const { isOpen, onOpen, onClose } = useDisclosure(); + + const modelConvertHandler = useCallback(() => { + dispatch( + addToast( + makeToast({ + title: `${t('modelManager.convertingModelBegin')}: ${model.name}`, + status: 'info', + }) + ) + ); + + convertModel(model.key) + .unwrap() + .then(() => { + dispatch( + addToast( + makeToast({ + title: `${t('modelManager.modelConverted')}: ${model.name}`, + status: 'success', + }) + ) + ); + }) + .catch(() => { + dispatch( + addToast( + makeToast({ + title: `${t('modelManager.modelConversionFailed')}: ${model.name}`, + status: 'error', + }) + ) + ); + }); + }, [convertModel, dispatch, model.base, model.name, t]); + + return ( + <> + + + + {t('modelManager.convertToDiffusersHelpText1')} + + + {t('modelManager.convertToDiffusersHelpText2')} + + + {t('modelManager.convertToDiffusersHelpText3')} + + + {t('modelManager.convertToDiffusersHelpText4')} + + + {t('modelManager.convertToDiffusersHelpText5')} + + + + {t('modelManager.convertToDiffusersHelpText6')} + + + + ); +}; 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 e2ff1c2a0a..8b9142fc45 100644 --- a/invokeai/frontend/web/src/features/modelManagerV2/subpanels/ModelPanel/ModelEdit.tsx +++ b/invokeai/frontend/web/src/features/modelManagerV2/subpanels/ModelPanel/ModelEdit.tsx @@ -171,7 +171,7 @@ export const ModelEdit = () => { colorScheme="invokeYellow" onClick={handleSubmit(onSubmit)} isLoading={isSubmitting} - isDisabled={Boolean(errors)} + isDisabled={Boolean(Object.keys(errors).length)} > Save diff --git a/invokeai/frontend/web/src/features/modelManagerV2/subpanels/ModelPanel/ModelView.tsx b/invokeai/frontend/web/src/features/modelManagerV2/subpanels/ModelPanel/ModelView.tsx index bf320e5692..6c907b3cd8 100644 --- a/invokeai/frontend/web/src/features/modelManagerV2/subpanels/ModelPanel/ModelView.tsx +++ b/invokeai/frontend/web/src/features/modelManagerV2/subpanels/ModelPanel/ModelView.tsx @@ -1,11 +1,11 @@ -import { Box,Button, Flex, Heading, Text } from '@invoke-ai/ui-library'; +import { Box, Button, Flex, Heading, Text } from '@invoke-ai/ui-library'; import { skipToken } from '@reduxjs/toolkit/query'; import { useAppDispatch, useAppSelector } from 'app/store/storeHooks'; import DataViewer from 'features/gallery/components/ImageMetadataViewer/DataViewer'; import { setSelectedModelMode } from 'features/modelManagerV2/store/modelManagerV2Slice'; import { useCallback, useMemo } from 'react'; import { IoPencil } from 'react-icons/io5'; -import { useGetModelConfigQuery,useGetModelMetadataQuery } from 'services/api/endpoints/models'; +import { useGetModelConfigQuery, useGetModelMetadataQuery } from 'services/api/endpoints/models'; import type { CheckpointModelConfig, ControlNetModelConfig, @@ -18,6 +18,7 @@ import type { } from 'services/api/types'; import { ModelAttrView } from './ModelAttrView'; +import { ModelConvert } from './ModelConvert'; export const ModelView = () => { const dispatch = useAppDispatch(); @@ -79,9 +80,12 @@ export const ModelView = () => { {modelData.source && Source: {modelData.source}} - + + + {modelData.type === 'main' && modelData.format === 'checkpoint' && } +