Browse Source

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

dev-ray
chenzhihang 11 months ago
parent
commit
6cf56e17d6
100 changed files with 1246 additions and 1373 deletions
  1. +2
    -0
      .gitignore
  2. +2
    -2
      k8s/build.sh
  3. +1
    -1
      k8s/build_and_deploy.sh
  4. +2
    -2
      k8s/deploy.sh
  5. +0
    -36
      k8s/k8s-10gen.yaml
  6. +0
    -36
      k8s/k8s-11visual.yaml
  7. +0
    -36
      k8s/k8s-12front.yaml
  8. +0
    -62
      k8s/k8s-3nacos.yaml
  9. +0
    -36
      k8s/k8s-4gateway.yaml
  10. +0
    -36
      k8s/k8s-5auth.yaml
  11. +0
    -36
      k8s/k8s-6system.yaml
  12. +0
    -44
      k8s/k8s-7management.yaml
  13. +0
    -36
      k8s/k8s-8file.yaml
  14. +0
    -36
      k8s/k8s-9job.yaml
  15. +0
    -36
      k8s/template-yaml/deploy/k8s-12front.yaml
  16. +0
    -53
      k8s/template-yaml/deploy/k8s-7management.yaml
  17. +43
    -0
      k8s/template-yaml/k8s-13oauth2.yaml
  18. +3
    -3
      k8s/template-yaml/k8s-3nacos.yaml
  19. +22
    -0
      k8s/template-yaml/k8s-7management.yaml
  20. +68
    -0
      k8s/template-yaml/rolebindings.yaml
  21. +2
    -7
      react-ui/.gitignore
  22. +1
    -0
      react-ui/.nvmrc
  23. +16
    -0
      react-ui/.storybook/babel-plugin-auto-css-modules.js
  24. +117
    -0
      react-ui/.storybook/main.ts
  25. +19
    -0
      react-ui/.storybook/mock/umijs.mock.tsx
  26. +107
    -0
      react-ui/.storybook/preview.tsx
  27. +19
    -0
      react-ui/.storybook/storybook.css
  28. +27
    -0
      react-ui/.storybook/tsconfig.json
  29. +20
    -0
      react-ui/.storybook/typings.d.ts
  30. +1
    -1
      react-ui/config/config.ts
  31. +1
    -1
      react-ui/config/defaultSettings.ts
  32. +37
    -1
      react-ui/config/routes.ts
  33. +28
    -0
      react-ui/package.json
  34. BIN
      react-ui/public/favicon-cc.ico
  35. BIN
      react-ui/public/favicon.ico
  36. +3
    -3
      react-ui/src/app.tsx
  37. BIN
      react-ui/src/assets/img/logo-cc.png
  38. BIN
      react-ui/src/assets/img/logo-cl.png
  39. BIN
      react-ui/src/assets/img/logo.png
  40. BIN
      react-ui/src/assets/img/popover-bg.png
  41. +86
    -0
      react-ui/src/components/BasicInfo/BasicInfoItem.tsx
  42. +58
    -0
      react-ui/src/components/BasicInfo/BasicInfoItemValue.tsx
  43. +0
    -113
      react-ui/src/components/BasicInfo/components.tsx
  44. +0
    -48
      react-ui/src/components/BasicInfo/format.ts
  45. +26
    -2
      react-ui/src/components/BasicInfo/index.less
  46. +35
    -6
      react-ui/src/components/BasicInfo/index.tsx
  47. +2
    -2
      react-ui/src/components/BasicInfo/types.ts
  48. +10
    -3
      react-ui/src/components/BasicTableInfo/index.less
  49. +9
    -11
      react-ui/src/components/BasicTableInfo/index.tsx
  50. +0
    -0
      react-ui/src/components/CodeConfigItem/index.less
  51. +0
    -0
      react-ui/src/components/CodeConfigItem/index.tsx
  52. +20
    -5
      react-ui/src/components/CodeSelect/index.tsx
  53. +49
    -0
      react-ui/src/components/CodeSelectorModal/index.less
  54. +11
    -7
      react-ui/src/components/CodeSelectorModal/index.tsx
  55. +41
    -0
      react-ui/src/components/ConfigInfo/index.tsx
  56. +0
    -9
      react-ui/src/components/DisabledInput/index.less
  57. +0
    -20
      react-ui/src/components/DisabledInput/index.tsx
  58. +20
    -0
      react-ui/src/components/FormInfo/index.less
  59. +41
    -0
      react-ui/src/components/FormInfo/index.tsx
  60. +13
    -5
      react-ui/src/components/FullScreenFrame/index.tsx
  61. +12
    -8
      react-ui/src/components/IFramePage/index.tsx
  62. +2
    -1
      react-ui/src/components/InfoGroup/InfoGroupTitle.less
  63. +7
    -1
      react-ui/src/components/InfoGroup/InfoGroupTitle.tsx
  64. +1
    -1
      react-ui/src/components/InfoGroup/index.less
  65. +12
    -3
      react-ui/src/components/InfoGroup/index.tsx
  66. +1
    -1
      react-ui/src/components/KFEmpty/index.less
  67. +16
    -6
      react-ui/src/components/KFEmpty/index.tsx
  68. +8
    -2
      react-ui/src/components/KFIcon/index.tsx
  69. +0
    -0
      react-ui/src/components/KFModal/KFModalTitle.less
  70. +5
    -1
      react-ui/src/components/KFModal/KFModalTitle.tsx
  71. +6
    -3
      react-ui/src/components/KFModal/index.tsx
  72. +12
    -4
      react-ui/src/components/KFRadio/index.tsx
  73. +11
    -5
      react-ui/src/components/KFSpin/index.tsx
  74. +0
    -19
      react-ui/src/components/LabelValue/index.less
  75. +0
    -20
      react-ui/src/components/LabelValue/index.tsx
  76. +1
    -2
      react-ui/src/components/MenuIconSelector/index.less
  77. +3
    -0
      react-ui/src/components/MenuIconSelector/index.tsx
  78. +8
    -1
      react-ui/src/components/PageTitle/index.tsx
  79. +18
    -5
      react-ui/src/components/ParameterInput/index.less
  80. +21
    -3
      react-ui/src/components/ParameterInput/index.tsx
  81. +1
    -1
      react-ui/src/components/ParameterSelect/index.tsx
  82. +55
    -15
      react-ui/src/components/ResourceSelect/index.tsx
  83. +0
    -0
      react-ui/src/components/ResourceSelectorModal/config.tsx
  84. +7
    -7
      react-ui/src/components/ResourceSelectorModal/index.less
  85. +26
    -10
      react-ui/src/components/ResourceSelectorModal/index.tsx
  86. +7
    -5
      react-ui/src/components/RightContent/AvatarDropdown.tsx
  87. +2
    -5
      react-ui/src/components/RightContent/index.tsx
  88. +9
    -2
      react-ui/src/components/SubAreaTitle/index.tsx
  89. +11
    -0
      react-ui/src/enums/index.ts
  90. +1
    -1
      react-ui/src/global.less
  91. +1
    -1
      react-ui/src/iconfont/iconfont-menu.json
  92. +1
    -1
      react-ui/src/iconfont/iconfont.js
  93. +5
    -0
      react-ui/src/overrides.less
  94. +1
    -1
      react-ui/src/pages/404.tsx
  95. +1
    -1
      react-ui/src/pages/AutoML/Create/index.less
  96. +3
    -4
      react-ui/src/pages/AutoML/Create/index.tsx
  97. +0
    -16
      react-ui/src/pages/AutoML/Info/index.tsx
  98. +3
    -411
      react-ui/src/pages/AutoML/List/index.tsx
  99. +6
    -51
      react-ui/src/pages/AutoML/components/AutoMLBasic/index.tsx
  100. +0
    -20
      react-ui/src/pages/AutoML/components/ConfigInfo/index.less

+ 2
- 0
.gitignore View File

@@ -58,3 +58,5 @@ mvnw

# web
**/node_modules

*storybook.log

+ 2
- 2
k8s/build.sh View File

@@ -13,7 +13,7 @@ show_help() {
echo
echo "Options:"
echo " -b Branch to deploy, default is master"
echo " -s Service to deploy (manage-front, manage, front, all, default is manage-front)"
echo " -s Service to deploy (manage-front, manage, front, all, system, default is manage-front)"
echo " -h Show this help message"
}

@@ -54,7 +54,7 @@ compile_front() {
# 编译前端
docker run -v ${baseDir}:${baseDir} \
-e http_proxy=http://172.20.32.253:3128 -e https_proxy=http://172.20.32.253:3128 \
172.20.32.187/ci4s/node:16.16.0 ${baseDir}/k8s/build-node.sh
172.20.32.187/tempimagefile/node:18.16.0 ${baseDir}/k8s/build-node.sh
if [ $? -ne 0 ]; then
echo "编译失败,请检查代码!"
exit 1


+ 1
- 1
k8s/build_and_deploy.sh View File

@@ -19,7 +19,7 @@ show_help() {
echo
echo "Options:"
echo " -b Branch to deploy, default: master"
echo " -s Service to deploy (manage-front, manage, front, all, default: manage-front)"
echo " -s Service to deploy (manage-front, manage, front, all, system default: manage-front)"
echo " -e Environment (e.g., dev, test, default: dev)"
echo " -h Show this help message"
}


+ 2
- 2
k8s/deploy.sh View File

@@ -10,7 +10,7 @@ show_help() {
echo "Usage: $0 [-s service] [-e environment]"
echo
echo "Options:"
echo " -s Service to deploy (manage-front, manage, front, all default: manage-front)"
echo " -s Service to deploy (manage-front, manage, front, all, system default: manage-front)"
echo " -e Environment (e.g., dev, test, default: dev)"
echo " -h Show this help message"
}
@@ -43,7 +43,7 @@ fi

# 根据环境设置 IP 地址
if [ "$env" == "dev" ]; then
remote_ip="172.20.32.181"
remote_ip="172.20.32.197"
elif [ "$env" == "test" ]; then
remote_ip="172.20.32.185"
else


+ 0
- 36
k8s/k8s-10gen.yaml View File

@@ -1,36 +0,0 @@
apiVersion: apps/v1
kind: Deployment
metadata:
name: ci4s-gen-deployment
namespace: ci4s-test
spec:
replicas: 1
selector:
matchLabels:
app: ci4s-gen
template:
metadata:
labels:
app: ci4s-gen
spec:
containers:
- name: ci4s-gen
image: ci4s-gen:v1.0
ports:
- containerPort: 9202

---
apiVersion: v1
kind: Service
metadata:
name: ci4s-gen-service
namespace: ci4s-test
spec:
type: NodePort
ports:
- port: 9202
nodePort: 31211
protocol: TCP
selector:
app: ci4s-gen


+ 0
- 36
k8s/k8s-11visual.yaml View File

@@ -1,36 +0,0 @@
apiVersion: apps/v1
kind: Deployment
metadata:
name: ci4s-visual-deployment
namespace: ci4s-test
spec:
replicas: 1
selector:
matchLabels:
app: ci4s-visual
template:
metadata:
labels:
app: ci4s-visual
spec:
containers:
- name: ci4s-visual
image: ci4s-visual:v1.0
ports:
- containerPort: 9100

---
apiVersion: v1
kind: Service
metadata:
name: ci4s-visual-service
namespace: ci4s-test
spec:
type: NodePort
ports:
- port: 9100
nodePort: 31212
protocol: TCP
selector:
app: ci4s-visual


+ 0
- 36
k8s/k8s-12front.yaml View File

@@ -1,36 +0,0 @@
apiVersion: apps/v1
kind: Deployment
metadata:
name: ci4s-front-deployment
namespace: argo
spec:
replicas: 1
selector:
matchLabels:
app: ci4s-front
template:
metadata:
labels:
app: ci4s-front
spec:
containers:
- name: ci4s-front
image: 172.20.32.187/ci4s/ci4s-front:20240401
ports:
- containerPort: 8000

---
apiVersion: v1
kind: Service
metadata:
name: ci4s-front-service
namespace: argo
spec:
type: NodePort
ports:
- port: 8000
nodePort: 31213
protocol: TCP
selector:
app: ci4s-front


+ 0
- 62
k8s/k8s-3nacos.yaml View File

@@ -1,62 +0,0 @@
apiVersion: apps/v1
kind: Deployment
metadata:
namespace: argo
name: nacos-ci4s
labels:
app: nacos-ci4s
spec:
replicas: 1
selector:
matchLabels:
app: nacos-ci4s
template:
metadata:
labels:
app: nacos-ci4s
spec:
containers:
- name: nacos-ci4s
image: nacos/nacos-server:v2.2.0
env:
- name: SPRING_DATASOURCE_PLATFORM
value: mysql
- name: MODE
value: standalone
- name: MYSQL_SERVICE_HOST
value: mysql.argo.svc
- name: MYSQL_SERVICE_PORT
value: "3306"
- name: MYSQL_SERVICE_DB_NAME
value: nacos-ci4s-config
- name: MYSQL_SERVICE_USER
value: root
- name: MYSQL_SERVICE_PASSWORD
value: qazxc123456.
ports:
- containerPort: 8848
- containerPort: 9848
restartPolicy: Always

---

apiVersion: v1
kind: Service
metadata:
namespace: argo
name: nacos-ci4s
labels:
app: nacos-ci4s
spec:
type: NodePort
selector:
app: nacos-ci4s
ports:
- port: 8848
targetPort: 8848
nodePort: 31203
name: web
- port: 9848
targetPort: 9848
nodePort: 31204
name: podsa

+ 0
- 36
k8s/k8s-4gateway.yaml View File

@@ -1,36 +0,0 @@
apiVersion: apps/v1
kind: Deployment
metadata:
name: ci4s-gateway-deployment
namespace: argo
spec:
replicas: 1
selector:
matchLabels:
app: ci4s-gateway
template:
metadata:
labels:
app: ci4s-gateway
spec:
containers:
- name: ci4s-gateway
image: 172.20.32.187/ci4s/ci4s-gateway:20240401
ports:
- containerPort: 8082

---
apiVersion: v1
kind: Service
metadata:
name: ci4s-gateway-service
namespace: argo
spec:
type: NodePort
ports:
- port: 8082
nodePort: 31205
protocol: TCP
selector:
app: ci4s-gateway


+ 0
- 36
k8s/k8s-5auth.yaml View File

@@ -1,36 +0,0 @@
apiVersion: apps/v1
kind: Deployment
metadata:
name: ci4s-auth-deployment
namespace: argo
spec:
replicas: 1
selector:
matchLabels:
app: ci4s-auth
template:
metadata:
labels:
app: ci4s-auth
spec:
containers:
- name: ci4s-auth
image: 172.20.32.187/ci4s/ci4s-auth:20240401
ports:
- containerPort: 9200

---
apiVersion: v1
kind: Service
metadata:
name: ci4s-auth-service
namespace: argo
spec:
type: NodePort
ports:
- port: 9200
nodePort: 31206
protocol: TCP
selector:
app: ci4s-auth


+ 0
- 36
k8s/k8s-6system.yaml View File

@@ -1,36 +0,0 @@
apiVersion: apps/v1
kind: Deployment
metadata:
name: ci4s-system-deployment
namespace: argo
spec:
replicas: 1
selector:
matchLabels:
app: ci4s-system
template:
metadata:
labels:
app: ci4s-system
spec:
containers:
- name: ci4s-system
image: 172.20.32.187/ci4s/ci4s-system:20240401
ports:
- containerPort: 9201

---
apiVersion: v1
kind: Service
metadata:
name: ci4s-system-service
namespace: argo
spec:
type: NodePort
ports:
- port: 9201
nodePort: 31207
protocol: TCP
selector:
app: ci4s-system


+ 0
- 44
k8s/k8s-7management.yaml View File

@@ -1,44 +0,0 @@
apiVersion: apps/v1
kind: Deployment
metadata:
name: ci4s-management-platform-deployment
namespace: argo
spec:
replicas: 1
selector:
matchLabels:
app: ci4s-management-platform
template:
metadata:
labels:
app: ci4s-management-platform
spec:
containers:
- name: ci4s-management-platform
image: 172.20.32.187/ci4s/managent:20240401
ports:
- containerPort: 9213
volumeMounts:
- name: resource
mountPath: /home/resource/
volumes:
- name: resource
hostPath:
path: /home/resource/
type: DirectoryOrCreate

---
apiVersion: v1
kind: Service
metadata:
name: ci4s-management-platform-service
namespace: argo
spec:
type: NodePort
ports:
- port: 9213
nodePort: 31208
protocol: TCP
selector:
app: ci4s-management-platform


+ 0
- 36
k8s/k8s-8file.yaml View File

@@ -1,36 +0,0 @@
apiVersion: apps/v1
kind: Deployment
metadata:
name: ci4s-file-deployment
namespace: ci4s-test
spec:
replicas: 1
selector:
matchLabels:
app: ci4s-file
template:
metadata:
labels:
app: ci4s-file
spec:
containers:
- name: ci4s-file
image: ci4s-file:v1.0
ports:
- containerPort: 9300

---
apiVersion: v1
kind: Service
metadata:
name: ci4s-file-service
namespace: ci4s-test
spec:
type: NodePort
ports:
- port: 9300
nodePort: 31209
protocol: TCP
selector:
app: ci4s-file


+ 0
- 36
k8s/k8s-9job.yaml View File

@@ -1,36 +0,0 @@
apiVersion: apps/v1
kind: Deployment
metadata:
name: ci4s-job-deployment
namespace: ci4s-test
spec:
replicas: 1
selector:
matchLabels:
app: ci4s-job
template:
metadata:
labels:
app: ci4s-job
spec:
containers:
- name: ci4s-job
image: ci4s-job:v1.0
ports:
- containerPort: 9203

---
apiVersion: v1
kind: Service
metadata:
name: ci4s-job-service
namespace: ci4s-test
spec:
type: NodePort
ports:
- port: 9203
nodePort: 31210
protocol: TCP
selector:
app: ci4s-job


+ 0
- 36
k8s/template-yaml/deploy/k8s-12front.yaml View File

@@ -1,36 +0,0 @@
apiVersion: apps/v1
kind: Deployment
metadata:
name: ci4s-front-deployment
namespace: argo
spec:
replicas: 1
selector:
matchLabels:
app: ci4s-front
template:
metadata:
labels:
app: ci4s-front
spec:
containers:
- name: ci4s-front
image: 172.20.32.187/ci4s/ci4s-front:202406120836
ports:
- containerPort: 8000

---
apiVersion: v1
kind: Service
metadata:
name: ci4s-front-service
namespace: argo
spec:
type: NodePort
ports:
- port: 8000
nodePort: 31213
protocol: TCP
selector:
app: ci4s-front


+ 0
- 53
k8s/template-yaml/deploy/k8s-7management.yaml View File

@@ -1,53 +0,0 @@
apiVersion: apps/v1
kind: Deployment
metadata:
name: ci4s-management-platform-deployment
namespace: argo
spec:
replicas: 1
selector:
matchLabels:
app: ci4s-management-platform
template:
metadata:
labels:
app: ci4s-management-platform
spec:
containers:
- name: ci4s-management-platform
image: 172.20.32.187/ci4s/ci4s-managent:202409201355
env:
- name: TZ
value: Asia/Shanghai
- name: JAVA_TOOL_OPTIONS
value: "-Xrunjdwp:transport=dt_socket,server=y,suspend=n,address=*:5005"
ports:
- containerPort: 9213
volumeMounts:
- name: resource-volume
mountPath: /home/resource/
volumes:
- name: resource-volume
persistentVolumeClaim:
claimName: platform-data-pvc-nfs
---
apiVersion: v1
kind: Service
metadata:
name: ci4s-management-platform-service
namespace: argo
spec:
type: NodePort
ports:
- name: http
port: 9213
nodePort: 31208
protocol: TCP
- name: debug
nodePort: 34567
port: 5005
protocol: TCP
targetPort: 5005
selector:
app: ci4s-management-platform


+ 43
- 0
k8s/template-yaml/k8s-13oauth2.yaml View File

@@ -0,0 +1,43 @@
apiVersion: apps/v1
kind: Deployment
metadata:
name: ci4s-oauth2-authenticator-deployment
namespace: argo
spec:
replicas: 1
selector:
matchLabels:
app: ci4s-oauth2-authenticator
template:
metadata:
labels:
app: ci4s-oauth2-authenticator
spec:
containers:
- name: ci4s-oauth2-authenticator
image: 172.20.32.187/ci4s/spring-oauth2-authenticator:latest
env:
- name: DB_URL
value: mysql.argo.svc:3306
- name: DB_USERNAME
value: root
- name: DB_PASSWORD
value: qazxc123456.
ports:
- containerPort: 8080
---
apiVersion: v1
kind: Service
metadata:
name: ci4s-oauth2-authenticator-service
namespace: argo
spec:
type: NodePort
ports:
- name: http
port: 8080
nodePort: 31080
protocol: TCP
selector:
app: ci4s-oauth2-authenticator


+ 3
- 3
k8s/template-yaml/k8s-3nacos.yaml View File

@@ -17,7 +17,7 @@ spec:
spec:
containers:
- name: nacos-ci4s
image: nacos/nacos-server:v2.2.0
image: 172.20.32.187/ci4s/nacos-server:v2.2.0
env:
- name: SPRING_DATASOURCE_PLATFORM
value: mysql
@@ -38,8 +38,8 @@ spec:
- containerPort: 9848
- containerPort: 9849
initContainers:
- name: init-mydb
image: busybox:1.31
- name: init-mydb-check
image: 172.20.32.187/ci4s/busybox:1.31
command: [ 'sh', '-c', 'nc -zv mysql.argo.svc 3306' ]
restartPolicy: Always



+ 22
- 0
k8s/template-yaml/k8s-7management.yaml View File

@@ -16,6 +16,8 @@ spec:
containers:
- name: ci4s-management-platform
image: ${k8s-7management-image}
securityContext:
privileged: true
env:
- name: TZ
value: Asia/Shanghai
@@ -27,10 +29,30 @@ spec:
- name: resource-volume
mountPath: /home/resource/
subPath: mini-model-platform-data
mountPropagation: Bidirectional
volumes:
- name: resource-volume
hostPath:
path: /platform-data
initContainers:
- name: init-fs-check
image: 172.20.32.187/ci4s/ci4s-managent:202502141722
securityContext:
privileged: true
volumeMounts:
- name: resource-volume
mountPath: /home/resource/
subPath: mini-model-platform-data
mountPropagation: Bidirectional
command: [ "/bin/sh", "-c" ]
args:
- |
mounted=$(findmnt /home/resource/ | grep 'fuse.juicefs')
if [ -z "$mounted" ]; then
echo "/platform-data not mounted";
exit 1
fi
restartPolicy: Always
---
apiVersion: v1
kind: Service


+ 68
- 0
k8s/template-yaml/rolebindings.yaml View File

@@ -0,0 +1,68 @@
apiVersion: rbac.authorization.k8s.io/v1
kind: Role
metadata:
name: custom-workflow
namespace: argo
rules:
- apiGroups:
- argoproj.io
resources:
- workflows
verbs:
- create
- get
- list
- watch
- update
- patch
- delete
- apiGroups:
- ""
resources:
- pods
- services
verbs:
- get
- list
- watch
- create
- update
- patch
- delete
- apiGroups:
- ""
resources:
- pods/exec
verbs:
- create
- get
- list
- watch
- update
- patch
- delete
- apiGroups:
- "apps"
resources:
- deployments
verbs:
- get
- list
- watch
- create
- update
- patch
- delete
---

apiVersion: rbac.authorization.k8s.io/v1
kind: RoleBinding
metadata:
name: custom-workflow-default
roleRef:
apiGroup: rbac.authorization.k8s.io
kind: Role
name: custom-workflow
subjects:
- kind: ServiceAccount
name: default

+ 2
- 7
react-ui/.gitignore View File

@@ -41,10 +41,5 @@ screenshot
build

pnpm-lock.yaml
/src/services/codeConfig/index.js
/src/pages/CodeConfig/components/AddCodeConfigModal/index.less
/src/pages/CodeConfig/List/index.less
/src/pages/Dataset/components/ResourceItem/index.less
/src/pages/CodeConfig/components/AddCodeConfigModal/index.tsx
/src/pages/CodeConfig/components/CodeConfigItem/index.tsx
/src/pages/Dataset/components/ResourceItem/index.tsx

*storybook.log

+ 1
- 0
react-ui/.nvmrc View File

@@ -0,0 +1 @@
v18.16.0

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

@@ -0,0 +1,16 @@
export default function(babel) {
const { types: t } = babel;
return {
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";
}
}
},
},
};
};

+ 117
- 0
react-ui/.storybook/main.ts View File

@@ -0,0 +1,117 @@
import type { StorybookConfig } from '@storybook/react-webpack5';
import path from 'path';
import webpack from 'webpack';

const config: StorybookConfig = {
stories: ['../src/**/*.mdx', '../src/**/*.stories.@(js|jsx|mjs|ts|tsx)'],
addons: [
// '@storybook/addon-webpack5-compiler-swc',
'@storybook/addon-webpack5-compiler-babel',
'@storybook/addon-onboarding',
'@storybook/addon-essentials',
'@chromatic-com/storybook',
'@storybook/addon-interactions',
],
framework: {
name: '@storybook/react-webpack5',
options: {},
},
staticDirs: ['../static'],
docs: {
defaultName: 'Documentation',
},
webpackFinal: async (config) => {
if (config.resolve) {
config.resolve.alias = {
...config.resolve.alias,
'@': path.resolve(__dirname, '../src'),
'@umijs/max$': path.resolve(__dirname, './mock/umijs.mock.tsx'),
};
}
if (config.module && config.module.rules) {
config.module.rules.push(
{
test: /\.less$/,
oneOf: [
{
resourceQuery: /modules/,
use: [
'style-loader',
{
loader: 'css-loader',
options: {
importLoaders: 1,
import: true,
esModule: true,
modules: {
localIdentName: '[local]___[hash:base64:5]',
},
},
},
{
loader: 'less-loader',
options: {
lessOptions: {
javascriptEnabled: true, // 如果需要支持 Ant Design 的 Less 变量,开启此项
modifyVars: {
hack: 'true; @import "@/styles/theme.less";',
},
},
},
},
],
include: path.resolve(__dirname, '../src'), // 限制范围,避免处理 node_modules
},
{
use: [
'style-loader',
'css-loader',
{
loader: 'less-loader',
options: {
lessOptions: {
javascriptEnabled: true, // 如果需要支持 Ant Design 的 Less 变量,开启此项
modifyVars: {
hack: 'true; @import "@/styles/theme.less";',
},
},
},
},
],
include: path.resolve(__dirname, '../src'), // 限制范围,避免处理 node_modules
},
],
},
{
test: /\.(tsx?|jsx?)$/,
loader: 'ts-loader',
options: {
transpileOnly: true,
},
include: [
path.resolve(__dirname, '../src'), // 限制范围,避免处理 node_modules
path.resolve(__dirname, './'),
],
},
);
}
if (config.plugins) {
config.plugins.push(
new webpack.ProvidePlugin({
React: 'react', // 全局注入 React
}),
);
}

return config;
},
babel: async (config: any) => {
if (!config.plugins) {
config.plugins = [];
}

config.plugins.push(path.resolve(__dirname, './babel-plugin-auto-css-modules.js'));
return config;
},
};
export default config;

+ 19
- 0
react-ui/.storybook/mock/umijs.mock.tsx View File

@@ -0,0 +1,19 @@
export const Link = ({ to, children, ...props }: any) => (
<a href={typeof to === 'string' ? to : '#'} {...props}>
{children}
</a>
);

export const request = (url: string, options: any) => {
return fetch(url, options)
.then((res) => {
if (!res.ok) {
throw new Error(res.statusText);
}
return res;
})
.then((res) => res.json());
};

export { useNavigate, useParams, useSearchParams } from 'react-router-dom';
export const history = window.history;

+ 107
- 0
react-ui/.storybook/preview.tsx View File

@@ -0,0 +1,107 @@
import '@/global.less';
import '@/overrides.less';
import themes from '@/styles/theme.less';
import type { Preview } from '@storybook/react';
import { App, ConfigProvider } from 'antd';
import zhCN from 'antd/locale/zh_CN';
import { initialize, mswLoader } from 'msw-storybook-addon';
import './storybook.css';

/*
* Initializes MSW
* See https://github.com/mswjs/msw-storybook-addon#configuring-msw
* to learn how to customize it
*/
initialize();

const preview: Preview = {
parameters: {
controls: {
expanded: true,
sort: 'requiredFirst',
matchers: {
color: /(background|color)$/i,
date: /Date$/i,
},
},
backgrounds: {
values: [
{ name: 'Dark', value: '#000' },
{ name: 'Gray', value: '#f9fafb' },
{ name: 'Light', value: '#FFF' },
],
default: 'Light',
},
options: {
storySort: {
method: 'alphabetical',
order: ['Documentation', 'Components'],
},
},
},
decorators: [
(Story) => (
<ConfigProvider
locale={zhCN}
theme={{
cssVar: true,
token: {
colorPrimary: themes['primaryColor'],
colorSuccess: themes['successColor'],
colorError: themes['errorColor'],
colorWarning: themes['warningColor'],
colorLink: themes['primaryColor'],
colorText: themes['textColor'],
controlHeightLG: 46,
},
components: {
Button: {
defaultBg: 'rgba(22, 100, 255, 0.06)',
defaultBorderColor: 'rgba(22, 100, 255, 0.11)',
defaultColor: themes['textColor'],
defaultHoverBg: 'rgba(22, 100, 255, 0.06)',
defaultHoverBorderColor: 'rgba(22, 100, 255, 0.5)',
defaultHoverColor: '#3F7FFF ',
defaultActiveBg: 'rgba(22, 100, 255, 0.12)',
defaultActiveBorderColor: 'rgba(22, 100, 255, 0.75)',
defaultActiveColor: themes['primaryColor'],
contentFontSize: parseInt(themes['fontSize']),
},
Input: {
inputFontSize: parseInt(themes['fontSizeInput']),
inputFontSizeLG: parseInt(themes['fontSizeInputLg']),
paddingBlockLG: 10,
},
Select: {
singleItemHeightLG: 46,
optionSelectedColor: themes['primaryColor'],
},
Table: {
headerBg: 'rgba(242, 244, 247, 0.36)',
headerBorderRadius: 4,
rowSelectedBg: 'rgba(22, 100, 255, 0.05)',
},
Tabs: {
titleFontSize: 16,
},
Form: {
labelColor: 'rgba(29, 29, 32, 0.8);',
},
Breadcrumb: {
iconFontSize: parseInt(themes['fontSize']),
linkColor: 'rgba(29, 29, 32, 0.7)',
separatorColor: 'rgba(29, 29, 32, 0.7)',
},
},
}}
>
<App message={{ maxCount: 3 }}>
<Story />
</App>
</ConfigProvider>
),
],
loaders: [mswLoader], // 👈 Add the MSW loader to all stories
};

export default preview;

+ 19
- 0
react-ui/.storybook/storybook.css View File

@@ -0,0 +1,19 @@
html,
body,
#root {
min-width: unset;
height: 100%;
margin: 0;
padding: 0;
overflow-y: visible;
}

.ant-input-search-large .ant-input-affix-wrapper, .ant-input-search-large .ant-input-search-button {
height: 46px;
}

*,
*::before,
*::after {
box-sizing: border-box;
}

+ 27
- 0
react-ui/.storybook/tsconfig.json View File

@@ -0,0 +1,27 @@
{
"compilerOptions": {
"target": "esnext", // 指定ECMAScript目标版本
"lib": ["dom", "dom.iterable", "esnext"], // 要包含在编译中的库文件列表
"allowJs": true, // 允许编译JavaScript文件
"skipLibCheck": true, // 跳过所有声明文件的类型检查
"esModuleInterop": true, // 禁用命名空间导入(import * as fs from "fs"),并启用CJS/AMD/UMD样式的导入(import fs from "fs")
"allowSyntheticDefaultImports": true, // 允许从没有默认导出的模块进行默认导入
"strict": true, // 启用所有严格类型检查选项
"forceConsistentCasingInFileNames": false, // 允许对同一文件的引用使用不一致的大小写
"module": "esnext", // 指定模块代码生成
"moduleResolution": "bundler", // 使用bundlers样式解析模块
"isolatedModules": true, // 无条件地为未解析的文件发出导入
"resolveJsonModule": true, // 包含.json扩展名的模块
"noEmit": true, // 不发出输出(即不编译代码,只进行类型检查)
"jsx": "react-jsx", // 在.tsx文件中支持JSX
"sourceMap": true, // 生成相应的.map文件
"declaration": true, // 生成相应的.d.ts文件
"noUnusedLocals": true, // 报告未使用的局部变量错误
"noUnusedParameters": true, // 报告未使用的参数错误
"incremental": true, // 通过读写磁盘上的文件来启用增量编译
"noFallthroughCasesInSwitch": true, // 报告switch语句中的fallthrough案例错误
"strictNullChecks": true, // 启用严格的null检查
"importHelpers": true,
"baseUrl": "./"
}
}

+ 20
- 0
react-ui/.storybook/typings.d.ts View File

@@ -0,0 +1,20 @@
declare module 'slash2';
declare module '*.css';
declare module '*.less';
declare module '*.scss';
declare module '*.sass';
declare module '*.svg';
declare module '*.png';
declare module '*.jpg';
declare module '*.jpeg';
declare module '*.gif';
declare module '*.bmp';
declare module '*.tiff';
declare module 'omit.js';
declare module 'numeral';
declare module '@antv/data-set';
declare module 'mockjs';
declare module 'react-fittext';
declare module 'bizcharts-plugin-slider';

declare const REACT_APP_ENV: 'test' | 'dev' | 'pre' | false;

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

@@ -75,7 +75,7 @@ export default defineConfig({
* @name layout 插件
* @doc https://umijs.org/docs/max/layout-menu
*/
title: '复杂智能软件',
title: '智能材料科研平台',
layout: {
...defaultSettings,
},


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

@@ -17,7 +17,7 @@ const Settings: ProLayoutProps & {
fixSiderbar: false,
splitMenus: false,
colorWeak: false,
title: '复杂智能软件',
title: '智能材料科研平台',
pwa: true,
token: {
// 参见ts声明,demo 见文档,通过token 修改样式


+ 37
- 1
react-ui/config/routes.ts View File

@@ -44,7 +44,7 @@ export default [
{
name: 'login',
path: '/user/login',
component: './User/Login/login',
component: process.env.NO_SSO ? './User/Login/login' : './User/Login',
},
],
},
@@ -181,6 +181,42 @@ export default [
},
],
},
{
name: '超参数自动寻优',
path: 'hyperparameter',
routes: [
{
name: '超参数寻优',
path: '',
component: './HyperParameter/List/index',
},
{
name: '实验详情',
path: 'info/:id',
component: './HyperParameter/Info/index',
},
{
name: '创建实验',
path: 'create',
component: './HyperParameter/Create/index',
},
{
name: '编辑实验',
path: 'edit/:id',
component: './HyperParameter/Create/index',
},
{
name: '复制实验',
path: 'copy/:id',
component: './HyperParameter/Create/index',
},
{
name: '实验实例详情',
path: 'instance/:autoMLId/:id',
component: './HyperParameter/Instance/index',
},
],
},
],
},
{


+ 28
- 0
react-ui/package.json View File

@@ -8,6 +8,7 @@
"build": "max build",
"deploy": "npm run build && npm run gh-pages",
"dev": "npm run start:dev",
"dev-no-sso": "NO_SSO=true npm run start:dev",
"docker-hub:build": "docker build -f Dockerfile.hub -t ant-design-pro ./",
"docker-prod:build": "docker-compose -f ./docker/docker-compose.yml build",
"docker-prod:dev": "docker-compose -f ./docker/docker-compose.yml up",
@@ -36,6 +37,10 @@
"start:mock": "cross-env REACT_APP_ENV=dev UMI_ENV=dev max dev",
"start:pre": "cross-env REACT_APP_ENV=pre UMI_ENV=dev max dev",
"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-docs": "storybook dev --docs",
"storybook-docs-build": "storybook build --docs",
"test": "jest",
"test:coverage": "npm run jest -- --coverage",
"test:update": "npm run jest -- -u",
@@ -83,6 +88,17 @@
},
"devDependencies": {
"@ant-design/pro-cli": "^3.1.0",
"@chromatic-com/storybook": "~3.2.4",
"@storybook/addon-essentials": "~8.5.3",
"@storybook/addon-interactions": "~8.5.3",
"@storybook/addon-onboarding": "~8.5.3",
"@storybook/addon-styling-webpack": "~1.0.1",
"@storybook/addon-webpack5-compiler-babel": "~3.0.5",
"@storybook/addon-webpack5-compiler-swc": "~2.0.0",
"@storybook/blocks": "~8.5.3",
"@storybook/react": "~8.5.3",
"@storybook/react-webpack5": "~8.5.3",
"@storybook/test": "~8.5.3",
"@testing-library/react": "^14.0.0",
"@types/antd": "^1.0.0",
"@types/express": "^4.17.14",
@@ -96,15 +112,22 @@
"@umijs/max": "^4.0.66",
"cross-env": "^7.0.3",
"eslint": "^8.39.0",
"eslint-plugin-storybook": "~0.11.2",
"express": "^4.18.2",
"gh-pages": "^5.0.0",
"husky": "^8.0.3",
"jest": "^29.5.0",
"jest-environment-jsdom": "^29.5.0",
"less": "~4.2.2",
"less-loader": "~12.2.0",
"lint-staged": "^13.2.0",
"mockjs": "^1.1.0",
"msw": "~2.7.0",
"msw-storybook-addon": "~2.0.4",
"prettier": "^2.8.1",
"storybook": "~8.5.3",
"swagger-ui-dist": "^4.18.2",
"ts-loader": "~9.5.2",
"ts-node": "^10.9.1",
"typescript": "^5.0.4",
"umi-presets-pro": "^2.0.0"
@@ -140,5 +163,10 @@
"CNAME",
"create-umi"
]
},
"msw": {
"workerDirectory": [
"static"
]
}
}

BIN
react-ui/public/favicon-cc.ico View File

Before After

BIN
react-ui/public/favicon.ico View File

Before After

+ 3
- 3
react-ui/src/app.tsx View File

@@ -7,7 +7,6 @@ import defaultSettings from '../config/defaultSettings';
import '../public/fonts/font.css';
import { getAccessToken } from './access';
import './dayjsConfig';
import './global.less';
import { removeAllPageCacheState } from './hooks/pageCacheState';
import {
getRemoteMenu,
@@ -41,7 +40,7 @@ export async function getInitialState(): Promise<GlobalInitialState> {
roleNames: response.user.roles,
} as API.CurrentUser;
} catch (error) {
console.error('1111', error);
console.error('getInitialState', error);
gotoLoginPage();
}
return undefined;
@@ -215,7 +214,7 @@ export const antd: RuntimeAntdConfig = (memo) => {
defaultColor: themes['textColor'],
defaultHoverBg: 'rgba(22, 100, 255, 0.06)',
defaultHoverBorderColor: 'rgba(22, 100, 255, 0.5)',
defaultHoverColor: '#3F7FFF ',
defaultHoverColor: '#3F7FFF',
defaultActiveBg: 'rgba(22, 100, 255, 0.12)',
defaultActiveBorderColor: 'rgba(22, 100, 255, 0.75)',
defaultActiveColor: themes['primaryColor'],
@@ -228,6 +227,7 @@ export const antd: RuntimeAntdConfig = (memo) => {
};
memo.theme.components.Select = {
singleItemHeightLG: 46,
optionSelectedColor: themes['primaryColor'],
};
memo.theme.components.Table = {
headerBg: 'rgba(242, 244, 247, 0.36)',


BIN
react-ui/src/assets/img/logo-cc.png View File

Before After
Width: 104  |  Height: 132  |  Size: 9.2 kB

BIN
react-ui/src/assets/img/logo-cl.png View File

Before After
Width: 112  |  Height: 112  |  Size: 5.3 kB

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

Before After
Width: 104  |  Height: 132  |  Size: 9.2 kB Width: 112  |  Height: 112  |  Size: 5.3 kB

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

Before After
Width: 1200  |  Height: 452  |  Size: 49 kB

+ 86
- 0
react-ui/src/components/BasicInfo/BasicInfoItem.tsx View File

@@ -0,0 +1,86 @@
/*
* @Author: 赵伟
* @Date: 2024-11-29 09:27:19
* @Description: 用于 BasicInfo 和 BasicTableInfo 组件的子组件
*/

import { Typography } from 'antd';
import React from 'react';
import BasicInfoItemValue from './BasicInfoItemValue';
import { type BasicInfoData, type BasicInfoLink } from './types';

type BasicInfoItemProps = {
/** 基础信息 */
data: BasicInfoData;
/** 标题宽度 */
labelWidth: number;
/** 自定义类名前缀 */
classPrefix: string;
/** 标题是否显示省略号 */
labelEllipsis?: boolean;
/** 标签对齐方式 */
labelAlign?: 'start' | 'end' | 'justify';
};

function BasicInfoItem({
data,
labelWidth,
classPrefix,
labelEllipsis = true,
labelAlign = 'start',
}: BasicInfoItemProps) {
const { label, value, format, ellipsis } = data;
const formatValue = format ? format(value) : value;
const myClassName = `${classPrefix}__item`;
let valueComponent = undefined;
if (React.isValidElement(formatValue)) {
valueComponent = <div className={`${myClassName}__node`}>{formatValue}</div>;
} else if (Array.isArray(formatValue)) {
valueComponent = (
<div className={`${myClassName}__value-container`}>
{formatValue.map((item: BasicInfoLink) => (
<BasicInfoItemValue
key={item.value}
value={item.value}
link={item.link}
url={item.url}
ellipsis={ellipsis}
classPrefix={classPrefix}
/>
))}
</div>
);
} else if (typeof formatValue === 'object' && formatValue) {
valueComponent = (
<BasicInfoItemValue
value={formatValue.value}
link={formatValue.link}
url={formatValue.url}
ellipsis={ellipsis}
classPrefix={classPrefix}
/>
);
} else {
valueComponent = (
<BasicInfoItemValue value={formatValue} ellipsis={ellipsis} classPrefix={classPrefix} />
);
}
return (
<div className={myClassName} key={label}>
<div
className={`${myClassName}__label`}
style={{ width: labelWidth, textAlign: labelAlign, textAlignLast: labelAlign }}
>
<Typography.Text
ellipsis={labelEllipsis !== false ? { tooltip: label } : false}
style={{ width: labelAlign === 'justify' ? '100%' : 'auto' }}
>
{label}
</Typography.Text>
</div>
{valueComponent}
</div>
);
}

export default BasicInfoItem;

+ 58
- 0
react-ui/src/components/BasicInfo/BasicInfoItemValue.tsx View File

@@ -0,0 +1,58 @@
/*
* @Author: 赵伟
* @Date: 2024-11-29 09:27:19
* @Description: 用于 BasicInfoItem 的组件
*/

import { isEmpty } from '@/utils';
import { Link } from '@umijs/max';
import { Typography } from 'antd';

type BasicInfoItemValueProps = {
/** 值是否显示省略号 */
ellipsis?: boolean;
/** 自定义类名前缀 */
classPrefix: string;
/** 值 */
value?: string;
/** 内部链接 */
link?: string;
/** 外部链接 */
url?: string;
};

function BasicInfoItemValue({
value,
link,
url,
classPrefix,
ellipsis = true,
}: BasicInfoItemValueProps) {
const myClassName = `${classPrefix}__item__value`;
let component = undefined;
if (url && value) {
component = (
<a className={`${myClassName}__link`} href={url} target="_blank" rel="noopener noreferrer">
{value}
</a>
);
} else if (link && value) {
component = (
<Link to={link} className={`${myClassName}__link`}>
{value}
</Link>
);
} else {
component = <span className={`${myClassName}__text`}>{!isEmpty(value) ? value : '--'}</span>;
}

return (
<div className={myClassName}>
<Typography.Text ellipsis={ellipsis !== false ? { tooltip: value } : false}>
{component}
</Typography.Text>
</div>
);
}

export default BasicInfoItemValue;

+ 0
- 113
react-ui/src/components/BasicInfo/components.tsx View File

@@ -1,113 +0,0 @@
/*
* @Author: 赵伟
* @Date: 2024-11-29 09:27:19
* @Description: 用于 BasicInfo 和 BasicTableInfo 组件的子组件
*/

import { Link } from '@umijs/max';
import { Typography } from 'antd';
import React from 'react';
import { type BasicInfoData, type BasicInfoLink } from './types';

type BasicInfoItemProps = {
data: BasicInfoData;
labelWidth: number;
classPrefix: string;
};

export function BasicInfoItem({ data, labelWidth, classPrefix }: BasicInfoItemProps) {
const { label, value, format, ellipsis } = data;
const formatValue = format ? format(value) : value;
const myClassName = `${classPrefix}__item`;
let valueComponent = undefined;
if (Array.isArray(formatValue)) {
valueComponent = (
<div className={`${myClassName}__value-container`}>
{formatValue.map((item: BasicInfoLink) => (
<BasicInfoItemValue
key={item.value}
value={item.value}
link={item.link}
url={item.url}
ellipsis={ellipsis}
classPrefix={classPrefix}
/>
))}
</div>
);
} else if (React.isValidElement(formatValue)) {
// 这个判断必须在下面的判断之前
valueComponent = (
<BasicInfoItemValue value={formatValue} ellipsis={ellipsis} classPrefix={classPrefix} />
);
} else if (typeof formatValue === 'object' && formatValue) {
valueComponent = (
<BasicInfoItemValue
value={formatValue.value}
link={formatValue.link}
url={formatValue.url}
ellipsis={ellipsis}
classPrefix={classPrefix}
/>
);
} else {
valueComponent = (
<BasicInfoItemValue value={formatValue} ellipsis={ellipsis} classPrefix={classPrefix} />
);
}
return (
<div className={myClassName} key={label}>
<div className={`${myClassName}__label`} style={{ width: labelWidth }}>
{label}
</div>
{valueComponent}
</div>
);
}

type BasicInfoItemValueProps = {
ellipsis?: boolean;
classPrefix: string;
value: string | React.ReactNode;
link?: string;
url?: string;
};

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

return (
<div className={myClassName}>
<Typography.Text
ellipsis={ellipsis ? { tooltip: value } : false}
style={{ fontSize: 'inherit' }}
>
{component}
</Typography.Text>
</div>
);
}

+ 0
- 48
react-ui/src/components/BasicInfo/format.ts View File

@@ -1,48 +0,0 @@
/*
* @Author: 赵伟
* @Date: 2024-11-29 09:27:19
* @Description: 用于 BasicInfo 和 BasicTableInfo 组件的常用转化格式
*/

// 格式化日期
export { formatDate } from '@/utils/date';

/**
* 格式化字符串数组
* @param value - 字符串数组
* @returns 逗号分隔的字符串
*/
export const formatList = (value: string[] | null | undefined): string => {
if (
value === undefined ||
value === null ||
Array.isArray(value) === false ||
value.length === 0
) {
return '--';
}
return value.join(',');
};

/**
* 格式化布尔值
* @param value - 布尔值
* @returns "是" 或 "否"
*/
export const formatBoolean = (value: boolean): string => {
return value ? '是' : '否';
};

type FormatEnum = (value: string | number) => string;

/**
* 格式化枚举
* @param options - 枚举选项
* @returns 格式化枚举函数
*/
export const formatEnum = (options: { value: string | number; label: string }[]): FormatEnum => {
return (value: string | number) => {
const option = options.find((item) => item.value === value);
return option ? option.label : '--';
};
};

+ 26
- 2
react-ui/src/components/BasicInfo/index.less View File

@@ -17,8 +17,6 @@
color: @text-color-secondary;
font-size: @font-size-content;
line-height: 1.6;
text-align: justify;
text-align-last: justify;

&::after {
position: absolute;
@@ -31,10 +29,12 @@
flex: 1;
flex-direction: column;
gap: 5px 0;
min-width: 0;
}

&__value {
flex: 1;
min-width: 0;
margin-left: 16px;
font-size: @font-size-content;
line-height: 1.6;
@@ -49,5 +49,29 @@
text-underline-offset: 3px;
}
}

&__node {
flex: 1;
min-width: 0;
margin-left: 16px;
font-size: @font-size-content;
line-height: 1.6;
word-break: break-all;
}
}
}

.kf-basic-info--three-columns {
width: 100%;

.kf-basic-info__item {
width: calc((100% - 80px) / 3);

&__label {
font-size: @font-size;
}
&__value {
font-size: @font-size;
}
}
}

+ 35
- 6
react-ui/src/components/BasicInfo/index.tsx View File

@@ -1,27 +1,56 @@
import classNames from 'classnames';
import React from 'react';
import { BasicInfoItem } from './components';
import BasicInfoItem from './BasicInfoItem';
import './index.less';
import type { BasicInfoData, BasicInfoLink } from './types';
export * from './format';
export type { BasicInfoData, BasicInfoLink };

type BasicInfoProps = {
export type BasicInfoProps = {
/** 基础信息 */
datas: BasicInfoData[];
/** 标题宽度 */
labelWidth: number;
/** 标题是否显示省略号 */
labelEllipsis?: boolean;
/** 是否一行三列 */
threeColumns?: boolean;
/** 标签对齐方式 */
labelAlign?: 'start' | 'end' | 'justify';
/** 自定义类名 */
className?: string;
/** 自定义样式 */
style?: React.CSSProperties;
labelWidth: number;
};

export default function BasicInfo({ datas, className, style, labelWidth }: BasicInfoProps) {
/**
* 基础信息展示组件,用于展示基础信息,支持一行两列或一行三列,支持数据格式化
*/
export default function BasicInfo({
datas,
className,
style,
labelWidth,
labelEllipsis = true,
threeColumns = false,
labelAlign = 'start',
}: BasicInfoProps) {
return (
<div className={classNames('kf-basic-info', className)} style={style}>
<div
className={classNames(
'kf-basic-info',
{ 'kf-basic-info--three-columns': threeColumns },
className,
)}
style={style}
>
{datas.map((item) => (
<BasicInfoItem
key={item.label}
data={item}
labelWidth={labelWidth}
classPrefix="kf-basic-info"
labelEllipsis={labelEllipsis}
labelAlign={labelAlign}
/>
))}
</div>


+ 2
- 2
react-ui/src/components/BasicInfo/types.ts View File

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

// 值为链接的类型
export type BasicInfoLink = {
value: string;
value?: string;
link?: string;
url?: string;
};

+ 10
- 3
react-ui/src/components/BasicTableInfo/index.less View File

@@ -4,7 +4,7 @@
flex-wrap: wrap;
align-items: stretch;
width: 100%;
border: 1px solid @border-color-base;
border: 1px solid @border-color;
border-bottom: none;
border-radius: 4px;

@@ -12,7 +12,7 @@
display: flex;
align-items: stretch;
width: 25%;
border-bottom: 1px solid @border-color-base;
border-bottom: 1px solid @border-color;

&__label {
flex: none;
@@ -34,7 +34,6 @@
&__value {
flex: 1;
min-width: 0;
margin: 0 !important;
padding: 12px 20px 4px;
font-size: @font-size;
word-break: break-all;
@@ -56,5 +55,13 @@
text-underline-offset: 3px;
}
}

&__node {
flex: 1;
min-width: 0;
padding: 12px 20px;
font-size: @font-size;
word-break: break-all;
}
}
}

+ 9
- 11
react-ui/src/components/BasicTableInfo/index.tsx View File

@@ -1,30 +1,27 @@
import classNames from 'classnames';
import { BasicInfoItem } from '../BasicInfo/components';
import { BasicInfoProps } from '../BasicInfo';
import BasicInfoItem from '../BasicInfo/BasicInfoItem';
import { type BasicInfoData, type BasicInfoLink } from '../BasicInfo/types';
import './index.less';
export * from '../BasicInfo/format';
export type { BasicInfoData, BasicInfoLink };

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

/**
* 表格基础信息展示组件,用于展示基础信息,一行四列,支持数据格式化
*/
export default function BasicTableInfo({
datas,
className,
style,
labelWidth,
}: BasicTableInfoProps) {
labelEllipsis,
}: BasicInfoProps) {
const remainder = datas.length % 4;
const array = [];
if (remainder > 0) {
for (let i = 0; i < 4 - remainder; i++) {
array.push({
label: '',
value: '',
value: false, // 用于区分是否是空数据,不能是空字符串、null、undefined
});
}
}
@@ -37,6 +34,7 @@ export default function BasicTableInfo({
key={`${item.label}-${index}`}
data={item}
labelWidth={labelWidth}
labelEllipsis={labelEllipsis}
classPrefix="kf-basic-table-info"
/>
))}


react-ui/src/pages/Pipeline/components/CodeConfigItem/index.less → react-ui/src/components/CodeConfigItem/index.less View File


react-ui/src/pages/Pipeline/components/CodeConfigItem/index.tsx → react-ui/src/components/CodeConfigItem/index.tsx View File


+ 20
- 5
react-ui/src/components/CodeSelect/index.tsx View File

@@ -4,18 +4,32 @@
* @Description: 流水线选择代码配置表单
*/

import CodeSelectorModal from '@/components/CodeSelectorModal';
import KFIcon from '@/components/KFIcon';
import CodeSelectorModal from '@/pages/Pipeline/components/CodeSelectorModal';
import { openAntdModal } from '@/utils/modal';
import { Button } from 'antd';
import classNames from 'classnames';
import ParameterInput, { type ParameterInputProps } from '../ParameterInput';
import './index.less';

export { requiredValidator, type ParameterInputObject } from '../ParameterInput';
export {
requiredValidator,
type ParameterInputObject,
type ParameterInputValue,
} from '../ParameterInput';

type CodeSelectProps = ParameterInputProps;

function CodeSelect({ value, onChange, disabled, ...rest }: CodeSelectProps) {
/** 代码配置选择表单组件 */
function CodeSelect({
value,
size,
disabled,
className,
style,
onChange,
...rest
}: CodeSelectProps) {
const selectResource = () => {
const { close } = openAntdModal(CodeSelectorModal, {
onOk: (res) => {
@@ -46,9 +60,10 @@ function CodeSelect({ value, onChange, disabled, ...rest }: CodeSelectProps) {
};

return (
<div className="kf-code-select">
<div className={classNames('kf-code-select', className)} style={style}>
<ParameterInput
{...rest}
size={size}
disabled={disabled}
value={value}
onChange={onChange}
@@ -56,7 +71,7 @@ function CodeSelect({ value, onChange, disabled, ...rest }: CodeSelectProps) {
></ParameterInput>
<Button
className="kf-code-select__button"
size="large"
size={size}
type="link"
icon={<KFIcon type="icon-xuanzedaimapeizhi" font={16} />}
disabled={disabled}


+ 49
- 0
react-ui/src/components/CodeSelectorModal/index.less View File

@@ -0,0 +1,49 @@
.kf-code-selector-modal {
width: 100%;
height: 100%;

&__search {
width: 100%;
}

&__content {
display: flex;
flex-direction: row;
flex-wrap: wrap;
gap: 10px;
width: 100%;
max-height: 50vh;
margin-top: 24px;
margin-bottom: 30px;
overflow-x: hidden;
overflow-y: auto;
}

&__empty {
padding-top: 40px;
}

// 覆盖 antd 样式
.ant-input-affix-wrapper {
border-radius: 23px !important;
.ant-input-prefix {
margin-inline-end: 12px;
}
.ant-input-suffix {
margin-inline-end: 12px;
}
.ant-input-clear-icon {
font-size: 16px;
}
}

.ant-input-group-addon {
display: none;
}

.ant-pagination {
.ant-select-single {
height: 32px !important;
}
}
}

react-ui/src/pages/Pipeline/components/CodeSelectorModal/index.tsx → react-ui/src/components/CodeSelectorModal/index.tsx View File

@@ -4,16 +4,16 @@
* @Description: 选择代码
*/

import KFIcon from '@/components/KFIcon';
import KFModal from '@/components/KFModal';
import { type CodeConfigData } from '@/pages/CodeConfig/List';
import { getCodeConfigListReq } from '@/services/codeConfig';
import { to } from '@/utils/promise';
import { Icon } from '@umijs/max';
import type { ModalProps, PaginationProps } from 'antd';
import { Empty, Input, Pagination } from 'antd';
import { useEffect, useState } from 'react';
import CodeConfigItem from '../CodeConfigItem';
import styles from './index.less';
import './index.less';

export { type CodeConfigData };

@@ -21,6 +21,7 @@ export interface CodeSelectorModalProps extends Omit<ModalProps, 'onOk'> {
onOk?: (params: CodeConfigData | undefined) => void;
}

/** 选择代码配置的弹窗,推荐使用函数的方式打开 */
function CodeSelectorModal({ onOk, ...rest }: CodeSelectorModalProps) {
const [dataList, setDataList] = useState<CodeConfigData[]>([]);
const [total, setTotal] = useState(0);
@@ -79,9 +80,9 @@ function CodeSelectorModal({ onOk, ...rest }: CodeSelectorModalProps) {
footer={null}
destroyOnClose
>
<div className={styles['code-selector']}>
<div className="kf-code-selector-modal">
<Input.Search
className={styles['code-selector__search']}
className="kf-code-selector-modal__search"
placeholder="按代码仓库名称筛选"
allowClear
onSearch={handleSearch}
@@ -90,12 +91,15 @@ function CodeSelectorModal({ onOk, ...rest }: CodeSelectorModalProps) {
suffix={null}
value={inputText}
prefix={
<Icon icon="local:magnifying-glass" style={{ marginLeft: '10px', marginTop: '2px' }} />
<KFIcon type="icon-sousuo" color="rgba(22,100,255,0.4" style={{ marginLeft: '10px' }} />
}
// prefix={
// <Icon icon="local:magnifying-glass" style={{ marginLeft: '10px', marginTop: '2px' }} />
// }
/>
{dataList?.length !== 0 ? (
<>
<div className={styles['code-selector__content']}>
<div className="kf-code-selector-modal__content">
{dataList?.map((item) => (
<CodeConfigItem item={item} key={item.id} onClick={handleClick} />
))}
@@ -112,7 +116,7 @@ function CodeSelectorModal({ onOk, ...rest }: CodeSelectorModalProps) {
/>
</>
) : (
<div className={styles['code-selector__empty']}>
<div className="kf-code-selector-modal__empty">
<Empty image={Empty.PRESENTED_IMAGE_SIMPLE}></Empty>
</div>
)}

+ 41
- 0
react-ui/src/components/ConfigInfo/index.tsx View File

@@ -0,0 +1,41 @@
import BasicInfo, { type BasicInfoData, type BasicInfoProps } from '@/components/BasicInfo';
import InfoGroup from '@/components/InfoGroup';
import classNames from 'classnames';
export type { BasicInfoData };

interface ConfigInfoProps extends BasicInfoProps {
/** 标题 */
title: string;
/** 子元素 */
children?: React.ReactNode;
}

/** 详情基本信息块,目前主要用于主动机器学习、超参数寻优、自主学习详情中 */
function ConfigInfo({
title,
datas,
labelWidth,
labelAlign = 'start',
labelEllipsis = true,
threeColumns = true,
className,
style,
children,
}: ConfigInfoProps) {
return (
<InfoGroup title={title} className={classNames('kf-config-info', className)} style={style}>
<div className={'kf-config-info__content'}>
<BasicInfo
datas={datas}
labelWidth={labelWidth}
labelAlign={labelAlign}
labelEllipsis={labelEllipsis}
threeColumns={threeColumns}
/>
{children}
</div>
</InfoGroup>
);
}

export default ConfigInfo;

+ 0
- 9
react-ui/src/components/DisabledInput/index.less View File

@@ -1,9 +0,0 @@
.disabled-input {
padding: 4px 11px;
color: rgba(0, 0, 0, 0.25);
font-size: @font-size-input;
background-color: rgba(0, 0, 0, 0.04);
border: 1px solid #d9d9d9;
border-radius: 6px;
cursor: not-allowed;
}

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

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

type DisabledInputProps = {
value?: any;
valuePropName?: string;
};

function DisabledInput({ value, valuePropName }: DisabledInputProps) {
const data = valuePropName ? value[valuePropName] : value;
return (
<div className={styles['disabled-input']}>
<Typography.Text ellipsis={{ tooltip: data }} style={{ color: 'inherit' }}>
{data}
</Typography.Text>
</div>
);
}

export default DisabledInput;

+ 20
- 0
react-ui/src/components/FormInfo/index.less View File

@@ -0,0 +1,20 @@
.form-info {
min-height: 32px;
padding: 4px 11px;
color: @text-disabled-color;
font-size: @font-size-input;
background-color: rgba(0, 0, 0, 0.04);
border: 1px solid #d9d9d9;
border-radius: 6px;
cursor: not-allowed;

.ant-typography {
margin: 0 !important;
}
}

.form-info--multiline {
.ant-typography {
white-space: pre-wrap;
}
}

+ 41
- 0
react-ui/src/components/FormInfo/index.tsx View File

@@ -0,0 +1,41 @@
import { Typography } from 'antd';
import classNames from 'classnames';
import './index.less';

type FormInfoProps = {
/** 自定义类名 */
value?: any;
/** 如果 `value` 是对象时,取对象的哪个属性作为值 */
valuePropName?: string;
/** 是否是多行 */
multiline?: boolean;
/** 自定义类名 */
className?: string;
/** 自定义样式 */
style?: React.CSSProperties;
};

/**
* 模拟禁用的输入框,但是内容超长时,hover 时显示所有内容
*/
function FormInfo({ value, valuePropName, className, style, multiline = false }: FormInfoProps) {
const data = value && typeof value === 'object' && valuePropName ? value[valuePropName] : value;
return (
<div
className={classNames(
'form-info',
{
'form-info--multiline': multiline,
},
className,
)}
style={style}
>
<Typography.Paragraph ellipsis={multiline ? false : { tooltip: data }}>
{data}
</Typography.Paragraph>
</div>
);
}

export default FormInfo;

+ 13
- 5
react-ui/src/components/FullScreenFrame/index.tsx View File

@@ -2,22 +2,30 @@ import classNames from 'classnames';
import './index.less';

type FullScreenFrameProps = {
/** URL */
url: string;
/** 自定义类名 */
className?: string;
/** 自定义样式 */
style?: React.CSSProperties;
onload?: (e?: React.SyntheticEvent<HTMLIFrameElement, Event>) => void;
onerror?: (e?: React.SyntheticEvent<HTMLIFrameElement, Event>) => void;
/** 加载完成回调 */
onLoad?: (e?: React.SyntheticEvent<HTMLIFrameElement, Event>) => void;
/** 加载失败回调 */
onError?: (e?: React.SyntheticEvent<HTMLIFrameElement, Event>) => void;
};

function FullScreenFrame({ url, className, style, onload, onerror }: FullScreenFrameProps) {
/**
* 全屏 iframe,IFramePage 组件的子组件,开发中应该使用 IFramePage
*/
function FullScreenFrame({ url, className, style, onLoad, onError }: FullScreenFrameProps) {
return (
<div className={classNames('kf-full-screen-frame', className ?? '')} style={style}>
{url && (
<iframe
src={url}
className="kf-full-screen-frame__iframe"
onLoad={onload}
onError={onerror}
onLoad={onLoad}
onError={onError}
></iframe>
)}
</div>


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

@@ -1,6 +1,6 @@
import FullScreenFrame from '@/components/FullScreenFrame';
import KFSpin from '@/components/KFSpin';
// import { getLabelStudioUrl } from '@/services/developmentEnvironment';
import { getLabelStudioUrl } from '@/services/developmentEnvironment';
import { to } from '@/utils/promise';
import SessionStorage from '@/utils/sessionStorage';
import classNames from 'classnames';
@@ -12,32 +12,36 @@ export enum IframePageType {
DatasetAnnotation = 'DatasetAnnotation', // 数据标注
AppDevelopment = 'AppDevelopment', // 应用开发
DevEnv = 'DevEnv', // 开发环境
GitLink = 'GitLink',
GitLink = 'GitLink', // git link
}

const getRequestAPI = (type: IframePageType): (() => Promise<any>) => {
switch (type) {
case IframePageType.DatasetAnnotation:
return () => Promise.resolve({ code: 200, data: 'http://172.20.32.181:18888/oauth/login' }); //getLabelStudioUrl;
case IframePageType.AppDevelopment:
case IframePageType.DatasetAnnotation: // 数据标注
return getLabelStudioUrl;
case IframePageType.AppDevelopment: // 应用开发
return () => Promise.resolve({ code: 200, data: 'http://172.20.32.185:30080/' });
case IframePageType.DevEnv:
case IframePageType.DevEnv: // 开发环境
return () =>
Promise.resolve({
code: 200,
data: SessionStorage.getItem(SessionStorage.editorUrlKey) || '',
});
case IframePageType.GitLink:
case IframePageType.GitLink: // git link
return () => Promise.resolve({ code: 200, data: 'http://172.20.32.201:4000' });
}
};

type IframePageProps = {
/** 子系统 */
type: IframePageType;
/** 自定义样式类名 */
className?: string;
/** 自定义样式 */
style?: React.CSSProperties;
};

/** 系统内嵌 iframe,目前系统有数据标注、应用开发、开发环境、GitLink 四个子系统,使用时可以添加其他子系统 */
function IframePage({ type, className, style }: IframePageProps) {
const [iframeUrl, setIframeUrl] = useState('');
const [loading, setLoading] = useState(false);
@@ -66,7 +70,7 @@ function IframePage({ type, className, style }: IframePageProps) {
return (
<div className={classNames('kf-iframe-page', className)} style={style}>
{loading && createPortal(<KFSpin size="large" />, document.body)}
<FullScreenFrame url={iframeUrl} onload={hideLoading} onerror={hideLoading} />
<FullScreenFrame url={iframeUrl} onLoad={hideLoading} onError={hideLoading} />
</div>
);
}


react-ui/src/components/InfoGroupTitle/index.less → react-ui/src/components/InfoGroup/InfoGroupTitle.less View File

@@ -1,7 +1,7 @@
.kf-info-group-title {
width: 100%;
height: 56px;
padding-left: @content-padding;
padding: 0 @content-padding;
background: linear-gradient(
179.03deg,
rgba(199, 223, 255, 0.12) 0%,
@@ -21,6 +21,7 @@
color: @text-color;
font-weight: 500;
font-size: @font-size-title;
.singleLine();

&::after {
position: absolute;

react-ui/src/components/InfoGroupTitle/index.tsx → react-ui/src/components/InfoGroup/InfoGroupTitle.tsx View File

@@ -1,13 +1,19 @@
import { Flex } from 'antd';
import classNames from 'classnames';
import './index.less';
import './InfoGroupTitle.less';

type InfoGroupTitleProps = {
/** 标题 */
title: string;
/** 自定义类名 */
className?: string;
/** 自定义样式 */
style?: React.CSSProperties;
};

/**
* 信息组标题
*/
function InfoGroupTitle({ title, style, className }: InfoGroupTitleProps) {
return (
<Flex align="center" className={classNames('kf-info-group-title', className)} style={style}>

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

@@ -4,7 +4,7 @@
&__content {
padding: 20px @content-padding;
background-color: white;
border: 1px solid @border-color-base;
border: 1px solid @border-color;
border-top: none;
border-radius: 0 0 4px 4px;
}


+ 12
- 3
react-ui/src/components/InfoGroup/index.tsx View File

@@ -1,16 +1,25 @@
import classNames from 'classnames';
import InfoGroupTitle from '../InfoGroupTitle';
import InfoGroupTitle from './InfoGroupTitle';
import './index.less';

type InfoGroupProps = {
/** 标题 */
title: string;
height?: string | number; // 如果要纵向滚动,需要设置高度
width?: string | number; // 如果要横向滚动,需要设置宽度
/** 高度, 如果要纵向滚动,需要设置高度 */
height?: string | number;
/** 宽度, 如果要横向滚动,需要设置宽度 */
width?: string | number;
/** 自定义类名 */
className?: string;
/** 自定义样式 */
style?: React.CSSProperties;
/** 子元素 */
children?: React.ReactNode;
};

/**
* 信息组,用于展示基本信息,支持横向、纵向滚动。自动机器学习、超参数寻优都是使用这个组件
*/
function InfoGroup({ title, height, width, className, style, children }: InfoGroupProps) {
const contentStyle: React.CSSProperties = {};
if (height) {


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

@@ -33,7 +33,7 @@
margin-top: 20px;
margin-bottom: 30px;

&__back-btn {
&__button {
height: 32px;
}
}


+ 16
- 6
react-ui/src/components/KFEmpty/index.tsx View File

@@ -9,15 +9,24 @@ export enum EmptyType {
}

type EmptyProps = {
className?: string;
style?: React.CSSProperties;
/** 类型 */
type: EmptyType;
/** 标题 */
title?: string;
/** 内容 */
content?: string;
/** 是否有页脚,如果有默认是一个按钮 */
hasFooter?: boolean;
footer?: () => React.ReactNode;
/** 按钮标题,默认是"刷新" */
buttonTitle?: string;
onRefresh?: () => void;
/** 按钮点击回调 */
onButtonClick?: () => void;
/** 自定义页脚内容 */
footer?: () => React.ReactNode;
/** 自定义类名 */
className?: string;
/** 自定义样式 */
style?: React.CSSProperties;
};

function getEmptyImage(type: EmptyType) {
@@ -31,6 +40,7 @@ function getEmptyImage(type: EmptyType) {
}
}

/** 空状态 */
function KFEmpty({
className,
style,
@@ -40,7 +50,7 @@ function KFEmpty({
hasFooter = true,
footer,
buttonTitle = '刷新',
onRefresh,
onButtonClick,
}: EmptyProps) {
const image = getEmptyImage(type);

@@ -54,7 +64,7 @@ function KFEmpty({
{footer ? (
footer()
) : (
<Button className="kf-empty__footer__back-btn" type="primary" onClick={onRefresh}>
<Button className="kf-empty__footer__button" type="primary" onClick={onButtonClick}>
{buttonTitle}
</Button>
)}


+ 8
- 2
react-ui/src/components/KFIcon/index.tsx View File

@@ -14,14 +14,20 @@ const Icon = createFromIconfontCN({
type IconFontProps = Parameters<typeof Icon>[0];

interface KFIconProps extends IconFontProps {
/** 图标 */
type: string;
/** 字体大小 */
font?: number;
/** 字体颜色 */
color?: string;
style?: React.CSSProperties;
/** 自定义类名 */
className?: string;
/** 自定义样式 */
style?: React.CSSProperties;
}

function KFIcon({ type, font = 15, color = '', style = {}, className, ...rest }: KFIconProps) {
/** 封装 iconfont 图标 */
function KFIcon({ type, font = 15, color, className, style, ...rest }: KFIconProps) {
const iconStyle = {
...style,
fontSize: font,


react-ui/src/components/ModalTitle/index.less → react-ui/src/components/KFModal/KFModalTitle.less View File


react-ui/src/components/ModalTitle/index.tsx → react-ui/src/components/KFModal/KFModalTitle.tsx View File

@@ -6,12 +6,16 @@

import classNames from 'classnames';
import React from 'react';
import './index.less';
import './KFModalTitle.less';

type ModalTitleProps = {
/** 标题 */
title: React.ReactNode;
/** 图片 */
image?: string;
/** 自定义样式 */
style?: React.CSSProperties;
/** 自定义类名 */
className?: string;
};


+ 6
- 3
react-ui/src/components/KFModal/index.tsx View File

@@ -4,19 +4,22 @@
* @Description: 自定义 Modal
*/

import ModalTitle from '@/components/ModalTitle';
import { Modal, type ModalProps } from 'antd';
import classNames from 'classnames';
import KFModalTitle from './KFModalTitle';
import './index.less';

export interface KFModalProps extends ModalProps {
/** 标题图片 */
image?: string;
}

/** 自定义 Modal,应用中的业务 Modal 应该使用它进行封装,推荐使用函数的方式打开 */
function KFModal({
title,
image,
children,
className = '',
className,
centered,
maskClosable,
...rest
@@ -27,7 +30,7 @@ function KFModal({
{...rest}
centered={centered ?? true}
maskClosable={maskClosable ?? false}
title={<ModalTitle title={title} image={image}></ModalTitle>}
title={<KFModalTitle title={title} image={image} />}
>
{children}
</Modal>


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

@@ -8,32 +8,40 @@ import classNames from 'classnames';
import './index.less';

export type KFRadioItem = {
key: string;
title: string;
value: string;
icon?: React.ReactNode;
};

type KFRadioProps = {
/** 选项 */
items: KFRadioItem[];
/** 当前选中项 */
value?: string;
/** 自定义样式 */
style?: React.CSSProperties;
/** 自定义类名 */
className?: string;
/** 选中回调 */
onChange?: (value: string) => void;
};

/**
* 自定义 Radio
*/
function KFRadio({ items, value, style, className, onChange }: KFRadioProps) {
return (
<span className={classNames('kf-radio', className)} style={style}>
{items.map((item) => {
return (
<span
key={item.key}
key={item.value}
className={
value === item.key
value === item.value
? classNames('kf-radio__item', 'kf-radio__item--active')
: 'kf-radio__item'
}
onClick={() => onChange?.(item.key)}
onClick={() => onChange?.(item.value)}
>
{item.icon}
<span style={{ marginLeft: '5px' }}>{item.title}</span>


+ 11
- 5
react-ui/src/components/KFSpin/index.tsx View File

@@ -5,13 +5,19 @@
*/

import { Spin, SpinProps } from 'antd';
import styles from './index.less';
import './index.less';

function KFSpin(props: SpinProps) {
interface KFSpinProps extends SpinProps {
/** 加载文本 */
label: string;
}

/** 自定义 Spin */
function KFSpin({ label = '加载中', ...rest }: KFSpinProps) {
return (
<div className={styles['kf-spin']}>
<Spin {...props} />
<div className={styles['kf-spin__label']}>加载中</div>
<div className={'kf-spin'}>
<Spin {...rest} />
<div className={'kf-spin__label'}>{label}</div>
</div>
);
}


+ 0
- 19
react-ui/src/components/LabelValue/index.less View File

@@ -1,19 +0,0 @@
.kf-label-value {
display: flex;
align-items: flex-start;
font-size: 16px;
line-height: 1.6;

&__label {
flex: none;
width: 80px;
color: @text-color-secondary;
}

&__value {
flex: 1;
color: @text-color;
white-space: pre-line;
word-break: break-all;
}
}

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

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

type labelValueProps = {
label: string;
value?: any;
className?: string;
style?: React.CSSProperties;
};

function LabelValue({ label, value, className, style }: labelValueProps) {
return (
<div className={classNames('kf-label-value', className)} style={style}>
<div className="kf-label-value__label">{label}</div>
<div className="kf-label-value__value">{value ?? '--'}</div>
</div>
);
}

export default LabelValue;

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

@@ -1,5 +1,4 @@
.menu-icon-selector {
// grid 布局,每行显示 8 个图标
display: grid;
grid-template-columns: repeat(4, 80px);
gap: 20px;
@@ -10,7 +9,7 @@
display: flex;
align-items: center;
justify-content: center;
width: 80x;
width: 80px;
height: 80px;
border: 1px solid transparent;
border-radius: 4px;


+ 3
- 0
react-ui/src/components/MenuIconSelector/index.tsx View File

@@ -12,7 +12,9 @@ import { useEffect, useState } from 'react';
import styles from './index.less';

interface MenuIconSelectorProps extends Omit<ModalProps, 'onOk'> {
/** 选中的图标 */
selectedIcon?: string;
/** 选择回调 */
onOk: (param: string) => void;
}

@@ -21,6 +23,7 @@ type IconObject = {
font_class: string;
};

/** 菜单图标选择器 */
function MenuIconSelector({ open, selectedIcon, onOk, ...rest }: MenuIconSelectorProps) {
const [icons, setIcons] = useState<IconObject[]>([]);
useEffect(() => {


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

@@ -8,10 +8,17 @@ import React from 'react';
import './index.less';

type PageTitleProps = {
title: string;
/** 标题 */
title: React.ReactNode;
/** 自定义类名 */
className?: string;
/** 自定义样式 */
style?: React.CSSProperties;
};

/**
* 页面标题
*/
function PageTitle({ title, style, className = '' }: PageTitleProps) {
return (
<div className={classNames('kf-page-title', className)} style={style}>


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

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

&__placeholder {
min-height: 22px;
color: rgba(0, 0, 0, 0.25);
color: @text-placeholder-color;
font-size: @font-size-input;
line-height: 1.5714285714285714;
}
@@ -49,18 +49,31 @@
padding: 10px 11px;
font-size: @font-size-input-lg;

.parameter-input__placeholder {
.parameter-input__placeholder,
.parameter-input__content__value {
min-height: 24px;
font-size: @font-size-input-lg;
line-height: 1.5;
}

.parameter-input__content__close-icon {
font-size: 12px;
}
}

.parameter-input.parameter-input--small {
padding: 0 7px;
font-size: @font-size-input;

.parameter-input__placeholder,
.parameter-input__content__value {
font-size: @font-size-input-lg;
line-height: 1.5;
min-height: 22px;
font-size: @font-size-input;
line-height: 1.5714285714285714;
}

.parameter-input__content__close-icon {
font-size: 12px;
font-size: 10px;
}
}



+ 21
- 3
react-ui/src/components/ParameterInput/index.tsx View File

@@ -1,21 +1,22 @@
/*
* @Author: 赵伟
* @Date: 2024-04-16 08:42:57
* @Description: 参数输入组件
* @Description: 参数输入表单组件,支持手动输入,也支持选择全局参数
*/

import { CommonTabKeys } from '@/enums';
import { CloseOutlined } from '@ant-design/icons';
import { Form, Input } from 'antd';
import { RuleObject } from 'antd/es/form';
import classNames from 'classnames';
import './index.less';

// 对象
// 如果值是对象时的类型
export type ParameterInputObject = {
value?: any; // 值
showValue?: any; // 显示值
fromSelect?: boolean; // 是否来自选择
activeTab?: string; // 选择镜像、数据集、模型时,保存当前激活的tab
activeTab?: CommonTabKeys; // 选择镜像、数据集、模型时,保存当前激活的tab
expandedKeys?: string[]; // 选择镜像、数据集、模型时,保存展开的keys
checkedKeys?: string[]; // 选择镜像、数据集、模型时,保存选中的keys
[key: string]: any;
@@ -25,18 +26,34 @@ export type ParameterInputObject = {
export type ParameterInputValue = ParameterInputObject | string;

export interface ParameterInputProps {
/** 值,可以是字符串,也可以是 ParameterInputObject 对象 */
value?: ParameterInputValue;
/**
* 值变化时的回调
* @param value 值,可以是字符串,也可以是 ParameterInputObject 对象
*/
onChange?: (value?: ParameterInputValue) => void;
/** 点击时的回调 */
onClick?: () => void;
/** 删除时的回调 */
onRemove?: () => void;
/** 是否可以手动输入 */
canInput?: boolean;
/** 是否是文本框 */
textArea?: boolean;
/** 占位符 */
placeholder?: string;
/** 是否允许清除 */
allowClear?: boolean;
/** 自定义类名 */
className?: string;
/** 自定义样式 */
style?: React.CSSProperties;
/** 大小 */
size?: 'middle' | 'small' | 'large';
/** 是否禁用 */
disabled?: boolean;
/** 元素 id */
id?: string;
}

@@ -88,6 +105,7 @@ function ParameterInput({
className={classNames(
'parameter-input',
{ 'parameter-input--large': size === 'large' },
{ 'parameter-input--small': size === 'small' },
{ [`parameter-input--${status}`]: status },
className,
)}


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

@@ -1,7 +1,7 @@
/*
* @Author: 赵伟
* @Date: 2024-04-16 08:42:57
* @Description: 参数选择组件
* @Description: 参数下拉选择组件,支持资源规格、数据集、模型、服务
*/

import { PipelineNodeModelParameter } from '@/types';


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

@@ -9,30 +9,71 @@ import ResourceSelectorModal, {
ResourceSelectorResponse,
ResourceSelectorType,
selectorTypeConfig,
} from '@/pages/Pipeline/components/ResourceSelectorModal';
} from '@/components/ResourceSelectorModal';
import { openAntdModal } from '@/utils/modal';
import { Button } from 'antd';
import { useState } from 'react';
import classNames from 'classnames';
import { pick } from 'lodash';
import { useEffect, useState } from 'react';
import ParameterInput, { type ParameterInputProps } from '../ParameterInput';
import './index.less';

export { requiredValidator, type ParameterInputObject } from '../ParameterInput';
export {
requiredValidator,
type ParameterInputObject,
type ParameterInputValue,
} from '../ParameterInput';
export { ResourceSelectorType, selectorTypeConfig, type ResourceSelectorResponse };

type ResourceSelectProps = {
interface ResourceSelectProps extends ParameterInputProps {
/** 类型,数据集、模型、镜像 */
type: ResourceSelectorType;
} & ParameterInputProps;
}

// 获取选择数据集、模型后面按钮 icon
// 获取选择数据集、模型、镜像后面按钮 icon
const getSelectBtnIcon = (type: ResourceSelectorType) => {
return <KFIcon type={selectorTypeConfig[type].buttonIcon} font={16} />;
};

function ResourceSelect({ type, value, onChange, disabled, ...rest }: ResourceSelectProps) {
/** 数据集、模型、镜像选择表单组件 */
function ResourceSelect({
type,
value,
size,
disabled,
className,
style,
onChange,
...rest
}: ResourceSelectProps) {
const [selectedResource, setSelectedResource] = useState<ResourceSelectorResponse | undefined>(
undefined,
);

useEffect(() => {
if (
value &&
typeof value === 'object' &&
value.activeTab &&
value.id &&
value.name &&
value.version &&
value.path &&
(type === ResourceSelectorType.Mirror || (value.identifier && value.owner))
) {
const originResource = pick(value, [
'activeTab',
'id',
'identifier',
'name',
'owner',
'version',
'path',
]) as ResourceSelectorResponse;
setSelectedResource(originResource);
}
}, [value]);

const selectResource = () => {
const resource = selectedResource;
const { close } = openAntdModal(ResourceSelectorModal, {
@@ -50,8 +91,10 @@ function ResourceSelect({ type, value, onChange, disabled, ...rest }: ResourceSe
showValue: path,
fromSelect: true,
activeTab,
expandedKeys: [`${id}`],
checkedKeys: [`${id}-${version}`],
id,
name,
version,
path,
});
} else {
const jsonObj = {
@@ -69,8 +112,6 @@ function ResourceSelect({ type, value, onChange, disabled, ...rest }: ResourceSe
showValue,
fromSelect: true,
activeTab,
expandedKeys: [`${id}`],
checkedKeys: [`${id}-${version}`],
...jsonObj,
});
}
@@ -80,8 +121,6 @@ function ResourceSelect({ type, value, onChange, disabled, ...rest }: ResourceSe
showValue: undefined,
fromSelect: false,
activeTab: undefined,
expandedKeys: [],
checkedKeys: [],
});
}
close();
@@ -90,18 +129,19 @@ function ResourceSelect({ type, value, onChange, disabled, ...rest }: ResourceSe
};

return (
<div className="kf-resource-select">
<div className={classNames('kf-resource-select', className)} style={style}>
<ParameterInput
{...rest}
disabled={disabled}
value={value}
size={size}
onChange={onChange}
onRemove={() => setSelectedResource(undefined)}
onClick={selectResource}
></ParameterInput>
<Button
className="kf-resource-select__button"
size="large"
size={size}
type="link"
icon={getSelectBtnIcon(type)}
disabled={disabled}


react-ui/src/pages/Pipeline/components/ResourceSelectorModal/config.tsx → react-ui/src/components/ResourceSelectorModal/config.tsx View File


react-ui/src/pages/Pipeline/components/ResourceSelectorModal/index.less → react-ui/src/components/ResourceSelectorModal/index.less View File

@@ -22,8 +22,8 @@
height: 398px;
margin-right: 15px;
padding: 15px;
background-color: @background-color-primary;
border: 1px solid @border-color;
background-color: rgba(22, 100, 255, 0.03);
border: 1px solid rgba(22, 100, 255, 0.3);
border-radius: 8px;

&__search {
@@ -31,7 +31,7 @@
padding-left: 0;
background-color: transparent;
border-width: 0;
border-bottom: 1px solid @border-color-secondary;
border-bottom: 1px solid rgba(22, 100, 255, 0.1);
border-radius: 0;
}

@@ -45,8 +45,8 @@
width: calc(100% - 488px - 15px);
height: 398px;
padding: 15px;
background-color: @background-color-primary;
border: 1px solid @border-color;
background-color: rgba(22, 100, 255, 0.03);
border: 1px solid rgba(22, 100, 255, 0.3);
border-radius: 8px;

&__title {
@@ -56,7 +56,7 @@
color: @text-color;
font-size: @font-size;
line-height: 46px;
border-bottom: 1px solid @border-color-secondary;
border-bottom: 1px solid rgba(22, 100, 255, 0.1);
}
&__files {
height: calc(100% - 75px);
@@ -68,7 +68,7 @@
color: @text-color-secondary;
font-size: 13px;
word-break: break-all;
background: @background-color-gray;
background: rgba(4, 3, 3, 0.06);
border-radius: 4px;
}
}

react-ui/src/pages/Pipeline/components/ResourceSelectorModal/index.tsx → react-ui/src/components/ResourceSelectorModal/index.tsx View File

@@ -4,11 +4,11 @@
* @Description: 选择数据集、模型、镜像
*/

import KFIcon from '@/components/KFIcon';
import KFModal from '@/components/KFModal';
import { CommonTabKeys } from '@/enums';
import { ResourceFileData } from '@/pages/Dataset/config';
import { to } from '@/utils/promise';
import { Icon } from '@umijs/max';
import type { GetRef, ModalProps, TreeDataNode, TreeProps } from 'antd';
import { Input, Tabs, Tree } from 'antd';
import React, { useEffect, useMemo, useRef, useState } from 'react';
@@ -16,22 +16,30 @@ import { ResourceSelectorType, selectorTypeConfig } from './config';
import styles from './index.less';
export { ResourceSelectorType, selectorTypeConfig };

// 选择数据集\模型\镜像的返回类型
// 选择数据集、模型、镜像的返回类型
export type ResourceSelectorResponse = {
activeTab: CommonTabKeys; // 是我的还是公开的
id: string; // 数据集\模型\镜像 id
name: string; // 数据集\模型\镜像 name
version: string; // 数据集\模型\镜像版本
path: string; // 数据集\模型\镜像版本路径
identifier: string; // 数据集\模型 identifier
owner: string; // 数据集\模型 owner
activeTab: CommonTabKeys; // 是我的还是公开的
identifier: string; // 数据集\模型 identifier,镜像这个字段为空
owner: string; // 数据集\模型 owner,镜像这个字段为空
};

export interface ResourceSelectorModalProps extends Omit<ModalProps, 'onOk'> {
type: ResourceSelectorType; // 数据集\模型\镜像
/** 类型,数据集、模型、镜像 */
type: ResourceSelectorType;
/** 默认展开的节点 */
defaultExpandedKeys?: React.Key[];
/** 默认展开的节点 */
defaultCheckedKeys?: React.Key[];
/** 默认激活的 Tab */
defaultActiveTab?: CommonTabKeys;
/**
* 确认回调
* @param params 选择的数据
*/
onOk?: (params: ResourceSelectorResponse | undefined) => void;
}

@@ -61,6 +69,7 @@ const getIdAndVersion = (versionKey: string) => {
};
};

/** 选择数据集、模型、镜像的弹框,推荐使用函数的方式打开 */
function ResourceSelectorModal({
type,
defaultExpandedKeys = [],
@@ -69,7 +78,7 @@ function ResourceSelectorModal({
onOk,
...rest
}: ResourceSelectorModalProps) {
const [activeTab, setActiveTab] = useState<string>(defaultActiveTab);
const [activeTab, setActiveTab] = useState<CommonTabKeys>(defaultActiveTab);
const [expandedKeys, setExpandedKeys] = useState<React.Key[]>([]);
const [checkedKeys, setCheckedKeys] = useState<React.Key[]>([]);
const [loadedKeys, setLoadedKeys] = useState<React.Key[]>([]);
@@ -234,7 +243,7 @@ function ResourceSelectorModal({
version,
identifier,
owner,
activeTab: activeTab as CommonTabKeys,
activeTab: activeTab,
};
onOk?.(res);
} else {
@@ -255,7 +264,7 @@ function ResourceSelectorModal({
<Tabs
activeKey={activeTab}
items={tabItems}
onChange={setActiveTab}
onChange={(e) => setActiveTab(e as CommonTabKeys)}
className={styles['model-tabs']}
/>
<div className={styles['model-selector']}>
@@ -267,7 +276,14 @@ function ResourceSelectorModal({
variant="borderless"
value={searchText}
onChange={(e) => setSearchText(e.target.value)}
prefix={<Icon icon="local:magnifying-glass" style={{ height: '15px' }} />}
prefix={
<KFIcon
type="icon-sousuo"
color="rgba(22,100,255,0.4)"
style={{ height: '15px' }}
/>
}
// prefix={<Icon icon="local:magnifying-glass" style={{ height: '15px' }} />}
/>
<Tree
ref={treeRef}

+ 7
- 5
react-ui/src/components/RightContent/AvatarDropdown.tsx View File

@@ -1,6 +1,8 @@
import { clearSessionToken } from '@/access';
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 { LogoutOutlined, UserOutlined } from '@ant-design/icons';
import { setAlpha } from '@ant-design/pro-components';
@@ -64,11 +66,11 @@ const AvatarDropdown: React.FC<GlobalHeaderRightProps> = ({ menu }) => {
clearSessionToken();
setRemoteMenu(null);
gotoLoginPage();
// const clientInfo: ClientInfo = SessionStorage.getItem(SessionStorage.clientInfoKey, true);
// if (clientInfo) {
// const { logoutUri } = clientInfo;
// location.replace(logoutUri);
// }
const clientInfo: ClientInfo = SessionStorage.getItem(SessionStorage.clientInfoKey, true);
if (clientInfo) {
const { logoutUri } = clientInfo;
location.replace(logoutUri);
}
};
const actionClassName = useEmotionCss(({ token }) => {
return {


+ 2
- 5
react-ui/src/components/RightContent/index.tsx View File

@@ -1,9 +1,8 @@
import { useModel } from '@umijs/max';
import React from 'react';
// import KFBreadcrumb from '../KFBreadcrumb';
import KFIcon from '@/components/KFIcon';
import { ProBreadcrumb } from '@ant-design/pro-components';
import { useModel } from '@umijs/max';
import { Button } from 'antd';
import React from 'react';
import Avatar from './AvatarDropdown';
import styles from './index.less';
// import { SelectLang } from '@umijs/max';
@@ -44,8 +43,6 @@ const GlobalHeaderRight: React.FC = () => {

<ProBreadcrumb></ProBreadcrumb>

{/* <KFBreadcrumb /> */}

<Avatar menu={true} />
{/* <SelectLang className={actionClassName} /> */}
</div>


+ 9
- 2
react-ui/src/components/SubAreaTitle/index.tsx View File

@@ -8,13 +8,20 @@ import classNames from 'classnames';
import './index.less';

type SubAreaTitleProps = {
/** 标题 */
title: string;
/** 图片 */
image?: string;
style?: React.CSSProperties;
/** 自定义类名 */
className?: string;
/** 自定义样式 */
style?: React.CSSProperties;
};

function SubAreaTitle({ title, image, style, className }: SubAreaTitleProps) {
/**
* 表单或者详情页的分区标题
*/
function SubAreaTitle({ title, image, className, style }: SubAreaTitleProps) {
return (
<div className={classNames('kf-subarea-title', className)} style={style}>
{image && (


+ 11
- 0
react-ui/src/enums/index.ts View File

@@ -118,3 +118,14 @@ export const autoMLResamplingStrategyOptions = [
{ label: 'holdout', value: AutoMLResamplingStrategy.Holdout },
{ label: 'crossValid', value: AutoMLResamplingStrategy.CrossValid },
];

// 超参数自动寻优优化方向
export enum hyperParameterOptimizedMode {
Min = 'min',
Max = 'max',
}

export const hyperParameterOptimizedModeOptions = [
{ label: '越大越好', value: hyperParameterOptimizedMode.Max },
{ label: '越小越好', value: hyperParameterOptimizedMode.Min },
];

+ 1
- 1
react-ui/src/global.less View File

@@ -5,7 +5,7 @@ body,
height: 100%;
margin: 0;
padding: 0;
overflow-y: hidden;
overflow-y: visible;
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, 'Helvetica Neue', Arial,
'Noto Sans', sans-serif, 'Apple Color Emoji', 'Segoe UI Emoji', 'Segoe UI Symbol',
'Noto Color Emoji';


+ 1
- 1
react-ui/src/iconfont/iconfont-menu.json View File

@@ -1,6 +1,6 @@
{
"id": "4511326",
"name": "复杂智能软件-导航",
"name": "智能材料科研平台-导航",
"font_family": "iconfont",
"css_prefix_text": "icon-",
"description": "",


+ 1
- 1
react-ui/src/iconfont/iconfont.js
File diff suppressed because it is too large
View File


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

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

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

+ 1
- 1
react-ui/src/pages/404.tsx View File

@@ -12,7 +12,7 @@ const NoFoundPage = () => {
content={'很抱歉,您访问的页面地址有误,\n或者该页面不存在。'}
hasFooter={true}
buttonTitle="返回首页"
onRefresh={() => navigate('/')}
onButtonClick={() => navigate('/')}
></KFEmpty>
);
};


+ 1
- 1
react-ui/src/pages/AutoML/Create/index.less View File

@@ -33,7 +33,7 @@
}

.ant-btn-variant-text:disabled {
color: rgba(0, 0, 0, 0.25);
color: @text-disabled-color;
}

.ant-btn-variant-text {


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

@@ -1,7 +1,7 @@
/*
* @Author: 赵伟
* @Date: 2024-04-16 13:58:08
* @Description: 创建服务版本
* @Description: 创建实验
*/
import PageTitle from '@/components/PageTitle';
import { AutoMLEnsembleClass, AutoMLTaskType } from '@/enums';
@@ -11,7 +11,6 @@ import { safeInvoke } from '@/utils/functional';
import { to } from '@/utils/promise';
import { useLocation, useNavigate, useParams } from '@umijs/max';
import { App, Button, Form } from 'antd';
import { omit } from 'lodash';
import { useEffect } from 'react';
import BasicConfig from '../components/CreateForm/BasicConfig';
import DatasetConfig from '../components/CreateForm/DatasetConfig';
@@ -106,7 +105,7 @@ function CreateAutoML() {

// 根据后台要求,修改表单数据
const object = {
...omit(formData),
...formData,
include_classifier: convertEmptyStringToUndefined(include_classifier),
include_feature_preprocessor: convertEmptyStringToUndefined(include_feature_preprocessor),
include_regressor: convertEmptyStringToUndefined(include_regressor),
@@ -191,7 +190,7 @@ function CreateAutoML() {
<TrialConfig />
<DatasetConfig />

<Form.Item wrapperCol={{ offset: 0, span: 16 }}>
<Form.Item wrapperCol={{ offset: 0, span: 16 }} style={{ marginTop: '40px' }}>
<Button type="primary" htmlType="submit">
{buttonText}
</Button>


+ 0
- 16
react-ui/src/pages/AutoML/Info/index.tsx View File

@@ -3,9 +3,7 @@
* @Date: 2024-04-16 13:58:08
* @Description: 自主机器学习详情
*/
import KFIcon from '@/components/KFIcon';
import PageTitle from '@/components/PageTitle';
import { CommonTabKeys } from '@/enums';
import { getAutoMLInfoReq } from '@/services/autoML';
import { safeInvoke } from '@/utils/functional';
import { to } from '@/utils/promise';
@@ -16,24 +14,10 @@ import { AutoMLData } from '../types';
import styles from './index.less';

function AutoMLInfo() {
const [activeTab, setActiveTab] = useState<string>(CommonTabKeys.Public);
const params = useParams();
const autoMLId = safeInvoke(Number)(params.id);
const [autoMLInfo, setAutoMLInfo] = useState<AutoMLData | undefined>(undefined);

const tabItems = [
{
key: CommonTabKeys.Public,
label: '基本信息',
icon: <KFIcon type="icon-jibenxinxi" />,
},
{
key: CommonTabKeys.Private,
label: 'Trial列表',
icon: <KFIcon type="icon-Trialliebiao" />,
},
];

useEffect(() => {
if (autoMLId) {
getAutoMLInfo();


+ 3
- 411
react-ui/src/pages/AutoML/List/index.tsx View File

@@ -3,419 +3,11 @@
* @Date: 2024-04-16 13:58:08
* @Description: 自主机器学习列表
*/
import KFIcon from '@/components/KFIcon';
import PageTitle from '@/components/PageTitle';
import { ExperimentStatus } from '@/enums';
import { useCacheState } from '@/hooks/pageCacheState';
import { experimentStatusInfo } from '@/pages/Experiment/status';
import {
deleteAutoMLReq,
getAutoMLListReq,
getExperimentInsListReq,
runAutoMLReq,
} from '@/services/autoML';
import themes from '@/styles/theme.less';
import { type ExperimentInstance as ExperimentInstanceData } from '@/types';
import { to } from '@/utils/promise';
import tableCellRender, { TableCellValueType } from '@/utils/table';
import { modalConfirm } from '@/utils/ui';
import { useNavigate } from '@umijs/max';
import {
App,
Button,
ConfigProvider,
Input,
Table,
Tooltip,
type TablePaginationConfig,
type TableProps,
} from 'antd';
import { type SearchProps } from 'antd/es/input';
import classNames from 'classnames';
import { useEffect, useState } from 'react';
import ExperimentInstance from '../components/ExperimentInstance';
import { AutoMLData } from '../types';
import styles from './index.less';

function AutoMLList() {
const navigate = useNavigate();
const { message } = App.useApp();
const [cacheState, setCacheState] = useCacheState();
const [searchText, setSearchText] = useState(cacheState?.searchText);
const [inputText, setInputText] = useState(cacheState?.searchText);
const [tableData, setTableData] = useState<AutoMLData[]>([]);
const [total, setTotal] = useState(0);
const [experimentInsList, setExperimentInsList] = useState<ExperimentInstanceData[]>([]);
const [expandedRowKeys, setExpandedRowKeys] = useState<number[]>([]);
const [experimentInsTotal, setExperimentInsTotal] = useState(0);
const [pagination, setPagination] = useState<TablePaginationConfig>(
cacheState?.pagination ?? {
current: 1,
pageSize: 10,
},
);

useEffect(() => {
getAutoMLList();
}, [pagination, searchText]);

// 获取自主机器学习列表
const getAutoMLList = async () => {
const params: Record<string, any> = {
page: pagination.current! - 1,
size: pagination.pageSize,
ml_name: searchText || undefined,
};
const [res] = await to(getAutoMLListReq(params));
if (res && res.data) {
const { content = [], totalElements = 0 } = res.data;
setTableData(content);
setTotal(totalElements);
}
};

// 搜索
const onSearch: SearchProps['onSearch'] = (value) => {
setSearchText(value);
setPagination((prev) => ({
...prev,
current: 1,
}));
};

// 删除模型部署
const deleteAutoML = async (record: AutoMLData) => {
const [res] = await to(deleteAutoMLReq(record.id));
if (res) {
message.success('删除成功');
// 如果是一页的唯一数据,删除时,请求第一页的数据
// 否则直接刷新这一页的数据
// 避免回到第一页
if (tableData.length > 1) {
setPagination((prev) => ({
...prev,
current: 1,
}));
} else {
getAutoMLList();
}
}
};

// 处理删除
const handleAutoMLDelete = (record: AutoMLData) => {
modalConfirm({
title: '删除后,该实验将不可恢复',
content: '是否确认删除?',
onOk: () => {
deleteAutoML(record);
},
});
};

// 创建、编辑、复制自动机器学习
const createAutoML = (record?: AutoMLData, isCopy: boolean = false) => {
setCacheState({
pagination,
searchText,
});

if (record) {
if (isCopy) {
navigate(`/pipeline/autoML/copy/${record.id}`);
} else {
navigate(`/pipeline/autoML/edit/${record.id}`);
}
} else {
navigate(`/pipeline/autoML/create`);
}
};

// 查看自动机器学习详情
const gotoDetail = (record: AutoMLData) => {
setCacheState({
pagination,
searchText,
});

navigate(`/pipeline/autoML/info/${record.id}`);
};

// 启动自动机器学习
const startAutoML = async (record: AutoMLData) => {
const [res] = await to(runAutoMLReq(record.id));
if (res) {
message.success('运行成功');
setExpandedRowKeys([record.id]);
refreshExperimentList();
refreshExperimentIns(record.id);
}
};
import ExperimentList, { ExperimentListType } from '../components/ExperimentList';

// --------------------------- 实验实例 ---------------------------
// 获取实验实例列表
const getExperimentInsList = async (autoMLId: number, page: number) => {
const params = {
autoMlId: autoMLId,
page: page,
size: 5,
};
const [res] = await to(getExperimentInsListReq(params));
if (res && res.data) {
const { content = [], totalElements = 0 } = res.data;
try {
if (page === 0) {
setExperimentInsList(content);
} else {
setExperimentInsList((prev) => [...prev, ...content]);
}
setExperimentInsTotal(totalElements);
} catch (error) {
console.error('JSON parse error: ', error);
}
}
};
// 展开实例
const handleExpandChange = (expanded: boolean, record: AutoMLData) => {
setExperimentInsList([]);
if (expanded) {
setExpandedRowKeys([record.id]);
getExperimentInsList(record.id, 0);
} else {
setExpandedRowKeys([]);
}
};

// 跳转到实验实例详情
const gotoInstanceInfo = (autoML: AutoMLData, record: ExperimentInstanceData) => {
navigate({ pathname: `/pipeline/automl/instance/${autoML.id}/${record.id}` });
};

// 刷新实验实例列表
const refreshExperimentIns = (experimentId: number) => {
getExperimentInsList(experimentId, 0);
};

// 加载更多实验实例
const loadMoreExperimentIns = () => {
const page = Math.round(experimentInsList.length / 5);
const autoMLId = expandedRowKeys[0];
getExperimentInsList(autoMLId, page);
};

// 实验实例终止
const handleInstanceTerminate = async (experimentIns: ExperimentInstanceData) => {
// 刷新实验列表
refreshExperimentList();
setExperimentInsList((prevList) => {
return prevList.map((item) => {
if (item.id === experimentIns.id) {
return {
...item,
status: ExperimentStatus.Terminated,
};
}
return item;
});
});
};

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

// --------------------------- Table ---------------------------
// 分页切换
const handleTableChange: TableProps<AutoMLData>['onChange'] = (
pagination,
_filters,
_sorter,
{ action },
) => {
if (action === 'paginate') {
setPagination(pagination);
}
};

const columns: TableProps<AutoMLData>['columns'] = [
{
title: '实验名称',
dataIndex: 'ml_name',
key: 'ml_name',
width: '16%',
render: tableCellRender(false, TableCellValueType.Link, {
onClick: gotoDetail,
}),
},
{
title: '实验描述',
dataIndex: 'ml_description',
key: 'ml_description',
render: tableCellRender(true),
ellipsis: { showTitle: false },
},

{
title: '创建时间',
dataIndex: 'update_time',
key: 'update_time',
width: '20%',
render: tableCellRender(true, TableCellValueType.Date),
ellipsis: { showTitle: false },
},
{
title: '最近五次运行状态',
dataIndex: 'status_list',
key: 'status_list',
width: 200,
render: (text) => {
const newText: string[] = text && text.replace(/\s+/g, '').split(',');
return (
<>
{newText && newText.length > 0
? newText.map((item, index) => {
return (
<Tooltip
key={index}
placement="top"
title={experimentStatusInfo[item as ExperimentStatus].label}
>
<img
style={{ width: '17px', marginRight: '6px' }}
src={experimentStatusInfo[item as ExperimentStatus].icon}
draggable={false}
alt=""
/>
</Tooltip>
);
})
: null}
</>
);
},
},
{
title: '操作',
dataIndex: 'operation',
width: 360,
key: 'operation',
render: (_: any, record: AutoMLData) => (
<div>
<Button
type="link"
size="small"
key="start"
icon={<KFIcon type="icon-yunhang" />}
onClick={() => startAutoML(record)}
>
运行
</Button>
<Button
type="link"
size="small"
key="edit"
icon={<KFIcon type="icon-bianji" />}
onClick={() => createAutoML(record, false)}
>
编辑
</Button>
<Button
type="link"
size="small"
key="copy"
icon={<KFIcon type="icon-fuzhi" />}
onClick={() => createAutoML(record, true)}
>
复制
</Button>

<ConfigProvider
theme={{
token: {
colorLink: themes['warningColor'],
},
}}
>
<Button
type="link"
size="small"
key="remove"
icon={<KFIcon type="icon-shanchu" />}
onClick={() => handleAutoMLDelete(record)}
>
删除
</Button>
</ConfigProvider>
</div>
),
},
];

return (
<div className={styles['auto-ml-list']}>
<PageTitle title="自动机器学习列表"></PageTitle>
<div className={styles['auto-ml-list__content']}>
<div className={styles['auto-ml-list__content__filter']}>
<Input.Search
placeholder="按实验名称筛选"
onSearch={onSearch}
onChange={(e) => setInputText(e.target.value)}
style={{ width: 300 }}
value={inputText}
allowClear
/>
<Button
style={{ marginLeft: '20px' }}
type="default"
onClick={() => createAutoML()}
icon={<KFIcon type="icon-xinjian2" />}
>
新建实验
</Button>
</div>
<div
className={classNames('vertical-scroll-table', styles['auto-ml-list__content__table'])}
>
<Table
dataSource={tableData}
columns={columns}
scroll={{ y: 'calc(100% - 55px)' }}
pagination={{
...pagination,
total: total,
showSizeChanger: true,
showQuickJumper: true,
showTotal: () => `共${total}条`,
}}
onChange={handleTableChange}
expandable={{
expandedRowRender: (record) => (
<ExperimentInstance
experimentInsList={experimentInsList}
experimentInsTotal={experimentInsTotal}
onClickInstance={(item) => gotoInstanceInfo(record, item)}
onRemove={() => {
refreshExperimentIns(record.id);
refreshExperimentList();
}}
onTerminate={handleInstanceTerminate}
onLoadMore={() => loadMoreExperimentIns()}
></ExperimentInstance>
),
onExpand: (e, a) => {
handleExpandChange(e, a);
},
expandedRowKeys: expandedRowKeys,
rowExpandable: () => true,
}}
rowKey="id"
/>
</div>
</div>
</div>
);
function AutoMLList() {
return <ExperimentList type={ExperimentListType.AutoML} />;
}

export default AutoMLList;

+ 6
- 51
react-ui/src/pages/AutoML/components/AutoMLBasic/index.tsx View File

@@ -1,28 +1,16 @@
import ConfigInfo, { type BasicInfoData } from '@/components/ConfigInfo';
import { AutoMLTaskType, autoMLEnsembleClassOptions, autoMLTaskTypeOptions } from '@/enums';
import { AutoMLData } from '@/pages/AutoML/types';
import { experimentStatusInfo } from '@/pages/Experiment/status';
import { type NodeStatus } from '@/types';
import { parseJsonText } from '@/utils';
import { elapsedTime } from '@/utils/date';
import { formatBoolean, formatDataset, formatDate, formatEnum } from '@/utils/format';
import { Flex } from 'antd';
import classNames from 'classnames';
import { useMemo } from 'react';
import ConfigInfo, {
formatBoolean,
formatDate,
formatEnum,
type BasicInfoData,
} from '../ConfigInfo';
import styles from './index.less';

// 格式化数据集
const formatDataset = (dataset: { name: string; version: string }) => {
if (!dataset || !dataset.name || !dataset.version) {
return '--';
}
return `${dataset.name}:${dataset.version}`;
};

// 格式化优化方向
const formatOptimizeMode = (value: boolean) => {
return value ? '越大越好' : '越小越好';
@@ -58,28 +46,23 @@ function AutoMLBasic({ info, className, runStatus, isInstance = false }: AutoMLB
{
label: '实验名称',
value: info.ml_name,
ellipsis: true,
},
{
label: '实验描述',
value: info.ml_description,
ellipsis: true,
},
{
label: '创建人',
value: info.create_by,
ellipsis: true,
},
{
label: '创建时间',
value: info.create_time,
ellipsis: true,
format: formatDate,
},
{
label: '更新时间',
value: info.update_time,
ellipsis: true,
format: formatDate,
},
];
@@ -93,18 +76,15 @@ function AutoMLBasic({ info, className, runStatus, isInstance = false }: AutoMLB
{
label: '任务类型',
value: info.task_type,
ellipsis: true,
format: formatEnum(autoMLTaskTypeOptions),
},
{
label: '特征预处理算法',
value: info.include_feature_preprocessor,
ellipsis: true,
},
{
label: '排除的特征预处理算法',
value: info.exclude_feature_preprocessor,
ellipsis: true,
},
{
label: info.task_type === AutoMLTaskType.Regression ? '回归算法' : '分类算法',
@@ -112,7 +92,6 @@ function AutoMLBasic({ info, className, runStatus, isInstance = false }: AutoMLB
info.task_type === AutoMLTaskType.Regression
? info.include_regressor
: info.include_classifier,
ellipsis: true,
},
{
label: info.task_type === AutoMLTaskType.Regression ? '排除的回归算法' : '排除的分类算法',
@@ -120,91 +99,73 @@ function AutoMLBasic({ info, className, runStatus, isInstance = false }: AutoMLB
info.task_type === AutoMLTaskType.Regression
? info.exclude_regressor
: info.exclude_classifier,
ellipsis: true,
},
{
label: '集成方式',
value: info.ensemble_class,
ellipsis: true,
format: formatEnum(autoMLEnsembleClassOptions),
},
{
label: '集成模型数量',
value: info.ensemble_size,
ellipsis: true,
},
{
label: '集成最佳模型数量',
value: info.ensemble_nbest,
ellipsis: true,
},
{
label: '最大数量',
value: info.max_models_on_disc,
ellipsis: true,
},
{
label: '内存限制(MB)',
value: info.memory_limit,
ellipsis: true,
},
{
label: '单次时间限制(秒)',
value: info.per_run_time_limit,
ellipsis: true,
},
{
label: '搜索时间限制(秒)',
value: info.time_left_for_this_task,
ellipsis: true,
},
{
label: '重采样策略',
value: info.resampling_strategy,
ellipsis: true,
},
{
label: '交叉验证折数',
value: info.folds,
ellipsis: true,
},
{
label: '是否打乱',
value: info.shuffle,
ellipsis: true,
format: formatBoolean,
},
{
label: '训练集比率',
value: info.train_size,
ellipsis: true,
},
{
label: '测试集比率',
value: info.test_size,
ellipsis: true,
},
{
label: '计算指标',
value: info.scoring_functions,
ellipsis: true,
},
{
label: '随机种子',
value: info.seed,
ellipsis: true,
},

{
label: '数据集',
value: info.dataset,
ellipsis: true,
format: formatDataset,
},
{
label: '预测目标列',
value: info.target_columns,
ellipsis: true,
},
];
}, [info]);
@@ -217,18 +178,15 @@ function AutoMLBasic({ info, className, runStatus, isInstance = false }: AutoMLB
{
label: '指标名称',
value: info.metric_name,
ellipsis: true,
},
{
label: '优化方向',
value: info.greater_is_better,
ellipsis: true,
format: formatOptimizeMode,
},
{
label: '指标权重',
value: info.metrics,
ellipsis: true,
format: formatMetricsWeight,
},
];
@@ -243,12 +201,10 @@ function AutoMLBasic({ info, className, runStatus, isInstance = false }: AutoMLB
{
label: '启动时间',
value: formatDate(runStatus.startedAt),
ellipsis: true,
},
{
label: '执行时长',
value: elapsedTime(runStatus.startedAt, runStatus.finishedAt),
ellipsis: true,
},
{
label: '状态',
@@ -271,7 +227,6 @@ function AutoMLBasic({ info, className, runStatus, isInstance = false }: AutoMLB
</div>
</Flex>
),
ellipsis: true,
},
];
}, [runStatus]);
@@ -281,7 +236,7 @@ function AutoMLBasic({ info, className, runStatus, isInstance = false }: AutoMLB
{isInstance && runStatus && (
<ConfigInfo
title="运行信息"
data={instanceDatas}
datas={instanceDatas}
labelWidth={70}
style={{ marginBottom: '20px' }}
/>
@@ -289,18 +244,18 @@ function AutoMLBasic({ info, className, runStatus, isInstance = false }: AutoMLB
{!isInstance && (
<ConfigInfo
title="基本信息"
data={basicDatas}
datas={basicDatas}
labelWidth={70}
style={{ marginBottom: '20px' }}
/>
)}
<ConfigInfo
title="配置信息"
data={configDatas}
datas={configDatas}
labelWidth={150}
style={{ marginBottom: '20px' }}
/>
<ConfigInfo title="优化指标" data={metricsData} labelWidth={70} />
<ConfigInfo title="优化指标" datas={metricsData} labelWidth={70} />
</div>
);
}


+ 0
- 20
react-ui/src/pages/AutoML/components/ConfigInfo/index.less View File

@@ -1,20 +0,0 @@
.config-info {
:global {
.kf-basic-info {
width: 100%;

&__item {
width: calc((100% - 80px) / 3);
&__label {
font-size: @font-size;
text-align: left;
text-align-last: left;
}
&__value {
min-width: 0;
font-size: @font-size;
}
}
}
}
}

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

Loading…
Cancel
Save