diff --git a/react-ui/src/components/BasicInfo/index.less b/react-ui/src/components/BasicInfo/index.less
index ffeb3d3d..e4570868 100644
--- a/react-ui/src/components/BasicInfo/index.less
+++ b/react-ui/src/components/BasicInfo/index.less
@@ -38,13 +38,8 @@
margin-left: 16px;
font-size: @font-size-content;
line-height: 1.6;
- white-space: pre-line;
word-break: break-all;
- &--ellipsis {
- .singleLine();
- }
-
&__text {
color: @text-color;
}
diff --git a/react-ui/src/components/BasicInfo/index.tsx b/react-ui/src/components/BasicInfo/index.tsx
index 60cd5b57..bd07db34 100644
--- a/react-ui/src/components/BasicInfo/index.tsx
+++ b/react-ui/src/components/BasicInfo/index.tsx
@@ -120,13 +120,10 @@ export function BasicInfoItemValue({
}
return (
-
- {component}
-
+
+
+ {component}
+
+
);
}
diff --git a/react-ui/src/components/BasicTableInfo/index.less b/react-ui/src/components/BasicTableInfo/index.less
index cc3d0984..314b05ca 100644
--- a/react-ui/src/components/BasicTableInfo/index.less
+++ b/react-ui/src/components/BasicTableInfo/index.less
@@ -33,10 +33,10 @@
&__value {
flex: 1;
+ min-width: 0;
margin: 0 !important;
padding: 12px 20px 4px;
font-size: @font-size;
- white-space: pre-line;
word-break: break-all;
& + & {
@@ -47,10 +47,6 @@
padding-bottom: 12px;
}
- &--ellipsis {
- .singleLine();
- }
-
&__text {
color: @text-color;
}
diff --git a/react-ui/src/enums/pagesEnums.ts b/react-ui/src/enums/pagesEnums.ts
index 756d3325..d11eb8b5 100644
--- a/react-ui/src/enums/pagesEnums.ts
+++ b/react-ui/src/enums/pagesEnums.ts
@@ -1,3 +1,4 @@
export enum PageEnum {
LOGIN = '/user/login',
+ Authorize = '/authorize',
}
diff --git a/react-ui/src/hooks/index.ts b/react-ui/src/hooks/index.ts
index 6f5bdbbc..f5ef64af 100644
--- a/react-ui/src/hooks/index.ts
+++ b/react-ui/src/hooks/index.ts
@@ -162,7 +162,7 @@ export const useCheck = (list: T[]) => {
const [selected, setSelected] = useState([]);
const checked = useMemo(() => {
- return selected.length === list.length;
+ return selected.length === list.length && selected.length > 0;
}, [selected, list]);
const indeterminate = useMemo(() => {
diff --git a/react-ui/src/pages/Experiment/Comparison/index.tsx b/react-ui/src/pages/Experiment/Comparison/index.tsx
index 1667a064..1edfd0a3 100644
--- a/react-ui/src/pages/Experiment/Comparison/index.tsx
+++ b/react-ui/src/pages/Experiment/Comparison/index.tsx
@@ -10,6 +10,7 @@ import {
getExpMetricsReq,
getExpTrainInfosReq,
} from '@/services/experiment';
+import { tableSorter } from '@/utils';
import { to } from '@/utils/promise';
import tableCellRender, { TableCellValueType } from '@/utils/table';
import { useSearchParams } from '@umijs/max';
@@ -103,7 +104,7 @@ function ExperimentComparison() {
};
// 选择行
- const rowSelection: TableProps['rowSelection'] = {
+ const rowSelection: TableProps['rowSelection'] = {
type: 'checkbox',
columnWidth: 48,
fixed: 'left',
@@ -126,8 +127,10 @@ function ExperimentComparison() {
}
};
- const columns: TableProps['columns'] = useMemo(() => {
+ const columns: TableProps['columns'] = useMemo(() => {
const first: TableData | undefined = tableData[0];
+ const metricsNames = first?.metrics_names ?? [];
+ const paramsNames = first?.params_names ?? [];
return [
{
title: '基本信息',
@@ -175,7 +178,7 @@ function ExperimentComparison() {
{
title: `${config.title}参数`,
align: 'center',
- children: first?.params_names.map((name) => ({
+ children: paramsNames.map((name) => ({
title: (
{name}
@@ -187,14 +190,14 @@ function ExperimentComparison() {
align: 'center',
render: tableCellRender(true),
ellipsis: { showTitle: false },
- sorter: (a, b) => a.params[name] - b.params[name],
+ sorter: (a, b) => tableSorter(a.params[name], b.params[name]),
showSorterTooltip: false,
})),
},
{
title: `${config.title}指标`,
align: 'center',
- children: first?.metrics_names.map((name) => ({
+ children: metricsNames.map((name) => ({
title: (
{name}
@@ -206,7 +209,7 @@ function ExperimentComparison() {
align: 'center',
render: tableCellRender(true),
ellipsis: { showTitle: false },
- sorter: (a, b) => a.metrics[name] - b.metrics[name],
+ sorter: (a, b) => tableSorter(a.metrics[name], b.metrics[name]),
showSorterTooltip: false,
})),
},
diff --git a/react-ui/src/pages/Model/components/ModelMetrics/index.tsx b/react-ui/src/pages/Model/components/ModelMetrics/index.tsx
index 7ffd7fbb..d1f8d2d6 100644
--- a/react-ui/src/pages/Model/components/ModelMetrics/index.tsx
+++ b/react-ui/src/pages/Model/components/ModelMetrics/index.tsx
@@ -1,6 +1,7 @@
import SubAreaTitle from '@/components/SubAreaTitle';
import { useCheck } from '@/hooks';
import { getModelPageVersionsReq, getModelVersionsMetricsReq } from '@/services/dataset';
+import { tableSorter } from '@/utils';
import { to } from '@/utils/promise';
import tableCellRender from '@/utils/table';
import { Checkbox, Table, Tooltip, type TablePaginationConfig, type TableProps } from 'antd';
@@ -18,7 +19,7 @@ type TableData = {
metrics_names?: string[];
metrics?: Record;
params_names?: string[];
- params?: Record;
+ params?: Record;
};
type ModelMetricsProps = {
@@ -113,14 +114,18 @@ function ModelMetrics({ resourceId, identifier, owner, version }: ModelMetricsPr
};
// 分页切换
- const handleTableChange: TableProps['onChange'] = (pagination, _filters, _sorter, { action }) => {
+ const handleTableChange: TableProps['onChange'] = (
+ pagination,
+ _filters,
+ _sorter,
+ { action },
+ ) => {
if (action === 'paginate') {
setPagination(pagination);
}
- // console.log(pagination, filters, sorter, action);
};
- const rowSelection: TableProps['rowSelection'] = {
+ const rowSelection: TableProps['rowSelection'] = {
type: 'checkbox',
fixed: 'left',
selectedRowKeys,
@@ -142,10 +147,12 @@ function ModelMetrics({ resourceId, identifier, owner, version }: ModelMetricsPr
}, [version, tableData]);
// 表头
- const columns: TableProps['columns'] = useMemo(() => {
+ const columns: TableProps['columns'] = useMemo(() => {
const first: TableData | undefined = tableData.find(
(item) => item.metrics_names && item.metrics_names.length > 0,
);
+ const metricsNames = first?.metrics_names ?? [];
+ const paramsNames = first?.params_names ?? [];
return [
{
title: '基本信息',
@@ -165,7 +172,7 @@ function ModelMetrics({ resourceId, identifier, owner, version }: ModelMetricsPr
{
title: `训练参数`,
align: 'center',
- children: first?.params_names?.map((name) => ({
+ children: paramsNames.map((name) => ({
title: (
{name}
@@ -177,7 +184,7 @@ function ModelMetrics({ resourceId, identifier, owner, version }: ModelMetricsPr
align: 'center',
render: tableCellRender(true),
ellipsis: { showTitle: false },
- sorter: (a, b) => a.params?.[name] ?? 0 - b.params?.[name] ?? 0,
+ sorter: (a, b) => tableSorter(a.params?.[name], b.params?.[name]),
showSorterTooltip: false,
})),
},
@@ -188,12 +195,13 @@ function ModelMetrics({ resourceId, identifier, owner, version }: ModelMetricsPr
checked={metricsChecked}
indeterminate={metricsIndeterminate}
onChange={checkAllMetrics}
+ disabled={metricsNames.length === 0}
>{' '}
训练指标
),
align: 'center',
- children: first?.metrics_names?.map((name) => ({
+ children: metricsNames.map((name) => ({
title: (
a.metrics?.[name] ?? 0 - b.metrics?.[name] ?? 0,
+ sorter: (a, b) => tableSorter(a.metrics?.[name], b.metrics?.[name]),
showSorterTooltip: false,
})),
},
diff --git a/react-ui/src/pages/ModelDeployment/ServiceInfo/index.tsx b/react-ui/src/pages/ModelDeployment/ServiceInfo/index.tsx
index 42b80e87..07d40de5 100644
--- a/react-ui/src/pages/ModelDeployment/ServiceInfo/index.tsx
+++ b/react-ui/src/pages/ModelDeployment/ServiceInfo/index.tsx
@@ -18,6 +18,7 @@ import {
} from '@/services/modelDeployment';
import themes from '@/styles/theme.less';
import { formatDate } from '@/utils/date';
+import { openAntdModal } from '@/utils/modal';
import { to } from '@/utils/promise';
import SessionStorage from '@/utils/sessionStorage';
import tableCellRender, { TableCellValueType } from '@/utils/table';
@@ -37,6 +38,7 @@ import { type SearchProps } from 'antd/es/input';
import classNames from 'classnames';
import { useEffect, useState } from 'react';
import ServiceRunStatusCell from '../components/ModelDeployStatusCell';
+import VersionCompareModal from '../components/VersionCompareModal';
import {
CreateServiceVersionFrom,
ServiceData,
@@ -56,6 +58,7 @@ function ServiceInfo() {
const [inputText, setInputText] = useState(cacheState?.searchText);
const [tableData, setTableData] = useState([]);
const [total, setTotal] = useState(0);
+ const [selectedRowKeys, setSelectedRowKeys] = useState([]);
const [pagination, setPagination] = useState(
cacheState?.pagination ?? {
current: 1,
@@ -208,11 +211,39 @@ function ServiceInfo() {
};
// 分页切换
- const handleTableChange: TableProps['onChange'] = (pagination, _filters, _sorter, { action }) => {
+ const handleTableChange: TableProps['onChange'] = (
+ pagination,
+ _filters,
+ _sorter,
+ { action },
+ ) => {
if (action === 'paginate') {
setPagination(pagination);
}
- // console.log(pagination, filters, sorter, action);
+ };
+
+ // 版本对比
+ const handleVersionCompare = () => {
+ if (selectedRowKeys.length !== 2) {
+ message.error('请选择两个版本进行对比');
+ return;
+ }
+
+ openAntdModal(VersionCompareModal, {
+ version1: selectedRowKeys[0] as string,
+ version2: selectedRowKeys[1] as string,
+ });
+ };
+
+ // 选择行
+ const rowSelection: TableProps['rowSelection'] = {
+ type: 'checkbox',
+ columnWidth: 48,
+ fixed: 'left',
+ selectedRowKeys,
+ onChange: (selectedRowKeys: React.Key[]) => {
+ setSelectedRowKeys(selectedRowKeys);
+ },
};
const columns: TableProps['columns'] = [
@@ -379,13 +410,16 @@ function ServiceInfo() {
allowClear
>
+
diff --git a/react-ui/src/pages/ModelDeployment/components/VersionCompareModal/index.less b/react-ui/src/pages/ModelDeployment/components/VersionCompareModal/index.less
new file mode 100644
index 00000000..f0d5449e
--- /dev/null
+++ b/react-ui/src/pages/ModelDeployment/components/VersionCompareModal/index.less
@@ -0,0 +1,117 @@
+@purple-color: #6516ff;
+
+.title(@color, @background) {
+ width: 100%;
+ margin-bottom: 20px;
+ color: @color;
+ font-weight: 500;
+ font-size: @font-size;
+ line-height: 42px;
+ text-align: center;
+ background: @background;
+ .singleLine();
+}
+
+.text() {
+ margin-bottom: 20px !important;
+ color: @text-color-secondary;
+ font-size: 13px;
+ word-break: break-all;
+ .singleLine();
+}
+
+.version-container(@background) {
+ flex: 1;
+ min-width: 0;
+ background: @background;
+ border-radius: 4px;
+}
+
+.version-compare {
+ :global {
+ .ant-modal-content {
+ padding: 40px 40px 25px !important;
+ }
+ .ant-modal-header {
+ margin-bottom: 20px !important;
+ }
+ .kf-modal-title {
+ color: @text-color;
+ font-weight: 500;
+ font-size: 20px;
+ }
+ }
+
+ &__container {
+ display: flex;
+ flex-wrap: nowrap;
+ gap: 0 5px;
+ align-items: stretch;
+ height: 100%;
+ }
+
+ &__fields {
+ flex: none;
+ width: 117px;
+ padding: 0 15px;
+ background: white;
+ border: 1px solid .addAlpha(@primary-color, 0.2) [];
+ border-radius: 4px;
+
+ &__title {
+ margin-bottom: 20px;
+ color: @text-color;
+ font-size: @font-size;
+ line-height: 42px;
+ }
+ &__text {
+ .text();
+
+ &--different {
+ color: @error-color;
+ }
+ }
+ }
+
+ &__left {
+ .version-container(.addAlpha(@primary-color, 0.04) []);
+
+ &__title {
+ .title(@primary-color, linear-gradient(
+ 159.9deg,rgba(138, 177, 255, 0.5) 0%,
+ rgba(22, 100, 255, 0.5) 100%
+ ));
+ }
+
+ &__text {
+ padding: 0 15px;
+ text-align: center;
+ .text();
+
+ &--different {
+ color: @primary-color;
+ }
+ }
+ }
+
+ &__right {
+ .version-container(rgba(100, 30, 237, 0.04));
+ &__title {
+ .title(@purple-color, linear-gradient(
+ 159.9deg,
+ rgba(193, 138, 255, 0.5) 0%,
+ rgba(146, 22, 255, 0.5) 100%
+ ));
+ }
+
+ &__text {
+ padding: 0 15px;
+ text-align: center;
+ .text();
+
+ &--different {
+ color: @purple-color;
+ }
+ }
+ }
+}
diff --git a/react-ui/src/pages/ModelDeployment/components/VersionCompareModal/index.tsx b/react-ui/src/pages/ModelDeployment/components/VersionCompareModal/index.tsx
new file mode 100644
index 00000000..be28ba3b
--- /dev/null
+++ b/react-ui/src/pages/ModelDeployment/components/VersionCompareModal/index.tsx
@@ -0,0 +1,193 @@
+import KFModal from '@/components/KFModal';
+import { ServiceRunStatus } from '@/enums';
+import { useComputingResource } from '@/hooks/resource';
+import { type ServiceVersionData } from '@/pages/ModelDeployment/types';
+import { getServiceVersionCompareReq } from '@/services/modelDeployment';
+import { to } from '@/utils/promise';
+import { Typography, type ModalProps } from 'antd';
+import classNames from 'classnames';
+import { useEffect, useMemo, useState } from 'react';
+import { statusInfo } from '../ModelDeployStatusCell';
+import styles from './index.less';
+
+type CompareData = {
+ differences: Record;
+ version1: ServiceVersionData;
+ version2: ServiceVersionData;
+};
+
+type ServiceVersionDataKey = keyof ServiceVersionData;
+
+type FiledType = {
+ key: ServiceVersionDataKey;
+ text: string;
+ format?: (data: any) => any;
+};
+
+interface CreateMirrorModalProps extends Omit {
+ version1: string;
+ version2: string;
+}
+
+// 格式化环境变量
+const formatEnvText = (env: Record) => {
+ if (!env || Object.keys(env).length === 0) {
+ return '--';
+ }
+ return Object.entries(env)
+ .map(([key, value]) => `${key} = ${value}`)
+ .join(',');
+};
+
+function VersionCompareModal({ version1, version2, ...rest }: CreateMirrorModalProps) {
+ const [compareData, setCompareData] = useState(undefined);
+ const getResourceDescription = useComputingResource()[2];
+
+ const fields: FiledType[] = useMemo(
+ () => [
+ {
+ key: 'service_name',
+ text: '服务名称',
+ },
+ {
+ key: 'run_state',
+ text: '状态',
+ format: (data: any) => {
+ return data ? statusInfo[data as ServiceRunStatus].text : '--';
+ },
+ },
+ {
+ key: 'image',
+ text: '镜像',
+ },
+ {
+ key: 'code_config',
+ text: '代码配置',
+ format: (data: any) => {
+ return data?.show_value;
+ },
+ },
+ {
+ key: 'model',
+ text: '模型',
+ format: (data: any) => {
+ return data?.show_value;
+ },
+ },
+ {
+ key: 'resource',
+ text: '资源规格',
+ format: getResourceDescription,
+ },
+ {
+ key: 'replicas',
+ text: '副本数',
+ },
+ {
+ key: 'mount_path',
+ text: '挂载路径',
+ },
+ {
+ key: 'url',
+ text: '服务URL',
+ },
+ {
+ key: 'env_variables',
+ text: '环境变量',
+ format: formatEnvText,
+ },
+ {
+ key: 'description',
+ text: '描述',
+ },
+ ],
+ [getResourceDescription],
+ );
+
+ useEffect(() => {
+ getServiceVersionCompare();
+ }, []);
+
+ // 获取对比数据
+ const getServiceVersionCompare = async () => {
+ const params = {
+ id1: version1,
+ id2: version2,
+ };
+ const [res] = await to(getServiceVersionCompareReq(params));
+ if (res && res.data) {
+ setCompareData(res.data);
+ }
+ };
+
+ const {
+ version1: v1 = {} as ServiceVersionData,
+ version2: v2 = {} as ServiceVersionData,
+ differences = {},
+ } = compareData || {};
+
+ const isDifferent = (key: ServiceVersionDataKey) => {
+ const keys = Object.keys(differences);
+ return keys.includes(key);
+ };
+
+ return (
+
+
+
+
基础版本号
+ {fields.map(({ key, text }) => (
+
+ {text}
+
+ ))}
+
+
+
{v1.version}
+ {fields.map(({ key, format }) => {
+ const text = format ? format(v1[key]) : v1[key];
+ return (
+
+ {text}
+
+ );
+ })}
+
+
+
{v2.version}
+ {fields.map(({ key, format }) => {
+ const text = format ? format(v2[key]) : v2[key];
+ return (
+
+ {text}
+
+ );
+ })}
+
+
+
+ );
+}
+
+export default VersionCompareModal;
diff --git a/react-ui/src/services/modelDeployment/index.ts b/react-ui/src/services/modelDeployment/index.ts
index eccf841e..9a33687f 100644
--- a/react-ui/src/services/modelDeployment/index.ts
+++ b/react-ui/src/services/modelDeployment/index.ts
@@ -104,3 +104,11 @@ export function getServiceVersionLogReq(params: any) {
params,
});
}
+
+// 获取服务版本对比
+export function getServiceVersionCompareReq(params: any) {
+ return request(`/api/mmp/service/serviceVersionCompare`, {
+ method: 'GET',
+ params,
+ });
+}
diff --git a/react-ui/src/utils/index.ts b/react-ui/src/utils/index.ts
index 0af6f5aa..67ca10d2 100644
--- a/react-ui/src/utils/index.ts
+++ b/react-ui/src/utils/index.ts
@@ -4,6 +4,7 @@
* @Description: 工具类
*/
+import { PageEnum } from '@/enums/pagesEnums';
import G6 from '@antv/g6';
// 生成 8 位随机数
@@ -218,3 +219,25 @@ export const getGitUrl = (url: string, branch: string): string => {
const gitUrl = url.replace(/\.git$/, '');
return branch ? `${gitUrl}/tree/${branch}` : gitUrl;
};
+
+// 判断是否需要登录
+export const needAuth = (pathname: string) => {
+ return pathname !== PageEnum.LOGIN && pathname !== PageEnum.Authorize;
+};
+
+// 表格排序
+export const tableSorter = (a: any, b: any) => {
+ if (b === null || b === undefined) {
+ return -1;
+ }
+ if (a === null || a === undefined) {
+ return 1;
+ }
+ if (typeof a === 'number' && typeof b === 'number') {
+ return a - b;
+ }
+ if (typeof a === 'string' && typeof b === 'string') {
+ return a.localeCompare(b);
+ }
+ return 0;
+};