From 2ceefab18c72566a00d0339d0f2ce66f56c89249 Mon Sep 17 00:00:00 2001 From: jialin Date: Wed, 19 Mar 2025 19:47:58 +0800 Subject: [PATCH] feat: download model --- src/components/drop-down-actions/index.tsx | 59 +-- .../seal-table/components/header-prefix.tsx | 2 +- src/layouts/runtime.tsx | 2 +- src/pages/llmodels/components/table-list.tsx | 340 +++--------------- src/pages/llmodels/config/button-actions.ts | 234 ++++++++++++ src/pages/llmodels/download/index.tsx | 202 +++++++++++ src/pages/llmodels/download/target-form.tsx | 122 +++++++ .../resources/components/model-files.tsx | 305 ++++++++++++++++ src/pages/resources/config/types.ts | 17 + src/pages/resources/index.tsx | 5 + 10 files changed, 971 insertions(+), 317 deletions(-) create mode 100644 src/pages/llmodels/config/button-actions.ts create mode 100644 src/pages/llmodels/download/index.tsx create mode 100644 src/pages/llmodels/download/target-form.tsx create mode 100644 src/pages/resources/components/model-files.tsx diff --git a/src/components/drop-down-actions/index.tsx b/src/components/drop-down-actions/index.tsx index 78cb702f..97e3f1f6 100644 --- a/src/components/drop-down-actions/index.tsx +++ b/src/components/drop-down-actions/index.tsx @@ -1,34 +1,35 @@ -import * as icons from '@ant-design/icons'; -import { Button, Dropdown, Space } from 'antd'; -import type { MenuProps } from 'antd/es/menu'; -import react from 'react'; +import { useIntl } from '@umijs/max'; +import { Dropdown, DropDownProps } from 'antd'; +import React, { useMemo } from 'react'; -type DropdownProps = { - trigger?: Array<'click' | 'hover' | 'contextMenu'>; - items: MenuProps['items']; - onClick: (e: any) => void; -}; -const renderIcon = (icon: string) => { - if (!icon) { - return null; - } - // @ts-ignore - const Icon = icons[icon] as React.FC; - return react.createElement(Icon); -}; -const DropDownActions: React.FC = (props) => { - const { trigger, items, onClick } = props; +const DropDownActions: React.FC = (props) => { + const { + menu, + trigger = ['hover'], + placement = 'bottomRight', + children, + ...rest + } = props; + const intl = useIntl(); + + const items = useMemo(() => { + return menu?.items?.map((item: any) => ({ + ...item, + label: item.locale ? intl.formatMessage({ id: item.label }) : item.label + })); + }, [menu?.items, intl]); return ( - - - - - + + {children} + ); }; diff --git a/src/components/seal-table/components/header-prefix.tsx b/src/components/seal-table/components/header-prefix.tsx index d8e86fe7..84a626f7 100644 --- a/src/components/seal-table/components/header-prefix.tsx +++ b/src/components/seal-table/components/header-prefix.tsx @@ -38,7 +38,7 @@ const HeaderPrefix: React.FC = (props) => { return (
{_.isBoolean(expandable) ? ( diff --git a/src/layouts/runtime.tsx b/src/layouts/runtime.tsx index 10a92e6f..549ec942 100644 --- a/src/layouts/runtime.tsx +++ b/src/layouts/runtime.tsx @@ -26,7 +26,7 @@ export function patchRoutes({ routes, initialState }) { export function renderMenuIcon(icon: string) { const upperIcon = formatIcon(icon); - console.log('upperIcon', upperIcon); + console.log('upperIcon', icon, upperIcon); if (icons[upperIcon] || icons[upperIcon + 'Outlined']) { return React.createElement( icons[upperIcon] || icons[upperIcon + 'Outlined'] diff --git a/src/pages/llmodels/components/table-list.tsx b/src/pages/llmodels/components/table-list.tsx index 7409f544..23df5aff 100644 --- a/src/pages/llmodels/components/table-list.tsx +++ b/src/pages/llmodels/components/table-list.tsx @@ -1,14 +1,13 @@ import { modelsExpandKeysAtom } from '@/atoms/models'; import AutoTooltip from '@/components/auto-tooltip'; import DeleteModal from '@/components/delete-modal'; +import DropDownActions from '@/components/drop-down-actions'; import DropdownButtons from '@/components/drop-down-buttons'; -import IconFont from '@/components/icon-font'; import { PageSize } from '@/components/logs-viewer/config'; import PageTools from '@/components/page-tools'; import SealTable from '@/components/seal-table'; import { SealColumnProps } from '@/components/seal-table/types'; import { PageAction } from '@/config'; -import HotKeys from '@/config/hotkeys'; import useBodyScroll from '@/hooks/use-body-scroll'; import useExpandedRowKeys from '@/hooks/use-expanded-row-keys'; import useTableRowSelection from '@/hooks/use-table-row-selection'; @@ -24,18 +23,14 @@ import { writeState } from '@/utils/localstore/index'; import { - DeleteOutlined, DownOutlined, - EditOutlined, - ExperimentOutlined, QuestionCircleOutlined, SyncOutlined } from '@ant-design/icons'; import { PageContainer } from '@ant-design/pro-components'; -import { useAccess, useIntl, useNavigate } from '@umijs/max'; +import { useIntl, useNavigate } from '@umijs/max'; import { Button, - Dropdown, Empty, Input, Select, @@ -47,8 +42,14 @@ import { import dayjs from 'dayjs'; import { useAtom } from 'jotai'; import _ from 'lodash'; -import { memo, useCallback, useEffect, useMemo, useRef, useState } from 'react'; -import { useHotkeys } from 'react-hotkeys-hook'; +import React, { + memo, + useCallback, + useEffect, + useMemo, + useRef, + useState +} from 'react'; import { MODELS_API, MODEL_INSTANCE_API, @@ -63,9 +64,16 @@ import { backendOptionsMap, getSourceRepoConfigValue, modelCategories, - modelCategoriesMap, modelSourceMap } from '../config'; +import { + ButtonList, + categoryToPathMap, + generateSource, + modalConfig, + setModelActionList, + sourceOptions +} from '../config/button-actions'; import { FormData, ListItem, ModelInstanceListItem } from '../config/types'; import { useGenerateFormEditInitialValues } from '../hooks'; import DeployModal from './deploy-modal'; @@ -100,90 +108,19 @@ interface ModelsProps { total: number; } -const ActionList = [ - { - label: 'common.button.edit', - key: 'edit', - icon: - }, - { - label: 'models.openinplayground', - key: 'chat', - icon: - }, - { - label: 'common.button.stop', - key: 'stop', - icon: - }, - { - label: 'common.button.start', - key: 'start', - icon: - }, - { - label: 'common.button.delete', - key: 'delete', - props: { - danger: true - }, - icon: +const getFormattedData = (record: any, extraData = {}) => ({ + id: record.id, + data: { + ..._.omit(record, [ + 'id', + 'ready_replicas', + 'created_at', + 'updated_at', + 'rowIndex' + ]), + ...extraData } -]; - -const ButtonList = [ - { - label: 'common.button.start', - key: 'start', - icon: - }, - { - label: 'common.button.stop', - key: 'stop', - icon: - }, - { - label: 'common.button.delete', - key: 'delete', - icon: , - props: { - danger: true - } - } -]; - -const generateSource = (record: ListItem) => { - if (record.source === modelSourceMap.modelscope_value) { - return `${modelSourceMap.modelScope}/${record.model_scope_model_id}`; - } - if (record.source === modelSourceMap.huggingface_value) { - return `${modelSourceMap.huggingface}/${record.huggingface_repo_id}`; - } - if (record.source === modelSourceMap.local_path_value) { - return `${record.local_path}`; - } - if (record.source === modelSourceMap.ollama_library_value) { - return `${modelSourceMap.ollama_library}/${record.ollama_library_model_name}`; - } - return ''; -}; - -const setActionList = (record: ListItem) => { - return _.filter(ActionList, (action: any) => { - if (action.key === 'chat') { - return record.ready_replicas > 0; - } - if (action.key === 'start') { - return record.replicas === 0; - } - - if (action.key === 'stop') { - return record.replicas > 0; - } - - return true; - }); -}; +}); const Models: React.FC = ({ handleNameChange, @@ -218,7 +155,6 @@ const Models: React.FC = ({ const [isFirstLogin, setIsFirstLogin] = useState(false); const [isLoading, setIsLoading] = useState(false); const [expandAtom, setExpandAtom] = useAtom(modelsExpandKeysAtom); - const access = useAccess(); const intl = useIntl(); const navigate = useNavigate(); const rowSelection = useTableRowSelection(); @@ -270,69 +206,6 @@ const Models: React.FC = ({ getFirstLoginState(); }, [catalogList?.length]); - useHotkeys( - HotKeys.NEW1.join(','), - () => { - setOpenDeployModal({ - show: true, - width: 'calc(100vw - 220px)', - source: modelSourceMap.huggingface_value, - gpuOptions: gpuDeviceList.current - }); - }, - { - preventDefault: true, - enabled: !openAddModal && !openDeployModal.show && !openLogModal - } - ); - - useHotkeys( - HotKeys.NEW3.join(','), - () => { - setOpenDeployModal({ - show: true, - width: 'calc(100vw - 220px)', - source: modelSourceMap.modelscope_value, - gpuOptions: gpuDeviceList.current - }); - }, - { - preventDefault: true, - enabled: !openAddModal && !openDeployModal.show && !openLogModal - } - ); - - useHotkeys( - HotKeys.NEW2.join(','), - () => { - setOpenDeployModal({ - show: true, - width: 600, - source: modelSourceMap.ollama_library_value, - gpuOptions: gpuDeviceList.current - }); - }, - { - preventDefault: true, - enabled: !openAddModal && !openDeployModal.show && !openLogModal - } - ); - useHotkeys( - HotKeys.NEW4.join(','), - () => { - setOpenDeployModal({ - show: true, - width: 600, - source: modelSourceMap.local_path_value, - gpuOptions: gpuDeviceList.current - }); - }, - { - preventDefault: true, - enabled: !openAddModal && !openDeployModal.show && !openLogModal - } - ); - useEffect(() => { if (deleteIds?.length) { rowSelection.removeSelectedKey(deleteIds); @@ -346,39 +219,6 @@ const Models: React.FC = ({ }; }, []); - const sourceOptions = [ - { - label: intl.formatMessage({ id: 'menu.models.modelCatalog' }), - value: 'catalog', - key: 'catalog', - icon: - }, - { - label: 'Hugging Face', - value: modelSourceMap.huggingface_value, - key: 'huggingface', - icon: - }, - { - label: 'Ollama Library', - value: modelSourceMap.ollama_library_value, - key: 'ollama_library', - icon: - }, - { - label: 'ModelScope', - value: modelSourceMap.modelscope_value, - key: 'modelscope', - icon: - }, - { - label: intl.formatMessage({ id: 'models.form.localPath' }), - value: modelSourceMap.local_path_value, - key: 'local_path', - icon: - } - ]; - const setCurrentData = (data: ListItem) => { currentData.current = data; }; @@ -387,51 +227,21 @@ const Models: React.FC = ({ setSortOrder(order); }; - const handleOnCell = useCallback(async (record: any, dataIndex: string) => { - const params = { - id: record.id, - data: _.omit(record, [ - 'id', - 'ready_replicas', - 'created_at', - 'updated_at', - 'rowIndex' - ]) - }; - await updateModel(params); - message.success(intl.formatMessage({ id: 'common.message.success' })); + const handleOnCell = useCallback(async (record: any) => { + try { + await updateModel(getFormattedData(record)); + message.success(intl.formatMessage({ id: 'common.message.success' })); + } catch (error) { + // ignore + } }, []); const handleStartModel = async (row: ListItem) => { - await updateModel({ - id: row.id, - data: { - ..._.omit(row, [ - 'id', - 'ready_replicas', - 'created_at', - 'updated_at', - 'rowIndex' - ]), - replicas: 1 - } - }); + await updateModel(getFormattedData(row, { replicas: 1 })); }; const handleStopModel = async (row: ListItem) => { - await updateModel({ - id: row.id, - data: { - ..._.omit(row, [ - 'id', - 'ready_replicas', - 'created_at', - 'updated_at', - 'rowIndex' - ]), - replicas: 0 - } - }); + await updateModel(getFormattedData(row, { replicas: 0 })); removeExpandedRowKey([row.id]); }; @@ -538,25 +348,12 @@ const Models: React.FC = ({ }; const handleOpenPlayGround = (row: any) => { - if (row.categories?.includes(modelCategoriesMap.image)) { - navigate(`/playground/text-to-image?model=${row.name}`); - return; - } - if (row.categories?.includes(modelCategoriesMap.text_to_speech)) { - navigate(`/playground/speech?model=${row.name}&type=tts`); - return; - } - if (row.categories?.includes(modelCategoriesMap.speech_to_text)) { - navigate(`/playground/speech?model=${row.name}&type=stt`); - return; - } - if (row.categories?.includes(modelCategoriesMap.reranker)) { - navigate(`/playground/rerank?model=${row.name}`); - return; - } - if (row.categories?.includes(modelCategoriesMap.embedding)) { - navigate(`/playground/embedding?model=${row.name}`); - return; + for (const [category, path] of Object.entries(categoryToPathMap)) { + console.log('category:', category, path); + if (row.categories?.includes(category)) { + navigate(`${path}?model=${row.name}`); + return; + } } navigate(`/playground/chat?model=${row.name}`); }; @@ -694,43 +491,14 @@ const Models: React.FC = ({ ); const handleClickDropdown = (item: any) => { - if (item.key === 'huggingface') { - setOpenDeployModal({ - show: true, - width: 'calc(100vw - 220px)', - source: modelSourceMap.huggingface_value, - gpuOptions: gpuDeviceList.current - }); - } - - if (item.key === 'ollama_library') { - setOpenDeployModal({ - show: true, - width: 600, - source: modelSourceMap.ollama_library_value, - gpuOptions: gpuDeviceList.current - }); - } - - if (item.key === 'modelscope') { - setOpenDeployModal({ - show: true, - width: 'calc(100vw - 220px)', - source: modelSourceMap.modelscope_value, - gpuOptions: gpuDeviceList.current - }); - } - - if (item.key === 'local_path') { - setOpenDeployModal({ - show: true, - width: 600, - source: modelSourceMap.local_path_value, - gpuOptions: gpuDeviceList.current - }); - } if (item.key === 'catalog') { navigate('/models/catalog'); + return; + } + + const config = modalConfig[item.key]; + if (config) { + setOpenDeployModal({ ...config, gpuOptions: gpuDeviceList.current }); } }; @@ -847,7 +615,7 @@ const Models: React.FC = ({ span: 4, render: (text, record) => ( handleSelect(val, record)} /> ) @@ -966,7 +734,7 @@ const Models: React.FC = ({ } right={ - = ({ > {intl?.formatMessage?.({ id: 'models.button.deploy' })} - + = { + huggingface: { + show: true, + width: 'calc(100vw - 220px)', + source: modelSourceMap.huggingface_value + }, + ollama_library: { + show: true, + width: 600, + source: modelSourceMap.ollama_library_value + }, + modelscope: { + show: true, + width: 'calc(100vw - 220px)', + source: modelSourceMap.modelscope_value + }, + local_path: { + show: true, + width: 600, + source: modelSourceMap.local_path_value + } +}; + +interface ActionItem { + label: string; + key: string; + icon: React.ReactNode; + props?: { + danger?: boolean; + }; +} + +export const ActionList: ActionItem[] = [ + { + label: 'common.button.edit', + key: 'edit', + icon: icons.EditOutlined + }, + { + label: 'models.openinplayground', + key: 'chat', + icon: icons.ExperimentOutlined + }, + { + label: 'common.button.stop', + key: 'stop', + icon: icons.Stop + }, + { + label: 'common.button.start', + key: 'start', + icon: icons.Play + }, + { + label: 'common.button.delete', + key: 'delete', + props: { + danger: true + }, + icon: icons.DeleteOutlined + } +]; + +export const ButtonList = [ + { + label: 'common.button.start', + key: 'start', + icon: icons.Play + }, + { + label: 'common.button.stop', + key: 'stop', + icon: icons.Stop + }, + { + label: 'common.button.delete', + key: 'delete', + icon: icons.DeleteOutlined, + props: { + danger: true + } + } +]; + +export const onLineSourceOptions = [ + { + label: 'Hugging Face', + locale: false, + value: modelSourceMap.huggingface_value, + key: 'huggingface', + icon: icons.HF + }, + { + label: 'Ollama Library', + locale: false, + value: modelSourceMap.ollama_library_value, + key: 'ollama_library', + icon: icons.Ollama + }, + { + label: 'ModelScope', + locale: false, + value: modelSourceMap.modelscope_value, + key: 'modelscope', + icon: icons.ModelScope + } +]; + +export const sourceOptions = [ + { + label: 'menu.models.modelCatalog', + locale: true, + value: 'catalog', + key: 'catalog', + icon: icons.Catalog + }, + ...onLineSourceOptions, + { + label: 'models.form.localPath', + locale: true, + value: modelSourceMap.local_path_value, + key: 'local_path', + icon: icons.LocalPath + } +]; + +export const generateSource = (record: any) => { + if (record.source === modelSourceMap.modelscope_value) { + return `${modelSourceMap.modelScope}/${record.model_scope_model_id}`; + } + if (record.source === modelSourceMap.huggingface_value) { + return `${modelSourceMap.huggingface}/${record.huggingface_repo_id}`; + } + if (record.source === modelSourceMap.local_path_value) { + return `${record.local_path}`; + } + if (record.source === modelSourceMap.ollama_library_value) { + return `${modelSourceMap.ollama_library}/${record.ollama_library_model_name}`; + } + return ''; +}; + +export const setModelActionList = (record: any) => { + return _.filter(ActionList, (action: any) => { + if (action.key === 'chat') { + return record.ready_replicas > 0; + } + if (action.key === 'start') { + return record.replicas === 0; + } + + if (action.key === 'stop') { + return record.replicas > 0; + } + + return true; + }); +}; + +export const categoryToPathMap: Record = { + [modelCategoriesMap.llm]: '/playground/chat', + [modelCategoriesMap.image]: '/playground/text-to-image', + [modelCategoriesMap.text_to_speech]: '/playground/speech?type=tts', + [modelCategoriesMap.speech_to_text]: '/playground/speech?type=stt', + [modelCategoriesMap.reranker]: '/playground/rerank', + [modelCategoriesMap.embedding]: '/playground/embedding' +}; + +export const hotkeyConfigs = [ + { + keys: HotKeys.NEW1, + width: 'calc(100vw - 220px)', + source: modelSourceMap.huggingface_value + }, + { + keys: HotKeys.NEW3, + width: 'calc(100vw - 220px)', + source: modelSourceMap.modelscope_value + }, + { + keys: HotKeys.NEW2, + width: 600, + source: modelSourceMap.ollama_library_value + }, + { keys: HotKeys.NEW4, width: 600, source: modelSourceMap.local_path_value } +]; + +/** + * hotkeyConfigs.map(({ keys, width, source }) => + useHotkeys( + keys.join(','), + () => { + setOpenDeployModal({ + show: true, + width, + source, + gpuOptions: gpuDeviceList.current + }); + }, + { + preventDefault: true, + enabled: !openAddModal && !openDeployModal.show && !openLogModal + } + ) + ); + * + */ diff --git a/src/pages/llmodels/download/index.tsx b/src/pages/llmodels/download/index.tsx new file mode 100644 index 00000000..06e265ed --- /dev/null +++ b/src/pages/llmodels/download/index.tsx @@ -0,0 +1,202 @@ +import ModalFooter from '@/components/modal-footer'; +import { CloseOutlined } from '@ant-design/icons'; +import { useIntl } from '@umijs/max'; +import { Button, Drawer } from 'antd'; +import { debounce } from 'lodash'; +import React, { useCallback, useEffect, useRef, useState } from 'react'; +import ColumnWrapper from '../components/column-wrapper'; +import HFModelFile from '../components/hf-model-file'; +import ModelCard from '../components/model-card'; +import SearchModel from '../components/search-model'; +import Separator from '../components/separator'; +import TitleWrapper from '../components/title-wrapper'; +import { modelSourceMap } from '../config'; +import { FormData } from '../config/types'; +import TargetForm from './target-form'; + +type AddModalProps = { + title: string; + open: boolean; + source: string; + width?: string | number; + onOk: (values: FormData) => void; + onCancel: () => void; +}; + +const DownloadModel: React.FC = (props) => { + const { title, open, onOk, onCancel, source, width = 600 } = props || {}; + const SEARCH_SOURCE = [ + modelSourceMap.huggingface_value, + modelSourceMap.modelscope_value + ]; + + const form = useRef({}); + const intl = useIntl(); + const [selectedModel, setSelectedModel] = useState({}); + const [collapsed, setCollapsed] = useState(false); + const [isGGUF, setIsGGUF] = useState(false); + const modelFileRef = useRef(null); + + const handleSelectModelFile = useCallback((item: any) => { + form.current?.setFieldValue?.('file_name', item.fakeName); + }, []); + + const handleOnSelectModel = (item: any) => { + setSelectedModel(item); + }; + + const handleSumit = () => { + form.current?.submit?.(); + }; + + const debounceFetchModelFiles = debounce(() => { + modelFileRef.current?.fetchModelFiles?.(); + }, 300); + + const handleSetIsGGUF = (flag: boolean) => { + setIsGGUF(flag); + if (flag) { + debounceFetchModelFiles(); + } + }; + + const handleCancel = useCallback(() => { + onCancel?.(); + }, [onCancel]); + + useEffect(() => { + handleSelectModelFile({ fakeName: '' }); + }, [selectedModel]); + + useEffect(() => { + if (!open) { + setIsGGUF(false); + } else if (source === modelSourceMap.ollama_library_value) { + setIsGGUF(true); + } + + return () => { + setSelectedModel({}); + }; + }, [open, source]); + + return ( + + + Download Model + + +
+ } + open={open} + onClose={handleCancel} + destroyOnClose={true} + closeIcon={false} + maskClosable={false} + keyboard={false} + zIndex={2000} + styles={{ + body: { + height: 'calc(100vh - 57px)', + padding: '16px 0', + overflowX: 'hidden' + }, + content: { + borderRadius: '6px 0 0 6px' + } + }} + width={width} + footer={false} + > +
+ {SEARCH_SOURCE.includes(props.source) && ( + <> +
+ + + + +
+
+ + + {isGGUF && ( + + )} + + +
+ + )} +
+ + + + } + > + <> + {SEARCH_SOURCE.includes(source) && ( + + Select Target + + + )} + + + +
+
+ + ); +}; + +export default DownloadModel; diff --git a/src/pages/llmodels/download/target-form.tsx b/src/pages/llmodels/download/target-form.tsx new file mode 100644 index 00000000..85b5be93 --- /dev/null +++ b/src/pages/llmodels/download/target-form.tsx @@ -0,0 +1,122 @@ +import IconFont from '@/components/icon-font'; +import SealAutoComplete from '@/components/seal-form/auto-complete'; +import SealInput from '@/components/seal-form/seal-input'; +import SealSelect from '@/components/seal-form/seal-select'; +import { ModelFile as FormData } from '@/pages/resources/config/types'; +import { useIntl } from '@umijs/max'; +import { Form, Typography } from 'antd'; +import React from 'react'; +import { modelSourceMap, ollamaModelOptions } from '../config'; + +interface TargetFormProps { + source: string; + onOk: (values: any) => void; +} + +const TargetForm: React.FC = (props) => { + const { onOk, source } = props; + const intl = useIntl(); + const [form] = Form.useForm(); + + const handleOk = (values: any) => { + onOk(values); + }; + + const renderOllamaModelFields = () => { + return ( + <> + + name="ollama_library_model_name" + key="ollama_library_model_name" + rules={[ + { + required: true, + message: intl.formatMessage( + { + id: 'common.form.rule.input' + }, + { name: intl.formatMessage({ id: 'models.table.name' }) } + ) + } + ]} + > + + + {intl.formatMessage({ id: 'models.form.ollamalink' })} + + + + + + } + label={intl.formatMessage({ id: 'model.form.ollama.model' })} + placeholder={intl.formatMessage({ id: 'model.form.ollamaholder' })} + required + > + + + ); + }; + + return ( +
+ {source === modelSourceMap.ollama_library_value && + renderOllamaModelFields()} + + {} + + + + +
+ ); +}; + +export default TargetForm; diff --git a/src/pages/resources/components/model-files.tsx b/src/pages/resources/components/model-files.tsx new file mode 100644 index 00000000..c62ad0bd --- /dev/null +++ b/src/pages/resources/components/model-files.tsx @@ -0,0 +1,305 @@ +import AutoTooltip from '@/components/auto-tooltip'; +import DeleteModal from '@/components/delete-modal'; +import DropDownActions from '@/components/drop-down-actions'; +import DropdownButtons from '@/components/drop-down-buttons'; +import PageTools from '@/components/page-tools'; +import StatusTag from '@/components/status-tag'; +import useTableFetch from '@/hooks/use-table-fetch'; +import { modelSourceMap } from '@/pages/llmodels/config'; +import { + modalConfig, + onLineSourceOptions +} from '@/pages/llmodels/config/button-actions'; +import DownloadModal from '@/pages/llmodels/download'; +import { + DeleteOutlined, + DownOutlined, + SyncOutlined, + ThunderboltOutlined +} from '@ant-design/icons'; +import { useIntl } from '@umijs/max'; +import React, { + Button, + ConfigProvider, + Empty, + Input, + Space, + Table +} from 'antd'; +import dayjs from 'dayjs'; +import { useState } from 'react'; +import { deleteWorker, queryWorkersList } from '../apis'; +import { WorkerStatusMapValue, status } from '../config'; +import { ModelFile as ListItem } from '../config/types'; + +const ActionList = [ + { + label: 'common.button.deploy', + key: 'deploy', + icon: + }, + { + label: 'common.button.delete', + key: 'delete', + props: { + danger: true + }, + icon: + } +]; + +const generateSource = (record: ListItem) => { + if (record.source === modelSourceMap.modelscope_value) { + return `${modelSourceMap.modelScope}/${record.model_scope_model_id}`; + } + if (record.source === modelSourceMap.huggingface_value) { + return `${modelSourceMap.huggingface}/${record.huggingface_repo_id}`; + } + if (record.source === modelSourceMap.local_path_value) { + return `${record.local_path}`; + } + if (record.source === modelSourceMap.ollama_library_value) { + return `${modelSourceMap.ollama_library}/${record.ollama_library_model_name}`; + } + return ''; +}; + +const ModelFiles: React.FC = () => { + const { + dataSource, + rowSelection, + queryParams, + modalRef, + handleDelete, + handleDeleteBatch, + handlePageChange, + handleTableChange, + handleSearch, + handleNameChange + } = useTableFetch({ + fetchAPI: queryWorkersList, + deleteAPI: deleteWorker, + contentForDelete: 'worker' + }); + + const intl = useIntl(); + const [downloadModalStatus, setDownlaodMoalStatus] = useState<{ + show: boolean; + width: number | string; + source: string; + gpuOptions: any[]; + }>({ + show: false, + width: 600, + source: modelSourceMap.huggingface_value, + gpuOptions: [] + }); + + const handleSelect = (val: any, record: ListItem) => { + if (val === 'delete') { + handleDelete({ + ...record, + name: record.local_path + }); + } + }; + + const renderEmpty = (type?: string) => { + if (type !== 'Table') return; + if ( + !dataSource.loading && + dataSource.loadend && + !dataSource.dataList.length + ) { + return ; + } + return
; + }; + + const handleClickDropdown = (item: any) => { + const config = modalConfig[item.key]; + if (config) { + setDownlaodMoalStatus({ ...config, gpuOptions: [] }); + } + }; + + const handleDownloadCancel = () => { + setDownlaodMoalStatus({ + ...downloadModalStatus, + show: false + }); + }; + + const handleDownload = (data: any) => { + console.log('download:', data); + }; + + const columns = [ + { + title: 'Path', + dataIndex: 'local_path', + width: 240, + render: (text: string, record: ListItem) => { + return ( + + {record.local_path} + + ); + } + }, + { + title: 'Size', + dataIndex: 'size', + render: (text: string, record: ListItem) => { + return ( + + {record.size} + + ); + } + }, + { + title: 'Worker', + dataIndex: 'worker_name', + render: (text: string, record: ListItem) => { + return ( + + {record.worker_name} + + ); + } + }, + { + title: intl.formatMessage({ id: 'models.form.source' }), + dataIndex: 'source', + render: (text: string, record: ListItem) => ( + + {generateSource(record)} + + ) + }, + { + title: intl.formatMessage({ id: 'common.table.status' }), + dataIndex: 'state', + render: (text: string, record: ListItem) => { + return ( + + ); + } + }, + { + title: intl.formatMessage({ id: 'common.table.createTime' }), + dataIndex: 'created_at', + sorter: false, + render: (text: number) => ( + + {dayjs(text).format('YYYY-MM-DD HH:mm:ss')} + + ) + }, + { + title: intl.formatMessage({ id: 'common.table.operation' }), + dataIndex: 'operation', + render: (text: string, record: ListItem) => ( + handleSelect(val, record)} + > + ) + } + ]; + + return ( + <> + + + + + } + right={ + + + + + + + } + > + +
+
+ + + + ); +}; + +export default ModelFiles; diff --git a/src/pages/resources/config/types.ts b/src/pages/resources/config/types.ts index b9ca50ce..fa0942c9 100644 --- a/src/pages/resources/config/types.ts +++ b/src/pages/resources/config/types.ts @@ -96,3 +96,20 @@ export interface ListItem { created_at: string; updated_at: string; } + +export interface ModelFile { + source: string; + size: number; + id: number; + created_at: string; + worker_name: string; + huggingface_repo_id: string; + huggingface_filename: string; + ollama_library_model_name: string; + model_scope_model_id: string; + model_scope_file_path: string; + local_path: string; + download_progress: number; + state: string; + state_message: string; +} diff --git a/src/pages/resources/index.tsx b/src/pages/resources/index.tsx index 7cf34179..b92f56ad 100644 --- a/src/pages/resources/index.tsx +++ b/src/pages/resources/index.tsx @@ -23,6 +23,11 @@ const items: TabsProps['items'] = [ label: 'GPUs', children: } + // { + // key: 'model-files', + // label: 'Model Files', + // children: + // } ]; const Resources = () => { const [activeKey, setActiveKey] = useState('workers');