Browse Source

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

# Conflicts:
#	ruoyi-modules/management-platform/src/main/java/com/ruoyi/platform/service/impl/CodeConfigServiceImpl.java
#	ruoyi-modules/management-platform/src/main/java/com/ruoyi/platform/service/impl/ImageServiceImpl.java
#	ruoyi-modules/management-platform/src/main/java/com/ruoyi/platform/service/impl/ImageVersionServiceImpl.java
#	ruoyi-modules/management-platform/src/main/java/com/ruoyi/platform/service/impl/ModelsServiceImpl.java
#	ruoyi-modules/management-platform/src/main/java/com/ruoyi/platform/service/impl/NewDatasetServiceImpl.java
dev-active_learn
chenzhihang 9 months ago
parent
commit
34060d0e5a
100 changed files with 1156 additions and 822 deletions
  1. +5
    -0
      .gitignore
  2. +1
    -1
      k8s/build_and_deploy.sh
  3. +3
    -1
      k8s/deploy.sh
  4. +12
    -2
      k8s/template-yaml/k8s-6system.yaml
  5. +0
    -1
      react-ui/.storybook/babel-plugin-auto-css-modules.js
  6. +5
    -1
      react-ui/.storybook/main.ts
  7. +3
    -0
      react-ui/Dockerfile
  8. +1
    -133
      react-ui/README.md
  9. +29
    -2
      react-ui/config/routes.ts
  10. +7
    -3
      react-ui/package.json
  11. BIN
      react-ui/public/fonts/DingTalk-JinBuTi.ttf
  12. BIN
      react-ui/public/fonts/DingTalk-JinBuTi.woff
  13. BIN
      react-ui/public/fonts/DingTalk-JinBuTi.woff2
  14. +12
    -4
      react-ui/public/fonts/font.css
  15. +5
    -8
      react-ui/src/app.tsx
  16. BIN
      react-ui/src/assets/img/confirm-icon.png
  17. +0
    -0
      react-ui/src/assets/img/copy-icon.png
  18. BIN
      react-ui/src/assets/img/user-points-bg.png
  19. BIN
      react-ui/src/assets/img/workspace-experiment.png
  20. BIN
      react-ui/src/assets/img/workspace-pipeline.png
  21. +13
    -2
      react-ui/src/components/CodeConfigItem/index.tsx
  22. +0
    -0
      react-ui/src/components/CopyingText/clipboard.js
  23. +0
    -0
      react-ui/src/components/CopyingText/index.less
  24. +26
    -0
      react-ui/src/components/CopyingText/index.tsx
  25. +12
    -4
      react-ui/src/components/IFramePage/index.tsx
  26. +1
    -1
      react-ui/src/components/ParameterInput/index.less
  27. +7
    -2
      react-ui/src/components/ParameterInput/index.tsx
  28. +1
    -1
      react-ui/src/components/ParameterSelect/config.tsx
  29. +14
    -3
      react-ui/src/components/ParameterSelect/index.tsx
  30. +1
    -1
      react-ui/src/components/ResourceSelect/index.tsx
  31. +2
    -0
      react-ui/src/components/ResourceSelectorModal/config.tsx
  32. +2
    -1
      react-ui/src/components/RightContent/AvatarDropdown.tsx
  33. +5
    -0
      react-ui/src/global.less
  34. +0
    -202
      react-ui/src/hooks/index.ts
  35. +6
    -1
      react-ui/src/hooks/useCacheState.ts
  36. +25
    -0
      react-ui/src/hooks/useCallbackState.ts
  37. +47
    -0
      react-ui/src/hooks/useCheck.ts
  38. +3
    -3
      react-ui/src/hooks/useComputingResource.ts
  39. +40
    -0
      react-ui/src/hooks/useDomSize.ts
  40. +3
    -1
      react-ui/src/hooks/useDraggable.ts
  41. +24
    -0
      react-ui/src/hooks/useEffectWhen.ts
  42. +24
    -0
      react-ui/src/hooks/useResetForm.ts
  43. +46
    -0
      react-ui/src/hooks/useSSE.ts
  44. +19
    -0
      react-ui/src/hooks/useStateRef.ts
  45. +26
    -0
      react-ui/src/hooks/useVisible.ts
  46. +0
    -5
      react-ui/src/overrides.less
  47. +1
    -1
      react-ui/src/pages/AutoML/Instance/index.tsx
  48. +0
    -26
      react-ui/src/pages/AutoML/components/CopyingText/index.tsx
  49. +14
    -10
      react-ui/src/pages/AutoML/components/ExperimentHistory/index.tsx
  50. +17
    -4
      react-ui/src/pages/AutoML/components/ExperimentInstance/index.tsx
  51. +8
    -10
      react-ui/src/pages/AutoML/components/ExperimentList/index.tsx
  52. +8
    -1
      react-ui/src/pages/CodeConfig/List/index.tsx
  53. +6
    -1
      react-ui/src/pages/CodeConfig/components/CodeConfigItem/index.tsx
  54. +29
    -21
      react-ui/src/pages/Dataset/components/AddDatasetModal/index.tsx
  55. +24
    -5
      react-ui/src/pages/Dataset/components/AddModelModal/index.tsx
  56. +2
    -1
      react-ui/src/pages/Dataset/components/AddVersionModal/index.tsx
  57. +3
    -0
      react-ui/src/pages/Dataset/components/ResourceInfo/index.tsx
  58. +2
    -2
      react-ui/src/pages/Dataset/components/ResourceIntro/index.tsx
  59. +8
    -1
      react-ui/src/pages/Dataset/components/ResourceList/index.tsx
  60. +1
    -1
      react-ui/src/pages/Dataset/components/ResourcePage/index.tsx
  61. +6
    -0
      react-ui/src/pages/Dataset/components/ResourceVersion/index.tsx
  62. +0
    -2
      react-ui/src/pages/Dataset/components/VersionCompareModal/index.less
  63. +20
    -6
      react-ui/src/pages/Dataset/components/VersionCompareModal/index.tsx
  64. +4
    -4
      react-ui/src/pages/DevelopmentEnvironment/Create/index.tsx
  65. +80
    -36
      react-ui/src/pages/DevelopmentEnvironment/List/index.tsx
  66. +7
    -7
      react-ui/src/pages/DevelopmentEnvironment/components/CreateMirrorModal/index.tsx
  67. +12
    -0
      react-ui/src/pages/Experiment/Aim/index.tsx
  68. +6
    -2
      react-ui/src/pages/Experiment/Comparison/index.tsx
  69. +4
    -2
      react-ui/src/pages/Experiment/Info/index.jsx
  70. +8
    -8
      react-ui/src/pages/Experiment/components/AddExperimentModal/index.tsx
  71. +1
    -2
      react-ui/src/pages/Experiment/components/ExperimentDrawer/index.less
  72. +17
    -2
      react-ui/src/pages/Experiment/components/ExperimentDrawer/index.tsx
  73. +5
    -3
      react-ui/src/pages/Experiment/components/ExperimentInstance/index.tsx
  74. +1
    -1
      react-ui/src/pages/Experiment/components/LogGroup/index.tsx
  75. +8
    -25
      react-ui/src/pages/Experiment/components/ViewParamsModal/index.less
  76. +41
    -9
      react-ui/src/pages/Experiment/components/ViewParamsModal/index.tsx
  77. +29
    -19
      react-ui/src/pages/Experiment/index.jsx
  78. +1
    -1
      react-ui/src/pages/HyperParameter/components/HyperParameterBasic/index.tsx
  79. +32
    -25
      react-ui/src/pages/Mirror/Create/index.tsx
  80. +20
    -16
      react-ui/src/pages/Mirror/Info/index.tsx
  81. +10
    -13
      react-ui/src/pages/Mirror/List/index.tsx
  82. +41
    -47
      react-ui/src/pages/Model/components/ModelEvolution/index.tsx
  83. +24
    -3
      react-ui/src/pages/Model/components/ModelMetrics/index.tsx
  84. +2
    -2
      react-ui/src/pages/ModelDeployment/CreateService/index.tsx
  85. +1
    -1
      react-ui/src/pages/ModelDeployment/CreateVersion/index.tsx
  86. +9
    -12
      react-ui/src/pages/ModelDeployment/List/index.tsx
  87. +1
    -0
      react-ui/src/pages/ModelDeployment/ServiceInfo/index.less
  88. +12
    -15
      react-ui/src/pages/ModelDeployment/ServiceInfo/index.tsx
  89. +1
    -2
      react-ui/src/pages/ModelDeployment/VersionInfo/index.less
  90. +15
    -7
      react-ui/src/pages/ModelDeployment/VersionInfo/index.tsx
  91. +14
    -3
      react-ui/src/pages/ModelDeployment/components/VersionBasicInfo/index.tsx
  92. +1
    -1
      react-ui/src/pages/ModelDeployment/components/VersionCompareModal/index.tsx
  93. +3
    -4
      react-ui/src/pages/ModelDeployment/types.ts
  94. +2
    -1
      react-ui/src/pages/Pipeline/Info/index.jsx
  95. +29
    -25
      react-ui/src/pages/Pipeline/components/GlobalParamsDrawer/index.tsx
  96. +3
    -2
      react-ui/src/pages/Pipeline/components/PipelineNodeDrawer/index.tsx
  97. +45
    -47
      react-ui/src/pages/Pipeline/index.jsx
  98. +31
    -0
      react-ui/src/pages/Points/components/Statistics/index.less
  99. +38
    -0
      react-ui/src/pages/Points/components/Statistics/index.tsx
  100. +19
    -0
      react-ui/src/pages/Points/index.less

+ 5
- 0
.gitignore View File

@@ -60,3 +60,8 @@ mvnw
**/node_modules

*storybook.log

/react-ui/docs
/react-ui/types/tsconfig.tsbuildinfo
/react-ui/storybook-static
/react-ui/.storybook/scripts

+ 1
- 1
k8s/build_and_deploy.sh View File

@@ -61,7 +61,7 @@ if [[ ! " ${valid_services[@]} " =~ " $service " ]]; then
exit 1
fi

valid_envs=("dev" "test")
valid_envs=("dev" "test" "test1")
if [[ ! " ${valid_envs[@]} " =~ " $env " ]]; then
echo "Invalid environment: $env" >&2
echo "Valid environments are: ${valid_envs[*]}"


+ 3
- 1
k8s/deploy.sh View File

@@ -34,7 +34,7 @@ if [[ ! " ${valid_services[@]} " =~ " $service " ]]; then
exit 1
fi

valid_envs=("dev" "test")
valid_envs=("dev" "test" "test1")
if [[ ! " ${valid_envs[@]} " =~ " $env " ]]; then
echo "Invalid environment: $env" >&2
echo "Valid environments are: ${valid_envs[*]}"
@@ -46,6 +46,8 @@ if [ "$env" == "dev" ]; then
remote_ip="172.20.32.197"
elif [ "$env" == "test" ]; then
remote_ip="172.20.32.185"
elif [ "$env" == "test1" ]; then
remote_ip="172.20.32.235"
else
echo "Invalid environment - $env"
exit 1


+ 12
- 2
k8s/template-yaml/k8s-6system.yaml View File

@@ -18,7 +18,11 @@ spec:
image: ${k8s-6system-image}
ports:
- containerPort: 9201

env:
- name: TZ
value: Asia/Shanghai
- name: JAVA_TOOL_OPTIONS
value: "-Xrunjdwp:transport=dt_socket,server=y,suspend=n,address=*:5005"
---
apiVersion: v1
kind: Service
@@ -28,9 +32,15 @@ metadata:
spec:
type: NodePort
ports:
- port: 9201
- name: http
port: 9201
nodePort: 31207
protocol: TCP
- name: debug
nodePort: 31220
port: 5005
protocol: TCP
targetPort: 5005
selector:
app: ci4s-system


+ 0
- 1
react-ui/.storybook/babel-plugin-auto-css-modules.js View File

@@ -4,7 +4,6 @@ export default function(babel) {
visitor: {
ImportDeclaration(path) {
const source = path.node.source.value;
// console.log("zzzz", source);
if (source.endsWith('.less')) {
if (path.node.specifiers.length > 0) {
path.node.source.value += "?modules";


+ 5
- 1
react-ui/.storybook/main.ts View File

@@ -16,7 +16,11 @@ const config: StorybookConfig = {
name: '@storybook/react-webpack5',
options: {},
},
staticDirs: ['../public'],
staticDirs: [
'../public',
{ from: '../docs', to: '/docs' },
{ from: '../docs/index.html', to: '/docs/index.html' },
],
docs: {
defaultName: 'Documentation',
},


+ 3
- 0
react-ui/Dockerfile View File

@@ -0,0 +1,3 @@
# Dockerfile
FROM nginx:alpine
COPY storybook-static/ /usr/share/nginx/html

+ 1
- 133
react-ui/README.md View File

@@ -1,133 +1 @@
Language : 🇺🇸 | [🇨🇳](./README.zh-CN.md) | [🇷🇺](./README.ru-RU.md) | [🇹🇷](./README.tr-TR.md) | [🇯🇵](./README.ja-JP.md) | [🇫🇷](./README.fr-FR.md) | [🇵🇹](./README.pt-BR.md) | [🇸🇦](./README.ar-DZ.md)

<h1 align="center">Ant Design Pro</h1>

<div align="center">

An out-of-box UI solution for enterprise applications as a React boilerplate.

[![Build Status](https://dev.azure.com/ant-design/ant-design-pro/_apis/build/status/ant-design.ant-design-pro?branchName=master)](https://dev.azure.com/ant-design/ant-design-pro/_build/latest?definitionId=1?branchName=master) ![Github Action](https://github.com/ant-design/ant-design-pro/workflows/Node%20CI/badge.svg) ![Deploy](https://github.com/ant-design/ant-design-pro/workflows/Deploy%20CI/badge.svg)

[![Gitter](https://img.shields.io/gitter/room/ant-design/pro-english.svg?style=flat-square&logoWidth=20&logo=data%3Aimage%2Fsvg%2Bxml%3Bbase64%2CPD94bWwgdmVyc2lvbj0iMS4wIiBlbmNvZGluZz0iVVRGLTgiPz4NCjxzdmcgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIiB4bWxuczp4bGluaz0iaHR0cDovL3d3dy53My5vcmcvMTk5OS94bGluayIgd2lkdGg9IjEyMzUiIGhlaWdodD0iNjUwIiB2aWV3Qm94PSIwIDAgNzQxMCAzOTAwIj4NCjxyZWN0IHdpZHRoPSI3NDEwIiBoZWlnaHQ9IjM5MDAiIGZpbGw9IiNiMjIyMzQiLz4NCjxwYXRoIGQ9Ik0wLDQ1MEg3NDEwbTAsNjAwSDBtMCw2MDBINzQxMG0wLDYwMEgwbTAsNjAwSDc0MTBtMCw2MDBIMCIgc3Ryb2tlPSIjZmZmIiBzdHJva2Utd2lkdGg9IjMwMCIvPg0KPHJlY3Qgd2lkdGg9IjI5NjQiIGhlaWdodD0iMjEwMCIgZmlsbD0iIzNjM2I2ZSIvPg0KPGcgZmlsbD0iI2ZmZiI%2BDQo8ZyBpZD0iczE4Ij4NCjxnIGlkPSJzOSI%2BDQo8ZyBpZD0iczUiPg0KPGcgaWQ9InM0Ij4NCjxwYXRoIGlkPSJzIiBkPSJNMjQ3LDkwIDMxNy41MzQyMzAsMzA3LjA4MjAzOSAxMzIuODczMjE4LDE3Mi45MTc5NjFIMzYxLjEyNjc4MkwxNzYuNDY1NzcwLDMwNy4wODIwMzl6Ii8%2BDQo8dXNlIHhsaW5rOmhyZWY9IiNzIiB5PSI0MjAiLz4NCjx1c2UgeGxpbms6aHJlZj0iI3MiIHk9Ijg0MCIvPg0KPHVzZSB4bGluazpocmVmPSIjcyIgeT0iMTI2MCIvPg0KPC9nPg0KPHVzZSB4bGluazpocmVmPSIjcyIgeT0iMTY4MCIvPg0KPC9nPg0KPHVzZSB4bGluazpocmVmPSIjczQiIHg9IjI0NyIgeT0iMjEwIi8%2BDQo8L2c%2BDQo8dXNlIHhsaW5rOmhyZWY9IiNzOSIgeD0iNDk0Ii8%2BDQo8L2c%2BDQo8dXNlIHhsaW5rOmhyZWY9IiNzMTgiIHg9Ijk4OCIvPg0KPHVzZSB4bGluazpocmVmPSIjczkiIHg9IjE5NzYiLz4NCjx1c2UgeGxpbms6aHJlZj0iI3M1IiB4PSIyNDcwIi8%2BDQo8L2c%2BDQo8L3N2Zz4%3D)](https://gitter.im/ant-design/pro-english?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge) [![Join the chat at https://gitter.im/ant-design/ant-design-pro](https://img.shields.io/gitter/room/ant-design/ant-design-pro.svg?style=flat-square&logoWidth=20&logo=data%3Aimage%2Fsvg%2Bxml%3Bbase64%2CPD94bWwgdmVyc2lvbj0iMS4wIiBlbmNvZGluZz0iVVRGLTgiPz4NCjxzdmcgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIiB4bWxuczp4bGluaz0iaHR0cDovL3d3dy53My5vcmcvMTk5OS94bGluayIgd2lkdGg9IjkwMCIgaGVpZ2h0PSI2MDAiIHZpZXdCb3g9IjAgMCAzMCAyMCI%2BDQo8ZGVmcz4NCjxwYXRoIGlkPSJzIiBkPSJNMCwtMSAwLjU4Nzc4NSwwLjgwOTAxNyAtMC45NTEwNTcsLTAuMzA5MDE3SDAuOTUxMDU3TC0wLjU4Nzc4NSwwLjgwOTAxN3oiIGZpbGw9IiNmZmRlMDAiLz4NCjwvZGVmcz4NCjxyZWN0IHdpZHRoPSIzMCIgaGVpZ2h0PSIyMCIgZmlsbD0iI2RlMjkxMCIvPg0KPHVzZSB4bGluazpocmVmPSIjcyIgdHJhbnNmb3JtPSJ0cmFuc2xhdGUoNSw1KSBzY2FsZSgzKSIvPg0KPHVzZSB4bGluazpocmVmPSIjcyIgdHJhbnNmb3JtPSJ0cmFuc2xhdGUoMTAsMikgcm90YXRlKDIzLjAzNjI0MykiLz4NCjx1c2UgeGxpbms6aHJlZj0iI3MiIHRyYW5zZm9ybT0idHJhbnNsYXRlKDEyLDQpIHJvdGF0ZSg0NS44Njk4OTgpIi8%2BDQo8dXNlIHhsaW5rOmhyZWY9IiNzIiB0cmFuc2Zvcm09InRyYW5zbGF0ZSgxMiw3KSByb3RhdGUoNjkuOTQ1Mzk2KSIvPg0KPHVzZSB4bGluazpocmVmPSIjcyIgdHJhbnNmb3JtPSJ0cmFuc2xhdGUoMTAsOSkgcm90YXRlKDIwLjY1OTgwOCkiLz4NCjwvc3ZnPg%3D%3D)](https://gitter.im/ant-design/ant-design-pro?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge) [![Build With Umi](https://img.shields.io/badge/build%20with-umi-028fe4.svg?style=flat-square)](http://umijs.org/) ![](https://badgen.net/badge/icon/Ant%20Design?icon=https://gw.alipayobjects.com/zos/antfincdn/Pp4WPgVDB3/KDpgvguMpGfqaHPjicRK.svg&label)

![](https://user-images.githubusercontent.com/8186664/44953195-581e3d80-aec4-11e8-8dcb-54b9db38ec11.png)

</div>

- Preview: http://preview.pro.ant.design
- Home Page: http://pro.ant.design
- Documentation: http://pro.ant.design/docs/getting-started
- ChangeLog: http://pro.ant.design/docs/changelog
- FAQ: http://pro.ant.design/docs/faq
- Mirror Site in China: http://ant-design-pro.gitee.io

## 5.0 is out! 🎉🎉🎉

[Ant Design Pro 5.0.0](https://github.com/ant-design/ant-design-pro/issues/8656)

## Translation Recruitment :loudspeaker:

We need your help: https://github.com/ant-design/ant-design-pro/issues/120

## Features

- :bulb: **TypeScript**: A language for application-scale JavaScript
- :scroll: **Blocks**: Build page with block template
- :gem: **Neat Design**: Follow [Ant Design specification](http://ant.design/)
- :triangular_ruler: **Common Templates**: Typical templates for enterprise applications
- :rocket: **State of The Art Development**: Newest development stack of React/umi/dva/antd
- :iphone: **Responsive**: Designed for variable screen sizes
- :art: **Theming**: Customizable theme with simple config
- :globe_with_meridians: **International**: Built-in i18n solution
- :gear: **Best Practices**: Solid workflow to make your code healthy
- :1234: **Mock development**: Easy to use mock development solution
- :white_check_mark: **UI Test**: Fly safely with unit and e2e tests

## Templates

```
- Dashboard
- Analytic
- Monitor
- Workspace
- Form
- Basic Form
- Step Form
- Advanced From
- List
- Standard Table
- Standard List
- Card List
- Search List (Project/Applications/Article)
- Profile
- Simple Profile
- Advanced Profile
- Account
- Account Center
- Account Settings
- Result
- Success
- Failed
- Exception
- 403
- 404
- 500
- User
- Login
- Register
- Register Result
```

## Usage

### Use bash

We provide pro-cli to quickly initialize scaffolding.

```bash
# use npm
npm i @ant-design/pro-cli -g
pro create myapp
```

select umi version

```shell
🐂 Use umi@4 or umi@3 ? (Use arrow keys)
❯ umi@4
umi@3
```

> If the umi@4 version is selected, full blocks are not yet supported.

If you choose umi@3, you can also choose the pro template. Pro is the basic template, which only provides the basic content of the framework operation. Complete contains all blocks, which is not suitable for secondary development as a basic template.

```shell
? 🚀 Full or a simple scaffold? (Use arrow keys)
❯ simple
complete
```

Install dependencies:

```shell
$ cd myapp && tyarn
// or
$ cd myapp && npm install
```

## Browsers support

Modern browsers.

| [<img src="https://raw.githubusercontent.com/alrra/browser-logos/master/src/edge/edge_48x48.png" alt="Edge" width="24px" height="24px" />](http://godban.github.io/browsers-support-badges/)</br>Edge | [<img src="https://raw.githubusercontent.com/alrra/browser-logos/master/src/firefox/firefox_48x48.png" alt="Firefox" width="24px" height="24px" />](http://godban.github.io/browsers-support-badges/)</br>Firefox | [<img src="https://raw.githubusercontent.com/alrra/browser-logos/master/src/chrome/chrome_48x48.png" alt="Chrome" width="24px" height="24px" />](http://godban.github.io/browsers-support-badges/)</br>Chrome | [<img src="https://raw.githubusercontent.com/alrra/browser-logos/master/src/safari/safari_48x48.png" alt="Safari" width="24px" height="24px" />](http://godban.github.io/browsers-support-badges/)</br>Safari | [<img src="https://raw.githubusercontent.com/alrra/browser-logos/master/src/opera/opera_48x48.png" alt="Opera" width="24px" height="24px" />](http://godban.github.io/browsers-support-badges/)</br>Opera |
| --- | --- | --- | --- | --- |
| Edge | last 2 versions | last 2 versions | last 2 versions | last 2 versions |

## Contributing

Any type of contribution is welcome, here are some examples of how you may contribute to this project:

- Use Ant Design Pro in your daily work.
- Submit [issues](http://github.com/ant-design/ant-design-pro/issues) to report bugs or ask questions.
- Propose [pull requests](http://github.com/ant-design/ant-design-pro/pulls) to improve our code.
# Documentation

+ 29
- 2
react-ui/config/routes.ts View File

@@ -143,6 +143,11 @@ export default [
path: 'compare',
component: './Experiment/Comparison/index',
},
{
name: '实验可视化对比',
path: 'compare-visual',
component: './Experiment/Aim/index',
},
],
},
{
@@ -271,7 +276,18 @@ export default [
{
name: '镜像详情',
path: 'info/:id',
component: './Mirror/Info',
routes: [
{
name: '镜像详情',
path: '',
component: './Mirror/Info',
},
{
name: '新增镜像版本',
path: 'add-version',
component: './Mirror/Create',
},
],
},
{
name: '创建镜像',
@@ -345,7 +361,6 @@ export default [
},
],
},

{
name: '应用开发',
path: '/appsDeployment',
@@ -498,6 +513,18 @@ export default [
},
],
},
{
name: '算力积分',
path: '/points',
routes: [
{
name: '算力积分',
path: '',
key: 'points',
component: './Points/index',
},
],
},
{
path: '*',
layout: false,


+ 7
- 3
react-ui/package.json View File

@@ -1,8 +1,8 @@
{
"name": "ant-design-pro",
"version": "6.0.0",
"name": "cl-model",
"version": "1.0.0",
"private": true,
"description": "An out-of-box UI solution for enterprise applications",
"description": "",
"scripts": {
"analyze": "cross-env ANALYZE=1 max build",
"build": "max build",
@@ -16,6 +16,7 @@
"docker:dev": "docker-compose -f ./docker/docker-compose.dev.yml up",
"docker:push": "npm run docker-hub:build && npm run docker:tag && docker push antdesign/ant-design-pro",
"docker:tag": "docker tag ant-design-pro antdesign/ant-design-pro",
"docs": "typedoc",
"gh-pages": "gh-pages -d dist",
"i18n-remove": "pro i18n-remove --locale=zh-CN --write",
"postinstall": "max setup",
@@ -39,6 +40,7 @@
"start:test": "cross-env REACT_APP_ENV=test MOCK=none UMI_ENV=dev max dev",
"storybook": "storybook dev -p 6006",
"storybook-build": "storybook build",
"storybook-deploy": "./.storybook/scripts/upload-deploy.sh",
"storybook-docs": "storybook dev --docs",
"storybook-docs-build": "storybook build --docs",
"test": "jest",
@@ -66,6 +68,7 @@
"@types/crypto-js": "^4.2.2",
"@umijs/route-utils": "^4.0.1",
"antd": "~5.21.4",
"caniuse-lite": "~1.0.30001707",
"classnames": "^2.3.2",
"crypto-js": "^4.2.0",
"echarts": "^5.5.0",
@@ -132,6 +135,7 @@
"swagger-ui-dist": "^4.18.2",
"ts-loader": "~9.5.2",
"ts-node": "^10.9.1",
"typedoc": "~0.28.1",
"typescript": "^5.0.4",
"umi-presets-pro": "^2.0.0"
},


BIN
react-ui/public/fonts/DingTalk-JinBuTi.ttf View File


BIN
react-ui/public/fonts/DingTalk-JinBuTi.woff View File


BIN
react-ui/public/fonts/DingTalk-JinBuTi.woff2 View File


+ 12
- 4
react-ui/public/fonts/font.css View File

@@ -1,5 +1,13 @@
@font-face {
font-family: Alibaba;
src: url('./ALIBABA-PUHUITI-MEDIUM.TTF');
font-display: swap;
}
font-family: Alibaba;
src: url('./ALIBABA-PUHUITI-MEDIUM.TTF');
font-display: swap;
}

@font-face {
font-family: 'DingTalk-JinBuTi';
src: url('./DingTalk-JinBuTi.woff2') format('woff2'), /* 最优先使用 woff2 */
url('./DingTalk-JinBuTi.woff') format('woff'), /* 兼容性较好的 woff */
url('./DingTalk-JinBuTi.ttf') format('truetype'); /* ttf 作为备选 */
font-display: swap; /* 优化页面加载时的字体显示 */
}

+ 5
- 8
react-ui/src/app.tsx View File

@@ -1,13 +1,16 @@
import RightContent from '@/components/RightContent';
import themes from '@/styles/theme.less';
import { type GlobalInitialState } from '@/types';
import { menuItemRender } from '@/utils/menuRender';
import type { Settings as LayoutSettings } from '@ant-design/pro-components';
import { RuntimeConfig, history } from '@umijs/max';
import { RuntimeAntdConfig } from 'umi';
import defaultSettings from '../config/defaultSettings';
import '../public/fonts/font.css';
import { getAccessToken } from './access';
import ErrorBoundary from './components/ErrorBoundary';
import './dayjsConfig';
import { removeAllPageCacheState } from './hooks/pageCacheState';
import { removeAllPageCacheState } from './hooks/useCacheState';
import {
getRemoteMenu,
getRoutersInfo,
@@ -16,14 +19,9 @@ import {
setRemoteMenu,
} from './services/session';
import './styles/menu.less';
export { requestConfig as request } from './requestConfig';
// const isDev = process.env.NODE_ENV === 'development';
import { type GlobalInitialState } from '@/types';
// import '@/utils/clipboard';
import { menuItemRender } from '@/utils/menuRender';
import ErrorBoundary from './components/ErrorBoundary';
import { needAuth } from './utils';
import { gotoLoginPage } from './utils/ui';
export { requestConfig as request } from './requestConfig';

/**
* @see https://umijs.org/zh-CN/plugins/plugin-initial-state
@@ -139,7 +137,6 @@ export const layout: RuntimeConfig['layout'] = ({ initialState }) => {
onClick: () => {
// 点击菜单项,删除所有的页面 state 缓存
removeAllPageCacheState();
// console.log('click menu');
},
},
...initialState?.settings,


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

Before After
Width: 462  |  Height: 276  |  Size: 45 kB

react-ui/src/assets/img/comfirm-icon.png → react-ui/src/assets/img/copy-icon.png View File


BIN
react-ui/src/assets/img/user-points-bg.png View File

Before After
Width: 981  |  Height: 672  |  Size: 220 kB

BIN
react-ui/src/assets/img/workspace-experiment.png View File

Before After
Width: 147  |  Height: 148  |  Size: 17 kB Width: 216  |  Height: 216  |  Size: 24 kB

BIN
react-ui/src/assets/img/workspace-pipeline.png View File

Before After
Width: 169  |  Height: 159  |  Size: 21 kB Width: 219  |  Height: 216  |  Size: 31 kB

+ 13
- 2
react-ui/src/components/CodeConfigItem/index.tsx View File

@@ -2,6 +2,7 @@ import { AvailableRange } from '@/enums';
import { type CodeConfigData } from '@/pages/CodeConfig/List';
import { Flex, Typography } from 'antd';
import classNames from 'classnames';
import { useState } from 'react';
import styles from './index.less';

type CodeConfigItemProps = {
@@ -10,6 +11,7 @@ type CodeConfigItemProps = {
};

function CodeConfigItem({ item, onClick }: CodeConfigItemProps) {
const [isEllipsis, setIsEllipsis] = useState(false);
return (
<div className={styles['code-config-item']} onClick={() => onClick?.(item)}>
<Flex justify="space-between" align="center" style={{ marginBottom: '15px' }}>
@@ -32,11 +34,20 @@ function CodeConfigItem({ item, onClick }: CodeConfigItemProps) {
</Flex>
<Typography.Paragraph
className={styles['code-config-item__url']}
ellipsis={{ rows: 2, tooltip: item.git_url }}
ellipsis={{
rows: 2,
tooltip: isEllipsis ? item.git_url : false, // 仅当省略时显示 tooltip
onEllipsis: (ellipsis) => setIsEllipsis(ellipsis),
}}
>
{item.git_url}
</Typography.Paragraph>
<div className={styles['code-config-item__branch']}>{item.git_branch}</div>
<Typography.Paragraph
className={styles['code-config-item__branch']}
ellipsis={{ tooltip: item.git_branch }}
>
{item.git_branch}
</Typography.Paragraph>
</div>
);
}


react-ui/src/utils/clipboard.js → react-ui/src/components/CopyingText/clipboard.js View File


react-ui/src/pages/AutoML/components/CopyingText/index.less → react-ui/src/components/CopyingText/index.less View File


+ 26
- 0
react-ui/src/components/CopyingText/index.tsx View File

@@ -0,0 +1,26 @@
import KFIcon from '@/components/KFIcon';
import { Tooltip } from 'antd';
import styles from './index.less';

export type CopyingTextProps = {
text: string;
};

function CopyingText({ text }: CopyingTextProps) {
return (
<div className={styles['copying-text']}>
<span className={styles['copying-text__text']}>{text}</span>
<Tooltip title="复制">
<KFIcon
id="copying"
data-clipboard-text={text}
type="icon-fuzhi2"
className={styles['copying-text__icon']}
color="#606b7a"
/>
</Tooltip>
</div>
);
}

export default CopyingText;

+ 12
- 4
react-ui/src/components/IFramePage/index.tsx View File

@@ -3,6 +3,7 @@ import KFSpin from '@/components/KFSpin';
import { getLabelStudioUrl } from '@/services/developmentEnvironment';
import { to } from '@/utils/promise';
import SessionStorage from '@/utils/sessionStorage';
import { FloatButton } from 'antd';
import classNames from 'classnames';
import { useEffect, useState } from 'react';
import { createPortal } from 'react-dom';
@@ -13,6 +14,7 @@ export enum IframePageType {
AppDevelopment = 'AppDevelopment', // 应用开发
DevEnv = 'DevEnv', // 开发环境
GitLink = 'GitLink', // git link
Aim = 'Aim', // 实验对比
}

const getRequestAPI = (type: IframePageType): (() => Promise<any>) => {
@@ -29,12 +31,20 @@ const getRequestAPI = (type: IframePageType): (() => Promise<any>) => {
});
case IframePageType.GitLink: // git link
return () => Promise.resolve({ code: 200, data: 'http://172.20.32.201:4000' });
case IframePageType.Aim: // Aim
return () =>
Promise.resolve({
code: 200,
data: SessionStorage.getItem(SessionStorage.aimUrlKey) || '',
});
}
};

type IframePageProps = {
/** 子系统 */
type: IframePageType;
/** 是否可以在页签上打开 */
openInTab?: boolean;
/** 自定义样式类名 */
className?: string;
/** 自定义样式 */
@@ -42,18 +52,15 @@ type IframePageProps = {
};

/** 系统内嵌 iframe,目前系统有数据标注、应用开发、开发环境、GitLink 四个子系统,使用时可以添加其他子系统 */
function IframePage({ type, className, style }: IframePageProps) {
function IframePage({ type, openInTab = false, className, style }: IframePageProps) {
const [iframeUrl, setIframeUrl] = useState('');
const [loading, setLoading] = useState(false);

useEffect(() => {
const requestIframeUrl = async () => {
setLoading(true);
const [res] = await to(getRequestAPI(type)());
if (res && res.data) {
setIframeUrl(res.data);
} else {
setLoading(false);
}
};

@@ -68,6 +75,7 @@ function IframePage({ type, className, style }: IframePageProps) {
<div className={classNames('kf-iframe-page', className)} style={style}>
{loading && createPortal(<KFSpin size="large" />, document.body)}
<FullScreenFrame url={iframeUrl} onLoad={hideLoading} onError={hideLoading} />
{openInTab && <FloatButton onClick={() => window.open(iframeUrl, '_blank')} />}
</div>
);
}


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

@@ -22,7 +22,7 @@
border-radius: 4px;

&__value {
.singleLine();
//.singleLine();
margin-right: 8px;
font-size: @font-size-input;
line-height: 1.5714285714285714;


+ 7
- 2
react-ui/src/components/ParameterInput/index.tsx View File

@@ -6,7 +6,7 @@

import { CommonTabKeys } from '@/enums';
import { CloseOutlined } from '@ant-design/icons';
import { ConfigProvider, Form, Input } from 'antd';
import { ConfigProvider, Form, Input, Typography } from 'antd';
import { RuleObject } from 'antd/es/form';
import classNames from 'classnames';
import './index.less';
@@ -120,7 +120,12 @@ function ParameterInput({
>
{valueObj?.showValue ? (
<div className="parameter-input__content">
<span className="parameter-input__content__value">{valueObj?.showValue}</span>
<Typography.Text
className="parameter-input__content__value"
ellipsis={{ tooltip: valueObj.showValue }}
>
{valueObj.showValue}
</Typography.Text>
<CloseOutlined
className="parameter-input__content__close-icon"
onClick={handleRemove}


+ 1
- 1
react-ui/src/components/ParameterSelect/config.tsx View File

@@ -1,4 +1,4 @@
import { filterResourceStandard, resourceFieldNames } from '@/hooks/resource';
import { filterResourceStandard, resourceFieldNames } from '@/hooks/useComputingResource';
import { ServiceData } from '@/pages/ModelDeployment/types';
import { getDatasetList, getModelList } from '@/services/dataset/index.js';
import { getServiceListReq } from '@/services/modelDeployment';


+ 14
- 3
react-ui/src/components/ParameterSelect/index.tsx View File

@@ -4,7 +4,7 @@
* @Description: 参数下拉选择组件,支持资源规格、数据集、模型、服务
*/

import { useComputingResource } from '@/hooks/resource';
import { useComputingResource } from '@/hooks/useComputingResource';
import { to } from '@/utils/promise';
import { Select, type SelectProps } from 'antd';
import { useEffect, useState } from 'react';
@@ -16,13 +16,17 @@ export type ParameterSelectObject = {
[key: string]: any;
};

export type ParameterSelectDataType = 'dataset' | 'model' | 'service' | 'resource';

export interface ParameterSelectProps extends SelectProps {
/** 类型 */
dataType: 'dataset' | 'model' | 'service' | 'resource';
dataType: ParameterSelectDataType;
/** 是否只是展示信息 */
display?: boolean;
/** 值,支持对象,对象必须包含 value */
value?: string | ParameterSelectObject;
/** 用于流水线, 流水线资源规格要求 id 为字符串 */
isPipeline?: boolean;
/** 修改后回调 */
onChange?: (value: string | ParameterSelectObject) => void;
}
@@ -32,6 +36,7 @@ function ParameterSelect({
dataType,
display = false,
value,
isPipeline = false,
onChange,
...rest
}: ParameterSelectProps) {
@@ -39,6 +44,12 @@ function ParameterSelect({
const propsConfig = paramSelectConfig[dataType];
const valueText = typeof value === 'object' && value !== null ? value.value : value;
const [resourceStandardList] = useComputingResource();
const computingResource = isPipeline
? resourceStandardList.map((v) => ({
...v,
id: String(v.id),
}))
: resourceStandardList;

useEffect(() => {
// 获取下拉数据
@@ -56,7 +67,7 @@ function ParameterSelect({
getSelectOptions();
}, [propsConfig]);

const selectOptions = dataType === 'resource' ? resourceStandardList : options;
const selectOptions = dataType === 'resource' ? computingResource : options;

const handleChange = (text: string) => {
if (typeof value === 'object' && value !== null) {


+ 1
- 1
react-ui/src/components/ResourceSelect/index.tsx View File

@@ -6,7 +6,7 @@

import KFIcon from '@/components/KFIcon';
import ResourceSelectorModal, {
ResourceSelectorResponse,
type ResourceSelectorResponse,
ResourceSelectorType,
selectorTypeConfig,
} from '@/components/ResourceSelectorModal';


+ 2
- 0
react-ui/src/components/ResourceSelectorModal/config.tsx View File

@@ -224,6 +224,8 @@ export class MirrorSelector implements SelectorTypeInfo {
image_id: parentKey,
page: 0,
size: 2000,
status: 'available',
state: 1,
});
if (res && res.data) {
const list = res.data.content || [];


+ 2
- 1
react-ui/src/components/RightContent/AvatarDropdown.tsx View File

@@ -3,7 +3,7 @@ import { setRemoteMenu } from '@/services/session';
import { logout } from '@/services/system/auth';
import { ClientInfo } from '@/types';
import SessionStorage from '@/utils/sessionStorage';
import { gotoLoginPage } from '@/utils/ui';
import { gotoLoginPage, oauthLogout } from '@/utils/ui';
import { LogoutOutlined, UserOutlined } from '@ant-design/icons';
import { setAlpha } from '@ant-design/pro-components';
import { useEmotionCss } from '@ant-design/use-emotion-css';
@@ -62,6 +62,7 @@ const AvatarDropdown: React.FC<GlobalHeaderRightProps> = ({ menu }) => {
* 退出登录,并且将当前的 url 保存
*/
const loginOut = async () => {
oauthLogout('http://172.20.32.197:31209/oauth/logout');
await logout();
clearSessionToken();
setRemoteMenu(null);


+ 5
- 0
react-ui/src/global.less View File

@@ -160,3 +160,8 @@ ol {
input:-webkit-autofill {
transition: background-color 5000s ease-in-out 0s;
}

.ant-typography {
color: inherit;
font-size: inherit;
}

+ 0
- 202
react-ui/src/hooks/index.ts View File

@@ -1,202 +0,0 @@
/*
* @Author: 赵伟
* @Date: 2024-04-15 10:01:29
* @Description: 自定义 hooks
*/
import { FormInstance } from 'antd';
import { debounce } from 'lodash';
import { useCallback, useEffect, useMemo, useRef, useState } from 'react';
/**
* 生成具有初始值的状态引用
*
* @param initialValue - 状态的初始值
* @return 包含状态值、状态设置函数和可变引用对象的数组
*/
export function useStateRef<T>(initialValue: T) {
const [value, setValue] = useState(initialValue);

const ref = useRef(value);

useEffect(() => {
ref.current = value;
}, [value]);

return [value, setValue, ref] as const;
}

/**
* 生成一个自定义钩子,用于管理模态框的可见性状态。
*
* @param initialValue - 模态框的初始可见性状态。
* @return 一个数组,包含可见性状态和打开和关闭模态框的函数。
*/
export function useVisible(initialValue: boolean) {
const [visible, setVisible] = useState(initialValue);
const ref = useRef(initialValue);

const open = useCallback(() => {
setVisible(true);
}, []);

const close = useCallback(() => {
setVisible(false);
}, []);

useEffect(() => {
ref.current = visible;
}, [visible]);

return [visible, open, close, ref] as const;
}

type Callback<T> = (state: T) => void;

/**
* 生成一个具有回调机制的可变状态值和更新它的函数。
*
* @param initialValue - 初始状态值。
* @return 一个元组,包含当前状态值和用于更新状态的函数。
*/
export function useCallbackState<T>(initialValue: T) {
const [state, _setState] = useState<T>(initialValue);
const callbackQueue = useRef<Callback<T>[]>([]);
useEffect(() => {
callbackQueue.current.forEach((cb) => cb(state));
callbackQueue.current = [];
}, [state]);
const setState = (newValue: T | ((prevState: T) => T), callback?: Callback<T>) => {
_setState(newValue);
if (callback && typeof callback === 'function') {
callbackQueue.current.push(callback);
}
};
return [state, setState] as const;
}

/**
* 用于追踪 DOM 元素尺寸的 hook。
*
* @param initialWidth - 初始宽度。
* @param initialHeight - 初始高度。
* @param deps - 依赖列表。
* @return 一个元组,包含 DOM 元素的 ref、当前宽度和当前高度。
*/
export function useDomSize<T extends HTMLElement>(
initialWidth: number,
initialHeight: number,
deps: React.DependencyList = [],
) {
const domRef = useRef<T>(null);
const [width, setWidth] = useState(initialWidth);
const [height, setHeight] = useState(initialHeight);

useEffect(() => {
const setDomHeight = () => {
if (domRef.current) {
setHeight(domRef.current.offsetHeight);
setWidth(domRef.current.offsetWidth);
}
};
const debounceFunc = debounce(setDomHeight, 100);

setDomHeight();
window.addEventListener('resize', debounceFunc);

return () => {
window.removeEventListener('resize', debounceFunc);
};
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [domRef, ...deps]);

return [domRef, { width, height }] as const;
}

/**
* 用于在 modal 关闭时重置 Form 表单的 hook。
*
* @param form - Ant Design Form 表单实例
* @param open - modal 是否打开
*/
export const useResetFormOnCloseModal = (form: FormInstance, open: boolean) => {
const prevOpenRef = useRef<boolean>();

useEffect(() => {
prevOpenRef.current = open;
}, [open]);

const prevOpen = prevOpenRef.current;

useEffect(() => {
if (!open && prevOpen) {
form.resetFields();
}
}, [form, prevOpen, open]);
};

/**
* Executes the effect function when the specified condition is true.
*
* @param effect - The effect function to execute.
* @param when - The condition to trigger the effect.
* @param deps - The dependencies for the effect.
*/
export const useEffectWhen = (effect: () => void, when: boolean, deps: React.DependencyList) => {
const requestFns = useRef<(() => void)[]>([]);
useEffect(() => {
if (when) {
effect();
} else {
requestFns.current.splice(0, 1, effect);
}
// eslint-disable-next-line react-hooks/exhaustive-deps
}, deps);

useEffect(() => {
if (when) {
const fn = requestFns.current.pop();
fn?.();
}
}, [when]);
};

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

const checked = useMemo(() => {
return selected.length === list.length && selected.length > 0;
}, [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];
}
});
},
[isSingleChecked],
);

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

react-ui/src/hooks/pageCacheState.ts → react-ui/src/hooks/useCacheState.ts View File

@@ -29,13 +29,18 @@ const removeCacheState = (key: string) => {
}
};

// 移除所有页面 state 缓存
/**
* 移除所有页面 state 缓存
*/
export const removeAllPageCacheState = () => {
pageKeys.forEach((key) => {
sessionStorage.removeItem(key);
});
};

/**
* 缓存页面数据
*/
export const useCacheState = () => {
const { pathname } = window.location;
const key = 'pagecache:' + pathname;

+ 25
- 0
react-ui/src/hooks/useCallbackState.ts View File

@@ -0,0 +1,25 @@
import { useEffect, useRef, useState } from 'react';

type Callback<T> = (state: T) => void;

/**
* 生成一个具有回调机制的可变状态值和更新它的函数。谨慎使用
*
* @param initialValue - 初始状态值。
* @return 一个元组,包含当前状态值和用于更新状态的函数。
*/
export function useCallbackState<T>(initialValue: T) {
const [state, _setState] = useState<T>(initialValue);
const callbackQueue = useRef<Callback<T>[]>([]);
useEffect(() => {
callbackQueue.current.forEach((cb) => cb(state));
callbackQueue.current = [];
}, [state]);
const setState = (newValue: T | ((prevState: T) => T), callback?: Callback<T>) => {
_setState(newValue);
if (callback && typeof callback === 'function') {
callbackQueue.current.push(callback);
}
};
return [state, setState] as const;
}

+ 47
- 0
react-ui/src/hooks/useCheck.ts View File

@@ -0,0 +1,47 @@
import { useCallback, useMemo, useState } from 'react';

/**
* 选择、全选操作
* @param list - 需要进行选择的列表
* @return [选中的项, 设置选中的方法, 是否全选, 是否部分选中, 全选方法,是否单个选中,选中单个方法]
*/
export const useCheck = <T>(list: T[]) => {
const [selected, setSelected] = useState<T[]>([]);

const checked = useMemo(() => {
return selected.length === list.length && selected.length > 0;
}, [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];
}
});
},
[isSingleChecked],
);

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

react-ui/src/hooks/resource.ts → react-ui/src/hooks/useComputingResource.ts View File

@@ -12,7 +12,7 @@ import { useCallback, useEffect, useState } from 'react';

const computingResource: ComputingResource[] = [];

// 过滤资源规格
/** 过滤资源规格 */
export const filterResourceStandard: SelectProps<string, ComputingResource>['filterOption'] = (
input: string,
option?: ComputingResource,
@@ -22,13 +22,13 @@ export const filterResourceStandard: SelectProps<string, ComputingResource>['fil
);
};

// 资源规格字段
/** 资源规格字段 */
export const resourceFieldNames = {
label: 'description',
value: 'id',
};

// 获取资源规格
/** 获取资源规格 */
export function useComputingResource() {
const [resourceStandardList, setResourceStandardList] = useState<ComputingResource[]>([]);


+ 40
- 0
react-ui/src/hooks/useDomSize.ts View File

@@ -0,0 +1,40 @@
import { debounce } from 'lodash';
import { useEffect, useRef, useState } from 'react';

/**
* 用于追踪 DOM 元素尺寸的 hook。
*
* @param initialWidth - 初始宽度。
* @param initialHeight - 初始高度。
* @param deps - 依赖列表。
* @return 一个元组,包含 DOM 元素的 ref、当前宽度和当前高度。
*/
export function useDomSize<T extends HTMLElement>(
initialWidth: number,
initialHeight: number,
deps: React.DependencyList = [],
) {
const domRef = useRef<T>(null);
const [width, setWidth] = useState(initialWidth);
const [height, setHeight] = useState(initialHeight);

useEffect(() => {
const setDomHeight = () => {
if (domRef.current) {
setHeight(domRef.current.offsetHeight);
setWidth(domRef.current.offsetWidth);
}
};
const debounceFunc = debounce(setDomHeight, 100);

setDomHeight();
window.addEventListener('resize', debounceFunc);

return () => {
window.removeEventListener('resize', debounceFunc);
};
// eslint-disable-next-line react-hooks/exhaustive-deps
}, deps);

return [domRef, { width, height }] as const;
}

react-ui/src/hooks/draggable.ts → react-ui/src/hooks/useDraggable.ts View File

@@ -1,6 +1,8 @@
// 处理 react-draggable 组件拖动结束时,响应了点击事件的
import { useState } from 'react';

/**
* 处理 react-draggable 组件拖动结束时,响应了点击事件的
*/
export const useDraggable = (onClick: () => void) => {
const [isDragging, setIsDragging] = useState(false);


+ 24
- 0
react-ui/src/hooks/useEffectWhen.ts View File

@@ -0,0 +1,24 @@
import { useEffect, useRef } from 'react';

/**
* 当指定的条件为真时执行 Effect 函数。
*
* @param effect - The effect function to execute.
* @param when - The condition to trigger the effect.
* @param deps - The dependencies for the effect.
*/
export const useEffectWhen = (effect: () => void, when: boolean, deps: React.DependencyList) => {
const requestFn = useRef<(() => void) | undefined>(effect);
useEffect(() => {
requestFn.current = effect;
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [...deps, effect]);

useEffect(() => {
if (when && requestFn.current) {
requestFn.current();
requestFn.current = undefined;
}
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [...deps, when]);
};

+ 24
- 0
react-ui/src/hooks/useResetForm.ts View File

@@ -0,0 +1,24 @@
import { FormInstance } from 'antd';
import { useEffect, useRef } from 'react';

/**
* 用于在 modal 关闭时重置 Form 表单的 hook。
*
* @param form - Ant Design Form 表单实例
* @param open - modal 是否打开
*/
export const useResetForm = (form: FormInstance, open: boolean) => {
const prevOpenRef = useRef<boolean>();

useEffect(() => {
prevOpenRef.current = open;
}, [open]);

const prevOpen = prevOpenRef.current;

useEffect(() => {
if (!open && prevOpen) {
form.resetFields();
}
}, [form, prevOpen, open]);
};

+ 46
- 0
react-ui/src/hooks/useSSE.ts View File

@@ -0,0 +1,46 @@
import { parseJsonText } from '@/utils';
import { useCallback, useRef } from 'react';

export const useSSE = (onMessage: (data: any) => void) => {
const evtSourceRef = useRef<EventSource | null>(null);

const setupSSE = useCallback(
(name: string, namespace: string) => {
const { origin } = location;
const params = encodeURIComponent(`metadata.namespace=${namespace},metadata.name=${name}`);
const evtSource = new EventSource(
`${origin}/api/v1/realtimeStatus?listOptions.fieldSelector=${params}`,
{ withCredentials: false },
);
evtSource.onmessage = (event) => {
const data = event?.data;
if (!data) {
return;
}
const dataJson = parseJsonText(data);
if (dataJson) {
const nodes = dataJson?.result?.object?.status?.nodes;
if (nodes) {
onMessage(nodes);
}
}
};

evtSource.onerror = (error) => {
console.error('SSE error: ', error);
};

evtSourceRef.current = evtSource;
},
[onMessage],
);

const closeSSE = useCallback(() => {
if (evtSourceRef.current) {
evtSourceRef.current.close();
evtSourceRef.current = null;
}
}, []);

return [setupSSE, closeSSE];
};

+ 19
- 0
react-ui/src/hooks/useStateRef.ts View File

@@ -0,0 +1,19 @@
import { useEffect, useRef, useState } from 'react';

/**
* 生成具有初始值的状态引用
*
* @param initialValue - 状态的初始值
* @return 包含状态值、状态设置函数和可变引用对象的数组
*/
export function useStateRef<T>(initialValue: T) {
const [value, setValue] = useState(initialValue);

const ref = useRef(value);

useEffect(() => {
ref.current = value;
}, [value]);

return [value, setValue, ref] as const;
}

+ 26
- 0
react-ui/src/hooks/useVisible.ts View File

@@ -0,0 +1,26 @@
import { useCallback, useEffect, useRef, useState } from 'react';

/**
* 生成一个自定义钩子,用于管理模态框的可见性状态。
*
* @param initialValue - 模态框的初始可见性状态。
* @return 一个数组,包含 visible、打开函数、关闭函数和 visible ref。
*/
export function useVisible(initialValue: boolean) {
const [visible, setVisible] = useState(initialValue);
const ref = useRef(initialValue);

const open = useCallback(() => {
setVisible(true);
}, []);

const close = useCallback(() => {
setVisible(false);
}, []);

useEffect(() => {
ref.current = visible;
}, [visible]);

return [visible, open, close, ref] as const;
}

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

@@ -261,8 +261,3 @@
}
}
}

.ant-typography {
color: inherit;
font-size: inherit;
}

+ 1
- 1
react-ui/src/pages/AutoML/Instance/index.tsx View File

@@ -28,7 +28,6 @@ function AutoMLInstance() {
const [autoMLInfo, setAutoMLInfo] = useState<AutoMLData | undefined>(undefined);
const [instanceInfo, setInstanceInfo] = useState<AutoMLInstanceData | undefined>(undefined);
const params = useParams();
// const autoMLId = safeInvoke(Number)(params.autoMLId);
const instanceId = safeInvoke(Number)(params.id);
const evtSourceRef = useRef<EventSource | null>(null);

@@ -187,6 +186,7 @@ function AutoMLInstance() {
icon: <KFIcon type="icon-Trialliebiao" />,
children: (
<ExperimentHistory
calcMetrics={autoMLInfo?.scoring_functions}
fileUrl={instanceInfo?.run_history_path}
isClassification={autoMLInfo?.task_type === AutoMLTaskType.Classification}
/>


+ 0
- 26
react-ui/src/pages/AutoML/components/CopyingText/index.tsx View File

@@ -1,26 +0,0 @@
import KFIcon from '@/components/KFIcon';
import { Typography } from 'antd';
import styles from './index.less';

export type CopyingTextProps = {
text: string;
};

function CopyingText({ text }: CopyingTextProps) {
return (
<div className={styles['copying-text']}>
<Typography.Text ellipsis={{ tooltip: text }} className={styles['copying-text__text']}>
{text}
</Typography.Text>
<KFIcon
id="copying"
data-clipboard-text={text}
type="icon-fuzhi2"
className={styles['copying-text__icon']}
color="#606b7a"
/>
</div>
);
}

export default CopyingText;

+ 14
- 10
react-ui/src/pages/AutoML/components/ExperimentHistory/index.tsx View File

@@ -8,8 +8,9 @@ import TrialStatusCell from '../TrialStatusCell';
import styles from './index.less';

type ExperimentHistoryProps = {
fileUrl?: string;
isClassification: boolean;
calcMetrics?: string; // 计算指标
fileUrl?: string; // 文件url
isClassification: boolean; // 是否是分类
};

type TableData = {
@@ -22,7 +23,7 @@ type TableData = {
althorithm?: string;
};

function ExperimentHistory({ fileUrl, isClassification }: ExperimentHistoryProps) {
function ExperimentHistory({ calcMetrics, fileUrl, isClassification }: ExperimentHistoryProps) {
const [tableData, setTableData] = useState<TableData[]>([]);
useEffect(() => {
// 获取实验运行历史记录
@@ -33,7 +34,7 @@ function ExperimentHistory({ fileUrl, isClassification }: ExperimentHistoryProps
const list: TableData[] = data.map((item) => {
return {
id: item[0]?.[0],
accuracy: item[1]?.[5]?.accuracy,
accuracy: calcMetrics ? item[1]?.[5]?.[calcMetrics] : undefined,
duration: item[1]?.[5]?.duration,
train_loss: item[1]?.[5]?.train_loss,
status: item[1]?.[2]?.['__enum__']?.split('.')?.[1],
@@ -64,12 +65,6 @@ function ExperimentHistory({ fileUrl, isClassification }: ExperimentHistoryProps
width: 80,
render: tableCellRender(false),
},
{
title: '准确率',
dataIndex: 'accuracy',
key: 'accuracy',
render: tableCellRender(true),
},
{
title: '耗时',
dataIndex: 'duration',
@@ -103,6 +98,15 @@ function ExperimentHistory({ fileUrl, isClassification }: ExperimentHistoryProps
},
];

if (calcMetrics) {
columns.splice(0, 0, {
title: `指标:${calcMetrics}`,
dataIndex: 'accuracy',
key: 'accuracy',
render: tableCellRender(true),
});
}

return (
<div className={styles['experiment-history']}>
<div className={styles['experiment-history__content']}>


+ 17
- 4
react-ui/src/pages/AutoML/components/ExperimentInstance/index.tsx View File

@@ -1,6 +1,6 @@
import KFIcon from '@/components/KFIcon';
import { ExperimentStatus } from '@/enums';
import { useCheck } from '@/hooks';
import { useCheck } from '@/hooks/useCheck';
import { experimentStatusInfo } from '@/pages/Experiment/status';
import themes from '@/styles/theme.less';
import { type ExperimentInstance } from '@/types';
@@ -58,7 +58,8 @@ function ExperimentInstanceComponent({
// 删除实验实例确认
const handleRemove = (instance: ExperimentInstance) => {
modalConfirm({
title: '确定删除该条实例吗?',
title: '删除后,该实验实例将不可恢复',
content: '是否确认删除?',
onOk: () => {
deleteExperimentInstance(instance.id);
},
@@ -96,6 +97,18 @@ function ExperimentInstanceComponent({
}
};

// 终止实验实例
const handleTerminate = (instance: ExperimentInstance) => {
modalConfirm({
title: '终止后,该次实验运行将不可恢复',
content: '是否确认终止?',
isDelete: false,
onOk: () => {
terminateExperimentInstance(instance);
},
});
};

// 终止实验实例
const terminateExperimentInstance = async (instance: ExperimentInstance) => {
const request = config.stopInsReq;
@@ -107,7 +120,7 @@ function ExperimentInstanceComponent({
};

if (!experimentInsList || experimentInsList.length === 0) {
return <div style={{ textAlign: 'center' }}>暂无实验实例</div>;
return <div style={{ textAlign: 'center' }}>暂无数据</div>;
}

return (
@@ -188,7 +201,7 @@ function ExperimentInstanceComponent({
item.status === ExperimentStatus.Terminated
}
icon={<KFIcon type="icon-zhongzhi" />}
onClick={() => terminateExperimentInstance(item)}
onClick={() => handleTerminate(item)}
>
终止
</Button>


+ 8
- 10
react-ui/src/pages/AutoML/components/ExperimentList/index.tsx View File

@@ -7,7 +7,7 @@
import KFIcon from '@/components/KFIcon';
import PageTitle from '@/components/PageTitle';
import { ExperimentStatus } from '@/enums';
import { useCacheState } from '@/hooks/pageCacheState';
import { useCacheState } from '@/hooks/useCacheState';
import { AutoMLData } from '@/pages/AutoML/types';
import { experimentStatusInfo } from '@/pages/Experiment/status';
import themes from '@/styles/theme.less';
@@ -93,17 +93,14 @@ function ExperimentList({ type }: ExperimentListProps) {
const [res] = await to(request(record.id));
if (res) {
message.success('删除成功');
// 如果是一页的唯一数据,删除,请求第一页的数据
// 如果是一页的唯一数据,删除,请求第一页的数据
// 否则直接刷新这一页的数据
// 避免回到第一页
if (tableData.length > 1) {
setPagination((prev) => ({
setPagination((prev) => {
return {
...prev,
current: 1,
}));
} else {
getAutoMLList();
}
current: tableData.length === 1 ? Math.max(1, prev.current! - 1) : prev.current,
};
});
}
};

@@ -188,6 +185,7 @@ function ExperimentList({ type }: ExperimentListProps) {
if (expanded) {
setExpandedRowKeys([record.id]);
getExperimentInsList(record.id, 0);
refreshExperimentList();
} else {
setExpandedRowKeys([]);
}


+ 8
- 1
react-ui/src/pages/CodeConfig/List/index.tsx View File

@@ -75,8 +75,15 @@ function CodeConfigList() {
const deleteRecord = async (id: number) => {
const [res] = await to(deleteCodeConfigReq(id));
if (res) {
getDataList();
message.success('删除成功');
// 如果是一页的唯一数据,删除后,请求第一页的数据
// 否则直接刷新这一页的数据
setPagination((prev) => {
return {
...prev,
current: dataList!.length === 1 ? Math.max(1, prev.current! - 1) : prev.current,
};
});
}
};



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

@@ -70,7 +70,12 @@ function CodeConfigItem({ item, onClick, onEdit, onRemove }: CodeConfigItemProps
>
{item.git_url}
</Typography.Paragraph>
<div className={styles['code-config-item__branch']}>{item.git_branch}</div>
<Typography.Paragraph
className={styles['code-config-item__branch']}
ellipsis={{ tooltip: item.git_branch }}
>
{item.git_branch}
</Typography.Paragraph>
</div>
<Flex justify="space-between">
<div className={styles['code-config-item__user']}>


+ 29
- 21
react-ui/src/pages/Dataset/components/AddDatasetModal/index.tsx View File

@@ -4,7 +4,12 @@ import KFModal from '@/components/KFModal';
import { CategoryData, DataSource, ResourceType, resourceConfig } from '@/pages/Dataset/config';
import { addDataset } from '@/services/dataset/index.js';
import { to } from '@/utils/promise';
import { getFileListFromEvent, limitUploadFileType, validateUploadFiles } from '@/utils/ui';
import {
getFileListFromEvent,
limitUploadFileType,
removeUploadedFile,
validateUploadFiles,
} from '@/utils/ui';
import {
Button,
Form,
@@ -29,11 +34,6 @@ interface AddDatasetModalProps extends Omit<ModalProps, 'onOk'> {

function AddDatasetModal({ typeList, tagList, onOk, ...rest }: AddDatasetModalProps) {
const [uuid] = useState(Date.now());
// const [clusterOptions, setClusterOptions] = useState<DictValueEnumObj[]>([]);

// useEffect(() => {
// getClusterOptions();
// }, []);

// 上传组件参数
const uploadProps: UploadProps = {
@@ -44,16 +44,9 @@ function AddDatasetModal({ typeList, tagList, onOk, ...rest }: AddDatasetModalPr
defaultFileList: [],
accept: '.zip,.tgz',
beforeUpload: limitUploadFileType('zip,tgz'),
onRemove: removeUploadedFile,
};

// 获取集群版本数据
// const getClusterOptions = async () => {
// const [res] = await to(getDictSelectOption('available_cluster'));
// if (res) {
// setClusterOptions(res);
// }
// };

// 上传请求
const createDataset = async (params: any) => {
const [res] = await to(addDataset(params));
@@ -113,7 +106,7 @@ function AddDatasetModal({ typeList, tagList, onOk, ...rest }: AddDatasetModalPr
},
]}
>
<Input placeholder="请输入数据名称" showCount allowClear maxLength={50} />
<Input placeholder="请输入数据名称" showCount allowClear maxLength={40} />
</Form.Item>
<Form.Item
label="数据集版本"
@@ -159,27 +152,42 @@ function AddDatasetModal({ typeList, tagList, onOk, ...rest }: AddDatasetModalPr
showSearch
/>
</Form.Item>
{/* <Form.Item label="集群版本" name="available_cluster">
<Select allowClear placeholder="请选择集群版本" options={clusterOptions} />
</Form.Item> */}
<Form.Item
label="数据集简介"
label="数据集描述"
name="description"
rules={[
{
required: true,
message: '请输入数据集简介',
message: '请输入数据集描述',
},
]}
>
<Input.TextArea
placeholder="请输入数据集简介"
placeholder="请输入数据集描述"
showCount
maxLength={200}
autoSize={{ minRows: 2, maxRows: 6 }}
allowClear
/>
</Form.Item>
<Form.Item
label="版本描述"
name="version_desc"
rules={[
{
required: true,
message: '请输入版本描述',
},
]}
>
<Input.TextArea
placeholder="请输入版本描述"
autoSize={{ minRows: 2, maxRows: 6 }}
maxLength={200}
showCount
allowClear
/>
</Form.Item>
<Form.Item
label="可见性"
name="is_public"


+ 24
- 5
react-ui/src/pages/Dataset/components/AddModelModal/index.tsx View File

@@ -4,7 +4,7 @@ import KFModal from '@/components/KFModal';
import { CategoryData, DataSource, ResourceType, resourceConfig } from '@/pages/Dataset/config';
import { addModel } from '@/services/dataset/index.js';
import { to } from '@/utils/promise';
import { getFileListFromEvent, validateUploadFiles } from '@/utils/ui';
import { getFileListFromEvent, removeUploadedFile, validateUploadFiles } from '@/utils/ui';
import {
Button,
Form,
@@ -37,6 +37,7 @@ function AddModelModal({ typeList, tagList, onOk, ...rest }: AddModelModalProps)
Authorization: getAccessToken() || '',
},
defaultFileList: [],
onRemove: removeUploadedFile,
};

// 上传请求
@@ -96,7 +97,7 @@ function AddModelModal({ typeList, tagList, onOk, ...rest }: AddModelModalProps)
},
]}
>
<Input placeholder="请输入模型名称" showCount allowClear maxLength={50} />
<Input placeholder="请输入模型名称" showCount allowClear maxLength={40} />
</Form.Item>
<Form.Item
label="模型版本"
@@ -143,23 +144,41 @@ function AddModelModal({ typeList, tagList, onOk, ...rest }: AddModelModalProps)
/>
</Form.Item>
<Form.Item
label="模型简介"
label="模型描述"
name="description"
rules={[
{
required: true,
message: '请输入模型简介',
message: '请输入模型描述',
},
]}
>
<Input.TextArea
placeholder="请输入模型简介"
placeholder="请输入模型描述"
maxLength={200}
autoSize={{ minRows: 2, maxRows: 6 }}
showCount
allowClear
/>
</Form.Item>
<Form.Item
label="版本描述"
name="version_desc"
rules={[
{
required: true,
message: '请输入版本描述',
},
]}
>
<Input.TextArea
placeholder="请输入版本描述"
autoSize={{ minRows: 2, maxRows: 6 }}
maxLength={200}
showCount
allowClear
/>
</Form.Item>
<Form.Item
label="可见性"
name="is_public"


+ 2
- 1
react-ui/src/pages/Dataset/components/AddVersionModal/index.tsx View File

@@ -3,7 +3,7 @@ import KFIcon from '@/components/KFIcon';
import KFModal from '@/components/KFModal';
import { DataSource, ResourceType, resourceConfig } from '@/pages/Dataset/config';
import { to } from '@/utils/promise';
import { getFileListFromEvent, validateUploadFiles } from '@/utils/ui';
import { getFileListFromEvent, removeUploadedFile, validateUploadFiles } from '@/utils/ui';
import {
Button,
Form,
@@ -50,6 +50,7 @@ function AddVersionModal({
defaultFileList: [],
beforeUpload: config.beforeUpload,
accept: config.uploadAccept,
onRemove: removeUploadedFile,
};

// 上传请求


+ 3
- 0
react-ui/src/pages/Dataset/components/ResourceInfo/index.tsx View File

@@ -13,6 +13,7 @@ import {
} from '@/pages/Dataset/config';
import GraphLegend from '@/pages/Model/components/GraphLegend';
import ModelEvolution from '@/pages/Model/components/ModelEvolution';
import { VersionChangedMessage } from '@/utils/constant';
import { openAntdModal } from '@/utils/modal';
import { to } from '@/utils/promise';
import { modalConfirm } from '@/utils/ui';
@@ -124,6 +125,7 @@ const ResourceInfo = ({ resourceType }: ResourceInfoProps) => {
onOk: () => {
getVersionList(true);
close();
window.postMessage({ type: VersionChangedMessage });
},
});
};
@@ -170,6 +172,7 @@ const ResourceInfo = ({ resourceType }: ResourceInfoProps) => {
if (res) {
message.success('删除成功');
getVersionList(true);
window.postMessage({ type: VersionChangedMessage });
}
};



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

@@ -21,7 +21,7 @@ const getDatasetDatas = (data: DatasetData): BasicInfoData[] => [
value: data.name,
},
{
label: '版本',
label: '数据集版本',
value: data.version,
},
{
@@ -64,7 +64,7 @@ const getModelDatas = (data: ModelData): BasicInfoData[] => [
ellipsis: true,
},
{
label: '版本',
label: '模型版本',
value: data.version,
ellipsis: true,
},


+ 8
- 1
react-ui/src/pages/Dataset/components/ResourceList/index.tsx View File

@@ -107,8 +107,15 @@ function ResourceList(
const request = config.deleteRecord;
const [res] = await to(request(params));
if (res) {
getDataList();
message.success('删除成功');
// 如果是一页的唯一数据,删除后,请求第一页的数据
// 否则直接刷新这一页的数据
setPagination((prev) => {
return {
...prev,
current: dataList!.length === 1 ? Math.max(1, prev.current! - 1) : prev.current,
};
});
}
};



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

@@ -1,5 +1,5 @@
import { CommonTabKeys } from '@/enums';
import { useCacheState } from '@/hooks/pageCacheState';
import { useCacheState } from '@/hooks/useCacheState';
import { getAssetIcon } from '@/services/dataset/index.js';
import { to } from '@/utils/promise';
import { Flex, Tabs, type TabsProps } from 'antd';


+ 6
- 0
react-ui/src/pages/Dataset/components/ResourceVersion/index.tsx View File

@@ -1,3 +1,9 @@
/*
* @Author: 赵伟
* @Date: 2025-03-24 15:41:42
* @Description: 版本文件列表
*/

import KFIcon from '@/components/KFIcon';
import {
ResourceData,


+ 0
- 2
react-ui/src/pages/Dataset/components/VersionCompareModal/index.less View File

@@ -11,7 +11,6 @@
text-align: center;
background: @background;
border-radius: 4px 4px 0 0;
.singleLine();
}

.text() {
@@ -20,7 +19,6 @@
font-size: 13px;
line-height: 22px;
word-break: break-all;
.singleLine();
}

.version-container(@background) {


+ 20
- 6
react-ui/src/pages/Dataset/components/VersionCompareModal/index.tsx View File

@@ -88,7 +88,7 @@ function VersionCompareModal({
format: formatProject,
},
{
key: 'description',
key: 'version_desc',
text: '版本描述',
},
]
@@ -123,7 +123,7 @@ function VersionCompareModal({
format: formatTrainTask,
},
{
key: 'description',
key: 'version_desc',
text: '版本描述',
},
],
@@ -193,7 +193,14 @@ function VersionCompareModal({
))}
</div>
<div className={styles['version-compare__left']}>
<div className={styles['version-compare__left__title']}>{v1.version}</div>
<div className={styles['version-compare__left__title']}>
<Typography.Text
ellipsis={{ tooltip: v1.version }}
style={{ width: '100%', lineHeight: 'inherit' }}
>
{v1.version}
</Typography.Text>
</div>
{fields.map(({ key, format }) => {
const text = getValue(v1, key as keyof typeof v1, format);
return (
@@ -203,7 +210,7 @@ function VersionCompareModal({
[styles['version-compare__left__text--different']]: isDifferent(key),
})}
>
<Typography.Text ellipsis={{ tooltip: text }}>
<Typography.Text ellipsis={{ tooltip: text }} style={{ width: '100%' }}>
{isEmpty(text) ? '--' : text}
</Typography.Text>
</div>
@@ -211,7 +218,14 @@ function VersionCompareModal({
})}
</div>
<div className={styles['version-compare__right']}>
<div className={styles['version-compare__right__title']}>{v2.version}</div>
<div className={styles['version-compare__right__title']}>
<Typography.Text
ellipsis={{ tooltip: v2.version }}
style={{ width: '100%', lineHeight: 'inherit' }}
>
{v2.version}
</Typography.Text>
</div>
{fields.map(({ key, format }) => {
const text = getValue(v2, key as keyof typeof v2, format);
return (
@@ -221,7 +235,7 @@ function VersionCompareModal({
[styles['version-compare__right__text--different']]: isDifferent(key),
})}
>
<Typography.Text ellipsis={{ tooltip: text }}>
<Typography.Text ellipsis={{ tooltip: text }} style={{ width: '100%' }}>
{isEmpty(text) ? '--' : text}
</Typography.Text>
</div>


+ 4
- 4
react-ui/src/pages/DevelopmentEnvironment/Create/index.tsx View File

@@ -104,16 +104,16 @@ function EditorCreate() {
<Row gutter={10}>
<Col span={10}>
<Form.Item
label="任务名称"
label="编辑器名称"
name="name"
rules={[
{
required: true,
message: '请输入任务名称',
message: '请输入编辑器名称',
},
]}
>
<Input placeholder="请输入任务名称" maxLength={64} showCount allowClear />
<Input placeholder="请输入编辑器名称" maxLength={64} showCount allowClear />
</Form.Item>
</Col>
</Row>
@@ -137,7 +137,7 @@ function EditorCreate() {
<Col span={10}>
<Form.Item
label="资源规格"
name="standard"
name="computing_resource_id"
rules={[
{
required: true,


+ 80
- 36
react-ui/src/pages/DevelopmentEnvironment/List/index.tsx View File

@@ -6,7 +6,9 @@

import KFIcon from '@/components/KFIcon';
import { DevEditorStatus } from '@/enums';
import { useCacheState } from '@/hooks/pageCacheState';
import { useCacheState } from '@/hooks/useCacheState';
import { useComputingResource } from '@/hooks/useComputingResource';
import { DatasetData, ModelData } from '@/pages/Dataset/config';
import {
deleteEditorReq,
getEditorListReq,
@@ -14,6 +16,7 @@ import {
stopEditorReq,
} from '@/services/developmentEnvironment';
import themes from '@/styles/theme.less';
import { parseJsonText } from '@/utils';
import { openAntdModal } from '@/utils/modal';
import { to } from '@/utils/promise';
import SessionStorage from '@/utils/sessionStorage';
@@ -42,6 +45,10 @@ export type EditorData = {
update_by: string;
create_time: string;
url: string;
computing_resource_id: number;
dataset?: string | DatasetData;
model?: string | ModelData;
image?: string;
};

function EditorList() {
@@ -56,6 +63,7 @@ function EditorList() {
pageSize: 10,
},
);
const getResourceDescription = useComputingResource()[1];

// 获取编辑器列表
const getEditorList = useCallback(async () => {
@@ -66,6 +74,10 @@ function EditorList() {
const [res] = await to(getEditorListReq(params));
if (res && res.data) {
const { content = [], totalElements = 0 } = res.data;
content.forEach((item: EditorData) => {
item.dataset = typeof item.dataset === 'string' ? parseJsonText(item.dataset) : null;
item.model = typeof item.model === 'string' ? parseJsonText(item.model) : null;
});
setTableData(content);
setTotal(totalElements);
}
@@ -80,17 +92,14 @@ function EditorList() {
const [res] = await to(deleteEditorReq(id));
if (res) {
message.success('删除成功');
// 如果是一页的唯一数据,删除,请求第一页的数据
// 如果是一页的唯一数据,删除,请求第一页的数据
// 否则直接刷新这一页的数据
// 避免回到第一页
if (tableData.length > 1) {
setPagination((prev) => ({
setPagination((prev) => {
return {
...prev,
current: 1,
}));
} else {
getEditorList();
}
current: tableData.length === 1 ? Math.max(1, prev.current! - 1) : prev.current,
};
});
}
};

@@ -105,11 +114,18 @@ function EditorList() {

// 停止编辑器
const stopEditor = async (id: number) => {
const [res] = await to(stopEditorReq(id));
if (res) {
message.success('操作成功');
getEditorList();
}
modalConfirm({
title: '停止后,该编辑器将不可使用',
content: '是否确认停止?',
isDelete: false,
onOk: async () => {
const [res] = await to(stopEditorReq(id));
if (res) {
message.success('操作成功');
getEditorList();
}
},
});
};

// 制作镜像
@@ -168,44 +184,72 @@ function EditorList() {
title: '编辑器名称',
dataIndex: 'name',
key: 'name',
width: '30%',
render: (text, record) =>
record.url && record.status === DevEditorStatus.Running ? (
<a className="kf-table-row-link" onClick={(e) => gotoEditorPage(e, record)}>
{text}
</a>
) : (
<span>{text ?? '--'}</span>
),
},
{
title: '状态',
dataIndex: 'status',
key: 'status',
width: '10%',
render: EditorStatusCell,
width: '20%',
render: (text, record, index) =>
record.url && record.status === DevEditorStatus.Running
? tableCellRender<EditorData>(true, TableCellValueType.Link, {
onClick: (record, e) => gotoEditorPage(e, record),
})(text, record, index)
: tableCellRender<EditorData>(true, TableCellValueType.Text)(text, record, index),
},
{
title: '资源',
title: '计算资源',
dataIndex: 'computing_resource',
key: 'computing_resource',
width: '20%',
width: 100,
render: tableCellRender(),
},
{
title: '资源规格',
dataIndex: 'computing_resource_id',
key: 'computing_resource_id',
width: '20%',
render: tableCellRender(true, TableCellValueType.Custom, {
format: getResourceDescription,
}),
},
{
title: '数据集',
dataIndex: ['dataset', 'showValue'],
key: 'dataset',
width: '15%',
render: tableCellRender(true),
},
{
title: '模型',
dataIndex: ['model', 'showValue'],
key: 'model',
width: '15%',
render: tableCellRender(true),
},
{
title: '镜像',
dataIndex: ['image'],
key: 'image',
width: '15%',
render: tableCellRender(true),
},
{
title: '创建者',
dataIndex: 'update_by',
key: 'update_by',
width: '20%',
render: tableCellRender(),
width: '15%',
render: tableCellRender(true),
},
{
title: '创建时间',
dataIndex: 'create_time',
key: 'create_time',
width: '20%',
width: 180,
render: tableCellRender(false, TableCellValueType.Date),
},
{
title: '状态',
dataIndex: 'status',
key: 'status',
width: 80,
render: EditorStatusCell,
},
{
title: '操作',
dataIndex: 'operation',


+ 7
- 7
react-ui/src/pages/DevelopmentEnvironment/components/CreateMirrorModal/index.tsx View File

@@ -20,7 +20,7 @@ function CreateMirrorModal({ envId, onOk, ...rest }: CreateMirrorModalProps) {
}),
);
if (res) {
message.success('创建成功,请到 “AI资产” - “个人镜像” 中查看');
message.success('创建成功,请到 “多形态资源库” - “个人镜像” 中查看');
onOk?.();
}
};
@@ -51,20 +51,20 @@ function CreateMirrorModal({ envId, onOk, ...rest }: CreateMirrorModalProps) {
message: '请输入镜像名称',
},
{
pattern: /^[a-z0-9/_-]*$/,
message: '只支持小写字母、数字、下划线(_)、中横线(-)、斜杠(/)',
pattern: /^[a-z0-9/._-]*$/,
message: '只支持小写字母、数字、点(.)、下划线(_)、中横线(-)、斜杠(/)',
},
]}
>
<Input placeholder="请输入镜像名称" maxLength={64} showCount allowClear />
</Form.Item>
<Form.Item
label="镜像Tag"
label="镜像版本"
name="tag_name"
rules={[
{
required: true,
message: '请输入镜像Tag',
message: '请输入镜像版本',
},
{
pattern: /^[a-zA-Z0-9._-]+$/,
@@ -72,7 +72,7 @@ function CreateMirrorModal({ envId, onOk, ...rest }: CreateMirrorModalProps) {
},
]}
>
<Input placeholder="请输入镜像Tag" maxLength={64} showCount allowClear />
<Input placeholder="请输入镜像版本" maxLength={64} showCount allowClear />
</Form.Item>
<Form.Item
label="镜像描述"
@@ -87,7 +87,7 @@ function CreateMirrorModal({ envId, onOk, ...rest }: CreateMirrorModalProps) {
<Input.TextArea
placeholder="请输入镜像描述"
autoSize={{ minRows: 3, maxRows: 6 }}
maxLength={256}
maxLength={128}
showCount
allowClear
/>


+ 12
- 0
react-ui/src/pages/Experiment/Aim/index.tsx View File

@@ -0,0 +1,12 @@
/*
* @Author: 赵伟
* @Date: 2025-03-31 16:38:59
* @Description: 实验对比 Aim
*/

import IframePage, { IframePageType } from '@/components/IFramePage';

function AimPage() {
return <IframePage type={IframePageType.Aim}></IframePage>;
}
export default AimPage;

+ 6
- 2
react-ui/src/pages/Experiment/Comparison/index.tsx View File

@@ -12,8 +12,9 @@ import {
} from '@/services/experiment';
import { tableSorter } from '@/utils';
import { to } from '@/utils/promise';
import SessionStorage from '@/utils/sessionStorage';
import tableCellRender, { TableCellValueType } from '@/utils/table';
import { useSearchParams } from '@umijs/max';
import { useNavigate, useSearchParams } from '@umijs/max';
import { App, Button, Table, TablePaginationConfig, TableProps } from 'antd';
import classNames from 'classnames';
import { useEffect, useMemo, useState } from 'react';
@@ -46,6 +47,7 @@ function ExperimentComparison() {
});

const { message } = App.useApp();
const navigate = useNavigate();
const config = comparisonConfig[comparisonType];

useEffect(() => {
@@ -73,7 +75,9 @@ function ExperimentComparison() {
const [res] = await to(getExpMetricsReq(selectedRowKeys));
if (res && res.data) {
const url = res.data;
window.open(url, '_blank');
// window.open(url, '_blank');
SessionStorage.setItem(SessionStorage.aimUrlKey, url);
navigate('../compare-visual');
}
};



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

@@ -1,5 +1,6 @@
import { ExperimentStatus } from '@/enums';
import { useStateRef, useVisible } from '@/hooks';
import { useStateRef } from '@/hooks/useStateRef';
import { useVisible } from '@/hooks/useVisible';
import { getExperimentIns } from '@/services/experiment/index.js';
import { getWorkflowById } from '@/services/pipeline/index.js';
import themes from '@/styles/theme.less';
@@ -179,11 +180,12 @@ function ExperimentText() {
if (!statusNode) {
return;
}
const { finishedAt, startedAt, phase, id } = statusNode;
const { finishedAt, startedAt, phase, id, message } = statusNode;
workflowNode.experimentStartTime = startedAt;
workflowNode.experimentEndTime = finishedAt;
workflowNode.experimentStatus = phase;
workflowNode.workflowId = id;
workflowNode.message = message;
workflowNode.img = phase
? `${workflowNode.imgName}-${phase}.png`
: `${workflowNode.imgName}.png`;


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

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

@@ -63,13 +63,14 @@ export const getParamRules = (paramType: number, required: boolean = false): For
};

// 根据参数设置 label
export const getParamType = (param: PipelineGlobalParam): string => {
export const getParamLabel = (param: PipelineGlobalParam): React.ReactNode => {
const paramTypes: Readonly<Record<number, string>> = {
1: '字符串',
2: '整型',
3: '布尔类型',
};
return param.param_name + `(${paramTypes[param.param_type]})`;
const label = param.param_name + `(${paramTypes[param.param_type]})`;
return <Typography.Text ellipsis={{ tooltip: label }}>{label}</Typography.Text>;
};

function AddExperimentModal({
@@ -99,8 +100,8 @@ function AddExperimentModal({
};

const paramLayout = {
labelCol: { span: 8 },
wrapperCol: { span: 16 },
labelCol: { span: 6 },
wrapperCol: { span: 18 },
};

// 除了流水线选择发生变化
@@ -157,7 +158,6 @@ function AddExperimentModal({
form={form}
{...layout}
labelAlign="left"
labelWrap
>
<Form.Item
label="实验名称"
@@ -215,9 +215,9 @@ function AddExperimentModal({
{...restField}
{...paramLayout}
key={key}
label={getParamType(globalParam[name])}
label={getParamLabel(globalParam[name])}
name={[name, 'param_value']}
rules={getParamRules(globalParam[name]['param_type'])}
rules={getParamRules(globalParam[name]['param_type'], true)}
>
{getParamComponent(
globalParam[name]['param_type'],


+ 1
- 2
react-ui/src/pages/Experiment/components/ExperimentDrawer/index.less View File

@@ -13,7 +13,6 @@
}

&__tabs {
height: calc(100% - 169px);
:global {
.ant-tabs-nav {
padding-left: 24px;
@@ -35,7 +34,7 @@
display: flex;
align-items: center;
margin-bottom: 15px;
padding-left: 24px;
padding: 0 24px;
color: @text-color;
font-size: 15px;
}


+ 17
- 2
react-ui/src/pages/Experiment/components/ExperimentDrawer/index.tsx View File

@@ -3,7 +3,7 @@ import { experimentStatusInfo } from '@/pages/Experiment/status';
import { PipelineNodeModelSerialize } from '@/types';
import { elapsedTime, formatDate } from '@/utils/date';
import { CloseOutlined, DatabaseOutlined, ProfileOutlined } from '@ant-design/icons';
import { Drawer, Tabs } from 'antd';
import { Drawer, Tabs, Typography } from 'antd';
import { useMemo } from 'react';
import ExperimentParameter from '../ExperimentParameter';
import ExperimentResult from '../ExperimentResult';
@@ -129,6 +129,14 @@ const ExperimentDrawer = ({
'--'
)}
</div>
{instanceNodeData.message && (
<div className={styles['experiment-drawer__info']}>
<div style={{ flex: 'none' }}>消息:</div>
<Typography.Text ellipsis={{ tooltip: instanceNodeData.message }}>
{instanceNodeData.message ?? '--'}
</Typography.Text>
</div>
)}
<div className={styles['experiment-drawer__info']}>
启动时间:{formatDate(instanceNodeStartTime)}
</div>
@@ -137,7 +145,14 @@ const ExperimentDrawer = ({
{elapsedTime(instanceNodeStartTime, instanceNodeEndTime)}
</div>
</div>
<Tabs defaultActiveKey="1" items={items} className={styles['experiment-drawer__tabs']} />
<Tabs
defaultActiveKey="1"
items={items}
className={styles['experiment-drawer__tabs']}
style={{
height: instanceNodeData.message ? 'calc(100% - 169px - 39px)' : 'calc(100% - 169px)',
}}
/>
</Drawer>
);
};


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

@@ -1,6 +1,6 @@
import KFIcon from '@/components/KFIcon';
import { ExperimentStatus } from '@/enums';
import { useCheck } from '@/hooks';
import { useCheck } from '@/hooks/useCheck';
import { experimentStatusInfo } from '@/pages/Experiment/status';
import {
deleteManyExperimentIns,
@@ -62,7 +62,8 @@ function ExperimentInstanceComponent({
// 删除实验实例确认
const handleRemove = (instance: ExperimentInstance) => {
modalConfirm({
title: '确定删除该条实例吗?',
title: '删除后,该实验实例将不可恢复',
content: '是否确认删除?',
onOk: () => {
deleteExperimentInstance(instance.id);
},
@@ -101,7 +102,8 @@ function ExperimentInstanceComponent({
// 终止实验实例
const handleTerminate = (instance: ExperimentInstance) => {
modalConfirm({
title: '确定要终止此次实验运行吗?',
title: '终止后,该次实验运行将不可恢复',
content: '是否确认终止?',
isDelete: false,
onOk: () => {
terminateExperimentInstance(instance);


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

@@ -5,7 +5,7 @@
*/

import { ExperimentStatus } from '@/enums';
import { useStateRef } from '@/hooks';
import { useStateRef } from '@/hooks/useStateRef';
import { getExperimentPodsLog } from '@/services/experiment/index.js';
import { DoubleRightOutlined, DownOutlined, UpOutlined } from '@ant-design/icons';
import { Button } from 'antd';


+ 8
- 25
react-ui/src/pages/Experiment/components/ViewParamsModal/index.less View File

@@ -1,31 +1,14 @@
.params_container {
max-height: 230px;
padding: 15px 15px 0;
.params-container {
max-height: calc(100vh - 300px);
padding: 24px 24px 0;
overflow-y: auto;
border: 1px solid #e6e6e6;
border-radius: 8px;

&_line {
display: flex;
align-items: center;
margin-bottom: 15px;

&_label {
width: 180px;
color: @text-color;
font-size: 15px;
}
&_value {
flex: 1;
width: 100px;
margin-left: 15px;
padding: 10px 20px;
color: @text-color;
font-size: @font-size;
line-height: 20px;
background: #f6f6f6;
border: 1px solid #e0e0e1;
border-radius: 4px;
}
.params-empty {
:global {
.kf-empty__image {
width: 300px;
}
}
}

+ 41
- 9
react-ui/src/pages/Experiment/components/ViewParamsModal/index.tsx View File

@@ -4,9 +4,11 @@
* @Description: 查看实验使用的参数
*/
import parameterImg from '@/assets/img/modal-parameter.png';
import KFEmpty, { EmptyType } from '@/components/KFEmpty';
import KFModal from '@/components/KFModal';
import { type PipelineGlobalParam } from '@/types';
import { getParamType } from '../AddExperimentModal';
import { Form } from 'antd';
import { getParamComponent, getParamLabel } from '../AddExperimentModal';
import styles from './index.less';

type ParamsModalProps = {
@@ -26,14 +28,44 @@ function ParamsModal({ open, onCancel, globalParam = [] }: ParamsModalProps) {
cancelButtonProps={{ style: { display: 'none' } }}
width={825}
>
<div className={styles.params_container}>
{globalParam?.map((item) => (
<div key={item.param_name} className={styles.params_container_line}>
<span className={styles.params_container_line_label}>{getParamType(item)}</span>
<span className={styles.params_container_line_value}>{item.param_value}</span>
</div>
))}
</div>
{Array.isArray(globalParam) && globalParam.length > 0 ? (
<div className={styles['params-container']}>
<Form
name="view_params_form"
labelCol={{ span: 6 }}
wrapperCol={{ span: 18 }}
initialValues={{ global_param: globalParam }}
labelAlign="left"
disabled
>
<Form.List name="global_param">
{(fields) =>
fields.map(({ key, name, ...restField }) => (
<Form.Item
{...restField}
key={key}
name={[name, 'param_value']}
label={getParamLabel(globalParam[name])}
>
{getParamComponent(
globalParam[name]['param_type'],
globalParam[name]['is_sensitive'],
)}
</Form.Item>
))
}
</Form.List>
</Form>
</div>
) : (
<KFEmpty
className={styles['params-empty']}
type={EmptyType.NoData}
title="暂无数据"
content="该流水线没有设置全局参数"
hasFooter={false}
/>
)}
</KFModal>
);
}


+ 29
- 19
react-ui/src/pages/Experiment/index.jsx View File

@@ -1,7 +1,7 @@
import KFIcon from '@/components/KFIcon';
import PageTitle from '@/components/PageTitle';
import { ExperimentStatus, TensorBoardStatus } from '@/enums';
import { useCacheState } from '@/hooks/pageCacheState';
import { useCacheState } from '@/hooks/useCacheState';
import {
deleteExperimentById,
getExperiment,
@@ -206,6 +206,7 @@ function Experiment() {
setExpandedRowKeys(null);
} else {
getQueryByExperiment(record.id, 0);
refreshExperimentList();
}
};

@@ -285,8 +286,6 @@ function Experiment() {
message.success('运行成功');
refreshExperimentList();
refreshExperimentIns(id);
} else {
message.error('运行失败');
}
};

@@ -377,6 +376,31 @@ function Experiment() {
getQueryByExperiment(expandedRowKeys, page);
};

// 处理删除
const handleExperimentDelete = (record) => {
modalConfirm({
title: '删除后,该实验将不可恢复',
content: '是否确认删除?',
onOk: () => {
deleteExperimentById(record.id).then((ret) => {
if (ret.code === 200) {
message.success('删除成功');
// 如果是一页的唯一数据,删除后,请求第一页的数据
// 否则直接刷新这一页的数据
setPagination((prev) => {
return {
...prev,
current: experimentList.length === 1 ? Math.max(1, prev.current - 1) : prev.current,
};
});
} else {
message.error(ret.msg);
}
});
},
});
};

const columns = [
{
title: '实验名称',
@@ -475,22 +499,7 @@ function Experiment() {
size="small"
key="batchRemove"
icon={<KFIcon type="icon-shanchu" />}
onClick={() => {
modalConfirm({
title: '删除后,该实验将不可恢复',
content: '是否确认删除?',
onOk: () => {
deleteExperimentById(record.id).then((ret) => {
if (ret.code === 200) {
message.success('删除成功');
getExperimentList();
} else {
message.error(ret.msg);
}
});
},
});
}}
onClick={() => handleExperimentDelete(record)}
>
删除
</Button>
@@ -499,6 +508,7 @@ function Experiment() {
),
},
];

return (
<div className={styles['experiment-list']}>
<PageTitle title="实验列表"></PageTitle>


+ 1
- 1
react-ui/src/pages/HyperParameter/components/HyperParameterBasic/index.tsx View File

@@ -1,6 +1,6 @@
import ConfigInfo, { type BasicInfoData } from '@/components/ConfigInfo';
import { hyperParameterOptimizedMode } from '@/enums';
import { useComputingResource } from '@/hooks/resource';
import { useComputingResource } from '@/hooks/useComputingResource';
import { experimentStatusInfo } from '@/pages/Experiment/status';
import {
schedulerAlgorithms,


+ 32
- 25
react-ui/src/pages/Mirror/Create/index.tsx View File

@@ -44,7 +44,7 @@ const mirrorRadioItems: KFRadioItem[] = [
function MirrorCreate() {
const navigate = useNavigate();
const [form] = Form.useForm();
const [nameDisabled, setNameDisabled] = useState(false);
const [isAddVersion, setIsAddVersion] = useState(false); // 是制作镜像还是新增镜像版本
const { message } = App.useApp();

const uploadProps: UploadProps = {
@@ -60,7 +60,7 @@ function MirrorCreate() {
const name = SessionStorage.getItem(SessionStorage.mirrorNameKey);
if (name) {
form.setFieldValue('name', name);
setNameDisabled(true);
setIsAddVersion(true);
}
return () => {
SessionStorage.removeItem(SessionStorage.mirrorNameKey);
@@ -70,32 +70,37 @@ function MirrorCreate() {
// 创建公网、本地镜像
const createPublicMirror = async (formData: FormData) => {
const upload_type = formData['upload_type'];
let params;
if (upload_type === CommonTabKeys.Public) {
params = {
const params = {
...omit(formData, ['upload_type']),
upload_type: 0,
image_type: 0,
};
const [res] = await to(createMirrorReq(params));
if (res) {
message.success('创建成功');
navigate(-1);
}
} else {
const fileList = formData['fileList'] ?? [];
if (validateUploadFiles(fileList)) {
const file = fileList[0];
params = {
const params = {
...omit(formData, ['fileList', 'upload_type']),
path: file.response.data.url,
file_size: file.response.data.fileSize,
file_name: file.response.data.fileName,
upload_type: 1,
image_type: 0,
};
const [res] = await to(createMirrorReq(params));
if (res) {
message.success('创建成功');
navigate(-1);
}
}
}

const [res] = await to(createMirrorReq(params));
if (res) {
message.success('创建成功');
navigate(-1);
}
};

// 提交
@@ -118,14 +123,16 @@ function MirrorCreate() {
return true;
};

const descTitle = isAddVersion ? '版本描述' : '镜像描述';

return (
<div className={styles['mirror-create']}>
<PageTitle title="创建镜像"></PageTitle>
<PageTitle title={!isAddVersion ? '创建镜像' : '新增镜像版本'}></PageTitle>
<div className={styles['mirror-create__content']}>
<div>
<Form
name="mirror-create"
labelCol={{ flex: '130px' }}
labelCol={{ flex: '135px' }}
wrapperCol={{ flex: 1 }}
labelAlign="left"
form={form}
@@ -142,7 +149,7 @@ function MirrorCreate() {
<Row gutter={10}>
<Col span={10}>
<Form.Item
label="镜像名称及Tag"
label="镜像名称和版本"
name="name"
rules={[
{
@@ -150,15 +157,15 @@ function MirrorCreate() {
message: '请输入镜像名称',
},
{
pattern: /^[a-z0-9/_-]*$/,
message: '只支持小写字母、数字、下划线(_)、中横线(-)、斜杠(/)',
pattern: /^[a-z0-9/._-]*$/,
message: '只支持小写字母、数字、点(.)、下划线(_)、中横线(-)、斜杠(/)',
},
]}
>
<Input
placeholder="请输入镜像名称"
maxLength={64}
disabled={nameDisabled}
disabled={isAddVersion}
showCount
allowClear
/>
@@ -174,33 +181,33 @@ function MirrorCreate() {
rules={[
{
required: true,
message: '请输入镜像Tag',
message: '请输入镜像版本',
},
{
pattern: /^[a-zA-Z0-9._-]+$/,
message: '版本只支持字母、数字、点(.)、下划线(_)、中横线(-)',
message: '镜像版本只支持字母、数字、点(.)、下划线(_)、中横线(-)',
},
]}
>
<Input placeholder="请输入镜像Tag" maxLength={64} showCount allowClear />
<Input placeholder="请输入镜像版本" maxLength={64} showCount allowClear />
</Form.Item>
</Col>
</Row>
<Row gutter={10}>
<Col span={20}>
<Form.Item
label="镜像描述"
label={descTitle}
name="description"
rules={[
{
required: true,
message: '请输入镜像描述',
message: `请输入${descTitle}`,
},
]}
>
<Input.TextArea
autoSize={{ minRows: 2, maxRows: 6 }}
placeholder="请输入镜像描述,最长128字符"
placeholder={`请输入${descTitle}`}
maxLength={128}
showCount
allowClear
@@ -283,7 +290,7 @@ function MirrorCreate() {
rules={[
{
required: true,
message: '请上传镜像地址',
message: '请上传镜像文件',
},
]}
>
@@ -303,7 +310,7 @@ function MirrorCreate() {

<Form.Item wrapperCol={{ offset: 0, span: 16 }}>
<Button type="primary" htmlType="submit">
创建镜像
确定
</Button>
<Button
type="default"


+ 20
- 16
react-ui/src/pages/Mirror/Info/index.tsx View File

@@ -7,8 +7,8 @@ import KFIcon from '@/components/KFIcon';
import PageTitle from '@/components/PageTitle';
import SubAreaTitle from '@/components/SubAreaTitle';
import { MirrorVersionStatus } from '@/enums';
import { useDomSize } from '@/hooks';
import { useCacheState } from '@/hooks/pageCacheState';
import { useCacheState } from '@/hooks/useCacheState';
import { useDomSize } from '@/hooks/useDomSize';
import {
deleteMirrorVersionReq,
getMirrorInfoReq,
@@ -117,17 +117,14 @@ function MirrorInfo() {
const [res] = await to(deleteMirrorVersionReq(id));
if (res) {
message.success('删除成功');
// 如果是一页的唯一数据,删除,请求第一页的数据
// 如果是一页的唯一数据,删除,请求第一页的数据
// 否则直接刷新这一页的数据
// 避免回到第一页
if (tableData.length === 1) {
setPagination((prev) => ({
setPagination((prev) => {
return {
...prev,
current: 1,
}));
} else {
getMirrorVersionList();
}
current: tableData.length === 1 ? Math.max(1, prev.current! - 1) : prev.current,
};
});
}
};

@@ -155,7 +152,7 @@ function MirrorInfo() {
};

const createMirrorVersion = () => {
navigate(`/dataset/mirror/create`);
navigate(`add-version`);
SessionStorage.setItem(SessionStorage.mirrorNameKey, mirrorInfo.name || '');
setCacheState({
pagination,
@@ -174,20 +171,27 @@ function MirrorInfo() {
title: '镜像地址',
dataIndex: 'url',
key: 'url',
render: tableCellRender(),
width: '25%',
render: tableCellRender('auto', TableCellValueType.Text, { copyable: true }),
},
{
title: '版本描述',
dataIndex: 'description',
key: 'description',
render: tableCellRender(true),
},
{
title: '状态',
dataIndex: 'status',
key: 'status',
width: 150,
width: 100,
render: MirrorStatusCell,
},
{
title: '镜像大小',
dataIndex: 'file_size',
key: 'file_size',
width: 150,
width: 120,
render: tableCellRender(),
},
{
@@ -200,7 +204,7 @@ function MirrorInfo() {
{
title: '操作',
dataIndex: 'operation',
width: 150,
width: 120,
key: 'operation',
hidden: isPublic,
render: (_: any, record: MirrorVersionData) => (


+ 10
- 13
react-ui/src/pages/Mirror/List/index.tsx View File

@@ -5,7 +5,7 @@
*/
import KFIcon from '@/components/KFIcon';
import { CommonTabKeys } from '@/enums';
import { useCacheState } from '@/hooks/pageCacheState';
import { useCacheState } from '@/hooks/useCacheState';
import { deleteMirrorReq, getMirrorListReq } from '@/services/mirror';
import themes from '@/styles/theme.less';
import { to } from '@/utils/promise';
@@ -103,17 +103,14 @@ function MirrorList() {
const [res] = await to(deleteMirrorReq(id));
if (res) {
message.success('删除成功');
// 如果是一页的唯一数据,删除,请求第一页的数据
// 如果是一页的唯一数据,删除,请求第一页的数据
// 否则直接刷新这一页的数据
// 避免回到第一页
if (tableData.length > 1) {
setPagination((prev) => ({
setPagination((prev) => {
return {
...prev,
current: 1,
}));
} else {
getMirrorList();
}
current: tableData.length === 1 ? Math.max(1, prev.current! - 1) : prev.current,
};
});
}
};

@@ -128,7 +125,7 @@ function MirrorList() {

// 查看详情
const toDetail = (record: MirrorData) => {
navigate(`/dataset/mirror/info/${record.id}`);
navigate(`info/${record.id}`);
setCacheState({
activeTab,
pagination,
@@ -149,7 +146,7 @@ function MirrorList() {

// 创建镜像
const createMirror = () => {
navigate(`/dataset/mirror/create`);
navigate(`create`);
SessionStorage.setItem(SessionStorage.mirrorNameKey, '');
setCacheState({
activeTab,
@@ -262,7 +259,7 @@ function MirrorList() {
onClick={createMirror}
icon={<KFIcon type="icon-xinjian2" />}
>
制作镜像
创建镜像
</Button>
)}
<Button


+ 41
- 47
react-ui/src/pages/Model/components/ModelEvolution/index.tsx View File

@@ -4,12 +4,12 @@
* @Description: 模型演化
*/

import { useEffectWhen } from '@/hooks';
import { useEffectWhen } from '@/hooks/useEffectWhen';
import { getModelAtlasReq } from '@/services/dataset/index.js';
import themes from '@/styles/theme.less';
import { to } from '@/utils/promise';
import G6, { G6GraphEvent, Graph, INode } from '@antv/g6';
import { useEffect, useRef, useState } from 'react';
import { useCallback, useEffect, useRef, useState } from 'react';

import NodeTooltips from '../NodeTooltips';
import styles from './index.less';
@@ -73,17 +73,45 @@ function ModelEvolution({
};
}, []);

useEffectWhen(
() => {
if (version) {
getModelAtlas();
} else {
clearGraphData();
}
},
isActive,
[resourceId, version],
);
const getModelAtlas = useCallback(async () => {
// 请求失败或者版本不存在时,清除图形
function clearGraphData() {
graph.data({
nodes: [],
edges: [],
});
graph.render();
graph.fitView();
}

if (!resourceId || !identifier || !version) {
clearGraphData();
return;
}

const params = {
id: resourceId,
identifier,
version,
};
const [res] = await to(getModelAtlasReq(params));
if (res && res.data) {
const data = normalizeTreeData(res.data);
apiData.current = data;
hierarchyNodes.current = traverseHierarchically(data);
const graphData = getGraphData(data, hierarchyNodes.current);

graph.data(graphData);
graph.render();
graph.fitView();
setShowNodeTooltip(false);
setEnterTooltip(false);
} else {
clearGraphData();
}
}, [resourceId, identifier, version]);

useEffectWhen(getModelAtlas, isActive, [resourceId, identifier, version]);

// 初始化图
const initGraph = () => {
@@ -249,40 +277,6 @@ function ModelEvolution({
}, 100);
};

// 获取模型依赖
const getModelAtlas = async () => {
const params = {
id: resourceId,
identifier,
version,
};
const [res] = await to(getModelAtlasReq(params));
if (res && res.data) {
const data = normalizeTreeData(res.data);
apiData.current = data;
hierarchyNodes.current = traverseHierarchically(data);
const graphData = getGraphData(data, hierarchyNodes.current);

graph.data(graphData);
graph.render();
graph.fitView();
setShowNodeTooltip(false);
setEnterTooltip(false);
} else {
clearGraphData();
}
};

// 请求失败或者版本不存在时,清除图形
function clearGraphData() {
graph.data({
nodes: [],
edges: [],
});
graph.render();
graph.fitView();
}

return (
<div className={styles['model-evolution']}>
<div className={styles['model-evolution__graph']} id="canvas" ref={graphRef}></div>


+ 24
- 3
react-ui/src/pages/Model/components/ModelMetrics/index.tsx View File

@@ -1,8 +1,9 @@
import SubAreaTitle from '@/components/SubAreaTitle';
import TableColTitle from '@/components/TableColTitle';
import { useCheck } from '@/hooks';
import { useCheck } from '@/hooks/useCheck';
import { getModelPageVersionsReq, getModelVersionsMetricsReq } from '@/services/dataset';
import { tableSorter } from '@/utils';
import { VersionChangedMessage } from '@/utils/constant';
import { to } from '@/utils/promise';
import tableCellRender from '@/utils/table';
import { Checkbox, Flex, Table, type TablePaginationConfig, type TableProps } from 'antd';
@@ -27,10 +28,10 @@ type ModelMetricsProps = {
resourceId: number;
identifier: string;
owner: string;
version: string;
version: string; // 当前版本
};

function ModelMetrics({ resourceId, identifier, owner, version }: ModelMetricsProps) {
function ModelMetrics({ resourceId, identifier, owner, version, refreshTag }: ModelMetricsProps) {
const [pagination, setPagination] = useState<TablePaginationConfig>({
current: 1,
pageSize: 10,
@@ -59,6 +60,24 @@ function ModelMetrics({ resourceId, identifier, owner, version }: ModelMetricsPr
checkSingleMetrics,
] = useCheck(allMetricsNames);

// 新增,删除版本时,重置分页,然后刷新版本列表
useEffect(() => {
const handleMessage = (e: MessageEvent) => {
const { type } = e.data;
if (type === VersionChangedMessage) {
setPagination({
current: 1,
pageSize: 10,
});
}
};

window.addEventListener('message', handleMessage);
return () => {
window.removeEventListener('message', handleMessage);
};
}, []);

useEffect(() => {
// 获取模型版本列表,带有参数和指标数据
const getModelPageVersions = async () => {
@@ -128,6 +147,7 @@ function ModelMetrics({ resourceId, identifier, owner, version }: ModelMetricsPr
}
};

// 行勾选
const rowSelection: TableProps<TableData>['rowSelection'] = {
type: 'checkbox',
fixed: 'left',
@@ -140,6 +160,7 @@ function ModelMetrics({ resourceId, identifier, owner, version }: ModelMetricsPr
}),
};

// 计算的表格数据
const showTableData = useMemo(() => {
const index = tableData.findIndex((item) => item.name === version);
if (index !== -1) {


+ 2
- 2
react-ui/src/pages/ModelDeployment/CreateService/index.tsx View File

@@ -8,11 +8,11 @@ import PageTitle from '@/components/PageTitle';
import SubAreaTitle from '@/components/SubAreaTitle';
import { CommonTabKeys, serviceTypeOptions } from '@/enums';
import { createServiceReq, getServiceInfoReq, updateServiceReq } from '@/services/modelDeployment';
import { ServiceCreatedMessage } from '@/utils/constant';
import { to } from '@/utils/promise';
import { useNavigate, useParams } from '@umijs/max';
import { App, Button, Col, Form, Input, Row, Select } from 'antd';
import { useEffect } from 'react';
import { createServiceVersionMessage } from '../types';
import styles from './index.less';

// 表单数据
@@ -63,7 +63,7 @@ function CreateService() {
navigate(-1);
if (!serviceId) {
setTimeout(() => {
window.postMessage({ type: createServiceVersionMessage, payload: res.data.id });
window.postMessage({ type: ServiceCreatedMessage, payload: res.data.id });
}, 500);
}
}


+ 1
- 1
react-ui/src/pages/ModelDeployment/CreateVersion/index.tsx View File

@@ -347,7 +347,7 @@ function CreateServiceVersion() {
<Col span={10}>
<Form.Item
label="资源规格"
name="resource"
name="computing_resource_id"
rules={[
{
required: true,


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

@@ -6,7 +6,7 @@
import KFIcon from '@/components/KFIcon';
import PageTitle from '@/components/PageTitle';
import { serviceTypeOptions } from '@/enums';
import { useCacheState } from '@/hooks/pageCacheState';
import { useCacheState } from '@/hooks/useCacheState';
import { deleteServiceReq, getServiceListReq } from '@/services/modelDeployment';
import themes from '@/styles/theme.less';
import { to } from '@/utils/promise';
@@ -31,9 +31,9 @@ import {
CreateServiceVersionFrom,
ServiceData,
ServiceOperationType,
createServiceVersionMessage,
} from '../types';
import styles from './index.less';
import { ServiceCreatedMessage } from '@/utils/constant';

const allServiceTypeOptions = [{ label: '全部', value: '' }, ...serviceTypeOptions];

@@ -95,7 +95,7 @@ function ModelDeployment() {
useEffect(() => {
const handleMessage = (e: MessageEvent) => {
const { type, payload } = e.data;
if (type === createServiceVersionMessage) {
if (type === ServiceCreatedMessage) {
modalConfirm({
title: '创建服务成功',
content: '是否创建服务版本?',
@@ -119,17 +119,14 @@ function ModelDeployment() {
const [res] = await to(deleteServiceReq(record.id));
if (res) {
message.success('删除成功');
// 如果是一页的唯一数据,删除,请求第一页的数据
// 如果是一页的唯一数据,删除,请求第一页的数据
// 否则直接刷新这一页的数据
// 避免回到第一页
if (tableData.length > 1) {
setPagination((prev) => ({
setPagination((prev) => {
return {
...prev,
current: 1,
}));
} else {
getServiceList();
}
current: tableData.length === 1 ? Math.max(1, prev.current! - 1) : prev.current,
};
});
}
};



+ 1
- 0
react-ui/src/pages/ModelDeployment/ServiceInfo/index.less View File

@@ -18,6 +18,7 @@

&__table {
flex: 1;
min-height: 0;
margin-top: 24px;
}
}


+ 12
- 15
react-ui/src/pages/ModelDeployment/ServiceInfo/index.tsx View File

@@ -8,8 +8,8 @@ import KFIcon from '@/components/KFIcon';
import PageTitle from '@/components/PageTitle';
import SubAreaTitle from '@/components/SubAreaTitle';
import { ServiceRunStatus, serviceStatusOptions } from '@/enums';
import { useCacheState } from '@/hooks/pageCacheState';
import { useComputingResource } from '@/hooks/resource';
import { useCacheState } from '@/hooks/useCacheState';
import { useComputingResource } from '@/hooks/useComputingResource';
import {
deleteServiceVersionReq,
getServiceInfoReq,
@@ -132,18 +132,15 @@ function ServiceInfo() {
const [res] = await to(deleteServiceVersionReq(record.id));
if (res) {
message.success('删除成功');
// 如果是一页的唯一数据,删除,请求第一页的数据
// 如果是一页的唯一数据,删除,请求第一页的数据
// 否则直接刷新这一页的数据
// 避免回到第一页
if (tableData.length > 1) {
setPagination((prev) => ({
setPagination((prev) => {
return {
...prev,
current: 1,
}));
} else {
getServiceInfo();
getServiceVersions();
}
current: tableData.length === 1 ? Math.max(1, prev.current! - 1) : prev.current,
};
});
getServiceInfo();
}
};

@@ -309,8 +306,8 @@ function ServiceInfo() {
},
{
title: '资源规格',
dataIndex: 'resource',
key: 'resource',
dataIndex: 'computing_resource_id',
key: 'computing_resource_id',
width: '20%',
render: tableCellRender(true, TableCellValueType.Custom, {
format: getResourceDescription,
@@ -432,7 +429,7 @@ function ServiceInfo() {
onClick={() => createServiceVersion(ServiceOperationType.Create)}
icon={<KFIcon type="icon-xinjian2" />}
>
新增版本
新增服务版本
</Button>
<Button style={{ marginRight: '15px' }} type="default" onClick={handleVersionCompare}>
版本对比


+ 1
- 2
react-ui/src/pages/ModelDeployment/VersionInfo/index.less View File

@@ -6,14 +6,13 @@
flex-direction: column;
height: calc(100% - 60px);
margin-top: 10px;
padding: 30px 30px 0;
padding: 10px 30px 0;
background-color: white;
border-radius: 10px;

&__tabs {
flex: 1;
min-height: 0;
margin-top: 20px;
padding-bottom: 10px;

:global {


+ 15
- 7
react-ui/src/pages/ModelDeployment/VersionInfo/index.tsx View File

@@ -3,9 +3,9 @@
* @Date: 2024-04-16 13:58:08
* @Description: 服务版本详情
*/
import FullScreenFrame from '@/components/FullScreenFrame';
import KFIcon from '@/components/KFIcon';
import PageTitle from '@/components/PageTitle';
import SubAreaTitle from '@/components/SubAreaTitle';
import { getServiceVersionInfoReq } from '@/services/modelDeployment';
import { to } from '@/utils/promise';
import { useParams } from '@umijs/max';
@@ -18,6 +18,7 @@ import { ServiceVersionData } from '../types';
import styles from './index.less';

export enum ModelDeploymentTabKey {
Basic = 'Basic', // 基本信息
Predict = 'Predict', // 预测
Guide = 'Guide', // 调用指南
Log = 'Log', // 服务日志
@@ -43,10 +44,23 @@ function ServiceVersionInfo() {
}, [id]);

const tabItems = [
{
key: ModelDeploymentTabKey.Basic,
label: '基本信息',
icon: <KFIcon type="icon-jibenxinxi" />,
children: <VersionBasicInfo info={versionInfo} />,
},
{
key: ModelDeploymentTabKey.Predict,
label: '预测',
icon: <KFIcon type="icon-yuce" />,
children: (
<div style={{ height: '100%', width: '100%' }}>
{versionInfo?.page_path && (
<FullScreenFrame url={versionInfo?.page_path}></FullScreenFrame>
)}
</div>
),
},
{
key: ModelDeploymentTabKey.Guide,
@@ -66,12 +80,6 @@ function ServiceVersionInfo() {
<div className={styles['service-version-info']}>
<PageTitle title="服务版本详情"></PageTitle>
<div className={styles['service-version-info__content']}>
<SubAreaTitle
title="基本信息"
image={require('@/assets/img/mirror-basic.png')}
style={{ marginBottom: '26px' }}
></SubAreaTitle>
<VersionBasicInfo info={versionInfo} />
<div className={styles['service-version-info__content__tabs']}>
<Tabs items={tabItems} />
</div>


+ 14
- 3
react-ui/src/pages/ModelDeployment/components/VersionBasicInfo/index.tsx View File

@@ -1,6 +1,6 @@
import BasicInfo, { type BasicInfoData } from '@/components/BasicInfo';
import { ServiceRunStatus } from '@/enums';
import { useComputingResource } from '@/hooks/resource';
import { useComputingResource } from '@/hooks/useComputingResource';
import { ServiceVersionData } from '@/pages/ModelDeployment/types';
import { formatDate } from '@/utils/date';
import { formatCodeConfig, formatModel } from '@/utils/format';
@@ -68,7 +68,7 @@ function VersionBasicInfo({ info }: BasicInfoProps) {
},
{
label: '资源规格',
value: info?.resource,
value: info?.computing_resource_id,
format: getResourceDescription,
},
{
@@ -79,6 +79,10 @@ function VersionBasicInfo({ info }: BasicInfoProps) {
label: 'API URL',
value: info?.url,
},
{
label: '文档地址',
value: info?.doc_path,
},
{
label: '副本数量',
value: info?.replicas,
@@ -104,7 +108,14 @@ function VersionBasicInfo({ info }: BasicInfoProps) {
},
];

return <BasicInfo datas={datas} labelWidth={66} labelAlign="justify"></BasicInfo>;
return (
<BasicInfo
datas={datas}
labelWidth={66}
labelAlign="justify"
style={{ marginTop: 10 }}
></BasicInfo>
);
}

export default VersionBasicInfo;

+ 1
- 1
react-ui/src/pages/ModelDeployment/components/VersionCompareModal/index.tsx View File

@@ -1,6 +1,6 @@
import KFModal from '@/components/KFModal';
import { ServiceRunStatus } from '@/enums';
import { useComputingResource } from '@/hooks/resource';
import { useComputingResource } from '@/hooks/useComputingResource';
import { type ServiceVersionData } from '@/pages/ModelDeployment/types';
import { getServiceVersionCompareReq } from '@/services/modelDeployment';
import { isEmpty } from '@/utils';


+ 3
- 4
react-ui/src/pages/ModelDeployment/types.ts View File

@@ -24,7 +24,7 @@ export type ServiceVersionData = {
run_state: ServiceRunStatus; // 运行状态
image: string; // 镜像
replicas: number; // 副本数
resource: string; // 资源
computing_resource_id: number; // 资源
mount_path: string; // 挂载路径
model: {
// 模型
@@ -49,6 +49,8 @@ export type ServiceVersionData = {
update_time: string;
create_time: string;
created_by: string;
doc_path?: string; // 文档地址
page_path?: string; // 预测地址
};

// 操作类型
@@ -63,6 +65,3 @@ export enum CreateServiceVersionFrom {
CreateService = 'CreateService', // 来自创建服务
ServiceInfo = 'ServiceInfo', // 来自服务详情
}

// 去创建服务版本消息
export const createServiceVersionMessage = 'createServiceVersion';

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

@@ -1,5 +1,6 @@
import KFIcon from '@/components/KFIcon';
import { useStateRef, useVisible } from '@/hooks';
import { useStateRef } from '@/hooks/useStateRef';
import { useVisible } from '@/hooks/useVisible';
import { getWorkflowById, saveWorkflow } from '@/services/pipeline/index.js';
import themes from '@/styles/theme.less';
import { fittingString, parseJsonText, s8 } from '@/utils';


+ 29
- 25
react-ui/src/pages/Pipeline/components/GlobalParamsDrawer/index.tsx View File

@@ -136,31 +136,35 @@ const GlobalParamsDrawer = forwardRef(
cur.global_param?.[name]?.param_type
}
>
{({ getFieldValue }) => (
<Form.Item
{...restField}
name={[name, 'param_value']}
label="值"
rules={getParamRules(
getFieldValue(['global_param', name, 'param_type']),
true,
)}
>
{getParamComponent(getFieldValue(['global_param', name, 'param_type']))}
</Form.Item>
)}
</Form.Item>
<Form.Item
{...restField}
name={[name, 'is_sensitive']}
label="脱敏显示"
rules={[{ required: true, message: '请选择' }]}
tooltip="展示关联的流水线的参数,脱敏的参数以xxxx展示"
>
<Radio.Group>
<Radio value={1}>是</Radio>
<Radio value={0}>否</Radio>
</Radio.Group>
{({ getFieldValue }) => {
const type = getFieldValue(['global_param', name, 'param_type']);
return (
<>
<Form.Item
{...restField}
name={[name, 'param_value']}
label="值"
rules={getParamRules(type, true)}
>
{getParamComponent(type)}
</Form.Item>
{type !== 3 && (
<Form.Item
{...restField}
name={[name, 'is_sensitive']}
label="脱敏显示"
rules={[{ required: true, message: '请选择' }]}
tooltip="展示关联的流水线的参数,脱敏的参数以xxxx展示"
>
<Radio.Group>
<Radio value={1}>是</Radio>
<Radio value={0}>否</Radio>
</Radio.Group>
</Form.Item>
)}
</>
);
}}
</Form.Item>
<Tooltip title="删除参数">
<Button


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

@@ -1,7 +1,7 @@
import CodeSelectorModal from '@/components/CodeSelectorModal';
import KFIcon from '@/components/KFIcon';
import ParameterInput, { requiredValidator } from '@/components/ParameterInput';
import ParameterSelect from '@/components/ParameterSelect';
import ParameterSelect, { type ParameterSelectDataType } from '@/components/ParameterSelect';
import ResourceSelectorModal, {
ResourceSelectorType,
selectorTypeConfig,
@@ -520,7 +520,8 @@ const PipelineNodeParameter = forwardRef(({ onFormChange }: PipelineNodeParamete
{item.value.type === 'select' ? (
['dataset', 'model', 'service', 'resource'].includes(item.value.item_type) ? (
<ParameterSelect
dataType={item.value.item_type as any}
isPipeline
dataType={item.value.item_type as ParameterSelectDataType}
placeholder={item.value.placeholder}
/>
) : null


+ 45
- 47
react-ui/src/pages/Pipeline/index.jsx View File

@@ -1,7 +1,7 @@
import KFIcon from '@/components/KFIcon';
import KFModal from '@/components/KFModal';
import PageTitle from '@/components/PageTitle';
import { useCacheState } from '@/hooks/pageCacheState';
import { useCacheState } from '@/hooks/useCacheState';
import {
addWorkflow,
cloneWorkflow,
@@ -11,6 +11,7 @@ import {
removeWorkflow,
} 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, Form, Input, Space, Table } from 'antd';
@@ -127,6 +128,47 @@ const Pipeline = () => {
}
};

// 处理删除
const handlePipelineDelete = (record) => {
modalConfirm({
title: '删除后,该流水线将不可恢复',
content: '是否确认删除?',
onOk: async () => {
const { id } = record;
const [res] = await to(removeWorkflow(id));
if (res) {
message.success('删除成功');
// 如果是一页的唯一数据,删除后,请求第一页的数据
// 否则直接刷新这一页的数据
setPagination((prev) => {
return {
...prev,
current: pipeList.length === 1 ? Math.max(1, prev.current - 1) : prev.current,
};
});
}
},
});
};

// 处理复制
const handlePipelineCopy = (record) => {
modalConfirm({
title: '确定复制该条流水线吗?',
okText: '确认',
cancelText: '取消',
isDelete: false,
onOk: async () => {
const { id } = record;
const [res] = await to(cloneWorkflow(id));
if (res) {
message.success('复制成功');
getList();
}
},
});
};

// 当前页面切换
const paginationChange = async (current, pageSize) => {
setPagination({
@@ -199,30 +241,7 @@ const Pipeline = () => {
size="small"
key="clone"
icon={<KFIcon type="icon-fuzhi" />}
onClick={async () => {
modalConfirm({
title: '确定复制该条流水线吗?',
okText: '确认',
cancelText: '取消',
isDelete: false,
onOk: () => {
cloneWorkflow(record.id).then((ret) => {
if (ret.code === 200) {
message.success('复制成功');
getList();
} else {
message.error('复制失败');
}
});

// if (success) {
// if (actionRef.current) {
// actionRef.current.reload();
// }
// }
},
});
}}
onClick={() => handlePipelineCopy(record)}
>
复制
</Button>
@@ -238,28 +257,7 @@ const Pipeline = () => {
size="small"
key="batchRemove"
icon={<KFIcon type="icon-shanchu" />}
onClick={() => {
modalConfirm({
title: '删除后,该流水线将不可恢复',
content: '是否确认删除?',
onOk: () => {
removeWorkflow(record.id).then((ret) => {
if (ret.code === 200) {
message.success('删除成功');
getList();
} else {
message.error(ret.msg);
}
});

// if (success) {
// if (actionRef.current) {
// actionRef.current.reload();
// }
// }
},
});
}}
onClick={() => handlePipelineDelete(record)}
>
删除
</Button>


+ 31
- 0
react-ui/src/pages/Points/components/Statistics/index.less View File

@@ -0,0 +1,31 @@
.statistics {
display: flex;
align-items: center;
width: 100%;
padding: 20px 0;
background-color: @background-color;
border-radius: 8px;

&__item {
display: flex;
flex: 1;
flex-direction: column;
align-items: center;

&--border {
border-right: 1px solid @border-color;
}

&__value {
margin-bottom: 8px;
color: @text-color;
font-weight: bold;
font-size: 30px;
}

&__title {
color: #999999;
font-size: @font-size-input;
}
}
}

+ 38
- 0
react-ui/src/pages/Points/components/Statistics/index.tsx View File

@@ -0,0 +1,38 @@
import classNames from 'classnames';
import styles from './index.less';

type StatisticsProps = {
remaining?: number;
consuming?: number;
};

function Statistics({ remaining, consuming }: StatisticsProps) {
const items = [
{
title: '当前可用算力积分(分)',
value: remaining ?? '-',
},
{
title: '总消耗算力积分(分)',
value: consuming ?? '-',
},
];

return (
<div className={styles['statistics']}>
{items.map((item, index) => (
<div
key={item.title}
className={classNames(styles['statistics__item'], {
[styles['statistics__item--border']]: index === 0,
})}
>
<span className={styles['statistics__item__value']}>{item.value}</span>
<span className={styles['statistics__item__title']}>{item.title}</span>
</div>
))}
</div>
);
}

export default Statistics;

+ 19
- 0
react-ui/src/pages/Points/index.less View File

@@ -0,0 +1,19 @@
.points-detail {
height: 100%;
&__content {
height: calc(100% - 60px);
margin-top: 10px;
padding: 20px 30px 0;
background-color: white;
border-radius: 10px;

&__top {
width: 100%;
}

&__table {
height: calc(100% - 117px - 28px);
margin-top: 28px;
}
}
}

Some files were not shown because too many files changed in this diff

Loading…
Cancel
Save