From 5747cae87a73b6a2234a8a398b73865690af4c09 Mon Sep 17 00:00:00 2001 From: jialin Date: Wed, 28 Jan 2026 14:51:19 +0800 Subject: [PATCH] style: detail layout --- src/locales/en-US/benchmark.ts | 2 +- src/locales/ja-JP/benchmark.ts | 2 +- src/locales/ru-RU/benchmark.ts | 2 +- src/locales/zh-CN/benchmark.ts | 2 +- src/pages/benchmark/apis/index.ts | 17 + .../components/environment/index.tsx | 141 +++++--- .../components/summary/benchmark.tsx | 10 +- .../benchmark/components/summary/index.tsx | 16 +- .../benchmark/components/summary/instance.tsx | 39 ++- .../components/summary/metrics-result.tsx | 317 ++++++++++++------ .../components/summary/percentile-result.tsx | 32 +- .../benchmark/components/summary/title.tsx | 21 ++ src/pages/benchmark/config/index.ts | 5 +- src/pages/benchmark/config/types.ts | 1 + src/pages/benchmark/forms/basic.tsx | 1 + src/pages/benchmark/forms/dataset.tsx | 12 +- src/pages/benchmark/forms/index.tsx | 24 +- .../benchmark/hooks/use-benchmark-columns.tsx | 27 +- src/pages/benchmark/index.tsx | 10 +- .../services/use-export-benchmark.ts | 45 +++ .../login/components/local-user-form.tsx | 2 + src/pages/login/components/login-form.tsx | 3 +- src/pages/login/hooks/use-local-auth.ts | 9 +- 23 files changed, 506 insertions(+), 234 deletions(-) create mode 100644 src/pages/benchmark/components/summary/title.tsx create mode 100644 src/pages/benchmark/services/use-export-benchmark.ts diff --git a/src/locales/en-US/benchmark.ts b/src/locales/en-US/benchmark.ts index f515d968..ff47730d 100644 --- a/src/locales/en-US/benchmark.ts +++ b/src/locales/en-US/benchmark.ts @@ -21,7 +21,7 @@ export default { 'benchmark.form.profile.longContext': 'Long Context', 'benchmark.form.profile.heavy': 'Generation Heavy', 'benchmark.form.profile.custom': 'Custom', - 'benchmark.table.inputTokenLength': 'Input Token Length', + 'benchmark.table.inputTokenLength': 'Prompt Token Length', 'benchmark.table.outputTokenLength': 'Output Token Length', 'benchmark.detail.summary.title': 'Summary', 'benchmark.detail.configure.title': 'Configure', diff --git a/src/locales/ja-JP/benchmark.ts b/src/locales/ja-JP/benchmark.ts index f515d968..ff47730d 100644 --- a/src/locales/ja-JP/benchmark.ts +++ b/src/locales/ja-JP/benchmark.ts @@ -21,7 +21,7 @@ export default { 'benchmark.form.profile.longContext': 'Long Context', 'benchmark.form.profile.heavy': 'Generation Heavy', 'benchmark.form.profile.custom': 'Custom', - 'benchmark.table.inputTokenLength': 'Input Token Length', + 'benchmark.table.inputTokenLength': 'Prompt Token Length', 'benchmark.table.outputTokenLength': 'Output Token Length', 'benchmark.detail.summary.title': 'Summary', 'benchmark.detail.configure.title': 'Configure', diff --git a/src/locales/ru-RU/benchmark.ts b/src/locales/ru-RU/benchmark.ts index f515d968..ff47730d 100644 --- a/src/locales/ru-RU/benchmark.ts +++ b/src/locales/ru-RU/benchmark.ts @@ -21,7 +21,7 @@ export default { 'benchmark.form.profile.longContext': 'Long Context', 'benchmark.form.profile.heavy': 'Generation Heavy', 'benchmark.form.profile.custom': 'Custom', - 'benchmark.table.inputTokenLength': 'Input Token Length', + 'benchmark.table.inputTokenLength': 'Prompt Token Length', 'benchmark.table.outputTokenLength': 'Output Token Length', 'benchmark.detail.summary.title': 'Summary', 'benchmark.detail.configure.title': 'Configure', diff --git a/src/locales/zh-CN/benchmark.ts b/src/locales/zh-CN/benchmark.ts index 5860e71d..da26b9ec 100644 --- a/src/locales/zh-CN/benchmark.ts +++ b/src/locales/zh-CN/benchmark.ts @@ -21,7 +21,7 @@ export default { 'benchmark.form.profile.longContext': '长上下文', 'benchmark.form.profile.heavy': '高生成量', 'benchmark.form.profile.custom': '自定义', - 'benchmark.table.inputTokenLength': '输入 Token 长度', + 'benchmark.table.inputTokenLength': '提示词 Token 长度', 'benchmark.table.outputTokenLength': '输出 Token 长度', 'benchmark.detail.summary.title': '摘要', 'benchmark.detail.configure.title': '配置', diff --git a/src/pages/benchmark/apis/index.ts b/src/pages/benchmark/apis/index.ts index c6ea7d96..791b7ac6 100644 --- a/src/pages/benchmark/apis/index.ts +++ b/src/pages/benchmark/apis/index.ts @@ -10,6 +10,7 @@ import { export const BENCHMARKS_API = '/benchmarks'; export const DATASETS_API = '/datasets'; export const PROFILES_CONFIG_API = '/benchmark-profiles/default-config'; +export const EXPORT_BENCHMARK_LIST = '/benchmarks/export'; export async function queryBenchmarkList( params: Global.SearchParams, @@ -103,3 +104,19 @@ export async function queryProfiles( cancelToken: options?.token }); } + +export async function exportBenchmarkList( + params: { + ids?: number[]; + }, + options?: { + token?: CancelToken; + } +) { + return request(`${BENCHMARKS_API}/export`, { + method: 'POST', + data: params.ids, + responseType: 'blob', + cancelToken: options?.token + }); +} diff --git a/src/pages/benchmark/components/environment/index.tsx b/src/pages/benchmark/components/environment/index.tsx index d416ea75..26b5e525 100644 --- a/src/pages/benchmark/components/environment/index.tsx +++ b/src/pages/benchmark/components/environment/index.tsx @@ -4,7 +4,8 @@ import SealTable from '@/components/seal-table/index'; import useExpandedRowKeys from '@/hooks/use-expanded-row-keys'; import { convertFileSize } from '@/utils'; import useMemoizedFn from 'ahooks/lib/useMemoizedFn'; -import { Col, Row } from 'antd'; +import { Col, Row, Tag } from 'antd'; +import _ from 'lodash'; import React, { useMemo } from 'react'; import styled from 'styled-components'; import { useDetailContext } from '../../config/detail-context'; @@ -32,30 +33,31 @@ const Environment: React.FC = () => { return { ...workerInfo, + ..._.pick(gpuData?.[0], ['driver_version', 'runtime_version']), isMain: true, children: gpuData }; }, [snapshot]); - // const subWorkers = useMemo(() => { - // const [[mainWorkerName, mainWorkerInfo]] = Object.entries(snapshot.workers); + const subWorkers = useMemo(() => { + const [[mainWorkerName, mainWorkerInfo]] = Object.entries(snapshot.workers); - // const subOrdinaryWorkers = Object.values(snapshot.instances).filter( - // (instance) => instance.worker_name === mainWorkerName - // ); + 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 subOrdinaryWorkers.map((worker) => { + const gpuData = Object.values(snapshot.gpus).filter( + (gpu) => gpu.worker_name === worker.worker_name + ); - // return { - // ...worker, - // isMain: false, - // children: gpuData - // }; - // }); - // }, [snapshot, mainWorker]); + return { + ...worker, + isMain: false, + children: gpuData + }; + }); + }, [snapshot, mainWorker]); const GPUColumns = [ { @@ -68,19 +70,26 @@ const Environment: React.FC = () => { {value} ) }, + { + title: 'Index', + dataIndex: 'index', + key: 'index', + span: 4, + colStyle: { paddingLeft: 48 } + }, { title: 'Vendor', dataIndex: 'vendor', key: 'vendor', - span: 4, - colStyle: { paddingLeft: 46 } + span: 6, + colStyle: { paddingLeft: 110 } }, { title: 'VRAM', dataIndex: 'memory_total', key: 'memory_total', label: 'VRAM', - span: 3, + span: 4, render: (value: number, record: any) => convertFileSize(value) }, { @@ -88,20 +97,8 @@ const Environment: React.FC = () => { dataIndex: 'core_total', key: 'core_total', label: 'Cores', - span: 3 - }, - { - title: 'Runtime Version', - dataIndex: 'runtime_version', - key: 'runtime_version', - span: 4 - }, - { - title: 'Driver Version', - dataIndex: 'driver_version', - key: 'driver_version', span: 4, - colStyle: { paddingLeft: 35 } + colStyle: { paddingLeft: 36 } } ]; @@ -110,31 +107,56 @@ const Environment: React.FC = () => { title: 'Worker Name', dataIndex: 'name', key: 'name', - span: 6 + span: 6, + render: (value: string, record: any) => { + return ( + <> + {value} + {record.isMain && ( + + Main + + )} + + ); + } }, { title: 'System', dataIndex: 'os', key: 'system', - span: 4, + span: 5, render: (os: { name: string; version: string }, record: any) => { - return `${record.os.name}`; + return ( + {`${record.os.name} (${record.os.version})`} + ); } }, { - title: 'Version', - dataIndex: 'version', - key: 'version', - span: 6, + title: 'Runtime Version', + dataIndex: 'runtime_version', + key: 'runtime_version', + span: 3, render: (val: any, record: any) => { - return {record.os.version}; + return {record.runtime_version}; + } + }, + { + title: 'Driver Version', + dataIndex: 'driver_version', + key: 'driver_version', + span: 3, + render: (val: any, record: any) => { + return {record.driver_version}; } }, { title: 'CPU Count', dataIndex: 'cpu_total', key: 'cpu_total', - span: 4 + span: 3 }, { title: 'Memory', @@ -146,8 +168,8 @@ const Environment: React.FC = () => { ]; const dataList = useMemo(() => { - return [mainWorker]; - }, [mainWorker]); + return [mainWorker, ...subWorkers]; + }, [mainWorker, subWorkers]); const handleToggleExpandAll = useMemoizedFn((expanded: boolean) => { const keys = dataList?.map((item) => item.id); @@ -161,6 +183,29 @@ const Environment: React.FC = () => { const renderChildren = useMemoizedFn((list: any[]) => { return (
+ + {GPUColumns.map((col) => ( + + + + {col.title} + + + + ))} + {list.map((gpu) => ( @@ -174,14 +219,6 @@ const Environment: React.FC = () => { flexDirection: 'column' }} > - - {col.title} - {col.render ? col.render((gpu as any)[col.dataIndex], gpu) : (gpu as any)[col.dataIndex]} diff --git a/src/pages/benchmark/components/summary/benchmark.tsx b/src/pages/benchmark/components/summary/benchmark.tsx index b18b6bd5..f9b940fd 100644 --- a/src/pages/benchmark/components/summary/benchmark.tsx +++ b/src/pages/benchmark/components/summary/benchmark.tsx @@ -1,7 +1,7 @@ import { Descriptions, DescriptionsProps } from 'antd'; import React from 'react'; import { useDetailContext } from '../../config/detail-context'; -import Section from './section'; +import Title from './title'; const Benchmark: React.FC = () => { const { detailData } = useDetailContext(); @@ -19,7 +19,7 @@ const Benchmark: React.FC = () => { }, { key: '3', - label: 'Token Length (Prompt/Out)', + label: 'Token Length (Prompt/Output)', children: ( {detailData?.dataset_prompt_tokens || '-'} /{' '} @@ -45,19 +45,19 @@ const Benchmark: React.FC = () => { ]; return ( -
+
+ Parameters -
+
); }; diff --git a/src/pages/benchmark/components/summary/index.tsx b/src/pages/benchmark/components/summary/index.tsx index 816ff6cc..59d10078 100644 --- a/src/pages/benchmark/components/summary/index.tsx +++ b/src/pages/benchmark/components/summary/index.tsx @@ -1,9 +1,11 @@ +import { Divider } from 'antd'; import React from 'react'; import styled from 'styled-components'; import BenchMark from './benchmark'; import Instance from './instance'; import MetricsResult from './metrics-result'; import PercentileResult from './percentile-result'; +import Section from './section'; const Container = styled.div` display: flex; @@ -14,10 +16,16 @@ const Container = styled.div` const Summary: React.FC = () => { return ( - - - - +
+ + + +
+
+ + + +
); }; diff --git a/src/pages/benchmark/components/summary/instance.tsx b/src/pages/benchmark/components/summary/instance.tsx index 49bf4c5c..31a23687 100644 --- a/src/pages/benchmark/components/summary/instance.tsx +++ b/src/pages/benchmark/components/summary/instance.tsx @@ -1,8 +1,9 @@ +import AutoTooltip from '@/components/auto-tooltip'; import { Descriptions, Flex, Tag } from 'antd'; import _ from 'lodash'; import React, { useMemo } from 'react'; import { useDetailContext } from '../../config/detail-context'; -import Section from './section'; +import Title from './title'; const calcTotalVram = (vram: Record) => { return _.sum(_.values(vram)); @@ -10,6 +11,8 @@ const calcTotalVram = (vram: Record) => { const Instance: React.FC = () => { const { detailData } = useDetailContext(); + const [, instanceData] = + Object.entries(detailData?.snapshot?.instances || {})[0] || []; const items = useMemo(() => { const { snapshot } = detailData; @@ -18,25 +21,21 @@ const Instance: React.FC = () => { return [ { key: '1', - label: 'Instance Name', + label: 'Model Name', children: ( -
- - {detailData?.model_name}/{detailData?.model_instance_name} - -
+ {detailData?.model_name || '-'} ) }, { key: '2', - label: 'Worker', - children: instanceData?.worker_name || '-' - }, - { - key: '6', - label: 'GPU Type', - children: instanceData?.gpu_type || '-' + label: 'Instance Name', + children: ( + + {detailData?.model_instance_name || '-'} + + ) }, + { key: '5', label: 'Backend', @@ -45,6 +44,13 @@ const Instance: React.FC = () => { ? `(${instanceData?.backend_version})` : '' }` + }, + { + key: '6', + label: 'Model File', + children: ( + {instanceData?.resolved_path || '-'} + ) } ]; }, [detailData]); @@ -144,7 +150,8 @@ const Instance: React.FC = () => { }, [detailData]); return ( -
+
+ Instance { } }} > -
+ ); }; diff --git a/src/pages/benchmark/components/summary/metrics-result.tsx b/src/pages/benchmark/components/summary/metrics-result.tsx index ba313909..f1d5bc15 100644 --- a/src/pages/benchmark/components/summary/metrics-result.tsx +++ b/src/pages/benchmark/components/summary/metrics-result.tsx @@ -1,25 +1,46 @@ import { Descriptions } from 'antd'; import _, { round } from 'lodash'; import React from 'react'; +import styled from 'styled-components'; import { useDetailContext } from '../../config/detail-context'; -import Section from './section'; +import Title from './title'; + +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 1fr; + gap: 16px; +`; + +const DescWrapper = styled.div` + display: grid; + grid-template-columns: 1fr 1fr 1fr; + gap: 24px; +`; const columns = [ - { - title: 'Duration (s)', - dataIndex: 'duration', - path: ['raw_metrics', 'benchmarks', '0', 'duration'], - unit: 's', - render: (value: number) => round(value, 2) - }, + // { + // title: 'Duration', + // dataIndex: 'duration', + // path: ['raw_metrics', 'benchmarks', '0', 'duration'], + // unit: 's', + // render: (value: number) => round(value, 2) + // }, - { - title: 'Total Requests', - dataIndex: 'total_requests', - path: 'total_requests', - unit: '', - render: (value: number) => round(value, 0) - }, + // { + // title: 'Total Requests', + // dataIndex: 'total_requests', + // path: 'total_requests', + // unit: '', + // render: (value: number) => round(value, 0) + // }, { title: 'Total token throughput', dataIndex: 'tokens_per_second_mean', @@ -28,18 +49,25 @@ const columns = [ render: (value: number) => round(value, 2) }, { - title: 'Success Requests', - dataIndex: 'successful_requests', - path: ['raw_metrics', 'benchmarks', '0'], - unit: '', - render: (value: number) => { - return ( - - {_.get(value, 'metrics.request_totals.successful')} - - ); - } + title: 'Prompt token throughput ', + dataIndex: 'prompt_tokens_per_second_mean', + path: 'prompt_tokens_per_second_mean', + unit: 'Tokens/s', + render: (value: number) => round(value, 2) }, + // { + // title: 'Success Requests', + // dataIndex: 'successful_requests', + // path: ['raw_metrics', 'benchmarks', '0'], + // unit: '', + // render: (value: number) => { + // return ( + // + // {_.get(value, 'metrics.request_totals.successful')} + // + // ); + // } + // }, { title: 'Output token throughput', @@ -51,65 +79,103 @@ const columns = [ ]; const columnsSub = [ + // { + // title: 'Failed Requests', + // dataIndex: 'failed_requests', + // path: ['raw_metrics', 'benchmarks', '0'], + // unit: '', + // render: (value: number) => { + // return ( + // + // {_.get(value, 'metrics.request_totals.errored')} + // + // ); + // } + // }, + // { + // title: 'Concurrency', + // dataIndex: 'request_concurrency', + // path: ['raw_metrics', 'benchmarks', '0'], + // unit: '', + // render: (value: number) => { + // return round( + // _.get(value, 'metrics.request_concurrency.successful.mean'), + // 0 + // ); + // } + // }, + { - title: 'Failed Requests', - dataIndex: 'failed_requests', - path: ['raw_metrics', 'benchmarks', '0'], - unit: '', - render: (value: number) => { - return ( - - {_.get(value, 'metrics.request_totals.errored')} - - ); - } - }, - { - title: 'Concurrency', - dataIndex: 'request_concurrency', - path: ['raw_metrics', 'benchmarks', '0'], - unit: '', - render: (value: number) => { - return round( - _.get(value, 'metrics.request_concurrency.successful.mean'), - 0 - ); - } - }, - { - title: 'Request token throughput ', - dataIndex: 'prompt_tokens_per_second_mean', - path: 'prompt_tokens_per_second_mean', - unit: 'Tokens/s', - render: (value: number) => round(value, 2) - }, - { - title: 'Average Request Latency (ms)', + title: 'Average Request Latency', dataIndex: 'request_latency_mean', path: 'request_latency_mean', unit: 'ms', render: (value: number) => round(value, 2) }, { - title: 'Average Time To First Token (ms)', + title: 'Average Time To First Token', dataIndex: 'time_to_first_token_mean', path: 'time_to_first_token_mean', unit: 'ms', render: (value: number) => round(value, 2) }, { - title: 'Average Time Per Output Token (ms)', + title: 'Average Time Per Output Token', dataIndex: 'time_per_output_token_mean', path: 'time_per_output_token_mean', unit: 'ms', render: (value: number) => round(value, 2) }, { - title: '', - dataIndex: '', - path: '', - unit: '', - render: (_: number) => '' + title: 'Average Inter Token Latency', + dataIndex: 'inter_token_latency_mean', + path: 'inter_token_latency_mean', + unit: 'ms', + render: (value: number) => round(value, 2) + } +]; + +const requestFields = [ + { + label: 'Total Requests', + key: 'total_requests', + dataIndex: 'total_requests', + path: 'total_requests', + precision: 0, + render: (value: number) => round(value, 0), + unit: '' + }, + { + label: 'Success Requests', + key: 'total_requests', + dataIndex: 'successful_requests', + path: ['raw_metrics', 'benchmarks', '0'], + render: (value: number) => + round(_.get(value, ['metrics', 'request_totals', 'successful']), 0), + precision: 0, + color: 'var(--ant-color-success)', + unit: '' + }, + { + label: 'Failed Requests', + key: 'total_requests', + dataIndex: 'failed_requests', + path: ['raw_metrics', 'benchmarks', '0'], + render: (value: number) => + round(_.get(value, ['metrics', 'request_totals', 'errored']), 0), + precision: 0, + color: 'var(--ant-color-error)', + unit: '' + }, + { + label: 'Concurrency', + key: 'request_concurrency', + dataIndex: 'request_concurrency', + path: ['raw_metrics', 'benchmarks', '0'], + render: (value: number) => + round(_.get(value, 'metrics.request_concurrency.successful.mean'), 0), + precision: 0, + unit: '' } ]; @@ -148,39 +214,94 @@ const PercentileResult: React.FC = () => { }) ); + const throughputItems = columns.map( + ({ title, dataIndex, path, render, unit }) => ({ + key: dataIndex, + label: title, + children: unit ? ( + + {render(_.get(detailData, path) ?? 0)}{' '} + ({unit}) + + ) : ( + render(_.get(detailData, path) ?? 0) + ) + }) + ); + + const latencyItems = columnsSub.map( + ({ title, dataIndex, path, render, unit }) => ({ + key: dataIndex, + label: title, + children: unit ? ( + + {render(_.get(detailData, path) ?? 0)}{' '} + ({unit}) + + ) : ( + render(_.get(detailData, path) ?? 0) + ) + }) + ); + + const requestItems = requestFields.map( + ({ label, dataIndex, path, render, unit, color }) => ({ + key: dataIndex, + label: label, + children: unit ? ( + + {render(_.get(detailData, path) ?? 0)}{' '} + ({unit}) + + ) : ( + + {render(_.get(detailData, path) ?? 0)} + + ) + }) + ); + + const descriptionStyles = { + header: { + marginBottom: 8 + }, + title: { + fontWeight: 500, + color: 'var(--ant-color-text-secondary)', + fontSize: 14 + }, + content: { + justifyContent: 'flex-start' + } + }; + return ( -
- {/*
*/} - -
+
+ Metrics + + + + + +
); }; diff --git a/src/pages/benchmark/components/summary/percentile-result.tsx b/src/pages/benchmark/components/summary/percentile-result.tsx index 93505327..0b1b64e3 100644 --- a/src/pages/benchmark/components/summary/percentile-result.tsx +++ b/src/pages/benchmark/components/summary/percentile-result.tsx @@ -2,7 +2,7 @@ import { Table } from 'antd'; import { round } from 'lodash'; import React from 'react'; import { useDetailContext } from '../../config/detail-context'; -import Section from './section'; +import Title from './title'; const columns = [ { @@ -76,13 +76,17 @@ const PercentileResult: React.FC = () => { }; return ( -
+
+ Percentile ( + {value} + ) }, ...columns ]} @@ -90,17 +94,15 @@ const PercentileResult: React.FC = () => { rowKey="percentile" pagination={false} styles={{ - // header: { - // row: { - // background: 'none', - // borderBottom: '1px solid var(--ant-color-split)' - // }, - // cell: { - // background: 'none', - // paddingBottom: 0, - // borderBottom: '1px solid var(--ant-color-split)' - // } - // }, + header: { + row: { + backgroundColor: 'transparent' + }, + cell: { + fontWeight: 400, + borderBottom: '1px solid var(--ant-color-split)' + } + }, body: { cell: { height: 54 @@ -108,7 +110,7 @@ const PercentileResult: React.FC = () => { } }} >
-
+ ); }; diff --git a/src/pages/benchmark/components/summary/title.tsx b/src/pages/benchmark/components/summary/title.tsx new file mode 100644 index 00000000..4e4f5dad --- /dev/null +++ b/src/pages/benchmark/components/summary/title.tsx @@ -0,0 +1,21 @@ +import { BulbOutlined } from '@ant-design/icons'; +import styled from 'styled-components'; + +const Content = styled.div` + font-weight: 500; + font-size: 14px; + margin-bottom: 16px; +`; + +const Title: React.FC<{ children: React.ReactNode }> = ({ children }) => { + return ( + + + {children} + + ); +}; + +export default Title; diff --git a/src/pages/benchmark/config/index.ts b/src/pages/benchmark/config/index.ts index 2c5bbcea..f3155ffb 100644 --- a/src/pages/benchmark/config/index.ts +++ b/src/pages/benchmark/config/index.ts @@ -7,6 +7,7 @@ export const BenchmarkStatusValueMap = { Running: 'running', Completed: 'completed', Error: 'error', + Stopped: 'stopped', Unreachable: 'unreachable' }; @@ -16,6 +17,7 @@ export const BenchmarkStatusLabelMap = { [BenchmarkStatusValueMap.Running]: 'Running', [BenchmarkStatusValueMap.Completed]: 'Completed', [BenchmarkStatusValueMap.Error]: 'Error', + [BenchmarkStatusValueMap.Stopped]: 'Stopped', [BenchmarkStatusValueMap.Unreachable]: 'Unreachable' }; @@ -25,7 +27,8 @@ export const BenchmarkStatus: Record = { [BenchmarkStatusValueMap.Running]: StatusMaps.success, [BenchmarkStatusValueMap.Completed]: StatusMaps.success, [BenchmarkStatusValueMap.Error]: StatusMaps.error, - [BenchmarkStatusValueMap.Unreachable]: StatusMaps.error + [BenchmarkStatusValueMap.Unreachable]: StatusMaps.error, + [BenchmarkStatusValueMap.Stopped]: StatusMaps.warning }; export const ProfileValueMap = { diff --git a/src/pages/benchmark/config/types.ts b/src/pages/benchmark/config/types.ts index bdd5b1e5..0654ea0d 100644 --- a/src/pages/benchmark/config/types.ts +++ b/src/pages/benchmark/config/types.ts @@ -91,6 +91,7 @@ export interface BenchmarkListItem extends FormData { updated_at: string; state: string; state_message: string; + progress: number; instance_snapshot: InstanceSnapshot; gpu_snapshot: GPUSnapshot[]; } diff --git a/src/pages/benchmark/forms/basic.tsx b/src/pages/benchmark/forms/basic.tsx index 7addb16e..513e5f10 100644 --- a/src/pages/benchmark/forms/basic.tsx +++ b/src/pages/benchmark/forms/basic.tsx @@ -77,6 +77,7 @@ const BasicForm: React.FC = () => { ]} > { hidden name="dataset_id"> - {profile === 'Custom' && ( - - )} + ); }; diff --git a/src/pages/benchmark/forms/index.tsx b/src/pages/benchmark/forms/index.tsx index 20ff6b40..061fb728 100644 --- a/src/pages/benchmark/forms/index.tsx +++ b/src/pages/benchmark/forms/index.tsx @@ -17,7 +17,6 @@ import FormContext from '../config/form-context'; import { FormData, BenchmarkListItem as ListItem } from '../config/types'; import Basic from './basic'; import DatasetForm from './dataset'; -import RandomSettingsForm from './random-settings'; interface ProviderFormProps { ref?: any; @@ -90,16 +89,16 @@ const ProviderForm: React.FC = forwardRef((props, ref) => { } }, [showAdvanced, activeKey]); - const advancedItems = showAdvanced - ? [ - { - key: TABKeysMap.ADVANCED, - label: intl.formatMessage({ id: 'resources.form.advanced' }), - forceRender: true, - children: - } - ] - : []; + // const advancedItems = showAdvanced + // ? [ + // { + // key: TABKeysMap.ADVANCED, + // label: intl.formatMessage({ id: 'resources.form.advanced' }), + // forceRender: true, + // children: + // } + // ] + // : []; console.log('render form with profile:', showAdvanced); @@ -144,8 +143,7 @@ const ProviderForm: React.FC = forwardRef((props, ref) => { label: intl.formatMessage({ id: 'common.title.config' }), forceRender: true, children: - }, - ...advancedItems + } ]} > diff --git a/src/pages/benchmark/hooks/use-benchmark-columns.tsx b/src/pages/benchmark/hooks/use-benchmark-columns.tsx index 00f18ad6..442e1f7f 100644 --- a/src/pages/benchmark/hooks/use-benchmark-columns.tsx +++ b/src/pages/benchmark/hooks/use-benchmark-columns.tsx @@ -3,7 +3,7 @@ import AutoTooltip from '@/components/auto-tooltip'; import StatusTag from '@/components/status-tag'; import { tableSorter } from '@/config/settings'; import { useIntl } from '@umijs/max'; -import { Typography } from 'antd'; +import { Progress, Typography } from 'antd'; import { ColumnsType } from 'antd/es/table'; import _ from 'lodash'; import { useMemo } from 'react'; @@ -58,15 +58,24 @@ const useBenchmarkColumns = ( ellipsis: { showTitle: false }, - width: 100, + width: 120, render: (value: number, record: ListItem) => ( - + + + {record.progress !== undefined && record.progress < 100 && ( + + )} + ) }, // { diff --git a/src/pages/benchmark/index.tsx b/src/pages/benchmark/index.tsx index 4e24dce1..7c4f5728 100644 --- a/src/pages/benchmark/index.tsx +++ b/src/pages/benchmark/index.tsx @@ -27,8 +27,8 @@ 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 useExportData from './hooks/use-export-data'; import useViewLogs from './hooks/use-view-logs'; +import { useExportBenchmark } from './services/use-export-benchmark'; import useQueryDataset from './services/use-query-dataset'; const Benchmark: React.FC = () => { @@ -65,6 +65,7 @@ const Benchmark: React.FC = () => { const { SettingsButton, selectedColumns } = useColumnSettings(); const { datasetList, fetchDatasetData } = useQueryDataset(); + const { exportData } = useExportBenchmark(); useEffect(() => { fetchModelList({ page: -1 }); @@ -154,13 +155,8 @@ const Benchmark: React.FC = () => { handleOnCellClick ); - const { exportData } = useExportData({ columns: columns }); - const handleExportData = () => { - const list = dataSource.dataList.filter((item) => - rowSelection.selectedRowKeys.includes(item.id) - ); - exportData(list); + exportData(rowSelection.selectedRowKeys); }; return ( diff --git a/src/pages/benchmark/services/use-export-benchmark.ts b/src/pages/benchmark/services/use-export-benchmark.ts new file mode 100644 index 00000000..dd066a02 --- /dev/null +++ b/src/pages/benchmark/services/use-export-benchmark.ts @@ -0,0 +1,45 @@ +import { GPUSTACK_API_BASE_URL } from '@/config/settings'; +import { downloadFile } from '@/utils/download-stream'; +import { message } from 'antd'; +import { EXPORT_BENCHMARK_LIST } from '../apis'; + +const matchFilename = (disposition: string | null): string | undefined => { + if (!disposition) return ''; + + const match = disposition.match(/filename="?([^"]+)"?/); + const filename = match ? match[1] : ''; + return filename; +}; + +export function useExportBenchmark() { + const exportData = async (data: any[]) => { + try { + const res = await fetch( + `${GPUSTACK_API_BASE_URL}${EXPORT_BENCHMARK_LIST}`, + { + method: 'POST', + headers: { + 'Content-Type': 'application/json' + }, + body: JSON.stringify(data) + } + ); + // header + const contentDispostion = res.headers.get('content-Disposition'); + const filename = + matchFilename(contentDispostion) || `benchmark-export.yml`; + if (res.ok) { + const blob = await res.blob(); + downloadFile(blob, filename); + } else { + message.error('Download failed'); + } + } catch (error) { + message.error('Download failed'); + } + }; + + return { + exportData + }; +} diff --git a/src/pages/login/components/local-user-form.tsx b/src/pages/login/components/local-user-form.tsx index ed35920f..edd919c2 100644 --- a/src/pages/login/components/local-user-form.tsx +++ b/src/pages/login/components/local-user-form.tsx @@ -36,6 +36,7 @@ const useStyles = createStyles(({ token, css }) => { interface LocalUserFormProps { handleLogin: (values: any) => void; form: FormInstance; + loading?: boolean; loginOption: { saml: boolean; oidc: boolean; @@ -144,6 +145,7 @@ const LocalUserForm: React.FC = (props) => { htmlType="submit" type="primary" block + loading={props.loading} style={{ height: '48px', fontSize: '14px' }} > {intl.formatMessage({ id: 'common.button.login' })} diff --git a/src/pages/login/components/login-form.tsx b/src/pages/login/components/login-form.tsx index ed4d6144..ac3cde04 100644 --- a/src/pages/login/components/login-form.tsx +++ b/src/pages/login/components/login-form.tsx @@ -148,7 +148,7 @@ const LoginForm = () => { }; // local user authentication - const { handleLogin } = useLocalAuth({ + const { handleLogin, submitLoading } = useLocalAuth({ fetchUserInfo, form, onSuccess: async (userInfo) => { @@ -254,6 +254,7 @@ const LoginForm = () => { )} diff --git a/src/pages/login/hooks/use-local-auth.ts b/src/pages/login/hooks/use-local-auth.ts index d6fa3412..58e9794b 100644 --- a/src/pages/login/hooks/use-local-auth.ts +++ b/src/pages/login/hooks/use-local-auth.ts @@ -10,7 +10,7 @@ import { import { FormInstance } from 'antd'; import CryptoJS from 'crypto-js'; import { useAtom } from 'jotai'; -import { useEffect } from 'react'; +import { useEffect, useState } from 'react'; import { login } from '../apis'; interface UseLocalAuthOptions { @@ -27,6 +27,7 @@ export const useLocalAuth = ({ form }: UseLocalAuthOptions) => { const [initialPassword, setInitialPassword] = useAtom(initialPasswordAtom); + const [submitLoading, setSubmitLoading] = useState(false); // Encrypt password before storing const encryptPassword = (password: string) => { @@ -66,6 +67,7 @@ export const useLocalAuth = ({ // click login button const handleLogin = async (values: any) => { + setSubmitLoading(true); try { await login({ username: values.username, @@ -86,6 +88,8 @@ export const useLocalAuth = ({ onSuccess?.(userInfo); } catch (error: any) { onError?.(error); + } finally { + setSubmitLoading(false); } }; @@ -94,6 +98,7 @@ export const useLocalAuth = ({ }, []); return { - handleLogin + handleLogin, + submitLoading }; };