feat: benchmark list

This commit is contained in:
jialin
2026-01-30 18:48:55 +08:00
parent 8b282afb03
commit 37b814b70a
29 changed files with 1034 additions and 19 deletions
+1 -1
View File
@@ -2,7 +2,7 @@ import { createFromIconfontCN } from '@ant-design/icons';
// import './iconfont/iconfont.js';
const IconFont = createFromIconfontCN({
scriptUrl: '//at.alicdn.com/t/c/font_4613488_7f4klfqdzm.js'
scriptUrl: '//at.alicdn.com/t/c/font_4613488_9wb9516md1k.js'
});
export default IconFont;
+1 -1
View File
@@ -112,7 +112,7 @@ export const FilterBar: React.FC<FilterBarProps> = (props) => {
return null;
}
return (
<Space size={20}>
<Space size={16}>
{handleClickPrimary ? (
actionType === 'dropdown' ? (
<DropDownActions
+16
View File
@@ -0,0 +1,16 @@
export default {
'benchmark.title': 'Benchmark',
'benchmark.button.add': 'Add Benchmark',
'benchmark.button.compare': 'Compare',
'benchmark.table.model': 'Model',
'benchmark.table.dataset': 'Dataset',
'benchmark.table.requestRate': 'Request Rate',
'benchmark.table.gpu': 'GPU ',
'benchmark.table.tpot': 'TPOT (ms)',
'benchmark.table.itl': 'ITL (ms)',
'benchmark.table.ttft': 'TTFT (ms)',
'benchmark.table.rps': 'Requests/s',
'benchmark.table.tps': 'Tokens/s',
'benchmark.table.inputLen': 'Input Length',
'benchmark.table.outputLen': 'Output Length'
};
+1
View File
@@ -14,6 +14,7 @@ export default {
'menu.models.catalog': 'Model Catalog',
'menu.models.deployment': 'Deployments',
'menu.models.userModels': 'My Models',
'menu.models.benchmark': 'Benchmark',
'menu.modelCatalog': 'Catalog',
'menu.resources': 'Resources',
'menu.apikeys': 'API Keys',
+4 -1
View File
@@ -39,5 +39,8 @@ export default {
'noresult.resources.worker':
'No workers available. Add a worker to get started.',
'noresult.resources.gotocluster': 'Create Your First Cluster',
'noresult.resources.gotoworker': 'Add Worker'
'noresult.resources.gotoworker': 'Add Worker',
'noresult.benchmark.title': 'No Benchmarks',
'noresult.benchmark.subTitle': 'No benchmarks have been added yet.',
'noresult.benchmark.nofound': 'No matching benchmarks found.'
};
+16
View File
@@ -0,0 +1,16 @@
export default {
'benchmark.title': 'Benchmark',
'benchmark.button.add': 'Add Benchmark',
'benchmark.button.compare': 'Compare',
'benchmark.table.model': 'Model',
'benchmark.table.dataset': 'Dataset',
'benchmark.table.requestRate': 'Request Rate',
'benchmark.table.gpu': 'GPU ',
'benchmark.table.tpot': 'TPOT (ms)',
'benchmark.table.itl': 'ITL (ms)',
'benchmark.table.ttft': 'TTFT (ms)',
'benchmark.table.rps': 'Requests/s',
'benchmark.table.tps': 'Tokens/s',
'benchmark.table.inputLen': 'Input Length',
'benchmark.table.outputLen': 'Output Length'
};
+3 -1
View File
@@ -13,6 +13,7 @@ export default {
'menu.models.modelCatalog': 'カタログ',
'menu.models.catalog': 'モデルカタログ',
'menu.models.deployment': 'Deployment',
'menu.models.benchmark': 'Benchmark',
'menu.modelCatalog': 'カタログ',
'menu.resources': 'リソース',
'menu.apikeys': 'APIキー',
@@ -50,5 +51,6 @@ export default {
// 11. 'menu.models.userModels': 'My Models'
// 12. 'menu.clusterManagement.clusterDetail': 'Cluster Detail',
// 13. 'menu.clusterManagement.clusterCreate': 'Create Cluster',
// 14. 'menu.resources.backendsList': 'Inference Backends'
// 14. 'menu.resources.backendsList': 'Inference Backends',
// 15. 'menu.models.benchmark': 'Benchmark',
// ========== End of To-Do List ==========
+4 -1
View File
@@ -39,5 +39,8 @@ export default {
'noresult.resources.worker':
'No workers available. Add a worker to get started.',
'noresult.resources.gotocluster': 'Create Your First Cluster',
'noresult.resources.gotoworker': 'Add Worker'
'noresult.resources.gotoworker': 'Add Worker',
'noresult.benchmark.title': 'No Benchmarks',
'noresult.benchmark.subTitle': 'No benchmarks have been added yet.',
'noresult.benchmark.nofound': 'No matching benchmarks found.'
};
+16
View File
@@ -0,0 +1,16 @@
export default {
'benchmark.title': 'Benchmark',
'benchmark.button.add': 'Add Benchmark',
'benchmark.button.compare': 'Compare',
'benchmark.table.model': 'Model',
'benchmark.table.dataset': 'Dataset',
'benchmark.table.requestRate': 'Request Rate',
'benchmark.table.gpu': 'GPU ',
'benchmark.table.tpot': 'TPOT (ms)',
'benchmark.table.itl': 'ITL (ms)',
'benchmark.table.ttft': 'TTFT (ms)',
'benchmark.table.rps': 'Requests/s',
'benchmark.table.tps': 'Tokens/s',
'benchmark.table.inputLen': 'Input Length',
'benchmark.table.outputLen': 'Output Length'
};
+2 -1
View File
@@ -13,6 +13,7 @@ export default {
'menu.models.modelCatalog': 'Каталог',
'menu.models.catalog': 'Каталог моделей',
'menu.models.deployment': 'Запуск',
'menu.models.benchmark': 'Benchmark',
'menu.modelCatalog': 'Каталог',
'menu.resources': 'Ресурсы',
'menu.apikeys': 'API-ключи',
@@ -37,5 +38,5 @@ export default {
};
// ========== To-Do: Translate Keys (Remove After Translation) ==========
// 1. 'menu.models.benchmark': 'Benchmark',
// ========== End of To-Do List ==========
+8 -2
View File
@@ -40,12 +40,18 @@ export default {
'noresult.resources.worker':
'No workers available. Add a worker to get started.',
'noresult.resources.gotocluster': 'Create Your First Cluster',
'noresult.resources.gotoworker': 'Add Worker'
'noresult.resources.gotoworker': 'Add Worker',
'noresult.benchmark.title': 'No Benchmarks',
'noresult.benchmark.subTitle': 'No benchmarks have been added yet.',
'noresult.benchmark.nofound': 'No matching benchmarks found.'
};
// ========== To-Do: Translate Keys (Remove After Translation) ==========
// 1. 'noresult.resources.cluster': 'No clusters available. Add a cluster to get started.',
// 2. 'noresult.resources.worker': 'No workers available. Add a worker to get started.',
// 3. 'noresult.resources.gotocluster': 'Create Your First Cluster',
// 4. 'noresult.resources.gotoworker': 'Add Worker'
// 4. 'noresult.resources.gotoworker': 'Add Worker',
// 5. 'noresult.benchmark.title': 'No Benchmarks',
// 6. 'noresult.benchmark.subTitle': 'No benchmarks have been added yet.',
// 7. 'noresult.benchmark.nofound': 'No matching benchmarks found.'
// ========================================================================
+16
View File
@@ -0,0 +1,16 @@
export default {
'benchmark.title': '基准测试',
'benchmark.button.add': '添加基准测试',
'benchmark.button.compare': '对比',
'benchmark.table.model': '模型',
'benchmark.table.dataset': '数据集',
'benchmark.table.requestRate': '请求率',
'benchmark.table.gpu': 'GPU ',
'benchmark.table.tpot': 'TPOT (ms)',
'benchmark.table.itl': 'ITL (ms)',
'benchmark.table.ttft': 'TTFT (ms)',
'benchmark.table.rps': '请求数/秒',
'benchmark.table.tps': 'Tokens/秒',
'benchmark.table.inputLen': '输入长度',
'benchmark.table.outputLen': '输出长度'
};
+1
View File
@@ -13,6 +13,7 @@ export default {
'menu.models.modelCatalog': '模型库',
'menu.models.deployment': '部署',
'menu.models.userModels': '我的模型',
'menu.models.benchmark': '基准测试',
'menu.modelCatalog': '模型库',
'menu.models.catalog': '模型库',
'menu.resources': '资源',
+4 -1
View File
@@ -37,5 +37,8 @@ export default {
'noresult.resources.cluster': '暂无可用集群,请添加集群以开始使用。',
'noresult.resources.worker': '暂无可用节点,请添加节点以开始使用。',
'noresult.resources.gotocluster': '创建您的第一个集群',
'noresult.resources.gotoworker': '添加节点'
'noresult.resources.gotoworker': '添加节点',
'noresult.benchmark.title': '暂无基准测试',
'noresult.benchmark.subTitle': '尚未添加任何基准测试。',
'noresult.benchmark.nofound': '未找到匹配的基准测试'
};
@@ -94,15 +94,17 @@ const ScrollSpyTabs: React.FC<ScrollSpyTabsProps> = forwardRef(
return (
<div>
<SegmentedHeader $top={segmentedTop.top}>
<SegmentLine
theme={'light'}
defaultValue={target}
value={target}
onChange={handleTargetChange}
options={segmentOptions}
/>
</SegmentedHeader>
{segmentOptions.length > 0 && (
<SegmentedHeader $top={segmentedTop.top}>
<SegmentLine
theme={'light'}
defaultValue={target}
value={target}
onChange={handleTargetChange}
options={segmentOptions}
/>
</SegmentedHeader>
)}
{children}
<div className="holder" style={{ height: holderHeight }}></div>
</div>
+71
View File
@@ -0,0 +1,71 @@
import { request } from '@umijs/max';
import { CancelToken } from 'axios';
import { BenchmarkListItem, FormData } from '../config/types';
export const BENCHMARKS_API = '/benchmark';
export const DATASETS_API = '/datasets';
export async function queryBenchmarkList(
params: Global.SearchParams,
options?: {
token?: CancelToken;
}
) {
return request<Global.PageResponse<BenchmarkListItem>>(`${BENCHMARKS_API}`, {
method: 'GET',
params,
cancelToken: options?.token
});
}
export async function createBenchmark(params: { data: FormData }) {
return request(`${BENCHMARKS_API}`, {
method: 'POST',
data: params.data
});
}
export async function updateBenchmark(params: { id: number; data: FormData }) {
return request(`${BENCHMARKS_API}/${params.id}`, {
method: 'PUT',
data: params.data
});
}
export async function deleteBenchmark(id: number) {
return request(`${BENCHMARKS_API}/${id}`, {
method: 'DELETE'
});
}
export async function queryBenchmarkLogs(
id: number,
options?: {
token?: CancelToken;
}
) {
return request(`${BENCHMARKS_API}/${id}/logs`, {
method: 'GET',
cancelToken: options?.token
});
}
export async function createBenchmarkResult(params: { id: number; data: any }) {
return request(`${BENCHMARKS_API}/${params.id}/result`, {
method: 'POST',
data: params.data
});
}
export async function queryDatasetList(
params: Global.SearchParams,
options?: {
token?: CancelToken;
}
) {
return request<Global.PageResponse<BenchmarkListItem>>(`${DATASETS_API}`, {
method: 'GET',
params,
cancelToken: options?.token
});
}
@@ -0,0 +1,59 @@
import { PageActionType } from '@/config/types';
import FormDrawer from '@/pages/_components/form-drawer';
import React, { useRef } from 'react';
import { FormData, BenchmarkListItem as ListItem } from '../config/types';
import BenchmarkForm from '../forms';
type AddModalProps = {
title: string;
action: PageActionType;
open: boolean;
currentData?: ListItem; // Used when action is EDIT
onOk: (values: FormData) => void;
onCancel: () => void;
};
const AddBenchmark: React.FC<AddModalProps> = ({
title,
action,
open,
currentData,
onOk,
onCancel
}) => {
const form = useRef<any>(null);
const handleSubmit = () => {
form.current?.submit();
};
const handleOk = async (data: FormData) => {
onOk({
...data
});
};
const handleCancel = () => {
form.current?.resetFields();
onCancel();
};
return (
<FormDrawer
title={title}
open={open}
onCancel={handleCancel}
onSubmit={handleSubmit}
width={600}
>
<BenchmarkForm
ref={form}
action={action}
currentData={currentData}
onFinish={handleOk}
/>
</FormDrawer>
);
};
export default AddBenchmark;
@@ -0,0 +1,61 @@
import {
DeleteOutlined,
PlusOutlined,
SettingOutlined
} from '@ant-design/icons';
import { useIntl } from '@umijs/max';
import { Button, Space } from 'antd';
import React from 'react';
export interface RightActionsProps {
handleDeleteByBatch: () => void;
handleClickPrimary?: () => void;
handleSettingFields?: () => void;
handleCompare?: () => void;
buttonText?: string;
rowSelection: {
selectedRowKeys: React.Key[];
};
}
const RightActions: React.FC<RightActionsProps> = ({
handleDeleteByBatch,
handleClickPrimary,
handleCompare,
handleSettingFields,
buttonText,
rowSelection
}) => {
const intl = useIntl();
return (
<Space size={16}>
<Button onClick={handleSettingFields} icon={<SettingOutlined />}></Button>
<Button variant="solid" color="orange" onClick={handleCompare}>
Compare
</Button>
<Button
icon={<PlusOutlined></PlusOutlined>}
type="primary"
onClick={handleClickPrimary}
>
{buttonText}
</Button>
<Button
icon={<DeleteOutlined />}
danger
onClick={handleDeleteByBatch}
disabled={!rowSelection?.selectedRowKeys?.length}
>
<span>
{intl?.formatMessage?.({ id: 'common.button.delete' })}
{rowSelection?.selectedRowKeys?.length > 0 && (
<span>({rowSelection?.selectedRowKeys?.length})</span>
)}
</span>
</Button>
</Space>
);
};
export default RightActions;
@@ -0,0 +1,20 @@
import { PageActionType } from '@/config/types';
import { createContext, useContext } from 'react';
interface FormContextProps {
action: PageActionType;
}
const FormContext = createContext<FormContextProps>({} as FormContextProps);
export const useFormContext = () => {
const context = useContext(FormContext);
if (!context) {
throw new Error(
'useFormContext must be used within a FormContext.Provider'
);
}
return context;
};
export default FormContext;
+29
View File
@@ -0,0 +1,29 @@
import { StatusMaps } from '@/config';
import { StatusType } from '@/config/types';
export const BenchmarkStatusValueMap = {
Pending: 'pending',
Claimed: 'claimed',
Running: 'running',
Completed: 'completed',
Error: 'error',
Unreachable: 'unreachable'
};
export const BenchmarkStatusLabelMap = {
[BenchmarkStatusValueMap.Pending]: 'Pending',
[BenchmarkStatusValueMap.Claimed]: 'Claimed',
[BenchmarkStatusValueMap.Running]: 'Running',
[BenchmarkStatusValueMap.Completed]: 'Completed',
[BenchmarkStatusValueMap.Error]: 'Error',
[BenchmarkStatusValueMap.Unreachable]: 'Unreachable'
};
export const BenchmarkStatus: Record<string, StatusType> = {
[BenchmarkStatusValueMap.Pending]: StatusMaps.transitioning,
[BenchmarkStatusValueMap.Claimed]: StatusMaps.warning,
[BenchmarkStatusValueMap.Running]: StatusMaps.success,
[BenchmarkStatusValueMap.Completed]: StatusMaps.success,
[BenchmarkStatusValueMap.Error]: StatusMaps.error,
[BenchmarkStatusValueMap.Unreachable]: StatusMaps.error
};
+103
View File
@@ -0,0 +1,103 @@
export interface ComputedResourceClaim {
is_unified_memory: boolean;
offload_layers: number;
total_layers: number;
ram: number;
vram: Record<string, number>;
tensor_split: number[];
vram_utilization: number;
}
export interface ComputedResourceClaim1 {
is_unified_memory: boolean;
offload_layers: number;
total_layers: number;
ram: number;
vram: Record<string, number>;
tensor_split: number[];
vram_utilization: number;
}
export interface SubordinateWorkersItem {
computed_resource_claim: ComputedResourceClaim1;
ports: number[];
worker_id: number;
worker_name: string;
gpu_type: string;
gpu_indexes: number[];
gpu_ids: string[];
}
export interface InstanceSnapshot {
computed_resource_claim: ComputedResourceClaim;
ports: number[];
worker_id: number;
worker_name: string;
gpu_type: string;
gpu_indexes: number[];
gpu_ids: string[];
id: number;
name: string;
state: string;
state_message: string;
backend: string;
backend_version: string;
api_detected_backend_version: string;
subordinate_workers: SubordinateWorkersItem[];
}
export interface GPUSnapshot {
vendor: string;
type: string;
index: number;
device_index: number;
device_chip_index: number;
arch_family: string;
name: string;
uuid: string;
driver_version: string;
runtime_version: string;
compute_capability: string;
id: string;
worker_id: number;
worker_name: string;
memory_total: number;
core_total: number;
}
export interface FormData {
name: string;
description: string;
labels: Record<string, string>;
cluster_id: number;
model_id: number;
model_name: string;
model_instance_name: string;
dataset_id: number;
dataset_name: string;
dataset_source: string;
dataset_prompt_tokens: number;
dataset_output_tokens: number;
total_requests: number;
request_rate: number;
instance_snapshots: Record<string, InstanceSnapshot>;
gpu_snapshots: Record<string, GPUSnapshot>;
state: string;
state_message: string;
worker_id: number;
gpu_summary: string;
gpu_vendor_summary: string;
}
export interface BenchmarkListItem extends FormData {
id: number;
created_at: string;
updated_at: string;
}
export interface DatasetListItem {
name: string;
source: string;
prompt_tokens: number;
output_tokens: number;
}
+34
View File
@@ -0,0 +1,34 @@
import SealInput from '@/components/seal-form/seal-input';
import { useIntl } from '@umijs/max';
import { Form } from 'antd';
import React from 'react';
import { FormData } from '../config/types';
const BasicForm: React.FC = () => {
const intl = useIntl();
const form = Form.useFormInstance();
return (
<Form.Item<FormData>
name="name"
rules={[
{
required: true,
message: intl.formatMessage(
{ id: 'common.form.rule.input' },
{
name: intl.formatMessage({ id: 'common.table.name' })
}
)
}
]}
>
<SealInput.Input
label={intl.formatMessage({ id: 'common.table.name' })}
required
></SealInput.Input>
</Form.Item>
);
};
export default BasicForm;
+109
View File
@@ -0,0 +1,109 @@
import IconFont from '@/components/icon-font';
import { PageAction } from '@/config';
import { PageActionType } from '@/config/types';
import { useWrapperContext } from '@/pages/_components/column-wrapper/use-wrapper-context';
import ScrollSpyTabs from '@/pages/_components/scroll-spy-tabs';
import { useIntl } from '@umijs/max';
import { Form } from 'antd';
import {
forwardRef,
useEffect,
useImperativeHandle,
useRef,
useState
} from 'react';
import FormContext from '../config/form-context';
import { FormData, BenchmarkListItem as ListItem } from '../config/types';
import Basic from './basic';
interface ProviderFormProps {
ref?: any;
action: PageActionType;
currentData?: ListItem; // Used when action is EDIT
onFinish: (values: FormData) => Promise<void>;
}
const TABKeysMap = {
BASIC: 'basic',
SUPPORTEDMODELS: 'supportedModels',
CUSTOMCONFIG: 'customConfig',
ADVANCED: 'advanced'
};
const ProviderForm: React.FC<ProviderFormProps> = forwardRef((props, ref) => {
const { action, currentData, onFinish } = props;
const intl = useIntl();
const [form] = Form.useForm();
const { getScrollElementScrollableHeight } = useWrapperContext();
const [activeKey, setActiveKey] = useState<string[]>([TABKeysMap.BASIC]);
const scrollTabsRef = useRef<any>(null);
const segmentOptions = [
{
value: TABKeysMap.BASIC,
label: 'Basic',
icon: <IconFont type="icon-basic" />,
field: 'name'
},
{
value: TABKeysMap.SUPPORTEDMODELS,
label: 'Supported Models',
icon: <IconFont type="icon-speed" />,
field: 'supportedModels'
},
{
value: TABKeysMap.ADVANCED,
label: intl.formatMessage({ id: 'resources.form.advanced' }),
icon: <IconFont type="icon-settings" />,
field: 'advanceConfig'
}
];
const handleActiveChange = (key: string[]) => {
setActiveKey(key);
};
const handleOnCollapseChange = (keys: string | string[]) => {
setActiveKey(Array.isArray(keys) ? keys : [keys]);
};
useImperativeHandle(ref, () => ({
submit: () => {
form.submit();
},
resetFields: () => {
form.resetFields();
}
}));
useEffect(() => {
if (action === PageAction.EDIT && currentData) {
form.setFieldsValue({
...currentData
});
}
}, [form, currentData, action]);
return (
<ScrollSpyTabs
ref={scrollTabsRef}
defaultTarget="basic"
segmentOptions={[]}
activeKey={activeKey}
setActiveKey={handleActiveChange}
segmentedTop={{
top: 0,
offsetTop: 96
}}
getScrollElementScrollableHeight={getScrollElementScrollableHeight}
>
<FormContext.Provider value={{ action }}>
<Form form={form} onFinish={onFinish} initialValues={{}}>
<Basic />
</Form>
</FormContext.Provider>
</ScrollSpyTabs>
);
});
export default ProviderForm;
@@ -0,0 +1,176 @@
// columns.ts
import AutoTooltip from '@/components/auto-tooltip';
import DropdownButtons from '@/components/drop-down-buttons';
import icons from '@/components/icon-font/icons';
import StatusTag from '@/components/status-tag';
import { tableSorter } from '@/config/settings';
import { useIntl } from '@umijs/max';
import { ColumnsType } from 'antd/es/table';
import dayjs from 'dayjs';
import { useMemo } from 'react';
import { BenchmarkStatus, BenchmarkStatusLabelMap } from '../config';
import { BenchmarkListItem as ListItem } from '../config/types';
const actionList = [
{
key: 'edit',
label: 'common.button.edit',
icon: icons.EditOutlined
},
{
key: 'delete',
label: 'common.button.delete',
icon: icons.DeleteOutlined,
props: {
danger: true
}
}
];
const useBenchmarkColumns = (
sortOrder: string[],
handleSelect: (val: string, record: ListItem) => void
): ColumnsType<ListItem> => {
const intl = useIntl();
return useMemo(() => {
return [
{
title: intl.formatMessage({ id: 'common.table.name' }),
dataIndex: 'name',
sorter: tableSorter(1),
render: (text: string) => (
<AutoTooltip ghost minWidth={20}>
{text}
</AutoTooltip>
)
},
{
title: intl.formatMessage({ id: 'benchmark.table.model' }),
dataIndex: 'model_name',
sorter: tableSorter(1),
render: (text: string) => (
<AutoTooltip ghost minWidth={20}>
{text}
</AutoTooltip>
)
},
{
title: intl.formatMessage({ id: 'benchmark.table.dataset' }),
dataIndex: 'dataset_name',
sorter: tableSorter(1),
render: (text: string) => (
<AutoTooltip ghost minWidth={20}>
{text}
</AutoTooltip>
)
},
{
title: intl.formatMessage({ id: 'common.table.status' }),
dataIndex: 'state',
render: (value: number, record: ListItem) => (
<StatusTag
statusValue={{
status: BenchmarkStatus[value],
text: BenchmarkStatusLabelMap[value],
message: record.state_message || undefined
}}
/>
)
},
{
title: intl.formatMessage({ id: 'benchmark.table.requestRate' }),
dataIndex: 'request_rate',
sorter: tableSorter(1),
render: (text: string) => (
<AutoTooltip ghost minWidth={20}>
{text}
</AutoTooltip>
)
},
{
title: intl.formatMessage({ id: 'benchmark.table.gpu' }),
dataIndex: 'gpu_summary',
sorter: tableSorter(1),
render: (text: string) => (
<AutoTooltip ghost minWidth={20}>
{text}
</AutoTooltip>
)
},
{
title: intl.formatMessage({ id: 'benchmark.table.itl' }),
dataIndex: 'itl',
sorter: tableSorter(1),
render: (text: string) => (
<AutoTooltip ghost minWidth={20}>
{text}
</AutoTooltip>
)
},
{
title: intl.formatMessage({ id: 'benchmark.table.tpot' }),
dataIndex: 'tpot',
sorter: tableSorter(1),
render: (text: string) => (
<AutoTooltip ghost minWidth={20}>
{text}
</AutoTooltip>
)
},
{
title: intl.formatMessage({ id: 'benchmark.table.ttft' }),
dataIndex: 'ttft',
sorter: tableSorter(1),
render: (text: string) => (
<AutoTooltip ghost minWidth={20}>
{text}
</AutoTooltip>
)
},
{
title: intl.formatMessage({ id: 'benchmark.table.rps' }),
dataIndex: 'requests_per_second',
sorter: tableSorter(1),
render: (text: string) => (
<AutoTooltip ghost minWidth={20}>
{text}
</AutoTooltip>
)
},
{
title: intl.formatMessage({ id: 'benchmark.table.tps' }),
dataIndex: 'tokens_per_second',
sorter: tableSorter(1),
render: (text: string) => (
<AutoTooltip ghost minWidth={20}>
{text}
</AutoTooltip>
)
},
{
title: intl.formatMessage({ id: 'common.table.createTime' }),
dataIndex: 'created_at',
sorter: tableSorter(3),
render: (value: string) => (
<span>{dayjs(value).format('YYYY-MM-DD HH:mm:ss')}</span>
)
},
{
title: intl.formatMessage({ id: 'common.table.operation' }),
dataIndex: 'operations',
ellipsis: {
showTitle: false
},
render: (value: string, record: ListItem) => (
<DropdownButtons
items={actionList}
onSelect={(val) => handleSelect(val, record)}
></DropdownButtons>
)
}
];
}, [intl, handleSelect]);
};
export default useBenchmarkColumns;
@@ -0,0 +1,46 @@
import { PageAction } from '@/config';
import { PageActionType } from '@/config/types';
import { useState } from 'react';
import { BenchmarkListItem as ListItem } from '../config/types';
const useCreateBenchmark = () => {
const [openModalStatus, setOpenModalStatus] = useState<{
open: boolean;
action: PageActionType;
currentData?: ListItem;
title: string;
}>({
open: false,
action: PageAction.CREATE,
currentData: undefined,
title: ''
});
const openModal = (action: PageActionType, title: string, row?: ListItem) => {
setOpenModalStatus({
...openModalStatus,
open: true,
title: title,
action,
currentData: row
});
};
const closeModal = () => {
setOpenModalStatus({
open: false,
action: PageAction.CREATE,
currentData: undefined,
title: ''
});
};
return {
openBenchmarkModalStatus: openModalStatus,
setOpenBenchmarkModalStatus: setOpenModalStatus,
openBenchmarkModal: openModal,
closeBenchmarkModal: closeModal
};
};
export default useCreateBenchmark;
@@ -0,0 +1,40 @@
import { PageActionType } from '@/config/types';
import { useState } from 'react';
import { BenchmarkListItem as ListItem } from '../config/types';
const useCreateCompare = () => {
const [openModalStatus, setOpenModalStatus] = useState<{
open: boolean;
currentData?: ListItem[];
}>({
open: false,
currentData: undefined
});
const openModal = (
action: PageActionType,
title: string,
rows?: ListItem[]
) => {
setOpenModalStatus({
open: true,
currentData: rows
});
};
const closeModal = () => {
setOpenModalStatus({
open: false,
currentData: undefined
});
};
return {
openCompareModalStatus: openModalStatus,
setOpenCompareModalStatus: setOpenModalStatus,
openCompareModal: openModal,
closeCompareModal: closeModal
};
};
export default useCreateCompare;
+172
View File
@@ -0,0 +1,172 @@
import DeleteModal from '@/components/delete-modal';
import IconFont from '@/components/icon-font';
import { FilterBar } from '@/components/page-tools';
import { PageAction } from '@/config';
import { TABLE_SORT_DIRECTIONS } from '@/config/settings';
import useTableFetch from '@/hooks/use-table-fetch';
import { useIntl } from '@umijs/max';
import { useMemoizedFn } from 'ahooks';
import { ConfigProvider, Table, message } from 'antd';
import _ from 'lodash';
import NoResult from '../_components/no-result';
import PageBox from '../_components/page-box';
import {
createBenchmark,
deleteBenchmark,
queryBenchmarkList,
updateBenchmark
} from './apis';
import AddBenchmarkModal from './components/add-benchmark-modal';
import RightActions from './components/right-actions';
import { FormData, BenchmarkListItem as ListItem } from './config/types';
import useBenchmarkColumns from './hooks/use-benchmark-columns';
import useCreateBenchmark from './hooks/use-create-benchmark';
const Benchmark: React.FC = () => {
const {
dataSource,
rowSelection,
queryParams,
sortOrder,
modalRef,
handleDelete,
handleDeleteBatch,
fetchData,
handlePageChange,
handleTableChange,
handleSearch,
handleNameChange
} = useTableFetch<ListItem>({
fetchAPI: queryBenchmarkList,
deleteAPI: deleteBenchmark,
contentForDelete: 'menu.models.benchmark'
});
const intl = useIntl();
const { openBenchmarkModal, closeBenchmarkModal, openBenchmarkModalStatus } =
useCreateBenchmark();
const handleAddBenchmark = () => {
openBenchmarkModal(PageAction.CREATE, 'Add Benchmark');
};
const handleModalOk = async (data: FormData) => {
const params = {
...data
};
try {
if (openBenchmarkModalStatus.action === PageAction.EDIT) {
await updateBenchmark({
data: {
...params
},
id: openBenchmarkModalStatus.currentData!.id
});
} else {
await createBenchmark({ data: params });
}
fetchData();
closeBenchmarkModal();
message.success(intl.formatMessage({ id: 'common.message.success' }));
} catch (error) {
closeBenchmarkModal();
}
};
const handleModalCancel = () => {
closeBenchmarkModal();
};
const handleEditUser = (row: ListItem) => {
openBenchmarkModal(PageAction.EDIT, 'Edit Benchmark', row);
};
const handleSelect = useMemoizedFn((val: any, row: ListItem) => {
if (val === 'edit') {
handleEditUser(row);
} else if (val === 'delete') {
handleDelete({ ...row, name: row.name });
}
});
const renderEmpty = (type?: string) => {
if (type !== 'Table') return;
return (
<NoResult
loading={dataSource.loading}
loadend={dataSource.loadend}
dataSource={[]}
image={<IconFont type="icon-credential-outline" />}
filters={_.omit(queryParams, ['sort_by'])}
noFoundText={intl.formatMessage({
id: 'noresult.benchmark.nofound'
})}
title={intl.formatMessage({ id: 'noresult.benchmark.title' })}
subTitle={intl.formatMessage({
id: 'noresult.benchmark.subTitle'
})}
onClick={handleAddBenchmark}
buttonText={intl.formatMessage({ id: 'noresult.button.add' })}
></NoResult>
);
};
const columns = useBenchmarkColumns(sortOrder, handleSelect);
return (
<>
<PageBox>
<FilterBar
showSelect={false}
marginBottom={22}
marginTop={0}
handleSearch={handleSearch}
handleInputChange={handleNameChange}
rowSelection={rowSelection}
widths={{ input: 300 }}
right={
<RightActions
handleDeleteByBatch={handleDeleteBatch}
handleClickPrimary={handleAddBenchmark}
buttonText={intl.formatMessage({
id: 'benchmark.button.add'
})}
rowSelection={rowSelection}
/>
}
></FilterBar>
<ConfigProvider renderEmpty={renderEmpty}>
<Table
tableLayout="fixed"
columns={columns}
dataSource={dataSource.dataList}
rowSelection={rowSelection}
loading={dataSource.loading}
sortDirections={TABLE_SORT_DIRECTIONS}
showSorterTooltip={false}
rowKey="id"
onChange={handleTableChange}
pagination={{
showSizeChanger: true,
pageSize: queryParams.perPage,
current: queryParams.page,
total: dataSource.total,
hideOnSinglePage: queryParams.perPage === 10,
onChange: handlePageChange
}}
></Table>
</ConfigProvider>
</PageBox>
<AddBenchmarkModal
open={openBenchmarkModalStatus.open}
action={openBenchmarkModalStatus.action}
title={openBenchmarkModalStatus.title}
currentData={openBenchmarkModalStatus.currentData}
onCancel={handleModalCancel}
onOk={handleModalOk}
></AddBenchmarkModal>
<DeleteModal ref={modalRef}></DeleteModal>
</>
);
};
export default Benchmark;
+1 -1
View File
@@ -693,7 +693,7 @@ const Models: React.FC<ModelsProps> = ({
</Space>
}
right={
<Space size={20}>
<Space size={16}>
{page !== 'clusters' && (
<DropDownActions
menu={{