diff --git a/invokeai/frontend/web/public/locales/en.json b/invokeai/frontend/web/public/locales/en.json
index 36cf1d7af6..ed2899cfcb 100644
--- a/invokeai/frontend/web/public/locales/en.json
+++ b/invokeai/frontend/web/public/locales/en.json
@@ -454,7 +454,8 @@
"none": "none",
"addDifference": "Add Difference",
"pickModelType": "Pick Model Type",
- "selectModel": "Select Model"
+ "selectModel": "Select Model",
+ "importModels": "Import Models"
},
"parameters": {
"general": "General",
diff --git a/invokeai/frontend/web/src/features/ui/components/tabs/ModelManager/ModelManagerTab.tsx b/invokeai/frontend/web/src/features/ui/components/tabs/ModelManager/ModelManagerTab.tsx
index 9aced0dda8..6f9a836902 100644
--- a/invokeai/frontend/web/src/features/ui/components/tabs/ModelManager/ModelManagerTab.tsx
+++ b/invokeai/frontend/web/src/features/ui/components/tabs/ModelManager/ModelManagerTab.tsx
@@ -5,7 +5,7 @@ import AddModelsPanel from './subpanels/AddModelsPanel';
import MergeModelsPanel from './subpanels/MergeModelsPanel';
import ModelManagerPanel from './subpanels/ModelManagerPanel';
-type ModelManagerTabName = 'modelManager' | 'addModels' | 'mergeModels';
+type ModelManagerTabName = 'modelManager' | 'importModels' | 'mergeModels';
type ModelManagerTabInfo = {
id: ModelManagerTabName;
@@ -20,8 +20,8 @@ const tabs: ModelManagerTabInfo[] = [
content: ,
},
{
- id: 'addModels',
- label: i18n.t('modelManager.addModel'),
+ id: 'importModels',
+ label: i18n.t('modelManager.importModels'),
content: ,
},
{
@@ -46,7 +46,7 @@ const ModelManagerTab = () => {
))}
-
+
{tabs.map((tab) => (
{tab.content}
diff --git a/invokeai/frontend/web/src/features/ui/components/tabs/ModelManager/subpanels/AddModelsPanel.tsx b/invokeai/frontend/web/src/features/ui/components/tabs/ModelManager/subpanels/AddModelsPanel.tsx
index 32fafe9ae1..d868dc6c80 100644
--- a/invokeai/frontend/web/src/features/ui/components/tabs/ModelManager/subpanels/AddModelsPanel.tsx
+++ b/invokeai/frontend/web/src/features/ui/components/tabs/ModelManager/subpanels/AddModelsPanel.tsx
@@ -1,43 +1,39 @@
-import { Divider, Flex, useColorMode } from '@chakra-ui/react';
-import { RootState } from 'app/store/store';
-import { useAppDispatch, useAppSelector } from 'app/store/storeHooks';
+import { ButtonGroup, Divider, Flex } from '@chakra-ui/react';
import IAIButton from 'common/components/IAIButton';
-import { setAddNewModelUIOption } from 'features/ui/store/uiSlice';
+import { useState } from 'react';
import { useTranslation } from 'react-i18next';
-import AddCheckpointModel from './AddModelsPanel/AddCheckpointModel';
-import AddDiffusersModel from './AddModelsPanel/AddDiffusersModel';
+import AddModels from './AddModelsPanel/AddModels';
+import ScanModels from './AddModelsPanel/ScanModels';
+
+type AddModelTabs = 'add' | 'scan';
export default function AddModelsPanel() {
- const addNewModelUIOption = useAppSelector(
- (state: RootState) => state.ui.addNewModelUIOption
- );
-
- const { colorMode } = useColorMode();
-
- const dispatch = useAppDispatch();
+ const [addModelTab, setAddModelTab] = useState('add');
const { t } = useTranslation();
return (
-
+
dispatch(setAddNewModelUIOption('ckpt'))}
- isChecked={addNewModelUIOption == 'ckpt'}
+ onClick={() => setAddModelTab('add')}
+ isChecked={addModelTab == 'add'}
+ size="sm"
>
- {t('modelManager.addCheckpointModel')}
+ {t('modelManager.addModel')}
dispatch(setAddNewModelUIOption('diffusers'))}
- isChecked={addNewModelUIOption == 'diffusers'}
+ onClick={() => setAddModelTab('scan')}
+ isChecked={addModelTab == 'scan'}
+ size="sm"
>
- {t('modelManager.addDiffuserModel')}
+ {t('modelManager.scanForModels')}
-
+
- {addNewModelUIOption == 'ckpt' && }
- {addNewModelUIOption == 'diffusers' && }
+ {addModelTab == 'add' && }
+ {addModelTab == 'scan' && }
);
}
diff --git a/invokeai/frontend/web/src/features/ui/components/tabs/ModelManager/subpanels/AddModelsPanel/AddDiffusersModel.tsx b/invokeai/frontend/web/src/features/ui/components/tabs/ModelManager/subpanels/AddModelsPanel/AddDiffusersModel.tsx
deleted file mode 100644
index c871a0ede5..0000000000
--- a/invokeai/frontend/web/src/features/ui/components/tabs/ModelManager/subpanels/AddModelsPanel/AddDiffusersModel.tsx
+++ /dev/null
@@ -1,259 +0,0 @@
-import {
- Flex,
- FormControl,
- FormErrorMessage,
- FormHelperText,
- FormLabel,
- Text,
- VStack,
-} from '@chakra-ui/react';
-import { InvokeDiffusersModelConfigProps } from 'app/types/invokeai';
-// import { addNewModel } from 'app/socketio/actions';
-import { useAppDispatch, useAppSelector } from 'app/store/storeHooks';
-import IAIButton from 'common/components/IAIButton';
-import IAIInput from 'common/components/IAIInput';
-import { setAddNewModelUIOption } from 'features/ui/store/uiSlice';
-import { Field, Formik } from 'formik';
-import { useTranslation } from 'react-i18next';
-
-import type { RootState } from 'app/store/store';
-import IAIForm from 'common/components/IAIForm';
-import { IAIFormItemWrapper } from 'common/components/IAIForms/IAIFormItemWrapper';
-
-export default function AddDiffusersModel() {
- const dispatch = useAppDispatch();
- const { t } = useTranslation();
-
- const isProcessing = useAppSelector(
- (state: RootState) => state.system.isProcessing
- );
-
- function hasWhiteSpace(s: string) {
- return /\s/.test(s);
- }
-
- function baseValidation(value: string) {
- let error;
- if (hasWhiteSpace(value)) error = t('modelManager.cannotUseSpaces');
- return error;
- }
-
- const addModelFormValues: InvokeDiffusersModelConfigProps = {
- name: '',
- description: '',
- repo_id: '',
- path: '',
- format: 'diffusers',
- default: false,
- vae: {
- repo_id: '',
- path: '',
- },
- };
-
- const addModelFormSubmitHandler = (
- values: InvokeDiffusersModelConfigProps
- ) => {
- const diffusersModelToAdd = values;
-
- if (values.path === '') delete diffusersModelToAdd.path;
- if (values.repo_id === '') delete diffusersModelToAdd.repo_id;
- if (values.vae.path === '') delete diffusersModelToAdd.vae.path;
- if (values.vae.repo_id === '') delete diffusersModelToAdd.vae.repo_id;
-
- dispatch(addNewModel(diffusersModelToAdd));
- dispatch(setAddNewModelUIOption(null));
- };
-
- return (
-
-
- {({ handleSubmit, errors, touched }) => (
-
-
-
- {/* Name */}
-
-
- {t('modelManager.name')}
-
-
-
- {!!errors.name && touched.name ? (
- {errors.name}
- ) : (
-
- {t('modelManager.nameValidationMsg')}
-
- )}
-
-
-
-
-
- {/* Description */}
-
-
- {t('modelManager.description')}
-
-
-
- {!!errors.description && touched.description ? (
- {errors.description}
- ) : (
-
- {t('modelManager.descriptionValidationMsg')}
-
- )}
-
-
-
-
-
-
- {t('modelManager.formMessageDiffusersModelLocation')}
-
-
- {t('modelManager.formMessageDiffusersModelLocationDesc')}
-
-
- {/* Path */}
-
-
- {t('modelManager.modelLocation')}
-
-
-
- {!!errors.path && touched.path ? (
- {errors.path}
- ) : (
-
- {t('modelManager.modelLocationValidationMsg')}
-
- )}
-
-
-
- {/* Repo ID */}
-
-
- {t('modelManager.repo_id')}
-
-
-
- {!!errors.repo_id && touched.repo_id ? (
- {errors.repo_id}
- ) : (
-
- {t('modelManager.repoIDValidationMsg')}
-
- )}
-
-
-
-
-
- {/* VAE Path */}
-
- {t('modelManager.formMessageDiffusersVAELocation')}
-
-
- {t('modelManager.formMessageDiffusersVAELocationDesc')}
-
-
-
- {t('modelManager.vaeLocation')}
-
-
-
- {!!errors.vae?.path && touched.vae?.path ? (
- {errors.vae?.path}
- ) : (
-
- {t('modelManager.vaeLocationValidationMsg')}
-
- )}
-
-
-
- {/* VAE Repo ID */}
-
-
- {t('modelManager.vaeRepoID')}
-
-
-
- {!!errors.vae?.repo_id && touched.vae?.repo_id ? (
- {errors.vae?.repo_id}
- ) : (
-
- {t('modelManager.vaeRepoIDValidationMsg')}
-
- )}
-
-
-
-
-
- {t('modelManager.addModel')}
-
-
-
- )}
-
-
- );
-}
diff --git a/invokeai/frontend/web/src/features/ui/components/tabs/ModelManager/subpanels/AddModelsPanel/AddModels.tsx b/invokeai/frontend/web/src/features/ui/components/tabs/ModelManager/subpanels/AddModelsPanel/AddModels.tsx
new file mode 100644
index 0000000000..c6517d38a7
--- /dev/null
+++ b/invokeai/frontend/web/src/features/ui/components/tabs/ModelManager/subpanels/AddModelsPanel/AddModels.tsx
@@ -0,0 +1,110 @@
+import { Flex } from '@chakra-ui/react';
+// import { addNewModel } from 'app/socketio/actions';
+import { useAppDispatch, useAppSelector } from 'app/store/storeHooks';
+import { useTranslation } from 'react-i18next';
+
+import { SelectItem } from '@mantine/core';
+import { useForm } from '@mantine/form';
+import { makeToast } from 'app/components/Toaster';
+import { RootState } from 'app/store/store';
+import IAIButton from 'common/components/IAIButton';
+import IAIMantineTextInput from 'common/components/IAIMantineInput';
+import IAIMantineSelect from 'common/components/IAIMantineSelect';
+import { addToast } from 'features/system/store/systemSlice';
+import { useImportMainModelsMutation } from 'services/api/endpoints/models';
+
+const predictionSelectData: SelectItem[] = [
+ { label: 'None', value: 'none' },
+ { label: 'v_prediction', value: 'v_prediction' },
+ { label: 'epsilon', value: 'epsilon' },
+ { label: 'sample', value: 'sample' },
+];
+
+type ExtendedImportModelConfig = {
+ location: string;
+ prediction_type?: 'v_prediction' | 'epsilon' | 'sample' | 'none' | undefined;
+};
+
+export default function AddModels() {
+ const dispatch = useAppDispatch();
+ const { t } = useTranslation();
+
+ const isProcessing = useAppSelector(
+ (state: RootState) => state.system.isProcessing
+ );
+
+ const [importMainModel, { isLoading }] = useImportMainModelsMutation();
+
+ const addModelForm = useForm({
+ initialValues: {
+ location: '',
+ prediction_type: undefined,
+ },
+ });
+
+ const handleAddModelSubmit = (values: ExtendedImportModelConfig) => {
+ const importModelResponseBody = {
+ location: values.location,
+ prediction_type:
+ values.prediction_type === 'none' ? undefined : values.prediction_type,
+ };
+
+ importMainModel({ body: importModelResponseBody })
+ .unwrap()
+ .then((_) => {
+ dispatch(
+ addToast(
+ makeToast({
+ title: 'Model Added',
+ status: 'success',
+ })
+ )
+ );
+ addModelForm.reset();
+ })
+ .catch((error) => {
+ if (error) {
+ console.log(error);
+ dispatch(
+ addToast(
+ makeToast({
+ title: `${error.data.detail} `,
+ status: 'error',
+ })
+ )
+ );
+ }
+ });
+ };
+
+ return (
+
+ );
+}
diff --git a/invokeai/frontend/web/src/features/ui/components/tabs/ModelManager/subpanels/AddModelsPanel/FoundModelsList.tsx b/invokeai/frontend/web/src/features/ui/components/tabs/ModelManager/subpanels/AddModelsPanel/FoundModelsList.tsx
index af862d005d..69dbd20746 100644
--- a/invokeai/frontend/web/src/features/ui/components/tabs/ModelManager/subpanels/AddModelsPanel/FoundModelsList.tsx
+++ b/invokeai/frontend/web/src/features/ui/components/tabs/ModelManager/subpanels/AddModelsPanel/FoundModelsList.tsx
@@ -22,7 +22,11 @@ export default function FoundModelsList() {
}
return (
-
+
{foundModels.map((model) => (
{model}
))}
diff --git a/invokeai/frontend/web/src/features/ui/components/tabs/ModelManager/subpanels/AddModelsPanel/AddCheckpointModel.tsx b/invokeai/frontend/web/src/features/ui/components/tabs/ModelManager/subpanels/AddModelsPanel/ScanModels.tsx
similarity index 99%
rename from invokeai/frontend/web/src/features/ui/components/tabs/ModelManager/subpanels/AddModelsPanel/AddCheckpointModel.tsx
rename to invokeai/frontend/web/src/features/ui/components/tabs/ModelManager/subpanels/AddModelsPanel/ScanModels.tsx
index 75e2017bb8..20f4330658 100644
--- a/invokeai/frontend/web/src/features/ui/components/tabs/ModelManager/subpanels/AddModelsPanel/AddCheckpointModel.tsx
+++ b/invokeai/frontend/web/src/features/ui/components/tabs/ModelManager/subpanels/AddModelsPanel/ScanModels.tsx
@@ -33,7 +33,7 @@ import SearchModels from './SearchModels';
const MIN_MODEL_SIZE = 64;
const MAX_MODEL_SIZE = 2048;
-export default function AddCheckpointModel() {
+export default function ScanModels() {
const dispatch = useAppDispatch();
const { t } = useTranslation();
diff --git a/invokeai/frontend/web/src/features/ui/components/tabs/ModelManager/subpanels/AddModelsPanel/SearchModels.tsx b/invokeai/frontend/web/src/features/ui/components/tabs/ModelManager/subpanels/AddModelsPanel/SearchModels.tsx
index e3e48c7e6b..be2d9ea20d 100644
--- a/invokeai/frontend/web/src/features/ui/components/tabs/ModelManager/subpanels/AddModelsPanel/SearchModels.tsx
+++ b/invokeai/frontend/web/src/features/ui/components/tabs/ModelManager/subpanels/AddModelsPanel/SearchModels.tsx
@@ -11,7 +11,9 @@ export default function SearchModels() {
return (
-
+
+
+
);
}
diff --git a/invokeai/frontend/web/src/features/ui/store/uiTypes.ts b/invokeai/frontend/web/src/features/ui/store/uiTypes.ts
index 4c72bd6239..325e8e898f 100644
--- a/invokeai/frontend/web/src/features/ui/store/uiTypes.ts
+++ b/invokeai/frontend/web/src/features/ui/store/uiTypes.ts
@@ -1,7 +1,5 @@
import { SchedulerParam } from 'features/parameters/types/parameterSchemas';
-export type AddNewModelType = 'ckpt' | 'diffusers' | null;
-
export type Coordinates = {
x: number;
y: number;
@@ -22,7 +20,6 @@ export interface UIState {
shouldUseCanvasBetaLayout: boolean;
shouldShowExistingModelsInSearch: boolean;
shouldUseSliders: boolean;
- addNewModelUIOption: AddNewModelType;
shouldHidePreview: boolean;
shouldPinGallery: boolean;
shouldShowGallery: boolean;
diff --git a/invokeai/frontend/web/src/services/api/endpoints/models.ts b/invokeai/frontend/web/src/services/api/endpoints/models.ts
index a838a82f46..2a5e5547b3 100644
--- a/invokeai/frontend/web/src/services/api/endpoints/models.ts
+++ b/invokeai/frontend/web/src/services/api/endpoints/models.ts
@@ -7,6 +7,7 @@ import {
ControlNetModelConfig,
ConvertModelConfig,
DiffusersModelConfig,
+ ImportModelConfig,
LoRAModelConfig,
MainModelConfig,
MergeModelConfig,
@@ -78,6 +79,13 @@ type MergeMainModelArg = {
type MergeMainModelResponse =
paths['/api/v1/models/merge/{base_model}']['put']['responses']['200']['content']['application/json'];
+type ImportMainModelArg = {
+ body: ImportModelConfig;
+};
+
+type ImportMainModelResponse =
+ paths['/api/v1/models/import']['post']['responses']['201']['content']['application/json'];
+
type SearchFolderResponse =
paths['/api/v1/models/search']['get']['responses']['200']['content']['application/json'];
@@ -168,6 +176,19 @@ export const modelsApi = api.injectEndpoints({
},
invalidatesTags: [{ type: 'MainModel', id: LIST_TAG }],
}),
+ importMainModels: build.mutation<
+ ImportMainModelResponse,
+ ImportMainModelArg
+ >({
+ query: ({ body }) => {
+ return {
+ url: `models/import`,
+ method: 'POST',
+ body: body,
+ };
+ },
+ invalidatesTags: [{ type: 'MainModel', id: LIST_TAG }],
+ }),
deleteMainModels: build.mutation<
DeleteMainModelResponse,
DeleteMainModelArg
@@ -356,6 +377,7 @@ export const {
useGetVaeModelsQuery,
useUpdateMainModelsMutation,
useDeleteMainModelsMutation,
+ useImportMainModelsMutation,
useConvertMainModelsMutation,
useMergeMainModelsMutation,
useGetModelsInFolderQuery,
diff --git a/invokeai/frontend/web/src/services/api/types.d.ts b/invokeai/frontend/web/src/services/api/types.d.ts
index 8519228c5e..6812a62925 100644
--- a/invokeai/frontend/web/src/services/api/types.d.ts
+++ b/invokeai/frontend/web/src/services/api/types.d.ts
@@ -60,7 +60,7 @@ export type AnyModelConfig =
export type MergeModelConfig = components['schemas']['Body_merge_models'];
export type ConvertModelConfig = components['schemas']['Body_convert_model'];
-export type SearchFolderConfig = components['schemas'];
+export type ImportModelConfig = components['schemas']['Body_import_model'];
// Graphs
export type Graph = components['schemas']['Graph'];