diff --git a/invokeai/frontend/web/src/features/modelManagerV2/subpanels/ModelManagerPanel/ModelListItem.tsx b/invokeai/frontend/web/src/features/modelManagerV2/subpanels/ModelManagerPanel/ModelListItem.tsx index 5cc429ebcd..d6cb70f4e8 100644 --- a/invokeai/frontend/web/src/features/modelManagerV2/subpanels/ModelManagerPanel/ModelListItem.tsx +++ b/invokeai/frontend/web/src/features/modelManagerV2/subpanels/ModelManagerPanel/ModelListItem.tsx @@ -3,10 +3,12 @@ import { Button, ConfirmationAlertDialog, Flex, + Icon, IconButton, Text, Tooltip, useDisclosure, + Box, } from '@invoke-ai/ui-library'; import { useAppDispatch, useAppSelector } from 'app/store/storeHooks'; import { setSelectedModelKey } from 'features/modelManagerV2/store/modelManagerV2Slice'; @@ -15,6 +17,7 @@ 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 { IoWarning } from 'react-icons/io5'; import { PiTrashSimpleBold } from 'react-icons/pi'; import { useDeleteModelsMutation } from 'services/api/endpoints/models'; import type { AnyModelConfig } from 'services/api/types'; @@ -88,8 +91,16 @@ const ModelListItem = (props: ModelListItemProps) => { {model.name} + {model.format === 'checkpoint' && ( + + + + + + )} + } diff --git a/invokeai/frontend/web/src/features/modelManagerV2/subpanels/ModelPane.tsx b/invokeai/frontend/web/src/features/modelManagerV2/subpanels/ModelPane.tsx new file mode 100644 index 0000000000..9756b357b3 --- /dev/null +++ b/invokeai/frontend/web/src/features/modelManagerV2/subpanels/ModelPane.tsx @@ -0,0 +1,13 @@ +import { Box } from '@invoke-ai/ui-library'; +import { useAppSelector } from '../../../app/store/storeHooks'; +import { ImportModels } from './ImportModels'; +import { ModelView } from './ModelPanel/ModelView'; + +export const ModelPane = () => { + const selectedModelKey = useAppSelector((s) => s.modelmanagerV2.selectedModelKey); + return ( + + {selectedModelKey ? : } + + ); +}; diff --git a/invokeai/frontend/web/src/features/modelManagerV2/subpanels/ModelPanel/ModelAttrView.tsx b/invokeai/frontend/web/src/features/modelManagerV2/subpanels/ModelPanel/ModelAttrView.tsx new file mode 100644 index 0000000000..f45bfca993 --- /dev/null +++ b/invokeai/frontend/web/src/features/modelManagerV2/subpanels/ModelPanel/ModelAttrView.tsx @@ -0,0 +1,15 @@ +import { FormControl, FormLabel, Text } from '@invoke-ai/ui-library'; + +interface Props { + label: string; + value: string | null | undefined; +} + +export const ModelAttrView = ({ label, value }: Props) => { + return ( + + {label} + {value || '-'} + + ); +}; diff --git a/invokeai/frontend/web/src/features/modelManagerV2/subpanels/ModelPanel/ModelView.tsx b/invokeai/frontend/web/src/features/modelManagerV2/subpanels/ModelPanel/ModelView.tsx new file mode 100644 index 0000000000..83e4e5380f --- /dev/null +++ b/invokeai/frontend/web/src/features/modelManagerV2/subpanels/ModelPanel/ModelView.tsx @@ -0,0 +1,116 @@ +import { skipToken } from '@reduxjs/toolkit/query'; +import { useAppSelector } from '../../../../app/store/storeHooks'; +import { useGetModelQuery } from '../../../../services/api/endpoints/models'; +import { Flex, Text, Heading } from '@invoke-ai/ui-library'; +import DataViewer from '../../../gallery/components/ImageMetadataViewer/DataViewer'; +import { useMemo } from 'react'; +import { + CheckpointModelConfig, + ControlNetConfig, + DiffusersModelConfig, + IPAdapterConfig, + LoRAConfig, + T2IAdapterConfig, + TextualInversionConfig, + VAEConfig, +} from '../../../../services/api/types'; +import { ModelAttrView } from './ModelAttrView'; + +export const ModelView = () => { + const selectedModelKey = useAppSelector((s) => s.modelmanagerV2.selectedModelKey); + const { data, isLoading } = useGetModelQuery(selectedModelKey ?? skipToken); + + const modelConfigData = useMemo(() => { + if (!data) { + return null; + } + const modelFormat = data.config.format; + const modelType = data.config.type; + + if (modelType === 'main') { + if (modelFormat === 'diffusers') { + return data.config as DiffusersModelConfig; + } else if (modelFormat === 'checkpoint') { + return data.config as CheckpointModelConfig; + } + } + + switch (modelType) { + case 'lora': + return data.config as LoRAConfig; + case 'embedding': + return data.config as TextualInversionConfig; + case 't2i_adapter': + return data.config as T2IAdapterConfig; + case 'ip_adapter': + return data.config as IPAdapterConfig; + case 'controlnet': + return data.config as ControlNetConfig; + case 'vae': + return data.config as VAEConfig; + default: + return null; + } + }, [data]); + + if (isLoading) { + return Loading; + } + + if (!modelConfigData) { + return Something went wrong; + } + return ( + + + + {modelConfigData.name} + + {modelConfigData.source && Source: {modelConfigData.source}} + + + + + + + + + + + + + + + {modelConfigData.type === 'main' && ( + <> + + {modelConfigData.format === 'diffusers' && ( + + )} + {modelConfigData.format === 'checkpoint' && ( + + )} + + + + + + + + + + + + + )} + {modelConfigData.type === 'ip_adapter' && ( + + + + )} + + + {!!data?.metadata && } + + ); +}; diff --git a/invokeai/frontend/web/src/features/modelManagerV2/types.ts b/invokeai/frontend/web/src/features/modelManagerV2/types.ts deleted file mode 100644 index a209fbb876..0000000000 --- a/invokeai/frontend/web/src/features/modelManagerV2/types.ts +++ /dev/null @@ -1,14 +0,0 @@ -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 8117631f22..9245f5c60d 100644 --- a/invokeai/frontend/web/src/features/ui/components/tabs/ModelManagerTab.tsx +++ b/invokeai/frontend/web/src/features/ui/components/tabs/ModelManagerTab.tsx @@ -1,16 +1,25 @@ -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 { Flex, Heading, Tab, TabList, TabPanel, TabPanels, Tabs, Box, Button } from '@invoke-ai/ui-library'; +import ImportModelsPanel from 'features/modelManager/subpanels/ImportModelsPanel'; +import MergeModelsPanel from 'features/modelManager/subpanels/MergeModelsPanel'; +import ModelManagerPanel from 'features/modelManager/subpanels/ModelManagerPanel'; +import ModelManagerSettingsPanel from 'features/modelManager/subpanels/ModelManagerSettingsPanel'; +import type { ReactNode } from 'react'; +import { memo, useMemo } from 'react'; +import { useTranslation } from 'react-i18next'; +import { SyncModelsIconButton } from '../../../modelManager/components/SyncModels/SyncModelsIconButton'; +import { ModelManager } from '../../../modelManagerV2/subpanels/ModelManager'; +import { ModelPane } from '../../../modelManagerV2/subpanels/ModelPane'; + +type ModelManagerTabName = 'modelManager' | 'importModels' | 'mergeModels' | 'settings'; const ModelManagerTab = () => { + const { t } = useTranslation(); + return ( - - - - - - + + + + ); }; diff --git a/invokeai/frontend/web/src/services/api/endpoints/models.ts b/invokeai/frontend/web/src/services/api/endpoints/models.ts index 6897768a1b..5db2dea00e 100644 --- a/invokeai/frontend/web/src/services/api/endpoints/models.ts +++ b/invokeai/frontend/web/src/services/api/endpoints/models.ts @@ -26,6 +26,10 @@ type UpdateModelArg = { type UpdateModelResponse = paths['/api/v2/models/i/{key}']['patch']['responses']['200']['content']['application/json']; + +type GetModelResponse = + paths['/api/v2/models/i/{key}']['get']['responses']['200']['content']['application/json']; + type ListModelsArg = NonNullable; type DeleteMainModelArg = { @@ -165,6 +169,12 @@ export const modelsApi = api.injectEndpoints({ providesTags: buildProvidesTags('MainModel'), transformResponse: buildTransformResponse(mainModelsAdapter), }), + getModel: build.query({ + query: (key) => { + return buildModelsUrl(`i/${key}`); + }, + providesTags: ['Model'], + }), updateModels: build.mutation({ query: ({ key, body }) => { return { @@ -320,4 +330,5 @@ export const { useGetModelsInFolderQuery, useGetCheckpointConfigsQuery, useGetModelImportsQuery, + useGetModelQuery } = modelsApi; diff --git a/invokeai/frontend/web/src/services/api/schema.ts b/invokeai/frontend/web/src/services/api/schema.ts index 3566fdb9e2..c9690649f8 100644 --- a/invokeai/frontend/web/src/services/api/schema.ts +++ b/invokeai/frontend/web/src/services/api/schema.ts @@ -22,7 +22,7 @@ export type paths = { "/api/v2/models/i/{key}": { /** * Get Model Record - * @description Get a model record + * @description Get a model record and metadata */ get: operations["get_model_record"]; /** @@ -4202,6 +4202,13 @@ export type components = { */ type: "freeu"; }; + /** GetModelResponse */ + GetModelResponse: { + /** Config */ + config: (components["schemas"]["MainDiffusersConfig"] | components["schemas"]["MainCheckpointConfig"]) | (components["schemas"]["ONNXSD1Config"] | components["schemas"]["ONNXSD2Config"] | components["schemas"]["ONNXSDXLConfig"]) | (components["schemas"]["VaeDiffusersConfig"] | components["schemas"]["VaeCheckpointConfig"]) | (components["schemas"]["ControlNetDiffusersConfig"] | components["schemas"]["ControlNetCheckpointConfig"]) | components["schemas"]["LoRAConfig"] | components["schemas"]["TextualInversionConfig"] | components["schemas"]["IPAdapterConfig"] | components["schemas"]["CLIPVisionDiffusersConfig"] | components["schemas"]["T2IConfig"]; + /** Metadata */ + metadata: (components["schemas"]["BaseMetadata"] | components["schemas"]["HuggingFaceMetadata"] | components["schemas"]["CivitaiMetadata"]) | null; + }; /** Graph */ Graph: { /** @@ -11169,7 +11176,7 @@ export type operations = { }; /** * Get Model Record - * @description Get a model record + * @description Get a model record and metadata */ get_model_record: { parameters: { @@ -11182,7 +11189,7 @@ export type operations = { /** @description The model configuration was retrieved successfully */ 200: { content: { - "application/json": (components["schemas"]["MainDiffusersConfig"] | components["schemas"]["MainCheckpointConfig"]) | (components["schemas"]["ONNXSD1Config"] | components["schemas"]["ONNXSD2Config"] | components["schemas"]["ONNXSDXLConfig"]) | (components["schemas"]["VaeDiffusersConfig"] | components["schemas"]["VaeCheckpointConfig"]) | (components["schemas"]["ControlNetDiffusersConfig"] | components["schemas"]["ControlNetCheckpointConfig"]) | components["schemas"]["LoRAConfig"] | components["schemas"]["TextualInversionConfig"] | components["schemas"]["IPAdapterConfig"] | components["schemas"]["CLIPVisionDiffusersConfig"] | components["schemas"]["T2IConfig"]; + "application/json": components["schemas"]["GetModelResponse"]; }; }; /** @description Bad request */