Browse Source

Merge remote-tracking branch 'origin/dev' into dev

dev-czh
chenzhihang 1 year ago
parent
commit
fef0428b62
65 changed files with 1279 additions and 1659 deletions
  1. +1
    -15
      react-ui/config/config.ts
  2. +0
    -593
      react-ui/config/oneapi.json
  3. +2
    -9
      react-ui/src/app.tsx
  4. BIN
      react-ui/src/assets/img/metrics-title-icon.png
  5. BIN
      react-ui/src/assets/img/model-metrics.png
  6. +43
    -37
      react-ui/src/components/BasicInfo/index.less
  7. +70
    -36
      react-ui/src/components/BasicInfo/index.tsx
  8. +64
    -0
      react-ui/src/components/BasicTableInfo/index.less
  9. +43
    -0
      react-ui/src/components/BasicTableInfo/index.tsx
  10. +0
    -25
      react-ui/src/components/CommonTableCell/index.tsx
  11. +0
    -20
      react-ui/src/components/DateTableCell/index.tsx
  12. +1
    -1
      react-ui/src/components/RobotFrame/index.less
  13. +5
    -3
      react-ui/src/components/SubAreaTitle/index.tsx
  14. +43
    -1
      react-ui/src/hooks/index.ts
  15. +2
    -5
      react-ui/src/hooks/pageCacheState.ts
  16. +6
    -0
      react-ui/src/overrides.less
  17. +1
    -1
      react-ui/src/pages/CodeConfig/components/CodeConfigItem/index.less
  18. +6
    -4
      react-ui/src/pages/Dataset/components/ResourceInfo/index.less
  19. +10
    -1
      react-ui/src/pages/Dataset/components/ResourceInfo/index.tsx
  20. +21
    -6
      react-ui/src/pages/Dataset/components/ResourceIntro/index.less
  21. +67
    -67
      react-ui/src/pages/Dataset/components/ResourceIntro/index.tsx
  22. +5
    -0
      react-ui/src/pages/Dataset/components/ResourceVersion/index.less
  23. +10
    -15
      react-ui/src/pages/Dataset/components/ResourceVersion/index.tsx
  24. +5
    -6
      react-ui/src/pages/DevelopmentEnvironment/List/index.tsx
  25. +1
    -1
      react-ui/src/pages/Experiment/Comparison/index.less
  26. +11
    -8
      react-ui/src/pages/Experiment/Comparison/index.tsx
  27. +2
    -2
      react-ui/src/pages/Experiment/Info/index.jsx
  28. +28
    -7
      react-ui/src/pages/Experiment/components/AddExperimentModal/index.tsx
  29. +6
    -1
      react-ui/src/pages/Experiment/components/ExperimentInstance/index.less
  30. +66
    -2
      react-ui/src/pages/Experiment/components/ExperimentInstance/index.tsx
  31. +5
    -3
      react-ui/src/pages/Experiment/components/LogGroup/index.tsx
  32. +2
    -1
      react-ui/src/pages/Experiment/components/LogList/index.tsx
  33. +32
    -20
      react-ui/src/pages/Experiment/index.jsx
  34. +5
    -6
      react-ui/src/pages/Mirror/Info/index.tsx
  35. +5
    -6
      react-ui/src/pages/Mirror/List/index.tsx
  36. +29
    -0
      react-ui/src/pages/Model/components/MetricsChart/index.less
  37. +174
    -0
      react-ui/src/pages/Model/components/MetricsChart/index.tsx
  38. +33
    -0
      react-ui/src/pages/Model/components/MetricsChart/tooltip.css
  39. +5
    -2
      react-ui/src/pages/Model/components/ModelEvolution/index.less
  40. +35
    -0
      react-ui/src/pages/Model/components/ModelMetrics/index.less
  41. +259
    -0
      react-ui/src/pages/Model/components/ModelMetrics/index.tsx
  42. +12
    -16
      react-ui/src/pages/ModelDeployment/List/index.tsx
  43. +13
    -27
      react-ui/src/pages/ModelDeployment/ServiceInfo/index.tsx
  44. +3
    -3
      react-ui/src/pages/Pipeline/Info/index.jsx
  45. +1
    -1
      react-ui/src/pages/Pipeline/components/PipelineNodeDrawer/index.tsx
  46. +12
    -15
      react-ui/src/pages/Pipeline/index.jsx
  47. +2
    -1
      react-ui/src/pages/User/Login/index.tsx
  48. +5
    -5
      react-ui/src/pages/Workspace/components/ExperimentChart/index.tsx
  49. +0
    -11
      react-ui/src/services/ant-design-pro/api.ts
  50. +0
    -12
      react-ui/src/services/ant-design-pro/index.ts
  51. +0
    -38
      react-ui/src/services/ant-design-pro/login.ts
  52. +0
    -42
      react-ui/src/services/ant-design-pro/rule.ts
  53. +0
    -114
      react-ui/src/services/ant-design-pro/typings.d.ts
  54. +16
    -0
      react-ui/src/services/dataset/index.js
  55. +12
    -2
      react-ui/src/services/experiment/index.js
  56. +0
    -12
      react-ui/src/services/swagger/index.ts
  57. +0
    -153
      react-ui/src/services/swagger/pet.ts
  58. +0
    -48
      react-ui/src/services/swagger/store.ts
  59. +0
    -112
      react-ui/src/services/swagger/typings.d.ts
  60. +0
    -100
      react-ui/src/services/swagger/user.ts
  61. +1
    -1
      react-ui/src/utils/date.ts
  62. +1
    -1
      react-ui/src/utils/index.ts
  63. +4
    -6
      react-ui/src/utils/localStorage.ts
  64. +4
    -8
      react-ui/src/utils/sessionStorage.ts
  65. +90
    -27
      react-ui/src/utils/table.tsx

+ 1
- 15
react-ui/config/config.ts View File

@@ -1,6 +1,5 @@
// https://umijs.org/config/
import { defineConfig } from '@umijs/max';
import { join } from 'path';
import defaultSettings from './defaultSettings';
import proxy from './proxy';
import routes from './routes';
@@ -145,20 +144,7 @@ export default defineConfig({
* @description 基于 openapi 的规范生成serve 和mock,能减少很多样板代码
* @doc https://pro.ant.design/zh-cn/docs/openapi/
*/
openAPI: [
{
requestLibPath: "import { request } from '@umijs/max'",
// 或者使用在线的版本
// schemaPath: "https://gw.alipayobjects.com/os/antfincdn/M%24jrzTTYJN/oneapi.json"
schemaPath: join(__dirname, 'oneapi.json'),
mock: false,
},
{
requestLibPath: "import { request } from '@umijs/max'",
schemaPath: 'https://gw.alipayobjects.com/os/antfincdn/CA1dOm%2631B/openapi.json',
projectName: 'swagger',
},
],
// openAPI: [],
// mfsu: {
// strategy: 'normal',
// },


+ 0
- 593
react-ui/config/oneapi.json View File

@@ -1,593 +0,0 @@
{
"openapi": "3.0.1",
"info": {
"title": "Ant Design Pro",
"version": "1.0.0"
},
"servers": [
{
"url": "http://localhost:8000/"
},
{
"url": "https://localhost:8000/"
}
],
"paths": {
"/api/currentUser": {
"get": {
"tags": ["api"],
"description": "获取当前的用户",
"operationId": "currentUser",
"responses": {
"200": {
"description": "Success",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/CurrentUser"
}
}
}
},
"401": {
"description": "Error",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
},
"x-swagger-router-controller": "api"
},
"/api/login/captcha": {
"post": {
"description": "发送验证码",
"operationId": "getFakeCaptcha",
"tags": ["login"],
"parameters": [
{
"name": "phone",
"in": "query",
"description": "手机号",
"schema": {
"type": "string"
}
}
],
"responses": {
"200": {
"description": "Success",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/FakeCaptcha"
}
}
}
}
}
}
},
"/api/login/outLogin": {
"post": {
"description": "登录接口",
"operationId": "outLogin",
"tags": ["login"],
"responses": {
"200": {
"description": "Success",
"content": {
"application/json": {
"schema": {
"type": "object"
}
}
}
},
"401": {
"description": "Error",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
},
"x-swagger-router-controller": "api"
},
"/api/login/account": {
"post": {
"tags": ["login"],
"description": "登录接口",
"operationId": "login",
"requestBody": {
"description": "登录系统",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/LoginParams"
}
}
},
"required": true
},
"responses": {
"200": {
"description": "Success",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/LoginResult"
}
}
}
},
"401": {
"description": "Error",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
},
"x-codegen-request-body-name": "body"
},
"x-swagger-router-controller": "api"
},
"/api/notices": {
"summary": "getNotices",
"description": "NoticeIconItem",
"get": {
"tags": ["api"],
"operationId": "getNotices",
"responses": {
"200": {
"description": "Success",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/NoticeIconList"
}
}
}
}
}
}
},
"/api/rule": {
"get": {
"tags": ["rule"],
"description": "获取规则列表",
"operationId": "rule",
"parameters": [
{
"name": "current",
"in": "query",
"description": "当前的页码",
"schema": {
"type": "number"
}
},
{
"name": "pageSize",
"in": "query",
"description": "页面的容量",
"schema": {
"type": "number"
}
}
],
"responses": {
"200": {
"description": "Success",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/RuleList"
}
}
}
},
"401": {
"description": "Error",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
},
"post": {
"tags": ["rule"],
"description": "新建规则",
"operationId": "addRule",
"responses": {
"200": {
"description": "Success",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/RuleListItem"
}
}
}
},
"401": {
"description": "Error",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
},
"put": {
"tags": ["rule"],
"description": "新建规则",
"operationId": "updateRule",
"responses": {
"200": {
"description": "Success",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/RuleListItem"
}
}
}
},
"401": {
"description": "Error",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
},
"delete": {
"tags": ["rule"],
"description": "删除规则",
"operationId": "removeRule",
"responses": {
"200": {
"description": "Success",
"content": {
"application/json": {
"schema": {
"type": "object"
}
}
}
},
"401": {
"description": "Error",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
},
"x-swagger-router-controller": "api"
},
"/swagger": {
"x-swagger-pipe": "swagger_raw"
}
},
"components": {
"schemas": {
"CurrentUser": {
"type": "object",
"properties": {
"name": {
"type": "string"
},
"avatar": {
"type": "string"
},
"userid": {
"type": "string"
},
"email": {
"type": "string"
},
"signature": {
"type": "string"
},
"title": {
"type": "string"
},
"group": {
"type": "string"
},
"tags": {
"type": "array",
"items": {
"type": "object",
"properties": {
"key": {
"type": "string"
},
"label": {
"type": "string"
}
}
}
},
"notifyCount": {
"type": "integer",
"format": "int32"
},
"unreadCount": {
"type": "integer",
"format": "int32"
},
"country": {
"type": "string"
},
"access": {
"type": "string"
},
"geographic": {
"type": "object",
"properties": {
"province": {
"type": "object",
"properties": {
"label": {
"type": "string"
},
"key": {
"type": "string"
}
}
},
"city": {
"type": "object",
"properties": {
"label": {
"type": "string"
},
"key": {
"type": "string"
}
}
}
}
},
"address": {
"type": "string"
},
"phone": {
"type": "string"
}
}
},
"LoginResult": {
"type": "object",
"properties": {
"status": {
"type": "string"
},
"type": {
"type": "string"
},
"currentAuthority": {
"type": "string"
}
}
},
"PageParams": {
"type": "object",
"properties": {
"current": {
"type": "number"
},
"pageSize": {
"type": "number"
}
}
},
"RuleListItem": {
"type": "object",
"properties": {
"key": {
"type": "integer",
"format": "int32"
},
"disabled": {
"type": "boolean"
},
"href": {
"type": "string"
},
"avatar": {
"type": "string"
},
"name": {
"type": "string"
},
"owner": {
"type": "string"
},
"desc": {
"type": "string"
},
"callNo": {
"type": "integer",
"format": "int32"
},
"status": {
"type": "integer",
"format": "int32"
},
"updatedAt": {
"type": "string",
"format": "datetime"
},
"createdAt": {
"type": "string",
"format": "datetime"
},
"progress": {
"type": "integer",
"format": "int32"
}
}
},
"RuleList": {
"type": "object",
"properties": {
"data": {
"type": "array",
"items": {
"$ref": "#/components/schemas/RuleListItem"
}
},
"total": {
"type": "integer",
"description": "列表的内容总数",
"format": "int32"
},
"success": {
"type": "boolean"
}
}
},
"FakeCaptcha": {
"type": "object",
"properties": {
"code": {
"type": "integer",
"format": "int32"
},
"status": {
"type": "string"
}
}
},
"LoginParams": {
"type": "object",
"properties": {
"username": {
"type": "string"
},
"password": {
"type": "string"
},
"autoLogin": {
"type": "boolean"
},
"type": {
"type": "string"
}
}
},
"ErrorResponse": {
"required": ["errorCode"],
"type": "object",
"properties": {
"errorCode": {
"type": "string",
"description": "业务约定的错误码"
},
"errorMessage": {
"type": "string",
"description": "业务上的错误信息"
},
"success": {
"type": "boolean",
"description": "业务上的请求是否成功"
}
}
},
"NoticeIconList": {
"type": "object",
"properties": {
"data": {
"type": "array",
"items": {
"$ref": "#/components/schemas/NoticeIconItem"
}
},
"total": {
"type": "integer",
"description": "列表的内容总数",
"format": "int32"
},
"success": {
"type": "boolean"
}
}
},
"NoticeIconItemType": {
"title": "NoticeIconItemType",
"description": "已读未读列表的枚举",
"type": "string",
"properties": {},
"enum": ["notification", "message", "event"]
},
"NoticeIconItem": {
"type": "object",
"properties": {
"id": {
"type": "string"
},
"extra": {
"type": "string",
"format": "any"
},
"key": { "type": "string" },
"read": {
"type": "boolean"
},
"avatar": {
"type": "string"
},
"title": {
"type": "string"
},
"status": {
"type": "string"
},
"datetime": {
"type": "string",
"format": "date"
},
"description": {
"type": "string"
},
"type": {
"extensions": {
"x-is-enum": true
},
"$ref": "#/components/schemas/NoticeIconItemType"
}
}
}
}
}
}

+ 2
- 9
react-ui/src/app.tsx View File

@@ -118,18 +118,10 @@ export const layout: RuntimeConfig['layout'] = ({ initialState }) => {
width: '331px',
},
],
// links: isDev
// ? [
// <Link key="openapi" to="/umi/plugin/openapi" target="_blank">
// <LinkOutlined />
// <span>OpenAPI 文档</span>
// </Link>,
// ]
// : [],
// 自定义 403 页面
// unAccessible: <div>unAccessible</div>,
// 增加一个 loading 的状态
childrenRender: (children) => {
// 增加一个 loading 的状态
// if (initialState?.loading) return <PageLoading />;
return (
<div className="kf-page-container">
@@ -236,6 +228,7 @@ export const antd: RuntimeAntdConfig = (memo) => {
memo.theme.components.Table = {
headerBg: 'rgba(242, 244, 247, 0.36)',
headerBorderRadius: 4,
rowSelectedBg: 'rgba(22, 100, 255, 0.05)',
};
memo.theme.components.Tabs = {
titleFontSize: 16,


BIN
react-ui/src/assets/img/metrics-title-icon.png View File

Before After
Width: 39  |  Height: 39  |  Size: 536 B

BIN
react-ui/src/assets/img/model-metrics.png View File

Before After
Width: 45  |  Height: 45  |  Size: 1.2 kB

+ 43
- 37
react-ui/src/components/BasicInfo/index.less View File

@@ -5,48 +5,54 @@
gap: 20px 40px;
align-items: flex-start;
width: 80%;
}

.kf-basic-info-item {
display: flex;
align-items: flex-start;
width: calc(50% - 20px);
font-size: 16px;
line-height: 1.6;

&__label {
position: relative;
flex: none;
color: @text-color-secondary;
text-align: justify;
text-align-last: justify;

&::after {
position: absolute;
content: ':';
&__item {
display: flex;
align-items: flex-start;
width: calc(50% - 20px);

&__label {
position: relative;
flex: none;
color: @text-color-secondary;
font-size: @font-size-content;
line-height: 1.6;
text-align: justify;
text-align-last: justify;

&::after {
position: absolute;
content: ':';
}
}
}

&__list-value {
display: flex;
flex: 1;
flex-direction: column;
gap: 5px 0;
}
&__value-container {
display: flex;
flex: 1;
flex-direction: column;
gap: 5px 0;
}

&__value {
flex: 1;
margin-left: 16px;
white-space: pre-line;
word-break: break-all;
}
&__value {
flex: 1;
margin-left: 16px;
font-size: @font-size-content;
line-height: 1.6;
white-space: pre-line;
word-break: break-all;

&__text {
color: @text-color;
}
&--ellipsis {
.singleLine();
}

&__text {
color: @text-color;
}

&__link:hover {
text-decoration: underline @underline-color;
text-underline-offset: 3px;
&__link:hover {
text-decoration: underline @underline-color;
text-underline-offset: 3px;
}
}
}
}

+ 70
- 36
react-ui/src/components/BasicInfo/index.tsx View File

@@ -1,4 +1,5 @@
import { Link } from '@umijs/max';
import { Typography } from 'antd';
import classNames from 'classnames';
import './index.less';

@@ -11,6 +12,7 @@ export type BasicInfoLink = {
export type BasicInfoData = {
label: string;
value?: any;
ellipsis?: boolean;
format?: (_value?: any) => string | BasicInfoLink | BasicInfoLink[] | undefined;
};

@@ -18,45 +20,73 @@ type BasicInfoProps = {
datas: BasicInfoData[];
className?: string;
style?: React.CSSProperties;
labelWidth?: number;
labelWidth: number;
};

function BasicInfo({ datas, className, style, labelWidth = 100 }: BasicInfoProps) {
type BasicInfoItemProps = {
data: BasicInfoData;
labelWidth: number;
classPrefix: string;
};

type BasicInfoItemValueProps = BasicInfoLink & {
ellipsis?: boolean;
classPrefix: string;
};

export default function BasicInfo({ datas, className, style, labelWidth }: BasicInfoProps) {
return (
<div className={classNames('kf-basic-info', className)} style={style}>
{datas.map((item) => (
<BasicInfoItem key={item.label} data={item} labelWidth={labelWidth} />
<BasicInfoItem
key={item.label}
data={item}
labelWidth={labelWidth}
classPrefix="kf-basic-info"
/>
))}
</div>
);
}

type BasicInfoItemProps = {
data: BasicInfoData;
labelWidth?: number;
};
function BasicInfoItem({ data, labelWidth = 100 }: BasicInfoItemProps) {
const { label, value, format } = data;
export function BasicInfoItem({ data, labelWidth, classPrefix }: BasicInfoItemProps) {
const { label, value, format, ellipsis } = data;
const formatValue = format ? format(value) : value;
const myClassName = `${classPrefix}__item`;
let valueComponent = undefined;
if (Array.isArray(formatValue)) {
valueComponent = (
<div className="kf-basic-info-item__list-value">
<div className={`${myClassName}__value-container`}>
{formatValue.map((item: BasicInfoLink) => (
<BasicInfoItemValue key={item.value} value={item.value} link={item.link} url={item.url} />
<BasicInfoItemValue
key={item.value}
value={item.value}
link={item.link}
url={item.url}
ellipsis={ellipsis}
classPrefix={classPrefix}
/>
))}
</div>
);
} else if (typeof formatValue === 'object' && formatValue) {
valueComponent = (
<BasicInfoItemValue value={formatValue.value} link={formatValue.link} url={formatValue.url} />
<BasicInfoItemValue
value={formatValue.value}
link={formatValue.link}
url={formatValue.url}
ellipsis={ellipsis}
classPrefix={classPrefix}
/>
);
} else {
valueComponent = <BasicInfoItemValue value={formatValue} />;
valueComponent = (
<BasicInfoItemValue value={formatValue} ellipsis={ellipsis} classPrefix={classPrefix} />
);
}
return (
<div className="kf-basic-info-item" key={label}>
<div className="kf-basic-info-item__label" style={{ width: labelWidth }}>
<div className={myClassName} key={label}>
<div className={`${myClassName}__label`} style={{ width: labelWidth }}>
{label}
</div>
{valueComponent}
@@ -64,35 +94,39 @@ function BasicInfoItem({ data, labelWidth = 100 }: BasicInfoItemProps) {
);
}

type BasicInfoItemValueProps = {
value: string;
link?: string;
url?: string;
};

function BasicInfoItemValue({ value, link, url }: BasicInfoItemValueProps) {
export function BasicInfoItemValue({
value,
link,
url,
ellipsis,
classPrefix,
}: BasicInfoItemValueProps) {
const myClassName = `${classPrefix}__item__value`;
let component = undefined;
if (url && value) {
return (
<a
className="kf-basic-info-item__value kf-basic-info-item__link"
href={url}
target="_blank"
rel="noopener noreferrer"
>
component = (
<a className={`${myClassName}__link`} href={url} target="_blank" rel="noopener noreferrer">
{value}
</a>
);
} else if (link && value) {
return (
<Link to={link} className="kf-basic-info-item__value kf-basic-info-item__link">
component = (
<Link to={link} className={`${myClassName}__link`}>
{value}
</Link>
);
} else {
return (
<div className="kf-basic-info-item__value kf-basic-info-item__text">{value ?? '--'}</div>
);
component = <span className={`${myClassName}__text`}>{value ?? '--'}</span>;
}
}

export default BasicInfo;
return (
<Typography.Text
className={classNames(myClassName, {
[`${myClassName}--ellipsis`]: ellipsis,
})}
ellipsis={{ tooltip: value }}
>
{component}
</Typography.Text>
);
}

+ 64
- 0
react-ui/src/components/BasicTableInfo/index.less View File

@@ -0,0 +1,64 @@
.kf-basic-table-info {
display: flex;
flex-direction: row;
flex-wrap: wrap;
align-items: stretch;
width: 100%;
border: 1px solid @border-color-base;
border-bottom: none;
border-radius: 4px;

&__item {
display: flex;
align-items: stretch;
width: 25%;
border-bottom: 1px solid @border-color-base;

&__label {
flex: none;
padding: 12px 20px;
color: @text-color-secondary;
font-size: 14px;
text-align: left;
background-color: .addAlpha(#606b7a, 0.05) [];
}

&__value-container {
display: flex;
flex: 1;
flex-direction: column;
align-items: flex-start;
min-width: 0;
}

&__value {
flex: 1;
margin: 0 !important;
padding: 12px 20px 4px;
font-size: @font-size;
white-space: pre-line;
word-break: break-all;

& + & {
padding-top: 0;
}

&:last-child {
padding-bottom: 12px;
}

&--ellipsis {
.singleLine();
}

&__text {
color: @text-color;
}

&__link:hover {
text-decoration: underline @underline-color;
text-underline-offset: 3px;
}
}
}
}

+ 43
- 0
react-ui/src/components/BasicTableInfo/index.tsx View File

@@ -0,0 +1,43 @@
import classNames from 'classnames';
import { BasicInfoItem, type BasicInfoData, type BasicInfoLink } from '../BasicInfo';
import './index.less';
export type { BasicInfoData, BasicInfoLink };

type BasicTableInfoProps = {
datas: BasicInfoData[];
className?: string;
style?: React.CSSProperties;
labelWidth: number;
};

export default function BasicTableInfo({
datas,
className,
style,
labelWidth,
}: BasicTableInfoProps) {
const remainder = datas.length % 4;
const array = [];
if (remainder > 0) {
for (let i = 0; i < 4 - remainder; i++) {
array.push({
label: '',
value: '',
});
}
}
const showDatas = [...datas, ...array];

return (
<div className={classNames('kf-basic-table-info', className)} style={style}>
{showDatas.map((item) => (
<BasicInfoItem
key={item.label}
data={item}
labelWidth={labelWidth}
classPrefix="kf-basic-table-info"
/>
))}
</div>
);
}

+ 0
- 25
react-ui/src/components/CommonTableCell/index.tsx View File

@@ -1,25 +0,0 @@
/*
* @Author: 赵伟
* @Date: 2024-04-28 14:18:11
* @Description: 自定义 Table 单元格,没有数据时展示 --
*/

import { Tooltip } from 'antd';

function renderCell(text?: any | null) {
return <span>{text ?? '--'}</span>;
}

function CommonTableCell(ellipsis: boolean = false) {
if (ellipsis) {
return (text?: any | null) => (
<Tooltip title={text} placement="topLeft" overlayStyle={{ maxWidth: '400px' }}>
{renderCell(text)}
</Tooltip>
);
} else {
return renderCell;
}
}

export default CommonTableCell;

+ 0
- 20
react-ui/src/components/DateTableCell/index.tsx View File

@@ -1,20 +0,0 @@
/*
* @Author: 赵伟
* @Date: 2024-04-28 14:18:11
* @Description: 自定义 Table 日期类单元格
*/

import { formatDate } from '@/utils/date';
import dayjs from 'dayjs';

function DateTableCell(text?: string | null) {
if (text === undefined || text === null || text === '') {
return <span>--</span>;
}
if (!dayjs(text).isValid()) {
return <span>无效的日期</span>;
}
return <span>{formatDate(text)}</span>;
}

export default DateTableCell;

+ 1
- 1
react-ui/src/components/RobotFrame/index.less View File

@@ -23,7 +23,7 @@
width: 100%;
height: 60px;
padding: 0 15px;
border-bottom: 1px solid #e8e8e8;
border-bottom: 1px solid @border-color-base;
}

&__iframe {


+ 5
- 3
react-ui/src/components/SubAreaTitle/index.tsx View File

@@ -9,7 +9,7 @@ import './index.less';

type SubAreaTitleProps = {
title: string;
image: string;
image?: string;
style?: React.CSSProperties;
className?: string;
};
@@ -17,8 +17,10 @@ type SubAreaTitleProps = {
function SubAreaTitle({ title, image, style, className }: SubAreaTitleProps) {
return (
<div className={classNames('kf-subarea-title', className)} style={style}>
<img src={image} width={14} draggable={false} alt="" />
<span style={{ marginLeft: '8px' }}>{title}</span>
{image && (
<img src={image} width={18} draggable={false} alt="" style={{ marginRight: '8px' }} />
)}
<span>{title}</span>
</div>
);
}


+ 43
- 1
react-ui/src/hooks/index.ts View File

@@ -5,7 +5,7 @@
*/
import { FormInstance } from 'antd';
import { debounce } from 'lodash';
import { useCallback, useEffect, useRef, useState } from 'react';
import { useCallback, useEffect, useMemo, useRef, useState } from 'react';
/**
* 生成具有初始值的状态引用
*
@@ -156,3 +156,45 @@ export const useEffectWhen = (effect: () => void, deps: React.DependencyList, wh
}
}, [when]);
};

// 选择、全选操作
export const useCheck = <T>(list: T[]) => {
const [selected, setSelected] = useState<T[]>([]);

const checked = useMemo(() => {
return selected.length === list.length;
}, [selected, list]);

const indeterminate = useMemo(() => {
return selected.length > 0 && selected.length < list.length;
}, [selected, list]);

const checkAll = useCallback(() => {
setSelected(checked ? [] : list);
}, [list, checked]);

const isSingleChecked = useCallback((item: T) => selected.includes(item), [selected]);

const checkSingle = useCallback(
(item: T) => {
setSelected((prev) => {
if (isSingleChecked(item)) {
return prev.filter((i) => i !== item);
} else {
return [...prev, item];
}
});
},
[selected, isSingleChecked],
);

return [
selected,
setSelected,
checked,
indeterminate,
checkAll,
isSingleChecked,
checkSingle,
] as const;
};

+ 2
- 5
react-ui/src/hooks/pageCacheState.ts View File

@@ -4,6 +4,7 @@
* @Description: 页面状态缓存,pop 回到这个页面的时候,重新构建之前的状态
*/

import { parseJsonText } from '@/utils';
import { useCallback, useState } from 'react';

const pageKeys: string[] = [];
@@ -14,11 +15,7 @@ const getCacheState = (key: string) => {
const jsonStr = sessionStorage.getItem(key);
if (jsonStr) {
removeCacheState(key);
try {
return JSON.parse(jsonStr);
} catch (error) {
return undefined;
}
return parseJsonText(jsonStr);
}
return undefined;
};


+ 6
- 0
react-ui/src/overrides.less View File

@@ -79,6 +79,12 @@
background-color: #fff;
}

.ant-table-row-selected {
.ant-table-cell {
color: @primary-color;
}
}

.ant-pro-page-container {
overflow-y: auto;
}


+ 1
- 1
react-ui/src/pages/CodeConfig/components/CodeConfigItem/index.less View File

@@ -39,7 +39,7 @@
}

&__url {
margin-bottom: 10px;
margin-bottom: 10px !important;
color: @text-color-secondary;
font-size: 14px;
}


+ 6
- 4
react-ui/src/pages/Dataset/components/ResourceInfo/index.less View File

@@ -38,10 +38,6 @@
&__bottom {
position: relative;
height: calc(100% - 135px);
padding: 8px 30px 20px;
background: #ffffff;
border-radius: 10px;
box-shadow: 0px 2px 12px rgba(180, 182, 191, 0.09);

&__legend {
position: absolute;
@@ -52,6 +48,12 @@
:global {
.ant-tabs {
height: 100%;
.ant-tabs-nav-wrap {
padding-top: 8px;
padding-left: 30px;
background-color: white;
border-radius: 10px 10px 0 0;
}
.ant-tabs-content-holder {
height: 100%;
.ant-tabs-content {


+ 10
- 1
react-ui/src/pages/Dataset/components/ResourceInfo/index.tsx View File

@@ -164,7 +164,16 @@ const ResourceInfo = ({ resourceType }: ResourceInfoProps) => {
key: ResourceInfoTabKeys.Introduction,
label: `${typeName}简介`,
icon: <KFIcon type="icon-moxingjianjie" />,
children: <ResourceIntro resourceType={resourceType} info={info}></ResourceIntro>,
children: (
<ResourceIntro
resourceType={resourceType}
info={info}
resourceId={resourceId}
identifier={identifier}
owner={owner}
version={version}
></ResourceIntro>
),
},
{
key: ResourceInfoTabKeys.Version,


+ 21
- 6
react-ui/src/pages/Dataset/components/ResourceIntro/index.less View File

@@ -1,10 +1,25 @@
.resource-intro {
width: 100%;
margin-top: 24px;
&__basic {
width: 100%;
}
&__usage {
width: 100%;

&__top {
padding: 20px 30px;
background: white;
border-radius: 0 0 10px 10px;
box-shadow: 0px 2px 12px rgba(180, 182, 191, 0.09);

pre {
margin-bottom: 0 !important;
}

&__title {
margin: 15px 0;
color: @text-color-secondary;
font-size: 14px;
}

&__desc {
color: @text-color;
font-size: @font-size;
}
}
}

+ 67
- 67
react-ui/src/pages/Dataset/components/ResourceIntro/index.tsx View File

@@ -1,4 +1,4 @@
import BasicInfo, { BasicInfoData } from '@/components/BasicInfo';
import BasicTableInfo, { BasicInfoData } from '@/components/BasicTableInfo';
import SubAreaTitle from '@/components/SubAreaTitle';
import { ResourceInfoTabKeys } from '@/pages/Dataset/components/ResourceInfo';
import {
@@ -8,13 +8,19 @@ import {
ProjectDependency,
ResourceType,
TrainTask,
resourceConfig,
} from '@/pages/Dataset/config';
import ModelMetrics from '@/pages/Model/components/ModelMetrics';
import { getGitUrl } from '@/utils';
import styles from './index.less';

type ResourceIntroProps = {
resourceType: ResourceType;
info: DatasetData | ModelData;
resourceId: number;
identifier: string;
owner: string;
version?: string;
};

const formatDataset = (datasets?: DatasetData[]) => {
@@ -27,29 +33,6 @@ const formatDataset = (datasets?: DatasetData[]) => {
}));
};

const formatParams = (map?: Record<string, string>, space: string = '') => {
if (!map || Object.keys(map).length === 0) {
return undefined;
}
return Object.entries(map)
.map(([key, value]) => `${space}${key} : ${value}`)
.join('\n');
};

const formatMetrics = (map?: Record<string, string>) => {
if (!map || Object.keys(map).length === 0) {
return undefined;
}
return Object.entries(map)
.map(([key, value]) => {
if (typeof value === 'object' && value !== null) {
return `${key} : \n${formatParams(value, ' ')}`;
}
return `${key} : ${value}`;
})
.join('\n');
};

const getProjectUrl = (project?: ProjectDependency) => {
if (!project || !project.url || !project.branch) {
return undefined;
@@ -93,49 +76,50 @@ const getDatasetDatas = (data: DatasetData): BasicInfoData[] => [
{
label: '数据集名称',
value: data.name,
ellipsis: true,
},
{
label: '版本',
value: data.version,
ellipsis: true,
},
{
label: '创建人',
value: data.create_by,
ellipsis: true,
},
{
label: '更新时间',
value: data.update_time,
ellipsis: true,
},
{
label: '数据来源',
value: data.dataset_source,
format: formatSource,
ellipsis: true,
},
{
label: '训练任务',
value: data.train_task,
format: formatTrainTask,
ellipsis: true,
},
{
label: '处理代码',
value: data.processing_code,
format: formatProject,
ellipsis: true,
},
{
label: '数据集分类',
value: data.data_type,
ellipsis: true,
},
{
label: '研究方向',
value: data.data_tag,
},
{
label: '数据集描述',
value: data.description,
},
{
label: '版本描述',
value: data.version_desc,
ellipsis: true,
},
];

@@ -143,77 +127,79 @@ const getModelDatas = (data: ModelData): BasicInfoData[] => [
{
label: '模型名称',
value: data.name,
ellipsis: true,
},
{
label: '版本',
value: data.version,
ellipsis: true,
},
{
label: '创建人',
value: data.create_by,
ellipsis: true,
},
{
label: '更新时间',
value: data.update_time,
ellipsis: true,
},
{
label: '训练镜像',
value: data.image,
ellipsis: true,
},
{
label: '训练代码',
value: data.project_depency,
format: formatProject,
ellipsis: true,
},
{
label: '训练数据集',
value: data.train_datasets,
format: formatDataset,
ellipsis: true,
},
{
label: '测试数据集',
value: data.test_datasets,
format: formatDataset,
},
{
label: '参数',
value: data.params,
format: formatParams,
},
{
label: '指标',
value: data.metrics,
format: formatMetrics,
ellipsis: true,
},
{
label: '模型来源',
value: data.model_source,
format: formatSource,
ellipsis: true,
},
{
label: '训练任务',
value: data.train_task,
format: formatTrainTask,
ellipsis: true,
},
{
label: '模型框架',
value: data.model_type,
ellipsis: true,
},
{
label: '模型能力',
value: data.model_tag,
},
{
label: '模型描述',
value: data.description,
},
{
label: '版本描述',
value: data.version_desc,
ellipsis: true,
},
];

function ResourceIntro({ resourceType, info }: ResourceIntroProps) {
function ResourceIntro({
resourceType,
info,
resourceId,
identifier,
owner,
version,
}: ResourceIntroProps) {
const config = resourceConfig[resourceType];
const basicDatas: BasicInfoData[] =
resourceType === ResourceType.Dataset
? getDatasetDatas(info as DatasetData)
@@ -221,23 +207,37 @@ function ResourceIntro({ resourceType, info }: ResourceIntroProps) {

return (
<div className={styles['resource-intro']}>
<SubAreaTitle
title="基本信息"
image={require('@/assets/img/mirror-basic.png')}
style={{ marginBottom: '26px' }}
></SubAreaTitle>
<div className={styles['resource-intro__basic']}>
<BasicInfo datas={basicDatas} labelWidth={86}></BasicInfo>
<div className={styles['resource-intro__top']}>
<SubAreaTitle
title="基本信息"
image={require('@/assets/img/mirror-basic.png')}
style={{ marginBottom: '15px' }}
></SubAreaTitle>
<div className={styles['resource-intro__top__basic']}>
<BasicTableInfo datas={basicDatas} labelWidth={135}></BasicTableInfo>
</div>
<div className={styles['resource-intro__top__title']}>{`${config.name}描述`}</div>
<div className={styles['resource-intro__top__desc']}>{info.description ?? '暂无描述'}</div>
<div className={styles['resource-intro__top__title']}>版本描述</div>
<div className={styles['resource-intro__top__desc']}>{info.version_desc ?? '暂无描述'}</div>
<SubAreaTitle
title="实例用法"
image={require('@/assets/img/usage-icon.png')}
style={{ margin: '25px 0 15px' }}
></SubAreaTitle>
<div
className={styles['resource-intro__top__usage']}
dangerouslySetInnerHTML={{ __html: info.usage ?? '暂无实例用法' }}
></div>
</div>
<SubAreaTitle
title="实例用法"
image={require('@/assets/img/usage-icon.png')}
style={{ margin: '40px 0 24px' }}
></SubAreaTitle>
<div
className={styles['resource-intro__usage']}
dangerouslySetInnerHTML={{ __html: info.usage ?? '暂无实例用法' }}
></div>
{resourceType === ResourceType.Model && version && (
<ModelMetrics
resourceId={resourceId}
identifier={identifier}
owner={owner}
version={version}
></ModelMetrics>
)}
</div>
);
}


+ 5
- 0
react-ui/src/pages/Dataset/components/ResourceVersion/index.less View File

@@ -1,4 +1,9 @@
.resource-version {
min-height: 100%;
padding: 20px 30px;
color: @text-color;
font-size: @font-size-content;
background: white;
border-radius: 0 0 10px 10px;
box-shadow: 0px 2px 12px rgba(180, 182, 191, 0.09);
}

+ 10
- 15
react-ui/src/pages/Dataset/components/ResourceVersion/index.tsx View File

@@ -1,5 +1,3 @@
import CommonTableCell from '@/components/CommonTableCell';
import DateTableCell from '@/components/DateTableCell';
import KFIcon from '@/components/KFIcon';
import {
ResourceData,
@@ -8,7 +6,8 @@ import {
resourceConfig,
} from '@/pages/Dataset/config';
import { downLoadZip } from '@/utils/downloadfile';
import { Button, Flex, Table } from 'antd';
import tableCellRender, { TableCellValueType } from '@/utils/table';
import { Button, Flex, Table, TableProps } from 'antd';
import styles from './index.less';

type ResourceVersionProps = {
@@ -38,37 +37,33 @@ function ResourceVersion({ resourceType, info }: ResourceVersionProps) {
downLoadZip(url, { url: record.url });
};

const columns = [
const columns: TableProps<ResourceFileData>['columns'] = [
{
title: '序号',
dataIndex: 'index',
key: 'index',
width: 80,
render(_text: string, _record: ResourceFileData, index: number) {
return <span>{index + 1}</span>;
},
render: tableCellRender(false, TableCellValueType.Index),
},
{
title: '文件名称',
dataIndex: 'file_name',
key: 'file_name',
render: (text: string, record: ResourceFileData) => (
<a className="kf-table-row-link" onClick={() => downloadAlone(record)}>
{text}
</a>
),
render: tableCellRender(false, TableCellValueType.Link, {
onClick: downloadAlone,
}),
},
{
title: '文件大小',
dataIndex: 'file_size',
key: 'file_size',
render: CommonTableCell(),
render: tableCellRender(),
},
{
title: '更新时间',
dataIndex: 'update_time',
key: 'update_time',
render: DateTableCell,
render: tableCellRender(false, TableCellValueType.Date),
},
{
title: '操作',
@@ -91,7 +86,7 @@ function ResourceVersion({ resourceType, info }: ResourceVersionProps) {

return (
<div className={styles['resource-version']}>
<Flex justify="space-between" align="center" style={{ margin: '30px 0' }}>
<Flex justify="space-between" align="center" style={{ marginBottom: '20px' }}>
<Flex align="center">
<Button
type="default"


+ 5
- 6
react-ui/src/pages/DevelopmentEnvironment/List/index.tsx View File

@@ -4,8 +4,6 @@
* @Description: 开发环境列表
*/

import CommonTableCell from '@/components/CommonTableCell';
import DateTableCell from '@/components/DateTableCell';
import KFIcon from '@/components/KFIcon';
import { DevEditorStatus } from '@/enums';
import { useCacheState } from '@/hooks/pageCacheState';
@@ -19,6 +17,7 @@ import themes from '@/styles/theme.less';
import { openAntdModal } from '@/utils/modal';
import { to } from '@/utils/promise';
import SessionStorage from '@/utils/sessionStorage';
import tableCellRender, { TableCellValueType } from '@/utils/table';
import { modalConfirm } from '@/utils/ui';
import { useNavigate } from '@umijs/max';
import {
@@ -153,7 +152,7 @@ function EditorList() {
};

// 分页切换
const handleTableChange: TableProps['onChange'] = (pagination, filters, sorter, { action }) => {
const handleTableChange: TableProps['onChange'] = (pagination, _filters, _sorter, { action }) => {
if (action === 'paginate') {
setPagination(pagination);
}
@@ -186,21 +185,21 @@ function EditorList() {
dataIndex: 'computing_resource',
key: 'computing_resource',
width: '20%',
render: CommonTableCell(),
render: tableCellRender(),
},
{
title: '创建者',
dataIndex: 'update_by',
key: 'update_by',
width: '20%',
render: CommonTableCell(),
render: tableCellRender(),
},
{
title: '创建时间',
dataIndex: 'create_time',
key: 'create_time',
width: '20%',
render: DateTableCell,
render: tableCellRender(false, TableCellValueType.Date),
},
{
title: '操作',


+ 1
- 1
react-ui/src/pages/Experiment/Comparison/index.less View File

@@ -25,7 +25,7 @@
.ant-table-thead {
.ant-table-cell {
background-color: rgb(247, 247, 247);
border-color: #e8e8e8 !important;
border-color: @border-color-base !important;
}
}
.ant-table-tbody {


+ 11
- 8
react-ui/src/pages/Experiment/Comparison/index.tsx View File

@@ -5,7 +5,7 @@ import {
getExpTrainInfosReq,
} from '@/services/experiment';
import { to } from '@/utils/promise';
import tableCellRender, { arrayFormatter, dateFormatter } from '@/utils/table';
import tableCellRender, { TableCellValueType } from '@/utils/table';
import { useSearchParams } from '@umijs/max';
import { App, Button, Table, /* TablePaginationConfig,*/ TableProps, Tooltip } from 'antd';
import classNames from 'classnames';
@@ -23,7 +23,7 @@ type TableData = {
metrics_names: string[];
metrics: Record<string, number>;
params_names: string[];
params: Record<string, string>;
params: Record<string, number>;
};

function ExperimentComparison() {
@@ -53,7 +53,7 @@ function ExperimentComparison() {
// setLoading(true);
const request =
comparisonType === ComparisonType.Train ? getExpTrainInfosReq : getExpEvaluateInfosReq;
const [res] = await to(request(experimentId));
const [res] = await to(request(experimentId, { offset: '', limit: 50 }));
// setLoading(false);
if (res && res.data) {
// const { content = [], totalElements = 0 } = res.data;
@@ -98,11 +98,12 @@ function ExperimentComparison() {
},
};

const columns: TableProps['columns'] = useMemo(() => {
const columns: TableProps<TableData>['columns'] = useMemo(() => {
const first: TableData | undefined = tableData[0];
return [
{
title: '基本信息',
align: 'center',
children: [
{
title: '实例 ID',
@@ -120,7 +121,7 @@ function ExperimentComparison() {
width: 180,
fixed: 'left',
align: 'center',
render: tableCellRender(false, dateFormatter),
render: tableCellRender(false, TableCellValueType.Date),
},
{
title: '运行状态',
@@ -128,7 +129,7 @@ function ExperimentComparison() {
key: 'status',
width: 100,
fixed: 'left',
align: 'center',
// align: 'center',
render: ExperimentStatusCell,
},
{
@@ -138,7 +139,7 @@ function ExperimentComparison() {
width: 180,
fixed: 'left',
align: 'center',
render: tableCellRender(true, arrayFormatter()),
render: tableCellRender(true, TableCellValueType.Array),
ellipsis: { showTitle: false },
},
],
@@ -201,9 +202,11 @@ function ExperimentComparison() {
dataSource={tableData}
columns={columns}
rowSelection={rowSelection}
scroll={{ y: 'calc(100% - 55px)', x: '100%' }}
scroll={{ y: 'calc(100% - 110px)', x: '100%' }}
pagination={false}
bordered={true}
virtual
// onScroll={handleTableScroll}
// loading={loading}
// pagination={{
// ...pagination,


+ 2
- 2
react-ui/src/pages/Experiment/Info/index.jsx View File

@@ -3,7 +3,7 @@ import { useStateRef, useVisible } from '@/hooks';
import { getExperimentIns } from '@/services/experiment/index.js';
import { getWorkflowById } from '@/services/pipeline/index.js';
import themes from '@/styles/theme.less';
import { fittingString } from '@/utils';
import { fittingString, parseJsonText } from '@/utils';
import { elapsedTime, formatDate } from '@/utils/date';
import { to } from '@/utils/promise';
import G6, { Util } from '@antv/g6';
@@ -88,7 +88,7 @@ function ExperimentText() {
setExperimentIns(res.data);
const { status, nodes_status, argo_ins_ns, argo_ins_name } = res.data;
const workflowData = workflowRef.current;
const experimentStatusObjs = JSON.parse(nodes_status);
const experimentStatusObjs = parseJsonText(nodes_status);
workflowData.nodes.forEach((item) => {
const experimentNode = experimentStatusObjs?.[item.id];
updateWorkflowNode(item, experimentNode);


+ 28
- 7
react-ui/src/pages/Experiment/components/AddExperimentModal/index.tsx View File

@@ -2,7 +2,8 @@ import createExperimentIcon from '@/assets/img/create-experiment.png';
import editExperimentIcon from '@/assets/img/edit-experiment.png';
import KFModal from '@/components/KFModal';
import { type PipelineGlobalParam } from '@/types';
import { Form, Input, Radio, Select, type FormRule } from 'antd';
import { to } from '@/utils/promise';
import { Button, Form, Input, Radio, Select, type FormRule } from 'antd';
import { useState } from 'react';
import styles from './index.less';

@@ -17,7 +18,7 @@ type AddExperimentModalProps = {
isAdd: boolean;
open: boolean;
onCancel: () => void;
onFinish: () => void;
onFinish: (values: any, isRun: boolean) => void;
workflowList: Workflow[];
initialValues: FormData;
};
@@ -113,25 +114,45 @@ function AddExperimentModal({
form.setFieldValue('global_param', []);
}
};

const handleRun = async (run: boolean) => {
const [values, error] = await to(form.validateFields());
if (!error && values) {
onFinish(values, run);
}
};

const footer = [
<Button key="cancel" onClick={onCancel}>
取消
</Button>,
<Button key="submit" type="primary" onClick={() => handleRun(false)}>
确定
</Button>,
];
if (!isAdd) {
footer.push(
<Button key="run" type="primary" onClick={() => handleRun(true)}>
确定并运行
</Button>,
);
}

return (
<KFModal
className={styles['add-experiment-modal']}
title={modalTitle}
image={modalIcon}
open={open}
okButtonProps={{
htmlType: 'submit',
form: 'form',
}}
onCancel={onCancel}
destroyOnClose={true}
width={825}
footer={footer}
>
<Form
name="form"
layout="horizontal"
initialValues={initialValues}
onFinish={onFinish}
autoComplete="off"
form={form}
{...layout}


+ 6
- 1
react-ui/src/pages/Experiment/components/ExperimentInstance/index.less View File

@@ -10,8 +10,12 @@
padding: 0 16px;
}

.check {
width: calc((100% + 32px + 33px) / 6.25 / 2);
}

.index {
width: calc((100% + 32px + 33px) / 6.25);
width: calc((100% + 32px + 33px) / 6.25 / 2);
}

.tensorBoard {
@@ -33,6 +37,7 @@
}

.operation {
position: relative;
width: 344px;
}
}


+ 66
- 2
react-ui/src/pages/Experiment/components/ExperimentInstance/index.tsx View File

@@ -1,7 +1,9 @@
import KFIcon from '@/components/KFIcon';
import { ExperimentStatus } from '@/enums';
import { useCheck } from '@/hooks';
import { experimentStatusInfo } from '@/pages/Experiment/status';
import {
deleteManyExperimentIns,
deleteQueryByExperimentInsId,
putQueryByExperimentInsId,
} from '@/services/experiment/index.js';
@@ -11,8 +13,9 @@ import { elapsedTime, formatDate } from '@/utils/date';
import { to } from '@/utils/promise';
import { modalConfirm } from '@/utils/ui';
import { DoubleRightOutlined } from '@ant-design/icons';
import { App, Button, ConfigProvider, Tooltip } from 'antd';
import { App, Button, Checkbox, ConfigProvider, Tooltip } from 'antd';
import classNames from 'classnames';
import { useEffect, useMemo } from 'react';
import TensorBoardStatusCell from '../TensorBoardStatus';
import styles from './index.less';

@@ -36,6 +39,25 @@ function ExperimentInstanceComponent({
onLoadMore,
}: ExperimentInstanceProps) {
const { message } = App.useApp();
const allIntanceIds = useMemo(() => {
return experimentInList?.map((item) => item.id) || [];
}, [experimentInList]);
const [
selectedIns,
setSelectedIns,
checked,
indeterminate,
checkAll,
isSingleChecked,
checkSingle,
] = useCheck(allIntanceIds);

useEffect(() => {
// 关闭时清空
if (allIntanceIds.length === 0) {
setSelectedIns([]);
}
}, [experimentInList]);

// 删除实验实例确认
const handleRemove = (instance: ExperimentInstance) => {
@@ -56,6 +78,26 @@ function ExperimentInstanceComponent({
}
};

// 批量删除实验实例确认
const handleDeleteAll = () => {
modalConfirm({
title: '确定批量删除选中的实例吗?',
onOk: () => {
batchDeleteExperimentInstances();
},
});
};

// 批量删除实验实例
const batchDeleteExperimentInstances = async () => {
const [res] = await to(deleteManyExperimentIns(selectedIns));
if (res) {
message.success('删除成功');
setSelectedIns([]);
onRemove?.();
}
};

// 终止实验实例
const terminateExperimentInstance = async (instance: ExperimentInstance) => {
const [res] = await to(putQueryByExperimentInsId(instance.id));
@@ -72,6 +114,9 @@ function ExperimentInstanceComponent({
return (
<div>
<div className={styles.tableExpandBox} style={{ paddingBottom: '16px' }}>
<div className={styles.check}>
<Checkbox checked={checked} indeterminate={indeterminate} onChange={checkAll}></Checkbox>
</div>
<div className={styles.index}>序号</div>
<div className={styles.tensorBoard}>可视化</div>
<div className={styles.description}>
@@ -79,7 +124,20 @@ function ExperimentInstanceComponent({
<div style={{ width: '50%' }}>开始时间</div>
</div>
<div className={styles.status}>状态</div>
<div className={styles.operation}>操作</div>
<div className={styles.operation}>
<span>操作</span>
{selectedIns.length > 0 && (
<Button
style={{ position: 'absolute', right: '0' }}
type="primary"
size="small"
onClick={handleDeleteAll}
icon={<KFIcon type="icon-shanchu" />}
>
删除
</Button>
)}
</div>
</div>

{experimentInList.map((item, index) => (
@@ -87,6 +145,12 @@ function ExperimentInstanceComponent({
key={item.id}
className={classNames(styles.tableExpandBox, styles.tableExpandBoxContent)}
>
<div className={styles.check}>
<Checkbox
checked={isSingleChecked(item.id)}
onChange={() => checkSingle(item.id)}
></Checkbox>
</div>
<a
className={styles.index}
style={{ padding: '0 16px' }}


+ 5
- 3
react-ui/src/pages/Experiment/components/LogGroup/index.tsx View File

@@ -52,7 +52,7 @@ function LogGroup({
const [_isMouseDown, setIsMouseDown, isMouseDownRef] = useStateRef(false);
const preStatusRef = useRef<ExperimentStatus | undefined>(undefined);
const socketRef = useRef<WebSocket | undefined>(undefined);
const retryRef = useRef(2);
const retryRef = useRef(2); // 等待 2 秒,重试 2 次

useEffect(() => {
scrollToBottom(false);
@@ -142,11 +142,12 @@ function LogGroup({
);

socket.addEventListener('open', () => {
// console.log('WebSocket is open now.');
console.log('WebSocket is open now.');
});

socket.addEventListener('close', (event) => {
// console.log('WebSocket is closed:', event);
console.log('WebSocket is closed:', event);
// 有时候会出现连接失败,重试 2 次
if (event.code !== 1000 && retryRef.current > 0) {
retryRef.current -= 1;
setTimeout(() => {
@@ -160,6 +161,7 @@ function LogGroup({
});

socket.addEventListener('message', (event) => {
console.log('message received.', event);
if (!event.data) {
return;
}


+ 2
- 1
react-ui/src/pages/Experiment/components/LogList/index.tsx View File

@@ -32,8 +32,9 @@ function LogList({
}: LogListProps) {
const [logList, setLogList] = useState<ExperimentLog[]>([]);
const preStatusRef = useRef<ExperimentStatus | undefined>(undefined);
const retryRef = useRef(3);
const retryRef = useRef(3); // 等待 2 秒,重试 3 次

// 当实例节点运行状态不是 Pending,而上一个运行状态不存在或者是 Pending 时,获取实验日志
useEffect(() => {
if (
instanceNodeStatus &&


+ 32
- 20
react-ui/src/pages/Experiment/index.jsx View File

@@ -1,4 +1,3 @@
import CommonTableCell from '@/components/CommonTableCell';
import KFIcon from '@/components/KFIcon';
import { ExperimentStatus, TensorBoardStatus } from '@/enums';
import {
@@ -15,6 +14,7 @@ import {
import { getWorkflow } from '@/services/pipeline/index.js';
import themes from '@/styles/theme.less';
import { to } from '@/utils/promise';
import tableCellRender, { TableCellValueType } from '@/utils/table';
import { modalConfirm } from '@/utils/ui';
import { App, Button, ConfigProvider, Dropdown, Space, Table } from 'antd';
import classNames from 'classnames';
@@ -60,7 +60,7 @@ function Experiment() {
};

useEffect(() => {
getList();
getExperimentList();
getWorkflowList();
return () => {
clearExperimentInTimers();
@@ -68,7 +68,7 @@ function Experiment() {
}, []);

// 获取实验列表
const getList = async () => {
const getExperimentList = async () => {
const params = {
offset: 0,
page: pageOption.current.page - 1,
@@ -228,8 +228,8 @@ function Experiment() {
setIsModalOpen(false);
};

// 创建或者编辑实验接口请求
const handleAddExperiment = async (values) => {
// 创建或者编辑实验
const handleAddExperiment = async (values, isRun) => {
const global_param = JSON.stringify(values.global_param);
if (!experimentId) {
const params = {
@@ -240,7 +240,7 @@ function Experiment() {
if (res) {
message.success('新建实验成功');
setIsModalOpen(false);
getList();
getExperimentList();
}
} else {
const params = { ...values, global_param, id: experimentId };
@@ -248,7 +248,12 @@ function Experiment() {
if (res) {
message.success('编辑实验成功');
setIsModalOpen(false);
getList();
getExperimentList();

// 确定并运行
if (isRun) {
runExperiment(experimentId);
}
}
}
};
@@ -259,7 +264,7 @@ function Experiment() {
page: current,
size: size,
};
getList();
getExperimentList();
};
// 运行实验
const runExperiment = async (id) => {
@@ -273,8 +278,7 @@ function Experiment() {
};

// 跳转到流水线
const gotoPipeline = (e, record) => {
e.stopPropagation();
const gotoPipeline = (record) => {
navigate({ pathname: `/pipeline/template/info/${record.workflow_id}` });
};

@@ -298,8 +302,16 @@ function Experiment() {
}
};

// 刷新实验列表状态,
// 目前是直接刷新实验列表,后续需要优化,只刷新状态
const refreshExperimentList = () => {
getExperimentList();
};

// 实验实例终止
const handleInstanceTerminate = async (experimentIn) => {
// 刷新实验列表
refreshExperimentList();
setExperimentInList((prevList) => {
return prevList.map((item) => {
if (item.id === experimentIn.id) {
@@ -348,25 +360,23 @@ function Experiment() {
title: '实验名称',
dataIndex: 'name',
key: 'name',
render: (text) => <div>{text}</div>,
render: tableCellRender(),
width: '16%',
},
{
title: '关联流水线名称',
dataIndex: 'workflow_name',
key: 'workflow_name',
render: (text, record) => (
<a className="kf-table-row-link" onClick={(e) => gotoPipeline(e, record)}>
{text}
</a>
),
render: tableCellRender(false, TableCellValueType.Link, {
onClick: gotoPipeline,
}),
width: '16%',
},
{
title: '实验描述',
dataIndex: 'description',
key: 'description',
render: CommonTableCell(true),
render: tableCellRender(true),
ellipsis: { showTitle: false },
},
{
@@ -395,7 +405,6 @@ function Experiment() {
);
},
},

{
title: '操作',
key: 'action',
@@ -452,7 +461,7 @@ function Experiment() {
deleteExperimentById(record.id).then((ret) => {
if (ret.code === 200) {
message.success('删除成功');
getList();
getExperimentList();
} else {
message.error(ret.msg);
}
@@ -489,7 +498,10 @@ function Experiment() {
experimentInsTotal={experimentInsTotal}
onClickInstance={(item) => gotoInstanceInfo(item, record)}
onClickTensorBoard={handleTensorboard}
onRemove={() => refreshExperimentIns(record.id)}
onRemove={() => {
refreshExperimentIns(record.id);
refreshExperimentList();
}}
onTerminate={handleInstanceTerminate}
onLoadMore={() => loadMoreExperimentIns()}
></ExperimentInstance>


+ 5
- 6
react-ui/src/pages/Mirror/Info/index.tsx View File

@@ -3,8 +3,6 @@
* @Date: 2024-04-16 13:58:08
* @Description: 镜像详情
*/
import CommonTableCell from '@/components/CommonTableCell';
import DateTableCell from '@/components/DateTableCell';
import KFIcon from '@/components/KFIcon';
import PageTitle from '@/components/PageTitle';
import SubAreaTitle from '@/components/SubAreaTitle';
@@ -20,6 +18,7 @@ import themes from '@/styles/theme.less';
import { formatDate } from '@/utils/date';
import { to } from '@/utils/promise';
import SessionStorage from '@/utils/sessionStorage';
import tableCellRender, { TableCellValueType } from '@/utils/table';
import { modalConfirm } from '@/utils/ui';
import { useNavigate, useParams } from '@umijs/max';
import {
@@ -156,13 +155,13 @@ function MirrorInfo() {
dataIndex: 'tag_name',
key: 'tag_name',
width: '25%',
render: CommonTableCell(),
render: tableCellRender(),
},
{
title: '镜像地址',
dataIndex: 'url',
key: 'url',
render: CommonTableCell(),
render: tableCellRender(),
},
{
title: '状态',
@@ -176,14 +175,14 @@ function MirrorInfo() {
dataIndex: 'file_size',
key: 'file_size',
width: 150,
render: CommonTableCell(),
render: tableCellRender(),
},
{
title: '创建时间',
dataIndex: 'create_time',
key: 'create_time',
width: 200,
render: DateTableCell,
render: tableCellRender(false, TableCellValueType.Date),
},
{
title: '操作',


+ 5
- 6
react-ui/src/pages/Mirror/List/index.tsx View File

@@ -3,8 +3,6 @@
* @Date: 2024-04-16 13:58:08
* @Description: 镜像列表
*/
import CommonTableCell from '@/components/CommonTableCell';
import DateTableCell from '@/components/DateTableCell';
import KFIcon from '@/components/KFIcon';
import { CommonTabKeys } from '@/enums';
import { useCacheState } from '@/hooks/pageCacheState';
@@ -12,6 +10,7 @@ import { deleteMirrorReq, getMirrorListReq } from '@/services/mirror';
import themes from '@/styles/theme.less';
import { to } from '@/utils/promise';
import SessionStorage from '@/utils/sessionStorage';
import tableCellRender, { TableCellValueType } from '@/utils/table';
import { modalConfirm } from '@/utils/ui';
import { useNavigate } from '@umijs/max';
import {
@@ -169,21 +168,21 @@ function MirrorList() {
dataIndex: 'name',
key: 'name',
width: '30%',
render: CommonTableCell(),
render: tableCellRender(),
},
{
title: '版本数据',
dataIndex: 'version_count',
key: 'version_count',
width: '15%',
render: CommonTableCell(),
render: tableCellRender(),
},
{
title: '镜像描述',
dataIndex: 'description',
key: 'description',
width: '35%',
render: CommonTableCell(true),
render: tableCellRender(true),
ellipsis: { showTitle: false },
},
{
@@ -191,7 +190,7 @@ function MirrorList() {
dataIndex: 'create_time',
key: 'create_time',
width: '20%',
render: DateTableCell,
render: tableCellRender(false, TableCellValueType.Date),
},
{
title: '操作',


+ 29
- 0
react-ui/src/pages/Model/components/MetricsChart/index.less View File

@@ -0,0 +1,29 @@
.metrics-chart {
width: calc((100% - 30px) / 3);
background-color: white;

&__title {
display: flex;
align-items: center;
height: 36px;
padding-left: 15px;
color: @text-color;
font-size: 14px;
background-color: #ebf2ff;

img {
width: 13px;
height: 13px;
margin-right: 12px;
}
}

&__chart {
width: 100%;
height: 280px;
background: linear-gradient(180deg, #ffffff 0%, #fdfeff 100%);
border: 1px solid white;
border-radius: 0 0 10px 10px;
box-shadow: 0px 2px 12px rgba(180, 182, 191, 0.09);
}
}

+ 174
- 0
react-ui/src/pages/Model/components/MetricsChart/index.tsx View File

@@ -0,0 +1,174 @@
import * as echarts from 'echarts';
import { useEffect, useRef } from 'react';
import styles from './index.less';
import './tooltip.css';

const colors = [
'#0D5EF8',
'#6AC21D',
'#F98E1B',
'#ECB934',
'#8A34EC',
'#FF1493',
'#FFFF00',
'#DAA520',
'#CD853F',
'#FF6347',
'#808080',
'#00BFFF',
'#008000',
'#00FFFF',
'#FFFACD',
'#FFA500',
'#FF4500',
'#800080',
'#FF1493',
'#000080',
];

const backgroundColor = new echarts.graphic.LinearGradient(
0,
0,
0,
1,
[
{ offset: 0, color: '#ffffff' },
{ offset: 1, color: '#fdfeff' },
],
false,
);

function getTooltip(xTitle: string, xValue: number, yTitle: string, yValue: number) {
const str = `<div class="metrics-tooltip">
<span class="y-text">Y:</span>
<span class="x-text">X:</span>
<div class="title">${yTitle}</div>
<div class="value">${yValue}</div>
<div class="title" style="margin-top: 10px">${xTitle}</div>
<div class="value">${xValue}</div>
<div>`;
return str;
}

export type MetricsChatData = {
name: string;
values: number[];
version: string;
iters: number[];
};

export type MetricsChartProps = {
name: string;
chartData: MetricsChatData[];
};

function MetricsChart({ name, chartData }: MetricsChartProps) {
const chartRef = useRef<HTMLDivElement>(null);
const xAxisData = chartData[0]?.iters;
const seriesData = chartData.map((item) => {
return {
name: item.version,
type: 'line' as const,
smooth: true,
data: item.values,
};
});

const options: echarts.EChartsOption = {
backgroundColor: backgroundColor,
title: {
show: false,
},
tooltip: {
trigger: 'item',
padding: 10,
formatter: (params: any) => {
const { name: xTitle, data } = params;
return getTooltip('step', xTitle, name, data);
},
},
legend: {
bottom: 10,
icon: 'rect',
itemWidth: 10,
itemHeight: 10,
itemGap: 20,
textStyle: {
color: 'rgba(29, 29, 32, 0.75)',
fontSize: 12,
},
},
color: colors,
grid: {
left: '15',
right: '15',
top: '20',
bottom: '60',
containLabel: true,
},
xAxis: {
type: 'category',
boundaryGap: true,
offset: 10,
data: xAxisData,
axisLabel: {
color: 'rgba(29, 29, 32, 0.75)',
fontSize: 12,
},
axisTick: {
show: false,
},
axisLine: {
lineStyle: {
color: '#eaeaea',
width: 1,
},
},
},
yAxis: {
type: 'value',
axisLabel: {
color: 'rgba(29, 29, 32, 0.75)',
fontSize: 12,
margin: 15,
},
axisLine: {
show: false,
},
splitLine: {
lineStyle: {
color: '#e4e4e4',
width: 1,
type: 'dashed',
},
},
},
series: seriesData,
};

useEffect(() => {
// 创建一个echarts实例,返回echarts实例
const chart = echarts.init(chartRef.current);

// 设置图表实例的配置项和数据
chart.setOption(options);

// 组件卸载
return () => {
// myChart.dispose() 销毁实例
chart.dispose();
};
}, []);

return (
<div className={styles['metrics-chart']}>
<div className={styles['metrics-chart__title']}>
<img src={require('@/assets/img/metrics-title-icon.png')}></img>
<span>{name}</span>
</div>
<div className={styles['metrics-chart__chart']} ref={chartRef}></div>
</div>
);
}

export default MetricsChart;

+ 33
- 0
react-ui/src/pages/Model/components/MetricsChart/tooltip.css View File

@@ -0,0 +1,33 @@
.metrics-tooltip {
width: 172px;
padding-left: 20px;
background-color: white;
font-size: 12px;
}

.metrics-tooltip .y-text {
position: absolute;
left: 10px;
top: 10px;
}

.metrics-tooltip .x-text {
position: absolute;
left: 10px;
top: 66px;
}

.metrics-tooltip .title {
color: #575757;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
margin-bottom: 3px;
}

.metrics-tooltip .value {
color: #1d1d20;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}

+ 5
- 2
react-ui/src/pages/Model/components/ModelEvolution/index.less View File

@@ -1,11 +1,14 @@
.model-evolution {
width: 100%;
height: 100%;
padding: 0 30px 20px;
overflow-x: hidden;
background-color: white;
background: white;
border-radius: 0 0 10px 10px;
box-shadow: 0px 2px 12px rgba(180, 182, 191, 0.09);

&__graph {
height: calc(100%);
height: 100%;
background-color: @background-color;
background-image: url(@/assets/img/pipeline-canvas-bg.png);
background-size: 100% 100%;


+ 35
- 0
react-ui/src/pages/Model/components/ModelMetrics/index.less View File

@@ -0,0 +1,35 @@
.model-metrics {
&__table {
margin-top: 10px;
padding: 20px 30px 0;
background: white;
border-radius: 10px;

:global {
.ant-table-container {
border: none !important;
}
.ant-table-thead {
.ant-table-cell {
background-color: rgb(247, 247, 247);
border-color: @border-color-base !important;
}
}
.ant-table-tbody {
.ant-table-cell {
border-right: none !important;
border-left: none !important;
}
}
}
}

&__chart {
display: flex;
flex-wrap: wrap;
gap: 15px;
align-items: center;
width: 100%;
margin-top: 10px;
}
}

+ 259
- 0
react-ui/src/pages/Model/components/ModelMetrics/index.tsx View File

@@ -0,0 +1,259 @@
import SubAreaTitle from '@/components/SubAreaTitle';
import { useCheck } from '@/hooks';
import { getModelPageVersionsReq, getModelVersionsMetricsReq } from '@/services/dataset';
import { to } from '@/utils/promise';
import tableCellRender from '@/utils/table';
import { Checkbox, Table, Tooltip, type TablePaginationConfig, type TableProps } from 'antd';
import { useEffect, useMemo, useState } from 'react';
import MetricsChart, { MetricsChatData } from '../MetricsChart';
import styles from './index.less';

enum MetricsType {
Train = 'train', // 训练
Evaluate = 'evaluate', // 评估
}

type TableData = {
name: string;
metrics_names?: string[];
metrics?: Record<string, number>;
params_names?: string[];
params?: Record<string, string>;
};

type ModelMetricsProps = {
resourceId: number;
identifier: string;
owner: string;
version: string;
};

function ModelMetrics({ resourceId, identifier, owner, version }: ModelMetricsProps) {
const [pagination, setPagination] = useState<TablePaginationConfig>({
current: 1,
pageSize: 10,
});
const [total, setTotal] = useState(0);
const [tableData, setTableData] = useState<TableData[]>([]);
const [chartData, setChartData] = useState<Record<string, MetricsChatData[]> | undefined>(
undefined,
);
const [selectedRowKeys, setSelectedRowKeys] = useState<React.Key[]>([]);
// 获取所有的指标名称
const allMetricsNames = useMemo(() => {
const first: TableData | undefined = tableData.find(
(item) => item.metrics_names && item.metrics_names.length > 0,
);
return first?.metrics_names ?? [];
}, [tableData]);
const [
selectedMetrics,
// eslint-disable-next-line @typescript-eslint/no-unused-vars
_setSelectedMetrics,
metricsChecked,
metricsIndeterminate,
checkAllMetrics,
isSingleMetricsChecked,
checkSingleMetrics,
] = useCheck(allMetricsNames);

useEffect(() => {
getModelPageVersions();
}, []);

useEffect(() => {
if (selectedMetrics.length !== 0 && selectedRowKeys.length !== 0) {
getModelVersionsMetrics();
} else {
setChartData(undefined);
}
}, [selectedMetrics, selectedRowKeys]);

useEffect(() => {
const curRow = tableData.find((item) => item.name === version);
if (
curRow &&
curRow.metrics_names &&
curRow.metrics_names.length > 0 &&
!selectedRowKeys.includes(version)
) {
setSelectedRowKeys([version, ...selectedRowKeys]);
}
}, [version]);

// 获取模型版本列表,带有参数和指标数据
const getModelPageVersions = async () => {
const params = {
page: pagination.current! - 1,
size: pagination.pageSize,
identifier: identifier,
owner: owner,
type: MetricsType.Train,
};
const [res] = await to(getModelPageVersionsReq(params));
if (res && res.data) {
const { content = [], totalElements = 0 } = res.data;
setTableData(content);
setTotal(totalElements);
}
};

const getModelVersionsMetrics = async () => {
const params = {
versions: selectedRowKeys,
metrics: selectedMetrics,
type: MetricsType.Train,
identifier: identifier,
repo_id: resourceId,
};
const [res] = await to(getModelVersionsMetricsReq(params));
if (res && res.data) {
setChartData(res.data);
}
};

// 分页切换
const handleTableChange: TableProps['onChange'] = (pagination, _filters, _sorter, { action }) => {
if (action === 'paginate') {
setPagination(pagination);
}
// console.log(pagination, filters, sorter, action);
};

const rowSelection: TableProps['rowSelection'] = {
type: 'checkbox',
fixed: 'left',
selectedRowKeys,
onChange: (selectedRowKeys: React.Key[]) => {
setSelectedRowKeys(selectedRowKeys);
},
getCheckboxProps: (record: TableData) => ({
disabled: !record.metrics_names || record.metrics_names.length === 0,
}),
};

const showTableData = useMemo(() => {
const index = tableData.findIndex((item) => item.name === version);
if (index !== -1) {
const rowData = tableData[index];
const newTableData = tableData.filter((_, idx) => idx !== index);
return [rowData, ...newTableData];
}
}, [version, tableData]);

// 表头
const columns: TableProps['columns'] = useMemo(() => {
const first: TableData | undefined = tableData.find(
(item) => item.metrics_names && item.metrics_names.length > 0,
);
return [
{
title: '基本信息',
align: 'center',
children: [
{
title: '版本号',
dataIndex: 'name',
key: 'name',
width: 180,
fixed: 'left',
align: 'center',
render: tableCellRender(false),
},
],
},
{
title: `训练参数`,
align: 'center',
children: first?.params_names?.map((name) => ({
title: (
<Tooltip title={name}>
<span>{name}</span>
</Tooltip>
),
dataIndex: ['params', name],
key: name,
width: 120,
align: 'center',
render: tableCellRender(true),
ellipsis: { showTitle: false },
sorter: (a, b) => a.params?.[name] ?? 0 - b.params?.[name] ?? 0,
showSorterTooltip: false,
})),
},
{
title: () => (
<div>
<Checkbox
checked={metricsChecked}
indeterminate={metricsIndeterminate}
onChange={checkAllMetrics}
></Checkbox>{' '}
<span>训练指标</span>
</div>
),
align: 'center',
children: first?.metrics_names?.map((name) => ({
title: (
<div>
<Checkbox
checked={isSingleMetricsChecked(name)}
onChange={(e) => {
e.stopPropagation();
checkSingleMetrics(name);
}}
onClick={(e) => e.stopPropagation()}
></Checkbox>{' '}
<Tooltip title={name}>
<span>{name}</span>
</Tooltip>
</div>
),
dataIndex: ['metrics', name],
key: name,
width: 120,
align: 'center',
render: tableCellRender(true),
ellipsis: { showTitle: false },
sorter: (a, b) => a.metrics?.[name] ?? 0 - b.metrics?.[name] ?? 0,
showSorterTooltip: false,
})),
},
];
}, [tableData, selectedMetrics]);

return (
<div className={styles['model-metrics']}>
<div className={styles['model-metrics__table']}>
<SubAreaTitle
title="指标参数差异对比"
image={require('@/assets/img/model-metrics.png')}
style={{ marginBottom: '15px' }}
></SubAreaTitle>
<Table
dataSource={showTableData}
columns={columns}
rowSelection={rowSelection}
bordered={true}
pagination={{
...pagination,
total: total,
showSizeChanger: true,
showQuickJumper: true,
showTotal: () => `共${total}条`,
}}
onChange={handleTableChange}
rowKey="name"
/>
</div>
<div className={styles['model-metrics__chart']}>
{chartData &&
Object.keys(chartData).map((key) => (
<MetricsChart key={key} name={key} chartData={chartData[key]}></MetricsChart>
))}
</div>
</div>
);
}

export default ModelMetrics;

+ 12
- 16
react-ui/src/pages/ModelDeployment/List/index.tsx View File

@@ -3,8 +3,6 @@
* @Date: 2024-04-16 13:58:08
* @Description: 模型部署服务列表
*/
import CommonTableCell from '@/components/CommonTableCell';
import DateTableCell from '@/components/DateTableCell';
import KFIcon from '@/components/KFIcon';
import PageTitle from '@/components/PageTitle';
import { serviceTypeOptions } from '@/enums';
@@ -13,6 +11,7 @@ import { deleteServiceReq, getServiceListReq } from '@/services/modelDeployment'
import themes from '@/styles/theme.less';
import { to } from '@/utils/promise';
import SessionStorage from '@/utils/sessionStorage';
import tableCellRender, { TableCellValueType } from '@/utils/table';
import { modalConfirm } from '@/utils/ui';
import { useNavigate } from '@umijs/max';
import {
@@ -190,42 +189,39 @@ function ModelDeployment() {
dataIndex: 'index',
key: 'index',
width: '20%',
render(_text, _record, index) {
return <span>{(pagination.current! - 1) * pagination.pageSize! + index + 1}</span>;
},
render: tableCellRender(false, TableCellValueType.Index, {
page: pagination.current! - 1,
pageSize: pagination.pageSize!,
}),
},
{
title: '服务名称',
dataIndex: 'service_name',
key: 'service_name',
width: '20%',
render: (text, record) => {
return (
<a className="kf-table-row-link" onClick={() => toDetail(record)}>
{text}
</a>
);
},
render: tableCellRender(false, TableCellValueType.Link, {
onClick: toDetail,
}),
},
{
title: '服务类型',
dataIndex: 'service_type_name',
key: 'service_type_name',
width: '20%',
render: CommonTableCell(),
render: tableCellRender(),
},
{
title: '版本数量',
dataIndex: 'version_count',
key: 'version_count',
width: '20%',
render: CommonTableCell(),
render: tableCellRender(),
},
{
title: '服务描述',
dataIndex: 'description',
key: 'description',
render: CommonTableCell(),
render: tableCellRender(),
width: '20%',
},
{
@@ -233,7 +229,7 @@ function ModelDeployment() {
dataIndex: 'update_time',
key: 'update_time',
width: '20%',
render: DateTableCell,
render: tableCellRender(false, TableCellValueType.Date),
},
{
title: '操作',


+ 13
- 27
react-ui/src/pages/ModelDeployment/ServiceInfo/index.tsx View File

@@ -4,7 +4,6 @@
* @Description: 模型部署列表
*/
import BasicInfo from '@/components/BasicInfo';
import CommonTableCell from '@/components/CommonTableCell';
import KFIcon from '@/components/KFIcon';
import PageTitle from '@/components/PageTitle';
import SubAreaTitle from '@/components/SubAreaTitle';
@@ -21,6 +20,7 @@ import themes from '@/styles/theme.less';
import { formatDate } from '@/utils/date';
import { to } from '@/utils/promise';
import SessionStorage from '@/utils/sessionStorage';
import tableCellRender, { TableCellValueType } from '@/utils/table';
import { modalConfirm } from '@/utils/ui';
import { useNavigate, useParams } from '@umijs/max';
import {
@@ -30,7 +30,6 @@ import {
Input,
Select,
Table,
Tooltip,
type TablePaginationConfig,
type TableProps,
} from 'antd';
@@ -222,31 +221,24 @@ function ServiceInfo() {
dataIndex: 'index',
key: 'index',
width: '20%',
render(_text, _record, index) {
return <span>{(pagination.current! - 1) * pagination.pageSize! + index + 1}</span>;
},
render: tableCellRender(false, TableCellValueType.Index, {
page: pagination.current! - 1,
pageSize: pagination.pageSize!,
}),
},
{
title: '服务版本',
dataIndex: 'version',
key: 'version',
width: '20%',
render: CommonTableCell(),
render: tableCellRender(),
},
{
title: '模型版本',
dataIndex: 'model',
dataIndex: ['model', 'show_value'],
key: 'model',
width: '20%',
render: (_text: string, record: ServiceVersionData) => (
<Tooltip
title={record.model.show_value}
placement="topLeft"
overlayStyle={{ maxWidth: '400px' }}
>
<span>{record.model.show_value}</span>
</Tooltip>
),
render: tableCellRender(true),
ellipsis: { showTitle: false },
},
{
@@ -261,14 +253,14 @@ function ServiceInfo() {
dataIndex: 'image',
key: 'image',
width: '20%',
render: CommonTableCell(true),
render: tableCellRender(true),
ellipsis: { showTitle: false },
},
{
title: '副本数量',
dataIndex: 'replicas',
key: 'replicas',
render: CommonTableCell(),
render: tableCellRender(),
width: '20%',
},
{
@@ -276,15 +268,9 @@ function ServiceInfo() {
dataIndex: 'resource',
key: 'resource',
width: '20%',
render: (resource: string) => (
<Tooltip
title={getResourceDescription(resource)}
placement="topLeft"
overlayStyle={{ maxWidth: '400px' }}
>
<span>{resource ? getResourceDescription(resource) : '--'}</span>
</Tooltip>
),
render: tableCellRender(true, TableCellValueType.Custom, {
format: getResourceDescription,
}),
ellipsis: { showTitle: false },
},
{


+ 3
- 3
react-ui/src/pages/Pipeline/Info/index.jsx View File

@@ -2,7 +2,7 @@ import KFIcon from '@/components/KFIcon';
import { useStateRef, useVisible } from '@/hooks';
import { getWorkflowById, saveWorkflow } from '@/services/pipeline/index.js';
import themes from '@/styles/theme.less';
import { fittingString, s8 } from '@/utils';
import { fittingString, parseJsonText, s8 } from '@/utils';
import { to } from '@/utils/promise';
import G6 from '@antv/g6';
import { useNavigate, useParams } from '@umijs/max';
@@ -130,7 +130,7 @@ const EditPipeline = () => {

// 渲染数据
const getGraphData = (data) => {
if (graph) {
if (graph && data) {
graph.data(data);
graph.render();
} else {
@@ -283,7 +283,7 @@ const EditPipeline = () => {
const { global_param, dag } = res.data;
setGlobalParam(global_param || []);
if (dag) {
getGraphData(JSON.parse(dag));
getGraphData(parseJsonText(dag));
}
}
};


+ 1
- 1
react-ui/src/pages/Pipeline/components/PipelineNodeDrawer/index.tsx View File

@@ -80,7 +80,7 @@ const PipelineNodeParameter = forwardRef(({ onFormChange }: PipelineNodeParamete
out_parameters: JSON.parse(model.out_parameters),
control_strategy: JSON.parse(model.control_strategy),
};
console.log('model', nodeData);
// console.log('model', nodeData);
setStagingItem({
...nodeData,
});


+ 12
- 15
react-ui/src/pages/Pipeline/index.jsx View File

@@ -1,5 +1,3 @@
import CommonTableCell from '@/components/CommonTableCell';
import DateTableCell from '@/components/DateTableCell';
import KFIcon from '@/components/KFIcon';
import KFModal from '@/components/KFModal';
import {
@@ -11,6 +9,7 @@ import {
removeWorkflow,
} from '@/services/pipeline/index.js';
import themes from '@/styles/theme.less';
import tableCellRender, { TableCellValueType } from '@/utils/table';
import { modalConfirm } from '@/utils/ui';
import { App, Button, ConfigProvider, Form, Input, Space, Table } from 'antd';
import classNames from 'classnames';
@@ -41,8 +40,7 @@ const Pipeline = () => {
}
});
};
const routeToEdit = (e, record) => {
e.stopPropagation();
const routeToEdit = (record) => {
navigate({ pathname: `/pipeline/template/info/${record.id}` });
};
const showModal = () => {
@@ -114,38 +112,37 @@ const Pipeline = () => {
key: 'index',
width: 120,
align: 'center',
render(text, record, index) {
return <span>{(pageOption.current.page - 1) * pageOption.current.size + index + 1}</span>;
},
render: tableCellRender(false, TableCellValueType.Index, {
page: pageOption.current.page - 1,
pageSize: pageOption.current.size,
}),
},
{
title: '流水线名称',
dataIndex: 'name',
key: 'name',
render: (text, record) => (
<a className="kf-table-row-link" onClick={(e) => routeToEdit(e, record)}>
{text}
</a>
),
render: tableCellRender(false, TableCellValueType.Link, {
onClick: routeToEdit,
}),
},
{
title: '流水线描述',
dataIndex: 'description',
key: 'description',
render: CommonTableCell(true),
render: tableCellRender(true),
ellipsis: { showTitle: false },
},
{
title: '创建时间',
dataIndex: 'create_time',
key: 'create_time',
render: DateTableCell,
render: tableCellRender(false, TableCellValueType.Date),
},
{
title: '修改时间',
dataIndex: 'update_time',
key: 'update_time',
render: DateTableCell,
render: tableCellRender(false, TableCellValueType.Date),
},
{
title: '操作',


+ 2
- 1
react-ui/src/pages/User/Login/index.tsx View File

@@ -1,5 +1,6 @@
import { clearSessionToken, setSessionToken } from '@/access';
import { getCaptchaImg, login } from '@/services/system/auth';
import { parseJsonText } from '@/utils';
import { safeInvoke } from '@/utils/functional';
import LocalStorage from '@/utils/localStorage';
import { to } from '@/utils/promise';
@@ -37,7 +38,7 @@ const Login = () => {
const userJson = safeInvoke((text: string) =>
CryptoJS.AES.decrypt(text, AESKEY).toString(CryptoJS.enc.Utf8),
)(userStorage);
const user = safeInvoke(JSON.parse)(userJson);
const user = safeInvoke(parseJsonText)(userJson);
if (user && typeof user === 'object' && user.version === VERSION) {
const { username, password } = user;
form.setFieldsValue({ username: username, password: password, autoLogin: true });


+ 5
- 5
react-ui/src/pages/Workspace/components/ExperimentChart/index.tsx View File

@@ -153,11 +153,11 @@ function ExperimentChart({ chartData, style }: ExperimentChartProps) {
show: false,
},
data: [
{ value: chartData.Failed > 0 ? chartData.Failed : null, name: '失败' },
{ value: chartData.Succeeded > 0 ? chartData.Succeeded : null, name: '成功' },
{ value: chartData.Terminated > 0 ? chartData.Terminated : null, name: '中止' },
{ value: chartData.Pending > 0 ? chartData.Pending : null, name: '等待' },
{ value: chartData.Running > 0 ? chartData.Running : null, name: '运行中' },
{ value: chartData.Failed > 0 ? chartData.Failed : undefined, name: '失败' },
{ value: chartData.Succeeded > 0 ? chartData.Succeeded : undefined, name: '成功' },
{ value: chartData.Terminated > 0 ? chartData.Terminated : undefined, name: '中止' },
{ value: chartData.Pending > 0 ? chartData.Pending : undefined, name: '等待' },
{ value: chartData.Running > 0 ? chartData.Running : undefined, name: '运行中' },
],
},
{


+ 0
- 11
react-ui/src/services/ant-design-pro/api.ts View File

@@ -1,11 +0,0 @@
// @ts-ignore
/* eslint-disable */
import { request } from '@umijs/max';

/** 此处后端没有提供注释 GET /api/notices */
export async function getNotices(options?: { [key: string]: any }) {
return request<API.NoticeIconList>('/api/notices', {
method: 'GET',
...(options || {}),
});
}

+ 0
- 12
react-ui/src/services/ant-design-pro/index.ts View File

@@ -1,12 +0,0 @@
// @ts-ignore
/* eslint-disable */
// API 更新时间:
// API 唯一标识:
import * as api from './api';
import * as login from './login';
import * as rule from './rule';
export default {
api,
login,
rule,
};

+ 0
- 38
react-ui/src/services/ant-design-pro/login.ts View File

@@ -1,38 +0,0 @@
// @ts-ignore
/* eslint-disable */
import { request } from '@umijs/max';

/** 登录接口 POST /api/login/account */
export async function login(body: API.LoginParams, options?: { [key: string]: any }) {
return request<API.LoginResult>('/api/login/account', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
data: body,
...(options || {}),
});
}

/** 发送验证码 POST /api/login/captcha */
export async function getFakeCaptcha(
// 叠加生成的Param类型 (非body参数swagger默认没有生成对象)
params: API.getFakeCaptchaParams,
options?: { [key: string]: any },
) {
return request<API.FakeCaptcha>('/api/login/captcha', {
method: 'POST',
params: {
...params,
},
...(options || {}),
});
}

/** 登录接口 POST /api/login/outLogin */
export async function outLogin(options?: { [key: string]: any }) {
return request<Record<string, any>>('/api/login/outLogin', {
method: 'POST',
...(options || {}),
});
}

+ 0
- 42
react-ui/src/services/ant-design-pro/rule.ts View File

@@ -1,42 +0,0 @@
// @ts-ignore
/* eslint-disable */
import { request } from '@umijs/max';

/** 获取规则列表 GET /api/rule */
export async function rule(
// 叠加生成的Param类型 (非body参数swagger默认没有生成对象)
params: API.ruleParams,
options?: { [key: string]: any },
) {
return request<API.RuleList>('/api/rule', {
method: 'GET',
params: {
...params,
},
...(options || {}),
});
}

/** 新建规则 PUT /api/rule */
export async function updateRule(options?: { [key: string]: any }) {
return request<API.RuleListItem>('/api/rule', {
method: 'PUT',
...(options || {}),
});
}

/** 新建规则 POST /api/rule */
export async function addRule(options?: { [key: string]: any }) {
return request<API.RuleListItem>('/api/rule', {
method: 'POST',
...(options || {}),
});
}

/** 删除规则 DELETE /api/rule */
export async function removeRule(options?: { [key: string]: any }) {
return request<Record<string, any>>('/api/rule', {
method: 'DELETE',
...(options || {}),
});
}

+ 0
- 114
react-ui/src/services/ant-design-pro/typings.d.ts View File

@@ -1,114 +0,0 @@
declare namespace API {
type CurrentUser = UserInfo & {
signature?: string;
title?: string;
group?: string;
tags?: { key?: string; label?: string }[];
notifyCount?: number;
unreadCount?: number;
country?: string;
access?: string;
geographic?: {
province?: { label?: string; key?: string };
city?: { label?: string; key?: string };
};
address?: string;
phone?: string;
roleNames?: {
roleName?: string;
}[];
};

type ErrorResponse = {
/** 业务约定的错误码 */
errorCode: string;
/** 业务上的错误信息 */
errorMessage?: string;
/** 业务上的请求是否成功 */
success?: boolean;
};

type FakeCaptcha = {
code?: number;
status?: string;
};

type getFakeCaptchaParams = {
/** 手机号 */
phone?: string;
};

type LoginParams = {
username?: string;
password?: string;
uuid?: string;
autoLogin?: boolean;
type?: string;
};

type LoginResult = {
code: number;
msg?: string;
type?: string;
data?: {
access_token?: string;
expires_in?: number;
};
};

type NoticeIconItem = {
id?: string;
extra?: string;
key?: string;
read?: boolean;
avatar?: string;
title?: string;
status?: string;
datetime?: string;
description?: string;
type?: NoticeIconItemType;
};

type NoticeIconItemType = 'notification' | 'message' | 'event';

type NoticeIconList = {
data?: NoticeIconItem[];
/** 列表的内容总数 */
total?: number;
success?: boolean;
};

type PageParams = {
current?: number;
pageSize?: number;
};

type RuleList = {
data?: RuleListItem[];
/** 列表的内容总数 */
total?: number;
success?: boolean;
};

type RuleListItem = {
key?: number;
disabled?: boolean;
href?: string;
avatar?: string;
name?: string;
owner?: string;
desc?: string;
callNo?: number;
status?: number;
updatedAt?: string;
createdAt?: string;
progress?: number;
};

type ruleParams = {
/** 当前的页码 */
current?: number;
/** 页面的容量 */
pageSize?: number;
};
}

+ 16
- 0
react-ui/src/services/dataset/index.js View File

@@ -149,4 +149,20 @@ export function exportModelReq(data) {
method: 'POST',
data
});
}

// 分页查询模型所有版本,带有参数和指标数据
export function getModelPageVersionsReq(params) {
return request(`/api/mmp/newmodel/queryVersions`, {
method: 'GET',
params
});
}

// 获取模型版本指标对比
export function getModelVersionsMetricsReq(data) {
return request(`/api/mmp/newmodel/queryVersionsMetrics`, {
method: 'POST',
data
});
}

+ 12
- 2
react-ui/src/services/experiment/index.js View File

@@ -40,6 +40,13 @@ export function deleteQueryByExperimentInsId(id) {
method: 'DELETE',
});
}
// 批量删除实验实例
export function deleteManyExperimentIns(data) {
return request(`/api/mmp/experimentIns/batchDelete`, {
method: 'DELETE',
data,
});
}
// 根据id终止实验实例
export function putQueryByExperimentInsId(id) {
return request(`/api/mmp/experimentIns/${id}`, {
@@ -52,6 +59,7 @@ export function getQueryByExperimentLog(data) {
method: 'POST',
data,
skipErrorHandler: true,
skipLoading: true,
});
}
// 查询实例节点结果
@@ -121,16 +129,18 @@ export function getTensorBoardStatusReq(data) {
}

// 获取当前实验的模型推理指标信息
export function getExpEvaluateInfosReq(experimentId) {
export function getExpEvaluateInfosReq(experimentId, params) {
return request(`/api/mmp/aim/getExpEvaluateInfos/${experimentId}`, {
method: 'GET',
params
});
}

// 获取当前实验的模型训练指标信息
export function getExpTrainInfosReq(experimentId) {
export function getExpTrainInfosReq(experimentId, params) {
return request(`/api/mmp/aim/getExpTrainInfos/${experimentId}`, {
method: 'GET',
params
});
}



+ 0
- 12
react-ui/src/services/swagger/index.ts View File

@@ -1,12 +0,0 @@
// @ts-ignore
/* eslint-disable */
// API 更新时间:
// API 唯一标识:
import * as pet from './pet';
import * as store from './store';
import * as user from './user';
export default {
pet,
store,
user,
};

+ 0
- 153
react-ui/src/services/swagger/pet.ts View File

@@ -1,153 +0,0 @@
// @ts-ignore
/* eslint-disable */
import { request } from '@umijs/max';

/** Update an existing pet PUT /pet */
export async function updatePet(body: API.Pet, options?: { [key: string]: any }) {
return request<any>('/pet', {
method: 'PUT',
headers: {
'Content-Type': 'application/json',
},
data: body,
...(options || {}),
});
}

/** Add a new pet to the store POST /pet */
export async function addPet(body: API.Pet, options?: { [key: string]: any }) {
return request<any>('/pet', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
data: body,
...(options || {}),
});
}

/** Find pet by ID Returns a single pet GET /pet/${param0} */
export async function getPetById(
// 叠加生成的Param类型 (非body参数swagger默认没有生成对象)
params: API.getPetByIdParams,
options?: { [key: string]: any },
) {
const { petId: param0, ...queryParams } = params;
return request<API.Pet>(`/pet/${param0}`, {
method: 'GET',
params: { ...queryParams },
...(options || {}),
});
}

/** Updates a pet in the store with form data POST /pet/${param0} */
export async function updatePetWithForm(
// 叠加生成的Param类型 (非body参数swagger默认没有生成对象)
params: API.updatePetWithFormParams,
body: { name?: string; status?: string },
options?: { [key: string]: any },
) {
const { petId: param0, ...queryParams } = params;
const formData = new FormData();

Object.keys(body).forEach((ele) => {
const item = (body as any)[ele];

if (item !== undefined && item !== null) {
formData.append(
ele,
typeof item === 'object' && !(item instanceof File) ? JSON.stringify(item) : item,
);
}
});

return request<any>(`/pet/${param0}`, {
method: 'POST',
params: { ...queryParams },
data: formData,
...(options || {}),
});
}

/** Deletes a pet DELETE /pet/${param0} */
export async function deletePet(
// 叠加生成的Param类型 (非body参数swagger默认没有生成对象)
params: API.deletePetParams & {
// header
api_key?: string;
},
options?: { [key: string]: any },
) {
const { petId: param0, ...queryParams } = params;
return request<any>(`/pet/${param0}`, {
method: 'DELETE',
headers: {},
params: { ...queryParams },
...(options || {}),
});
}

/** uploads an image POST /pet/${param0}/uploadImage */
export async function uploadFile(
// 叠加生成的Param类型 (非body参数swagger默认没有生成对象)
params: API.uploadFileParams,
body: { additionalMetadata?: string; file?: string },
file?: File,
options?: { [key: string]: any },
) {
const { petId: param0, ...queryParams } = params;
const formData = new FormData();

if (file) {
formData.append('file', file);
}

Object.keys(body).forEach((ele) => {
const item = (body as any)[ele];

if (item !== undefined && item !== null) {
formData.append(
ele,
typeof item === 'object' && !(item instanceof File) ? JSON.stringify(item) : item,
);
}
});

return request<API.ApiResponse>(`/pet/${param0}/uploadImage`, {
method: 'POST',
params: { ...queryParams },
data: formData,
requestType: 'form',
...(options || {}),
});
}

/** Finds Pets by status Multiple status values can be provided with comma separated strings GET /pet/findByStatus */
export async function findPetsByStatus(
// 叠加生成的Param类型 (非body参数swagger默认没有生成对象)
params: API.findPetsByStatusParams,
options?: { [key: string]: any },
) {
return request<API.Pet[]>('/pet/findByStatus', {
method: 'GET',
params: {
...params,
},
...(options || {}),
});
}

/** Finds Pets by tags Muliple tags can be provided with comma separated strings. Use tag1, tag2, tag3 for testing. GET /pet/findByTags */
export async function findPetsByTags(
// 叠加生成的Param类型 (非body参数swagger默认没有生成对象)
params: API.findPetsByTagsParams,
options?: { [key: string]: any },
) {
return request<API.Pet[]>('/pet/findByTags', {
method: 'GET',
params: {
...params,
},
...(options || {}),
});
}

+ 0
- 48
react-ui/src/services/swagger/store.ts View File

@@ -1,48 +0,0 @@
// @ts-ignore
/* eslint-disable */
import { request } from '@umijs/max';

/** Returns pet inventories by status Returns a map of status codes to quantities GET /store/inventory */
export async function getInventory(options?: { [key: string]: any }) {
return request<Record<string, any>>('/store/inventory', {
method: 'GET',
...(options || {}),
});
}

/** Place an order for a pet POST /store/order */
export async function placeOrder(body: API.Order, options?: { [key: string]: any }) {
return request<API.Order>('/store/order', {
method: 'POST',
data: body,
...(options || {}),
});
}

/** Find purchase order by ID For valid response try integer IDs with value >= 1 and <= 10. Other values will generated exceptions GET /store/order/${param0} */
export async function getOrderById(
// 叠加生成的Param类型 (非body参数swagger默认没有生成对象)
params: API.getOrderByIdParams,
options?: { [key: string]: any },
) {
const { orderId: param0, ...queryParams } = params;
return request<API.Order>(`/store/order/${param0}`, {
method: 'GET',
params: { ...queryParams },
...(options || {}),
});
}

/** Delete purchase order by ID For valid response try integer IDs with positive integer value. Negative or non-integer values will generate API errors DELETE /store/order/${param0} */
export async function deleteOrder(
// 叠加生成的Param类型 (非body参数swagger默认没有生成对象)
params: API.deleteOrderParams,
options?: { [key: string]: any },
) {
const { orderId: param0, ...queryParams } = params;
return request<any>(`/store/order/${param0}`, {
method: 'DELETE',
params: { ...queryParams },
...(options || {}),
});
}

+ 0
- 112
react-ui/src/services/swagger/typings.d.ts View File

@@ -1,112 +0,0 @@
declare namespace API {
type ApiResponse = {
code?: number;
type?: string;
message?: string;
};

type Category = {
id?: number;
name?: string;
};

type deleteOrderParams = {
/** ID of the order that needs to be deleted */
orderId: number;
};

type deletePetParams = {
api_key?: string;
/** Pet id to delete */
petId: number;
};

type deleteUserParams = {
/** The name that needs to be deleted */
username: string;
};

type findPetsByStatusParams = {
/** Status values that need to be considered for filter */
status: ('available' | 'pending' | 'sold')[];
};

type findPetsByTagsParams = {
/** Tags to filter by */
tags: string[];
};

type getOrderByIdParams = {
/** ID of pet that needs to be fetched */
orderId: number;
};

type getPetByIdParams = {
/** ID of pet to return */
petId: number;
};

type getUserByNameParams = {
/** The name that needs to be fetched. Use user1 for testing. */
username: string;
};

type loginUserParams = {
/** The user name for login */
username: string;
/** The password for login in clear text */
password: string;
};

type Order = {
id?: number;
petId?: number;
quantity?: number;
shipDate?: string;
/** Order Status */
status?: 'placed' | 'approved' | 'delivered';
complete?: boolean;
};

type Pet = {
id?: number;
category?: Category;
name: string;
photoUrls: string[];
tags?: Tag[];
/** pet status in the store */
status?: 'available' | 'pending' | 'sold';
};

type Tag = {
id?: number;
name?: string;
};

type updatePetWithFormParams = {
/** ID of pet that needs to be updated */
petId: number;
};

type updateUserParams = {
/** name that need to be updated */
username: string;
};

type uploadFileParams = {
/** ID of pet to update */
petId: number;
};

type User = {
id?: number;
username?: string;
firstName?: string;
lastName?: string;
email?: string;
password?: string;
phone?: string;
/** User Status */
userStatus?: number;
};
}

+ 0
- 100
react-ui/src/services/swagger/user.ts View File

@@ -1,100 +0,0 @@
// @ts-ignore
/* eslint-disable */
import { request } from '@umijs/max';

/** Create user This can only be done by the logged in user. POST /user */
export async function createUser(body: API.User, options?: { [key: string]: any }) {
return request<any>('/user', {
method: 'POST',
data: body,
...(options || {}),
});
}

/** Get user by user name GET /user/${param0} */
export async function getUserByName(
// 叠加生成的Param类型 (非body参数swagger默认没有生成对象)
params: API.getUserByNameParams,
options?: { [key: string]: any },
) {
const { username: param0, ...queryParams } = params;
return request<API.User>(`/user/${param0}`, {
method: 'GET',
params: { ...queryParams },
...(options || {}),
});
}

/** Updated user This can only be done by the logged in user. PUT /user/${param0} */
export async function updateUser(
// 叠加生成的Param类型 (非body参数swagger默认没有生成对象)
params: API.updateUserParams,
body: API.User,
options?: { [key: string]: any },
) {
const { username: param0, ...queryParams } = params;
return request<any>(`/user/${param0}`, {
method: 'PUT',
params: { ...queryParams },
data: body,
...(options || {}),
});
}

/** Delete user This can only be done by the logged in user. DELETE /user/${param0} */
export async function deleteUser(
// 叠加生成的Param类型 (非body参数swagger默认没有生成对象)
params: API.deleteUserParams,
options?: { [key: string]: any },
) {
const { username: param0, ...queryParams } = params;
return request<any>(`/user/${param0}`, {
method: 'DELETE',
params: { ...queryParams },
...(options || {}),
});
}

/** Creates list of users with given input array POST /user/createWithArray */
export async function createUsersWithArrayInput(
body: API.User[],
options?: { [key: string]: any },
) {
return request<any>('/user/createWithArray', {
method: 'POST',
data: body,
...(options || {}),
});
}

/** Creates list of users with given input array POST /user/createWithList */
export async function createUsersWithListInput(body: API.User[], options?: { [key: string]: any }) {
return request<any>('/user/createWithList', {
method: 'POST',
data: body,
...(options || {}),
});
}

/** Logs user into the system GET /user/login */
export async function loginUser(
// 叠加生成的Param类型 (非body参数swagger默认没有生成对象)
params: API.loginUserParams,
options?: { [key: string]: any },
) {
return request<string>('/user/login', {
method: 'GET',
params: {
...params,
},
...(options || {}),
});
}

/** Logs out current logged in user session GET /user/logout */
export async function logoutUser(options?: { [key: string]: any }) {
return request<any>('/user/logout', {
method: 'GET',
...(options || {}),
});
}

+ 1
- 1
react-ui/src/utils/date.ts View File

@@ -79,7 +79,7 @@ export const canBeConvertToDate = (date?: Date | string | number | null): boolea
* @return {string} The formatted date string.
*/
export const formatDate = (
date?: Date | string | number | null,
date: Date | string | number | null | undefined,
format: string = 'YYYY-MM-DD HH:mm:ss',
): string => {
if (date === undefined || date === null || date === '') {


+ 1
- 1
react-ui/src/utils/index.ts View File

@@ -21,7 +21,7 @@ export function getNameByCode(list: any[], code: any) {

// 解析 json 字符串
export function parseJsonText(text?: string | null): any | null {
if (!text) {
if (text === undefined || text === null || text === '') {
return null;
}
try {


+ 4
- 6
react-ui/src/utils/localStorage.ts View File

@@ -1,3 +1,5 @@
import { parseJsonText } from './index';

export default class LocalStorage {
// 登录的用户,包括用户名、密码和版本号
static readonly loginUserKey = 'login-user';
@@ -10,13 +12,9 @@ export default class LocalStorage {
return jsonStr;
}
if (jsonStr) {
try {
return JSON.parse(jsonStr);
} catch (error) {
return undefined;
}
return parseJsonText(jsonStr);
}
return undefined;
return null;
}

static setItem(key: string, state?: any, isObject: boolean = false) {


+ 4
- 8
react-ui/src/utils/sessionStorage.ts View File

@@ -1,3 +1,5 @@
import { parseJsonText } from './index';

export default class SessionStorage {
// 用于新建镜像
static readonly mirrorNameKey = 'mirror-name';
@@ -7,8 +9,6 @@ export default class SessionStorage {
static readonly serviceVersionInfoKey = 'service-version-info';
// 编辑器 url
static readonly editorUrlKey = 'editor-url';
// 数据集、模型资源
static readonly resourceItemKey = 'resource-item';

static getItem(key: string, isObject: boolean = false) {
const jsonStr = sessionStorage.getItem(key);
@@ -16,13 +16,9 @@ export default class SessionStorage {
return jsonStr;
}
if (jsonStr) {
try {
return JSON.parse(jsonStr);
} catch (error) {
return undefined;
}
return parseJsonText(jsonStr);
}
return undefined;
return null;
}

static setItem(key: string, state?: any, isObject: boolean = false) {


+ 90
- 27
react-ui/src/utils/table.tsx View File

@@ -1,34 +1,53 @@
/*
* @Author: 赵伟
* @Date: 2024-06-26 10:05:52
* @Description: 列表自定义 render
* @Description: Table cell 自定义 render
*/

import { formatDate } from '@/utils/date';
import { Tooltip } from 'antd';
import dayjs from 'dayjs';

type TableCellFormatter = (value?: any | null) => string | undefined | null;
export enum TableCellValueType {
Index = 'Index',
Text = 'Text',
Date = 'Date',
Array = 'Array',
Link = 'Link',
Custom = 'Custom',
}

// 字符串转换函数
export const stringFormatter: TableCellFormatter = (value?: any | null) => {
return value;
export type TableCellValueOptions<T> = {
page?: number; // 类型为 Index 时有效
pageSize?: number; // 类型为 Index 时有效
property?: string; // 类型为 Array 时有效
dateFormat?: string; // 类型为 Date 时有效
onClick?: (record: T, e: React.MouseEvent) => void; // 类型为 Link 时有效
format?: (value: any | undefined | null, record: T, index: number) => string | undefined | null; // 类型为 Custom 时有效
};

type TableCellFormatter = (value: any | undefined | null) => string | undefined | null;

// 日期转换函数
export const dateFormatter: TableCellFormatter = (value?: any | null) => {
if (value === undefined || value === null || value === '') {
return null;
}
if (!dayjs(value).isValid()) {
return null;
}
return formatDate(value);
};
function formatDateText(dateFormat?: string): TableCellFormatter {
return (value: any | undefined | null): ReturnType<TableCellFormatter> => {
if (value === undefined || value === null || value === '') {
return null;
}
if (!dayjs(value).isValid()) {
return null;
}
return formatDate(value, dateFormat);
};
}

// 数组转换函数
export function arrayFormatter(property?: string) {
return (value?: any | null): ReturnType<TableCellFormatter> => {
/**
* 数组转换函数,将数组元素转换为字符串,用逗号分隔
* @param {string} property 如果数组元素是对象,那么取数组元素的某个属性
* @returns {TableCellFormatter} Table cell 渲染函数
*/
function formatArray(property?: string): TableCellFormatter {
return (value: any | undefined | null): ReturnType<TableCellFormatter> => {
if (
value === undefined ||
value === null ||
@@ -38,31 +57,75 @@ export function arrayFormatter(property?: string) {
return null;
}

let list = value;
if (property && typeof value[0] === 'object') {
list = value.map((item) => item[property]);
}
const list =
property && typeof value[0] === 'object' ? value.map((item) => item[property]) : value;
return list.join(',');
};
}

function tableCellRender(ellipsis: boolean = false, format: TableCellFormatter = stringFormatter) {
return (value?: any | null) => {
const text = format(value);
function tableCellRender<T>(
ellipsis: boolean = false,
type: TableCellValueType = TableCellValueType.Text,
options?: TableCellValueOptions<T>,
) {
return (value: any | undefined | null, record: T, index: number) => {
let text = value;
switch (type) {
case TableCellValueType.Index:
text = (options?.page ?? 0) * (options?.pageSize ?? 0) + index + 1;
break;
case TableCellValueType.Text:
case TableCellValueType.Link:
text = value;
break;
case TableCellValueType.Date:
text = formatDateText(options?.dateFormat)(value);
break;
case TableCellValueType.Array:
text = formatArray(options?.property)(value);
break;
case TableCellValueType.Custom:
text = options?.format?.(value, record, index);
break;
default:
break;
}

if (ellipsis && text) {
return (
<Tooltip title={text} placement="topLeft" overlayStyle={{ maxWidth: '400px' }}>
{renderCell(text)}
{renderCell(text, type === TableCellValueType.Link, record, options?.onClick)}
</Tooltip>
);
} else {
return renderCell(text);
return renderCell(text, type === TableCellValueType.Link, record, options?.onClick);
}
};
}

function renderCell(text?: any | null) {
function renderCell<T>(
text: any | undefined | null,
isLink: boolean,
record: T,
onClick?: (record: T, e: React.MouseEvent) => void,
) {
return isLink ? renderLink(text, record, onClick) : renderText(text);
}

function renderText(text: any | undefined | null) {
return <span>{text ?? '--'}</span>;
}

function renderLink<T>(
text: any | undefined | null,
record: T,
onClick?: (record: T, e: React.MouseEvent) => void,
) {
return (
<a className="kf-table-row-link" onClick={(e) => onClick?.(record, e)}>
{text}
</a>
);
}

export default tableCellRender;

Loading…
Cancel
Save