style: model list buttons

This commit is contained in:
jialin
2025-03-12 10:43:59 +08:00
parent a7cfa05504
commit 5edbc1a7b5
24 changed files with 460 additions and 157 deletions
@@ -101,12 +101,14 @@ const AdvanceConfig: React.FC<AdvanceConfigProps> = (props) => {
if (backend === backendOptionsMap.llamaBox) {
return {
backend: 'llama-box',
releases: 'https://github.com/gpustack/llama-box/releases',
link: 'https://github.com/gpustack/llama-box?tab=readme-ov-file#usage'
};
}
if (backend === backendOptionsMap.vllm) {
return {
backend: 'vLLM',
releases: 'https://github.com/vllm-project/vllm/releases',
link: 'https://docs.vllm.ai/en/stable/serving/openai_compatible_server.html#command-line-arguments-for-the-server'
};
}
@@ -327,6 +329,25 @@ const AdvanceConfig: React.FC<AdvanceConfigProps> = (props) => {
description={intl.formatMessage({
id: 'models.form.backendVersion.tips'
})}
labelExtra={
backendParamsTips?.releases && (
<span
style={{
marginLeft: 5
}}
>
(
<Typography.Link
style={{ lineHeight: 1 }}
href={backendParamsTips?.releases}
target="_blank"
>
{intl.formatMessage({ id: 'models.form.releases' })}
</Typography.Link>
)
</span>
)
}
></SealInput.Input>
</Form.Item>
@@ -346,19 +367,20 @@ const AdvanceConfig: React.FC<AdvanceConfigProps> = (props) => {
dataList={form.getFieldValue('backend_parameters') || []}
onChange={handleBackendParametersChange}
options={paramsConfig}
description={
backendParamsTips && (
<span>
{intl.formatMessage(
{ id: 'models.form.backend_parameters.vllm.tips' },
{ backend: backendParamsTips.backend || '' }
)}{' '}
labelExtra={
backendParamsTips?.link && (
<span style={{ marginLeft: 2 }}>
(
<Typography.Link
href={backendParamsTips.link}
style={{ lineHeight: 1 }}
href={backendParamsTips?.link}
target="_blank"
>
{intl.formatMessage({ id: 'common.text.here' })}
{intl.formatMessage({
id: 'models.form.moreparameters'
})}
</Typography.Link>
)
</span>
)
}
+99 -69
View File
@@ -2,7 +2,7 @@ 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 from '@/components/simple-table';
import SimpleTabel, { ColumnProps } from '@/components/simple-table';
import StatusTag from '@/components/status-tag';
import { HandlerOptions } from '@/hooks/use-chunk-fetch';
import useDownloadStream from '@/hooks/use-download-stream';
@@ -12,7 +12,8 @@ import {
DeleteOutlined,
DownloadOutlined,
HddFilled,
InfoCircleOutlined
InfoCircleOutlined,
ThunderboltFilled
} from '@ant-design/icons';
import { useIntl } from '@umijs/max';
import {
@@ -27,17 +28,48 @@ import {
} from 'antd';
import dayjs from 'dayjs';
import _ from 'lodash';
import React, { useCallback, useMemo } from 'react';
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 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 (
<span className="server-info-wrapper">
<Tooltip
open={open}
onOpenChange={setOpen}
title={props.title}
overlayInnerStyle={{
width: 'max-content',
maxWidth: '400px'
}}
>
<span className="server-info">
<InfoCircleOutlined />
</span>
</Tooltip>
</span>
);
};
interface InstanceItemProps {
instanceData: ModelInstanceListItem;
workerList: WorkerListItem[];
modelData?: any;
defaultOpenId: string;
handleChildSelect: (val: string, item: ModelInstanceListItem) => void;
}
@@ -76,7 +108,7 @@ const childActionList = [
}
];
const distributeCols = [
const distributeCols: ColumnProps[] = [
{
title: 'Worker',
key: 'worker_name'
@@ -84,7 +116,7 @@ const distributeCols = [
{
title: 'IP',
key: 'worker_ip',
render: ({ row }: { row: ModelInstanceListItem }) => {
render: ({ row }) => {
return row.port ? `${row.worker_ip}:${row.port}` : row.worker_ip;
}
},
@@ -94,9 +126,21 @@ const distributeCols = [
key: 'gpu_index'
},
{
title: 'resources.table.memory',
title: 'resources.table.vram',
locale: true,
key: 'ram'
key: 'vram',
rowSpan: ({ row, rowIndex, colIndex, dataIndex, dataList }) => {
return rowIndex === 0 ? dataList.length : 0;
},
render: ({ rowIndex, dataList }) => {
if (rowIndex === 0) {
return convertFileSize(
_.sumBy(dataList, (item: any) => item.vram),
2
);
}
return null;
}
}
];
@@ -153,6 +197,7 @@ const InstanceItem: React.FC<InstanceItemProps> = ({
instanceData,
workerList,
modelData,
defaultOpenId,
handleChildSelect
}) => {
const [api, contextHolder] = notification.useNotification({
@@ -224,55 +269,32 @@ const InstanceItem: React.FC<InstanceItemProps> = ({
? `${instanceData.worker_ip}:${instanceData.port}`
: instanceData.worker_ip;
}
let backend = modelData?.backend || '';
if (modelData.backend_version) {
backend += ` (${modelData.backend_version})`;
}
const vrams = instanceData.computed_resource_claim?.vram || {};
return (
<div>
<div style={{ marginBottom: 5 }}>
<div>{instanceData.worker_name}</div>
<div className="flex-center">
<HddFilled className="m-r-5" />
{instanceData.worker_name}
{workerIp}
</div>
<div className="flex m-b-6 gap-6">
<InfoItem label="IP" value={workerIp} width={180}></InfoItem>
<InfoItem
width={90}
label={intl.formatMessage({ id: 'models.form.backend' })}
value={backend}
></InfoItem>
<div className="flex-center">
<IconFont type="icon-filled-gpu" className="m-r-5" />
{intl.formatMessage({ id: 'models.table.gpuindex' })}: [
{_.join(instanceData.gpu_indexes?.sort?.(), ',')}]
</div>
<div className="flex gap-6">
<InfoItem
width={180}
label={intl.formatMessage({ id: 'models.table.gpuindex' })}
value={Object.keys(vrams)
?.sort?.()
.map((index) => {
return (
<span className="flex-1" key={index}>
<span className="index">
[{index}] {''}
</span>
{convertFileSize(vrams?.[index], 0)}
</span>
);
})}
></InfoItem>
<InfoItem
width={90}
label={intl.formatMessage({ id: 'resources.table.memory' })}
value={convertFileSize(
instanceData.computed_resource_claim?.ram,
0
)}
></InfoItem>
<div className="flex-center">
<ThunderboltFilled className="m-r-5" />
{intl.formatMessage({ id: 'models.form.backend' })}:{' '}
{modelData?.backend || ''}
{modelData.backend_version ? `(${modelData.backend_version})` : ''}
</div>
</div>
);
}, [modelData, instanceData, intl]);
const calcTotalVram = (vram: Record<string, number>) => {
return _.sum(_.values(vram));
};
const renderDistributedServer = useCallback(
(severList: any[]) => {
const list = _.map(severList, (item: any) => {
@@ -281,20 +303,35 @@ const InstanceItem: React.FC<InstanceItemProps> = ({
worker_name: data?.name,
worker_ip: data?.ip,
port: '',
ram: convertFileSize(item.computed_resource_claim?.ram, 0),
gpu_index: displayGPUs(item.computed_resource_claim?.vram || {})
vram: calcTotalVram(item.computed_resource_claim?.vram || {}),
gpu_index: _.keys(item.computed_resource_claim?.vram).join(',')
};
});
// const list = [
// {
// worker_name: 'worker1',
// worker_ip: '192.168.50.23',
// port: '',
// vram: 21555525632,
// gpu_index: '0,1'
// },
// {
// worker_name: 'worker2',
// worker_ip: '192.168.50.25',
// port: '',
// vram: 21555525632,
// gpu_index: '2,3'
// }
// ];
const mainWorker = [
{
worker_name: `${instanceData.worker_name} (main)`,
worker_name: `${instanceData.worker_name}`,
worker_ip: `${instanceData.worker_ip}`,
port: '',
ram: convertFileSize(instanceData.computed_resource_claim?.ram, 0),
gpu_index: displayGPUs(
instanceData.computed_resource_claim?.vram || {}
)
vram: calcTotalVram(instanceData.computed_resource_claim?.vram || {}),
gpu_index: `${instanceData.gpu_indexes?.join?.(',')}(main)`
}
];
@@ -443,18 +480,12 @@ const InstanceItem: React.FC<InstanceItemProps> = ({
<AutoTooltip title={instanceData.name} ghost>
<span className="m-r-5">{instanceData.name}</span>
</AutoTooltip>
<Tooltip
title={renderWorkerInfo}
overlayInnerStyle={{
width: 'max-content',
maxWidth: '400px'
}}
>
<span className="server-info">
<InfoCircleOutlined className="m-r-2" />{' '}
{intl.formatMessage({ id: 'common.button.moreInfo' })}
</span>
</Tooltip>
{!!instanceData.worker_id && (
<WorkerInfo
title={renderWorkerInfo}
defaultOpen={defaultOpenId === instanceData.name}
></WorkerInfo>
)}
</span>
</Col>
<Col span={6}>
@@ -468,10 +499,9 @@ const InstanceItem: React.FC<InstanceItemProps> = ({
>
{renderOffloadInfo}
{renderDistributionInfo(
instanceData.distributed_servers?.rpc_servers || []
)}
{renderDistributionInfo(
instanceData.distributed_servers?.ray_actors || []
instanceData.distributed_servers?.rpc_servers ||
instanceData.distributed_servers?.ray_actors ||
[]
)}
</span>
</Col>
+21 -1
View File
@@ -1,7 +1,7 @@
import { ListItem as WorkerListItem } from '@/pages/resources/config/types';
import { Space } from 'antd';
import _ from 'lodash';
import React from 'react';
import React, { useEffect, useMemo } from 'react';
import { ModelInstanceListItem } from '../config/types';
import '../style/instance-item.less';
import InstanceItem from './instance-item';
@@ -10,6 +10,7 @@ interface InstanceItemProps {
list: ModelInstanceListItem[];
workerList: WorkerListItem[];
modelData?: any;
currentExpanded?: string;
handleChildSelect: (val: string, item: ModelInstanceListItem) => void;
}
@@ -17,8 +18,26 @@ const Instances: React.FC<InstanceItemProps> = ({
list,
workerList,
modelData,
currentExpanded,
handleChildSelect
}) => {
const [firstLoad, setFirstLoad] = React.useState(true);
const defaultOpenId = useMemo(() => {
if (!currentExpanded) {
return '';
}
const current = _.find(
list,
(item: ModelInstanceListItem) => item.worker_id
);
return current ? current.name : '';
}, [currentExpanded, list]);
useEffect(() => {
setFirstLoad(false);
}, []);
return (
<Space size={16} direction="vertical" style={{ width: '100%' }}>
{_.map(list, (item: ModelInstanceListItem, index: number) => {
@@ -28,6 +47,7 @@ const Instances: React.FC<InstanceItemProps> = ({
modelData={modelData}
workerList={workerList}
instanceData={item}
defaultOpenId={firstLoad ? defaultOpenId : ''}
handleChildSelect={handleChildSelect}
></InstanceItem>
);
+58 -19
View File
@@ -32,7 +32,7 @@ import {
SyncOutlined
} from '@ant-design/icons';
import { PageContainer } from '@ant-design/pro-components';
import { Access, useAccess, useIntl, useNavigate } from '@umijs/max';
import { useAccess, useIntl, useNavigate } from '@umijs/max';
import {
Button,
Dropdown,
@@ -68,7 +68,6 @@ import {
} from '../config';
import { FormData, ListItem, ModelInstanceListItem } from '../config/types';
import { useGenerateFormEditInitialValues } from '../hooks';
import DeployDropdown from './deploy-dropdown';
import DeployModal from './deploy-modal';
import Instances from './instances';
import ModelTag from './model-tag';
@@ -120,7 +119,7 @@ const ActionList = [
{
label: 'common.button.start',
key: 'start',
icon: <IconFont type="icon-playcircle"></IconFont>
icon: <IconFont type="icon-outline-play"></IconFont>
},
{
label: 'common.button.delete',
@@ -132,6 +131,27 @@ const ActionList = [
}
];
const ButtonList = [
{
label: 'common.button.start',
key: 'start',
icon: <IconFont type="icon-outline-play"></IconFont>
},
{
label: 'common.button.stop',
key: 'stop',
icon: <IconFont type="icon-stop1"></IconFont>
},
{
label: 'common.button.delete',
key: 'delete',
icon: <DeleteOutlined />,
props: {
danger: true
}
}
];
const generateSource = (record: ListItem) => {
if (record.source === modelSourceMap.modelscope_value) {
return `${modelSourceMap.modelScope}/${record.model_scope_model_id}`;
@@ -327,6 +347,12 @@ const Models: React.FC<ModelsProps> = ({
}, []);
const sourceOptions = [
{
label: intl.formatMessage({ id: 'menu.models.modelCatalog' }),
value: 'catalog',
key: 'catalog',
icon: <IconFont type="icon-catalog"></IconFont>
},
{
label: 'Hugging Face',
value: modelSourceMap.huggingface_value,
@@ -345,12 +371,6 @@ const Models: React.FC<ModelsProps> = ({
key: 'modelscope',
icon: <IconFont type="icon-tu2"></IconFont>
},
{
label: intl.formatMessage({ id: 'menu.models.modelCatalog' }),
value: 'catalog',
key: 'catalog',
icon: <IconFont type="icon-catalog"></IconFont>
},
{
label: intl.formatMessage({ id: 'models.form.localPath' }),
value: modelSourceMap.local_path_value,
@@ -659,11 +679,12 @@ const Models: React.FC<ModelsProps> = ({
);
const renderChildren = useCallback(
(list: any, parent?: any) => {
(list: any, options: { parent?: any; [key: string]: any }) => {
return (
<Instances
list={list}
modelData={parent}
currentExpanded={options.currentExpanded}
modelData={options.parent}
workerList={workerList}
handleChildSelect={handleChildSelect}
></Instances>
@@ -739,6 +760,18 @@ const Models: React.FC<ModelsProps> = ({
});
};
const handleActionSelect = (val: any) => {
if (val === 'delete') {
handleDeleteBatch();
}
if (val === 'start') {
handleStartBatch();
}
if (val === 'stop') {
handleStopBatch();
}
};
const columns: SealColumnProps[] = useMemo(() => {
return [
{
@@ -939,12 +972,6 @@ const Models: React.FC<ModelsProps> = ({
onClick: handleClickDropdown
}}
trigger={['hover']}
dropdownRender={() => (
<DeployDropdown
items={sourceOptions}
onSelect={handleClickDropdown}
></DeployDropdown>
)}
placement="bottomRight"
>
<Button
@@ -955,7 +982,19 @@ const Models: React.FC<ModelsProps> = ({
{intl?.formatMessage?.({ id: 'models.button.deploy' })}
</Button>
</Dropdown>
<Button
<DropdownButtons
items={ButtonList}
extra={
rowSelection.selectedRowKeys.length > 0 && (
<span>({rowSelection.selectedRowKeys.length})</span>
)
}
size="large"
showText={true}
disabled={!rowSelection.selectedRowKeys.length}
onSelect={handleActionSelect}
/>
{/* <Button
icon={<IconFont type="icon-outline-play"></IconFont>}
onClick={handleStartBatch}
disabled={!rowSelection.selectedRows.length}
@@ -993,7 +1032,7 @@ const Models: React.FC<ModelsProps> = ({
)}
</span>
</Button>
</Access>
</Access> */}
</Space>
}
></PageTools>
+32 -13
View File
@@ -1,6 +1,7 @@
import AutoTooltip from '@/components/auto-tooltip';
import PageTools from '@/components/page-tools';
import ProgressBar from '@/components/progress-bar';
import InfoColumn from '@/components/simple-table/info-column';
import useTableSort from '@/hooks/use-table-sort';
import { convertFileSize } from '@/utils';
import { SyncOutlined } from '@ant-design/icons';
@@ -12,6 +13,33 @@ import { queryGpuDevicesList } from '../apis';
import { GPUDeviceItem } from '../config/types';
const { Column } = Table;
const fieldList = [
{
label: 'resources.table.total',
key: 'total',
locale: true,
render: (val: any) => {
return convertFileSize(val, 0);
}
},
{
label: 'resources.table.used',
key: 'used',
locale: true,
render: (val: any) => {
return convertFileSize(val, 0);
}
},
{
label: 'resources.table.allocated',
key: 'allocated',
locale: true,
render: (val: any) => {
return convertFileSize(val, 0);
}
}
];
const GPUList: React.FC = () => {
console.log('GPUList======');
const intl = useIntl();
@@ -228,19 +256,10 @@ const GPUList: React.FC = () => {
) * 100
}
label={
<span className="flex-column">
<span>
{intl.formatMessage({ id: 'resources.table.total' })}:{' '}
{convertFileSize(record.memory?.total, 0)}
</span>
<span>
{intl.formatMessage({ id: 'resources.table.used' })}:{' '}
{convertFileSize(
record.memory?.used || record.memory?.allocated,
0
)}
</span>
</span>
<InfoColumn
fieldList={fieldList}
data={record.memory}
></InfoColumn>
}
></ProgressBar>
);