import AutoTooltip from '@/components/auto-tooltip'; import DropdownButtons from '@/components/drop-down-buttons'; import IconFont from '@/components/icon-font'; import RowChildren from '@/components/seal-table/components/row-children'; import SimpleTabel, { ColumnProps } from '@/components/simple-table'; import InfoColumn from '@/components/simple-table/info-column'; import StatusTag from '@/components/status-tag'; import { HandlerOptions } from '@/hooks/use-chunk-fetch'; import useDownloadStream from '@/hooks/use-download-stream'; import { ListItem as WorkerListItem } from '@/pages/resources/config/types'; import { convertFileSize } from '@/utils'; import { DeleteOutlined, DownloadOutlined, HddFilled, InfoCircleOutlined, ThunderboltFilled } from '@ant-design/icons'; import { useIntl } from '@umijs/max'; import { Button, Col, Progress, Row, Tag, Tooltip, notification } from 'antd'; import dayjs from 'dayjs'; import _ from 'lodash'; import React, { useCallback, useEffect, useMemo } from 'react'; import styled from 'styled-components'; import { MODEL_INSTANCE_API } from '../apis'; import { InstanceStatusMap, InstanceStatusMapValue, status } from '../config'; import { ModelInstanceListItem } from '../config/types'; import '../style/instance-item.less'; const fieldList = [ { label: 'CPU', key: 'cpuoffload', locale: false }, { label: 'GPU', key: 'gpuoffload', locale: false } ]; const downloadList: ColumnProps[] = [ { title: 'Worker', key: 'worker_name', width: 200 }, { title: 'models.table.download.progress', locale: true, key: 'download_progress', render: ({ row }) => { return ( ); } } ]; const WorkerInfo = (props: { title: React.ReactNode; defaultOpen: boolean; }) => { const [open, setOpen] = React.useState(props.defaultOpen); useEffect(() => { if (props.defaultOpen) { setTimeout(() => { setOpen(false); }, 1000); } }, [props.defaultOpen]); return ( ); }; const GPUIndexWrapper = styled.span` display: flex; flex-direction: column; gap: 2px; `; 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.Downloading || !rayActors.length ) { return null; } return ( } > item.download_progress < 100) ?.download_progress || 0 } /> ); }; const InstanceStatusTag = ( props: Pick ) => { const intl = useIntl(); const { instanceData, handleChildSelect } = props; if (!instanceData.state) { 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 }} /> ); }; interface InstanceItemProps { instanceData: ModelInstanceListItem; workerList: WorkerListItem[]; modelData?: any; defaultOpenId: string; handleChildSelect: (val: string, item: ModelInstanceListItem) => void; } const childActionList = [ { label: 'common.button.viewlog', key: 'viewlog', status: [ InstanceStatusMap.Initializing, InstanceStatusMap.Running, InstanceStatusMap.Error, InstanceStatusMap.Starting, InstanceStatusMap.Downloading ], icon: }, { label: 'common.button.downloadLog', key: 'download', status: [ InstanceStatusMap.Initializing, InstanceStatusMap.Running, InstanceStatusMap.Error, InstanceStatusMap.Starting, InstanceStatusMap.Downloading ], icon: }, { label: 'common.button.delrecreate', key: 'delete', props: { danger: true }, icon: } ]; const renderGpuIndexs = (gpuIndexes: number[]) => { return ( {_.chunk(gpuIndexes, 8).map((item: number[], index: number) => { return {item.join(',')}; })} ); }; const distributeCols: ColumnProps[] = [ { title: 'Worker', key: 'worker_name' }, { title: 'IP', key: 'worker_ip', render: ({ row }) => { return row.port ? `${row.worker_ip}:${row.port}` : row.worker_ip; } }, { title: 'models.table.gpuindex', locale: true, key: 'gpu_index', render: ({ row }) => { return row.is_main ? ( <> {renderGpuIndexs(row.gpu_index)} (main) ) : ( renderGpuIndexs(row.gpu_index) ); } }, { title: 'models.table.vram.allocated', locale: true, key: 'vram', render: ({ rowIndex, row, dataList }) => { return convertFileSize(row.vram, 1); } } ]; const renderMessage = (title: string) => { return (
{title}
); }; const InstanceItem: React.FC = ({ instanceData, workerList, modelData, defaultOpenId, handleChildSelect }) => { const [api, contextHolder] = notification.useNotification({ stack: { threshold: 1 } }); const { downloadStream } = useDownloadStream(); const intl = useIntl(); const actionItems = useMemo(() => { return _.filter(childActionList, (action: any) => { if (action.key === 'viewlog' || action.key === 'download') { return action.status.includes(instanceData.state); } return true; }); }, [instanceData.state]); const createFileName = (name: string) => { const timestamp = dayjs().format('YYYY-MM-DD_HH-mm-ss'); const fileName = `${name}_${timestamp}.txt`; return fileName; }; const downloadNotification = useCallback( ( data: HandlerOptions & { filename: string; duration?: number; chunkRequestRef: any; } ) => { api.open({ duration: data.duration, message: renderMessage(data.filename), key: data.filename, closeIcon: ( {intl.formatMessage({ id: 'common.button.cancel' })} ), description: , onClose() { data.chunkRequestRef?.current?.abort(); notification.destroy?.(data.filename); } }); }, [] ); const renderWorkerInfo = useMemo(() => { let workerIp = '-'; if (instanceData.worker_ip) { workerIp = instanceData.port ? `${instanceData.worker_ip}:${instanceData.port}` : instanceData.worker_ip; } return (
{instanceData.worker_name}
{workerIp}
{intl.formatMessage({ id: 'models.table.gpuindex' })}: [ {_.join(instanceData.gpu_indexes?.sort?.(), ',')}]
{intl.formatMessage({ id: 'models.form.backend' })}:{' '} {modelData?.backend || ''} {modelData.backend_version ? `(${modelData.backend_version})` : ''}
); }, [ instanceData.worker_name, instanceData.worker_ip, instanceData.port, instanceData.gpu_indexes, modelData?.backend, modelData?.backend_version, intl ]); const calcTotalVram = (vram: Record) => { return _.sum(_.values(vram)); }; const renderDistributedServer = (severList: any[]) => { const list = _.map(severList, (item: any) => { const data = _.find(workerList, { id: item.worker_id }); return { worker_name: data?.name, worker_ip: data?.ip, port: '', is_main: false, vram: calcTotalVram(item.computed_resource_claim?.vram || {}), gpu_index: _.keys(item.computed_resource_claim?.vram) }; }); const mainWorker = [ { worker_name: `${instanceData.worker_name}`, worker_ip: `${instanceData.worker_ip}`, port: '', vram: calcTotalVram(instanceData.computed_resource_claim?.vram || {}), is_main: true, gpu_index: instanceData.gpu_indexes } ]; return (
); }; const renderDistributionInfo = (severList: any[]) => { if (!severList.length) { return null; } return ( {intl.formatMessage({ id: 'models.table.acrossworker' })} ); }; const renderOffloadInfo = useMemo(() => { const total_layers = instanceData.computed_resource_claim?.total_layers; const offload_layers = instanceData.computed_resource_claim?.offload_layers; if (total_layers === offload_layers || !total_layers) { return null; } const offloadData = { cpuoffload: `${ _.subtract( instanceData.computed_resource_claim?.total_layers, instanceData.computed_resource_claim?.offload_layers ) || 0 } ${intl.formatMessage({ id: 'models.table.layers' })}`, gpuoffload: `${instanceData.computed_resource_claim?.offload_layers} ${intl.formatMessage( { id: 'models.table.layers' } )}` }; return ( } > {intl.formatMessage({ id: 'models.table.cpuoffload' })} ); }, [ instanceData.computed_resource_claim?.total_layers, instanceData.computed_resource_claim?.offload_layers ]); const handleOnSelect = (val: string) => { if (val === 'download') { downloadStream({ url: `${MODEL_INSTANCE_API}/${instanceData.id}/logs`, filename: createFileName(instanceData.name), downloadNotification }); } else { handleChildSelect(val, instanceData); } }; return ( <> {contextHolder}
{instanceData.name} {!!instanceData.worker_id && ( )} {renderOffloadInfo} {renderDistributionInfo( instanceData.distributed_servers?.rpc_servers || instanceData.distributed_servers?.ray_actors || [] )} {dayjs(instanceData.created_at).format('YYYY-MM-DD HH:mm:ss')}
); }; export default React.memo(InstanceItem);