chore: detail api
This commit is contained in:
@@ -124,6 +124,17 @@ export default [
|
|||||||
defaultIcon: 'icon-speed',
|
defaultIcon: 'icon-speed',
|
||||||
access: 'canSeeAdmin',
|
access: 'canSeeAdmin',
|
||||||
component: './benchmark/index'
|
component: './benchmark/index'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'benchmarkDetail',
|
||||||
|
path: '/models/benchmark/detail',
|
||||||
|
key: 'benchmarkDetail',
|
||||||
|
icon: 'icon-speed',
|
||||||
|
selectedIcon: 'icon-speed-filled',
|
||||||
|
defaultIcon: 'icon-speed',
|
||||||
|
access: 'canSeeAdmin',
|
||||||
|
hideInMenu: true,
|
||||||
|
component: './benchmark/details'
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -0,0 +1,142 @@
|
|||||||
|
import { createAxiosToken } from '@/hooks/use-chunk-request';
|
||||||
|
import { useRequest } from 'ahooks';
|
||||||
|
import { message } from 'antd';
|
||||||
|
import { CancelTokenSource } from 'axios';
|
||||||
|
import { useEffect, useRef, useState } from 'react';
|
||||||
|
|
||||||
|
/**
|
||||||
|
|
||||||
|
* generic hook to query data list
|
||||||
|
* @template ListItem
|
||||||
|
* @param option.fetchList: (params, extra) => Promise<{ items: ListItem[] }>
|
||||||
|
* @returns loading, dataList, fetchData, cancelRequest
|
||||||
|
*/
|
||||||
|
export function useQueryDataList<ListItem, Params = any>(option: {
|
||||||
|
key: string;
|
||||||
|
fetchList: (
|
||||||
|
params: Params,
|
||||||
|
options?: any
|
||||||
|
) => Promise<Global.PageResponse<ListItem>>;
|
||||||
|
getLabel?: (item: ListItem) => string;
|
||||||
|
getValue?: (item: ListItem) => any;
|
||||||
|
errorMsg?: string;
|
||||||
|
}) {
|
||||||
|
const { key, fetchList, getLabel, getValue, errorMsg } = option;
|
||||||
|
const axiosTokenRef = useRef<CancelTokenSource | null>(null);
|
||||||
|
const [dataList, setDataList] = useState<
|
||||||
|
Array<ListItem & { label: string; value: any }>
|
||||||
|
>([]);
|
||||||
|
|
||||||
|
const {
|
||||||
|
runAsync: fetchData,
|
||||||
|
loading,
|
||||||
|
cancel
|
||||||
|
} = useRequest(
|
||||||
|
async (params: Params, extra?: any) => {
|
||||||
|
axiosTokenRef.current?.cancel();
|
||||||
|
axiosTokenRef.current = createAxiosToken();
|
||||||
|
const res = await fetchList(params, {
|
||||||
|
token: axiosTokenRef.current?.token,
|
||||||
|
...(extra || {})
|
||||||
|
});
|
||||||
|
|
||||||
|
setDataList(
|
||||||
|
res.items?.map((item: ListItem) => ({
|
||||||
|
...item,
|
||||||
|
label: getLabel ? getLabel(item) : (item as any).name,
|
||||||
|
value: getValue ? getValue(item) : (item as any).id
|
||||||
|
})) || []
|
||||||
|
);
|
||||||
|
|
||||||
|
return res.items || [];
|
||||||
|
},
|
||||||
|
{
|
||||||
|
manual: true,
|
||||||
|
onSuccess: () => {},
|
||||||
|
onError: (error) => {
|
||||||
|
message.error(
|
||||||
|
error?.message || errorMsg || `Failed to fetch ${key} list`
|
||||||
|
);
|
||||||
|
setDataList([]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
const cancelRequest = () => {
|
||||||
|
cancel();
|
||||||
|
axiosTokenRef.current?.cancel();
|
||||||
|
};
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
return () => {
|
||||||
|
cancel();
|
||||||
|
axiosTokenRef.current?.cancel();
|
||||||
|
};
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
return {
|
||||||
|
loading,
|
||||||
|
dataList,
|
||||||
|
cancelRequest,
|
||||||
|
fetchData
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
export function useQueryData<Detail, Params = any>(option: {
|
||||||
|
key: string;
|
||||||
|
fetchDetail: (params: Params, options?: any) => Promise<Detail>;
|
||||||
|
getData?: (response: Detail) => any;
|
||||||
|
errorMsg?: string;
|
||||||
|
}) {
|
||||||
|
const { key, fetchDetail, getData, errorMsg } = option;
|
||||||
|
const axiosTokenRef = useRef<CancelTokenSource | null>(null);
|
||||||
|
const [detailData, setDetailData] = useState<Detail>({} as Detail);
|
||||||
|
|
||||||
|
const {
|
||||||
|
runAsync: fetchData,
|
||||||
|
loading,
|
||||||
|
cancel
|
||||||
|
} = useRequest(
|
||||||
|
async (params: Params, extra?: any) => {
|
||||||
|
axiosTokenRef.current?.cancel();
|
||||||
|
axiosTokenRef.current = createAxiosToken();
|
||||||
|
const res = await fetchDetail(params, {
|
||||||
|
token: axiosTokenRef.current?.token,
|
||||||
|
...(extra || {})
|
||||||
|
});
|
||||||
|
|
||||||
|
setDetailData(getData ? getData(res) : res);
|
||||||
|
|
||||||
|
return res;
|
||||||
|
},
|
||||||
|
{
|
||||||
|
manual: true,
|
||||||
|
onSuccess: () => {},
|
||||||
|
onError: (error) => {
|
||||||
|
message.error(
|
||||||
|
error?.message || errorMsg || `Failed to fetch ${key} list`
|
||||||
|
);
|
||||||
|
setDetailData({} as Detail);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
const cancelRequest = () => {
|
||||||
|
cancel();
|
||||||
|
axiosTokenRef.current?.cancel();
|
||||||
|
};
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
return () => {
|
||||||
|
cancel();
|
||||||
|
axiosTokenRef.current?.cancel();
|
||||||
|
};
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
return {
|
||||||
|
loading,
|
||||||
|
detailData,
|
||||||
|
cancelRequest,
|
||||||
|
fetchData
|
||||||
|
};
|
||||||
|
}
|
||||||
@@ -46,6 +46,7 @@ const NO_CONTAINER_PAGES = [
|
|||||||
'text2images',
|
'text2images',
|
||||||
'clusterDetail',
|
'clusterDetail',
|
||||||
'clusterCreate',
|
'clusterCreate',
|
||||||
|
'benchmarkDetail',
|
||||||
'video'
|
'video'
|
||||||
];
|
];
|
||||||
|
|
||||||
|
|||||||
@@ -10,6 +10,9 @@ const StyledBreadcrumb = styled(Breadcrumb)`
|
|||||||
a {
|
a {
|
||||||
background: unset;
|
background: unset;
|
||||||
}
|
}
|
||||||
|
a:hover {
|
||||||
|
background: unset;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
.ant-breadcrumb-separator {
|
.ant-breadcrumb-separator {
|
||||||
display: flex;
|
display: flex;
|
||||||
|
|||||||
@@ -1,8 +1,9 @@
|
|||||||
import { request } from '@umijs/max';
|
import { request } from '@umijs/max';
|
||||||
import { CancelToken } from 'axios';
|
import { CancelToken } from 'axios';
|
||||||
import { BenchmarkListItem, FormData } from '../config/types';
|
import { BenchmarkMetricsFormData } from '../config/detail-types';
|
||||||
|
import { BenchmarkListItem, DatasetListItem, FormData } from '../config/types';
|
||||||
|
|
||||||
export const BENCHMARKS_API = '/benchmark';
|
export const BENCHMARKS_API = '/benchmarks';
|
||||||
export const DATASETS_API = '/datasets';
|
export const DATASETS_API = '/datasets';
|
||||||
|
|
||||||
export async function queryBenchmarkList(
|
export async function queryBenchmarkList(
|
||||||
@@ -50,6 +51,18 @@ export async function queryBenchmarkLogs(
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export async function queryBenchmarkDetail(
|
||||||
|
id: number,
|
||||||
|
options?: {
|
||||||
|
token?: CancelToken;
|
||||||
|
}
|
||||||
|
) {
|
||||||
|
return request(`${BENCHMARKS_API}/${id}`, {
|
||||||
|
method: 'GET',
|
||||||
|
cancelToken: options?.token
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
export async function createBenchmarkResult(params: { id: number; data: any }) {
|
export async function createBenchmarkResult(params: { id: number; data: any }) {
|
||||||
return request(`${BENCHMARKS_API}/${params.id}/result`, {
|
return request(`${BENCHMARKS_API}/${params.id}/result`, {
|
||||||
method: 'POST',
|
method: 'POST',
|
||||||
@@ -63,9 +76,25 @@ export async function queryDatasetList(
|
|||||||
token?: CancelToken;
|
token?: CancelToken;
|
||||||
}
|
}
|
||||||
) {
|
) {
|
||||||
return request<Global.PageResponse<BenchmarkListItem>>(`${DATASETS_API}`, {
|
return request<Global.PageResponse<DatasetListItem>>(`${DATASETS_API}`, {
|
||||||
method: 'GET',
|
method: 'GET',
|
||||||
params,
|
params,
|
||||||
cancelToken: options?.token
|
cancelToken: options?.token
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export async function queryBenchmarkMetrics(
|
||||||
|
params: {
|
||||||
|
id: number;
|
||||||
|
data: BenchmarkMetricsFormData;
|
||||||
|
},
|
||||||
|
options?: {
|
||||||
|
token?: CancelToken;
|
||||||
|
}
|
||||||
|
) {
|
||||||
|
return request<any>(`${BENCHMARKS_API}/${params.id}/metrics`, {
|
||||||
|
method: 'POST',
|
||||||
|
data: params.data,
|
||||||
|
cancelToken: options?.token
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|||||||
@@ -49,6 +49,7 @@ const AddBenchmark: React.FC<AddModalProps> = ({
|
|||||||
<BenchmarkForm
|
<BenchmarkForm
|
||||||
ref={form}
|
ref={form}
|
||||||
action={action}
|
action={action}
|
||||||
|
open={open}
|
||||||
currentData={currentData}
|
currentData={currentData}
|
||||||
onFinish={handleOk}
|
onFinish={handleOk}
|
||||||
/>
|
/>
|
||||||
|
|||||||
@@ -184,7 +184,7 @@ const CompareConditions: React.FC = () => {
|
|||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<Container>
|
<Container>
|
||||||
<span className="holder">Compare Filter</span>
|
<span className="holder">Export filter</span>
|
||||||
</Container>
|
</Container>
|
||||||
</Popover>
|
</Popover>
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -0,0 +1,89 @@
|
|||||||
|
import IconFont from '@/components/icon-font';
|
||||||
|
import { useQueryClusterList } from '@/pages/cluster-management/services/use-query-cluster-list';
|
||||||
|
import { useIntl, useSearchParams } from '@umijs/max';
|
||||||
|
import { Tabs, TabsProps } from 'antd';
|
||||||
|
import React, { useEffect, useState } from 'react';
|
||||||
|
import DetailContext from '../config/detail-context';
|
||||||
|
import { BenchmarkListItem } from '../config/types';
|
||||||
|
import useQueryDetail from '../services/use-query-detail';
|
||||||
|
import Configure from './configure';
|
||||||
|
import Environment from './environment';
|
||||||
|
import Logs from './logs';
|
||||||
|
import Summary from './summary';
|
||||||
|
|
||||||
|
const Details: React.FC<{ currentData?: BenchmarkListItem }> = ({
|
||||||
|
currentData
|
||||||
|
}) => {
|
||||||
|
const intl = useIntl();
|
||||||
|
const [searchParams] = useSearchParams();
|
||||||
|
const id = searchParams.get('id');
|
||||||
|
const { loading, detailData, cancelRequest, fetchData } = useQueryDetail();
|
||||||
|
const {
|
||||||
|
clusterList,
|
||||||
|
fetchClusterList,
|
||||||
|
cancelRequest: cancelClusterRequest
|
||||||
|
} = useQueryClusterList();
|
||||||
|
const [activeKey, setActiveKey] = useState('summary');
|
||||||
|
|
||||||
|
const items: TabsProps['items'] = [
|
||||||
|
{
|
||||||
|
key: 'summary',
|
||||||
|
label: intl.formatMessage({ id: 'benchmark.detail.summary.title' }),
|
||||||
|
children: <Summary />,
|
||||||
|
icon: <IconFont type="icon-basic" />
|
||||||
|
},
|
||||||
|
{
|
||||||
|
key: 'configure',
|
||||||
|
label: intl.formatMessage({ id: 'benchmark.detail.configure.title' }),
|
||||||
|
children: <Configure />,
|
||||||
|
icon: <IconFont type="icon-settings-02" />
|
||||||
|
},
|
||||||
|
{
|
||||||
|
key: 'environment',
|
||||||
|
label: intl.formatMessage({ id: 'benchmark.detail.environment.title' }),
|
||||||
|
children: <Environment />,
|
||||||
|
icon: <IconFont type="icon-server02" />
|
||||||
|
},
|
||||||
|
{
|
||||||
|
key: 'logs',
|
||||||
|
label: intl.formatMessage({ id: 'benchmark.detail.logs.title' }),
|
||||||
|
children: <Logs />,
|
||||||
|
icon: <IconFont type="icon-logs" />
|
||||||
|
}
|
||||||
|
];
|
||||||
|
|
||||||
|
const handleChangeTab = (key: string) => {
|
||||||
|
setActiveKey(key);
|
||||||
|
};
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (id) {
|
||||||
|
fetchData(id);
|
||||||
|
fetchClusterList({ page: -1 });
|
||||||
|
} else {
|
||||||
|
cancelRequest();
|
||||||
|
cancelClusterRequest();
|
||||||
|
}
|
||||||
|
}, [id]);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<DetailContext.Provider
|
||||||
|
value={{
|
||||||
|
detailData: detailData || {},
|
||||||
|
clusterList: clusterList,
|
||||||
|
loading: loading,
|
||||||
|
id: Number(id)
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<Tabs
|
||||||
|
size="small"
|
||||||
|
activeKey={activeKey}
|
||||||
|
onChange={handleChangeTab}
|
||||||
|
items={items}
|
||||||
|
type="card"
|
||||||
|
/>
|
||||||
|
</DetailContext.Provider>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default Details;
|
||||||
@@ -0,0 +1,29 @@
|
|||||||
|
import FormDrawer from '@/pages/_components/form-drawer';
|
||||||
|
import { BenchmarkListItem } from '../config/types';
|
||||||
|
import DetailContent from './detail-content';
|
||||||
|
|
||||||
|
interface DetailModalProps {
|
||||||
|
open: boolean;
|
||||||
|
currentData?: BenchmarkListItem;
|
||||||
|
onClose: () => void;
|
||||||
|
}
|
||||||
|
|
||||||
|
const DetailModal: React.FC<DetailModalProps> = ({
|
||||||
|
open,
|
||||||
|
currentData,
|
||||||
|
onClose
|
||||||
|
}) => {
|
||||||
|
return (
|
||||||
|
<FormDrawer
|
||||||
|
title={currentData?.name}
|
||||||
|
open={open}
|
||||||
|
onCancel={onClose}
|
||||||
|
width={700}
|
||||||
|
footer={null}
|
||||||
|
>
|
||||||
|
<DetailContent currentData={currentData} />
|
||||||
|
</FormDrawer>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default DetailModal;
|
||||||
@@ -0,0 +1,57 @@
|
|||||||
|
import { convertFileSize } from '@/utils';
|
||||||
|
import { Descriptions, DescriptionsProps } from 'antd';
|
||||||
|
import React from 'react';
|
||||||
|
import { GPUData } from '../../config/detail-types';
|
||||||
|
import Section from '../summary/section';
|
||||||
|
|
||||||
|
const Environment: React.FC<GPUData> = (props) => {
|
||||||
|
const items: DescriptionsProps['items'] = [
|
||||||
|
{
|
||||||
|
key: '4',
|
||||||
|
label: 'Name',
|
||||||
|
children: props.name
|
||||||
|
},
|
||||||
|
{
|
||||||
|
key: '1',
|
||||||
|
label: 'VRAM',
|
||||||
|
children: convertFileSize(props.memory_total)
|
||||||
|
},
|
||||||
|
{
|
||||||
|
key: '3',
|
||||||
|
label: 'Driver Version',
|
||||||
|
children: props.driver_version
|
||||||
|
},
|
||||||
|
{
|
||||||
|
key: '2',
|
||||||
|
label: 'Runtime Version',
|
||||||
|
children: props.runtime_version
|
||||||
|
},
|
||||||
|
{
|
||||||
|
key: '6',
|
||||||
|
label: 'Core',
|
||||||
|
children: props.core_total
|
||||||
|
},
|
||||||
|
{
|
||||||
|
key: '5',
|
||||||
|
label: 'Vendor',
|
||||||
|
children: props.vendor
|
||||||
|
}
|
||||||
|
];
|
||||||
|
return (
|
||||||
|
<Section title={`GPU ${props.index}`}>
|
||||||
|
<Descriptions
|
||||||
|
items={items}
|
||||||
|
colon={false}
|
||||||
|
column={3}
|
||||||
|
layout="vertical"
|
||||||
|
styles={{
|
||||||
|
content: {
|
||||||
|
justifyContent: 'flex-start'
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
></Descriptions>
|
||||||
|
</Section>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default Environment;
|
||||||
@@ -1,9 +1,82 @@
|
|||||||
import React from 'react';
|
import { Tag } from 'antd';
|
||||||
|
import React, { useMemo } from 'react';
|
||||||
|
import styled from 'styled-components';
|
||||||
import { useDetailContext } from '../../config/detail-context';
|
import { useDetailContext } from '../../config/detail-context';
|
||||||
|
import WorkerData from './worker-data';
|
||||||
|
|
||||||
|
const Container = styled.div`
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
gap: 16px;
|
||||||
|
`;
|
||||||
|
|
||||||
const Environment: React.FC = () => {
|
const Environment: React.FC = () => {
|
||||||
const { detailData } = useDetailContext();
|
const { detailData } = useDetailContext();
|
||||||
return <div>Environment Content</div>;
|
const { snapshot } = detailData;
|
||||||
|
|
||||||
|
const mainWorker = useMemo(() => {
|
||||||
|
// get workers data
|
||||||
|
const [[workerName, workerInfo]] = Object.entries(snapshot.workers);
|
||||||
|
const gpuData = Object.values(snapshot.gpus).filter(
|
||||||
|
(gpu) => gpu.worker_name === workerName
|
||||||
|
);
|
||||||
|
|
||||||
|
return {
|
||||||
|
workerData: workerInfo,
|
||||||
|
gpuData: [...gpuData, ...gpuData]
|
||||||
|
};
|
||||||
|
}, [snapshot]);
|
||||||
|
|
||||||
|
const subWorkers = useMemo(() => {
|
||||||
|
const [[mainWorkerName, mainWorkerInfo]] = Object.entries(snapshot.workers);
|
||||||
|
|
||||||
|
const subOrdinaryWorkers = Object.values(snapshot.instances).filter(
|
||||||
|
(instance) => instance.worker_name === mainWorkerName
|
||||||
|
);
|
||||||
|
|
||||||
|
return subOrdinaryWorkers.map((worker) => {
|
||||||
|
const gpuData = Object.values(snapshot.gpus).filter(
|
||||||
|
(gpu) => gpu.worker_name === worker.worker_name
|
||||||
|
);
|
||||||
|
|
||||||
|
return {
|
||||||
|
workerData: worker,
|
||||||
|
gpuData: gpuData
|
||||||
|
};
|
||||||
|
});
|
||||||
|
}, [snapshot, mainWorker]);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Container>
|
||||||
|
<WorkerData
|
||||||
|
workerData={mainWorker.workerData}
|
||||||
|
gpuData={mainWorker.gpuData}
|
||||||
|
title={
|
||||||
|
<div className="flex-center gap-8">
|
||||||
|
<Tag color="geekblue">Main</Tag>
|
||||||
|
<span>{mainWorker?.workerData?.name}</span>
|
||||||
|
</div>
|
||||||
|
}
|
||||||
|
></WorkerData>
|
||||||
|
<WorkerData
|
||||||
|
workerData={mainWorker.workerData}
|
||||||
|
gpuData={mainWorker.gpuData}
|
||||||
|
title={
|
||||||
|
<div className="flex-center gap-8">
|
||||||
|
<Tag color="geekblue">Sub</Tag>
|
||||||
|
<span>{mainWorker?.workerData?.name}</span>
|
||||||
|
</div>
|
||||||
|
}
|
||||||
|
></WorkerData>
|
||||||
|
{/* {subWorkers?.map?.((worker, index) => (
|
||||||
|
<WorkerData
|
||||||
|
key={index}
|
||||||
|
workerData={worker.workerData}
|
||||||
|
gpuData={worker.gpuData}
|
||||||
|
></WorkerData>
|
||||||
|
))} */}
|
||||||
|
</Container>
|
||||||
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
export default Environment;
|
export default Environment;
|
||||||
|
|||||||
@@ -0,0 +1,43 @@
|
|||||||
|
import { convertFileSize } from '@/utils';
|
||||||
|
import { Descriptions, DescriptionsProps } from 'antd';
|
||||||
|
import React from 'react';
|
||||||
|
import { WorkerData } from '../../config/detail-types';
|
||||||
|
|
||||||
|
const Environment: React.FC<WorkerData> = (props) => {
|
||||||
|
const { os, cpu_total, memory_total } = props;
|
||||||
|
|
||||||
|
const items: DescriptionsProps['items'] = [
|
||||||
|
{
|
||||||
|
key: '4',
|
||||||
|
label: 'System',
|
||||||
|
children: os.name
|
||||||
|
},
|
||||||
|
{
|
||||||
|
key: '1',
|
||||||
|
label: 'CPU Count',
|
||||||
|
children: cpu_total
|
||||||
|
},
|
||||||
|
{
|
||||||
|
key: '5',
|
||||||
|
label: 'Memory Total',
|
||||||
|
children: convertFileSize(memory_total)
|
||||||
|
}
|
||||||
|
];
|
||||||
|
return (
|
||||||
|
<div>
|
||||||
|
<Descriptions
|
||||||
|
items={items}
|
||||||
|
colon={false}
|
||||||
|
column={3}
|
||||||
|
layout="vertical"
|
||||||
|
styles={{
|
||||||
|
content: {
|
||||||
|
justifyContent: 'flex-start'
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
></Descriptions>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default Environment;
|
||||||
@@ -0,0 +1,57 @@
|
|||||||
|
import { Col, Row } from 'antd';
|
||||||
|
import React from 'react';
|
||||||
|
import styled from 'styled-components';
|
||||||
|
import { GPUData as GPUDataType, WorkerData } from '../../config/detail-types';
|
||||||
|
import Section from '../summary/section';
|
||||||
|
import GPUData from './gpu-data';
|
||||||
|
import Metadata from './metadata';
|
||||||
|
|
||||||
|
const Container = styled.div`
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
border-radius: var(--ant-border-radius);
|
||||||
|
border: 1px solid var(--ant-color-border);
|
||||||
|
`;
|
||||||
|
|
||||||
|
const Content = styled.div`
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
gap: 16px;
|
||||||
|
padding: 16px 16px;
|
||||||
|
`;
|
||||||
|
|
||||||
|
const Environment: React.FC<{
|
||||||
|
workerData: WorkerData;
|
||||||
|
gpuData: GPUDataType[];
|
||||||
|
title?: React.ReactNode;
|
||||||
|
}> = (props) => {
|
||||||
|
const { title } = props;
|
||||||
|
return (
|
||||||
|
<Container>
|
||||||
|
<Section
|
||||||
|
title={title}
|
||||||
|
styles={{
|
||||||
|
container: {
|
||||||
|
border: 'none',
|
||||||
|
paddingInline: 16,
|
||||||
|
borderRadius: 0,
|
||||||
|
borderBottom: '1px solid var(--ant-color-split)'
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<Metadata {...props.workerData} />
|
||||||
|
</Section>
|
||||||
|
<Content>
|
||||||
|
<Row gutter={16}>
|
||||||
|
{props.gpuData.map((gpu, index) => (
|
||||||
|
<Col span={12} key={index}>
|
||||||
|
<GPUData {...gpu} />
|
||||||
|
</Col>
|
||||||
|
))}
|
||||||
|
</Row>
|
||||||
|
</Content>
|
||||||
|
</Container>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default Environment;
|
||||||
@@ -2,7 +2,6 @@ import { SearchOutlined, SyncOutlined } from '@ant-design/icons';
|
|||||||
import { useIntl } from '@umijs/max';
|
import { useIntl } from '@umijs/max';
|
||||||
import { Button, Input, Space } from 'antd';
|
import { Button, Input, Space } from 'antd';
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
import CompareConditions from './compare-conditions';
|
|
||||||
|
|
||||||
export interface RightActionsProps {
|
export interface RightActionsProps {
|
||||||
handleInputChange: (e: React.ChangeEvent<HTMLInputElement>) => void;
|
handleInputChange: (e: React.ChangeEvent<HTMLInputElement>) => void;
|
||||||
@@ -30,7 +29,7 @@ const RightActions: React.FC<RightActionsProps> = ({
|
|||||||
allowClear
|
allowClear
|
||||||
onChange={handleInputChange}
|
onChange={handleInputChange}
|
||||||
></Input>
|
></Input>
|
||||||
<CompareConditions></CompareConditions>
|
{/* <CompareConditions></CompareConditions> */}
|
||||||
<Button
|
<Button
|
||||||
type="text"
|
type="text"
|
||||||
style={{ color: 'var(--ant-color-text-tertiary)' }}
|
style={{ color: 'var(--ant-color-text-tertiary)' }}
|
||||||
|
|||||||
@@ -1,9 +1,26 @@
|
|||||||
import React from 'react';
|
import LogsViewer from '@/components/logs-viewer/virtual-log-list';
|
||||||
|
import React, { useRef } from 'react';
|
||||||
|
import { BENCHMARKS_API } from '../../apis';
|
||||||
import { useDetailContext } from '../../config/detail-context';
|
import { useDetailContext } from '../../config/detail-context';
|
||||||
|
|
||||||
const Logs: React.FC = () => {
|
const Logs: React.FC = () => {
|
||||||
const { id } = useDetailContext();
|
const { id } = useDetailContext();
|
||||||
return <div>Logs Content</div>;
|
const logsViewerRef = useRef<any>(null);
|
||||||
|
return (
|
||||||
|
<div>
|
||||||
|
<LogsViewer
|
||||||
|
ref={logsViewerRef}
|
||||||
|
diffHeight={175}
|
||||||
|
url={`${BENCHMARKS_API}/${id}/logs`}
|
||||||
|
tail={undefined}
|
||||||
|
enableScorllLoad={true}
|
||||||
|
isDownloading={false}
|
||||||
|
params={{
|
||||||
|
follow: true
|
||||||
|
}}
|
||||||
|
></LogsViewer>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
export default Logs;
|
export default Logs;
|
||||||
|
|||||||
@@ -1,7 +1,8 @@
|
|||||||
|
import DropdownButtons from '@/components/drop-down-buttons';
|
||||||
import {
|
import {
|
||||||
DeleteOutlined,
|
DeleteOutlined,
|
||||||
PlusOutlined,
|
DownloadOutlined,
|
||||||
SettingOutlined
|
PlusOutlined
|
||||||
} from '@ant-design/icons';
|
} from '@ant-design/icons';
|
||||||
import { useIntl } from '@umijs/max';
|
import { useIntl } from '@umijs/max';
|
||||||
import { Button, Space } from 'antd';
|
import { Button, Space } from 'antd';
|
||||||
@@ -11,6 +12,7 @@ export interface RightActionsProps {
|
|||||||
handleDeleteByBatch: () => void;
|
handleDeleteByBatch: () => void;
|
||||||
handleClickPrimary?: () => void;
|
handleClickPrimary?: () => void;
|
||||||
handleSettingFields?: () => void;
|
handleSettingFields?: () => void;
|
||||||
|
settingButton?: React.ReactNode;
|
||||||
buttonText?: string;
|
buttonText?: string;
|
||||||
rowSelection: {
|
rowSelection: {
|
||||||
selectedRowKeys: React.Key[];
|
selectedRowKeys: React.Key[];
|
||||||
@@ -21,14 +23,38 @@ const RightActions: React.FC<RightActionsProps> = ({
|
|||||||
handleDeleteByBatch,
|
handleDeleteByBatch,
|
||||||
handleClickPrimary,
|
handleClickPrimary,
|
||||||
handleSettingFields,
|
handleSettingFields,
|
||||||
|
settingButton,
|
||||||
buttonText,
|
buttonText,
|
||||||
rowSelection
|
rowSelection
|
||||||
}) => {
|
}) => {
|
||||||
const intl = useIntl();
|
const intl = useIntl();
|
||||||
|
const ButtonList = [
|
||||||
|
{
|
||||||
|
label: 'common.button.export',
|
||||||
|
key: 'export',
|
||||||
|
icon: <DownloadOutlined />
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: 'common.button.delete',
|
||||||
|
key: 'start',
|
||||||
|
props: {
|
||||||
|
danger: true
|
||||||
|
},
|
||||||
|
icon: <DeleteOutlined />
|
||||||
|
}
|
||||||
|
];
|
||||||
|
|
||||||
|
const handleActionSelect = (val: string) => {};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Space size={16}>
|
<Space size={16}>
|
||||||
<Button onClick={handleSettingFields} icon={<SettingOutlined />}></Button>
|
{/* <Tooltip title="Column Settings">
|
||||||
|
<Button
|
||||||
|
onClick={handleSettingFields}
|
||||||
|
icon={<SettingOutlined />}
|
||||||
|
></Button>
|
||||||
|
</Tooltip> */}
|
||||||
|
{settingButton}
|
||||||
<Button
|
<Button
|
||||||
icon={<PlusOutlined></PlusOutlined>}
|
icon={<PlusOutlined></PlusOutlined>}
|
||||||
type="primary"
|
type="primary"
|
||||||
@@ -36,7 +62,7 @@ const RightActions: React.FC<RightActionsProps> = ({
|
|||||||
>
|
>
|
||||||
{buttonText}
|
{buttonText}
|
||||||
</Button>
|
</Button>
|
||||||
<Button
|
{/* <Button
|
||||||
icon={<DeleteOutlined />}
|
icon={<DeleteOutlined />}
|
||||||
danger
|
danger
|
||||||
onClick={handleDeleteByBatch}
|
onClick={handleDeleteByBatch}
|
||||||
@@ -48,7 +74,19 @@ const RightActions: React.FC<RightActionsProps> = ({
|
|||||||
<span>({rowSelection?.selectedRowKeys?.length})</span>
|
<span>({rowSelection?.selectedRowKeys?.length})</span>
|
||||||
)}
|
)}
|
||||||
</span>
|
</span>
|
||||||
</Button>
|
</Button> */}
|
||||||
|
<DropdownButtons
|
||||||
|
items={ButtonList}
|
||||||
|
extra={
|
||||||
|
rowSelection.selectedRowKeys.length > 0 && (
|
||||||
|
<span>({rowSelection.selectedRowKeys.length})</span>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
size="large"
|
||||||
|
showText={true}
|
||||||
|
disabled={!rowSelection.selectedRowKeys.length}
|
||||||
|
onSelect={handleActionSelect}
|
||||||
|
/>
|
||||||
</Space>
|
</Space>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -0,0 +1,244 @@
|
|||||||
|
import AutoTooltip from '@/components/auto-tooltip';
|
||||||
|
import StatusTag from '@/components/status-tag';
|
||||||
|
import { useIntl } from '@umijs/max';
|
||||||
|
import { Col, Descriptions, DescriptionsProps, Row, Statistic } from 'antd';
|
||||||
|
import dayjs from 'dayjs';
|
||||||
|
import { round } from 'lodash';
|
||||||
|
import React from 'react';
|
||||||
|
import styled from 'styled-components';
|
||||||
|
import { BenchmarkStatus, BenchmarkStatusLabelMap } from '../../config';
|
||||||
|
import { useDetailContext } from '../../config/detail-context';
|
||||||
|
import PercentileResult from './percentile-result';
|
||||||
|
import Section from './section';
|
||||||
|
|
||||||
|
const Container = styled.div`
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
gap: 16px;
|
||||||
|
`;
|
||||||
|
|
||||||
|
const Card = styled.div`
|
||||||
|
height: 78px;
|
||||||
|
padding: 12px 16px;
|
||||||
|
border: 1px solid var(--ant-color-border);
|
||||||
|
border-radius: var(--ant-border-radius);
|
||||||
|
background-color: var(--ant-color-bg-container);
|
||||||
|
`;
|
||||||
|
|
||||||
|
const Box = styled.div`
|
||||||
|
display: grid;
|
||||||
|
grid-template-columns: 1fr 1fr;
|
||||||
|
gap: 16px;
|
||||||
|
`;
|
||||||
|
|
||||||
|
const Summary: React.FC = () => {
|
||||||
|
const { detailData, clusterList } = useDetailContext();
|
||||||
|
const intl = useIntl();
|
||||||
|
|
||||||
|
const items: DescriptionsProps['items'] = [
|
||||||
|
{
|
||||||
|
key: '1',
|
||||||
|
label: 'Model',
|
||||||
|
children: <AutoTooltip ghost>{detailData.model_name}</AutoTooltip>
|
||||||
|
},
|
||||||
|
{
|
||||||
|
key: '2',
|
||||||
|
label: 'Cluster',
|
||||||
|
children: (
|
||||||
|
<AutoTooltip ghost>
|
||||||
|
{clusterList?.find((item) => item.value === detailData.cluster_id)
|
||||||
|
?.label || detailData.cluster_id}
|
||||||
|
</AutoTooltip>
|
||||||
|
)
|
||||||
|
},
|
||||||
|
{
|
||||||
|
key: '4',
|
||||||
|
label: 'Dataset',
|
||||||
|
children: detailData.dataset_name
|
||||||
|
},
|
||||||
|
{
|
||||||
|
key: '5',
|
||||||
|
label: 'Concurrency',
|
||||||
|
children: detailData.concurrency
|
||||||
|
},
|
||||||
|
{
|
||||||
|
key: '5',
|
||||||
|
label: 'Duration (s)',
|
||||||
|
children: detailData.duration
|
||||||
|
},
|
||||||
|
{
|
||||||
|
key: '3',
|
||||||
|
label: 'Update Time',
|
||||||
|
children: dayjs(detailData.updated_at).format('YYYY-MM-DD HH:mm:ss')
|
||||||
|
}
|
||||||
|
];
|
||||||
|
const cardFields = [
|
||||||
|
{
|
||||||
|
label: 'Total Requests',
|
||||||
|
key: 'total_requests',
|
||||||
|
value: detailData.total_requests,
|
||||||
|
unit: ''
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: 'Success Rate',
|
||||||
|
key: 'success_rate',
|
||||||
|
value: detailData.success_rate,
|
||||||
|
color: 'var(--ant-color-success)',
|
||||||
|
unit: '%'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: 'TPS',
|
||||||
|
key: 'tps',
|
||||||
|
value: detailData.tokens_per_second_mean,
|
||||||
|
unit: 't/s'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: 'RPS',
|
||||||
|
key: 'rps',
|
||||||
|
value: detailData.requests_per_second_mean,
|
||||||
|
unit: 'req/s'
|
||||||
|
},
|
||||||
|
// {
|
||||||
|
// label: 'ITL',
|
||||||
|
// key: 'itl',
|
||||||
|
// value: detailData.inter_token_latency_mean,
|
||||||
|
// unit: 'ms'
|
||||||
|
// },
|
||||||
|
{
|
||||||
|
label: 'TPOT',
|
||||||
|
key: 'time_per_output_token_mean',
|
||||||
|
value: detailData.time_per_output_token_mean,
|
||||||
|
unit: 'ms'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: 'TTFT',
|
||||||
|
key: 'time_to_first_token_mean',
|
||||||
|
value: detailData.time_to_first_token_mean,
|
||||||
|
unit: 'ms'
|
||||||
|
}
|
||||||
|
];
|
||||||
|
|
||||||
|
const throughputFields = [
|
||||||
|
{
|
||||||
|
key: '3',
|
||||||
|
label: 'Total throughput',
|
||||||
|
children: `${round(detailData.total_through || 0, 2)} t/s`
|
||||||
|
},
|
||||||
|
{
|
||||||
|
key: '1',
|
||||||
|
label: 'Request throughput',
|
||||||
|
children: `${round(detailData.request_through || 0, 2)} t/s`
|
||||||
|
},
|
||||||
|
{
|
||||||
|
key: '2',
|
||||||
|
label: 'Output throughput',
|
||||||
|
children: `${round(detailData.output_through || 0, 2)} t/s`
|
||||||
|
}
|
||||||
|
];
|
||||||
|
|
||||||
|
const latencyFields = [
|
||||||
|
{
|
||||||
|
key: '1',
|
||||||
|
label: 'Request Latency',
|
||||||
|
children: `${round(detailData.request_latency_mean, 2)} t/s`
|
||||||
|
},
|
||||||
|
{
|
||||||
|
key: '2',
|
||||||
|
label: 'Avg Time to First Token',
|
||||||
|
children: `${round(detailData.time_to_first_token_mean, 2)} t/s`
|
||||||
|
},
|
||||||
|
{
|
||||||
|
key: '3',
|
||||||
|
label: 'Avg Time Per Output Token',
|
||||||
|
children: `${round(detailData.time_per_output_token_mean, 2)} t/s`
|
||||||
|
}
|
||||||
|
];
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Container>
|
||||||
|
<Row gutter={16}>
|
||||||
|
{cardFields.map((field) => (
|
||||||
|
<Col span={8} key={field.key} style={{ padding: '8px' }}>
|
||||||
|
<Card>
|
||||||
|
<Statistic
|
||||||
|
title={field.label}
|
||||||
|
value={field.value}
|
||||||
|
precision={2}
|
||||||
|
styles={{
|
||||||
|
content: {
|
||||||
|
color: field.color,
|
||||||
|
fontSize: 16,
|
||||||
|
fontWeight: 500
|
||||||
|
},
|
||||||
|
header: {
|
||||||
|
paddingBottom: 4
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
suffix={field.unit}
|
||||||
|
/>
|
||||||
|
</Card>
|
||||||
|
</Col>
|
||||||
|
))}
|
||||||
|
</Row>
|
||||||
|
<Section>
|
||||||
|
<div className="flex-center section-title gap-8">
|
||||||
|
<span>{detailData.model_instance_name}</span>
|
||||||
|
<StatusTag
|
||||||
|
statusValue={{
|
||||||
|
status: BenchmarkStatus[detailData.state],
|
||||||
|
text: BenchmarkStatusLabelMap[detailData.state],
|
||||||
|
message: detailData.state_message || undefined
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<Descriptions
|
||||||
|
items={items}
|
||||||
|
colon={false}
|
||||||
|
column={3}
|
||||||
|
layout="vertical"
|
||||||
|
styles={{
|
||||||
|
content: {
|
||||||
|
justifyContent: 'flex-start'
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
></Descriptions>
|
||||||
|
</Section>
|
||||||
|
|
||||||
|
<Box>
|
||||||
|
<Section>
|
||||||
|
<div className="flex-center section-title gap-8">
|
||||||
|
<span>Throughput</span>
|
||||||
|
</div>
|
||||||
|
<Descriptions
|
||||||
|
items={throughputFields}
|
||||||
|
colon={true}
|
||||||
|
column={1}
|
||||||
|
styles={{
|
||||||
|
content: {
|
||||||
|
justifyContent: 'flex-end'
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
></Descriptions>
|
||||||
|
</Section>
|
||||||
|
<Section>
|
||||||
|
<div className="flex-center section-title gap-8">
|
||||||
|
<span>Latency</span>
|
||||||
|
</div>
|
||||||
|
<Descriptions
|
||||||
|
items={latencyFields}
|
||||||
|
colon={true}
|
||||||
|
column={1}
|
||||||
|
styles={{
|
||||||
|
content: {
|
||||||
|
justifyContent: 'flex-end'
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
></Descriptions>
|
||||||
|
</Section>
|
||||||
|
</Box>
|
||||||
|
<PercentileResult />
|
||||||
|
</Container>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default Summary;
|
||||||
@@ -1,9 +1,21 @@
|
|||||||
import React from 'react';
|
import React from 'react';
|
||||||
import { useDetailContext } from '../../config/detail-context';
|
import styled from 'styled-components';
|
||||||
|
import BasicInfo from './basic';
|
||||||
|
import MetricsInfo from './metrics';
|
||||||
|
|
||||||
|
const Container = styled.div`
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
gap: 24px;
|
||||||
|
`;
|
||||||
|
|
||||||
const Summary: React.FC = () => {
|
const Summary: React.FC = () => {
|
||||||
const { detailData } = useDetailContext();
|
return (
|
||||||
return <div>Summary Content</div>;
|
<Container>
|
||||||
|
<BasicInfo />
|
||||||
|
<MetricsInfo />
|
||||||
|
</Container>
|
||||||
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
export default Summary;
|
export default Summary;
|
||||||
|
|||||||
@@ -0,0 +1,40 @@
|
|||||||
|
import _ from 'lodash';
|
||||||
|
import React, { useEffect } from 'react';
|
||||||
|
import { useDetailContext } from '../../config/detail-context';
|
||||||
|
import useQueryMtrics from '../../services/use-query-metrics';
|
||||||
|
|
||||||
|
const fields = [
|
||||||
|
'requests_per_second_mean',
|
||||||
|
'request_latency_mean',
|
||||||
|
'time_per_output_token_mean',
|
||||||
|
'inter_token_latency_mean',
|
||||||
|
'time_to_first_token_mean',
|
||||||
|
'tokens_per_second_mean',
|
||||||
|
'output_tokens_per_second_mean',
|
||||||
|
'prompt_tokens_per_second_mean'
|
||||||
|
];
|
||||||
|
|
||||||
|
const Summary: React.FC = () => {
|
||||||
|
const { detailData, id } = useDetailContext();
|
||||||
|
const {
|
||||||
|
detailData: metricsData,
|
||||||
|
fetchData,
|
||||||
|
cancelRequest
|
||||||
|
} = useQueryMtrics();
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (id) {
|
||||||
|
fetchData({
|
||||||
|
id: id,
|
||||||
|
data: {
|
||||||
|
..._.pick(detailData, fields)
|
||||||
|
}
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
cancelRequest();
|
||||||
|
}
|
||||||
|
}, [id]);
|
||||||
|
return <div></div>;
|
||||||
|
};
|
||||||
|
|
||||||
|
export default Summary;
|
||||||
@@ -0,0 +1,119 @@
|
|||||||
|
import { Table } from 'antd';
|
||||||
|
import React from 'react';
|
||||||
|
import { useDetailContext } from '../../config/detail-context';
|
||||||
|
import Section from './section';
|
||||||
|
|
||||||
|
// Define table columns: Percentile、 TTFT (ms) 、 ITL (ms) 、TPOT (ms)、Latency (ms)、Input Tokens (t/s)、 Output Tokens (t/s)、 Output (t/s)、Total (t/s)
|
||||||
|
|
||||||
|
const columns = [
|
||||||
|
{
|
||||||
|
title: 'Percentile',
|
||||||
|
dataIndex: 'percentile',
|
||||||
|
key: 'percentile'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: 'TTFT (ms)',
|
||||||
|
dataIndex: 'ttft',
|
||||||
|
key: 'ttft'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: 'ITL (ms)',
|
||||||
|
dataIndex: 'itl',
|
||||||
|
key: 'itl'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: 'TPOT (ms)',
|
||||||
|
dataIndex: 'tpot',
|
||||||
|
key: 'tpot'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: 'Latency (ms)',
|
||||||
|
dataIndex: 'latency',
|
||||||
|
key: 'latency'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: 'Input Tokens (t/s)',
|
||||||
|
dataIndex: 'inputTokens',
|
||||||
|
key: 'inputTokens'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: 'Output Tokens (t/s)',
|
||||||
|
dataIndex: 'outputTokens',
|
||||||
|
key: 'outputTokens'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: 'Output (t/s)',
|
||||||
|
dataIndex: 'output',
|
||||||
|
key: 'output'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: 'Total (t/s)',
|
||||||
|
dataIndex: 'total',
|
||||||
|
key: 'total'
|
||||||
|
}
|
||||||
|
];
|
||||||
|
|
||||||
|
// mock data example: three rows of percentiles: 50th, 90th, 99th
|
||||||
|
const data = [
|
||||||
|
{
|
||||||
|
key: '1',
|
||||||
|
percentile: '50%',
|
||||||
|
ttft: 100,
|
||||||
|
itl: 50,
|
||||||
|
tpot: 20,
|
||||||
|
latency: 200,
|
||||||
|
inputTokens: 300,
|
||||||
|
outputTokens: 400,
|
||||||
|
output: 500,
|
||||||
|
total: 600
|
||||||
|
},
|
||||||
|
{
|
||||||
|
key: '2',
|
||||||
|
percentile: '90%',
|
||||||
|
ttft: 150,
|
||||||
|
itl: 70,
|
||||||
|
tpot: 30,
|
||||||
|
latency: 250,
|
||||||
|
inputTokens: 350,
|
||||||
|
outputTokens: 450,
|
||||||
|
output: 550,
|
||||||
|
total: 650
|
||||||
|
},
|
||||||
|
{
|
||||||
|
key: '3',
|
||||||
|
percentile: '99%',
|
||||||
|
ttft: 200,
|
||||||
|
itl: 90,
|
||||||
|
tpot: 40,
|
||||||
|
latency: 300,
|
||||||
|
inputTokens: 400,
|
||||||
|
outputTokens: 500,
|
||||||
|
output: 600,
|
||||||
|
total: 700
|
||||||
|
}
|
||||||
|
];
|
||||||
|
|
||||||
|
const PercentileResult: React.FC = () => {
|
||||||
|
const { detailData, id } = useDetailContext();
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Section title="Percentiles Result">
|
||||||
|
<Table
|
||||||
|
size="small"
|
||||||
|
columns={columns}
|
||||||
|
dataSource={data}
|
||||||
|
rowKey="percentile"
|
||||||
|
pagination={false}
|
||||||
|
styles={{
|
||||||
|
body: {
|
||||||
|
cell: {
|
||||||
|
height: 54
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
></Table>
|
||||||
|
</Section>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default PercentileResult;
|
||||||
@@ -0,0 +1,42 @@
|
|||||||
|
import styled from 'styled-components';
|
||||||
|
|
||||||
|
const Container = styled.div`
|
||||||
|
padding: 16px;
|
||||||
|
border: 1px solid var(--ant-color-border);
|
||||||
|
border-radius: 4px;
|
||||||
|
.section-title {
|
||||||
|
font-weight: 500;
|
||||||
|
margin-bottom: 12px;
|
||||||
|
font-size: 14px;
|
||||||
|
}
|
||||||
|
th.ant-descriptions-item {
|
||||||
|
padding-bottom: 0;
|
||||||
|
}
|
||||||
|
`;
|
||||||
|
|
||||||
|
const Title = styled.div`
|
||||||
|
display: flex;
|
||||||
|
font-weight: 500;
|
||||||
|
margin-bottom: 12px;
|
||||||
|
font-size: 14px;
|
||||||
|
gap: 8px;
|
||||||
|
align-items: center;
|
||||||
|
`;
|
||||||
|
|
||||||
|
const DetailSection: React.FC<{
|
||||||
|
children: React.ReactNode;
|
||||||
|
title?: React.ReactNode;
|
||||||
|
styles?: {
|
||||||
|
container?: React.CSSProperties;
|
||||||
|
title?: React.CSSProperties;
|
||||||
|
};
|
||||||
|
}> = ({ children, title, styles }) => {
|
||||||
|
return (
|
||||||
|
<Container style={styles?.container}>
|
||||||
|
{title && <Title style={styles?.title}>{title}</Title>}
|
||||||
|
{children}
|
||||||
|
</Container>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default DetailSection;
|
||||||
@@ -1,7 +1,9 @@
|
|||||||
import { createContext, useContext } from 'react';
|
import { createContext, useContext } from 'react';
|
||||||
|
import { BenchmarkDetail } from './detail-types';
|
||||||
|
|
||||||
interface DetailContextProps {
|
interface DetailContextProps {
|
||||||
detailData: any;
|
detailData: BenchmarkDetail;
|
||||||
|
clusterList?: Global.BaseOption<number>[];
|
||||||
id: number;
|
id: number;
|
||||||
loading?: boolean;
|
loading?: boolean;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,117 @@
|
|||||||
|
export interface ComputedResourceClaim {
|
||||||
|
is_unified_memory: boolean;
|
||||||
|
offload_layers: any;
|
||||||
|
total_layers: any;
|
||||||
|
ram: any;
|
||||||
|
vram: Record<string, number>;
|
||||||
|
tensor_split: any;
|
||||||
|
vram_utilization: any;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface InstancesData {
|
||||||
|
computed_resource_claim: ComputedResourceClaim;
|
||||||
|
ports: number[];
|
||||||
|
worker_id: number;
|
||||||
|
worker_name: string;
|
||||||
|
worker_ip: string;
|
||||||
|
gpu_type: string;
|
||||||
|
gpu_indexes: number[];
|
||||||
|
gpu_ids: string[];
|
||||||
|
id: number;
|
||||||
|
name: string;
|
||||||
|
resolved_path: string;
|
||||||
|
state: string;
|
||||||
|
state_message: string;
|
||||||
|
backend: any;
|
||||||
|
backend_version: any;
|
||||||
|
api_detected_backend_version: any;
|
||||||
|
backend_parameters: any;
|
||||||
|
image_name: any;
|
||||||
|
run_command: any;
|
||||||
|
env: any;
|
||||||
|
extended_kv_cache: any;
|
||||||
|
speculative_config: any;
|
||||||
|
subordinate_workers: any;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface WorkerData {
|
||||||
|
id: number;
|
||||||
|
name: string;
|
||||||
|
cpu_total: number;
|
||||||
|
memory_total: number;
|
||||||
|
os: {
|
||||||
|
name: string;
|
||||||
|
version: string;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface GPUData {
|
||||||
|
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 BenchmarkDetail {
|
||||||
|
requests_per_second_mean: number;
|
||||||
|
request_latency_mean: number;
|
||||||
|
time_per_output_token_mean: number;
|
||||||
|
inter_token_latency_mean: number;
|
||||||
|
time_to_first_token_mean: number;
|
||||||
|
tokens_per_second_mean: number;
|
||||||
|
output_tokens_per_second_mean: number;
|
||||||
|
prompt_tokens_per_second_mean: number;
|
||||||
|
name: string;
|
||||||
|
description: string;
|
||||||
|
labels: Record<string, any>;
|
||||||
|
dataset_id: number;
|
||||||
|
dataset_name: string;
|
||||||
|
dataset_source: string;
|
||||||
|
dataset_prompt_tokens: number;
|
||||||
|
dataset_output_tokens: number;
|
||||||
|
cluster_id: number;
|
||||||
|
model_id: number;
|
||||||
|
model_name: string;
|
||||||
|
model_instance_name: string;
|
||||||
|
request_rate: number;
|
||||||
|
total_requests: number;
|
||||||
|
state: string;
|
||||||
|
state_message: any;
|
||||||
|
progress: any;
|
||||||
|
worker_id: number;
|
||||||
|
pid: number;
|
||||||
|
snapshot: {
|
||||||
|
instances: Record<string, InstancesData>;
|
||||||
|
workers: Record<string, WorkerData>;
|
||||||
|
gpus: Record<string, GPUData>;
|
||||||
|
};
|
||||||
|
gpu_summary: string;
|
||||||
|
gpu_vendor_summary: string;
|
||||||
|
id: number;
|
||||||
|
created_at: string;
|
||||||
|
updated_at: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface BenchmarkMetricsFormData {
|
||||||
|
requests_per_second_mean: number;
|
||||||
|
request_latency_mean: number;
|
||||||
|
time_per_output_token_mean: number;
|
||||||
|
inter_token_latency_mean: number;
|
||||||
|
time_to_first_token_mean: number;
|
||||||
|
tokens_per_second_mean: number;
|
||||||
|
output_tokens_per_second_mean: number;
|
||||||
|
prompt_tokens_per_second_mean: number;
|
||||||
|
raw_metrics: Record<string, any>;
|
||||||
|
}
|
||||||
@@ -3,9 +3,9 @@ import { createContext, useContext } from 'react';
|
|||||||
|
|
||||||
interface FormContextProps {
|
interface FormContextProps {
|
||||||
action: PageActionType;
|
action: PageActionType;
|
||||||
clusterList: Global.BaseOption<number>[];
|
open?: boolean;
|
||||||
modelList: Global.BaseOption<number>[];
|
clusterList?: Global.BaseOption<number>[];
|
||||||
modelInstanceList: Global.BaseOption<number>[];
|
modelList?: Global.BaseOption<number>[];
|
||||||
}
|
}
|
||||||
|
|
||||||
const FormContext = createContext<FormContextProps>({} as FormContextProps);
|
const FormContext = createContext<FormContextProps>({} as FormContextProps);
|
||||||
|
|||||||
@@ -67,6 +67,7 @@ export interface GPUSnapshot {
|
|||||||
|
|
||||||
export interface FormData {
|
export interface FormData {
|
||||||
name: string;
|
name: string;
|
||||||
|
profile: string;
|
||||||
description: string;
|
description: string;
|
||||||
labels: Record<string, string>;
|
labels: Record<string, string>;
|
||||||
cluster_id: number;
|
cluster_id: number;
|
||||||
|
|||||||
@@ -1,55 +1,32 @@
|
|||||||
import { useIntl } from '@umijs/max';
|
import { useIntl, useNavigate, useSearchParams } from '@umijs/max';
|
||||||
import { Tabs, TabsProps } from 'antd';
|
import React from 'react';
|
||||||
import React, { useState } from 'react';
|
import { PageContainerInner } from '../_components/page-box';
|
||||||
import PageBox from '../_components/page-box';
|
import PageBreadcrumb from '../_components/page-breadcrumb';
|
||||||
import Configure from './components/configure';
|
import DetailContent from './components/detail-content';
|
||||||
import Environment from './components/environment';
|
|
||||||
import Logs from './components/logs';
|
|
||||||
import Summary from './components/summary';
|
|
||||||
import DetailContext from './config/detail-context';
|
|
||||||
|
|
||||||
const Details: React.FC = () => {
|
const Details: React.FC = () => {
|
||||||
|
const navigate = useNavigate();
|
||||||
const intl = useIntl();
|
const intl = useIntl();
|
||||||
const [activeKey, setActiveKey] = useState('summary');
|
const [searchParams] = useSearchParams();
|
||||||
|
const name = searchParams.get('name');
|
||||||
|
|
||||||
const items: TabsProps['items'] = [
|
const breadcrumbItems = [
|
||||||
{
|
{
|
||||||
key: 'summary',
|
title: <a>{intl.formatMessage({ id: 'benchmark.title' })}</a>,
|
||||||
label: intl.formatMessage({ id: 'benchmark.detail.summary.title' }),
|
onClick: () => navigate(-1)
|
||||||
children: <Summary />
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
key: 'configure',
|
title: name
|
||||||
label: intl.formatMessage({ id: 'benchmark.detail.configure.title' }),
|
|
||||||
children: <Configure />
|
|
||||||
},
|
|
||||||
{
|
|
||||||
key: 'environment',
|
|
||||||
label: intl.formatMessage({ id: 'benchmark.detail.environment.title' }),
|
|
||||||
children: <Environment />
|
|
||||||
},
|
|
||||||
{
|
|
||||||
key: 'logs',
|
|
||||||
label: intl.formatMessage({ id: 'benchmark.detail.logs.title' }),
|
|
||||||
children: <Logs />
|
|
||||||
}
|
}
|
||||||
];
|
];
|
||||||
|
|
||||||
const handleChangeTab = (key: string) => {
|
|
||||||
setActiveKey(key);
|
|
||||||
};
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<PageBox>
|
<PageContainerInner
|
||||||
<DetailContext.Provider value={{}}>
|
header={{
|
||||||
<Tabs
|
title: <PageBreadcrumb items={breadcrumbItems} />
|
||||||
activeKey={activeKey}
|
}}
|
||||||
onChange={handleChangeTab}
|
>
|
||||||
items={items}
|
<DetailContent></DetailContent>
|
||||||
type="card"
|
</PageContainerInner>
|
||||||
/>
|
|
||||||
</DetailContext.Provider>
|
|
||||||
</PageBox>
|
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -1,11 +1,16 @@
|
|||||||
import LabelSelector from '@/components/label-selector';
|
import LabelSelector from '@/components/label-selector';
|
||||||
import SealInput from '@/components/seal-form/seal-input';
|
import SealInput from '@/components/seal-form/seal-input';
|
||||||
import SealSelect from '@/components/seal-form/seal-select';
|
import SealSelect from '@/components/seal-form/seal-select';
|
||||||
|
import { PageAction } from '@/config';
|
||||||
import useAppUtils from '@/hooks/use-app-utils';
|
import useAppUtils from '@/hooks/use-app-utils';
|
||||||
|
import { ClusterStatusValueMap } from '@/pages/cluster-management/config';
|
||||||
|
import { useQueryClusterList } from '@/pages/cluster-management/services/use-query-cluster-list';
|
||||||
|
import { useQueryModelInstancesList } from '@/pages/llmodels/services/use-query-model-instances';
|
||||||
|
import { useQueryModelList } from '@/pages/llmodels/services/use-query-model-list';
|
||||||
import { useIntl } from '@umijs/max';
|
import { useIntl } from '@umijs/max';
|
||||||
import { Form } from 'antd';
|
import { Form } from 'antd';
|
||||||
import _ from 'lodash';
|
import _ from 'lodash';
|
||||||
import React from 'react';
|
import React, { useEffect } from 'react';
|
||||||
import { useFormContext } from '../config/form-context';
|
import { useFormContext } from '../config/form-context';
|
||||||
import { FormData } from '../config/types';
|
import { FormData } from '../config/types';
|
||||||
|
|
||||||
@@ -14,12 +19,81 @@ const BasicForm: React.FC = () => {
|
|||||||
const form = Form.useFormInstance();
|
const form = Form.useFormInstance();
|
||||||
const labels = Form.useWatch('labels', form);
|
const labels = Form.useWatch('labels', form);
|
||||||
const { getRuleMessage } = useAppUtils();
|
const { getRuleMessage } = useAppUtils();
|
||||||
const { modelList, modelInstanceList, clusterList } = useFormContext();
|
const { action, open } = useFormContext();
|
||||||
|
const {
|
||||||
|
loading: clusterLoading,
|
||||||
|
fetchClusterList,
|
||||||
|
cancelRequest: cancelClusterRequest,
|
||||||
|
clusterList
|
||||||
|
} = useQueryClusterList();
|
||||||
|
const {
|
||||||
|
loading: modelLoading,
|
||||||
|
fetchModelList,
|
||||||
|
cancelRequest: cancelModelRequest,
|
||||||
|
modelList
|
||||||
|
} = useQueryModelList();
|
||||||
|
const {
|
||||||
|
loading: instanceLoading,
|
||||||
|
fetchInstanceList,
|
||||||
|
cancelRequest: cancelInstanceRequest,
|
||||||
|
instanceList
|
||||||
|
} = useQueryModelInstancesList();
|
||||||
|
|
||||||
const handleLabelsChange = (labels: object) => {
|
const handleLabelsChange = (labels: object) => {
|
||||||
form.setFieldValue('labels', labels);
|
form.setFieldValue('labels', labels);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const onModelListOpenChange = async (open: boolean) => {
|
||||||
|
if (open && modelList.length === 0) {
|
||||||
|
await form.validateFields(['cluster_id']);
|
||||||
|
const cluster_id = form.getFieldValue('cluster_id');
|
||||||
|
if (cluster_id) {
|
||||||
|
fetchModelList({ page: -1, cluster_id });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const onInstanceOpenChange = async (open: boolean) => {
|
||||||
|
if (open && instanceList.length === 0) {
|
||||||
|
const model_id = form.getFieldValue('model_id');
|
||||||
|
await form.validateFields(['model_name']);
|
||||||
|
if (model_id) fetchInstanceList({ id: model_id });
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleOnModelChange = (value: string, option: any) => {
|
||||||
|
form.setFieldValue('model_id', option?.id);
|
||||||
|
};
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
const initClusterId = (list: any[]) => {
|
||||||
|
// Find default cluster
|
||||||
|
const defaultCluster = list?.find((item) => item.is_default);
|
||||||
|
if (defaultCluster) {
|
||||||
|
return defaultCluster.id;
|
||||||
|
}
|
||||||
|
|
||||||
|
const cluster_id =
|
||||||
|
list?.find((item) => item.state === ClusterStatusValueMap.Ready)?.id ||
|
||||||
|
list?.[0]?.id;
|
||||||
|
|
||||||
|
return cluster_id;
|
||||||
|
};
|
||||||
|
fetchClusterList({ page: -1 }).then((list) => {
|
||||||
|
if (list.length > 0 && action === PageAction.CREATE) {
|
||||||
|
form.setFieldValue('cluster_id', initClusterId(list));
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}, [form, action]);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (!open) {
|
||||||
|
cancelClusterRequest();
|
||||||
|
cancelModelRequest();
|
||||||
|
cancelInstanceRequest();
|
||||||
|
}
|
||||||
|
}, [open]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<Form.Item<FormData>
|
<Form.Item<FormData>
|
||||||
@@ -47,13 +121,14 @@ const BasicForm: React.FC = () => {
|
|||||||
]}
|
]}
|
||||||
>
|
>
|
||||||
<SealSelect
|
<SealSelect
|
||||||
|
loading={clusterLoading}
|
||||||
options={clusterList}
|
options={clusterList}
|
||||||
label={intl.formatMessage({ id: 'clusters.title' })}
|
label={intl.formatMessage({ id: 'clusters.title' })}
|
||||||
required
|
required
|
||||||
></SealSelect>
|
></SealSelect>
|
||||||
</Form.Item>
|
</Form.Item>
|
||||||
<Form.Item<FormData>
|
<Form.Item<FormData>
|
||||||
name="model_id"
|
name="model_name"
|
||||||
rules={[
|
rules={[
|
||||||
{
|
{
|
||||||
required: true,
|
required: true,
|
||||||
@@ -62,11 +137,21 @@ const BasicForm: React.FC = () => {
|
|||||||
]}
|
]}
|
||||||
>
|
>
|
||||||
<SealSelect
|
<SealSelect
|
||||||
options={modelList}
|
loading={modelLoading}
|
||||||
|
options={modelList.map((item) => ({
|
||||||
|
...item,
|
||||||
|
label: item.name,
|
||||||
|
value: item.name
|
||||||
|
}))}
|
||||||
|
onOpenChange={onModelListOpenChange}
|
||||||
|
onChange={handleOnModelChange}
|
||||||
label={intl.formatMessage({ id: 'benchmark.table.model' })}
|
label={intl.formatMessage({ id: 'benchmark.table.model' })}
|
||||||
required
|
required
|
||||||
></SealSelect>
|
></SealSelect>
|
||||||
</Form.Item>
|
</Form.Item>
|
||||||
|
<Form.Item<FormData> name="model_id" hidden={true}>
|
||||||
|
<SealInput.Input></SealInput.Input>
|
||||||
|
</Form.Item>
|
||||||
<Form.Item<FormData>
|
<Form.Item<FormData>
|
||||||
name="model_instance_name"
|
name="model_instance_name"
|
||||||
rules={[
|
rules={[
|
||||||
@@ -77,7 +162,9 @@ const BasicForm: React.FC = () => {
|
|||||||
]}
|
]}
|
||||||
>
|
>
|
||||||
<SealSelect
|
<SealSelect
|
||||||
options={modelInstanceList}
|
loading={instanceLoading}
|
||||||
|
options={instanceList}
|
||||||
|
onOpenChange={onInstanceOpenChange}
|
||||||
label={intl.formatMessage({ id: 'benchmark.table.instance' })}
|
label={intl.formatMessage({ id: 'benchmark.table.instance' })}
|
||||||
required
|
required
|
||||||
></SealSelect>
|
></SealSelect>
|
||||||
|
|||||||
@@ -1,16 +1,42 @@
|
|||||||
|
import SealInput from '@/components/seal-form/seal-input';
|
||||||
import SealSelect from '@/components/seal-form/seal-select';
|
import SealSelect from '@/components/seal-form/seal-select';
|
||||||
import useAppUtils from '@/hooks/use-app-utils';
|
import useAppUtils from '@/hooks/use-app-utils';
|
||||||
import { useIntl } from '@umijs/max';
|
import { useIntl } from '@umijs/max';
|
||||||
import { Form } from 'antd';
|
import { Form } from 'antd';
|
||||||
import React from 'react';
|
import React, { useEffect } from 'react';
|
||||||
import { profileOptions } from '../config';
|
import { profileOptions } from '../config';
|
||||||
|
import { useFormContext } from '../config/form-context';
|
||||||
import { FormData } from '../config/types';
|
import { FormData } from '../config/types';
|
||||||
|
import useQueryDataset from '../services/use-query-dataset';
|
||||||
import RandomSettingsForm from './random-settings';
|
import RandomSettingsForm from './random-settings';
|
||||||
|
|
||||||
const DatasetForm: React.FC = () => {
|
const DatasetForm: React.FC = () => {
|
||||||
const intl = useIntl();
|
const intl = useIntl();
|
||||||
const form = Form.useFormInstance();
|
const form = Form.useFormInstance();
|
||||||
const { getRuleMessage } = useAppUtils();
|
const { getRuleMessage } = useAppUtils();
|
||||||
|
const { action, open } = useFormContext();
|
||||||
|
const {
|
||||||
|
dataList: datasetList,
|
||||||
|
loading: datasetLoading,
|
||||||
|
fetchData,
|
||||||
|
cancelRequest: cancelDatasetRequest
|
||||||
|
} = useQueryDataset();
|
||||||
|
|
||||||
|
const handleOnDataSetChange = (value: any, option: any) => {
|
||||||
|
form.setFieldValue('dataset_id', option?.data?.id);
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleOnDatasetOpenChange = async (open: boolean) => {
|
||||||
|
if (!datasetList.length && open) {
|
||||||
|
await fetchData({ page: -1 });
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (!open) {
|
||||||
|
cancelDatasetRequest();
|
||||||
|
}
|
||||||
|
}, [open]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
@@ -40,11 +66,20 @@ const DatasetForm: React.FC = () => {
|
|||||||
]}
|
]}
|
||||||
>
|
>
|
||||||
<SealSelect
|
<SealSelect
|
||||||
options={[]}
|
options={datasetList.map((item) => ({
|
||||||
|
label: item.name,
|
||||||
|
value: item.name
|
||||||
|
}))}
|
||||||
|
loading={datasetLoading}
|
||||||
|
onChange={handleOnDataSetChange}
|
||||||
|
onOpenChange={handleOnDatasetOpenChange}
|
||||||
label={intl.formatMessage({ id: 'benchmark.table.dataset' })}
|
label={intl.formatMessage({ id: 'benchmark.table.dataset' })}
|
||||||
required
|
required
|
||||||
></SealSelect>
|
></SealSelect>
|
||||||
</Form.Item>
|
</Form.Item>
|
||||||
|
<Form.Item<FormData> hidden name="dataset_id">
|
||||||
|
<SealInput.Input></SealInput.Input>
|
||||||
|
</Form.Item>
|
||||||
<RandomSettingsForm></RandomSettingsForm>
|
<RandomSettingsForm></RandomSettingsForm>
|
||||||
<Form.Item<FormData>
|
<Form.Item<FormData>
|
||||||
name="request_rate"
|
name="request_rate"
|
||||||
|
|||||||
@@ -22,6 +22,7 @@ interface ProviderFormProps {
|
|||||||
ref?: any;
|
ref?: any;
|
||||||
action: PageActionType;
|
action: PageActionType;
|
||||||
currentData?: ListItem; // Used when action is EDIT
|
currentData?: ListItem; // Used when action is EDIT
|
||||||
|
open?: boolean;
|
||||||
onFinish: (values: FormData) => Promise<void>;
|
onFinish: (values: FormData) => Promise<void>;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -31,9 +32,10 @@ const TABKeysMap = {
|
|||||||
};
|
};
|
||||||
|
|
||||||
const ProviderForm: React.FC<ProviderFormProps> = forwardRef((props, ref) => {
|
const ProviderForm: React.FC<ProviderFormProps> = forwardRef((props, ref) => {
|
||||||
const { action, currentData, onFinish } = props;
|
const { action, currentData, onFinish, open } = props;
|
||||||
const intl = useIntl();
|
const intl = useIntl();
|
||||||
const [form] = Form.useForm();
|
const [form] = Form.useForm();
|
||||||
|
|
||||||
const { getScrollElementScrollableHeight } = useWrapperContext();
|
const { getScrollElementScrollableHeight } = useWrapperContext();
|
||||||
const [activeKey, setActiveKey] = useState<string[]>([TABKeysMap.PROFILE]);
|
const [activeKey, setActiveKey] = useState<string[]>([TABKeysMap.PROFILE]);
|
||||||
const scrollTabsRef = useRef<any>(null);
|
const scrollTabsRef = useRef<any>(null);
|
||||||
@@ -91,7 +93,12 @@ const ProviderForm: React.FC<ProviderFormProps> = forwardRef((props, ref) => {
|
|||||||
}}
|
}}
|
||||||
getScrollElementScrollableHeight={getScrollElementScrollableHeight}
|
getScrollElementScrollableHeight={getScrollElementScrollableHeight}
|
||||||
>
|
>
|
||||||
<FormContext.Provider value={{ action }}>
|
<FormContext.Provider
|
||||||
|
value={{
|
||||||
|
action,
|
||||||
|
open
|
||||||
|
}}
|
||||||
|
>
|
||||||
<Form form={form} onFinish={onFinish} initialValues={{}}>
|
<Form form={form} onFinish={onFinish} initialValues={{}}>
|
||||||
<Basic />
|
<Basic />
|
||||||
<CollapsePanel
|
<CollapsePanel
|
||||||
|
|||||||
@@ -14,7 +14,7 @@ const DatasetForm: React.FC = () => {
|
|||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<Form.Item<FormData>
|
<Form.Item<FormData>
|
||||||
name="input_length"
|
name="dataset_prompt_tokens"
|
||||||
rules={[
|
rules={[
|
||||||
{
|
{
|
||||||
required: true,
|
required: true,
|
||||||
@@ -26,13 +26,22 @@ const DatasetForm: React.FC = () => {
|
|||||||
]}
|
]}
|
||||||
>
|
>
|
||||||
<SealSelect
|
<SealSelect
|
||||||
options={[]}
|
options={[
|
||||||
|
{
|
||||||
|
label: '100',
|
||||||
|
value: 100
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: '128',
|
||||||
|
value: 128
|
||||||
|
}
|
||||||
|
]}
|
||||||
label={intl.formatMessage({ id: 'benchmark.table.inputTokenLength' })}
|
label={intl.formatMessage({ id: 'benchmark.table.inputTokenLength' })}
|
||||||
required
|
required
|
||||||
></SealSelect>
|
></SealSelect>
|
||||||
</Form.Item>
|
</Form.Item>
|
||||||
<Form.Item<FormData>
|
<Form.Item<FormData>
|
||||||
name="output_length"
|
name="dataset_output_tokens"
|
||||||
rules={[
|
rules={[
|
||||||
{
|
{
|
||||||
required: true,
|
required: true,
|
||||||
@@ -44,7 +53,16 @@ const DatasetForm: React.FC = () => {
|
|||||||
]}
|
]}
|
||||||
>
|
>
|
||||||
<SealSelect
|
<SealSelect
|
||||||
options={[]}
|
options={[
|
||||||
|
{
|
||||||
|
label: '4',
|
||||||
|
value: 4
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: '8',
|
||||||
|
value: 8
|
||||||
|
}
|
||||||
|
]}
|
||||||
label={intl.formatMessage({
|
label={intl.formatMessage({
|
||||||
id: 'benchmark.table.outputTokenLength'
|
id: 'benchmark.table.outputTokenLength'
|
||||||
})}
|
})}
|
||||||
|
|||||||
@@ -5,8 +5,10 @@ import icons from '@/components/icon-font/icons';
|
|||||||
import StatusTag from '@/components/status-tag';
|
import StatusTag from '@/components/status-tag';
|
||||||
import { tableSorter } from '@/config/settings';
|
import { tableSorter } from '@/config/settings';
|
||||||
import { useIntl } from '@umijs/max';
|
import { useIntl } from '@umijs/max';
|
||||||
|
import { Typography } from 'antd';
|
||||||
import { ColumnsType } from 'antd/es/table';
|
import { ColumnsType } from 'antd/es/table';
|
||||||
import dayjs from 'dayjs';
|
import dayjs from 'dayjs';
|
||||||
|
import _ from 'lodash';
|
||||||
import { useMemo } from 'react';
|
import { useMemo } from 'react';
|
||||||
import { BenchmarkStatus, BenchmarkStatusLabelMap } from '../config';
|
import { BenchmarkStatus, BenchmarkStatusLabelMap } from '../config';
|
||||||
import { BenchmarkListItem as ListItem } from '../config/types';
|
import { BenchmarkListItem as ListItem } from '../config/types';
|
||||||
@@ -29,7 +31,8 @@ const actionList = [
|
|||||||
|
|
||||||
const useBenchmarkColumns = (
|
const useBenchmarkColumns = (
|
||||||
sortOrder: string[],
|
sortOrder: string[],
|
||||||
handleSelect: (val: string, record: ListItem) => void
|
handleSelect: (val: string, record: ListItem) => void,
|
||||||
|
onCellClick?: (record: ListItem, dataIndex: string) => void
|
||||||
): ColumnsType<ListItem> => {
|
): ColumnsType<ListItem> => {
|
||||||
const intl = useIntl();
|
const intl = useIntl();
|
||||||
|
|
||||||
@@ -39,9 +42,11 @@ const useBenchmarkColumns = (
|
|||||||
title: intl.formatMessage({ id: 'common.table.name' }),
|
title: intl.formatMessage({ id: 'common.table.name' }),
|
||||||
dataIndex: 'name',
|
dataIndex: 'name',
|
||||||
sorter: tableSorter(1),
|
sorter: tableSorter(1),
|
||||||
render: (text: string) => (
|
render: (text: string, record) => (
|
||||||
<AutoTooltip ghost minWidth={20}>
|
<AutoTooltip ghost minWidth={20}>
|
||||||
{text}
|
<Typography.Link onClick={() => onCellClick?.(record, 'name')}>
|
||||||
|
{text}
|
||||||
|
</Typography.Link>
|
||||||
</AutoTooltip>
|
</AutoTooltip>
|
||||||
)
|
)
|
||||||
},
|
},
|
||||||
@@ -68,6 +73,10 @@ const useBenchmarkColumns = (
|
|||||||
{
|
{
|
||||||
title: intl.formatMessage({ id: 'common.table.status' }),
|
title: intl.formatMessage({ id: 'common.table.status' }),
|
||||||
dataIndex: 'state',
|
dataIndex: 'state',
|
||||||
|
ellipsis: {
|
||||||
|
showTitle: false
|
||||||
|
},
|
||||||
|
width: 100,
|
||||||
render: (value: number, record: ListItem) => (
|
render: (value: number, record: ListItem) => (
|
||||||
<StatusTag
|
<StatusTag
|
||||||
statusValue={{
|
statusValue={{
|
||||||
@@ -100,51 +109,51 @@ const useBenchmarkColumns = (
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
title: intl.formatMessage({ id: 'benchmark.table.itl' }),
|
title: intl.formatMessage({ id: 'benchmark.table.itl' }),
|
||||||
dataIndex: 'itl',
|
dataIndex: 'inter_token_latency_mean',
|
||||||
sorter: tableSorter(1),
|
sorter: tableSorter(1),
|
||||||
render: (text: string) => (
|
render: (text: string) => (
|
||||||
<AutoTooltip ghost minWidth={20}>
|
<AutoTooltip ghost minWidth={20}>
|
||||||
{text}
|
{_.round(text, 1)}
|
||||||
</AutoTooltip>
|
</AutoTooltip>
|
||||||
)
|
)
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
title: intl.formatMessage({ id: 'benchmark.table.tpot' }),
|
title: intl.formatMessage({ id: 'benchmark.table.tpot' }),
|
||||||
dataIndex: 'tpot',
|
dataIndex: 'time_per_output_token_mean',
|
||||||
sorter: tableSorter(1),
|
sorter: tableSorter(1),
|
||||||
render: (text: string) => (
|
render: (text: string) => (
|
||||||
<AutoTooltip ghost minWidth={20}>
|
<AutoTooltip ghost minWidth={20}>
|
||||||
{text}
|
{_.round(text, 2)}
|
||||||
</AutoTooltip>
|
</AutoTooltip>
|
||||||
)
|
)
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
title: intl.formatMessage({ id: 'benchmark.table.ttft' }),
|
title: intl.formatMessage({ id: 'benchmark.table.ttft' }),
|
||||||
dataIndex: 'ttft',
|
dataIndex: 'time_to_first_token_mean',
|
||||||
sorter: tableSorter(1),
|
sorter: tableSorter(1),
|
||||||
render: (text: string) => (
|
render: (text: string) => (
|
||||||
<AutoTooltip ghost minWidth={20}>
|
<AutoTooltip ghost minWidth={20}>
|
||||||
{text}
|
{_.round(text, 2)}
|
||||||
</AutoTooltip>
|
</AutoTooltip>
|
||||||
)
|
)
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
title: intl.formatMessage({ id: 'benchmark.table.rps' }),
|
title: intl.formatMessage({ id: 'benchmark.table.rps' }),
|
||||||
dataIndex: 'requests_per_second',
|
dataIndex: 'requests_per_second_mean',
|
||||||
sorter: tableSorter(1),
|
sorter: tableSorter(1),
|
||||||
render: (text: string) => (
|
render: (text: string) => (
|
||||||
<AutoTooltip ghost minWidth={20}>
|
<AutoTooltip ghost minWidth={20}>
|
||||||
{text}
|
{_.round(text, 0)}
|
||||||
</AutoTooltip>
|
</AutoTooltip>
|
||||||
)
|
)
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
title: intl.formatMessage({ id: 'benchmark.table.tps' }),
|
title: intl.formatMessage({ id: 'benchmark.table.tps' }),
|
||||||
dataIndex: 'tokens_per_second',
|
dataIndex: 'tokens_per_second_mean',
|
||||||
sorter: tableSorter(1),
|
sorter: tableSorter(1),
|
||||||
render: (text: string) => (
|
render: (text: string) => (
|
||||||
<AutoTooltip ghost minWidth={20}>
|
<AutoTooltip ghost minWidth={20}>
|
||||||
{text}
|
{_.round(text, 2)}
|
||||||
</AutoTooltip>
|
</AutoTooltip>
|
||||||
)
|
)
|
||||||
},
|
},
|
||||||
@@ -153,7 +162,9 @@ const useBenchmarkColumns = (
|
|||||||
dataIndex: 'created_at',
|
dataIndex: 'created_at',
|
||||||
sorter: tableSorter(3),
|
sorter: tableSorter(3),
|
||||||
render: (value: string) => (
|
render: (value: string) => (
|
||||||
<span>{dayjs(value).format('YYYY-MM-DD HH:mm:ss')}</span>
|
<AutoTooltip ghost>
|
||||||
|
{dayjs(value).format('YYYY-MM-DD HH:mm:ss')}
|
||||||
|
</AutoTooltip>
|
||||||
)
|
)
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@@ -170,7 +181,7 @@ const useBenchmarkColumns = (
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
];
|
];
|
||||||
}, [intl, handleSelect]);
|
}, [intl, onCellClick, handleSelect]);
|
||||||
};
|
};
|
||||||
|
|
||||||
export default useBenchmarkColumns;
|
export default useBenchmarkColumns;
|
||||||
|
|||||||
@@ -0,0 +1,201 @@
|
|||||||
|
import { SettingOutlined } from '@ant-design/icons';
|
||||||
|
import { useIntl } from '@umijs/max';
|
||||||
|
import { Button, Checkbox, Col, Popover, Row, Tooltip } from 'antd';
|
||||||
|
import React from 'react';
|
||||||
|
import styled from 'styled-components';
|
||||||
|
|
||||||
|
const Container = styled.div`
|
||||||
|
max-height: 450px;
|
||||||
|
overflow-y: auto;
|
||||||
|
padding: 12px;
|
||||||
|
.title {
|
||||||
|
font-weight: 500;
|
||||||
|
margin-bottom: 12px;
|
||||||
|
}
|
||||||
|
.btn-wrapper {
|
||||||
|
margin-top: 12px;
|
||||||
|
display: flex;
|
||||||
|
justify-content: space-between;
|
||||||
|
align-items: center;
|
||||||
|
border-top: 1px solid var(--ant-color-split);
|
||||||
|
padding-top: 12px;
|
||||||
|
}
|
||||||
|
.buttons {
|
||||||
|
display: flex;
|
||||||
|
gap: 8px;
|
||||||
|
justify-content: flex-end;
|
||||||
|
}
|
||||||
|
`;
|
||||||
|
|
||||||
|
const useColumnSettings = () => {
|
||||||
|
const intl = useIntl();
|
||||||
|
|
||||||
|
const [open, setOpen] = React.useState(false);
|
||||||
|
const [selectedColumns, setSelectedColumns] = React.useState<string[]>([]);
|
||||||
|
|
||||||
|
const handleToggle = () => {
|
||||||
|
setOpen(!open);
|
||||||
|
};
|
||||||
|
|
||||||
|
const allColumns = [
|
||||||
|
{
|
||||||
|
title: intl.formatMessage({ id: 'clusters.title' }),
|
||||||
|
dataIndex: 'cluster_id'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: intl.formatMessage({ id: 'resources.worker' }),
|
||||||
|
dataIndex: 'worker_id'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: intl.formatMessage({ id: 'common.table.name' }),
|
||||||
|
dataIndex: 'name'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: intl.formatMessage({ id: 'benchmark.table.model' }),
|
||||||
|
dataIndex: 'model_name'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: intl.formatMessage({ id: 'benchmark.table.dataset' }),
|
||||||
|
dataIndex: 'dataset_name'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: 'Latency',
|
||||||
|
dataIndex: 'latency_mean'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: 'Throughput',
|
||||||
|
dataIndex: 'throughput_mean'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: 'Throughput request',
|
||||||
|
dataIndex: 'throughput_request_mean'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: 'generated tokens',
|
||||||
|
dataIndex: 'generated_tokens_mean'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: 'ITL Avg',
|
||||||
|
dataIndex: 'inter_token_latency_mean'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: intl.formatMessage({ id: 'common.table.status' }),
|
||||||
|
dataIndex: 'state'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: intl.formatMessage({ id: 'benchmark.table.requestRate' }),
|
||||||
|
dataIndex: 'request_rate'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: intl.formatMessage({ id: 'benchmark.table.gpu' }),
|
||||||
|
dataIndex: 'gpu_summary'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: intl.formatMessage({ id: 'benchmark.table.itl' }),
|
||||||
|
dataIndex: 'inter_token_latency_mean'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: intl.formatMessage({ id: 'benchmark.table.tpot' }),
|
||||||
|
dataIndex: 'time_per_output_token_mean'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: intl.formatMessage({ id: 'benchmark.table.ttft' }),
|
||||||
|
dataIndex: 'time_to_first_token_mean'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: intl.formatMessage({ id: 'benchmark.table.rps' }),
|
||||||
|
dataIndex: 'requests_per_second_mean'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: intl.formatMessage({ id: 'benchmark.table.tps' }),
|
||||||
|
dataIndex: 'tokens_per_second_mean'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: intl.formatMessage({ id: 'common.table.createTime' }),
|
||||||
|
dataIndex: 'created_at'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: intl.formatMessage({ id: 'common.table.operation' }),
|
||||||
|
dataIndex: 'operations'
|
||||||
|
}
|
||||||
|
];
|
||||||
|
|
||||||
|
const contentRender = () => {
|
||||||
|
return (
|
||||||
|
<Container>
|
||||||
|
<div className="title">Column Settings</div>
|
||||||
|
<Checkbox.Group
|
||||||
|
value={selectedColumns}
|
||||||
|
onChange={(checkedValues) => {
|
||||||
|
setSelectedColumns(checkedValues as string[]);
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<Row>
|
||||||
|
{allColumns.map((col) => (
|
||||||
|
<Col key={col.dataIndex} span={12}>
|
||||||
|
<Checkbox value={col.dataIndex} style={{ marginBottom: 8 }}>
|
||||||
|
<span className="text-secondary">{col.title}</span>
|
||||||
|
</Checkbox>
|
||||||
|
</Col>
|
||||||
|
))}
|
||||||
|
</Row>
|
||||||
|
</Checkbox.Group>
|
||||||
|
<div className="btn-wrapper">
|
||||||
|
<Button
|
||||||
|
size="middle"
|
||||||
|
onClick={() => {
|
||||||
|
setSelectedColumns([]);
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
Clear All
|
||||||
|
</Button>
|
||||||
|
<div className="buttons">
|
||||||
|
<Button
|
||||||
|
size="middle"
|
||||||
|
type="primary"
|
||||||
|
onClick={() => {
|
||||||
|
setSelectedColumns(allColumns.map((col) => col.dataIndex));
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
Select All
|
||||||
|
</Button>
|
||||||
|
<Button
|
||||||
|
size="middle"
|
||||||
|
type="primary"
|
||||||
|
onClick={() => {
|
||||||
|
setSelectedColumns(allColumns.map((col) => col.dataIndex));
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
Confirm
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</Container>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
const SettingsButton = (
|
||||||
|
<Popover
|
||||||
|
trigger={'click'}
|
||||||
|
arrow={false}
|
||||||
|
placement="bottomRight"
|
||||||
|
content={contentRender()}
|
||||||
|
styles={{
|
||||||
|
root: {
|
||||||
|
width: '420px'
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<Tooltip title="Column Settings">
|
||||||
|
<Button onClick={handleToggle} icon={<SettingOutlined />}></Button>
|
||||||
|
</Tooltip>
|
||||||
|
</Popover>
|
||||||
|
);
|
||||||
|
|
||||||
|
return {
|
||||||
|
SettingsButton,
|
||||||
|
selectedColumns
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
export default useColumnSettings;
|
||||||
@@ -0,0 +1,35 @@
|
|||||||
|
import { useState } from 'react';
|
||||||
|
import { BenchmarkListItem as ListItem } from '../config/types';
|
||||||
|
|
||||||
|
const useViewDetail = () => {
|
||||||
|
const [openModalStatus, setOpenModalStatus] = useState<{
|
||||||
|
open: boolean;
|
||||||
|
currentData?: ListItem;
|
||||||
|
}>({
|
||||||
|
open: false,
|
||||||
|
currentData: undefined
|
||||||
|
});
|
||||||
|
|
||||||
|
const openModal = (rows?: ListItem) => {
|
||||||
|
setOpenModalStatus({
|
||||||
|
open: true,
|
||||||
|
currentData: rows
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
const closeModal = () => {
|
||||||
|
setOpenModalStatus({
|
||||||
|
open: false,
|
||||||
|
currentData: undefined
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
return {
|
||||||
|
openViewDetailModalStatus: openModalStatus,
|
||||||
|
setOpenViewDetailModalStatus: setOpenModalStatus,
|
||||||
|
openViewDetailModal: openModal,
|
||||||
|
closeViewDetailModal: closeModal
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
export default useViewDetail;
|
||||||
@@ -4,7 +4,7 @@ import { FilterBar } from '@/components/page-tools';
|
|||||||
import { PageAction } from '@/config';
|
import { PageAction } from '@/config';
|
||||||
import { TABLE_SORT_DIRECTIONS } from '@/config/settings';
|
import { TABLE_SORT_DIRECTIONS } from '@/config/settings';
|
||||||
import useTableFetch from '@/hooks/use-table-fetch';
|
import useTableFetch from '@/hooks/use-table-fetch';
|
||||||
import { useIntl } from '@umijs/max';
|
import { useIntl, useNavigate } from '@umijs/max';
|
||||||
import { useMemoizedFn } from 'ahooks';
|
import { useMemoizedFn } from 'ahooks';
|
||||||
import { ConfigProvider, Table, message } from 'antd';
|
import { ConfigProvider, Table, message } from 'antd';
|
||||||
import _ from 'lodash';
|
import _ from 'lodash';
|
||||||
@@ -17,11 +17,14 @@ import {
|
|||||||
updateBenchmark
|
updateBenchmark
|
||||||
} from './apis';
|
} from './apis';
|
||||||
import AddBenchmarkModal from './components/add-benchmark-modal';
|
import AddBenchmarkModal from './components/add-benchmark-modal';
|
||||||
|
import DetailModal from './components/detail-modal';
|
||||||
import LeftActions from './components/left-actions';
|
import LeftActions from './components/left-actions';
|
||||||
import RightActions from './components/right-actions';
|
import RightActions from './components/right-actions';
|
||||||
import { FormData, BenchmarkListItem as ListItem } from './config/types';
|
import { FormData, BenchmarkListItem as ListItem } from './config/types';
|
||||||
import useBenchmarkColumns from './hooks/use-benchmark-columns';
|
import useBenchmarkColumns from './hooks/use-benchmark-columns';
|
||||||
|
import useColumnSettings from './hooks/use-column-settings';
|
||||||
import useCreateBenchmark from './hooks/use-create-benchmark';
|
import useCreateBenchmark from './hooks/use-create-benchmark';
|
||||||
|
import useViewDetail from './hooks/use-view-detail';
|
||||||
|
|
||||||
const Benchmark: React.FC = () => {
|
const Benchmark: React.FC = () => {
|
||||||
const {
|
const {
|
||||||
@@ -43,8 +46,15 @@ const Benchmark: React.FC = () => {
|
|||||||
contentForDelete: 'menu.models.benchmark'
|
contentForDelete: 'menu.models.benchmark'
|
||||||
});
|
});
|
||||||
const intl = useIntl();
|
const intl = useIntl();
|
||||||
|
const navigate = useNavigate();
|
||||||
const { openBenchmarkModal, closeBenchmarkModal, openBenchmarkModalStatus } =
|
const { openBenchmarkModal, closeBenchmarkModal, openBenchmarkModalStatus } =
|
||||||
useCreateBenchmark();
|
useCreateBenchmark();
|
||||||
|
const {
|
||||||
|
openViewDetailModal,
|
||||||
|
closeViewDetailModal,
|
||||||
|
openViewDetailModalStatus
|
||||||
|
} = useViewDetail();
|
||||||
|
const { SettingsButton, selectedColumns } = useColumnSettings();
|
||||||
|
|
||||||
const handleAddBenchmark = () => {
|
const handleAddBenchmark = () => {
|
||||||
openBenchmarkModal(PageAction.CREATE, 'Add Benchmark');
|
openBenchmarkModal(PageAction.CREATE, 'Add Benchmark');
|
||||||
@@ -89,6 +99,16 @@ const Benchmark: React.FC = () => {
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
const handleOnCellClick = useMemoizedFn(
|
||||||
|
(record: ListItem, dataIndex: string) => {
|
||||||
|
if (dataIndex === 'name') {
|
||||||
|
navigate(
|
||||||
|
`/models/benchmark/detail?id=${record.id}&name=${record.name}`
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
const renderEmpty = (type?: string) => {
|
const renderEmpty = (type?: string) => {
|
||||||
if (type !== 'Table') return;
|
if (type !== 'Table') return;
|
||||||
return (
|
return (
|
||||||
@@ -111,7 +131,11 @@ const Benchmark: React.FC = () => {
|
|||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
const columns = useBenchmarkColumns(sortOrder, handleSelect);
|
const columns = useBenchmarkColumns(
|
||||||
|
sortOrder,
|
||||||
|
handleSelect,
|
||||||
|
handleOnCellClick
|
||||||
|
);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
@@ -132,6 +156,7 @@ const Benchmark: React.FC = () => {
|
|||||||
}
|
}
|
||||||
right={
|
right={
|
||||||
<RightActions
|
<RightActions
|
||||||
|
settingButton={SettingsButton}
|
||||||
handleDeleteByBatch={handleDeleteBatch}
|
handleDeleteByBatch={handleDeleteBatch}
|
||||||
handleClickPrimary={handleAddBenchmark}
|
handleClickPrimary={handleAddBenchmark}
|
||||||
buttonText={intl.formatMessage({
|
buttonText={intl.formatMessage({
|
||||||
@@ -171,6 +196,11 @@ const Benchmark: React.FC = () => {
|
|||||||
onCancel={handleModalCancel}
|
onCancel={handleModalCancel}
|
||||||
onOk={handleModalOk}
|
onOk={handleModalOk}
|
||||||
></AddBenchmarkModal>
|
></AddBenchmarkModal>
|
||||||
|
<DetailModal
|
||||||
|
open={openViewDetailModalStatus.open}
|
||||||
|
currentData={openViewDetailModalStatus.currentData}
|
||||||
|
onClose={closeViewDetailModal}
|
||||||
|
></DetailModal>
|
||||||
<DeleteModal ref={modalRef}></DeleteModal>
|
<DeleteModal ref={modalRef}></DeleteModal>
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -0,0 +1,22 @@
|
|||||||
|
import { useQueryDataList } from '@/hooks/use-query-data-list';
|
||||||
|
import { queryDatasetList } from '../apis';
|
||||||
|
import { DatasetListItem } from '../config/types';
|
||||||
|
|
||||||
|
const useQueryDataset = () => {
|
||||||
|
const { dataList, loading, fetchData, cancelRequest } = useQueryDataList<
|
||||||
|
DatasetListItem,
|
||||||
|
Global.SearchParams
|
||||||
|
>({
|
||||||
|
key: 'datasetList',
|
||||||
|
fetchList: queryDatasetList
|
||||||
|
});
|
||||||
|
|
||||||
|
return {
|
||||||
|
dataList,
|
||||||
|
loading,
|
||||||
|
fetchData,
|
||||||
|
cancelRequest
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
export default useQueryDataset;
|
||||||
@@ -0,0 +1,17 @@
|
|||||||
|
import { useQueryData } from '@/hooks/use-query-data-list';
|
||||||
|
import { queryBenchmarkDetail } from '../apis';
|
||||||
|
import { BenchmarkDetail } from '../config/detail-types';
|
||||||
|
|
||||||
|
export default function useQueryBenchmarkDetail() {
|
||||||
|
const { detailData, loading, cancelRequest, fetchData } =
|
||||||
|
useQueryData<BenchmarkDetail>({
|
||||||
|
fetchDetail: queryBenchmarkDetail,
|
||||||
|
key: 'benchmarkDetail'
|
||||||
|
});
|
||||||
|
return {
|
||||||
|
detailData,
|
||||||
|
loading,
|
||||||
|
cancelRequest,
|
||||||
|
fetchData
|
||||||
|
};
|
||||||
|
}
|
||||||
@@ -0,0 +1,22 @@
|
|||||||
|
import { useQueryData } from '@/hooks/use-query-data-list';
|
||||||
|
import { queryBenchmarkMetrics } from '../apis';
|
||||||
|
import { BenchmarkMetricsFormData } from '../config/detail-types';
|
||||||
|
|
||||||
|
export default function useQueryBenchmarkMetrics() {
|
||||||
|
const { detailData, loading, cancelRequest, fetchData } = useQueryData<
|
||||||
|
any,
|
||||||
|
{
|
||||||
|
id: number;
|
||||||
|
data: BenchmarkMetricsFormData;
|
||||||
|
}
|
||||||
|
>({
|
||||||
|
fetchDetail: queryBenchmarkMetrics,
|
||||||
|
key: 'benchmarkMetrics'
|
||||||
|
});
|
||||||
|
return {
|
||||||
|
detailData,
|
||||||
|
loading,
|
||||||
|
cancelRequest,
|
||||||
|
fetchData
|
||||||
|
};
|
||||||
|
}
|
||||||
@@ -106,10 +106,14 @@ export async function deleteCredential(id: number) {
|
|||||||
|
|
||||||
// ===================== Cluster =====================
|
// ===================== Cluster =====================
|
||||||
|
|
||||||
export async function queryClusterList(params: Global.SearchParams) {
|
export async function queryClusterList(
|
||||||
|
params: Global.SearchParams,
|
||||||
|
options?: any
|
||||||
|
) {
|
||||||
return request<Global.PageResponse<ClusterListItem>>(`${CLUSTERS_API}`, {
|
return request<Global.PageResponse<ClusterListItem>>(`${CLUSTERS_API}`, {
|
||||||
method: 'GET',
|
method: 'GET',
|
||||||
params
|
params,
|
||||||
|
cancelToken: options?.token
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,67 @@
|
|||||||
|
import { createAxiosToken } from '@/hooks/use-chunk-request';
|
||||||
|
import { useRequest } from 'ahooks';
|
||||||
|
import { message } from 'antd';
|
||||||
|
import { CancelTokenSource } from 'axios';
|
||||||
|
import { useEffect, useRef, useState } from 'react';
|
||||||
|
import { queryClusterList } from '../apis';
|
||||||
|
import { ClusterListItem } from '../config/types';
|
||||||
|
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
* @returns loading, fetch, dataList
|
||||||
|
*/
|
||||||
|
export const useQueryClusterList = () => {
|
||||||
|
const axiosTokenRef = useRef<CancelTokenSource | null>(null);
|
||||||
|
const [dataList, setDataList] = useState<
|
||||||
|
Array<Partial<ClusterListItem> & { label: string; value: number }>
|
||||||
|
>([]);
|
||||||
|
|
||||||
|
const {
|
||||||
|
runAsync: fetchData,
|
||||||
|
loading,
|
||||||
|
cancel
|
||||||
|
} = useRequest(
|
||||||
|
async (params: { page: number; perPage?: number }) => {
|
||||||
|
axiosTokenRef.current?.cancel();
|
||||||
|
axiosTokenRef.current = createAxiosToken();
|
||||||
|
const res = await queryClusterList(params, {
|
||||||
|
token: axiosTokenRef.current.token
|
||||||
|
});
|
||||||
|
setDataList(
|
||||||
|
res.items?.map((item: ClusterListItem) => ({
|
||||||
|
...item,
|
||||||
|
label: item.name,
|
||||||
|
value: item.id
|
||||||
|
})) || []
|
||||||
|
);
|
||||||
|
return res.items || [];
|
||||||
|
},
|
||||||
|
{
|
||||||
|
manual: true,
|
||||||
|
onSuccess: (response) => {},
|
||||||
|
onError: (error) => {
|
||||||
|
message.error(error?.message || 'Failed to fetch cluster list');
|
||||||
|
setDataList([]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
const cancelRequest = () => {
|
||||||
|
cancel();
|
||||||
|
axiosTokenRef.current?.cancel();
|
||||||
|
};
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
return () => {
|
||||||
|
cancel();
|
||||||
|
axiosTokenRef.current?.cancel();
|
||||||
|
};
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
return {
|
||||||
|
loading,
|
||||||
|
clusterList: dataList,
|
||||||
|
cancelRequest,
|
||||||
|
fetchClusterList: fetchData
|
||||||
|
};
|
||||||
|
};
|
||||||
@@ -106,7 +106,7 @@ const ViewLogsModal: React.FC<ViewModalProps> = (props) => {
|
|||||||
maskClosable={false}
|
maskClosable={false}
|
||||||
keyboard={true}
|
keyboard={true}
|
||||||
styles={{
|
styles={{
|
||||||
content: {
|
wrapper: {
|
||||||
borderRadius: 0
|
borderRadius: 0
|
||||||
}
|
}
|
||||||
}}
|
}}
|
||||||
|
|||||||
@@ -0,0 +1,71 @@
|
|||||||
|
import { createAxiosToken } from '@/hooks/use-chunk-request';
|
||||||
|
import { useRequest } from 'ahooks';
|
||||||
|
import { message } from 'antd';
|
||||||
|
import { CancelTokenSource } from 'axios';
|
||||||
|
import { useEffect, useRef, useState } from 'react';
|
||||||
|
import { queryModelInstancesList } from '../apis';
|
||||||
|
import { ModelInstanceListItem } from '../config/types';
|
||||||
|
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
* @returns loading, fetch, dataList
|
||||||
|
*/
|
||||||
|
export const useQueryModelInstancesList = () => {
|
||||||
|
const axiosTokenRef = useRef<CancelTokenSource | null>(null);
|
||||||
|
const [dataList, setDataList] = useState<
|
||||||
|
Array<Partial<ModelInstanceListItem> & { label: string; value: string }>
|
||||||
|
>([]);
|
||||||
|
|
||||||
|
const {
|
||||||
|
runAsync: fetchData,
|
||||||
|
loading,
|
||||||
|
cancel
|
||||||
|
} = useRequest(
|
||||||
|
async (params: { id: number }) => {
|
||||||
|
const query = {
|
||||||
|
page: -1,
|
||||||
|
id: params.id
|
||||||
|
};
|
||||||
|
axiosTokenRef.current?.cancel();
|
||||||
|
axiosTokenRef.current = createAxiosToken();
|
||||||
|
const res = await queryModelInstancesList(query, {
|
||||||
|
token: axiosTokenRef.current.token
|
||||||
|
});
|
||||||
|
setDataList(
|
||||||
|
res.items?.map((item: ModelInstanceListItem) => ({
|
||||||
|
...item,
|
||||||
|
label: item.name,
|
||||||
|
value: item.name
|
||||||
|
})) || []
|
||||||
|
);
|
||||||
|
return res.items || [];
|
||||||
|
},
|
||||||
|
{
|
||||||
|
manual: true,
|
||||||
|
onSuccess: (response) => {},
|
||||||
|
onError: (error) => {
|
||||||
|
message.error(error?.message || 'Failed to fetch model instances list');
|
||||||
|
setDataList([]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
const cancelRequest = () => {
|
||||||
|
cancel();
|
||||||
|
axiosTokenRef.current?.cancel();
|
||||||
|
};
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
return () => {
|
||||||
|
cancel();
|
||||||
|
axiosTokenRef.current?.cancel();
|
||||||
|
};
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
return {
|
||||||
|
loading,
|
||||||
|
instanceList: dataList,
|
||||||
|
cancelRequest,
|
||||||
|
fetchInstanceList: fetchData
|
||||||
|
};
|
||||||
|
};
|
||||||
@@ -0,0 +1,67 @@
|
|||||||
|
import { createAxiosToken } from '@/hooks/use-chunk-request';
|
||||||
|
import { useRequest } from 'ahooks';
|
||||||
|
import { message } from 'antd';
|
||||||
|
import { CancelTokenSource } from 'axios';
|
||||||
|
import { useEffect, useRef, useState } from 'react';
|
||||||
|
import { queryModelsList } from '../apis';
|
||||||
|
import { ListItem } from '../config/types';
|
||||||
|
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
* @returns loading, fetch, dataList
|
||||||
|
*/
|
||||||
|
export const useQueryModelList = () => {
|
||||||
|
const axiosTokenRef = useRef<CancelTokenSource | null>(null);
|
||||||
|
const [dataList, setDataList] = useState<
|
||||||
|
Array<Partial<ListItem> & { label: string; value: number }>
|
||||||
|
>([]);
|
||||||
|
|
||||||
|
const {
|
||||||
|
runAsync: fetchData,
|
||||||
|
loading,
|
||||||
|
cancel
|
||||||
|
} = useRequest(
|
||||||
|
async (params: { page: number; perPage?: number; cluster_id?: number }) => {
|
||||||
|
axiosTokenRef.current?.cancel();
|
||||||
|
axiosTokenRef.current = createAxiosToken();
|
||||||
|
const res = await queryModelsList(params, {
|
||||||
|
token: axiosTokenRef.current.token
|
||||||
|
});
|
||||||
|
setDataList(
|
||||||
|
res.items?.map((item: ListItem) => ({
|
||||||
|
...item,
|
||||||
|
label: item.name,
|
||||||
|
value: item.id
|
||||||
|
})) || []
|
||||||
|
);
|
||||||
|
return res.items || [];
|
||||||
|
},
|
||||||
|
{
|
||||||
|
manual: true,
|
||||||
|
onSuccess: (response) => {},
|
||||||
|
onError: (error) => {
|
||||||
|
message.error(error?.message || 'Failed to fetch model list');
|
||||||
|
setDataList([]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
const cancelRequest = () => {
|
||||||
|
cancel();
|
||||||
|
axiosTokenRef.current?.cancel();
|
||||||
|
};
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
return () => {
|
||||||
|
cancel();
|
||||||
|
axiosTokenRef.current?.cancel();
|
||||||
|
};
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
return {
|
||||||
|
loading,
|
||||||
|
modelList: dataList,
|
||||||
|
cancelRequest,
|
||||||
|
fetchModelList: fetchData
|
||||||
|
};
|
||||||
|
};
|
||||||
@@ -8,6 +8,10 @@ import { DEFAULT_ENTER_PAGE } from './config/settings';
|
|||||||
const NoBaseURLAPIs = ['/auth', '/v1', '/version', '/proxy', '/update'];
|
const NoBaseURLAPIs = ['/auth', '/v1', '/version', '/proxy', '/update'];
|
||||||
|
|
||||||
export const requestConfig: RequestConfig = {
|
export const requestConfig: RequestConfig = {
|
||||||
|
headers: {
|
||||||
|
'Content-Security-Policy': "frame-ancestors 'self'",
|
||||||
|
'X-Frame-Options': 'SAMEORIGIN'
|
||||||
|
},
|
||||||
errorConfig: {
|
errorConfig: {
|
||||||
errorThrower: (res: any) => {
|
errorThrower: (res: any) => {
|
||||||
// to do something
|
// to do something
|
||||||
|
|||||||
Reference in New Issue
Block a user