diff --git a/invokeai/frontend/web/src/app/store/store.ts b/invokeai/frontend/web/src/app/store/store.ts index 16f1632d88..c63bc02e09 100644 --- a/invokeai/frontend/web/src/app/store/store.ts +++ b/invokeai/frontend/web/src/app/store/store.ts @@ -16,6 +16,7 @@ import { galleryPersistConfig, gallerySlice } from 'features/gallery/store/galle import { hrfPersistConfig, hrfSlice } from 'features/hrf/store/hrfSlice'; import { loraPersistConfig, loraSlice } from 'features/lora/store/loraSlice'; import { modelManagerPersistConfig, modelManagerSlice } from 'features/modelManager/store/modelManagerSlice'; +import { modelManagerV2Slice } from 'features/modelManagerV2/store/modelManagerV2Slice'; import { nodesPersistConfig, nodesSlice } from 'features/nodes/store/nodesSlice'; import { workflowPersistConfig, workflowSlice } from 'features/nodes/store/workflowSlice'; import { generationPersistConfig, generationSlice } from 'features/parameters/store/generationSlice'; @@ -55,6 +56,7 @@ const allReducers = { [changeBoardModalSlice.name]: changeBoardModalSlice.reducer, [loraSlice.name]: loraSlice.reducer, [modelManagerSlice.name]: modelManagerSlice.reducer, + [modelManagerV2Slice.name]: modelManagerV2Slice.reducer, [sdxlSlice.name]: sdxlSlice.reducer, [queueSlice.name]: queueSlice.reducer, [workflowSlice.name]: workflowSlice.reducer, diff --git a/invokeai/frontend/web/src/features/modelManager/subpanels/AddModelsPanel/AddModels.tsx b/invokeai/frontend/web/src/features/modelManager/subpanels/AddModelsPanel/AddModels.tsx index 9b4b95be9e..82ccb7f309 100644 --- a/invokeai/frontend/web/src/features/modelManager/subpanels/AddModelsPanel/AddModels.tsx +++ b/invokeai/frontend/web/src/features/modelManager/subpanels/AddModelsPanel/AddModels.tsx @@ -1,10 +1,10 @@ import { Button, ButtonGroup, Flex, Text } from '@invoke-ai/ui-library'; import { memo, useCallback, useState } from 'react'; import { useTranslation } from 'react-i18next'; +import { useGetModelImportsQuery } from 'services/api/endpoints/models'; import AdvancedAddModels from './AdvancedAddModels'; import SimpleAddModels from './SimpleAddModels'; -import { useGetModelImportsQuery } from '../../../../services/api/endpoints/models'; const AddModels = () => { const { t } = useTranslation(); diff --git a/invokeai/frontend/web/src/features/modelManager/subpanels/ModelManagerPanel.tsx b/invokeai/frontend/web/src/features/modelManager/subpanels/ModelManagerPanel.tsx index dab4e0b872..06b5b7db36 100644 --- a/invokeai/frontend/web/src/features/modelManager/subpanels/ModelManagerPanel.tsx +++ b/invokeai/frontend/web/src/features/modelManager/subpanels/ModelManagerPanel.tsx @@ -3,12 +3,12 @@ import { memo, useState } from 'react'; import { useTranslation } from 'react-i18next'; import { ALL_BASE_MODELS } from 'services/api/constants'; import { useGetLoRAModelsQuery, useGetMainModelsQuery } from 'services/api/endpoints/models'; +import type { DiffusersModelConfig, LoRAConfig, MainModelConfig } from 'services/api/types'; import CheckpointModelEdit from './ModelManagerPanel/CheckpointModelEdit'; import DiffusersModelEdit from './ModelManagerPanel/DiffusersModelEdit'; import LoRAModelEdit from './ModelManagerPanel/LoRAModelEdit'; import ModelList from './ModelManagerPanel/ModelList'; -import { DiffusersModelConfig, LoRAConfig, MainModelConfig } from '../../../services/api/types'; const ModelManagerPanel = () => { const [selectedModelId, setSelectedModelId] = useState(); diff --git a/invokeai/frontend/web/src/features/modelManager/subpanels/ModelManagerPanel/CheckpointModelEdit.tsx b/invokeai/frontend/web/src/features/modelManager/subpanels/ModelManagerPanel/CheckpointModelEdit.tsx index 0dd8a7add6..c24d660cc6 100644 --- a/invokeai/frontend/web/src/features/modelManager/subpanels/ModelManagerPanel/CheckpointModelEdit.tsx +++ b/invokeai/frontend/web/src/features/modelManager/subpanels/ModelManagerPanel/CheckpointModelEdit.tsx @@ -22,8 +22,9 @@ import type { SubmitHandler } from 'react-hook-form'; import { useForm } from 'react-hook-form'; import { useTranslation } from 'react-i18next'; import { useGetCheckpointConfigsQuery, useUpdateModelsMutation } from 'services/api/endpoints/models'; +import type { CheckpointModelConfig } from 'services/api/types'; + import ModelConvert from './ModelConvert'; -import { CheckpointModelConfig } from '../../../../services/api/types'; type CheckpointModelEditProps = { model: CheckpointModelConfig; diff --git a/invokeai/frontend/web/src/features/modelManager/subpanels/ModelManagerPanel/DiffusersModelEdit.tsx b/invokeai/frontend/web/src/features/modelManager/subpanels/ModelManagerPanel/DiffusersModelEdit.tsx index 5be9a5631c..b5023f0eff 100644 --- a/invokeai/frontend/web/src/features/modelManager/subpanels/ModelManagerPanel/DiffusersModelEdit.tsx +++ b/invokeai/frontend/web/src/features/modelManager/subpanels/ModelManagerPanel/DiffusersModelEdit.tsx @@ -9,8 +9,8 @@ import { memo, useCallback } from 'react'; import type { SubmitHandler } from 'react-hook-form'; import { useForm } from 'react-hook-form'; import { useTranslation } from 'react-i18next'; +import { useUpdateModelsMutation } from 'services/api/endpoints/models'; import type { DiffusersModelConfig } from 'services/api/types'; -import { useUpdateModelsMutation } from '../../../../services/api/endpoints/models'; type DiffusersModelEditProps = { model: DiffusersModelConfig; diff --git a/invokeai/frontend/web/src/features/modelManager/subpanels/ModelManagerPanel/LoRAModelEdit.tsx b/invokeai/frontend/web/src/features/modelManager/subpanels/ModelManagerPanel/LoRAModelEdit.tsx index edb73e8275..81f2c4df29 100644 --- a/invokeai/frontend/web/src/features/modelManager/subpanels/ModelManagerPanel/LoRAModelEdit.tsx +++ b/invokeai/frontend/web/src/features/modelManager/subpanels/ModelManagerPanel/LoRAModelEdit.tsx @@ -8,6 +8,7 @@ import { memo, useCallback } from 'react'; import type { SubmitHandler } from 'react-hook-form'; import { useForm } from 'react-hook-form'; import { useTranslation } from 'react-i18next'; +import { useUpdateModelsMutation } from 'services/api/endpoints/models'; import type { LoRAModelConfig } from 'services/api/types'; type LoRAModelEditProps = { diff --git a/invokeai/frontend/web/src/features/modelManager/subpanels/ModelManagerPanel/ModelList.tsx b/invokeai/frontend/web/src/features/modelManager/subpanels/ModelManagerPanel/ModelList.tsx index c546831476..b129b7310d 100644 --- a/invokeai/frontend/web/src/features/modelManager/subpanels/ModelManagerPanel/ModelList.tsx +++ b/invokeai/frontend/web/src/features/modelManager/subpanels/ModelManagerPanel/ModelList.tsx @@ -7,9 +7,9 @@ import { useTranslation } from 'react-i18next'; import { ALL_BASE_MODELS } from 'services/api/constants'; // import type { LoRAConfig, MainModelConfig } from 'services/api/endpoints/models'; import { useGetLoRAModelsQuery, useGetMainModelsQuery } from 'services/api/endpoints/models'; +import type { LoRAConfig, MainModelConfig } from 'services/api/types'; import ModelListItem from './ModelListItem'; -import { LoRAConfig, MainModelConfig } from '../../../../services/api/types'; type ModelListProps = { selectedModelId: string | undefined; diff --git a/invokeai/frontend/web/src/features/modelManager/subpanels/ModelManagerPanel/ModelListItem.tsx b/invokeai/frontend/web/src/features/modelManager/subpanels/ModelManagerPanel/ModelListItem.tsx index 2014d88961..08b9b61aea 100644 --- a/invokeai/frontend/web/src/features/modelManager/subpanels/ModelManagerPanel/ModelListItem.tsx +++ b/invokeai/frontend/web/src/features/modelManager/subpanels/ModelManagerPanel/ModelListItem.tsx @@ -16,7 +16,7 @@ import { memo, useCallback } from 'react'; import { useTranslation } from 'react-i18next'; import { PiTrashSimpleBold } from 'react-icons/pi'; import { useDeleteModelsMutation } from 'services/api/endpoints/models'; -import { LoRAConfig, MainModelConfig } from '../../../../services/api/types'; +import type { LoRAConfig, MainModelConfig } from 'services/api/types'; type ModelListItemProps = { model: MainModelConfig | LoRAConfig; diff --git a/invokeai/frontend/web/src/features/modelManagerV2/store/modelManagerV2Slice.ts b/invokeai/frontend/web/src/features/modelManagerV2/store/modelManagerV2Slice.ts new file mode 100644 index 0000000000..29b071e9b1 --- /dev/null +++ b/invokeai/frontend/web/src/features/modelManagerV2/store/modelManagerV2Slice.ts @@ -0,0 +1,54 @@ +import type { PayloadAction } from '@reduxjs/toolkit'; +import { createSlice } from '@reduxjs/toolkit'; +import type { PersistConfig, RootState } from 'app/store/store'; + + +type ModelManagerState = { + _version: 1; + selectedModelKey: string | null; + searchTerm: string; + filteredModelType: string | null; +}; + +export const initialModelManagerState: ModelManagerState = { + _version: 1, + selectedModelKey: null, + filteredModelType: null, + searchTerm: "" +}; + +export const modelManagerV2Slice = createSlice({ + name: 'modelmanagerV2', + initialState: initialModelManagerState, + reducers: { + setSelectedModelKey: (state, action: PayloadAction) => { + state.selectedModelKey = action.payload; + }, + setSearchTerm: (state, action: PayloadAction) => { + state.searchTerm = action.payload; + }, + + setFilteredModelType: (state, action: PayloadAction) => { + state.filteredModelType = action.payload; + }, + }, +}); + +export const { setSelectedModelKey, setSearchTerm, setFilteredModelType } = modelManagerV2Slice.actions; + +export const selectModelManagerSlice = (state: RootState) => state.modelmanager; + +/* eslint-disable-next-line @typescript-eslint/no-explicit-any */ +export const migrateModelManagerState = (state: any): any => { + if (!('_version' in state)) { + state._version = 1; + } + return state; +}; + +export const modelManagerPersistConfig: PersistConfig = { + name: modelManagerV2Slice.name, + initialState: initialModelManagerState, + migrate: migrateModelManagerState, + persistDenylist: [], +}; diff --git a/invokeai/frontend/web/src/features/modelManagerV2/subpanels/ModelManager.tsx b/invokeai/frontend/web/src/features/modelManagerV2/subpanels/ModelManager.tsx index 59b01de0c0..648f98dbb3 100644 --- a/invokeai/frontend/web/src/features/modelManagerV2/subpanels/ModelManager.tsx +++ b/invokeai/frontend/web/src/features/modelManagerV2/subpanels/ModelManager.tsx @@ -1,17 +1,8 @@ -import { - Box, - Button, - Flex, - Heading, - IconButton, - Input, - InputGroup, - InputRightElement, - Spacer, -} from '@invoke-ai/ui-library'; -import { t } from 'i18next'; -import { PiXBold } from 'react-icons/pi'; -import { SyncModelsIconButton } from '../../modelManager/components/SyncModels/SyncModelsIconButton'; +import { Box, Button, Flex, Heading } from '@invoke-ai/ui-library'; +import { SyncModelsIconButton } from 'features/modelManager/components/SyncModels/SyncModelsIconButton'; + +import ModelList from './ModelManagerPanel/ModelList'; +import { ModelListNavigation } from './ModelManagerPanel/ModelListNavigation'; export const ModelManager = () => { return ( @@ -27,17 +18,8 @@ export const ModelManager = () => { - - - - - ( - - } /> - - ) - - + + ); diff --git a/invokeai/frontend/web/src/features/modelManagerV2/subpanels/ModelManagerPanel/ModelList.tsx b/invokeai/frontend/web/src/features/modelManagerV2/subpanels/ModelManagerPanel/ModelList.tsx new file mode 100644 index 0000000000..bf43c01da2 --- /dev/null +++ b/invokeai/frontend/web/src/features/modelManagerV2/subpanels/ModelManagerPanel/ModelList.tsx @@ -0,0 +1,160 @@ +import { Flex, Spinner, Text } from '@invoke-ai/ui-library'; +import type { EntityState } from '@reduxjs/toolkit'; +import { useAppSelector } from 'app/store/storeHooks'; +import { forEach } from 'lodash-es'; +import { memo } from 'react'; +import { ALL_BASE_MODELS } from 'services/api/constants'; +import { + useGetControlNetModelsQuery, + useGetIPAdapterModelsQuery, + useGetLoRAModelsQuery, + useGetMainModelsQuery, + useGetT2IAdapterModelsQuery, + useGetTextualInversionModelsQuery, + useGetVaeModelsQuery, +} from 'services/api/endpoints/models'; +import type { AnyModelConfig } from 'services/api/types'; + +import { ModelListWrapper } from './ModelListWrapper'; + +const ModelList = () => { + const { searchTerm, filteredModelType } = useAppSelector((s) => s.modelmanagerV2); + + const { filteredMainModels, isLoadingMainModels } = useGetMainModelsQuery(ALL_BASE_MODELS, { + selectFromResult: ({ data, isLoading }) => ({ + filteredMainModels: modelsFilter(data, searchTerm, filteredModelType), + isLoadingMainModels: isLoading, + }), + }); + + const { filteredLoraModels, isLoadingLoraModels } = useGetLoRAModelsQuery(undefined, { + selectFromResult: ({ data, isLoading }) => ({ + filteredLoraModels: modelsFilter(data, searchTerm, filteredModelType), + isLoadingLoraModels: isLoading, + }), + }); + + const { filteredTextualInversionModels, isLoadingTextualInversionModels } = useGetTextualInversionModelsQuery( + undefined, + { + selectFromResult: ({ data, isLoading }) => ({ + filteredTextualInversionModels: modelsFilter(data, searchTerm, filteredModelType), + isLoadingTextualInversionModels: isLoading, + }), + } + ); + + const { filteredControlnetModels, isLoadingControlnetModels } = useGetControlNetModelsQuery(undefined, { + selectFromResult: ({ data, isLoading }) => ({ + filteredControlnetModels: modelsFilter(data, searchTerm, filteredModelType), + isLoadingControlnetModels: isLoading, + }), + }); + + const { filteredT2iAdapterModels, isLoadingT2IAdapterModels } = useGetT2IAdapterModelsQuery(undefined, { + selectFromResult: ({ data, isLoading }) => ({ + filteredT2iAdapterModels: modelsFilter(data, searchTerm, filteredModelType), + isLoadingT2IAdapterModels: isLoading, + }), + }); + + const { filteredIpAdapterModels, isLoadingIpAdapterModels } = useGetIPAdapterModelsQuery(undefined, { + selectFromResult: ({ data, isLoading }) => ({ + filteredIpAdapterModels: modelsFilter(data, searchTerm, filteredModelType), + isLoadingIpAdapterModels: isLoading, + }), + }); + + const { filteredVaeModels, isLoadingVaeModels } = useGetVaeModelsQuery(undefined, { + selectFromResult: ({ data, isLoading }) => ({ + filteredVaeModels: modelsFilter(data, searchTerm, filteredModelType), + isLoadingVaeModels: isLoading, + }), + }); + + return ( + + + {/* Main Model List */} + {isLoadingMainModels && } + {!isLoadingMainModels && filteredMainModels.length > 0 && ( + + )} + {/* LoRAs List */} + {isLoadingLoraModels && } + {!isLoadingLoraModels && filteredLoraModels.length > 0 && ( + + )} + + {/* TI List */} + {isLoadingTextualInversionModels && } + {!isLoadingTextualInversionModels && filteredTextualInversionModels.length > 0 && ( + + )} + + {/* VAE List */} + {isLoadingVaeModels && } + {!isLoadingVaeModels && filteredVaeModels.length > 0 && ( + + )} + + {/* Controlnet List */} + {isLoadingControlnetModels && } + {!isLoadingControlnetModels && filteredControlnetModels.length > 0 && ( + + )} + {/* IP Adapter List */} + {isLoadingIpAdapterModels && } + {!isLoadingIpAdapterModels && filteredIpAdapterModels.length > 0 && ( + + )} + {/* T2I Adapters List */} + {isLoadingT2IAdapterModels && } + {!isLoadingT2IAdapterModels && filteredT2iAdapterModels.length > 0 && ( + + )} + + + ); +}; + +export default memo(ModelList); + +const modelsFilter = ( + data: EntityState | undefined, + nameFilter: string, + filteredModelType: string | null +): T[] => { + const filteredModels: T[] = []; + + forEach(data?.entities, (model) => { + if (!model) { + return; + } + + const matchesFilter = model.name.toLowerCase().includes(nameFilter.toLowerCase()); + const matchesType = filteredModelType ? model.type === filteredModelType : true; + + if (matchesFilter && matchesType) { + filteredModels.push(model); + } + }); + return filteredModels; +}; + +const FetchingModelsLoader = memo(({ loadingMessage }: { loadingMessage?: string }) => { + return ( + + + + {loadingMessage ? loadingMessage : 'Fetching...'} + + + ); +}); + +FetchingModelsLoader.displayName = 'FetchingModelsLoader'; diff --git a/invokeai/frontend/web/src/features/modelManagerV2/subpanels/ModelManagerPanel/ModelListHeader.tsx b/invokeai/frontend/web/src/features/modelManagerV2/subpanels/ModelManagerPanel/ModelListHeader.tsx new file mode 100644 index 0000000000..874d1c9ac2 --- /dev/null +++ b/invokeai/frontend/web/src/features/modelManagerV2/subpanels/ModelManagerPanel/ModelListHeader.tsx @@ -0,0 +1,23 @@ +import { Box, Divider, Text } from '@invoke-ai/ui-library'; + +export const ModelListHeader = ({ title }: { title: string }) => { + return ( + + + + + {title} + + + + ); +}; diff --git a/invokeai/frontend/web/src/features/modelManagerV2/subpanels/ModelManagerPanel/ModelListItem.tsx b/invokeai/frontend/web/src/features/modelManagerV2/subpanels/ModelManagerPanel/ModelListItem.tsx new file mode 100644 index 0000000000..5cc429ebcd --- /dev/null +++ b/invokeai/frontend/web/src/features/modelManagerV2/subpanels/ModelManagerPanel/ModelListItem.tsx @@ -0,0 +1,115 @@ +import { + Badge, + Button, + ConfirmationAlertDialog, + Flex, + IconButton, + Text, + Tooltip, + useDisclosure, +} from '@invoke-ai/ui-library'; +import { useAppDispatch, useAppSelector } from 'app/store/storeHooks'; +import { setSelectedModelKey } from 'features/modelManagerV2/store/modelManagerV2Slice'; +import { MODEL_TYPE_SHORT_MAP } from 'features/parameters/types/constants'; +import { addToast } from 'features/system/store/systemSlice'; +import { makeToast } from 'features/system/util/makeToast'; +import { memo, useCallback, useMemo } from 'react'; +import { useTranslation } from 'react-i18next'; +import { PiTrashSimpleBold } from 'react-icons/pi'; +import { useDeleteModelsMutation } from 'services/api/endpoints/models'; +import type { AnyModelConfig } from 'services/api/types'; + +type ModelListItemProps = { + model: AnyModelConfig; +}; + +const ModelListItem = (props: ModelListItemProps) => { + const { t } = useTranslation(); + const dispatch = useAppDispatch(); + const selectedModelKey = useAppSelector((s) => s.modelmanagerV2.selectedModelKey); + const [deleteModel] = useDeleteModelsMutation(); + const { isOpen, onOpen, onClose } = useDisclosure(); + + const { model } = props; + + const handleSelectModel = useCallback(() => { + dispatch(setSelectedModelKey(model.key)); + }, [model.key, dispatch]); + + const isSelected = useMemo(() => { + return selectedModelKey === model.key; + }, [selectedModelKey, model.key]); + + const handleModelDelete = useCallback(() => { + deleteModel({ key: model.key }) + .unwrap() + .then((_) => { + dispatch( + addToast( + makeToast({ + title: `${t('modelManager.modelDeleted')}: ${model.name}`, + status: 'success', + }) + ) + ); + }) + .catch((error) => { + if (error) { + dispatch( + addToast( + makeToast({ + title: `${t('modelManager.modelDeleteFailed')}: ${model.name}`, + status: 'error', + }) + ) + ); + } + }); + dispatch(setSelectedModelKey(null)); + }, [deleteModel, model, dispatch, t]); + + return ( + + + + + {MODEL_TYPE_SHORT_MAP[model.base as keyof typeof MODEL_TYPE_SHORT_MAP]} + + + {model.name} + + + + } + aria-label={t('modelManager.deleteConfig')} + colorScheme="error" + /> + + + {t('modelManager.deleteMsg1')} + {t('modelManager.deleteMsg2')} + + + + ); +}; + +export default memo(ModelListItem); diff --git a/invokeai/frontend/web/src/features/modelManagerV2/subpanels/ModelManagerPanel/ModelListNavigation.tsx b/invokeai/frontend/web/src/features/modelManagerV2/subpanels/ModelManagerPanel/ModelListNavigation.tsx new file mode 100644 index 0000000000..c0d06af245 --- /dev/null +++ b/invokeai/frontend/web/src/features/modelManagerV2/subpanels/ModelManagerPanel/ModelListNavigation.tsx @@ -0,0 +1,52 @@ +import { Flex, IconButton,Input, InputGroup, InputRightElement, Spacer } from '@invoke-ai/ui-library'; +import { useAppDispatch, useAppSelector } from 'app/store/storeHooks'; +import { setSearchTerm } from 'features/modelManagerV2/store/modelManagerV2Slice'; +import { t } from 'i18next'; +import type { ChangeEventHandler} from 'react'; +import { useCallback } from 'react'; +import { PiXBold } from 'react-icons/pi'; + +import { ModelTypeFilter } from './ModelTypeFilter'; + +export const ModelListNavigation = () => { + const dispatch = useAppDispatch(); + const searchTerm = useAppSelector((s) => s.modelmanagerV2.searchTerm); + + const handleSearch: ChangeEventHandler = useCallback( + (event) => { + dispatch(setSearchTerm(event.target.value)); + }, + [dispatch] + ); + + const clearSearch = useCallback(() => { + dispatch(setSearchTerm('')); + }, [dispatch]); + + return ( + + + + + + + {!!searchTerm?.length && ( + + } + onClick={clearSearch} + /> + + )} + + + ); +}; diff --git a/invokeai/frontend/web/src/features/modelManagerV2/subpanels/ModelManagerPanel/ModelListWrapper.tsx b/invokeai/frontend/web/src/features/modelManagerV2/subpanels/ModelManagerPanel/ModelListWrapper.tsx new file mode 100644 index 0000000000..24460e6453 --- /dev/null +++ b/invokeai/frontend/web/src/features/modelManagerV2/subpanels/ModelManagerPanel/ModelListWrapper.tsx @@ -0,0 +1,25 @@ +import { Flex } from '@invoke-ai/ui-library'; +import type { AnyModelConfig } from 'services/api/types'; + +import { ModelListHeader } from './ModelListHeader'; +import ModelListItem from './ModelListItem'; + +type ModelListWrapperProps = { + title: string; + modelList: AnyModelConfig[]; +}; + +export const ModelListWrapper = (props: ModelListWrapperProps) => { + const { title, modelList } = props; + return ( + + + + + {modelList.map((model) => ( + + ))} + + + ); +}; diff --git a/invokeai/frontend/web/src/features/modelManagerV2/subpanels/ModelManagerPanel/ModelTypeFilter.tsx b/invokeai/frontend/web/src/features/modelManagerV2/subpanels/ModelManagerPanel/ModelTypeFilter.tsx new file mode 100644 index 0000000000..0134ffc811 --- /dev/null +++ b/invokeai/frontend/web/src/features/modelManagerV2/subpanels/ModelManagerPanel/ModelTypeFilter.tsx @@ -0,0 +1,54 @@ +import { Button, Menu, MenuButton, MenuItem, MenuList } from '@invoke-ai/ui-library'; +import { useAppDispatch, useAppSelector } from 'app/store/storeHooks'; +import { setFilteredModelType } from 'features/modelManagerV2/store/modelManagerV2Slice'; +import { useCallback } from 'react'; +import { IoFilter } from 'react-icons/io5'; + +export const MODEL_TYPE_LABELS: { [key: string]: string } = { + main: 'Main', + lora: 'LoRA', + embedding: 'Textual Inversion', + controlnet: 'ControlNet', + vae: 'VAE', + t2i_adapter: 'T2I Adapter', + ip_adapter: 'IP Adapter', + clip_vision: 'Clip Vision', + onnx: 'Onnx', +}; + +export const ModelTypeFilter = () => { + const dispatch = useAppDispatch(); + const filteredModelType = useAppSelector((s) => s.modelmanagerV2.filteredModelType); + + const selectModelType = useCallback( + (option: string) => { + dispatch(setFilteredModelType(option)); + }, + [dispatch] + ); + + const clearModelType = useCallback(() => { + dispatch(setFilteredModelType(null)); + }, [dispatch]); + + return ( + + }> + {filteredModelType ? MODEL_TYPE_LABELS[filteredModelType] : 'All Models'} + + + All Models + {Object.keys(MODEL_TYPE_LABELS).map((option) => ( + + {MODEL_TYPE_LABELS[option]} + + ))} + + + ); +}; diff --git a/invokeai/frontend/web/src/features/modelManagerV2/types.ts b/invokeai/frontend/web/src/features/modelManagerV2/types.ts new file mode 100644 index 0000000000..a209fbb876 --- /dev/null +++ b/invokeai/frontend/web/src/features/modelManagerV2/types.ts @@ -0,0 +1,14 @@ +import { z } from "zod"; + +export const zBaseModel = z.enum(['any', 'sd-1', 'sd-2', 'sdxl', 'sdxl-refiner']); +export const zModelType = z.enum([ + 'main', + 'vae', + 'lora', + 'controlnet', + 'embedding', + 'ip_adapter', + 'clip_vision', + 't2i_adapter', + 'onnx', // TODO(psyche): Remove this when removed from backend +]); \ No newline at end of file diff --git a/invokeai/frontend/web/src/features/ui/components/tabs/ModelManagerTab.tsx b/invokeai/frontend/web/src/features/ui/components/tabs/ModelManagerTab.tsx index 50db10fb57..8117631f22 100644 --- a/invokeai/frontend/web/src/features/ui/components/tabs/ModelManagerTab.tsx +++ b/invokeai/frontend/web/src/features/ui/components/tabs/ModelManagerTab.tsx @@ -1,8 +1,7 @@ -import { Flex, Box } from '@invoke-ai/ui-library'; +import { Box,Flex } from '@invoke-ai/ui-library'; +import { ImportModels } from 'features/modelManagerV2/subpanels/ImportModels'; +import { ModelManager } from 'features/modelManagerV2/subpanels/ModelManager'; import { memo } from 'react'; -import { useTranslation } from 'react-i18next'; -import { ImportModels } from '../../../modelManagerV2/subpanels/ImportModels'; -import { ModelManager } from '../../../modelManagerV2/subpanels/ModelManager'; const ModelManagerTab = () => { return (