chore: detail api
This commit is contained in:
@@ -124,6 +124,17 @@ export default [
|
||||
defaultIcon: 'icon-speed',
|
||||
access: 'canSeeAdmin',
|
||||
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',
|
||||
'clusterDetail',
|
||||
'clusterCreate',
|
||||
'benchmarkDetail',
|
||||
'video'
|
||||
];
|
||||
|
||||
|
||||
@@ -10,6 +10,9 @@ const StyledBreadcrumb = styled(Breadcrumb)`
|
||||
a {
|
||||
background: unset;
|
||||
}
|
||||
a:hover {
|
||||
background: unset;
|
||||
}
|
||||
}
|
||||
.ant-breadcrumb-separator {
|
||||
display: flex;
|
||||
|
||||
@@ -1,8 +1,9 @@
|
||||
import { request } from '@umijs/max';
|
||||
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 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 }) {
|
||||
return request(`${BENCHMARKS_API}/${params.id}/result`, {
|
||||
method: 'POST',
|
||||
@@ -63,9 +76,25 @@ export async function queryDatasetList(
|
||||
token?: CancelToken;
|
||||
}
|
||||
) {
|
||||
return request<Global.PageResponse<BenchmarkListItem>>(`${DATASETS_API}`, {
|
||||
return request<Global.PageResponse<DatasetListItem>>(`${DATASETS_API}`, {
|
||||
method: 'GET',
|
||||
params,
|
||||
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
|
||||
ref={form}
|
||||
action={action}
|
||||
open={open}
|
||||
currentData={currentData}
|
||||
onFinish={handleOk}
|
||||
/>
|
||||
|
||||
@@ -184,7 +184,7 @@ const CompareConditions: React.FC = () => {
|
||||
}}
|
||||
>
|
||||
<Container>
|
||||
<span className="holder">Compare Filter</span>
|
||||
<span className="holder">Export filter</span>
|
||||
</Container>
|
||||
</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 WorkerData from './worker-data';
|
||||
|
||||
const Container = styled.div`
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 16px;
|
||||
`;
|
||||
|
||||
const Environment: React.FC = () => {
|
||||
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;
|
||||
|
||||
@@ -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 { Button, Input, Space } from 'antd';
|
||||
import React from 'react';
|
||||
import CompareConditions from './compare-conditions';
|
||||
|
||||
export interface RightActionsProps {
|
||||
handleInputChange: (e: React.ChangeEvent<HTMLInputElement>) => void;
|
||||
@@ -30,7 +29,7 @@ const RightActions: React.FC<RightActionsProps> = ({
|
||||
allowClear
|
||||
onChange={handleInputChange}
|
||||
></Input>
|
||||
<CompareConditions></CompareConditions>
|
||||
{/* <CompareConditions></CompareConditions> */}
|
||||
<Button
|
||||
type="text"
|
||||
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';
|
||||
|
||||
const Logs: React.FC = () => {
|
||||
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;
|
||||
|
||||
@@ -1,7 +1,8 @@
|
||||
import DropdownButtons from '@/components/drop-down-buttons';
|
||||
import {
|
||||
DeleteOutlined,
|
||||
PlusOutlined,
|
||||
SettingOutlined
|
||||
DownloadOutlined,
|
||||
PlusOutlined
|
||||
} from '@ant-design/icons';
|
||||
import { useIntl } from '@umijs/max';
|
||||
import { Button, Space } from 'antd';
|
||||
@@ -11,6 +12,7 @@ export interface RightActionsProps {
|
||||
handleDeleteByBatch: () => void;
|
||||
handleClickPrimary?: () => void;
|
||||
handleSettingFields?: () => void;
|
||||
settingButton?: React.ReactNode;
|
||||
buttonText?: string;
|
||||
rowSelection: {
|
||||
selectedRowKeys: React.Key[];
|
||||
@@ -21,14 +23,38 @@ const RightActions: React.FC<RightActionsProps> = ({
|
||||
handleDeleteByBatch,
|
||||
handleClickPrimary,
|
||||
handleSettingFields,
|
||||
settingButton,
|
||||
buttonText,
|
||||
rowSelection
|
||||
}) => {
|
||||
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 (
|
||||
<Space size={16}>
|
||||
<Button onClick={handleSettingFields} icon={<SettingOutlined />}></Button>
|
||||
{/* <Tooltip title="Column Settings">
|
||||
<Button
|
||||
onClick={handleSettingFields}
|
||||
icon={<SettingOutlined />}
|
||||
></Button>
|
||||
</Tooltip> */}
|
||||
{settingButton}
|
||||
<Button
|
||||
icon={<PlusOutlined></PlusOutlined>}
|
||||
type="primary"
|
||||
@@ -36,7 +62,7 @@ const RightActions: React.FC<RightActionsProps> = ({
|
||||
>
|
||||
{buttonText}
|
||||
</Button>
|
||||
<Button
|
||||
{/* <Button
|
||||
icon={<DeleteOutlined />}
|
||||
danger
|
||||
onClick={handleDeleteByBatch}
|
||||
@@ -48,7 +74,19 @@ const RightActions: React.FC<RightActionsProps> = ({
|
||||
<span>({rowSelection?.selectedRowKeys?.length})</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>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -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 { 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 { detailData } = useDetailContext();
|
||||
return <div>Summary Content</div>;
|
||||
return (
|
||||
<Container>
|
||||
<BasicInfo />
|
||||
<MetricsInfo />
|
||||
</Container>
|
||||
);
|
||||
};
|
||||
|
||||
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 { BenchmarkDetail } from './detail-types';
|
||||
|
||||
interface DetailContextProps {
|
||||
detailData: any;
|
||||
detailData: BenchmarkDetail;
|
||||
clusterList?: Global.BaseOption<number>[];
|
||||
id: number;
|
||||
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 {
|
||||
action: PageActionType;
|
||||
clusterList: Global.BaseOption<number>[];
|
||||
modelList: Global.BaseOption<number>[];
|
||||
modelInstanceList: Global.BaseOption<number>[];
|
||||
open?: boolean;
|
||||
clusterList?: Global.BaseOption<number>[];
|
||||
modelList?: Global.BaseOption<number>[];
|
||||
}
|
||||
|
||||
const FormContext = createContext<FormContextProps>({} as FormContextProps);
|
||||
|
||||
@@ -67,6 +67,7 @@ export interface GPUSnapshot {
|
||||
|
||||
export interface FormData {
|
||||
name: string;
|
||||
profile: string;
|
||||
description: string;
|
||||
labels: Record<string, string>;
|
||||
cluster_id: number;
|
||||
|
||||
@@ -1,55 +1,32 @@
|
||||
import { useIntl } from '@umijs/max';
|
||||
import { Tabs, TabsProps } from 'antd';
|
||||
import React, { useState } from 'react';
|
||||
import PageBox from '../_components/page-box';
|
||||
import Configure from './components/configure';
|
||||
import Environment from './components/environment';
|
||||
import Logs from './components/logs';
|
||||
import Summary from './components/summary';
|
||||
import DetailContext from './config/detail-context';
|
||||
import { useIntl, useNavigate, useSearchParams } from '@umijs/max';
|
||||
import React from 'react';
|
||||
import { PageContainerInner } from '../_components/page-box';
|
||||
import PageBreadcrumb from '../_components/page-breadcrumb';
|
||||
import DetailContent from './components/detail-content';
|
||||
|
||||
const Details: React.FC = () => {
|
||||
const navigate = useNavigate();
|
||||
const intl = useIntl();
|
||||
const [activeKey, setActiveKey] = useState('summary');
|
||||
const [searchParams] = useSearchParams();
|
||||
const name = searchParams.get('name');
|
||||
|
||||
const items: TabsProps['items'] = [
|
||||
const breadcrumbItems = [
|
||||
{
|
||||
key: 'summary',
|
||||
label: intl.formatMessage({ id: 'benchmark.detail.summary.title' }),
|
||||
children: <Summary />
|
||||
title: <a>{intl.formatMessage({ id: 'benchmark.title' })}</a>,
|
||||
onClick: () => navigate(-1)
|
||||
},
|
||||
{
|
||||
key: 'configure',
|
||||
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 />
|
||||
title: name
|
||||
}
|
||||
];
|
||||
|
||||
const handleChangeTab = (key: string) => {
|
||||
setActiveKey(key);
|
||||
};
|
||||
|
||||
return (
|
||||
<PageBox>
|
||||
<DetailContext.Provider value={{}}>
|
||||
<Tabs
|
||||
activeKey={activeKey}
|
||||
onChange={handleChangeTab}
|
||||
items={items}
|
||||
type="card"
|
||||
/>
|
||||
</DetailContext.Provider>
|
||||
</PageBox>
|
||||
<PageContainerInner
|
||||
header={{
|
||||
title: <PageBreadcrumb items={breadcrumbItems} />
|
||||
}}
|
||||
>
|
||||
<DetailContent></DetailContent>
|
||||
</PageContainerInner>
|
||||
);
|
||||
};
|
||||
|
||||
|
||||
@@ -1,11 +1,16 @@
|
||||
import LabelSelector from '@/components/label-selector';
|
||||
import SealInput from '@/components/seal-form/seal-input';
|
||||
import SealSelect from '@/components/seal-form/seal-select';
|
||||
import { PageAction } from '@/config';
|
||||
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 { Form } from 'antd';
|
||||
import _ from 'lodash';
|
||||
import React from 'react';
|
||||
import React, { useEffect } from 'react';
|
||||
import { useFormContext } from '../config/form-context';
|
||||
import { FormData } from '../config/types';
|
||||
|
||||
@@ -14,12 +19,81 @@ const BasicForm: React.FC = () => {
|
||||
const form = Form.useFormInstance();
|
||||
const labels = Form.useWatch('labels', form);
|
||||
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) => {
|
||||
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 (
|
||||
<>
|
||||
<Form.Item<FormData>
|
||||
@@ -47,13 +121,14 @@ const BasicForm: React.FC = () => {
|
||||
]}
|
||||
>
|
||||
<SealSelect
|
||||
loading={clusterLoading}
|
||||
options={clusterList}
|
||||
label={intl.formatMessage({ id: 'clusters.title' })}
|
||||
required
|
||||
></SealSelect>
|
||||
</Form.Item>
|
||||
<Form.Item<FormData>
|
||||
name="model_id"
|
||||
name="model_name"
|
||||
rules={[
|
||||
{
|
||||
required: true,
|
||||
@@ -62,11 +137,21 @@ const BasicForm: React.FC = () => {
|
||||
]}
|
||||
>
|
||||
<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' })}
|
||||
required
|
||||
></SealSelect>
|
||||
</Form.Item>
|
||||
<Form.Item<FormData> name="model_id" hidden={true}>
|
||||
<SealInput.Input></SealInput.Input>
|
||||
</Form.Item>
|
||||
<Form.Item<FormData>
|
||||
name="model_instance_name"
|
||||
rules={[
|
||||
@@ -77,7 +162,9 @@ const BasicForm: React.FC = () => {
|
||||
]}
|
||||
>
|
||||
<SealSelect
|
||||
options={modelInstanceList}
|
||||
loading={instanceLoading}
|
||||
options={instanceList}
|
||||
onOpenChange={onInstanceOpenChange}
|
||||
label={intl.formatMessage({ id: 'benchmark.table.instance' })}
|
||||
required
|
||||
></SealSelect>
|
||||
|
||||
@@ -1,16 +1,42 @@
|
||||
import SealInput from '@/components/seal-form/seal-input';
|
||||
import SealSelect from '@/components/seal-form/seal-select';
|
||||
import useAppUtils from '@/hooks/use-app-utils';
|
||||
import { useIntl } from '@umijs/max';
|
||||
import { Form } from 'antd';
|
||||
import React from 'react';
|
||||
import React, { useEffect } from 'react';
|
||||
import { profileOptions } from '../config';
|
||||
import { useFormContext } from '../config/form-context';
|
||||
import { FormData } from '../config/types';
|
||||
import useQueryDataset from '../services/use-query-dataset';
|
||||
import RandomSettingsForm from './random-settings';
|
||||
|
||||
const DatasetForm: React.FC = () => {
|
||||
const intl = useIntl();
|
||||
const form = Form.useFormInstance();
|
||||
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 (
|
||||
<>
|
||||
@@ -40,11 +66,20 @@ const DatasetForm: React.FC = () => {
|
||||
]}
|
||||
>
|
||||
<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' })}
|
||||
required
|
||||
></SealSelect>
|
||||
</Form.Item>
|
||||
<Form.Item<FormData> hidden name="dataset_id">
|
||||
<SealInput.Input></SealInput.Input>
|
||||
</Form.Item>
|
||||
<RandomSettingsForm></RandomSettingsForm>
|
||||
<Form.Item<FormData>
|
||||
name="request_rate"
|
||||
|
||||
@@ -22,6 +22,7 @@ interface ProviderFormProps {
|
||||
ref?: any;
|
||||
action: PageActionType;
|
||||
currentData?: ListItem; // Used when action is EDIT
|
||||
open?: boolean;
|
||||
onFinish: (values: FormData) => Promise<void>;
|
||||
}
|
||||
|
||||
@@ -31,9 +32,10 @@ const TABKeysMap = {
|
||||
};
|
||||
|
||||
const ProviderForm: React.FC<ProviderFormProps> = forwardRef((props, ref) => {
|
||||
const { action, currentData, onFinish } = props;
|
||||
const { action, currentData, onFinish, open } = props;
|
||||
const intl = useIntl();
|
||||
const [form] = Form.useForm();
|
||||
|
||||
const { getScrollElementScrollableHeight } = useWrapperContext();
|
||||
const [activeKey, setActiveKey] = useState<string[]>([TABKeysMap.PROFILE]);
|
||||
const scrollTabsRef = useRef<any>(null);
|
||||
@@ -91,7 +93,12 @@ const ProviderForm: React.FC<ProviderFormProps> = forwardRef((props, ref) => {
|
||||
}}
|
||||
getScrollElementScrollableHeight={getScrollElementScrollableHeight}
|
||||
>
|
||||
<FormContext.Provider value={{ action }}>
|
||||
<FormContext.Provider
|
||||
value={{
|
||||
action,
|
||||
open
|
||||
}}
|
||||
>
|
||||
<Form form={form} onFinish={onFinish} initialValues={{}}>
|
||||
<Basic />
|
||||
<CollapsePanel
|
||||
|
||||
@@ -14,7 +14,7 @@ const DatasetForm: React.FC = () => {
|
||||
return (
|
||||
<>
|
||||
<Form.Item<FormData>
|
||||
name="input_length"
|
||||
name="dataset_prompt_tokens"
|
||||
rules={[
|
||||
{
|
||||
required: true,
|
||||
@@ -26,13 +26,22 @@ const DatasetForm: React.FC = () => {
|
||||
]}
|
||||
>
|
||||
<SealSelect
|
||||
options={[]}
|
||||
options={[
|
||||
{
|
||||
label: '100',
|
||||
value: 100
|
||||
},
|
||||
{
|
||||
label: '128',
|
||||
value: 128
|
||||
}
|
||||
]}
|
||||
label={intl.formatMessage({ id: 'benchmark.table.inputTokenLength' })}
|
||||
required
|
||||
></SealSelect>
|
||||
</Form.Item>
|
||||
<Form.Item<FormData>
|
||||
name="output_length"
|
||||
name="dataset_output_tokens"
|
||||
rules={[
|
||||
{
|
||||
required: true,
|
||||
@@ -44,7 +53,16 @@ const DatasetForm: React.FC = () => {
|
||||
]}
|
||||
>
|
||||
<SealSelect
|
||||
options={[]}
|
||||
options={[
|
||||
{
|
||||
label: '4',
|
||||
value: 4
|
||||
},
|
||||
{
|
||||
label: '8',
|
||||
value: 8
|
||||
}
|
||||
]}
|
||||
label={intl.formatMessage({
|
||||
id: 'benchmark.table.outputTokenLength'
|
||||
})}
|
||||
|
||||
@@ -5,8 +5,10 @@ import icons from '@/components/icon-font/icons';
|
||||
import StatusTag from '@/components/status-tag';
|
||||
import { tableSorter } from '@/config/settings';
|
||||
import { useIntl } from '@umijs/max';
|
||||
import { Typography } from 'antd';
|
||||
import { ColumnsType } from 'antd/es/table';
|
||||
import dayjs from 'dayjs';
|
||||
import _ from 'lodash';
|
||||
import { useMemo } from 'react';
|
||||
import { BenchmarkStatus, BenchmarkStatusLabelMap } from '../config';
|
||||
import { BenchmarkListItem as ListItem } from '../config/types';
|
||||
@@ -29,7 +31,8 @@ const actionList = [
|
||||
|
||||
const useBenchmarkColumns = (
|
||||
sortOrder: string[],
|
||||
handleSelect: (val: string, record: ListItem) => void
|
||||
handleSelect: (val: string, record: ListItem) => void,
|
||||
onCellClick?: (record: ListItem, dataIndex: string) => void
|
||||
): ColumnsType<ListItem> => {
|
||||
const intl = useIntl();
|
||||
|
||||
@@ -39,9 +42,11 @@ const useBenchmarkColumns = (
|
||||
title: intl.formatMessage({ id: 'common.table.name' }),
|
||||
dataIndex: 'name',
|
||||
sorter: tableSorter(1),
|
||||
render: (text: string) => (
|
||||
render: (text: string, record) => (
|
||||
<AutoTooltip ghost minWidth={20}>
|
||||
{text}
|
||||
<Typography.Link onClick={() => onCellClick?.(record, 'name')}>
|
||||
{text}
|
||||
</Typography.Link>
|
||||
</AutoTooltip>
|
||||
)
|
||||
},
|
||||
@@ -68,6 +73,10 @@ const useBenchmarkColumns = (
|
||||
{
|
||||
title: intl.formatMessage({ id: 'common.table.status' }),
|
||||
dataIndex: 'state',
|
||||
ellipsis: {
|
||||
showTitle: false
|
||||
},
|
||||
width: 100,
|
||||
render: (value: number, record: ListItem) => (
|
||||
<StatusTag
|
||||
statusValue={{
|
||||
@@ -100,51 +109,51 @@ const useBenchmarkColumns = (
|
||||
},
|
||||
{
|
||||
title: intl.formatMessage({ id: 'benchmark.table.itl' }),
|
||||
dataIndex: 'itl',
|
||||
dataIndex: 'inter_token_latency_mean',
|
||||
sorter: tableSorter(1),
|
||||
render: (text: string) => (
|
||||
<AutoTooltip ghost minWidth={20}>
|
||||
{text}
|
||||
{_.round(text, 1)}
|
||||
</AutoTooltip>
|
||||
)
|
||||
},
|
||||
{
|
||||
title: intl.formatMessage({ id: 'benchmark.table.tpot' }),
|
||||
dataIndex: 'tpot',
|
||||
dataIndex: 'time_per_output_token_mean',
|
||||
sorter: tableSorter(1),
|
||||
render: (text: string) => (
|
||||
<AutoTooltip ghost minWidth={20}>
|
||||
{text}
|
||||
{_.round(text, 2)}
|
||||
</AutoTooltip>
|
||||
)
|
||||
},
|
||||
{
|
||||
title: intl.formatMessage({ id: 'benchmark.table.ttft' }),
|
||||
dataIndex: 'ttft',
|
||||
dataIndex: 'time_to_first_token_mean',
|
||||
sorter: tableSorter(1),
|
||||
render: (text: string) => (
|
||||
<AutoTooltip ghost minWidth={20}>
|
||||
{text}
|
||||
{_.round(text, 2)}
|
||||
</AutoTooltip>
|
||||
)
|
||||
},
|
||||
{
|
||||
title: intl.formatMessage({ id: 'benchmark.table.rps' }),
|
||||
dataIndex: 'requests_per_second',
|
||||
dataIndex: 'requests_per_second_mean',
|
||||
sorter: tableSorter(1),
|
||||
render: (text: string) => (
|
||||
<AutoTooltip ghost minWidth={20}>
|
||||
{text}
|
||||
{_.round(text, 0)}
|
||||
</AutoTooltip>
|
||||
)
|
||||
},
|
||||
{
|
||||
title: intl.formatMessage({ id: 'benchmark.table.tps' }),
|
||||
dataIndex: 'tokens_per_second',
|
||||
dataIndex: 'tokens_per_second_mean',
|
||||
sorter: tableSorter(1),
|
||||
render: (text: string) => (
|
||||
<AutoTooltip ghost minWidth={20}>
|
||||
{text}
|
||||
{_.round(text, 2)}
|
||||
</AutoTooltip>
|
||||
)
|
||||
},
|
||||
@@ -153,7 +162,9 @@ const useBenchmarkColumns = (
|
||||
dataIndex: 'created_at',
|
||||
sorter: tableSorter(3),
|
||||
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;
|
||||
|
||||
@@ -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 { TABLE_SORT_DIRECTIONS } from '@/config/settings';
|
||||
import useTableFetch from '@/hooks/use-table-fetch';
|
||||
import { useIntl } from '@umijs/max';
|
||||
import { useIntl, useNavigate } from '@umijs/max';
|
||||
import { useMemoizedFn } from 'ahooks';
|
||||
import { ConfigProvider, Table, message } from 'antd';
|
||||
import _ from 'lodash';
|
||||
@@ -17,11 +17,14 @@ import {
|
||||
updateBenchmark
|
||||
} from './apis';
|
||||
import AddBenchmarkModal from './components/add-benchmark-modal';
|
||||
import DetailModal from './components/detail-modal';
|
||||
import LeftActions from './components/left-actions';
|
||||
import RightActions from './components/right-actions';
|
||||
import { FormData, BenchmarkListItem as ListItem } from './config/types';
|
||||
import useBenchmarkColumns from './hooks/use-benchmark-columns';
|
||||
import useColumnSettings from './hooks/use-column-settings';
|
||||
import useCreateBenchmark from './hooks/use-create-benchmark';
|
||||
import useViewDetail from './hooks/use-view-detail';
|
||||
|
||||
const Benchmark: React.FC = () => {
|
||||
const {
|
||||
@@ -43,8 +46,15 @@ const Benchmark: React.FC = () => {
|
||||
contentForDelete: 'menu.models.benchmark'
|
||||
});
|
||||
const intl = useIntl();
|
||||
const navigate = useNavigate();
|
||||
const { openBenchmarkModal, closeBenchmarkModal, openBenchmarkModalStatus } =
|
||||
useCreateBenchmark();
|
||||
const {
|
||||
openViewDetailModal,
|
||||
closeViewDetailModal,
|
||||
openViewDetailModalStatus
|
||||
} = useViewDetail();
|
||||
const { SettingsButton, selectedColumns } = useColumnSettings();
|
||||
|
||||
const handleAddBenchmark = () => {
|
||||
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) => {
|
||||
if (type !== 'Table') return;
|
||||
return (
|
||||
@@ -111,7 +131,11 @@ const Benchmark: React.FC = () => {
|
||||
);
|
||||
};
|
||||
|
||||
const columns = useBenchmarkColumns(sortOrder, handleSelect);
|
||||
const columns = useBenchmarkColumns(
|
||||
sortOrder,
|
||||
handleSelect,
|
||||
handleOnCellClick
|
||||
);
|
||||
|
||||
return (
|
||||
<>
|
||||
@@ -132,6 +156,7 @@ const Benchmark: React.FC = () => {
|
||||
}
|
||||
right={
|
||||
<RightActions
|
||||
settingButton={SettingsButton}
|
||||
handleDeleteByBatch={handleDeleteBatch}
|
||||
handleClickPrimary={handleAddBenchmark}
|
||||
buttonText={intl.formatMessage({
|
||||
@@ -171,6 +196,11 @@ const Benchmark: React.FC = () => {
|
||||
onCancel={handleModalCancel}
|
||||
onOk={handleModalOk}
|
||||
></AddBenchmarkModal>
|
||||
<DetailModal
|
||||
open={openViewDetailModalStatus.open}
|
||||
currentData={openViewDetailModalStatus.currentData}
|
||||
onClose={closeViewDetailModal}
|
||||
></DetailModal>
|
||||
<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 =====================
|
||||
|
||||
export async function queryClusterList(params: Global.SearchParams) {
|
||||
export async function queryClusterList(
|
||||
params: Global.SearchParams,
|
||||
options?: any
|
||||
) {
|
||||
return request<Global.PageResponse<ClusterListItem>>(`${CLUSTERS_API}`, {
|
||||
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}
|
||||
keyboard={true}
|
||||
styles={{
|
||||
content: {
|
||||
wrapper: {
|
||||
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'];
|
||||
|
||||
export const requestConfig: RequestConfig = {
|
||||
headers: {
|
||||
'Content-Security-Policy': "frame-ancestors 'self'",
|
||||
'X-Frame-Options': 'SAMEORIGIN'
|
||||
},
|
||||
errorConfig: {
|
||||
errorThrower: (res: any) => {
|
||||
// to do something
|
||||
|
||||
Reference in New Issue
Block a user