From b25644c590438264391917632adda3a670c76337 Mon Sep 17 00:00:00 2001 From: jialin Date: Tue, 25 Mar 2025 12:32:46 +0800 Subject: [PATCH] chore: worker downloading tooltip --- src/components/simple-table/index.less | 14 +- src/components/simple-table/index.tsx | 12 +- src/components/simple-table/row.tsx | 1 + src/global.less | 6 + src/hooks/use-table-fetch.ts | 3 +- src/locales/en-US/models.ts | 3 +- src/locales/en-US/resources.ts | 4 +- src/locales/ru-RU/models.ts | 4 +- src/locales/ru-RU/resources.ts | 6 +- src/locales/zh-CN/models.ts | 3 +- src/locales/zh-CN/resources.ts | 4 +- .../llmodels/components/instance-item.tsx | 182 ++++++++++++++---- src/pages/llmodels/components/instances.tsx | 58 ++++++ .../resources/components/model-files.tsx | 76 +++++--- 14 files changed, 299 insertions(+), 77 deletions(-) diff --git a/src/components/simple-table/index.less b/src/components/simple-table/index.less index c3d3d82d..50d34d3e 100644 --- a/src/components/simple-table/index.less +++ b/src/components/simple-table/index.less @@ -31,11 +31,23 @@ .cell-span { display: flex; - padding: 4px 6px; + padding: 6px; min-height: 32px; } .cell-header { font-weight: var(--font-weight-bold); } + + &.light { + th { + color: var(--ant-color-text); + border-bottom: 1px solid var(--ant-color-split); + } + + td { + color: var(--ant-color-text); + border-bottom: 1px solid var(--ant-color-split); + } + } } diff --git a/src/components/simple-table/index.tsx b/src/components/simple-table/index.tsx index 052c4875..264ba1d6 100644 --- a/src/components/simple-table/index.tsx +++ b/src/components/simple-table/index.tsx @@ -9,6 +9,7 @@ import TableRow from './row'; export interface ColumnProps { title: string; key: string; + width?: string | number; render?: (data: { dataIndex: string; dataList?: any[]; @@ -34,13 +35,20 @@ export interface ColumnProps { } interface SimpleTableProps { + theme?: 'dark' | 'light'; columns: ColumnProps[]; dataSource: any[]; bordered?: boolean; rowKey?: string; } const SimpleTabel: React.FC = (props) => { - const { columns, dataSource, rowKey, bordered = true } = props; + const { + columns, + dataSource, + rowKey, + bordered = true, + theme = 'dark' + } = props; const scroller = React.useRef(null); const { initialize } = useOverlayScroller(); @@ -50,7 +58,7 @@ const SimpleTabel: React.FC = (props) => { return (
diff --git a/src/components/simple-table/row.tsx b/src/components/simple-table/row.tsx index cf59d2b6..957010db 100644 --- a/src/components/simple-table/row.tsx +++ b/src/components/simple-table/row.tsx @@ -35,6 +35,7 @@ const TableCell: React.FC = (props: TableCellProps) => { return ( + diff --git a/src/pages/llmodels/components/instances.tsx b/src/pages/llmodels/components/instances.tsx index bc7e76ab..c1bd33ae 100644 --- a/src/pages/llmodels/components/instances.tsx +++ b/src/pages/llmodels/components/instances.tsx @@ -6,6 +6,64 @@ import { ModelInstanceListItem } from '../config/types'; import '../style/instance-item.less'; import InstanceItem from './instance-item'; +const testInstanceData = { + source: 'huggingface', + huggingface_repo_id: 'Qwen/Qwen2.5-3B-Instruct', + huggingface_filename: null, + ollama_library_model_name: null, + model_scope_model_id: null, + model_scope_file_path: null, + local_path: null, + name: 'qwen2.5-XVCXa', + worker_id: 1, + worker_name: 'sealgpuhost4080', + worker_ip: '192.168.50.12', + pid: 208852, + port: 40063, + download_progress: 72.11, + resolved_path: null, + state: 'downloading', + state_message: '', + computed_resource_claim: { + is_unified_memory: false, + offload_layers: null, + total_layers: null, + ram: null, + vram: { + '0': 1717148058 + }, + tensor_split: null + }, + gpu_indexes: [0], + model_id: 1, + model_name: 'qwen2.5', + distributed_servers: { + rpc_servers: null, + ray_actors: [ + { + worker_id: 2, + worker_ip: '192.168.50.13', + total_gpus: 1, + gpu_indexes: [0], + computed_resource_claim: { + is_unified_memory: false, + offload_layers: null, + total_layers: null, + ram: null, + vram: { + '0': 23181498777 + }, + tensor_split: null + }, + download_progress: 27.49 + } + ] + }, + id: 1, + created_at: '2025-03-24T12:06:30Z', + updated_at: '2025-03-24T12:09:01Z' +}; + interface InstanceItemProps { list: ModelInstanceListItem[]; workerList: WorkerListItem[]; diff --git a/src/pages/resources/components/model-files.tsx b/src/pages/resources/components/model-files.tsx index 87c73500..c5032aec 100644 --- a/src/pages/resources/components/model-files.tsx +++ b/src/pages/resources/components/model-files.tsx @@ -12,7 +12,6 @@ import useBodyScroll from '@/hooks/use-body-scroll'; import useTableFetch from '@/hooks/use-table-fetch'; import { createModel } from '@/pages/llmodels/apis'; import DeployModal from '@/pages/llmodels/components/deploy-modal'; -import FileParts from '@/pages/llmodels/components/file-parts'; import { backendOptionsMap, getSourceRepoConfigValue, @@ -27,22 +26,17 @@ import { } from '@/pages/llmodels/config/button-actions'; import DownloadModal from '@/pages/llmodels/download'; import { convertFileSize } from '@/utils'; -import { - DeleteOutlined, - DownOutlined, - InfoCircleOutlined, - SyncOutlined -} from '@ant-design/icons'; +import { DeleteOutlined, DownOutlined, SyncOutlined } from '@ant-design/icons'; import { useIntl, useNavigate } from '@umijs/max'; import { Button, ConfigProvider, Empty, Input, + Select, Space, Table, Tag, - Tooltip, message } from 'antd'; import dayjs from 'dayjs'; @@ -112,7 +106,7 @@ const InstanceStatusTag = (props: { data: ListItem }) => { }; const ModelFiles = () => { - const { getGPUList, generateFormValues } = useGenerateFormEditInitialValues(); + const { getGPUList } = useGenerateFormEditInitialValues(); const { saveScrollHeight, restoreScrollHeight } = useBodyScroll(); const [modelsExpandKeys, setModelsExpandKeys] = useAtom(modelsExpandKeysAtom); const navigate = useNavigate(); @@ -127,7 +121,8 @@ const ModelFiles = () => { handlePageChange, handleTableChange, handleSearch, - handleNameChange + handleNameChange, + setQueryParams } = useTableFetch({ fetchAPI: queryModelFilesList, deleteAPI: deleteModelFile, @@ -202,6 +197,20 @@ const ModelFiles = () => { return name.replace(filterPattern, '$1'); }; + const handleWorkerChange = (value: number) => { + setQueryParams({ + ...queryParams, + page: 1, + worker_id: value + }); + fetchData({ + query: { + ...queryParams, + page: 1, + worker_id: value + } + }); + }; const generateInitialValues = (record: ListItem) => { const isGGUF = _.includes(record.resolved_paths?.[0], 'gguf'); const isOllama = !!record.ollama_library_model_name; @@ -251,28 +260,20 @@ const ModelFiles = () => { }; }); return ( - } > - - - - {partsList.length} parts - - - + + {record.resolved_paths?.length}{' '} + {intl.formatMessage({ id: 'models.form.files' })} + + ); }; @@ -492,12 +493,23 @@ const ModelFiles = () => { +
(options: { loadend: false, total: 0 }); - const [queryParams, setQueryParams] = useState({ + const [queryParams, setQueryParams] = useState({ page: 1, perPage: 10, search: '' @@ -191,6 +191,7 @@ export default function useTableFetch(options: { sortOrder, queryParams, modalRef, + setQueryParams, handleDelete, handleDeleteBatch, fetchData, diff --git a/src/locales/en-US/models.ts b/src/locales/en-US/models.ts index 7c9d301a..0cdfa80a 100644 --- a/src/locales/en-US/models.ts +++ b/src/locales/en-US/models.ts @@ -119,5 +119,6 @@ export default { 'models.form.backend.warning.llamabox': 'To use the llama-box backend, specify the full path to the model file (e.g.,/data/models/model.gguf). For sharded models, provide the path to the first shard (e.g.,/data/models/model-00001-of-00004.gguf).', 'models.form.keyvalue.paste': - 'Paste multiple lines of text, with each line containing a key-value pair. The key and value are separated by an = sign, and different key-value pairs are separated by newline characters.' + 'Paste multiple lines of text, with each line containing a key-value pair. The key and value are separated by an = sign, and different key-value pairs are separated by newline characters.', + 'models.form.files': 'files' }; diff --git a/src/locales/en-US/resources.ts b/src/locales/en-US/resources.ts index d40fc446..b84e3411 100644 --- a/src/locales/en-US/resources.ts +++ b/src/locales/en-US/resources.ts @@ -66,5 +66,7 @@ export default { 'The default storage directory is /var/lib/gpustack/cache.', 'resources.modelfiles.retry.download': 'Retry Download', 'resources.modelfiles.storagePath.holder': - 'Waiting for download to complete...' + 'Waiting for download to complete...', + 'resources.filter.worker': 'Filter by Worker', + 'resources.filter.file': 'Filter by File' }; diff --git a/src/locales/ru-RU/models.ts b/src/locales/ru-RU/models.ts index b39b798e..a463c367 100644 --- a/src/locales/ru-RU/models.ts +++ b/src/locales/ru-RU/models.ts @@ -119,9 +119,11 @@ export default { 'models.form.backend.warning.llamabox': 'Чтобы использовать бэкенд llama-box , укажите полный путь к файлу модели (например,/data/models/model.gguf). Для шардированных моделей укажите путь к первому шарду (например,/data/models/model-00001-of-00004.gguf).', 'models.form.keyvalue.paste': - 'Paste multiple lines of text, with each line containing a key-value pair. The key and value are separated by an = sign, and different key-value pairs are separated by newline characters.' + 'Paste multiple lines of text, with each line containing a key-value pair. The key and value are separated by an = sign, and different key-value pairs are separated by newline characters.', + 'models.form.files': 'files' }; // ========== To-Do: Translate Keys (Remove After Translation) ========== //1. 'models.form.keyvalue.paste' +//2. 'models.form.files' // ========== End of To-Do List ========== diff --git a/src/locales/ru-RU/resources.ts b/src/locales/ru-RU/resources.ts index 56c7743e..ab907e84 100644 --- a/src/locales/ru-RU/resources.ts +++ b/src/locales/ru-RU/resources.ts @@ -66,7 +66,9 @@ export default { 'The default storage directory is /var/lib/gpustack/cache.', 'resources.modelfiles.retry.download': 'Retry Download', 'resources.modelfiles.storagePath.holder': - 'Waiting for download to complete...' + 'Waiting for download to complete...', + 'resources.filter.worker': 'Filter by Worker', + 'resources.filter.file': 'Filter by File' }; // ========== To-Do: Translate Keys (Remove After Translation) ========== @@ -79,4 +81,6 @@ export default { //7. 'resources.modelfiles.retry.download', //8. 'resources.modelfiles.form.localdir.tips', //9. 'resources.modelfiles.storagePath.holder', +//10. 'resources.filter.worker', +//11. 'resources.filter.file', // ========== End of To-Do List ========== diff --git a/src/locales/zh-CN/models.ts b/src/locales/zh-CN/models.ts index e26562e4..f2cf0720 100644 --- a/src/locales/zh-CN/models.ts +++ b/src/locales/zh-CN/models.ts @@ -112,5 +112,6 @@ export default { 'models.form.backend.warning.llamabox': '要使用 llama-box 后端,请指定模型文件的完整路径(例如:/data/models/model.gguf)。对于分片模型,请提供第一个分片的路径(例如:/data/models/model-00001-of-00004.gguf)。', 'models.form.keyvalue.paste': - '粘贴多行文本,每行包含一个键值对,键和值之间用 = 号分隔,不同的键值对之间用换行符分隔。' + '粘贴多行文本,每行包含一个键值对,键和值之间用 = 号分隔,不同的键值对之间用换行符分隔。', + 'models.form.files': '文件' }; diff --git a/src/locales/zh-CN/resources.ts b/src/locales/zh-CN/resources.ts index a7253347..d4106b28 100644 --- a/src/locales/zh-CN/resources.ts +++ b/src/locales/zh-CN/resources.ts @@ -64,5 +64,7 @@ export default { 'resources.modelfiles.form.localdir.tips': '默认存储目录为 /var/lib/gpustack/cache', 'resources.modelfiles.retry.download': '重新下载', - 'resources.modelfiles.storagePath.holder': '等待下载完成...' + 'resources.modelfiles.storagePath.holder': '等待下载完成...', + 'resources.filter.worker': '按 worker 筛选', + 'resources.filter.file': '按文件筛选' }; diff --git a/src/pages/llmodels/components/instance-item.tsx b/src/pages/llmodels/components/instance-item.tsx index c338e15f..4bfde448 100644 --- a/src/pages/llmodels/components/instance-item.tsx +++ b/src/pages/llmodels/components/instance-item.tsx @@ -39,6 +39,34 @@ const fieldList = [ } ]; +const downloadList: ColumnProps[] = [ + { + title: 'Worker', + key: 'worker_name', + width: 200 + }, + { + title: 'Status', + key: 'download_progress', + render: ({ row }) => { + return ( + + ); + } + } +]; + const WorkerInfo = (props: { title: React.ReactNode; defaultOpen: boolean; @@ -70,6 +98,82 @@ const WorkerInfo = (props: { ); }; +const RenderRayactorDownloading = (props: { + severList: any[]; + instanceData: any; + workerList: WorkerListItem[]; +}) => { + const { severList, instanceData, workerList } = props; + if (!severList.length) { + return null; + } + const list = _.map(severList, (item: any) => { + const data = _.find(workerList, { id: item.worker_id }); + return { + worker_name: data?.name, + worker_ip: data?.ip, + download_progress: _.round(item.download_progress, 2) + }; + }); + + const mainWorker = [ + { + worker_name: `${instanceData.worker_name}`, + worker_ip: `${instanceData.worker_ip}`, + download_progress: _.round(instanceData.download_progress, 2) + } + ]; + + return ( +
+ +
+ ); +}; + +const RenderWorkerDownloading = (props: { + rayActors: any[]; + workerList: WorkerListItem[]; + instanceData: ModelInstanceListItem; +}) => { + const { rayActors, workerList, instanceData } = props; + if (instanceData.state === InstanceStatusMap.Error || !rayActors.length) { + return null; + } + return ( + + } + > + item.download_progress < 100) + ?.download_progress || 100 + } + /> + + ); +}; + const InstanceStatusTag = ( props: Pick ) => { @@ -79,39 +183,41 @@ const InstanceStatusTag = ( return null; } return ( - handleChildSelect('viewlog', instanceData)} - > - {intl.formatMessage({ id: 'models.list.more.logs' })} - - ) : null - } - statusValue={{ - status: - instanceData.state === InstanceStatusMap.Downloading && - instanceData.download_progress === 100 - ? status[InstanceStatusMap.Running] - : status[instanceData.state], - text: InstanceStatusMapValue[instanceData.state], - message: - instanceData.state === InstanceStatusMap.Downloading && - instanceData.download_progress === 100 - ? '' - : instanceData.state_message - }} - /> + <> + handleChildSelect('viewlog', instanceData)} + > + {intl.formatMessage({ id: 'models.list.more.logs' })} + + ) : null + } + statusValue={{ + status: + instanceData.state === InstanceStatusMap.Downloading && + instanceData.download_progress === 100 + ? status[InstanceStatusMap.Running] + : status[instanceData.state], + text: InstanceStatusMapValue[instanceData.state], + message: + instanceData.state === InstanceStatusMap.Downloading && + instanceData.download_progress === 100 + ? '' + : instanceData.state_message + }} + /> + ); }; @@ -341,6 +447,7 @@ const InstanceItem: React.FC = ({ = ({