| @@ -1,6 +1,8 @@ | |||||
| #!/bin/bash | #!/bin/bash | ||||
| baseDir="/home/somuns/ci4s" | baseDir="/home/somuns/ci4s" | ||||
| #判断$1是否为all,如果是,则编译所有模块,否则只编译management-platform模块 | #判断$1是否为all,如果是,则编译所有模块,否则只编译management-platform模块 | ||||
| if [ "$1" == "all" ]; then | if [ "$1" == "all" ]; then | ||||
| buildDir=$baseDir | buildDir=$baseDir | ||||
| @@ -3,12 +3,13 @@ | |||||
| baseDir="/home/somuns/ci4s" | baseDir="/home/somuns/ci4s" | ||||
| cd ${baseDir}/react-ui | cd ${baseDir}/react-ui | ||||
| npm install | |||||
| if [ $? -ne 0 ]; then | |||||
| echo "Failed to install npm depend package" | |||||
| exit 1 | |||||
| fi | |||||
| #npm install -g npm@10.9.0 | |||||
| #npm install | |||||
| # | |||||
| #if [ $? -ne 0 ]; then | |||||
| # echo "Failed to install npm depend package" | |||||
| # exit 1 | |||||
| #fi | |||||
| npm run build | npm run build | ||||
| @@ -43,18 +43,23 @@ cd ${baseDir} | |||||
| # 拉取指定分支的最新代码 | # 拉取指定分支的最新代码 | ||||
| echo "Checking out and pulling branch $branch..." | echo "Checking out and pulling branch $branch..." | ||||
| git stash | |||||
| git checkout $branch | git checkout $branch | ||||
| if [ $? -ne 0 ]; then | if [ $? -ne 0 ]; then | ||||
| echo "切换到分支 $branch 失败,请检查分支名称是否正确!" | echo "切换到分支 $branch 失败,请检查分支名称是否正确!" | ||||
| exit 1 | exit 1 | ||||
| fi | fi | ||||
| git stash | |||||
| git pull origin $branch | git pull origin $branch | ||||
| if [ $? -ne 0 ]; then | if [ $? -ne 0 ]; then | ||||
| echo "拉取代码失败,请检查网络或联系管理员!" | echo "拉取代码失败,请检查网络或联系管理员!" | ||||
| exit 1 | exit 1 | ||||
| fi | fi | ||||
| chmod +777 ${baseDir}/k8s/*.sh | |||||
| # 创建目录 | # 创建目录 | ||||
| mkdir -p ${baseDir}/k8s/dockerfiles/jar | mkdir -p ${baseDir}/k8s/dockerfiles/jar | ||||
| mkdir -p ${baseDir}/k8s/dockerfiles/html | mkdir -p ${baseDir}/k8s/dockerfiles/html | ||||
| @@ -134,7 +139,7 @@ fi | |||||
| if [ "$service" == "all" ]; then | if [ "$service" == "all" ]; then | ||||
| # 编译前端 | # 编译前端 | ||||
| compile_front | |||||
| # compile_front | |||||
| # 编译java | # 编译java | ||||
| compile_java "all" | compile_java "all" | ||||
| @@ -143,7 +143,7 @@ fi | |||||
| if [ "$service" == "all" ]; then | if [ "$service" == "all" ]; then | ||||
| #部署前端 | #部署前端 | ||||
| build_and_deploy "nginx-dockerfile" "172.20.32.187/ci4s/ci4s-front:${tag}" "k8s-12front.yaml" | |||||
| # build_and_deploy "nginx-dockerfile" "172.20.32.187/ci4s/ci4s-front:${tag}" "k8s-12front.yaml" | |||||
| #部署管理平台 | #部署管理平台 | ||||
| build_and_deploy "managent-dockerfile" "172.20.32.187/ci4s/ci4s-managent:${tag}" "k8s-7management.yaml" | build_and_deploy "managent-dockerfile" "172.20.32.187/ci4s/ci4s-managent:${tag}" "k8s-7management.yaml" | ||||
| #部署认证中心 | #部署认证中心 | ||||
| @@ -1,6 +1,6 @@ | |||||
| # 基础镜像 | # 基础镜像 | ||||
| #FROM openjdk:8-jre | #FROM openjdk:8-jre | ||||
| FROM 172.20.32.187/ci4s/openjdk:8-jre | |||||
| FROM 172.20.32.187/ci4s/openjdk-dvc:2024829 | |||||
| # author | # author | ||||
| MAINTAINER ruoyi | MAINTAINER ruoyi | ||||
| @@ -1,60 +1,131 @@ | |||||
| worker_processes 1; | |||||
| events { | |||||
| worker_connections 1024; | |||||
| } | |||||
| http { | |||||
| include mime.types; | |||||
| default_type application/octet-stream; | |||||
| sendfile on; | |||||
| keepalive_timeout 65; | |||||
| server { | |||||
| listen 8000; | |||||
| server_name localhost; | |||||
| location /api/{ | |||||
| rewrite ^/prod-api/(.*)$ /$1 break; | |||||
| proxy_set_header Host $http_host; | |||||
| proxy_set_header X-Real-IP $remote_addr; | |||||
| proxy_set_header REMOTE-HOST $remote_addr; | |||||
| proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; | |||||
| proxy_pass http://ci4s-gateway-service.argo.svc:8082/; | |||||
| } | |||||
| location /label-studio { | |||||
| rewrite ^/prod-api/(.*)$ /$1 break; | |||||
| proxy_pass http://label-studio-ls-app.label-data.svc:80/; | |||||
| proxy_hide_header X-Frame-Options; | |||||
| add_header X-Frame-Options "ALLOW-FROM http://label-studio-ls-app.label-data.svc:80/"; | |||||
| } | |||||
| location /api/v1/model/ { | |||||
| proxy_pass http://pipeline-convert-service.argo.svc:80; | |||||
| proxy_set_header REMOTE-HOST $remote_addr; | |||||
| proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; | |||||
| } | |||||
| location / { | |||||
| rewrite ^/prod-api/(.*)$ /$1 break; | |||||
| root /home/ruoyi/projects/ruoyi-ui; | |||||
| try_files $uri $uri/ /index.html; | |||||
| index index.html index.htm; | |||||
| } | |||||
| location @router { | |||||
| rewrite ^.*$ /index.html last; | |||||
| } | |||||
| # 避免actuator暴露 | |||||
| if ($request_uri ~ "/actuator") { | |||||
| return 403; | |||||
| } | |||||
| error_page 500 502 503 504 /50x.html; | |||||
| location = /50x.html { | |||||
| root html; | |||||
| } | |||||
| } | |||||
| } | |||||
| worker_processes 1; | |||||
| events { | |||||
| worker_connections 1024; | |||||
| } | |||||
| http { | |||||
| include mime.types; | |||||
| default_type application/octet-stream; | |||||
| sendfile on; | |||||
| keepalive_timeout 65; | |||||
| client_max_body_size 20480m; | |||||
| error_log /var/log/nginx/error.log debug; | |||||
| server { | |||||
| listen 8000; | |||||
| server_name localhost; | |||||
| location /api/{ | |||||
| # rewrite ^/prod-api/(.*)$ /$1 break; | |||||
| proxy_set_header Host $http_host; | |||||
| proxy_set_header X-Real-IP $remote_addr; | |||||
| proxy_set_header REMOTE-HOST $remote_addr; | |||||
| proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; | |||||
| proxy_pass http://ci4s-gateway-service.argo.svc:8082/; | |||||
| proxy_connect_timeout 500s; # 设置连接超时时间为 120 秒 | |||||
| proxy_read_timeout 500s; # 设置读取超时时间为 120 秒 | |||||
| proxy_send_timeout 500s; # 设置发送超时时间为 120 秒 | |||||
| } | |||||
| location /label-studio/ { | |||||
| # rewrite ^/label-studio/(.*)$ /$1 break; | |||||
| proxy_pass http://label-studio-service.argo.svc:8080/projects/; | |||||
| proxy_hide_header X-Frame-Options; | |||||
| add_header X-Frame-Options ALLOWALL; | |||||
| } | |||||
| location / { | |||||
| rewrite ^/prod-api/(.*)$ /$1 break; | |||||
| root /home/ruoyi/projects/ruoyi-ui; | |||||
| try_files $uri $uri/ /index.html; | |||||
| index index.html index.htm; | |||||
| } | |||||
| location /api/v1/model/ { | |||||
| proxy_pass http://pipeline-convert-service.argo.svc:80; | |||||
| proxy_set_header REMOTE-HOST $remote_addr; | |||||
| proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; | |||||
| } | |||||
| # location /api/v1/realtimeStatus { | |||||
| # proxy_pass http://argo-server.argo.svc:2746/api/v1/workflow-events/argo; | |||||
| # proxy_set_header REMOTE-HOST $remote_addr; | |||||
| # proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; | |||||
| # } | |||||
| location /api/v1/tensorboard/show { | |||||
| # 提取查询参数中的 `svc` 值 | |||||
| set $svc ""; | |||||
| if ($arg_svc) { | |||||
| set $svc $arg_svc; | |||||
| } | |||||
| # 将请求转发到动态生成的内部服务地址 | |||||
| proxy_pass http://$svc.argo.svc:6006; | |||||
| # 传递必要的头信息 | |||||
| proxy_set_header Host $host; | |||||
| proxy_set_header X-Real-IP $remote_addr; | |||||
| proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; | |||||
| proxy_set_header X-Forwarded-Proto $scheme; | |||||
| # 对于 WebSocket 应用很重要 | |||||
| proxy_buffering off; | |||||
| } | |||||
| location /api/v1/realtimeStatus { | |||||
| rewrite ^/api/v1/realtimeStatus(.*)$ /api/v1/workflow-events/argo$1 break; | |||||
| proxy_pass https://argo-server.argo.svc:2746; | |||||
| proxy_http_version 1.1; | |||||
| proxy_set_header Host $host; | |||||
| proxy_set_header REMOTE-HOST $remote_addr; | |||||
| proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; | |||||
| proxy_set_header X-Forwarded-Proto $scheme; | |||||
| # 保留查询参数 | |||||
| proxy_set_header X-Original-URI $request_uri; | |||||
| # 禁用缓冲 | |||||
| proxy_buffering off; | |||||
| # 增加超时时间 | |||||
| proxy_read_timeout 3600s; | |||||
| proxy_send_timeout 3600s; | |||||
| proxy_connect_timeout 60s; | |||||
| # 设置传递的请求头 | |||||
| # proxy_set_header Connection ''; | |||||
| # chunked_transfer_encoding off; | |||||
| # 如果需要保留自定义头部 | |||||
| proxy_set_header Accept 'text/event-stream'; | |||||
| } | |||||
| location /newlog/realtimeLog { | |||||
| proxy_pass http://loki.loki-log.svc:3100/loki/api/v1/tail; | |||||
| proxy_http_version 1.1; | |||||
| proxy_set_header Upgrade $http_upgrade; | |||||
| proxy_set_header Connection "Upgrade"; | |||||
| proxy_set_header Host $host; | |||||
| proxy_set_header X-Real-IP $remote_addr; | |||||
| proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; | |||||
| proxy_set_header X-Forwarded-Proto $scheme; | |||||
| } | |||||
| location @router { | |||||
| rewrite ^.*$ /index.html last; | |||||
| } | |||||
| # 避免actuator暴露 | |||||
| if ($request_uri ~ "/actuator") { | |||||
| return 403; | |||||
| } | |||||
| error_page 500 502 503 504 /50x.html; | |||||
| location = /50x.html { | |||||
| root html; | |||||
| } | |||||
| } | |||||
| } | |||||
| @@ -1,5 +1,5 @@ | |||||
| # 基础镜像 | # 基础镜像 | ||||
| FROM 172.20.32.187/ci4s/openjdk:8-jre | |||||
| FROM 172.20.32.187/ci4s/openjdk-dvc:2024829 | |||||
| #FROM openjdk:8-jre | #FROM openjdk:8-jre | ||||
| # author | # author | ||||
| MAINTAINER ruoyi | MAINTAINER ruoyi | ||||
| @@ -1,6 +1,6 @@ | |||||
| # 基础镜像 | # 基础镜像 | ||||
| #FROM openjdk:8-jre | #FROM openjdk:8-jre | ||||
| FROM 172.20.32.187/ci4s/openjdk:8-jre | |||||
| FROM 172.20.32.187/ci4s/openjdk-dvc:2024829 | |||||
| # author | # author | ||||
| MAINTAINER ruoyi | MAINTAINER ruoyi | ||||
| @@ -1,6 +1,6 @@ | |||||
| # 基础镜像 | # 基础镜像 | ||||
| #FROM openjdk:8-jre | #FROM openjdk:8-jre | ||||
| FROM 172.20.32.187/ci4s/openjdk:8-jre | |||||
| FROM 172.20.32.187/ci4s/openjdk-dvc:2024829 | |||||
| # author | # author | ||||
| MAINTAINER ruoyi | MAINTAINER ruoyi | ||||
| @@ -1,6 +1,6 @@ | |||||
| # 基础镜像 | # 基础镜像 | ||||
| #FROM openjdk:8-jre | #FROM openjdk:8-jre | ||||
| FROM 172.20.32.187/ci4s/openjdk:8-jre | |||||
| FROM 172.20.32.187/ci4s/openjdk-dvc:2024829 | |||||
| # author | # author | ||||
| MAINTAINER ruoyi | MAINTAINER ruoyi | ||||
| @@ -1,6 +1,6 @@ | |||||
| # 基础镜像 | # 基础镜像 | ||||
| #FROM openjdk:8-jre | |||||
| FROM 172.20.32.187/ci4s/openjdk:8-jre | |||||
| #FROM 172.20.32.187/ci4s/openjdk:8u162 | |||||
| FROM 172.20.32.187/ci4s/openjdk-dvc:2024829 | |||||
| # author | # author | ||||
| MAINTAINER ruoyi | MAINTAINER ruoyi | ||||
| @@ -13,4 +13,4 @@ WORKDIR /home/ruoyi | |||||
| # 复制jar文件到路径 | # 复制jar文件到路径 | ||||
| COPY ./jar/management-platform.jar /home/ruoyi/management-platform.jar | COPY ./jar/management-platform.jar /home/ruoyi/management-platform.jar | ||||
| # 启动系统服务 | # 启动系统服务 | ||||
| ENTRYPOINT ["java","-jar","management-platform.jar"] | |||||
| ENTRYPOINT ["java","-jar","-Djdk.tls.client.protocols=TLSv1.2","management-platform.jar"] | |||||
| @@ -1,6 +1,6 @@ | |||||
| # 基础镜像 | # 基础镜像 | ||||
| #FROM openjdk:8-jre | #FROM openjdk:8-jre | ||||
| FROM 172.20.32.187/ci4s/openjdk:8-jre | |||||
| FROM 172.20.32.187/ci4s/openjdk-dvc:2024829 | |||||
| # author | # author | ||||
| MAINTAINER ruoyi | MAINTAINER ruoyi | ||||
| @@ -1,6 +1,6 @@ | |||||
| # 基础镜像 | # 基础镜像 | ||||
| #FROM openjdk:8-jre | #FROM openjdk:8-jre | ||||
| FROM 172.20.32.187/ci4s/openjdk:8-jre | |||||
| FROM 172.20.32.187/ci4s/openjdk-dvc:2024829 | |||||
| # author | # author | ||||
| MAINTAINER ruoyi | MAINTAINER ruoyi | ||||
| @@ -18,6 +18,14 @@ spec: | |||||
| image: 172.20.32.187/ci4s/managent:20240401 | image: 172.20.32.187/ci4s/managent:20240401 | ||||
| ports: | ports: | ||||
| - containerPort: 9213 | - containerPort: 9213 | ||||
| volumeMounts: | |||||
| - name: resource | |||||
| mountPath: /home/resource/ | |||||
| volumes: | |||||
| - name: resource | |||||
| hostPath: | |||||
| path: /home/resource/ | |||||
| type: DirectoryOrCreate | |||||
| --- | --- | ||||
| apiVersion: v1 | apiVersion: v1 | ||||
| @@ -15,10 +15,21 @@ spec: | |||||
| spec: | spec: | ||||
| containers: | containers: | ||||
| - name: ci4s-management-platform | - name: ci4s-management-platform | ||||
| image: 172.20.32.187/ci4s/managent:202406121003 | |||||
| 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: | ports: | ||||
| - containerPort: 9213 | - containerPort: 9213 | ||||
| volumeMounts: | |||||
| - name: resource-volume | |||||
| mountPath: /home/resource/ | |||||
| volumes: | |||||
| - name: resource-volume | |||||
| persistentVolumeClaim: | |||||
| claimName: platform-data-pvc-nfs | |||||
| --- | --- | ||||
| apiVersion: v1 | apiVersion: v1 | ||||
| kind: Service | kind: Service | ||||
| @@ -28,9 +39,15 @@ metadata: | |||||
| spec: | spec: | ||||
| type: NodePort | type: NodePort | ||||
| ports: | ports: | ||||
| - port: 9213 | |||||
| - name: http | |||||
| port: 9213 | |||||
| nodePort: 31208 | nodePort: 31208 | ||||
| protocol: TCP | protocol: TCP | ||||
| - name: debug | |||||
| nodePort: 34567 | |||||
| port: 5005 | |||||
| protocol: TCP | |||||
| targetPort: 5005 | |||||
| selector: | selector: | ||||
| app: ci4s-management-platform | app: ci4s-management-platform | ||||
| @@ -36,6 +36,7 @@ spec: | |||||
| ports: | ports: | ||||
| - containerPort: 8848 | - containerPort: 8848 | ||||
| - containerPort: 9848 | - containerPort: 9848 | ||||
| - containerPort: 9849 | |||||
| restartPolicy: Always | restartPolicy: Always | ||||
| --- | --- | ||||
| @@ -58,5 +59,9 @@ spec: | |||||
| name: web | name: web | ||||
| - port: 9848 | - port: 9848 | ||||
| targetPort: 9848 | targetPort: 9848 | ||||
| nodePort: 31204 | |||||
| nodePort: 32203 | |||||
| name: podsa | name: podsa | ||||
| - port: 9849 | |||||
| targetPort: 9849 | |||||
| nodePort: 32204 | |||||
| name: tcp-9849 | |||||
| @@ -16,9 +16,20 @@ spec: | |||||
| containers: | containers: | ||||
| - name: ci4s-management-platform | - name: ci4s-management-platform | ||||
| image: ${k8s-7management-image} | image: ${k8s-7management-image} | ||||
| env: | |||||
| - name: TZ | |||||
| value: Asia/Shanghai | |||||
| - name: JAVA_TOOL_OPTIONS | |||||
| value: "-Xrunjdwp:transport=dt_socket,server=y,suspend=n,address=*:5005" | |||||
| ports: | ports: | ||||
| - containerPort: 9213 | - containerPort: 9213 | ||||
| volumeMounts: | |||||
| - name: resource-volume | |||||
| mountPath: /home/resource/ | |||||
| volumes: | |||||
| - name: resource-volume | |||||
| persistentVolumeClaim: | |||||
| claimName: platform-data-pvc-nfs | |||||
| --- | --- | ||||
| apiVersion: v1 | apiVersion: v1 | ||||
| kind: Service | kind: Service | ||||
| @@ -28,9 +39,15 @@ metadata: | |||||
| spec: | spec: | ||||
| type: NodePort | type: NodePort | ||||
| ports: | ports: | ||||
| - port: 9213 | |||||
| - name: http | |||||
| port: 9213 | |||||
| nodePort: 31208 | nodePort: 31208 | ||||
| protocol: TCP | protocol: TCP | ||||
| - name: debug | |||||
| nodePort: 31219 | |||||
| port: 5005 | |||||
| protocol: TCP | |||||
| targetPort: 5005 | |||||
| selector: | selector: | ||||
| app: ci4s-management-platform | app: ci4s-management-platform | ||||
| @@ -40,4 +40,11 @@ screenshot | |||||
| build | build | ||||
| pnpm-lock.yaml | |||||
| 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 | |||||
| @@ -150,9 +150,9 @@ export default defineConfig({ | |||||
| projectName: 'swagger', | projectName: 'swagger', | ||||
| }, | }, | ||||
| ], | ], | ||||
| mfsu: { | |||||
| strategy: 'normal', | |||||
| }, | |||||
| // mfsu: { | |||||
| // strategy: 'normal', | |||||
| // }, | |||||
| requestRecord: {}, | requestRecord: {}, | ||||
| icons: {}, | icons: {}, | ||||
| lessLoader: { | lessLoader: { | ||||
| @@ -221,14 +221,24 @@ export default [ | |||||
| component: './ModelDeployment/List', | component: './ModelDeployment/List', | ||||
| }, | }, | ||||
| { | { | ||||
| name: '模型部署详情', | |||||
| path: 'info/:id', | |||||
| component: './ModelDeployment/Info', | |||||
| name: '服务详情', | |||||
| path: 'serviceInfo/:id', | |||||
| component: './ModelDeployment/ServiceInfo', | |||||
| }, | |||||
| { | |||||
| name: '服务版本详情', | |||||
| path: 'versionInfo/:id', | |||||
| component: './ModelDeployment/VersionInfo', | |||||
| }, | }, | ||||
| { | { | ||||
| name: '创建推理服务', | name: '创建推理服务', | ||||
| path: 'create', | |||||
| component: './ModelDeployment/Create', | |||||
| path: 'createService', | |||||
| component: './ModelDeployment/CreateService', | |||||
| }, | |||||
| { | |||||
| name: '新增服务版本', | |||||
| path: 'addVersion/:id', | |||||
| component: './ModelDeployment/CreateVersion', | |||||
| }, | }, | ||||
| ], | ], | ||||
| }, | }, | ||||
| @@ -22,6 +22,7 @@ export { requestConfig as request } from './requestConfig'; | |||||
| // const isDev = process.env.NODE_ENV === 'development'; | // const isDev = process.env.NODE_ENV === 'development'; | ||||
| import { type GlobalInitialState } from '@/types'; | import { type GlobalInitialState } from '@/types'; | ||||
| import { menuItemRender } from '@/utils/menuRender'; | import { menuItemRender } from '@/utils/menuRender'; | ||||
| import ErrorBoundary from './components/ErrorBoundary'; | |||||
| import { gotoLoginPage } from './utils/ui'; | import { gotoLoginPage } from './utils/ui'; | ||||
| /** | /** | ||||
| @@ -65,6 +66,7 @@ export async function getInitialState(): Promise<GlobalInitialState> { | |||||
| // ProLayout 支持的api https://procomponents.ant.design/components/layout | // ProLayout 支持的api https://procomponents.ant.design/components/layout | ||||
| export const layout: RuntimeConfig['layout'] = ({ initialState }) => { | export const layout: RuntimeConfig['layout'] = ({ initialState }) => { | ||||
| return { | return { | ||||
| ErrorBoundary: ErrorBoundary, | |||||
| rightContentRender: false, | rightContentRender: false, | ||||
| waterMarkProps: { | waterMarkProps: { | ||||
| // content: initialState?.currentUser?.nickName, | // content: initialState?.currentUser?.nickName, | ||||
| @@ -0,0 +1,52 @@ | |||||
| .kf-basic-info { | |||||
| display: flex; | |||||
| flex-direction: row; | |||||
| flex-wrap: wrap; | |||||
| gap: 20px 40px; | |||||
| align-items: flex-start; | |||||
| width: 80%; | |||||
| } | |||||
| .kf-basic-info-item { | |||||
| display: flex; | |||||
| align-items: flex-start; | |||||
| width: calc(50% - 20px); | |||||
| font-size: 16px; | |||||
| line-height: 1.6; | |||||
| &__label { | |||||
| position: relative; | |||||
| flex: none; | |||||
| color: @text-color-secondary; | |||||
| text-align: justify; | |||||
| text-align-last: justify; | |||||
| &::after { | |||||
| position: absolute; | |||||
| content: ':'; | |||||
| } | |||||
| } | |||||
| &__list-value { | |||||
| display: flex; | |||||
| flex: 1; | |||||
| flex-direction: column; | |||||
| gap: 5px 0; | |||||
| } | |||||
| &__value { | |||||
| flex: 1; | |||||
| margin-left: 16px; | |||||
| white-space: pre-line; | |||||
| word-break: break-all; | |||||
| } | |||||
| &__text { | |||||
| color: @text-color; | |||||
| } | |||||
| &__link:hover { | |||||
| text-decoration: underline @underline-color; | |||||
| text-underline-offset: 3px; | |||||
| } | |||||
| } | |||||
| @@ -0,0 +1,98 @@ | |||||
| import { Link } from '@umijs/max'; | |||||
| import classNames from 'classnames'; | |||||
| import './index.less'; | |||||
| export type BasicInfoLink = { | |||||
| value: string; | |||||
| link?: string; | |||||
| url?: string; | |||||
| }; | |||||
| export type BasicInfoData = { | |||||
| label: string; | |||||
| value?: any; | |||||
| format?: (_value?: any) => string | BasicInfoLink | BasicInfoLink[] | undefined; | |||||
| }; | |||||
| type BasicInfoProps = { | |||||
| datas: BasicInfoData[]; | |||||
| className?: string; | |||||
| style?: React.CSSProperties; | |||||
| labelWidth?: number; | |||||
| }; | |||||
| function BasicInfo({ datas, className, style, labelWidth = 100 }: BasicInfoProps) { | |||||
| return ( | |||||
| <div className={classNames('kf-basic-info', className)} style={style}> | |||||
| {datas.map((item) => ( | |||||
| <BasicInfoItem key={item.label} data={item} labelWidth={labelWidth} /> | |||||
| ))} | |||||
| </div> | |||||
| ); | |||||
| } | |||||
| type BasicInfoItemProps = { | |||||
| data: BasicInfoData; | |||||
| labelWidth?: number; | |||||
| }; | |||||
| function BasicInfoItem({ data, labelWidth = 100 }: BasicInfoItemProps) { | |||||
| const { label, value, format } = data; | |||||
| const formatValue = format ? format(value) : value; | |||||
| let valueComponent = undefined; | |||||
| if (Array.isArray(formatValue)) { | |||||
| valueComponent = ( | |||||
| <div className="kf-basic-info-item__list-value"> | |||||
| {formatValue.map((item: BasicInfoLink) => ( | |||||
| <BasicInfoItemValue key={item.value} value={item.value} link={item.link} url={item.url} /> | |||||
| ))} | |||||
| </div> | |||||
| ); | |||||
| } else if (typeof formatValue === 'object' && formatValue) { | |||||
| valueComponent = ( | |||||
| <BasicInfoItemValue value={formatValue.value} link={formatValue.link} url={formatValue.url} /> | |||||
| ); | |||||
| } else { | |||||
| valueComponent = <BasicInfoItemValue value={formatValue} />; | |||||
| } | |||||
| return ( | |||||
| <div className="kf-basic-info-item" key={label}> | |||||
| <div className="kf-basic-info-item__label" style={{ width: labelWidth }}> | |||||
| {label} | |||||
| </div> | |||||
| {valueComponent} | |||||
| </div> | |||||
| ); | |||||
| } | |||||
| type BasicInfoItemValueProps = { | |||||
| value: string; | |||||
| link?: string; | |||||
| url?: string; | |||||
| }; | |||||
| function BasicInfoItemValue({ value, link, url }: BasicInfoItemValueProps) { | |||||
| if (url && value) { | |||||
| return ( | |||||
| <a | |||||
| className="kf-basic-info-item__value kf-basic-info-item__link" | |||||
| href={url} | |||||
| target="_blank" | |||||
| rel="noopener noreferrer" | |||||
| > | |||||
| {value} | |||||
| </a> | |||||
| ); | |||||
| } else if (link && value) { | |||||
| return ( | |||||
| <Link to={link} className="kf-basic-info-item__value kf-basic-info-item__link"> | |||||
| {value} | |||||
| </Link> | |||||
| ); | |||||
| } else { | |||||
| return ( | |||||
| <div className="kf-basic-info-item__value kf-basic-info-item__text">{value ?? '--'}</div> | |||||
| ); | |||||
| } | |||||
| } | |||||
| export default BasicInfo; | |||||
| @@ -0,0 +1,11 @@ | |||||
| .kf-code-select { | |||||
| position: relative; | |||||
| display: flex; | |||||
| align-items: center; | |||||
| &__button { | |||||
| position: absolute; | |||||
| top: 0; | |||||
| left: calc(100% + 10px); | |||||
| } | |||||
| } | |||||
| @@ -0,0 +1,71 @@ | |||||
| /* | |||||
| * @Author: 赵伟 | |||||
| * @Date: 2024-10-08 15:36:08 | |||||
| * @Description: 代码配置选择表单组件 | |||||
| */ | |||||
| import KFIcon from '@/components/KFIcon'; | |||||
| import CodeSelectorModal from '@/pages/Pipeline/components/CodeSelectorModal'; | |||||
| import { openAntdModal } from '@/utils/modal'; | |||||
| import { Button } from 'antd'; | |||||
| import ParameterInput, { type ParameterInputProps } from '../ParameterInput'; | |||||
| import './index.less'; | |||||
| export { requiredValidator, type ParameterInputObject } from '../ParameterInput'; | |||||
| type CodeSelectProps = ParameterInputProps; | |||||
| function CodeSelect({ value, onChange, disabled, ...rest }: CodeSelectProps) { | |||||
| const selectResource = () => { | |||||
| const { close } = openAntdModal(CodeSelectorModal, { | |||||
| onOk: (res) => { | |||||
| if (res) { | |||||
| const { git_url, git_branch, code_repo_name } = res; | |||||
| const jsonObj = { | |||||
| code_path: git_url, | |||||
| branch: git_branch, | |||||
| }; | |||||
| const jsonObjStr = JSON.stringify(jsonObj); | |||||
| const showValue = code_repo_name; | |||||
| onChange?.({ | |||||
| value: jsonObjStr, | |||||
| showValue, | |||||
| fromSelect: true, | |||||
| ...jsonObj, | |||||
| }); | |||||
| } else { | |||||
| onChange?.({ | |||||
| value: undefined, | |||||
| showValue: undefined, | |||||
| fromSelect: false, | |||||
| }); | |||||
| } | |||||
| close(); | |||||
| }, | |||||
| }); | |||||
| }; | |||||
| return ( | |||||
| <div className="kf-code-select"> | |||||
| <ParameterInput | |||||
| {...rest} | |||||
| disabled={disabled} | |||||
| value={value} | |||||
| onChange={onChange} | |||||
| onClick={selectResource} | |||||
| ></ParameterInput> | |||||
| <Button | |||||
| className="kf-code-select__button" | |||||
| size="large" | |||||
| type="link" | |||||
| icon={<KFIcon type="icon-xuanzedaimapeizhi" font={16} />} | |||||
| disabled={disabled} | |||||
| onClick={selectResource} | |||||
| > | |||||
| 选择代码配置 | |||||
| </Button> | |||||
| </div> | |||||
| ); | |||||
| } | |||||
| export default CodeSelect; | |||||
| @@ -0,0 +1,78 @@ | |||||
| import KFEmpty, { EmptyType } from '@/components/KFEmpty'; | |||||
| import { Button } from 'antd'; | |||||
| import { Component, ReactNode } from 'react'; | |||||
| interface ErrorBoundaryProps { | |||||
| children: ReactNode; | |||||
| fallback?: ReactNode; // Optional fallback UI to show in case of error | |||||
| } | |||||
| interface ErrorBoundaryState { | |||||
| hasError: boolean; | |||||
| error: Error | null; | |||||
| } | |||||
| class ErrorBoundary extends Component<ErrorBoundaryProps, ErrorBoundaryState> { | |||||
| constructor(props: ErrorBoundaryProps) { | |||||
| super(props); | |||||
| this.state = { | |||||
| hasError: false, | |||||
| error: null, | |||||
| }; | |||||
| } | |||||
| static getDerivedStateFromError(error: Error): ErrorBoundaryState { | |||||
| // Update state so the next render shows the fallback UI | |||||
| return { hasError: true, error }; | |||||
| } | |||||
| // componentDidCatch(error: Error, errorInfo: ErrorInfo) { | |||||
| // // You can log the error to an error reporting service here | |||||
| // console.error('Error caught by ErrorBoundary:', error.message, errorInfo.componentStack); | |||||
| // } | |||||
| render() { | |||||
| if (this.state.hasError) { | |||||
| return this.props.fallback || <ErrorBoundaryFallback error={this.state.error} />; | |||||
| } | |||||
| return this.props.children; | |||||
| } | |||||
| } | |||||
| function ErrorBoundaryFallback({ error }: { error: Error | null }) { | |||||
| const message = error && error instanceof Error ? error.message : 'Unknown error'; | |||||
| const errorMsg = | |||||
| process.env.NODE_ENV === 'development' ? message : '非常抱歉,程序运行错误,\n我们会尽快修复。'; | |||||
| return ( | |||||
| <KFEmpty | |||||
| style={{ height: '100vh' }} | |||||
| type={EmptyType.NotFound} | |||||
| title="出错了" | |||||
| content={errorMsg} | |||||
| footer={() => { | |||||
| return ( | |||||
| <> | |||||
| <Button | |||||
| type="default" | |||||
| onClick={() => { | |||||
| window.history.pushState({}, '', '/'); | |||||
| window.location.reload(); | |||||
| }} | |||||
| > | |||||
| 返回首页 | |||||
| </Button> | |||||
| <Button | |||||
| type="primary" | |||||
| style={{ marginLeft: 20 }} | |||||
| onClick={() => window.location.reload()} | |||||
| > | |||||
| 刷新 | |||||
| </Button> | |||||
| </> | |||||
| ); | |||||
| }} | |||||
| ></KFEmpty> | |||||
| ); | |||||
| } | |||||
| export default ErrorBoundary; | |||||
| @@ -9,6 +9,7 @@ import { | |||||
| } from '@/utils/sessionStorage'; | } from '@/utils/sessionStorage'; | ||||
| import classNames from 'classnames'; | import classNames from 'classnames'; | ||||
| import { useEffect, useState } from 'react'; | import { useEffect, useState } from 'react'; | ||||
| import { createPortal } from 'react-dom'; | |||||
| import './index.less'; | import './index.less'; | ||||
| export enum IframePageType { | export enum IframePageType { | ||||
| @@ -61,7 +62,7 @@ function IframePage({ type, className, style }: IframePageProps) { | |||||
| return ( | return ( | ||||
| <div className={classNames('kf-iframe-page', className)} style={style}> | <div className={classNames('kf-iframe-page', className)} style={style}> | ||||
| {loading && <KFSpin />} | |||||
| {loading && createPortal(<KFSpin size="large" />, document.body)} | |||||
| <FullScreenFrame url={iframeUrl} onload={hideLoading} onerror={hideLoading} /> | <FullScreenFrame url={iframeUrl} onload={hideLoading} onerror={hideLoading} /> | ||||
| </div> | </div> | ||||
| ); | ); | ||||
| @@ -0,0 +1,40 @@ | |||||
| .kf-empty { | |||||
| display: flex; | |||||
| flex-direction: column; | |||||
| align-items: center; | |||||
| justify-content: center; | |||||
| width: 100%; | |||||
| &__image { | |||||
| width: 475px; | |||||
| } | |||||
| &__title { | |||||
| margin-top: 15px; | |||||
| color: @text-color; | |||||
| font-weight: 500; | |||||
| font-size: 30px; | |||||
| text-align: center; | |||||
| } | |||||
| &__content { | |||||
| max-width: 50%; | |||||
| margin-top: 15px; | |||||
| color: @text-color-secondary; | |||||
| font-size: 15px; | |||||
| white-space: pre-line; | |||||
| text-align: center; | |||||
| } | |||||
| &__footer { | |||||
| display: flex; | |||||
| align-items: center; | |||||
| justify-content: center; | |||||
| margin-top: 20px; | |||||
| margin-bottom: 30px; | |||||
| &__back-btn { | |||||
| height: 32px; | |||||
| } | |||||
| } | |||||
| } | |||||
| @@ -0,0 +1,67 @@ | |||||
| import { Button } from 'antd'; | |||||
| import classNames from 'classnames'; | |||||
| import './index.less'; | |||||
| export enum EmptyType { | |||||
| NoData = 'NoData', | |||||
| NotFound = 'NotFound', | |||||
| Developing = 'Developing', | |||||
| } | |||||
| type EmptyProps = { | |||||
| className?: string; | |||||
| style?: React.CSSProperties; | |||||
| type: EmptyType; | |||||
| title?: string; | |||||
| content?: string; | |||||
| hasFooter?: boolean; | |||||
| footer?: () => React.ReactNode; | |||||
| buttonTitle?: string; | |||||
| onRefresh?: () => void; | |||||
| }; | |||||
| function getEmptyImage(type: EmptyType) { | |||||
| switch (type) { | |||||
| case EmptyType.NoData: | |||||
| return require('@/assets/img/no-data.png'); | |||||
| case EmptyType.NotFound: | |||||
| return require('@/assets/img/404.png'); | |||||
| case EmptyType.Developing: | |||||
| return require('@/assets/img/missing-back.png'); | |||||
| } | |||||
| } | |||||
| function KFEmpty({ | |||||
| className, | |||||
| style, | |||||
| type, | |||||
| title, | |||||
| content, | |||||
| hasFooter = true, | |||||
| footer, | |||||
| buttonTitle = '刷新', | |||||
| onRefresh, | |||||
| }: EmptyProps) { | |||||
| const image = getEmptyImage(type); | |||||
| return ( | |||||
| <div className={classNames('kf-empty', className)} style={style}> | |||||
| <img className="kf-empty__image" src={image} draggable={false} alt="" /> | |||||
| <div className="kf-empty__title">{title}</div> | |||||
| <div className="kf-empty__content">{content}</div> | |||||
| {hasFooter && ( | |||||
| <div className="kf-empty__footer"> | |||||
| {footer ? ( | |||||
| footer() | |||||
| ) : ( | |||||
| <Button className="kf-empty__footer__back-btn" type="primary" onClick={onRefresh}> | |||||
| {buttonTitle} | |||||
| </Button> | |||||
| )} | |||||
| </div> | |||||
| )} | |||||
| </div> | |||||
| ); | |||||
| } | |||||
| export default KFEmpty; | |||||
| @@ -4,12 +4,12 @@ | |||||
| right: 0; | right: 0; | ||||
| bottom: 0; | bottom: 0; | ||||
| left: 0; | left: 0; | ||||
| z-index: 1000; | |||||
| z-index: 1001; | |||||
| display: flex; | display: flex; | ||||
| flex-direction: column; | flex-direction: column; | ||||
| align-items: center; | align-items: center; | ||||
| justify-content: center; | justify-content: center; | ||||
| background-color: rgba(255, 255, 255, 0.5); | |||||
| background-color: rgba(255, 255, 255, 0.3); | |||||
| &__label { | &__label { | ||||
| margin-top: 20px; | margin-top: 20px; | ||||
| @@ -1,3 +1,9 @@ | |||||
| /* | |||||
| * @Author: 赵伟 | |||||
| * @Date: 2024-09-02 08:42:57 | |||||
| * @Description: 自定义 Spin | |||||
| */ | |||||
| import { Spin, SpinProps } from 'antd'; | import { Spin, SpinProps } from 'antd'; | ||||
| import styles from './index.less'; | import styles from './index.less'; | ||||
| @@ -1,16 +1,25 @@ | |||||
| .menu-icon-selector { | .menu-icon-selector { | ||||
| // grid 布局,每行显示 8 个图标 | // grid 布局,每行显示 8 个图标 | ||||
| display: grid; | display: grid; | ||||
| grid-auto-rows: 1fr; | |||||
| grid-template-columns: repeat(4, 1fr); | |||||
| grid-template-columns: repeat(4, 80px); | |||||
| gap: 20px; | |||||
| justify-content: space-between; | |||||
| width: 100%; | |||||
| &__item { | &__item { | ||||
| display: flex; | display: flex; | ||||
| align-items: center; | align-items: center; | ||||
| justify-content: center; | justify-content: center; | ||||
| width: 80x; | |||||
| height: 80px; | height: 80px; | ||||
| border: 1px solid transparent; | |||||
| border-radius: 4px; | |||||
| cursor: pointer; | cursor: pointer; | ||||
| &:hover { | |||||
| border-color: @primary-color; | |||||
| } | |||||
| &__icon { | &__icon { | ||||
| display: block; | display: block; | ||||
| } | } | ||||
| @@ -1,3 +1,9 @@ | |||||
| /* | |||||
| * @Author: 赵伟 | |||||
| * @Date: 2024-04-17 16:59:42 | |||||
| * @Description: 菜单图标选择器 | |||||
| */ | |||||
| import KFIcon from '@/components/KFIcon'; | import KFIcon from '@/components/KFIcon'; | ||||
| import KFModal from '@/components/KFModal'; | import KFModal from '@/components/KFModal'; | ||||
| import iconData from '@/iconfont/iconfont-menu.json'; | import iconData from '@/iconfont/iconfont-menu.json'; | ||||
| @@ -18,7 +18,7 @@ type ModalTitleProps = { | |||||
| function ModalTitle({ title, image, style, className }: ModalTitleProps) { | function ModalTitle({ title, image, style, className }: ModalTitleProps) { | ||||
| return ( | return ( | ||||
| <div className={classNames('kf-modal-title', className)} style={style}> | <div className={classNames('kf-modal-title', className)} style={style}> | ||||
| {image && <img className={'kf-modal-title__image'} src={image} alt="" />} | |||||
| {image && <img className={'kf-modal-title__image'} src={image} draggable={false} alt="" />} | |||||
| {title} | {title} | ||||
| </div> | </div> | ||||
| ); | ); | ||||
| @@ -1,3 +1,9 @@ | |||||
| /* | |||||
| * @Author: 赵伟 | |||||
| * @Date: 2024-04-16 08:42:57 | |||||
| * @Description: 参数输入组件 | |||||
| */ | |||||
| import { CloseOutlined } from '@ant-design/icons'; | import { CloseOutlined } from '@ant-design/icons'; | ||||
| import { Form, Input } from 'antd'; | import { Form, Input } from 'antd'; | ||||
| import { RuleObject } from 'antd/es/form'; | import { RuleObject } from 'antd/es/form'; | ||||
| @@ -14,7 +14,15 @@ const filterResourceStandard: SelectProps<string, ComputingResource>['filterOpti | |||||
| }; | }; | ||||
| // id 从 number 转换为 string | // id 从 number 转换为 string | ||||
| const convertId = (item: any) => ({ ...item, id: String(item.id) }); | |||||
| const convertId = (item: any) => ({ | |||||
| ...item, | |||||
| id: JSON.stringify({ | |||||
| id: `${item.id}`, | |||||
| name: item.name, | |||||
| identifier: item.identifier, | |||||
| owner: item.owner, | |||||
| }), | |||||
| }); | |||||
| export type SelectPropsConfig = { | export type SelectPropsConfig = { | ||||
| getOptions: () => Promise<any>; // 获取下拉数据 | getOptions: () => Promise<any>; // 获取下拉数据 | ||||
| @@ -29,7 +37,7 @@ export const paramSelectConfig: Record<string, SelectPropsConfig> = { | |||||
| const res = await getDatasetList({ | const res = await getDatasetList({ | ||||
| page: 0, | page: 0, | ||||
| size: 1000, | size: 1000, | ||||
| available_range: 0, | |||||
| is_public: false, | |||||
| }); | }); | ||||
| return res?.data?.content?.map(convertId) ?? []; | return res?.data?.content?.map(convertId) ?? []; | ||||
| }, | }, | ||||
| @@ -44,7 +52,7 @@ export const paramSelectConfig: Record<string, SelectPropsConfig> = { | |||||
| const res = await getModelList({ | const res = await getModelList({ | ||||
| page: 0, | page: 0, | ||||
| size: 1000, | size: 1000, | ||||
| available_range: 0, | |||||
| is_public: false, | |||||
| }); | }); | ||||
| return res?.data?.content?.map(convertId) ?? []; | return res?.data?.content?.map(convertId) ?? []; | ||||
| }, | }, | ||||
| @@ -1,3 +1,9 @@ | |||||
| /* | |||||
| * @Author: 赵伟 | |||||
| * @Date: 2024-04-16 08:42:57 | |||||
| * @Description: 参数选择组件 | |||||
| */ | |||||
| import { PipelineNodeModelParameter } from '@/types'; | import { PipelineNodeModelParameter } from '@/types'; | ||||
| import { to } from '@/utils/promise'; | import { to } from '@/utils/promise'; | ||||
| import { Select } from 'antd'; | import { Select } from 'antd'; | ||||
| @@ -1,3 +1,9 @@ | |||||
| /* | |||||
| * @Author: 赵伟 | |||||
| * @Date: 2024-04-16 13:58:08 | |||||
| * @Description: 数据集、模型、镜像选择表单组件 | |||||
| */ | |||||
| import KFIcon from '@/components/KFIcon'; | import KFIcon from '@/components/KFIcon'; | ||||
| import ResourceSelectorModal, { | import ResourceSelectorModal, { | ||||
| ResourceSelectorResponse, | ResourceSelectorResponse, | ||||
| @@ -11,6 +17,7 @@ import ParameterInput, { type ParameterInputProps } from '../ParameterInput'; | |||||
| import './index.less'; | import './index.less'; | ||||
| export { requiredValidator, type ParameterInputObject } from '../ParameterInput'; | export { requiredValidator, type ParameterInputObject } from '../ParameterInput'; | ||||
| export { ResourceSelectorType, selectorTypeConfig, type ResourceSelectorResponse }; | |||||
| type ResourceSelectProps = { | type ResourceSelectProps = { | ||||
| type: ResourceSelectorType; | type: ResourceSelectorType; | ||||
| @@ -21,7 +28,7 @@ const getSelectBtnIcon = (type: ResourceSelectorType) => { | |||||
| return <KFIcon type={selectorTypeConfig[type].buttonIcon} font={16} />; | return <KFIcon type={selectorTypeConfig[type].buttonIcon} font={16} />; | ||||
| }; | }; | ||||
| function ResourceSelect({ type, value, onChange, ...rest }: ResourceSelectProps) { | |||||
| function ResourceSelect({ type, value, onChange, disabled, ...rest }: ResourceSelectProps) { | |||||
| const [selectedResource, setSelectedResource] = useState<ResourceSelectorResponse | undefined>( | const [selectedResource, setSelectedResource] = useState<ResourceSelectorResponse | undefined>( | ||||
| undefined, | undefined, | ||||
| ); | ); | ||||
| @@ -36,7 +43,7 @@ function ResourceSelect({ type, value, onChange, ...rest }: ResourceSelectProps) | |||||
| onOk: (res) => { | onOk: (res) => { | ||||
| setSelectedResource(res); | setSelectedResource(res); | ||||
| if (res) { | if (res) { | ||||
| const { activeTab, id, name, version, path } = res; | |||||
| const { activeTab, id, name, version, path, identifier, owner } = res; | |||||
| if (type === ResourceSelectorType.Mirror) { | if (type === ResourceSelectorType.Mirror) { | ||||
| onChange?.({ | onChange?.({ | ||||
| value: path, | value: path, | ||||
| @@ -49,8 +56,11 @@ function ResourceSelect({ type, value, onChange, ...rest }: ResourceSelectProps) | |||||
| } else { | } else { | ||||
| const jsonObj = { | const jsonObj = { | ||||
| id, | id, | ||||
| name, | |||||
| version, | version, | ||||
| path, | path, | ||||
| identifier, | |||||
| owner, | |||||
| }; | }; | ||||
| const jsonObjStr = JSON.stringify(jsonObj); | const jsonObjStr = JSON.stringify(jsonObj); | ||||
| const showValue = `${name}:${version}`; | const showValue = `${name}:${version}`; | ||||
| @@ -83,6 +93,7 @@ function ResourceSelect({ type, value, onChange, ...rest }: ResourceSelectProps) | |||||
| <div className="kf-resource-select"> | <div className="kf-resource-select"> | ||||
| <ParameterInput | <ParameterInput | ||||
| {...rest} | {...rest} | ||||
| disabled={disabled} | |||||
| value={value} | value={value} | ||||
| onChange={onChange} | onChange={onChange} | ||||
| onRemove={() => setSelectedResource(undefined)} | onRemove={() => setSelectedResource(undefined)} | ||||
| @@ -93,6 +104,7 @@ function ResourceSelect({ type, value, onChange, ...rest }: ResourceSelectProps) | |||||
| size="large" | size="large" | ||||
| type="link" | type="link" | ||||
| icon={getSelectBtnIcon(type)} | icon={getSelectBtnIcon(type)} | ||||
| disabled={disabled} | |||||
| onClick={selectResource} | onClick={selectResource} | ||||
| > | > | ||||
| {selectorTypeConfig[type].buttontTitle} | {selectorTypeConfig[type].buttontTitle} | ||||
| @@ -17,7 +17,7 @@ type SubAreaTitleProps = { | |||||
| function SubAreaTitle({ title, image, style, className }: SubAreaTitleProps) { | function SubAreaTitle({ title, image, style, className }: SubAreaTitleProps) { | ||||
| return ( | return ( | ||||
| <div className={classNames('kf-subarea-title', className)} style={style}> | <div className={classNames('kf-subarea-title', className)} style={style}> | ||||
| <img src={image} width={14} /> | |||||
| <img src={image} width={14} draggable={false} alt="" /> | |||||
| <span style={{ marginLeft: '8px' }}>{title}</span> | <span style={{ marginLeft: '8px' }}>{title}</span> | ||||
| </div> | </div> | ||||
| ); | ); | ||||
| @@ -44,8 +44,8 @@ export enum MirrorVersionStatus { | |||||
| Failed = 'failed', // 构建中 | Failed = 'failed', // 构建中 | ||||
| } | } | ||||
| // 模型部署状态 | |||||
| export enum ModelDeploymentStatus { | |||||
| // 服务运行状态 | |||||
| export enum ServiceRunStatus { | |||||
| Init = 'Init', // 启动中 | Init = 'Init', // 启动中 | ||||
| Running = 'Running', // 运行中 | Running = 'Running', // 运行中 | ||||
| Stopped = 'Stopped', // 已停止 | Stopped = 'Stopped', // 已停止 | ||||
| @@ -53,14 +53,13 @@ export enum ModelDeploymentStatus { | |||||
| Pending = 'Pending', // 挂起中 | Pending = 'Pending', // 挂起中 | ||||
| } | } | ||||
| // 模型部署状态选项列表 | |||||
| export const modelDeploymentStatusOptions = [ | |||||
| { label: '全部', value: '' }, | |||||
| { label: '启动中', value: ModelDeploymentStatus.Init }, | |||||
| { label: '运行中', value: ModelDeploymentStatus.Running }, | |||||
| { label: '已停止', value: ModelDeploymentStatus.Stopped }, | |||||
| { label: '失败', value: ModelDeploymentStatus.Failed }, | |||||
| { label: '挂起中', value: ModelDeploymentStatus.Pending }, | |||||
| // 服务运行状态选项列表 | |||||
| export const serviceStatusOptions = [ | |||||
| { label: '启动中', value: ServiceRunStatus.Init }, | |||||
| { label: '运行中', value: ServiceRunStatus.Running }, | |||||
| { label: '已停止', value: ServiceRunStatus.Stopped }, | |||||
| { label: '失败', value: ServiceRunStatus.Failed }, | |||||
| { label: '挂起中', value: ServiceRunStatus.Pending }, | |||||
| ]; | ]; | ||||
| // 开发环境编辑器状态 | // 开发环境编辑器状态 | ||||
| @@ -71,3 +70,17 @@ export enum DevEditorStatus { | |||||
| Failed = 'Failed', // 失败 | Failed = 'Failed', // 失败 | ||||
| Unknown = 'Unknown', // 未启动 | Unknown = 'Unknown', // 未启动 | ||||
| } | } | ||||
| export enum ServiceType { | |||||
| Video = 'video', | |||||
| Image = 'image', | |||||
| Audio = 'audio', | |||||
| Text = 'text', | |||||
| } | |||||
| export const serviceTypeOptions = [ | |||||
| { label: '视频', value: ServiceType.Video }, | |||||
| { label: '图像', value: ServiceType.Image }, | |||||
| { label: '音频', value: ServiceType.Audio }, | |||||
| { label: '文本', value: ServiceType.Text }, | |||||
| ]; | |||||
| @@ -101,15 +101,18 @@ body { | |||||
| ::-webkit-scrollbar { | ::-webkit-scrollbar { | ||||
| width: 8px; | width: 8px; | ||||
| height: 8px; | |||||
| background: transparent; | background: transparent; | ||||
| } | } | ||||
| ::-webkit-scrollbar-thumb { | ::-webkit-scrollbar-thumb { | ||||
| width: 8px; | width: 8px; | ||||
| height: 8px; | |||||
| background: rgba(0, 0, 0, 0.5); | background: rgba(0, 0, 0, 0.5); | ||||
| border-radius: 99px; | border-radius: 99px; | ||||
| } | } | ||||
| ::-webkit-scrollbar-track { | ::-webkit-scrollbar-track { | ||||
| width: 8px; | width: 8px; | ||||
| height: 8px; | |||||
| background: transparent; | background: transparent; | ||||
| } | } | ||||
| ul, | ul, | ||||
| @@ -116,6 +116,10 @@ | |||||
| } | } | ||||
| } | } | ||||
| .ant-input.ant-input-disabled { | |||||
| height: 46px; | |||||
| } | |||||
| // 选择框高度为46px | // 选择框高度为46px | ||||
| .ant-select-single { | .ant-select-single { | ||||
| height: 46px; | height: 46px; | ||||
| @@ -1,18 +1,20 @@ | |||||
| import { history } from '@umijs/max'; | |||||
| import { Button, Result } from 'antd'; | |||||
| import React from 'react'; | |||||
| import KFEmpty, { EmptyType } from '@/components/KFEmpty'; | |||||
| import { useNavigate } from '@umijs/max'; | |||||
| const NoFoundPage: React.FC = () => ( | |||||
| <Result | |||||
| status="404" | |||||
| title="404" | |||||
| subTitle="Sorry, the page you visited does not exist." | |||||
| extra={ | |||||
| <Button type="primary" onClick={() => history.push('/')}> | |||||
| Back Home | |||||
| </Button> | |||||
| } | |||||
| /> | |||||
| ); | |||||
| const NoFoundPage = () => { | |||||
| const navigate = useNavigate(); | |||||
| return ( | |||||
| <KFEmpty | |||||
| style={{ height: '100vh' }} | |||||
| type={EmptyType.NotFound} | |||||
| title="404" | |||||
| content={'很抱歉,您访问的页面地址有误,\n或者该页面不存在。'} | |||||
| hasFooter={true} | |||||
| buttonTitle="返回首页" | |||||
| onRefresh={() => navigate('/')} | |||||
| ></KFEmpty> | |||||
| ); | |||||
| }; | |||||
| export default NoFoundPage; | export default NoFoundPage; | ||||
| @@ -1,9 +1,10 @@ | |||||
| import KFEmpty, { EmptyType } from '@/components/KFEmpty'; | |||||
| import KFIcon from '@/components/KFIcon'; | import KFIcon from '@/components/KFIcon'; | ||||
| import { deleteCodeConfigReq, getCodeConfigListReq } from '@/services/codeConfig'; | import { deleteCodeConfigReq, getCodeConfigListReq } from '@/services/codeConfig'; | ||||
| import { openAntdModal } from '@/utils/modal'; | import { openAntdModal } from '@/utils/modal'; | ||||
| import { to } from '@/utils/promise'; | import { to } from '@/utils/promise'; | ||||
| import { modalConfirm } from '@/utils/ui'; | import { modalConfirm } from '@/utils/ui'; | ||||
| import { App, Button, Empty, Input, Pagination, PaginationProps } from 'antd'; | |||||
| import { App, Button, Input, Pagination, PaginationProps } from 'antd'; | |||||
| import { useEffect, useState } from 'react'; | import { useEffect, useState } from 'react'; | ||||
| import AddCodeConfigModal, { OperationType } from '../components/AddCodeConfigModal'; | import AddCodeConfigModal, { OperationType } from '../components/AddCodeConfigModal'; | ||||
| import CodeConfigItem from '../components/CodeConfigItem'; | import CodeConfigItem from '../components/CodeConfigItem'; | ||||
| @@ -31,7 +32,7 @@ export type ResourceListRef = { | |||||
| }; | }; | ||||
| function CodeConfigList() { | function CodeConfigList() { | ||||
| const [dataList, setDataList] = useState<CodeConfigData[]>([]); | |||||
| const [dataList, setDataList] = useState<CodeConfigData[] | undefined>(undefined); | |||||
| const [total, setTotal] = useState(0); | const [total, setTotal] = useState(0); | ||||
| const [pagination, setPagination] = useState<PaginationProps>({ | const [pagination, setPagination] = useState<PaginationProps>({ | ||||
| current: 1, | current: 1, | ||||
| @@ -56,6 +57,9 @@ function CodeConfigList() { | |||||
| if (res && res.data && res.data.content) { | if (res && res.data && res.data.content) { | ||||
| setDataList(res.data.content); | setDataList(res.data.content); | ||||
| setTotal(res.data.totalElements); | setTotal(res.data.totalElements); | ||||
| } else { | |||||
| setDataList([]); | |||||
| setTotal(0); | |||||
| } | } | ||||
| }; | }; | ||||
| @@ -117,7 +121,7 @@ function CodeConfigList() { | |||||
| return ( | return ( | ||||
| <div className={styles['code-config-list']}> | <div className={styles['code-config-list']}> | ||||
| <div className={styles['code-config-list__header']}> | <div className={styles['code-config-list__header']}> | ||||
| <span>数据总数:{total}个</span> | |||||
| <span>数据总数:{total} 个</span> | |||||
| <div> | <div> | ||||
| <Input.Search | <Input.Search | ||||
| placeholder="按代码仓库名称筛选" | placeholder="按代码仓库名称筛选" | ||||
| @@ -139,10 +143,10 @@ function CodeConfigList() { | |||||
| </Button> | </Button> | ||||
| </div> | </div> | ||||
| </div> | </div> | ||||
| {dataList?.length !== 0 ? ( | |||||
| {dataList && dataList.length !== 0 && ( | |||||
| <> | <> | ||||
| <div className={styles['code-config-list__content']}> | <div className={styles['code-config-list__content']}> | ||||
| {dataList?.map((item) => ( | |||||
| {dataList.map((item) => ( | |||||
| <CodeConfigItem | <CodeConfigItem | ||||
| item={item} | item={item} | ||||
| key={item.id} | key={item.id} | ||||
| @@ -161,10 +165,16 @@ function CodeConfigList() { | |||||
| {...pagination} | {...pagination} | ||||
| /> | /> | ||||
| </> | </> | ||||
| ) : ( | |||||
| <div className={styles['code-config-list__empty']}> | |||||
| <Empty></Empty> | |||||
| </div> | |||||
| )} | |||||
| {dataList && dataList.length === 0 && ( | |||||
| <KFEmpty | |||||
| className={styles['code-config-list__empty']} | |||||
| type={EmptyType.NoData} | |||||
| title="暂无数据" | |||||
| content={'很抱歉,没有搜索到您想要的内容\n建议刷新试试'} | |||||
| hasFooter={true} | |||||
| onRefresh={getDataList} | |||||
| /> | |||||
| )} | )} | ||||
| </div> | </div> | ||||
| ); | ); | ||||
| @@ -14,35 +14,43 @@ | |||||
| } | } | ||||
| &__name { | &__name { | ||||
| position: relative; | |||||
| display: inline-block; | |||||
| height: 24px; | |||||
| margin: 0 10px 0 0 !important; | |||||
| margin-right: 10px; | |||||
| margin-bottom: 0 !important; | |||||
| color: @text-color; | color: @text-color; | ||||
| font-size: 16px; | font-size: 16px; | ||||
| } | } | ||||
| &__tag { | &__tag { | ||||
| padding: 4px; | |||||
| color: @primary-color; | |||||
| padding: 2px 11px; | |||||
| font-size: 12px; | font-size: 12px; | ||||
| background-color: .addAlpha(@primary-color, 0.1) []; | |||||
| border-radius: 4px; | |||||
| border-radius: 1000px; | |||||
| &--public { | |||||
| color: @primary-color; | |||||
| background-color: .addAlpha(@primary-color, 0.08) []; | |||||
| border-color: .addAlpha(@primary-color, 0.5) []; | |||||
| } | |||||
| &--private { | |||||
| color: @warning-color; | |||||
| background-color: .addAlpha(@warning-color, 0.08) []; | |||||
| border-color: .addAlpha(@warning-color, 0.5) []; | |||||
| } | |||||
| } | } | ||||
| &__url { | &__url { | ||||
| margin-bottom: 10px; | |||||
| color: @text-color-secondary; | color: @text-color-secondary; | ||||
| font-size: 14px; | font-size: 14px; | ||||
| } | } | ||||
| &__description { | |||||
| height: 44px; | |||||
| &__branch { | |||||
| margin-bottom: 20px; | margin-bottom: 20px; | ||||
| color: @text-color-secondary; | |||||
| color: @text-color-tertiary; | |||||
| font-size: 14px; | font-size: 14px; | ||||
| .multiLine(2); | |||||
| } | } | ||||
| &__user, | |||||
| &__time { | &__time { | ||||
| display: flex; | display: flex; | ||||
| flex: 0 1 content; | flex: 0 1 content; | ||||
| @@ -55,21 +63,9 @@ | |||||
| &:hover { | &:hover { | ||||
| border-color: @primary-color; | border-color: @primary-color; | ||||
| box-shadow: 0px 0px 6px 1px rgba(0, 0, 0, 0.1); | box-shadow: 0px 0px 6px 1px rgba(0, 0, 0, 0.1); | ||||
| .resource-item__name { | |||||
| color: @primary-color; | |||||
| } | |||||
| } | } | ||||
| } | |||||
| .resource-item__name { | |||||
| &::after { | |||||
| position: absolute; | |||||
| top: 14px; | |||||
| left: 0; | |||||
| width: 100%; | |||||
| height: 6px; | |||||
| background: linear-gradient(to right, rgba(22, 100, 255, 0.3) 0, rgba(22, 100, 255, 0) 100%); | |||||
| content: ''; | |||||
| &:hover &__name { | |||||
| color: @primary-color; | |||||
| } | } | ||||
| } | } | ||||
| @@ -5,6 +5,7 @@ import { AvailableRange } from '@/enums'; | |||||
| import { type CodeConfigData } from '@/pages/CodeConfig/List'; | import { type CodeConfigData } from '@/pages/CodeConfig/List'; | ||||
| import { formatDate } from '@/utils/date'; | import { formatDate } from '@/utils/date'; | ||||
| import { Button, Flex, Typography } from 'antd'; | import { Button, Flex, Typography } from 'antd'; | ||||
| import classNames from 'classnames'; | |||||
| import styles from './index.less'; | import styles from './index.less'; | ||||
| type CodeConfigItemProps = { | type CodeConfigItemProps = { | ||||
| @@ -24,10 +25,16 @@ function CodeConfigItem({ item, onClick, onEdit, onRemove }: CodeConfigItemProps | |||||
| > | > | ||||
| {item.code_repo_name} | {item.code_repo_name} | ||||
| </Typography.Paragraph> | </Typography.Paragraph> | ||||
| <div className={styles['code-config-item__tag']}> | |||||
| <div | |||||
| className={classNames( | |||||
| styles['code-config-item__tag'], | |||||
| item.code_repo_vis === AvailableRange.Public | |||||
| ? styles['code-config-item__tag--public'] | |||||
| : styles['code-config-item__tag--private'], | |||||
| )} | |||||
| > | |||||
| {item.code_repo_vis === AvailableRange.Public ? '公开' : '私有'} | {item.code_repo_vis === AvailableRange.Public ? '公开' : '私有'} | ||||
| </div> | </div> | ||||
| <Button | <Button | ||||
| type="text" | type="text" | ||||
| shape="circle" | shape="circle" | ||||
| @@ -54,20 +61,22 @@ function CodeConfigItem({ item, onClick, onEdit, onRemove }: CodeConfigItemProps | |||||
| <Typography.Paragraph | <Typography.Paragraph | ||||
| className={styles['code-config-item__url']} | className={styles['code-config-item__url']} | ||||
| ellipsis={{ tooltip: item.git_url }} | ellipsis={{ tooltip: item.git_url }} | ||||
| style={{ marginBottom: '8px' }} | |||||
| > | > | ||||
| {item.git_url} | {item.git_url} | ||||
| </Typography.Paragraph> | </Typography.Paragraph> | ||||
| <div className={styles['code-config-item__url']} style={{ marginBottom: '20px' }}> | |||||
| {item.git_branch} | |||||
| </div> | |||||
| <div className={styles['code-config-item__branch']}>{item.git_branch}</div> | |||||
| <Flex justify="space-between"> | <Flex justify="space-between"> | ||||
| <div className={styles['code-config-item__time']}> | |||||
| <img style={{ width: '17px', marginRight: '6px' }} src={creatByImg} alt="" /> | |||||
| <div className={styles['code-config-item__user']}> | |||||
| <img | |||||
| style={{ width: '17px', marginRight: '6px' }} | |||||
| src={creatByImg} | |||||
| alt="" | |||||
| draggable={false} | |||||
| /> | |||||
| <span>{item.create_by}</span> | <span>{item.create_by}</span> | ||||
| </div> | </div> | ||||
| <div className={styles['code-config-item__time']}> | <div className={styles['code-config-item__time']}> | ||||
| <img style={{ width: '12px', marginRight: '5px' }} src={clock} alt="" /> | |||||
| <img style={{ width: '12px', marginRight: '5px' }} src={clock} alt="" draggable={false} /> | |||||
| <span>最近更新: {formatDate(item.update_time, 'YYYY-MM-DD')}</span> | <span>最近更新: {formatDate(item.update_time, 'YYYY-MM-DD')}</span> | ||||
| </div> | </div> | ||||
| </Flex> | </Flex> | ||||
| @@ -1,6 +1,7 @@ | |||||
| .upload-tip { | .upload-tip { | ||||
| margin-top: 5px; | margin-top: 5px; | ||||
| color: @error-color; | |||||
| color: @text-color-secondary; | |||||
| font-size: 14px; | |||||
| } | } | ||||
| .upload-button { | .upload-button { | ||||
| @@ -1,9 +1,8 @@ | |||||
| import { getAccessToken } from '@/access'; | import { getAccessToken } from '@/access'; | ||||
| import { DictValueEnumObj } from '@/components/DictTag'; | |||||
| import KFIcon from '@/components/KFIcon'; | import KFIcon from '@/components/KFIcon'; | ||||
| import KFModal from '@/components/KFModal'; | import KFModal from '@/components/KFModal'; | ||||
| import { addDatesetAndVesion } from '@/services/dataset/index.js'; | |||||
| import { getDictSelectOption } from '@/services/system/dict'; | |||||
| import { CategoryData, DataSource, ResourceType, resourceConfig } from '@/pages/Dataset/config'; | |||||
| import { addDataset } from '@/services/dataset/index.js'; | |||||
| import { to } from '@/utils/promise'; | import { to } from '@/utils/promise'; | ||||
| import { getFileListFromEvent, validateUploadFiles } from '@/utils/ui'; | import { getFileListFromEvent, validateUploadFiles } from '@/utils/ui'; | ||||
| import { | import { | ||||
| @@ -19,8 +18,7 @@ import { | |||||
| type UploadProps, | type UploadProps, | ||||
| } from 'antd'; | } from 'antd'; | ||||
| import { omit } from 'lodash'; | import { omit } from 'lodash'; | ||||
| import { useEffect, useState } from 'react'; | |||||
| import { CategoryData } from '../../config'; | |||||
| import { useState } from 'react'; | |||||
| import styles from './index.less'; | import styles from './index.less'; | ||||
| interface AddDatasetModalProps extends Omit<ModalProps, 'onOk'> { | interface AddDatasetModalProps extends Omit<ModalProps, 'onOk'> { | ||||
| @@ -31,15 +29,15 @@ interface AddDatasetModalProps extends Omit<ModalProps, 'onOk'> { | |||||
| function AddDatasetModal({ typeList, tagList, onOk, ...rest }: AddDatasetModalProps) { | function AddDatasetModal({ typeList, tagList, onOk, ...rest }: AddDatasetModalProps) { | ||||
| const [uuid] = useState(Date.now()); | const [uuid] = useState(Date.now()); | ||||
| const [clusterOptions, setClusterOptions] = useState<DictValueEnumObj[]>([]); | |||||
| // const [clusterOptions, setClusterOptions] = useState<DictValueEnumObj[]>([]); | |||||
| useEffect(() => { | |||||
| getClusterOptions(); | |||||
| }, []); | |||||
| // useEffect(() => { | |||||
| // getClusterOptions(); | |||||
| // }, []); | |||||
| // 上传组件参数 | // 上传组件参数 | ||||
| const uploadProps: UploadProps = { | const uploadProps: UploadProps = { | ||||
| action: '/api/mmp/dataset/upload', | |||||
| action: resourceConfig[ResourceType.Dataset].uploadAction, | |||||
| headers: { | headers: { | ||||
| Authorization: getAccessToken() || '', | Authorization: getAccessToken() || '', | ||||
| }, | }, | ||||
| @@ -47,16 +45,16 @@ function AddDatasetModal({ typeList, tagList, onOk, ...rest }: AddDatasetModalPr | |||||
| }; | }; | ||||
| // 获取集群版本数据 | // 获取集群版本数据 | ||||
| const getClusterOptions = async () => { | |||||
| const [res] = await to(getDictSelectOption('available_cluster')); | |||||
| if (res) { | |||||
| setClusterOptions(res); | |||||
| } | |||||
| }; | |||||
| // const getClusterOptions = async () => { | |||||
| // const [res] = await to(getDictSelectOption('available_cluster')); | |||||
| // if (res) { | |||||
| // setClusterOptions(res); | |||||
| // } | |||||
| // }; | |||||
| // 上传请求 | // 上传请求 | ||||
| const createDataset = async (params: any) => { | const createDataset = async (params: any) => { | ||||
| const [res] = await to(addDatesetAndVesion(params)); | |||||
| const [res] = await to(addDataset(params)); | |||||
| if (res) { | if (res) { | ||||
| message.success('创建成功'); | message.success('创建成功'); | ||||
| onOk?.(); | onOk?.(); | ||||
| @@ -69,6 +67,7 @@ function AddDatasetModal({ typeList, tagList, onOk, ...rest }: AddDatasetModalPr | |||||
| if (validateUploadFiles(fileList)) { | if (validateUploadFiles(fileList)) { | ||||
| const params = { | const params = { | ||||
| ...omit(formData, ['fileList']), | ...omit(formData, ['fileList']), | ||||
| dataset_source: DataSource.Create, | |||||
| dataset_version_vos: fileList.map((item) => { | dataset_version_vos: fileList.map((item) => { | ||||
| const data = item.response?.data?.[0] ?? {}; | const data = item.response?.data?.[0] ?? {}; | ||||
| return { | return { | ||||
| @@ -94,7 +93,13 @@ function AddDatasetModal({ typeList, tagList, onOk, ...rest }: AddDatasetModalPr | |||||
| }} | }} | ||||
| destroyOnClose | destroyOnClose | ||||
| > | > | ||||
| <Form name="form" layout="vertical" onFinish={onFinish} autoComplete="off"> | |||||
| <Form | |||||
| name="form" | |||||
| layout="vertical" | |||||
| onFinish={onFinish} | |||||
| initialValues={{ is_public: false }} | |||||
| autoComplete="off" | |||||
| > | |||||
| <Form.Item | <Form.Item | ||||
| label="数据集名称" | label="数据集名称" | ||||
| name="name" | name="name" | ||||
| @@ -106,7 +111,7 @@ function AddDatasetModal({ typeList, tagList, onOk, ...rest }: AddDatasetModalPr | |||||
| }, | }, | ||||
| ]} | ]} | ||||
| > | > | ||||
| <Input placeholder="请输入数据名称" showCount allowClear maxLength={64} /> | |||||
| <Input placeholder="请输入数据名称" showCount allowClear maxLength={50} /> | |||||
| </Form.Item> | </Form.Item> | ||||
| <Form.Item | <Form.Item | ||||
| label="数据集版本" | label="数据集版本" | ||||
| @@ -116,6 +121,18 @@ function AddDatasetModal({ typeList, tagList, onOk, ...rest }: AddDatasetModalPr | |||||
| required: true, | required: true, | ||||
| message: '请输入数据集版本', | message: '请输入数据集版本', | ||||
| }, | }, | ||||
| { | |||||
| pattern: /^[a-zA-Z0-9._-]+$/, | |||||
| message: '版本只支持字母、数字、下划线、点、横杠', | |||||
| }, | |||||
| { | |||||
| validator: (_rule, value) => { | |||||
| if (value === 'master') { | |||||
| return Promise.reject(`版本不能为 master`); | |||||
| } | |||||
| return Promise.resolve(); | |||||
| }, | |||||
| }, | |||||
| ]} | ]} | ||||
| > | > | ||||
| <Input placeholder="请输入数据集版本" showCount allowClear maxLength={64} /> | <Input placeholder="请输入数据集版本" showCount allowClear maxLength={64} /> | ||||
| @@ -125,7 +142,7 @@ function AddDatasetModal({ typeList, tagList, onOk, ...rest }: AddDatasetModalPr | |||||
| allowClear | allowClear | ||||
| placeholder="请选择数据集分类" | placeholder="请选择数据集分类" | ||||
| options={typeList} | options={typeList} | ||||
| fieldNames={{ label: 'name', value: 'id' }} | |||||
| fieldNames={{ label: 'name', value: 'name' }} | |||||
| optionFilterProp="name" | optionFilterProp="name" | ||||
| showSearch | showSearch | ||||
| /> | /> | ||||
| @@ -135,14 +152,14 @@ function AddDatasetModal({ typeList, tagList, onOk, ...rest }: AddDatasetModalPr | |||||
| allowClear | allowClear | ||||
| placeholder="请选择研究方向/应用领域" | placeholder="请选择研究方向/应用领域" | ||||
| options={tagList} | options={tagList} | ||||
| fieldNames={{ label: 'name', value: 'id' }} | |||||
| fieldNames={{ label: 'name', value: 'name' }} | |||||
| optionFilterProp="name" | optionFilterProp="name" | ||||
| showSearch | showSearch | ||||
| /> | /> | ||||
| </Form.Item> | </Form.Item> | ||||
| <Form.Item label="集群版本" name="available_cluster"> | |||||
| {/* <Form.Item label="集群版本" name="available_cluster"> | |||||
| <Select allowClear placeholder="请选择集群版本" options={clusterOptions} /> | <Select allowClear placeholder="请选择集群版本" options={clusterOptions} /> | ||||
| </Form.Item> | |||||
| </Form.Item> */} | |||||
| <Form.Item | <Form.Item | ||||
| label="数据集简介" | label="数据集简介" | ||||
| name="description" | name="description" | ||||
| @@ -156,15 +173,19 @@ function AddDatasetModal({ typeList, tagList, onOk, ...rest }: AddDatasetModalPr | |||||
| <Input.TextArea | <Input.TextArea | ||||
| placeholder="请输入数据集简介" | placeholder="请输入数据集简介" | ||||
| showCount | showCount | ||||
| maxLength={256} | |||||
| maxLength={200} | |||||
| autoSize={{ minRows: 2, maxRows: 6 }} | autoSize={{ minRows: 2, maxRows: 6 }} | ||||
| allowClear | allowClear | ||||
| /> | /> | ||||
| </Form.Item> | </Form.Item> | ||||
| <Form.Item label="选择流水线" name="range"> | |||||
| <Form.Item | |||||
| label="可见性" | |||||
| name="is_public" | |||||
| rules={[{ required: true, message: '请选择可见性' }]} | |||||
| > | |||||
| <Radio.Group> | <Radio.Group> | ||||
| <Radio value="0">仅自己可见</Radio> | |||||
| <Radio value="1">工作空间可见</Radio> | |||||
| <Radio value={false}>私有</Radio> | |||||
| <Radio value={true}>公开</Radio> | |||||
| </Radio.Group> | </Radio.Group> | ||||
| </Form.Item> | </Form.Item> | ||||
| <Form.Item | <Form.Item | ||||
| @@ -187,7 +208,7 @@ function AddDatasetModal({ typeList, tagList, onOk, ...rest }: AddDatasetModalPr | |||||
| > | > | ||||
| 上传文件 | 上传文件 | ||||
| </Button> | </Button> | ||||
| <div className={styles['upload-tip']}>只允许上传.zip,.tgz格式文件</div> | |||||
| <div className={styles['upload-tip']}>只允许上传 .zip 和 .tgz 格式文件</div> | |||||
| </Upload> | </Upload> | ||||
| </Form.Item> | </Form.Item> | ||||
| </Form> | </Form> | ||||
| @@ -1,7 +1,7 @@ | |||||
| import { getAccessToken } from '@/access'; | import { getAccessToken } from '@/access'; | ||||
| import KFIcon from '@/components/KFIcon'; | import KFIcon from '@/components/KFIcon'; | ||||
| import KFModal from '@/components/KFModal'; | import KFModal from '@/components/KFModal'; | ||||
| import { CategoryData } from '@/pages/Dataset/config'; | |||||
| import { CategoryData, DataSource, ResourceType, resourceConfig } from '@/pages/Dataset/config'; | |||||
| import { addModel } from '@/services/dataset/index.js'; | import { addModel } from '@/services/dataset/index.js'; | ||||
| import { to } from '@/utils/promise'; | import { to } from '@/utils/promise'; | ||||
| import { getFileListFromEvent, validateUploadFiles } from '@/utils/ui'; | import { getFileListFromEvent, validateUploadFiles } from '@/utils/ui'; | ||||
| @@ -9,6 +9,7 @@ import { | |||||
| Button, | Button, | ||||
| Form, | Form, | ||||
| Input, | Input, | ||||
| Radio, | |||||
| Select, | Select, | ||||
| Upload, | Upload, | ||||
| UploadFile, | UploadFile, | ||||
| @@ -31,7 +32,7 @@ function AddModelModal({ typeList, tagList, onOk, ...rest }: AddModelModalProps) | |||||
| // 上传组件参数 | // 上传组件参数 | ||||
| const uploadProps: UploadProps = { | const uploadProps: UploadProps = { | ||||
| action: '/api/mmp/models/upload', | |||||
| action: resourceConfig[ResourceType.Model].uploadAction, | |||||
| headers: { | headers: { | ||||
| Authorization: getAccessToken() || '', | Authorization: getAccessToken() || '', | ||||
| }, | }, | ||||
| @@ -53,7 +54,8 @@ function AddModelModal({ typeList, tagList, onOk, ...rest }: AddModelModalProps) | |||||
| if (validateUploadFiles(fileList)) { | if (validateUploadFiles(fileList)) { | ||||
| const params = { | const params = { | ||||
| ...omit(formData, ['fileList']), | ...omit(formData, ['fileList']), | ||||
| models_version_vos: fileList.map((item) => { | |||||
| model_source: DataSource.Create, | |||||
| model_version_vos: fileList.map((item) => { | |||||
| const data = item.response?.data?.[0] ?? {}; | const data = item.response?.data?.[0] ?? {}; | ||||
| return { | return { | ||||
| file_name: data.fileName, | file_name: data.fileName, | ||||
| @@ -77,7 +79,13 @@ function AddModelModal({ typeList, tagList, onOk, ...rest }: AddModelModalProps) | |||||
| form: 'form', | form: 'form', | ||||
| }} | }} | ||||
| > | > | ||||
| <Form name="form" layout="vertical" onFinish={onFinish} autoComplete="off"> | |||||
| <Form | |||||
| name="form" | |||||
| layout="vertical" | |||||
| onFinish={onFinish} | |||||
| autoComplete="off" | |||||
| initialValues={{ is_public: false }} | |||||
| > | |||||
| <Form.Item | <Form.Item | ||||
| label="模型名称" | label="模型名称" | ||||
| name="name" | name="name" | ||||
| @@ -88,9 +96,8 @@ function AddModelModal({ typeList, tagList, onOk, ...rest }: AddModelModalProps) | |||||
| }, | }, | ||||
| ]} | ]} | ||||
| > | > | ||||
| <Input placeholder="请输入模型名称" showCount allowClear maxLength={64} /> | |||||
| <Input placeholder="请输入模型名称" showCount allowClear maxLength={50} /> | |||||
| </Form.Item> | </Form.Item> | ||||
| <Form.Item | <Form.Item | ||||
| label="模型版本" | label="模型版本" | ||||
| name="version" | name="version" | ||||
| @@ -99,40 +106,28 @@ function AddModelModal({ typeList, tagList, onOk, ...rest }: AddModelModalProps) | |||||
| required: true, | required: true, | ||||
| message: '请输入模型版本', | message: '请输入模型版本', | ||||
| }, | }, | ||||
| ]} | |||||
| > | |||||
| <Input placeholder="请输入模型版本" allowClear maxLength={64} /> | |||||
| </Form.Item> | |||||
| <Form.Item | |||||
| label="模型简介" | |||||
| name="description" | |||||
| rules={[ | |||||
| { | { | ||||
| required: true, | |||||
| message: '请输入模型简介', | |||||
| pattern: /^[a-zA-Z0-9._-]+$/, | |||||
| message: '版本只支持字母、数字、下划线、点、横杠', | |||||
| }, | |||||
| { | |||||
| validator: (_rule, value) => { | |||||
| if (value === 'master') { | |||||
| return Promise.reject(`版本不能为 master`); | |||||
| } | |||||
| return Promise.resolve(); | |||||
| }, | |||||
| }, | }, | ||||
| ]} | ]} | ||||
| > | > | ||||
| <Input.TextArea | |||||
| placeholder="请输入模型简介" | |||||
| showCount | |||||
| maxLength={256} | |||||
| autoSize={{ minRows: 2, maxRows: 6 }} | |||||
| allowClear | |||||
| /> | |||||
| <Input placeholder="请输入模型版本" showCount allowClear maxLength={64} /> | |||||
| </Form.Item> | </Form.Item> | ||||
| {/* <Form.Item label="可见范围" name="available_range"> | |||||
| <Radio.Group> | |||||
| <Radio value="0">仅自己可见</Radio> | |||||
| <Radio value="1">工作空间可见</Radio> | |||||
| </Radio.Group> | |||||
| </Form.Item> */} | |||||
| <Form.Item label="模型框架" name="model_type"> | <Form.Item label="模型框架" name="model_type"> | ||||
| <Select | <Select | ||||
| allowClear | allowClear | ||||
| placeholder="请选择模型类型" | placeholder="请选择模型类型" | ||||
| options={typeList} | options={typeList} | ||||
| fieldNames={{ label: 'name', value: 'id' }} | |||||
| fieldNames={{ label: 'name', value: 'name' }} | |||||
| optionFilterProp="name" | optionFilterProp="name" | ||||
| showSearch | showSearch | ||||
| /> | /> | ||||
| @@ -142,11 +137,39 @@ function AddModelModal({ typeList, tagList, onOk, ...rest }: AddModelModalProps) | |||||
| allowClear | allowClear | ||||
| placeholder="请选择模型标签" | placeholder="请选择模型标签" | ||||
| options={tagList} | options={tagList} | ||||
| fieldNames={{ label: 'name', value: 'id' }} | |||||
| fieldNames={{ label: 'name', value: 'name' }} | |||||
| optionFilterProp="name" | optionFilterProp="name" | ||||
| showSearch | showSearch | ||||
| /> | /> | ||||
| </Form.Item> | </Form.Item> | ||||
| <Form.Item | |||||
| label="模型简介" | |||||
| name="description" | |||||
| rules={[ | |||||
| { | |||||
| required: true, | |||||
| message: '请输入模型简介', | |||||
| }, | |||||
| ]} | |||||
| > | |||||
| <Input.TextArea | |||||
| placeholder="请输入模型简介" | |||||
| maxLength={200} | |||||
| autoSize={{ minRows: 2, maxRows: 6 }} | |||||
| showCount | |||||
| allowClear | |||||
| /> | |||||
| </Form.Item> | |||||
| <Form.Item | |||||
| label="可见性" | |||||
| name="is_public" | |||||
| rules={[{ required: true, message: '请选择可见性' }]} | |||||
| > | |||||
| <Radio.Group> | |||||
| <Radio value={false}>私有</Radio> | |||||
| <Radio value={true}>公开</Radio> | |||||
| </Radio.Group> | |||||
| </Form.Item> | |||||
| <Form.Item | <Form.Item | ||||
| label="模型文件" | label="模型文件" | ||||
| name="fileList" | name="fileList" | ||||
| @@ -1,7 +1,7 @@ | |||||
| import { getAccessToken } from '@/access'; | import { getAccessToken } from '@/access'; | ||||
| import KFIcon from '@/components/KFIcon'; | import KFIcon from '@/components/KFIcon'; | ||||
| import KFModal from '@/components/KFModal'; | import KFModal from '@/components/KFModal'; | ||||
| import { ResourceType, resourceConfig } from '@/pages/Dataset/config'; | |||||
| import { DataSource, ResourceType, resourceConfig } from '@/pages/Dataset/config'; | |||||
| import { to } from '@/utils/promise'; | import { to } from '@/utils/promise'; | ||||
| import { getFileListFromEvent, validateUploadFiles } from '@/utils/ui'; | import { getFileListFromEvent, validateUploadFiles } from '@/utils/ui'; | ||||
| import { | import { | ||||
| @@ -21,14 +21,16 @@ import styles from '../AddDatasetModal/index.less'; | |||||
| interface AddVersionModalProps extends Omit<ModalProps, 'onOk'> { | interface AddVersionModalProps extends Omit<ModalProps, 'onOk'> { | ||||
| resourceType: ResourceType; | resourceType: ResourceType; | ||||
| resourceId: number; | resourceId: number; | ||||
| initialName: string; | |||||
| identifier: string; | |||||
| resoureName: string; | |||||
| onOk: () => void; | onOk: () => void; | ||||
| } | } | ||||
| function AddVersionModal({ | function AddVersionModal({ | ||||
| resourceType, | resourceType, | ||||
| resourceId, | resourceId, | ||||
| initialName, | |||||
| resoureName, | |||||
| identifier, | |||||
| onOk, | onOk, | ||||
| ...rest | ...rest | ||||
| }: AddVersionModalProps) { | }: AddVersionModalProps) { | ||||
| @@ -58,17 +60,21 @@ function AddVersionModal({ | |||||
| const onFinish = (formData: any) => { | const onFinish = (formData: any) => { | ||||
| const fileList: UploadFile[] = formData['fileList'] ?? []; | const fileList: UploadFile[] = formData['fileList'] ?? []; | ||||
| if (validateUploadFiles(fileList)) { | if (validateUploadFiles(fileList)) { | ||||
| const otherParams = omit(formData, ['fileList']); | |||||
| const params = fileList.map((item) => { | |||||
| const version_vos = fileList.map((item) => { | |||||
| const data = item.response?.data?.[0] ?? {}; | const data = item.response?.data?.[0] ?? {}; | ||||
| return { | return { | ||||
| ...otherParams, | |||||
| [config.idParamKey]: resourceId, | |||||
| file_name: data.fileName, | file_name: data.fileName, | ||||
| file_size: data.fileSize, | file_size: data.fileSize, | ||||
| url: data.url, | url: data.url, | ||||
| }; | }; | ||||
| }); | }); | ||||
| const params = { | |||||
| id: resourceId, | |||||
| identifier, | |||||
| [config.filePropKey]: version_vos, | |||||
| ...omit(formData, 'fileList'), | |||||
| [config.sourceParamKey]: DataSource.Create, | |||||
| }; | |||||
| createDatasetVersion(params); | createDatasetVersion(params); | ||||
| } | } | ||||
| }; | }; | ||||
| @@ -90,7 +96,7 @@ function AddVersionModal({ | |||||
| name="form" | name="form" | ||||
| layout="vertical" | layout="vertical" | ||||
| initialValues={{ | initialValues={{ | ||||
| name: initialName, | |||||
| name: resoureName, | |||||
| }} | }} | ||||
| onFinish={onFinish} | onFinish={onFinish} | ||||
| autoComplete="off" | autoComplete="off" | ||||
| @@ -115,13 +121,25 @@ function AddVersionModal({ | |||||
| required: true, | required: true, | ||||
| message: `请输入${name}版本`, | message: `请输入${name}版本`, | ||||
| }, | }, | ||||
| { | |||||
| pattern: /^[a-zA-Z0-9._-]+$/, | |||||
| message: '版本只支持字母、数字、下划线、点、横杠', | |||||
| }, | |||||
| { | |||||
| validator: (_rule, value) => { | |||||
| if (value === 'master') { | |||||
| return Promise.reject(`版本不能为 master`); | |||||
| } | |||||
| return Promise.resolve(); | |||||
| }, | |||||
| }, | |||||
| ]} | ]} | ||||
| > | > | ||||
| <Input placeholder={`请输入${name}版本`} maxLength={64} showCount allowClear /> | <Input placeholder={`请输入${name}版本`} maxLength={64} showCount allowClear /> | ||||
| </Form.Item> | </Form.Item> | ||||
| <Form.Item | <Form.Item | ||||
| label="版本描述" | label="版本描述" | ||||
| name="description" | |||||
| name="version_desc" | |||||
| rules={[ | rules={[ | ||||
| { | { | ||||
| required: true, | required: true, | ||||
| @@ -132,7 +150,7 @@ function AddVersionModal({ | |||||
| <Input.TextArea | <Input.TextArea | ||||
| placeholder="请输入版本描述" | placeholder="请输入版本描述" | ||||
| autoSize={{ minRows: 2, maxRows: 6 }} | autoSize={{ minRows: 2, maxRows: 6 }} | ||||
| maxLength={256} | |||||
| maxLength={200} | |||||
| showCount | showCount | ||||
| allowClear | allowClear | ||||
| /> | /> | ||||
| @@ -158,7 +176,7 @@ function AddVersionModal({ | |||||
| 上传文件 | 上传文件 | ||||
| </Button> | </Button> | ||||
| {resourceType === ResourceType.Dataset && ( | {resourceType === ResourceType.Dataset && ( | ||||
| <div className={styles['upload-tip']}>只允许上传.zip格式文件</div> | |||||
| <div className={styles['upload-tip']}>只允许上传 .zip 格式文件</div> | |||||
| )} | )} | ||||
| </Upload> | </Upload> | ||||
| </Form.Item> | </Form.Item> | ||||
| @@ -23,12 +23,14 @@ function CategoryItem({ resourceType, item, isSelected, onClick }: CategoryItemP | |||||
| style={{ width: '22px' }} | style={{ width: '22px' }} | ||||
| src={`/assets/images/${config.prefix}/${item.path}.png`} | src={`/assets/images/${config.prefix}/${item.path}.png`} | ||||
| alt="" | alt="" | ||||
| draggable={false} | |||||
| /> | /> | ||||
| <img | <img | ||||
| className={styles['category-item__active-icon']} | className={styles['category-item__active-icon']} | ||||
| style={{ width: '22px' }} | style={{ width: '22px' }} | ||||
| src={`/assets/images/${config.prefix}/${item.path}-hover.png`} | src={`/assets/images/${config.prefix}/${item.path}-hover.png`} | ||||
| alt="" | alt="" | ||||
| draggable={false} | |||||
| /> | /> | ||||
| <span className={styles['category-item__name']}>{item.name}</span> | <span className={styles['category-item__name']}>{item.name}</span> | ||||
| </div> | </div> | ||||
| @@ -3,17 +3,12 @@ import { CategoryData, ResourceType, resourceConfig } from '../../config'; | |||||
| import CategoryItem from '../CategoryItem'; | import CategoryItem from '../CategoryItem'; | ||||
| import styles from './index.less'; | import styles from './index.less'; | ||||
| export type CategoryValue = { | |||||
| dataType: number | undefined; | |||||
| dataTag: number | undefined; | |||||
| }; | |||||
| type CategoryProps = { | type CategoryProps = { | ||||
| resourceType: ResourceType; // 资源类型,数据集还是模型 | resourceType: ResourceType; // 资源类型,数据集还是模型 | ||||
| typeList: CategoryData[]; | typeList: CategoryData[]; | ||||
| tagList: CategoryData[]; | tagList: CategoryData[]; | ||||
| activeType?: number; | |||||
| activeTag?: number; | |||||
| activeType?: string; | |||||
| activeTag?: string; | |||||
| onTypeSelect: (value: CategoryData) => void; | onTypeSelect: (value: CategoryData) => void; | ||||
| onTagSelect: (value: CategoryData) => void; | onTagSelect: (value: CategoryData) => void; | ||||
| onSearch: (value: string) => void; | onSearch: (value: string) => void; | ||||
| @@ -44,7 +39,7 @@ function CategoryList({ | |||||
| resourceType={resourceType} | resourceType={resourceType} | ||||
| item={item} | item={item} | ||||
| onClick={onTypeSelect} | onClick={onTypeSelect} | ||||
| isSelected={item.id === activeType} | |||||
| isSelected={item.name === activeType} | |||||
| ></CategoryItem> | ></CategoryItem> | ||||
| ))} | ))} | ||||
| </Flex> | </Flex> | ||||
| @@ -58,7 +53,7 @@ function CategoryList({ | |||||
| resourceType={resourceType} | resourceType={resourceType} | ||||
| item={item} | item={item} | ||||
| onClick={onTagSelect} | onClick={onTagSelect} | ||||
| isSelected={item.id === activeTag} | |||||
| isSelected={item.name === activeTag} | |||||
| ></CategoryItem> | ></CategoryItem> | ||||
| ))} | ))} | ||||
| </Flex> | </Flex> | ||||
| @@ -0,0 +1,68 @@ | |||||
| .resource-info { | |||||
| height: 100%; | |||||
| &__top { | |||||
| width: 100%; | |||||
| height: 125px; | |||||
| margin-bottom: 10px; | |||||
| padding: 20px 30px; | |||||
| background-image: url(@/assets/img/dataset-intro-top.png); | |||||
| background-repeat: no-repeat; | |||||
| background-position: top center; | |||||
| background-size: 100% 100%; | |||||
| &__name { | |||||
| margin-right: 10px; | |||||
| color: @text-color; | |||||
| font-weight: 500; | |||||
| font-size: 20px; | |||||
| .singleLine(); | |||||
| } | |||||
| &__tag { | |||||
| flex: none; | |||||
| padding: 4px 10px; | |||||
| color: @primary-color; | |||||
| font-size: 14px; | |||||
| background: .addAlpha(@primary-color, 0.1) []; | |||||
| border-radius: 4px; | |||||
| } | |||||
| :global { | |||||
| .ant-btn-dangerous { | |||||
| background-color: transparent !important; | |||||
| } | |||||
| } | |||||
| } | |||||
| &__bottom { | |||||
| position: relative; | |||||
| height: calc(100% - 135px); | |||||
| padding: 8px 30px 20px; | |||||
| background: #ffffff; | |||||
| border-radius: 10px; | |||||
| box-shadow: 0px 2px 12px rgba(180, 182, 191, 0.09); | |||||
| &__legend { | |||||
| position: absolute; | |||||
| top: 20px; | |||||
| right: 30px; | |||||
| } | |||||
| :global { | |||||
| .ant-tabs { | |||||
| height: 100%; | |||||
| .ant-tabs-content-holder { | |||||
| height: 100%; | |||||
| .ant-tabs-content { | |||||
| height: 100%; | |||||
| .ant-tabs-tabpane { | |||||
| height: 100%; | |||||
| overflow-y: auto; | |||||
| } | |||||
| } | |||||
| } | |||||
| } | |||||
| } | |||||
| } | |||||
| } | |||||
| @@ -0,0 +1,247 @@ | |||||
| /* | |||||
| * @Author: 赵伟 | |||||
| * @Date: 2024-09-06 09:23:15 | |||||
| * @Description: 数据集、模型详情 | |||||
| */ | |||||
| import KFIcon from '@/components/KFIcon'; | |||||
| import { | |||||
| ResourceData, | |||||
| ResourceType, | |||||
| ResourceVersionData, | |||||
| resourceConfig, | |||||
| } from '@/pages/Dataset/config'; | |||||
| import GraphLegend from '@/pages/Model/components/GraphLegend'; | |||||
| import ModelEvolution from '@/pages/Model/components/ModelEvolution'; | |||||
| import { openAntdModal } from '@/utils/modal'; | |||||
| import { to } from '@/utils/promise'; | |||||
| import { modalConfirm } from '@/utils/ui'; | |||||
| import { useParams, useSearchParams } from '@umijs/max'; | |||||
| import { App, Button, Flex, Select, Tabs } from 'antd'; | |||||
| import { useEffect, useState } from 'react'; | |||||
| import AddVersionModal from '../AddVersionModal'; | |||||
| import ResourceIntro from '../ResourceIntro'; | |||||
| import ResourceVersion from '../ResourceVersion'; | |||||
| import styles from './index.less'; | |||||
| // 这里值小写是因为值会写在 url 中 | |||||
| export enum ResourceInfoTabKeys { | |||||
| Introduction = 'introduction', // 简介 | |||||
| Version = 'version', // 版本 | |||||
| Evolution = 'evolution', // 演化 | |||||
| } | |||||
| type ResourceInfoProps = { | |||||
| resourceType: ResourceType; | |||||
| }; | |||||
| const ResourceInfo = ({ resourceType }: ResourceInfoProps) => { | |||||
| const [info, setInfo] = useState<ResourceData>({} as ResourceData); | |||||
| const locationParams = useParams(); | |||||
| const [searchParams] = useSearchParams(); | |||||
| const resourceId = Number(locationParams.id); | |||||
| // 模型演化传入的 tab | |||||
| const defaultTab = searchParams.get('tab') || ResourceInfoTabKeys.Introduction; | |||||
| // 模型演化传入的版本 | |||||
| let versionParam = searchParams.get('version'); | |||||
| const name = searchParams.get('name') || ''; | |||||
| const owner = searchParams.get('owner') || ''; | |||||
| const identifier = searchParams.get('identifier') || ''; | |||||
| const [versionList, setVersionList] = useState<ResourceVersionData[]>([]); | |||||
| const [version, setVersion] = useState<string | undefined>(undefined); | |||||
| const [activeTab, setActiveTab] = useState<string>(defaultTab); | |||||
| const config = resourceConfig[resourceType]; | |||||
| const typeName = config.name; // 数据集/模型 | |||||
| const { message } = App.useApp(); | |||||
| useEffect(() => { | |||||
| getVersionList(); | |||||
| }, [resourceId, owner, identifier]); | |||||
| useEffect(() => { | |||||
| if (version) { | |||||
| getResourceDetail({ | |||||
| id: resourceId, | |||||
| owner, | |||||
| name, | |||||
| identifier, | |||||
| version, | |||||
| }); | |||||
| } | |||||
| }, [version]); | |||||
| // 获取详情 | |||||
| const getResourceDetail = async (params: { | |||||
| owner: string; | |||||
| name: string; | |||||
| id: number; | |||||
| identifier: string; | |||||
| version?: string; | |||||
| }) => { | |||||
| const request = config.getInfo; | |||||
| const [res] = await to(request(params)); | |||||
| if (res && res.data) { | |||||
| setInfo(res.data); | |||||
| } | |||||
| }; | |||||
| // 获取版本列表 | |||||
| const getVersionList = async () => { | |||||
| const request = config.getVersions; | |||||
| const [res] = await to( | |||||
| request({ | |||||
| owner, | |||||
| identifier, | |||||
| }), | |||||
| ); | |||||
| if (res && res.data && res.data.length > 0) { | |||||
| setVersionList(res.data); | |||||
| if ( | |||||
| versionParam && | |||||
| res.data.find((item: ResourceVersionData) => item.name === versionParam) | |||||
| ) { | |||||
| setVersion(versionParam); | |||||
| versionParam = null; | |||||
| } else { | |||||
| setVersion(res.data[0].name); | |||||
| } | |||||
| } else { | |||||
| setVersion(undefined); | |||||
| } | |||||
| }; | |||||
| // 新建版本 | |||||
| const showModal = () => { | |||||
| const { close } = openAntdModal(AddVersionModal, { | |||||
| resourceType: resourceType, | |||||
| resourceId: resourceId, | |||||
| resoureName: info.name, | |||||
| identifier: info.identifier, | |||||
| onOk: () => { | |||||
| getVersionList(); | |||||
| close(); | |||||
| }, | |||||
| }); | |||||
| }; | |||||
| // 版本变化 | |||||
| const handleVersionChange = (value: string) => { | |||||
| setVersion(value); | |||||
| }; | |||||
| // 删除版本 | |||||
| const deleteVersion = async () => { | |||||
| const request = config.deleteVersion; | |||||
| const params = { | |||||
| id: resourceId, | |||||
| owner, | |||||
| identifier, | |||||
| relative_paths: info.relative_paths, | |||||
| version, | |||||
| }; | |||||
| const [res] = await to(request(params)); | |||||
| if (res) { | |||||
| message.success('删除成功'); | |||||
| getVersionList(); | |||||
| } | |||||
| }; | |||||
| // 处理删除 | |||||
| const hanldeDelete = () => { | |||||
| modalConfirm({ | |||||
| title: '删除后,该版本将不可恢复', | |||||
| content: '是否确认删除?', | |||||
| okText: '确认', | |||||
| cancelText: '取消', | |||||
| onOk: () => { | |||||
| deleteVersion(); | |||||
| }, | |||||
| }); | |||||
| }; | |||||
| const items = [ | |||||
| { | |||||
| key: ResourceInfoTabKeys.Introduction, | |||||
| label: `${typeName}简介`, | |||||
| icon: <KFIcon type="icon-moxingjianjie" />, | |||||
| children: <ResourceIntro resourceType={resourceType} info={info}></ResourceIntro>, | |||||
| }, | |||||
| { | |||||
| key: ResourceInfoTabKeys.Version, | |||||
| label: `${typeName}文件`, | |||||
| icon: <KFIcon type="icon-moxingwenjian" />, | |||||
| children: <ResourceVersion resourceType={resourceType} info={info}></ResourceVersion>, | |||||
| }, | |||||
| ]; | |||||
| if (resourceType === ResourceType.Model) { | |||||
| items.push({ | |||||
| key: ResourceInfoTabKeys.Evolution, | |||||
| label: `模型演化`, | |||||
| icon: <KFIcon type="icon-moxingyanhua1" />, | |||||
| children: ( | |||||
| <ModelEvolution | |||||
| resourceId={resourceId} | |||||
| version={version} | |||||
| identifier={identifier} | |||||
| isActive={activeTab === ResourceInfoTabKeys.Evolution} | |||||
| onVersionChange={handleVersionChange} | |||||
| ></ModelEvolution> | |||||
| ), | |||||
| }); | |||||
| } | |||||
| const typePropertyName = config.typeParamKey as keyof ResourceData; | |||||
| const tagPropertyName = config.tagParamKey as keyof ResourceData; | |||||
| return ( | |||||
| <div className={styles['resource-info']}> | |||||
| <div className={styles['resource-info__top']}> | |||||
| <Flex align="center" gap={10} style={{ marginBottom: '20px' }}> | |||||
| <div className={styles['resource-info__top__name']}>{info.name}</div> | |||||
| {info[typePropertyName] && ( | |||||
| <div className={styles['resource-info__top__tag']}> | |||||
| {(info[typePropertyName] as string) || '--'} | |||||
| </div> | |||||
| )} | |||||
| {info[tagPropertyName] && ( | |||||
| <div className={styles['resource-info__top__tag']}> | |||||
| {(info[tagPropertyName] as string) || '--'} | |||||
| </div> | |||||
| )} | |||||
| </Flex> | |||||
| <Flex align="center"> | |||||
| <span style={{ marginRight: '10px' }}>版本号:</span> | |||||
| <Select | |||||
| placeholder="请选择版本号" | |||||
| style={{ width: '160px', marginRight: '20px' }} | |||||
| value={version} | |||||
| onChange={handleVersionChange} | |||||
| fieldNames={{ label: 'name', value: 'name' }} | |||||
| options={versionList} | |||||
| /> | |||||
| <Button type="default" onClick={showModal} icon={<KFIcon type="icon-xinjian2" />}> | |||||
| 创建新版本 | |||||
| </Button> | |||||
| <Button | |||||
| type="default" | |||||
| style={{ marginLeft: 'auto', marginRight: 0 }} | |||||
| onClick={hanldeDelete} | |||||
| icon={<KFIcon type="icon-shanchu" />} | |||||
| disabled={!version} | |||||
| danger | |||||
| > | |||||
| 删除版本 | |||||
| </Button> | |||||
| </Flex> | |||||
| </div> | |||||
| <div className={styles['resource-info__bottom']}> | |||||
| <Tabs activeKey={activeTab} items={items} onChange={(key) => setActiveTab(key)}></Tabs> | |||||
| <div className={styles['resource-info__bottom__legend']}> | |||||
| {activeTab === ResourceInfoTabKeys.Evolution && <GraphLegend />} | |||||
| </div> | |||||
| </div> | |||||
| </div> | |||||
| ); | |||||
| }; | |||||
| export default ResourceInfo; | |||||
| @@ -1,65 +1,10 @@ | |||||
| .resource-intro { | .resource-intro { | ||||
| height: 100%; | |||||
| &__top { | |||||
| width: 100%; | |||||
| margin-top: 24px; | |||||
| &__basic { | |||||
| width: 100%; | width: 100%; | ||||
| height: 110px; | |||||
| margin-bottom: 10px; | |||||
| padding: 20px 30px 0; | |||||
| background-image: url(@/assets/img/dataset-intro-top.png); | |||||
| background-repeat: no-repeat; | |||||
| background-position: top center; | |||||
| background-size: 100% 100%; | |||||
| &__name { | |||||
| margin-bottom: 12px; | |||||
| color: @text-color; | |||||
| font-size: 20px; | |||||
| } | |||||
| &__tag { | |||||
| margin-right: 10px; | |||||
| padding: 4px 10px; | |||||
| color: @primary-color; | |||||
| font-size: 14px; | |||||
| background: rgba(22, 100, 255, 0.1); | |||||
| border-radius: 4px; | |||||
| } | |||||
| } | } | ||||
| &__bottom { | |||||
| height: calc(100% - 120px); | |||||
| padding: 8px 30px 20px; | |||||
| background: #ffffff; | |||||
| border-radius: 10px; | |||||
| box-shadow: 0px 2px 12px rgba(180, 182, 191, 0.09); | |||||
| :global { | |||||
| .ant-tabs { | |||||
| height: 100%; | |||||
| .ant-tabs-content-holder { | |||||
| height: 100%; | |||||
| .ant-tabs-content { | |||||
| height: 100%; | |||||
| .ant-tabs-tabpane { | |||||
| height: 100%; | |||||
| overflow-y: auto; | |||||
| } | |||||
| } | |||||
| } | |||||
| } | |||||
| } | |||||
| } | |||||
| &__title { | |||||
| margin: 30px 0 10px; | |||||
| color: @text-color; | |||||
| font-weight: 500; | |||||
| font-size: @font-size; | |||||
| } | |||||
| &__intro { | |||||
| color: @text-color-secondary; | |||||
| font-size: 14px; | |||||
| &__usage { | |||||
| width: 100%; | |||||
| } | } | ||||
| } | } | ||||
| @@ -1,156 +1,246 @@ | |||||
| import KFIcon from '@/components/KFIcon'; | |||||
| import ModelEvolution from '@/pages/Model/components/ModelEvolution'; | |||||
| import { to } from '@/utils/promise'; | |||||
| import { useParams, useSearchParams } from '@umijs/max'; | |||||
| import { Flex, Tabs } from 'antd'; | |||||
| import { useEffect, useState } from 'react'; | |||||
| import { ResourceData, ResourceType, resourceConfig } from '../../config'; | |||||
| import ResourceVersion from '../ResourceVersion'; | |||||
| import BasicInfo, { BasicInfoData } from '@/components/BasicInfo'; | |||||
| import SubAreaTitle from '@/components/SubAreaTitle'; | |||||
| import { ResourceInfoTabKeys } from '@/pages/Dataset/components/ResourceInfo'; | |||||
| import { | |||||
| DataSource, | |||||
| DatasetData, | |||||
| ModelData, | |||||
| ProjectDependency, | |||||
| ResourceType, | |||||
| TrainTask, | |||||
| } from '@/pages/Dataset/config'; | |||||
| import styles from './index.less'; | import styles from './index.less'; | ||||
| // 这里值小写是因为值会写在 url 中 | |||||
| export enum ResourceInfoTabKeys { | |||||
| Introduction = 'introduction', // 简介 | |||||
| Version = 'version', // 版本 | |||||
| Evolution = 'evolution', // 演化 | |||||
| } | |||||
| type ResourceIntroProps = { | type ResourceIntroProps = { | ||||
| resourceType: ResourceType; | resourceType: ResourceType; | ||||
| info: DatasetData | ModelData; | |||||
| }; | }; | ||||
| const ResourceIntro = ({ resourceType }: ResourceIntroProps) => { | |||||
| const [info, setInfo] = useState<ResourceData>({} as ResourceData); | |||||
| const locationParams = useParams(); | |||||
| const [searchParams] = useSearchParams(); | |||||
| const defaultTab = searchParams.get('tab') || ResourceInfoTabKeys.Introduction; | |||||
| let versionParam = searchParams.get('version'); | |||||
| const [versionList, setVersionList] = useState([]); | |||||
| const [version, setVersion] = useState<string | undefined>(undefined); | |||||
| const [activeTab, setActiveTab] = useState<string>(defaultTab); | |||||
| const resourceId = Number(locationParams.id); | |||||
| const config = resourceConfig[resourceType]; | |||||
| const typeName = config.name; // 数据集/模型 | |||||
| useEffect(() => { | |||||
| getModelByDetail(); | |||||
| getVersionList(); | |||||
| }, [resourceId]); | |||||
| const formatDataset = (datasets?: DatasetData[]) => { | |||||
| if (!datasets || datasets.length === 0) { | |||||
| return undefined; | |||||
| } | |||||
| return datasets.map((item) => ({ | |||||
| value: item.name, | |||||
| url: `${origin}/dataset/dataset/info/${item.id}?tab=${ResourceInfoTabKeys.Version}&version=${item.version}&name=${item.name}&owner=${item.owner}&identifier=${item.identifier}`, | |||||
| })); | |||||
| }; | |||||
| // 获取详情 | |||||
| const getModelByDetail = async () => { | |||||
| const request = config.getInfo; | |||||
| const [res] = await to(request(resourceId)); | |||||
| if (res) { | |||||
| setInfo(res.data); | |||||
| } | |||||
| }; | |||||
| const formatParams = (map?: Record<string, string>, space: string = '') => { | |||||
| if (!map || Object.keys(map).length === 0) { | |||||
| return undefined; | |||||
| } | |||||
| return Object.entries(map) | |||||
| .map(([key, value]) => `${space}${key} : ${value}`) | |||||
| .join('\n'); | |||||
| }; | |||||
| // 获取版本列表 | |||||
| const getVersionList = async () => { | |||||
| const request = config.getVersions; | |||||
| const [res] = await to(request(resourceId)); | |||||
| if (res && res.data && res.data.length > 0) { | |||||
| setVersionList( | |||||
| res.data.map((item: string) => { | |||||
| return { | |||||
| label: item, | |||||
| value: item, | |||||
| }; | |||||
| }), | |||||
| ); | |||||
| if (versionParam && res.data.includes(versionParam)) { | |||||
| setVersion(versionParam); | |||||
| versionParam = null; | |||||
| } else { | |||||
| setVersion(res.data[0]); | |||||
| const formatMetrics = (map?: Record<string, string>) => { | |||||
| if (!map || Object.keys(map).length === 0) { | |||||
| return undefined; | |||||
| } | |||||
| return Object.entries(map) | |||||
| .map(([key, value]) => { | |||||
| if (typeof value === 'object' && value !== null) { | |||||
| return `${key} : \n${formatParams(value, ' ')}`; | |||||
| } | } | ||||
| } else { | |||||
| setVersion(undefined); | |||||
| } | |||||
| }; | |||||
| return `${key} : ${value}`; | |||||
| }) | |||||
| .join('\n'); | |||||
| }; | |||||
| // 版本变化 | |||||
| const handleVersionChange = (value: string) => { | |||||
| setVersion(value); | |||||
| const getProjectUrl = (project?: ProjectDependency) => { | |||||
| if (!project || !project.url || !project.branch) { | |||||
| return undefined; | |||||
| } | |||||
| const { url, branch } = project; | |||||
| if (url.endsWith('.git')) { | |||||
| return `${url.substring(0, url.length - 4)}/tree/${branch}`; | |||||
| } | |||||
| }; | |||||
| const formatProject = (project?: ProjectDependency) => { | |||||
| if (!project) { | |||||
| return undefined; | |||||
| } | |||||
| return { | |||||
| value: project.name, | |||||
| url: getProjectUrl(project), | |||||
| }; | }; | ||||
| }; | |||||
| const items = [ | |||||
| { | |||||
| key: ResourceInfoTabKeys.Introduction, | |||||
| label: `${typeName}简介`, | |||||
| icon: <KFIcon type="icon-moxingjianjie" />, | |||||
| children: ( | |||||
| <> | |||||
| <div className={styles['resource-intro__title']}>简介</div> | |||||
| <div className={styles['resource-intro__intro']}>{info.description}</div> | |||||
| </> | |||||
| ), | |||||
| }, | |||||
| { | |||||
| key: ResourceInfoTabKeys.Version, | |||||
| label: `${typeName}文件/版本`, | |||||
| icon: <KFIcon type="icon-moxingwenjian" />, | |||||
| children: ( | |||||
| <ResourceVersion | |||||
| resourceType={resourceType} | |||||
| resourceId={resourceId} | |||||
| resourceName={info.name} | |||||
| isPublic={info.available_range === 1} | |||||
| versionList={versionList} | |||||
| version={version} | |||||
| isActive={activeTab === ResourceInfoTabKeys.Version} | |||||
| getVersionList={getVersionList} | |||||
| onVersionChange={handleVersionChange} | |||||
| ></ResourceVersion> | |||||
| ), | |||||
| }, | |||||
| ]; | |||||
| const formatTrainTask = (task?: TrainTask) => { | |||||
| if (!task) { | |||||
| return undefined; | |||||
| } | |||||
| return { | |||||
| value: task.name, | |||||
| url: `${origin}/pipeline/experiment/instance/${task.workflow_id}/${task.ins_id}`, | |||||
| }; | |||||
| }; | |||||
| if (resourceType === ResourceType.Model) { | |||||
| items.push({ | |||||
| key: ResourceInfoTabKeys.Evolution, | |||||
| label: `模型演化`, | |||||
| icon: <KFIcon type="icon-moxingyanhua1" />, | |||||
| children: ( | |||||
| <ModelEvolution | |||||
| resourceId={resourceId} | |||||
| versionList={versionList} | |||||
| version={version} | |||||
| isActive={activeTab === ResourceInfoTabKeys.Evolution} | |||||
| onVersionChange={handleVersionChange} | |||||
| ></ModelEvolution> | |||||
| ), | |||||
| }); | |||||
| const formatSource = (source?: string) => { | |||||
| if (source === DataSource.Create) { | |||||
| return '用户上传'; | |||||
| } else if (source === DataSource.HandExport) { | |||||
| return '手动导入'; | |||||
| } else if (source === DataSource.AtuoExport) { | |||||
| return '实验自动导入'; | |||||
| } | } | ||||
| return source; | |||||
| }; | |||||
| const infoTypePropertyName = config.infoTypePropertyName as keyof ResourceData; | |||||
| const infoTagPropertyName = config.infoTagPropertyName as keyof ResourceData; | |||||
| const getDatasetDatas = (data: DatasetData): BasicInfoData[] => [ | |||||
| { | |||||
| label: '数据集名称', | |||||
| value: data.name, | |||||
| }, | |||||
| { | |||||
| label: '版本', | |||||
| value: data.version, | |||||
| }, | |||||
| { | |||||
| label: '创建人', | |||||
| value: data.create_by, | |||||
| }, | |||||
| { | |||||
| label: '更新时间', | |||||
| value: data.update_time, | |||||
| }, | |||||
| { | |||||
| label: '数据来源', | |||||
| value: data.dataset_source, | |||||
| format: formatSource, | |||||
| }, | |||||
| { | |||||
| label: '训练任务', | |||||
| value: data.train_task, | |||||
| format: formatTrainTask, | |||||
| }, | |||||
| { | |||||
| label: '处理代码', | |||||
| value: data.processing_code, | |||||
| format: formatProject, | |||||
| }, | |||||
| { | |||||
| label: '数据集分类', | |||||
| value: data.data_type, | |||||
| }, | |||||
| { | |||||
| label: '研究方向', | |||||
| value: data.data_tag, | |||||
| }, | |||||
| { | |||||
| label: '数据集描述', | |||||
| value: data.description, | |||||
| }, | |||||
| { | |||||
| label: '版本描述', | |||||
| value: data.version_desc, | |||||
| }, | |||||
| ]; | |||||
| const getModelDatas = (data: ModelData): BasicInfoData[] => [ | |||||
| { | |||||
| label: '模型名称', | |||||
| value: data.name, | |||||
| }, | |||||
| { | |||||
| label: '版本', | |||||
| value: data.version, | |||||
| }, | |||||
| { | |||||
| label: '创建人', | |||||
| value: data.create_by, | |||||
| }, | |||||
| { | |||||
| label: '更新时间', | |||||
| value: data.update_time, | |||||
| }, | |||||
| { | |||||
| label: '训练镜像', | |||||
| value: data.image, | |||||
| }, | |||||
| { | |||||
| label: '训练代码', | |||||
| value: data.project_depency, | |||||
| format: formatProject, | |||||
| }, | |||||
| { | |||||
| label: '训练数据集', | |||||
| value: data.train_datasets, | |||||
| format: formatDataset, | |||||
| }, | |||||
| { | |||||
| label: '测试数据集', | |||||
| value: data.test_datasets, | |||||
| format: formatDataset, | |||||
| }, | |||||
| { | |||||
| label: '参数', | |||||
| value: data.params, | |||||
| format: formatParams, | |||||
| }, | |||||
| { | |||||
| label: '指标', | |||||
| value: data.metrics, | |||||
| format: formatMetrics, | |||||
| }, | |||||
| { | |||||
| label: '模型来源', | |||||
| value: data.model_source, | |||||
| format: formatSource, | |||||
| }, | |||||
| { | |||||
| label: '训练任务', | |||||
| value: data.train_task, | |||||
| format: formatTrainTask, | |||||
| }, | |||||
| { | |||||
| label: '模型框架', | |||||
| value: data.model_type, | |||||
| }, | |||||
| { | |||||
| label: '模型能力', | |||||
| value: data.model_tag, | |||||
| }, | |||||
| { | |||||
| label: '模型描述', | |||||
| value: data.description, | |||||
| }, | |||||
| { | |||||
| label: '版本描述', | |||||
| value: data.version_desc, | |||||
| }, | |||||
| ]; | |||||
| function ResourceIntro({ resourceType, info }: ResourceIntroProps) { | |||||
| const basicDatas: BasicInfoData[] = | |||||
| resourceType === ResourceType.Dataset | |||||
| ? getDatasetDatas(info as DatasetData) | |||||
| : getModelDatas(info as ModelData); | |||||
| return ( | return ( | ||||
| <div className={styles['resource-intro']}> | <div className={styles['resource-intro']}> | ||||
| <div className={styles['resource-intro__top']}> | |||||
| <div className={styles['resource-intro__top__name']}>{info.name}</div> | |||||
| <Flex align="center"> | |||||
| <div className={styles['resource-intro__top__tag']}> | |||||
| {typeName} id:{info.id} | |||||
| </div> | |||||
| {info[infoTypePropertyName] && ( | |||||
| <div className={styles['resource-intro__top__tag']}> | |||||
| {info[infoTypePropertyName] || '--'} | |||||
| </div> | |||||
| )} | |||||
| {info[infoTagPropertyName] && ( | |||||
| <div className={styles['resource-intro__top__tag']}> | |||||
| {info[infoTagPropertyName] || '--'} | |||||
| </div> | |||||
| )} | |||||
| </Flex> | |||||
| </div> | |||||
| <div className={styles['resource-intro__bottom']}> | |||||
| <Tabs activeKey={activeTab} items={items} onChange={(key) => setActiveTab(key)}></Tabs> | |||||
| <SubAreaTitle | |||||
| title="基本信息" | |||||
| image={require('@/assets/img/mirror-basic.png')} | |||||
| style={{ marginBottom: '26px' }} | |||||
| ></SubAreaTitle> | |||||
| <div className={styles['resource-intro__basic']}> | |||||
| <BasicInfo datas={basicDatas} labelWidth={86}></BasicInfo> | |||||
| </div> | </div> | ||||
| <SubAreaTitle | |||||
| title="实例用法" | |||||
| image={require('@/assets/img/usage-icon.png')} | |||||
| style={{ margin: '40px 0 24px' }} | |||||
| ></SubAreaTitle> | |||||
| <div | |||||
| className={styles['resource-intro__usage']} | |||||
| dangerouslySetInnerHTML={{ __html: info.usage ?? '暂无实例用法' }} | |||||
| ></div> | |||||
| </div> | </div> | ||||
| ); | ); | ||||
| }; | |||||
| } | |||||
| export default ResourceIntro; | export default ResourceIntro; | ||||
| @@ -39,12 +39,20 @@ function ResourceItem({ item, isPublic, onClick, onRemove }: ResourceItemProps) | |||||
| <div className={styles['resource-item__description']}>{item.description}</div> | <div className={styles['resource-item__description']}>{item.description}</div> | ||||
| <Flex justify="space-between"> | <Flex justify="space-between"> | ||||
| <div className={styles['resource-item__time']}> | <div className={styles['resource-item__time']}> | ||||
| <img style={{ width: '17px', marginRight: '6px' }} src={creatByImg} alt="" /> | |||||
| <span>{item.create_by}</span> | |||||
| <img | |||||
| style={{ width: '17px', marginRight: '6px' }} | |||||
| src={creatByImg} | |||||
| draggable={false} | |||||
| alt="" | |||||
| /> | |||||
| <span>{item.create_by ?? ''}</span> | |||||
| </div> | </div> | ||||
| <div className={styles['resource-item__time']}> | <div className={styles['resource-item__time']}> | ||||
| <img style={{ width: '12px', marginRight: '5px' }} src={clock} alt="" /> | |||||
| <span>最近更新: {formatDate(item.update_time, 'YYYY-MM-DD')}</span> | |||||
| <img style={{ width: '12px', marginRight: '5px' }} src={clock} draggable={false} alt="" /> | |||||
| <span> | |||||
| {'最近更新: '} | |||||
| {item.update_time ? formatDate(item.update_time, 'YYYY-MM-DD') : item.time_ago ?? ''} | |||||
| </span> | |||||
| </div> | </div> | ||||
| </Flex> | </Flex> | ||||
| </div> | </div> | ||||
| @@ -36,4 +36,8 @@ | |||||
| text-align: right; | text-align: right; | ||||
| } | } | ||||
| } | } | ||||
| &__empty { | |||||
| flex: 1; | |||||
| } | |||||
| } | } | ||||
| @@ -1,3 +1,4 @@ | |||||
| import KFEmpty, { EmptyType } from '@/components/KFEmpty'; | |||||
| import KFIcon from '@/components/KFIcon'; | import KFIcon from '@/components/KFIcon'; | ||||
| import { CommonTabKeys } from '@/enums'; | import { CommonTabKeys } from '@/enums'; | ||||
| import AddModelModal from '@/pages/Dataset/components/AddModelModal'; | import AddModelModal from '@/pages/Dataset/components/AddModelModal'; | ||||
| @@ -6,6 +7,7 @@ import { to } from '@/utils/promise'; | |||||
| import { modalConfirm } from '@/utils/ui'; | import { modalConfirm } from '@/utils/ui'; | ||||
| import { useNavigate } from '@umijs/max'; | import { useNavigate } from '@umijs/max'; | ||||
| import { App, Button, Input, Pagination, PaginationProps } from 'antd'; | import { App, Button, Input, Pagination, PaginationProps } from 'antd'; | ||||
| import { pick } from 'lodash'; | |||||
| import { Ref, forwardRef, useEffect, useImperativeHandle, useState } from 'react'; | import { Ref, forwardRef, useEffect, useImperativeHandle, useState } from 'react'; | ||||
| import { CategoryData, ResourceData, ResourceType, resourceConfig } from '../../config'; | import { CategoryData, ResourceData, ResourceType, resourceConfig } from '../../config'; | ||||
| import AddDatasetModal from '../AddDatasetModal'; | import AddDatasetModal from '../AddDatasetModal'; | ||||
| @@ -18,8 +20,8 @@ export type ResourceListRef = { | |||||
| type ResourceListProps = { | type ResourceListProps = { | ||||
| resourceType: ResourceType; | resourceType: ResourceType; | ||||
| dataType?: number; | |||||
| dataTag?: number; | |||||
| dataType?: string; | |||||
| dataTag?: string; | |||||
| isPublic: boolean; | isPublic: boolean; | ||||
| typeList: CategoryData[]; | typeList: CategoryData[]; | ||||
| tagList: CategoryData[]; | tagList: CategoryData[]; | ||||
| @@ -43,7 +45,7 @@ function ResourceList( | |||||
| ref: Ref<ResourceListRef>, | ref: Ref<ResourceListRef>, | ||||
| ) { | ) { | ||||
| const navigate = useNavigate(); | const navigate = useNavigate(); | ||||
| const [dataList, setDataList] = useState<ResourceData[]>([]); | |||||
| const [dataList, setDataList] = useState<ResourceData[] | undefined>(undefined); | |||||
| const [total, setTotal] = useState(0); | const [total, setTotal] = useState(0); | ||||
| const [pagination, setPagination] = useState<PaginationProps>( | const [pagination, setPagination] = useState<PaginationProps>( | ||||
| initialPagination ?? { | initialPagination ?? { | ||||
| @@ -71,7 +73,8 @@ function ResourceList( | |||||
| }); | }); | ||||
| setSearchText(''); | setSearchText(''); | ||||
| setInputText(''); | setInputText(''); | ||||
| setDataList([]); | |||||
| setDataList(undefined); | |||||
| setTotal(0); | |||||
| }, | }, | ||||
| }; | }; | ||||
| }, | }, | ||||
| @@ -80,12 +83,12 @@ function ResourceList( | |||||
| // 获取数据请求 | // 获取数据请求 | ||||
| const getDataList = async () => { | const getDataList = async () => { | ||||
| const params = { | |||||
| const params: Record<string, any> = { | |||||
| page: pagination.current! - 1, | page: pagination.current! - 1, | ||||
| size: pagination.pageSize, | size: pagination.pageSize, | ||||
| is_public: isPublic, | |||||
| [config.typeParamKey]: dataType, | [config.typeParamKey]: dataType, | ||||
| [config.tagParamKey]: dataTag, | [config.tagParamKey]: dataTag, | ||||
| available_range: isPublic ? 1 : 0, | |||||
| name: searchText !== '' ? searchText : undefined, | name: searchText !== '' ? searchText : undefined, | ||||
| }; | }; | ||||
| const request = config.getList; | const request = config.getList; | ||||
| @@ -93,13 +96,16 @@ function ResourceList( | |||||
| if (res && res.data && res.data.content) { | if (res && res.data && res.data.content) { | ||||
| setDataList(res.data.content); | setDataList(res.data.content); | ||||
| setTotal(res.data.totalElements); | setTotal(res.data.totalElements); | ||||
| } else { | |||||
| setDataList([]); | |||||
| setTotal(0); | |||||
| } | } | ||||
| }; | }; | ||||
| // 删除请求 | // 删除请求 | ||||
| const deleteRecord = async (id: number) => { | |||||
| const deleteRecord = async (params: { owner: string; identifier: string; repo_id?: number }) => { | |||||
| const request = config.deleteRecord; | const request = config.deleteRecord; | ||||
| const [res] = await to(request(id)); | |||||
| const [res] = await to(request(params)); | |||||
| if (res) { | if (res) { | ||||
| getDataList(); | getDataList(); | ||||
| message.success('删除成功'); | message.success('删除成功'); | ||||
| @@ -116,7 +122,7 @@ function ResourceList( | |||||
| modalConfirm({ | modalConfirm({ | ||||
| title: config.deleteModalTitle, | title: config.deleteModalTitle, | ||||
| onOk: () => { | onOk: () => { | ||||
| deleteRecord(record.id); | |||||
| deleteRecord(pick(record, ['owner', 'identifier', 'id'])); | |||||
| }, | }, | ||||
| }); | }); | ||||
| }; | }; | ||||
| @@ -131,7 +137,9 @@ function ResourceList( | |||||
| activeTag: dataTag, | activeTag: dataTag, | ||||
| }); | }); | ||||
| const prefix = config.prefix; | const prefix = config.prefix; | ||||
| navigate(`/dataset/${prefix}/info/${record.id}`); | |||||
| navigate( | |||||
| `/dataset/${prefix}/info/${record.id}?name=${record.name}&owner=${record.owner}&identifier=${record.identifier}`, | |||||
| ); | |||||
| }; | }; | ||||
| // 分页切换 | // 分页切换 | ||||
| @@ -158,7 +166,7 @@ function ResourceList( | |||||
| return ( | return ( | ||||
| <div className={styles['resource-list']}> | <div className={styles['resource-list']}> | ||||
| <div className={styles['resource-list__header']}> | <div className={styles['resource-list__header']}> | ||||
| <span>数据总数:{total}个</span> | |||||
| <span>数据总数:{total} 个</span> | |||||
| <div> | <div> | ||||
| <Input.Search | <Input.Search | ||||
| placeholder={`按${config.name}名称筛选`} | placeholder={`按${config.name}名称筛选`} | ||||
| @@ -182,26 +190,40 @@ function ResourceList( | |||||
| )} | )} | ||||
| </div> | </div> | ||||
| </div> | </div> | ||||
| <div className={styles['resource-list__content']}> | |||||
| {dataList?.map((item) => ( | |||||
| <ResourceItem | |||||
| item={item} | |||||
| key={item.id} | |||||
| isPublic={isPublic} | |||||
| onRemove={handleRemove} | |||||
| onClick={handleClick} | |||||
| ></ResourceItem> | |||||
| ))} | |||||
| </div> | |||||
| <Pagination | |||||
| total={total} | |||||
| showSizeChanger | |||||
| defaultPageSize={20} | |||||
| pageSizeOptions={[20, 40, 60, 80, 100]} | |||||
| showQuickJumper | |||||
| onChange={handlePageChange} | |||||
| {...pagination} | |||||
| /> | |||||
| {dataList && dataList.length > 0 && ( | |||||
| <> | |||||
| <div className={styles['resource-list__content']}> | |||||
| {dataList?.map((item) => ( | |||||
| <ResourceItem | |||||
| item={item} | |||||
| key={item.id} | |||||
| isPublic={isPublic} | |||||
| onRemove={handleRemove} | |||||
| onClick={handleClick} | |||||
| ></ResourceItem> | |||||
| ))} | |||||
| </div> | |||||
| <Pagination | |||||
| total={total} | |||||
| showSizeChanger | |||||
| defaultPageSize={20} | |||||
| pageSizeOptions={[20, 40, 60, 80, 100]} | |||||
| showQuickJumper | |||||
| onChange={handlePageChange} | |||||
| {...pagination} | |||||
| /> | |||||
| </> | |||||
| )} | |||||
| {dataList && dataList.length === 0 && ( | |||||
| <KFEmpty | |||||
| className={styles['resource-list__empty']} | |||||
| type={EmptyType.NoData} | |||||
| title="暂无数据" | |||||
| content={'很抱歉,没有搜索到您想要的内容\n建议刷新试试'} | |||||
| hasFooter={true} | |||||
| onRefresh={getDataList} | |||||
| /> | |||||
| )} | |||||
| </div> | </div> | ||||
| ); | ); | ||||
| } | } | ||||
| @@ -18,8 +18,8 @@ function ResourcePage({ resourceType }: ResourcePageProps) { | |||||
| const [activeTab, setActiveTab] = useState<string>(cacheState?.activeTab ?? CommonTabKeys.Public); | const [activeTab, setActiveTab] = useState<string>(cacheState?.activeTab ?? CommonTabKeys.Public); | ||||
| const [typeList, setTypeList] = useState<CategoryData[]>([]); | const [typeList, setTypeList] = useState<CategoryData[]>([]); | ||||
| const [tagList, setTagList] = useState<CategoryData[]>([]); | const [tagList, setTagList] = useState<CategoryData[]>([]); | ||||
| const [activeType, setActiveType] = useState<number | undefined>(cacheState?.activeType); | |||||
| const [activeTag, setActiveTag] = useState<number | undefined>(cacheState?.activeTag); | |||||
| const [activeType, setActiveType] = useState<string | undefined>(cacheState?.activeType); | |||||
| const [activeTag, setActiveTag] = useState<string | undefined>(cacheState?.activeTag); | |||||
| const dataListRef = useRef<ResourceListRef>(null); | const dataListRef = useRef<ResourceListRef>(null); | ||||
| const config = resourceConfig[resourceType]; | const config = resourceConfig[resourceType]; | ||||
| @@ -34,12 +34,12 @@ function ResourcePage({ resourceType }: ResourcePageProps) { | |||||
| // 选择类型 | // 选择类型 | ||||
| const chooseType = (record: CategoryData) => { | const chooseType = (record: CategoryData) => { | ||||
| setActiveType((prev) => (prev === record.id ? undefined : record.id)); | |||||
| setActiveType((prev) => (prev === record.name ? undefined : record.name)); | |||||
| }; | }; | ||||
| // 选择 Tag | // 选择 Tag | ||||
| const chooseTag = (record: CategoryData) => { | const chooseTag = (record: CategoryData) => { | ||||
| setActiveTag((prev) => (prev === record.id ? undefined : record.id)); | |||||
| setActiveTag((prev) => (prev === record.name ? undefined : record.name)); | |||||
| }; | }; | ||||
| // 获取分类 | // 获取分类 | ||||
| @@ -1,125 +1,41 @@ | |||||
| import CommonTableCell from '@/components/CommonTableCell'; | import CommonTableCell from '@/components/CommonTableCell'; | ||||
| import DateTableCell from '@/components/DateTableCell'; | import DateTableCell from '@/components/DateTableCell'; | ||||
| import KFIcon from '@/components/KFIcon'; | import KFIcon from '@/components/KFIcon'; | ||||
| import { useEffectWhen } from '@/hooks'; | |||||
| import AddVersionModal from '@/pages/Dataset/components/AddVersionModal'; | |||||
| import { | import { | ||||
| ResourceData, | |||||
| ResourceFileData, | ResourceFileData, | ||||
| ResourceType, | ResourceType, | ||||
| ResourceVersionData, | |||||
| resourceConfig, | resourceConfig, | ||||
| } from '@/pages/Dataset/config'; | } from '@/pages/Dataset/config'; | ||||
| import { downLoadZip } from '@/utils/downloadfile'; | import { downLoadZip } from '@/utils/downloadfile'; | ||||
| import { openAntdModal } from '@/utils/modal'; | |||||
| import { to } from '@/utils/promise'; | |||||
| import { modalConfirm } from '@/utils/ui'; | |||||
| import { App, Button, Flex, Select, Table } from 'antd'; | |||||
| import { useState } from 'react'; | |||||
| import { Button, Flex, Table } from 'antd'; | |||||
| import styles from './index.less'; | import styles from './index.less'; | ||||
| type ResourceVersionProps = { | type ResourceVersionProps = { | ||||
| resourceType: ResourceType; | resourceType: ResourceType; | ||||
| resourceId: number; | |||||
| resourceName: string; | |||||
| isPublic: boolean; | |||||
| versionList: ResourceVersionData[]; | |||||
| version?: string; | |||||
| isActive: boolean; | |||||
| getVersionList: () => void; | |||||
| onVersionChange: (version: string) => void; | |||||
| info: ResourceData; | |||||
| }; | }; | ||||
| function ResourceVersion({ | |||||
| resourceType, | |||||
| resourceId, | |||||
| resourceName, | |||||
| isPublic, | |||||
| versionList, | |||||
| version, | |||||
| isActive, | |||||
| getVersionList, | |||||
| onVersionChange, | |||||
| }: ResourceVersionProps) { | |||||
| const [fileList, setFileList] = useState<ResourceFileData[]>([]); | |||||
| const { message } = App.useApp(); | |||||
| function ResourceVersion({ resourceType, info }: ResourceVersionProps) { | |||||
| const config = resourceConfig[resourceType]; | const config = resourceConfig[resourceType]; | ||||
| // 获取版本文件列表 | |||||
| useEffectWhen( | |||||
| () => { | |||||
| if (version) { | |||||
| getFileList(version); | |||||
| } else { | |||||
| setFileList([]); | |||||
| } | |||||
| }, | |||||
| [resourceId, version], | |||||
| isActive, | |||||
| ); | |||||
| // 获取版本下的文件列表 | |||||
| const getFileList = async (version: string) => { | |||||
| const params = { | |||||
| version, | |||||
| [config.fileReqParamKey]: resourceId, | |||||
| }; | |||||
| const request = config.getFiles; | |||||
| const [res] = await to(request(params)); | |||||
| if (res) { | |||||
| setFileList(res?.data?.content ?? []); | |||||
| } | |||||
| }; | |||||
| // 删除版本 | |||||
| const deleteVersion = async () => { | |||||
| const request = config.deleteVersion; | |||||
| const params = { | |||||
| [config.idParamKey]: resourceId, | |||||
| version, | |||||
| }; | |||||
| const [res] = await to(request(params)); | |||||
| if (res) { | |||||
| getVersionList(); | |||||
| message.success('删除成功'); | |||||
| } | |||||
| }; | |||||
| // 新建版本 | |||||
| const showModal = () => { | |||||
| const { close } = openAntdModal(AddVersionModal, { | |||||
| resourceType: resourceType, | |||||
| resourceId: resourceId, | |||||
| initialName: resourceName, | |||||
| onOk: () => { | |||||
| getVersionList(); | |||||
| close(); | |||||
| }, | |||||
| }); | |||||
| }; | |||||
| // 处理删除 | |||||
| const hanldeDelete = () => { | |||||
| modalConfirm({ | |||||
| title: '删除后,该版本将不可恢复', | |||||
| content: '是否确认删除?', | |||||
| okText: '确认', | |||||
| cancelText: '取消', | |||||
| onOk: () => { | |||||
| deleteVersion(); | |||||
| }, | |||||
| }); | |||||
| }; | |||||
| const filePropKey = config.filePropKey as keyof ResourceData; | |||||
| const fileList = (info[filePropKey] ?? []) as ResourceFileData[]; | |||||
| fileList.forEach((item) => (item.update_time = info.update_time)); | |||||
| // 全部导出 | // 全部导出 | ||||
| const handleExport = async () => { | const handleExport = async () => { | ||||
| const url = config.downloadAllAction; | const url = config.downloadAllAction; | ||||
| downLoadZip(url, { models_id: resourceId, version }); | |||||
| downLoadZip(url, { | |||||
| name: info.name, | |||||
| id: info.id, | |||||
| version: info.version, | |||||
| identifier: info.identifier, | |||||
| }); | |||||
| }; | }; | ||||
| // 单个导出 | // 单个导出 | ||||
| const downloadAlone = (record: ResourceFileData) => { | |||||
| const downloadAlone = async (record: ResourceFileData) => { | |||||
| const url = config.downloadSingleAction; | const url = config.downloadSingleAction; | ||||
| downLoadZip(`${url}/${record.id}`); | |||||
| downLoadZip(url, { url: record.url }); | |||||
| }; | }; | ||||
| const columns = [ | const columns = [ | ||||
| @@ -142,12 +58,6 @@ function ResourceVersion({ | |||||
| </a> | </a> | ||||
| ), | ), | ||||
| }, | }, | ||||
| { | |||||
| title: '版本号', | |||||
| dataIndex: 'version', | |||||
| key: 'version', | |||||
| render: CommonTableCell(), | |||||
| }, | |||||
| { | { | ||||
| title: '文件大小', | title: '文件大小', | ||||
| dataIndex: 'file_size', | dataIndex: 'file_size', | ||||
| @@ -163,7 +73,7 @@ function ResourceVersion({ | |||||
| { | { | ||||
| title: '操作', | title: '操作', | ||||
| dataIndex: 'option', | dataIndex: 'option', | ||||
| width: '100px', | |||||
| width: 160, | |||||
| key: 'option', | key: 'option', | ||||
| render: (_: any, record: ResourceFileData) => [ | render: (_: any, record: ResourceFileData) => [ | ||||
| <Button | <Button | ||||
| @@ -183,32 +93,9 @@ function ResourceVersion({ | |||||
| <div className={styles['resource-version']}> | <div className={styles['resource-version']}> | ||||
| <Flex justify="space-between" align="center" style={{ margin: '30px 0' }}> | <Flex justify="space-between" align="center" style={{ margin: '30px 0' }}> | ||||
| <Flex align="center"> | <Flex align="center"> | ||||
| <span style={{ marginRight: '10px' }}>版本号:</span> | |||||
| <Select | |||||
| placeholder="请选择版本号" | |||||
| style={{ width: '160px', marginRight: '20px' }} | |||||
| value={version} | |||||
| onChange={onVersionChange} | |||||
| options={versionList} | |||||
| /> | |||||
| <Button type="default" onClick={showModal} icon={<KFIcon type="icon-xinjian2" />}> | |||||
| 创建新版本 | |||||
| </Button> | |||||
| </Flex> | |||||
| <Flex align="center"> | |||||
| {!isPublic && ( | |||||
| <Button | |||||
| type="default" | |||||
| style={{ marginRight: '20px' }} | |||||
| onClick={hanldeDelete} | |||||
| icon={<KFIcon type="icon-shanchu" />} | |||||
| > | |||||
| 删除 | |||||
| </Button> | |||||
| )} | |||||
| <Button | <Button | ||||
| type="default" | type="default" | ||||
| disabled={!version} | |||||
| disabled={fileList.length === 0} | |||||
| onClick={handleExport} | onClick={handleExport} | ||||
| icon={<KFIcon type="icon-xiazai" />} | icon={<KFIcon type="icon-xiazai" />} | ||||
| > | > | ||||
| @@ -216,12 +103,7 @@ function ResourceVersion({ | |||||
| </Button> | </Button> | ||||
| </Flex> | </Flex> | ||||
| </Flex> | </Flex> | ||||
| <div style={{ marginBottom: '30px', fontSize: '15px' }}> | |||||
| {fileList.length > 0 && fileList[0].description | |||||
| ? '版本描述:' + fileList[0].description | |||||
| : null} | |||||
| </div> | |||||
| <Table columns={columns} dataSource={fileList} pagination={false} rowKey="id" /> | |||||
| <Table columns={columns} dataSource={fileList} pagination={false} rowKey="url" /> | |||||
| </div> | </div> | ||||
| ); | ); | ||||
| } | } | ||||
| @@ -1,20 +1,18 @@ | |||||
| import KFIcon from '@/components/KFIcon'; | import KFIcon from '@/components/KFIcon'; | ||||
| import { CommonTabKeys } from '@/enums'; | import { CommonTabKeys } from '@/enums'; | ||||
| import { | import { | ||||
| addDatasetVersionDetail, | |||||
| addModelsVersionDetail, | |||||
| addDatasetVersion, | |||||
| addModelVersion, | |||||
| deleteDataset, | deleteDataset, | ||||
| deleteDatasetVersion, | deleteDatasetVersion, | ||||
| deleteModel, | deleteModel, | ||||
| deleteModelVersion, | deleteModelVersion, | ||||
| getDatasetById, | |||||
| getDatasetInfo, | |||||
| getDatasetList, | getDatasetList, | ||||
| getDatasetVersionIdList, | |||||
| getDatasetVersionsById, | |||||
| getModelById, | |||||
| getDatasetVersionList, | |||||
| getModelInfo, | |||||
| getModelList, | getModelList, | ||||
| getModelVersionIdList, | |||||
| getModelVersionsById, | |||||
| getModelVersionList, | |||||
| } from '@/services/dataset/index.js'; | } from '@/services/dataset/index.js'; | ||||
| import type { TabsProps } from 'antd'; | import type { TabsProps } from 'antd'; | ||||
| @@ -23,18 +21,24 @@ export enum ResourceType { | |||||
| Dataset = 'Dataset', // 数据集 | Dataset = 'Dataset', // 数据集 | ||||
| } | } | ||||
| export enum DataSource { | |||||
| AtuoExport = 'auto_export', // 自动导出 | |||||
| HandExport = 'hand_export', // 手动导出 | |||||
| Create = 'add', // 新增 | |||||
| } | |||||
| type ResourceTypeInfo = { | type ResourceTypeInfo = { | ||||
| getList: (params: any) => Promise<any>; // 获取资源列表 | getList: (params: any) => Promise<any>; // 获取资源列表 | ||||
| getVersions: (params: any) => Promise<any>; // 获取版本列表 | getVersions: (params: any) => Promise<any>; // 获取版本列表 | ||||
| getFiles: (params: any) => Promise<any>; // 获取版本下的文件列表 | |||||
| deleteRecord: (params: any) => Promise<any>; // 删除 | deleteRecord: (params: any) => Promise<any>; // 删除 | ||||
| addVersion: (params: any) => Promise<any>; // 新增版本 | addVersion: (params: any) => Promise<any>; // 新增版本 | ||||
| deleteVersion: (params: any) => Promise<any>; // 删除版本 | deleteVersion: (params: any) => Promise<any>; // 删除版本 | ||||
| getInfo: (params: any) => Promise<any>; // 获取详情 | getInfo: (params: any) => Promise<any>; // 获取详情 | ||||
| name: string; // 名称 | name: string; // 名称 | ||||
| typeParamKey: string; // 类型参数名称,获取资源列表接口使用 | |||||
| tagParamKey: string; // 标签参数名称,获取资源列表接口使用 | |||||
| fileReqParamKey: 'models_id' | 'dataset_id'; // 文件请求参数名称,获取文件列表接口使用 | |||||
| typeParamKey: 'data_type' | 'model_type'; // 类型参数名称,获取资源列表接口使用 | |||||
| tagParamKey: 'data_tag' | 'model_tag'; // 标签参数名称,获取资源列表接口使用 | |||||
| filePropKey: 'dataset_version_vos' | 'model_version_vos'; // 文件列表属性 | |||||
| sourceParamKey: 'dataset_source' | 'model_source'; // 来源参数名称 | |||||
| tabItems: TabsProps['items']; // tab 列表 | tabItems: TabsProps['items']; // tab 列表 | ||||
| typeTitle: string; // 类型标题 | typeTitle: string; // 类型标题 | ||||
| tagTitle: string; // 标签标题 | tagTitle: string; // 标签标题 | ||||
| @@ -43,28 +47,25 @@ type ResourceTypeInfo = { | |||||
| prefix: string; // 图片资源、详情 url 的前缀 | prefix: string; // 图片资源、详情 url 的前缀 | ||||
| deleteModalTitle: string; // 删除弹框的title | deleteModalTitle: string; // 删除弹框的title | ||||
| addBtnTitle: string; // 新增按钮的title | addBtnTitle: string; // 新增按钮的title | ||||
| idParamKey: 'models_id' | 'dataset_id'; // 新建版本、删除版本接口,版本 id 的参数名称 | |||||
| uploadAction: string; // 上传接口 url | uploadAction: string; // 上传接口 url | ||||
| uploadAccept?: string; // 上传文件类型 | uploadAccept?: string; // 上传文件类型 | ||||
| downloadAllAction: string; // 批量下载接口 url | downloadAllAction: string; // 批量下载接口 url | ||||
| downloadSingleAction: string; // 单个下载接口 url | downloadSingleAction: string; // 单个下载接口 url | ||||
| infoTypePropertyName: string; // 详情数据中,类型属性名称 | |||||
| infoTagPropertyName: string; // 详情数据中,标签属性名称 | |||||
| }; | }; | ||||
| export const resourceConfig: Record<ResourceType, ResourceTypeInfo> = { | export const resourceConfig: Record<ResourceType, ResourceTypeInfo> = { | ||||
| [ResourceType.Dataset]: { | [ResourceType.Dataset]: { | ||||
| getList: getDatasetList, | getList: getDatasetList, | ||||
| getVersions: getDatasetVersionsById, | |||||
| getFiles: getDatasetVersionIdList, | |||||
| getVersions: getDatasetVersionList, | |||||
| deleteRecord: deleteDataset, | deleteRecord: deleteDataset, | ||||
| addVersion: addDatasetVersionDetail, | |||||
| addVersion: addDatasetVersion, | |||||
| deleteVersion: deleteDatasetVersion, | deleteVersion: deleteDatasetVersion, | ||||
| getInfo: getDatasetById, | |||||
| getInfo: getDatasetInfo, | |||||
| name: '数据集', | name: '数据集', | ||||
| typeParamKey: 'data_type', | typeParamKey: 'data_type', | ||||
| tagParamKey: 'data_tag', | tagParamKey: 'data_tag', | ||||
| fileReqParamKey: 'dataset_id', | |||||
| filePropKey: 'dataset_version_vos', | |||||
| sourceParamKey: 'dataset_source', | |||||
| tabItems: [ | tabItems: [ | ||||
| { | { | ||||
| key: CommonTabKeys.Public, | key: CommonTabKeys.Public, | ||||
| @@ -84,26 +85,23 @@ export const resourceConfig: Record<ResourceType, ResourceTypeInfo> = { | |||||
| prefix: 'dataset', | prefix: 'dataset', | ||||
| deleteModalTitle: '确定删除该条数据集实例吗?', | deleteModalTitle: '确定删除该条数据集实例吗?', | ||||
| addBtnTitle: '新建数据集', | addBtnTitle: '新建数据集', | ||||
| idParamKey: 'dataset_id', | |||||
| uploadAction: '/api/mmp/dataset/upload', | |||||
| uploadAction: '/api/mmp/newdataset/upload', | |||||
| uploadAccept: '.zip,.tgz', | uploadAccept: '.zip,.tgz', | ||||
| downloadAllAction: '/api/mmp/dataset/downloadAllFilesl', | |||||
| downloadSingleAction: '/api/mmp/dataset/download', | |||||
| infoTypePropertyName: 'dataset_type_name', | |||||
| infoTagPropertyName: 'dataset_tag_name', | |||||
| downloadAllAction: '/api/mmp/newdataset/downloadAllFiles', | |||||
| downloadSingleAction: '/api/mmp/newdataset/downloadSingleFile', | |||||
| }, | }, | ||||
| [ResourceType.Model]: { | [ResourceType.Model]: { | ||||
| getList: getModelList, | getList: getModelList, | ||||
| getVersions: getModelVersionsById, | |||||
| getFiles: getModelVersionIdList, | |||||
| getVersions: getModelVersionList, | |||||
| deleteRecord: deleteModel, | deleteRecord: deleteModel, | ||||
| addVersion: addModelsVersionDetail, | |||||
| addVersion: addModelVersion, | |||||
| deleteVersion: deleteModelVersion, | deleteVersion: deleteModelVersion, | ||||
| getInfo: getModelById, | |||||
| getInfo: getModelInfo, | |||||
| name: '模型', | name: '模型', | ||||
| typeParamKey: 'model_type', | typeParamKey: 'model_type', | ||||
| tagParamKey: 'model_tag', | tagParamKey: 'model_tag', | ||||
| fileReqParamKey: 'models_id', | |||||
| filePropKey: 'model_version_vos', | |||||
| sourceParamKey: 'model_source', | |||||
| tabItems: [ | tabItems: [ | ||||
| { | { | ||||
| key: CommonTabKeys.Public, | key: CommonTabKeys.Public, | ||||
| @@ -123,13 +121,10 @@ export const resourceConfig: Record<ResourceType, ResourceTypeInfo> = { | |||||
| prefix: 'model', | prefix: 'model', | ||||
| deleteModalTitle: '确定删除该条模型实例吗?', | deleteModalTitle: '确定删除该条模型实例吗?', | ||||
| addBtnTitle: '新建模型', | addBtnTitle: '新建模型', | ||||
| idParamKey: 'models_id', | |||||
| uploadAction: '/api/mmp/models/upload', | |||||
| uploadAction: '/api/mmp/newmodel/upload', | |||||
| uploadAccept: undefined, | uploadAccept: undefined, | ||||
| downloadAllAction: '/api/mmp/models/downloadAllFiles', | |||||
| downloadSingleAction: '/api/mmp/models/download_model', | |||||
| infoTypePropertyName: 'model_type_name', | |||||
| infoTagPropertyName: 'model_tag_name', | |||||
| downloadAllAction: '/api/mmp/newmodel/downloadAllFiles', | |||||
| downloadSingleAction: '/api/mmp/newmodel/downloadSingleFile', | |||||
| }, | }, | ||||
| }; | }; | ||||
| @@ -141,36 +136,78 @@ export type CategoryData = { | |||||
| path: string; | path: string; | ||||
| }; | }; | ||||
| // 资源数据 | |||||
| export type ResourceData = { | |||||
| // 数据集、模型列表数据 | |||||
| export interface ResourceData { | |||||
| resourceType: ResourceType.Dataset | ResourceType.Model; // 用于 ts 类型判断 | |||||
| id: number; | id: number; | ||||
| name: string; | name: string; | ||||
| description: string; | |||||
| create_by: string; | |||||
| update_time: string; | |||||
| available_range: number; | |||||
| model_type_name?: string; | |||||
| model_tag_name?: string; | |||||
| dataset_type_name?: string; | |||||
| dataset_tag_name?: string; | |||||
| }; | |||||
| identifier: string; | |||||
| owner: string; | |||||
| version: string; | |||||
| is_public: boolean; | |||||
| description?: string; | |||||
| create_by?: string; | |||||
| update_time?: string; | |||||
| time_ago?: string; | |||||
| version_desc?: string; | |||||
| usage?: string; | |||||
| relative_paths?: string; | |||||
| train_task?: TrainTask; // 训练任务 | |||||
| } | |||||
| // 数据集数据 | |||||
| export interface DatasetData extends ResourceData { | |||||
| resourceType: ResourceType.Dataset; // 用于区别类型 | |||||
| data_type?: string; // 数据集分类 | |||||
| data_tag?: string; // 研究方向 | |||||
| processing_code?: ProjectDependency; // 处理代码 | |||||
| dataset_source?: string; // 数据来源 | |||||
| dataset_version_vos?: ResourceFileData[]; | |||||
| } | |||||
| // 模型数据 | |||||
| export interface ModelData extends ResourceData { | |||||
| resourceType: ResourceType.Model; // 用于区别类型 | |||||
| model_type?: string; // 模型框架 | |||||
| model_tag?: string; // 模型能力 | |||||
| image?: string; // 训练镜像 | |||||
| code?: string; // 训练镜像 | |||||
| train_datasets?: DatasetData[]; // 训练数据集 | |||||
| test_datasets?: DatasetData[]; // 测试数据集 | |||||
| params?: Record<string, string>; // 参数 | |||||
| metrics?: Record<string, string>; // 指标 | |||||
| project_depency?: ProjectDependency; // 项目依赖 | |||||
| model_source?: string; // 模型来源 | |||||
| model_version_vos?: ResourceFileData[]; | |||||
| } | |||||
| // 版本数据 | // 版本数据 | ||||
| export type ResourceVersionData = { | export type ResourceVersionData = { | ||||
| label: string; | |||||
| value: string; | |||||
| name: string; | |||||
| http_url: string; | |||||
| tar_url: string; | |||||
| zip_url: string; | |||||
| }; | }; | ||||
| // 版本文件数据 | // 版本文件数据 | ||||
| export type ResourceFileData = { | export type ResourceFileData = { | ||||
| id: number; | |||||
| file_name: string; | file_name: string; | ||||
| file_size: string; | file_size: string; | ||||
| description: string; | |||||
| create_by: string; | |||||
| create_time: string; | |||||
| update_by: string; | |||||
| update_time: string; | |||||
| url: string; | url: string; | ||||
| version: string; | |||||
| update_time?: string; | |||||
| }; | |||||
| // 训练任务 | |||||
| export type TrainTask = { | |||||
| ins_id: number; // 实例id | |||||
| name: string; // 实验名称 | |||||
| experiment_id: number; //实验 id | |||||
| workflow_id: number; // 流水线 id | |||||
| }; | |||||
| // 项目依赖 | |||||
| export type ProjectDependency = { | |||||
| url: string; // 项目地址 | |||||
| name: string; // 项目名称 | |||||
| branch: string; // 分支 | |||||
| }; | }; | ||||
| @@ -1,8 +1,8 @@ | |||||
| import ResourceIntro from '@/pages/Dataset/components/ResourceIntro'; | |||||
| import ResourceInfo from '@/pages/Dataset/components/ResourceInfo'; | |||||
| import { ResourceType } from '@/pages/Dataset/config'; | import { ResourceType } from '@/pages/Dataset/config'; | ||||
| function DatasetIntro() { | |||||
| return <ResourceIntro resourceType={ResourceType.Dataset} />; | |||||
| function DatasetInfo() { | |||||
| return <ResourceInfo resourceType={ResourceType.Dataset} />; | |||||
| } | } | ||||
| export default DatasetIntro; | |||||
| export default DatasetInfo; | |||||
| @@ -8,11 +8,11 @@ import KFRadio, { type KFRadioItem } from '@/components/KFRadio'; | |||||
| import PageTitle from '@/components/PageTitle'; | import PageTitle from '@/components/PageTitle'; | ||||
| import ResourceSelect, { | import ResourceSelect, { | ||||
| requiredValidator, | requiredValidator, | ||||
| ResourceSelectorType, | |||||
| type ParameterInputObject, | type ParameterInputObject, | ||||
| } from '@/components/ResourceSelect'; | } from '@/components/ResourceSelect'; | ||||
| import SubAreaTitle from '@/components/SubAreaTitle'; | import SubAreaTitle from '@/components/SubAreaTitle'; | ||||
| import { useComputingResource } from '@/hooks/resource'; | import { useComputingResource } from '@/hooks/resource'; | ||||
| import { ResourceSelectorType } from '@/pages/Pipeline/components/ResourceSelectorModal'; | |||||
| import { createEditorReq } from '@/services/developmentEnvironment'; | import { createEditorReq } from '@/services/developmentEnvironment'; | ||||
| import { to } from '@/utils/promise'; | import { to } from '@/utils/promise'; | ||||
| import { useNavigate } from '@umijs/max'; | import { useNavigate } from '@umijs/max'; | ||||
| @@ -90,7 +90,6 @@ function EditorCreate() { | |||||
| <Form | <Form | ||||
| name="editor-create" | name="editor-create" | ||||
| labelCol={{ flex: '100px' }} | labelCol={{ flex: '100px' }} | ||||
| wrapperCol={{ flex: 1 }} | |||||
| labelAlign="left" | labelAlign="left" | ||||
| form={form} | form={form} | ||||
| initialValues={{ computing_resource: ComputingResourceType.GPU }} | initialValues={{ computing_resource: ComputingResourceType.GPU }} | ||||
| @@ -34,7 +34,7 @@ function ExperimentComparison() { | |||||
| // const [cacheState, setCacheState] = useCacheState(); | // const [cacheState, setCacheState] = useCacheState(); | ||||
| // const [total, setTotal] = useState(0); | // const [total, setTotal] = useState(0); | ||||
| const [selectedRowKeys, setSelectedRowKeys] = useState<React.Key[]>([]); | const [selectedRowKeys, setSelectedRowKeys] = useState<React.Key[]>([]); | ||||
| const [loading, setLoading] = useState(false); | |||||
| // const [loading, setLoading] = useState(false); | |||||
| const { message } = App.useApp(); | const { message } = App.useApp(); | ||||
| const config = useMemo(() => comparisonConfig[comparisonType], [comparisonType]); | const config = useMemo(() => comparisonConfig[comparisonType], [comparisonType]); | ||||
| // const [pagination, setPagination] = useState<TablePaginationConfig>( | // const [pagination, setPagination] = useState<TablePaginationConfig>( | ||||
| @@ -50,11 +50,11 @@ function ExperimentComparison() { | |||||
| // 获取对比数据列表 | // 获取对比数据列表 | ||||
| const getComparisonData = async () => { | const getComparisonData = async () => { | ||||
| setLoading(true); | |||||
| // setLoading(true); | |||||
| const request = | const request = | ||||
| comparisonType === ComparisonType.Train ? getExpTrainInfosReq : getExpEvaluateInfosReq; | comparisonType === ComparisonType.Train ? getExpTrainInfosReq : getExpEvaluateInfosReq; | ||||
| const [res] = await to(request(experimentId)); | const [res] = await to(request(experimentId)); | ||||
| setLoading(false); | |||||
| // setLoading(false); | |||||
| if (res && res.data) { | if (res && res.data) { | ||||
| // const { content = [], totalElements = 0 } = res.data; | // const { content = [], totalElements = 0 } = res.data; | ||||
| setTableData(res.data); | setTableData(res.data); | ||||
| @@ -204,7 +204,7 @@ function ExperimentComparison() { | |||||
| scroll={{ y: 'calc(100% - 55px)', x: '100%' }} | scroll={{ y: 'calc(100% - 55px)', x: '100%' }} | ||||
| pagination={false} | pagination={false} | ||||
| bordered={true} | bordered={true} | ||||
| loading={loading} | |||||
| // loading={loading} | |||||
| // pagination={{ | // pagination={{ | ||||
| // ...pagination, | // ...pagination, | ||||
| // total: total, | // total: total, | ||||
| @@ -353,7 +353,7 @@ function ExperimentText() { | |||||
| fitView: true, | fitView: true, | ||||
| minZoom: 0.5, | minZoom: 0.5, | ||||
| maxZoom: 5, | maxZoom: 5, | ||||
| fitViewPadding: 300, | |||||
| fitViewPadding: 200, | |||||
| modes: { | modes: { | ||||
| default: [ | default: [ | ||||
| // config the shouldBegin for drag-node to avoid node moving while dragging on the anchor-point circles | // config the shouldBegin for drag-node to avoid node moving while dragging on the anchor-point circles | ||||
| @@ -504,6 +504,9 @@ function ExperimentText() { | |||||
| key={experimentNodeData.id} | key={experimentNodeData.id} | ||||
| open={propsDrawerOpen} | open={propsDrawerOpen} | ||||
| onClose={closePropsDrawer} | onClose={closePropsDrawer} | ||||
| pipelineId={Number(locationParams.workflowId)} | |||||
| experimentId={experimentIns.experiment_id} | |||||
| experimentName={experimentIns.experiment_name} | |||||
| instanceId={experimentIns.id} | instanceId={experimentIns.id} | ||||
| instanceName={experimentIns.argo_ins_name} | instanceName={experimentIns.argo_ins_name} | ||||
| instanceNamespace={experimentIns.argo_ins_ns} | instanceNamespace={experimentIns.argo_ins_ns} | ||||
| @@ -13,6 +13,9 @@ import styles from './index.less'; | |||||
| type ExperimentDrawerProps = { | type ExperimentDrawerProps = { | ||||
| open: boolean; | open: boolean; | ||||
| onClose: () => void; | onClose: () => void; | ||||
| pipelineId: number; // 流水线 id | |||||
| experimentId: number; // 实验 id | |||||
| experimentName: string; // 实验 name | |||||
| instanceId: number; // 实验实例 id | instanceId: number; // 实验实例 id | ||||
| instanceName: string; // 实验实例 name | instanceName: string; // 实验实例 name | ||||
| instanceNamespace: string; // 实验实例 namespace | instanceNamespace: string; // 实验实例 namespace | ||||
| @@ -26,6 +29,9 @@ type ExperimentDrawerProps = { | |||||
| const ExperimentDrawer = ({ | const ExperimentDrawer = ({ | ||||
| open, | open, | ||||
| onClose, | onClose, | ||||
| pipelineId, | |||||
| experimentId, | |||||
| experimentName, | |||||
| instanceId, | instanceId, | ||||
| instanceName, | instanceName, | ||||
| instanceNamespace, | instanceNamespace, | ||||
| @@ -64,6 +70,9 @@ const ExperimentDrawer = ({ | |||||
| label: '输出结果', | label: '输出结果', | ||||
| children: ( | children: ( | ||||
| <ExperimentResult | <ExperimentResult | ||||
| pipelineId={pipelineId} | |||||
| experimentId={experimentId} | |||||
| experimentName={experimentName} | |||||
| experimentInsId={instanceId} | experimentInsId={instanceId} | ||||
| pipelineNodeId={instanceNodeData.id} | pipelineNodeId={instanceNodeData.id} | ||||
| ></ExperimentResult> | ></ExperimentResult> | ||||
| @@ -116,6 +116,8 @@ function ExperimentInstanceComponent({ | |||||
| <img | <img | ||||
| style={{ width: '17px', marginRight: '7px' }} | style={{ width: '17px', marginRight: '7px' }} | ||||
| src={experimentStatusInfo[item.status as ExperimentStatus]?.icon} | src={experimentStatusInfo[item.status as ExperimentStatus]?.icon} | ||||
| draggable={false} | |||||
| alt="" | |||||
| /> | /> | ||||
| <span | <span | ||||
| style={{ color: experimentStatusInfo[item.status as ExperimentStatus]?.color }} | style={{ color: experimentStatusInfo[item.status as ExperimentStatus]?.color }} | ||||
| @@ -1,3 +1,4 @@ | |||||
| import { ResourceType } from '@/pages/Dataset/config'; | |||||
| import { getNodeResult } from '@/services/experiment/index.js'; | import { getNodeResult } from '@/services/experiment/index.js'; | ||||
| import { downLoadZip } from '@/utils/downloadfile'; | import { downLoadZip } from '@/utils/downloadfile'; | ||||
| import { openAntdModal } from '@/utils/modal'; | import { openAntdModal } from '@/utils/modal'; | ||||
| @@ -8,6 +9,9 @@ import ExportModelModal from '../ExportModelModal'; | |||||
| import styles from './index.less'; | import styles from './index.less'; | ||||
| type ExperimentResultProps = { | type ExperimentResultProps = { | ||||
| pipelineId: number; // 流水线 id | |||||
| experimentId: number; // 实验 id | |||||
| experimentName: string; // 实验 name | |||||
| experimentInsId: number; // 实验实例 id | experimentInsId: number; // 实验实例 id | ||||
| pipelineNodeId: string; // 流水线节点 id | pipelineNodeId: string; // 流水线节点 id | ||||
| }; | }; | ||||
| @@ -22,9 +26,21 @@ type ExperimentResultData = { | |||||
| }[]; | }[]; | ||||
| }; | }; | ||||
| function ExperimentResult({ experimentInsId, pipelineNodeId }: ExperimentResultProps) { | |||||
| function ExperimentResult({ | |||||
| pipelineId, | |||||
| experimentId, | |||||
| experimentName, | |||||
| experimentInsId, | |||||
| pipelineNodeId, | |||||
| }: ExperimentResultProps) { | |||||
| const { message } = App.useApp(); | const { message } = App.useApp(); | ||||
| const [experimentResults, setExperimentResults] = useState<ExperimentResultData[]>([]); | const [experimentResults, setExperimentResults] = useState<ExperimentResultData[]>([]); | ||||
| const resourceType: ResourceType | undefined = pipelineNodeId.startsWith('general-data-process') | |||||
| ? ResourceType.Dataset | |||||
| : pipelineNodeId.startsWith('model-train') || | |||||
| pipelineNodeId.startsWith('distributed-model-train') | |||||
| ? ResourceType.Model | |||||
| : undefined; | |||||
| useEffect(() => { | useEffect(() => { | ||||
| getExperimentResult({ id: `${experimentInsId}`, node_id: pipelineNodeId }); | getExperimentResult({ id: `${experimentInsId}`, node_id: pipelineNodeId }); | ||||
| @@ -33,8 +49,11 @@ function ExperimentResult({ experimentInsId, pipelineNodeId }: ExperimentResultP | |||||
| // 获取实验结果 | // 获取实验结果 | ||||
| const getExperimentResult = async (params: any) => { | const getExperimentResult = async (params: any) => { | ||||
| const [res] = await to(getNodeResult(params)); | const [res] = await to(getNodeResult(params)); | ||||
| if (res && res.data) { | |||||
| setExperimentResults(res.data); | |||||
| if (res && res.data && Array.isArray(res.data)) { | |||||
| const data = res.data.filter((item: ExperimentResultData) => item.value.length > 0); | |||||
| setExperimentResults(data); | |||||
| } else { | |||||
| setExperimentResults([]); | |||||
| } | } | ||||
| }; | }; | ||||
| @@ -43,9 +62,15 @@ function ExperimentResult({ experimentInsId, pipelineNodeId }: ExperimentResultP | |||||
| downLoadZip(`/api/mmp/minioStorage/download`, { path }); | downLoadZip(`/api/mmp/minioStorage/download`, { path }); | ||||
| }; | }; | ||||
| // 导出到模型库 | |||||
| const exportToModel = (path: string) => { | |||||
| // 导出到数据集、模型 | |||||
| const exportToResource = (path: string) => { | |||||
| const { close } = openAntdModal(ExportModelModal, { | const { close } = openAntdModal(ExportModelModal, { | ||||
| resourceType: resourceType!, | |||||
| pipelineId, | |||||
| experimentId, | |||||
| experimentName, | |||||
| experimentInsId, | |||||
| pipelineNodeId, | |||||
| path, | path, | ||||
| onOk: () => { | onOk: () => { | ||||
| message.success('导出成功'); | message.success('导出成功'); | ||||
| @@ -72,17 +97,17 @@ function ExperimentResult({ experimentInsId, pipelineNodeId }: ExperimentResultP | |||||
| > | > | ||||
| 下载 | 下载 | ||||
| </Button> | </Button> | ||||
| <Button | |||||
| size="small" | |||||
| type="link" | |||||
| onClick={() => { | |||||
| exportToModel(item.path); | |||||
| }} | |||||
| > | |||||
| 导出到模型库 | |||||
| </Button> | |||||
| {/* <a style={{ marginRight: '10px' }}>导出到模型库</a> | |||||
| <a style={{ marginRight: '10px' }}>导出到数据集</a> */} | |||||
| {resourceType && ( | |||||
| <Button | |||||
| size="small" | |||||
| type="link" | |||||
| onClick={() => { | |||||
| exportToResource(item.path); | |||||
| }} | |||||
| > | |||||
| 导出到{resourceType === ResourceType.Model ? '模型' : '数据集'} | |||||
| </Button> | |||||
| )} | |||||
| </div> | </div> | ||||
| <div style={{ margin: '15px 0' }} className={styles['experiment-result__item__file']}> | <div style={{ margin: '15px 0' }} className={styles['experiment-result__item__file']}> | ||||
| <span>文件名称</span> | <span>文件名称</span> | ||||
| @@ -14,7 +14,12 @@ function ExperimentStatusCell(status?: ExperimentStatus | null) { | |||||
| } | } | ||||
| return ( | return ( | ||||
| <div className={styles['experiment-status-cell']}> | <div className={styles['experiment-status-cell']}> | ||||
| <img style={{ width: '17px', marginRight: '7px' }} src={statusInfo[status]?.icon} /> | |||||
| <img | |||||
| style={{ width: '17px', marginRight: '7px' }} | |||||
| src={statusInfo[status]?.icon} | |||||
| draggable={false} | |||||
| alt="" | |||||
| /> | |||||
| <span | <span | ||||
| style={{ color: statusInfo[status]?.color }} | style={{ color: statusInfo[status]?.color }} | ||||
| className={styles['experiment-status-cell__label']} | className={styles['experiment-status-cell__label']} | ||||
| @@ -1,47 +1,51 @@ | |||||
| import editExperimentIcon from '@/assets/img/edit-experiment.png'; | import editExperimentIcon from '@/assets/img/edit-experiment.png'; | ||||
| import KFModal from '@/components/KFModal'; | import KFModal from '@/components/KFModal'; | ||||
| import { type ResourceData } from '@/pages/Dataset/config'; | |||||
| import { | import { | ||||
| addModelsVersionDetail, | |||||
| exportModelReq, | |||||
| getModelList, | |||||
| getModelVersionsById, | |||||
| } from '@/services/dataset'; | |||||
| DataSource, | |||||
| ResourceType, | |||||
| ResourceVersionData, | |||||
| resourceConfig, | |||||
| type ResourceData, | |||||
| } from '@/pages/Dataset/config'; | |||||
| import { to } from '@/utils/promise'; | import { to } from '@/utils/promise'; | ||||
| import { InfoCircleOutlined } from '@ant-design/icons'; | import { InfoCircleOutlined } from '@ant-design/icons'; | ||||
| import { Form, Input, ModalProps, Select } from 'antd'; | import { Form, Input, ModalProps, Select } from 'antd'; | ||||
| import { pick } from 'lodash'; | |||||
| import { useEffect, useState } from 'react'; | import { useEffect, useState } from 'react'; | ||||
| import styles from './index.less'; | import styles from './index.less'; | ||||
| type FormData = { | type FormData = { | ||||
| models_id: string; | |||||
| id: number; | |||||
| version: string; | version: string; | ||||
| description: string; | |||||
| }; | |||||
| type ExportModelResponce = { | |||||
| fileName: string; | |||||
| fileSize: string; | |||||
| url: string; | |||||
| }; | |||||
| type CreateModelVersionParams = FormData & { | |||||
| file_name: string; | |||||
| file_size: string; | |||||
| url: string; | |||||
| // name: string; | |||||
| version_desc: string; | |||||
| }; | }; | ||||
| interface ExportModelModalProps extends Omit<ModalProps, 'onOk'> { | interface ExportModelModalProps extends Omit<ModalProps, 'onOk'> { | ||||
| path: string; | |||||
| resourceType: ResourceType; | |||||
| pipelineId: number; // 流水线 id | |||||
| experimentId: number; // 实验 id | |||||
| experimentName: string; // 实验 name | |||||
| experimentInsId: number; // 实验实例 id | |||||
| pipelineNodeId: string; // 流水线节点 id | |||||
| path: string; // 文件路径 | |||||
| onOk: () => void; | onOk: () => void; | ||||
| } | } | ||||
| function ExportModelModal({ path, onOk, ...rest }: ExportModelModalProps) { | |||||
| function ExportModelModal({ | |||||
| resourceType, | |||||
| pipelineId, | |||||
| experimentId, | |||||
| experimentName, | |||||
| experimentInsId, | |||||
| pipelineNodeId, | |||||
| path, | |||||
| onOk, | |||||
| ...rest | |||||
| }: ExportModelModalProps) { | |||||
| const [form] = Form.useForm(); | const [form] = Form.useForm(); | ||||
| const [models, setModels] = useState<ResourceData[]>([]); | |||||
| const [versions, setVersions] = useState<string[]>([]); | |||||
| const [uuid] = useState(Date.now()); | |||||
| const [resources, setResources] = useState<ResourceData[]>([]); | |||||
| const [versions, setVersions] = useState<ResourceVersionData[]>([]); | |||||
| const config = resourceConfig[resourceType]; | |||||
| const layout = { | const layout = { | ||||
| labelCol: { span: 24 }, | labelCol: { span: 24 }, | ||||
| @@ -49,43 +53,57 @@ function ExportModelModal({ path, onOk, ...rest }: ExportModelModalProps) { | |||||
| }; | }; | ||||
| useEffect(() => { | useEffect(() => { | ||||
| requestModelList(); | |||||
| requestResourceList(); | |||||
| }, []); | }, []); | ||||
| // 模型版本tooltip | |||||
| // 获取选中的数据集、模型 | |||||
| const getSelectedResource = (id: number | undefined) => { | |||||
| if (id) { | |||||
| return resources.find((item) => item.id === id); | |||||
| } | |||||
| return undefined; | |||||
| }; | |||||
| // 版本 tooltip | |||||
| const getTooltip = () => { | const getTooltip = () => { | ||||
| const id = form.getFieldValue('models_id'); | |||||
| const name = models.find((item) => item.id === id)?.name ?? ''; | |||||
| const id = form.getFieldValue('id'); | |||||
| const resource = getSelectedResource(id); | |||||
| const name = resource?.name ?? ''; | |||||
| const versionNames = versions.map((item: ResourceVersionData) => item.name).join('、'); | |||||
| const tooltip = | const tooltip = | ||||
| versions.length > 0 ? `${name}有以下版本:\n${versions.join('、')}\n注意不能重复` : undefined; | |||||
| versions.length > 0 ? `${name}有以下版本:\n${versionNames}\n注意不能重复` : undefined; | |||||
| return tooltip; | return tooltip; | ||||
| }; | }; | ||||
| // 处理模型名称变化 | |||||
| const handleModelChange = (id: number | undefined) => { | |||||
| // 处理数据集、模型选择变化 | |||||
| const handleResourceChange = (id: number | undefined) => { | |||||
| if (id) { | if (id) { | ||||
| getModelVersions(id); | |||||
| getRecourceVersions(id); | |||||
| } else { | } else { | ||||
| setVersions([]); | setVersions([]); | ||||
| } | } | ||||
| }; | }; | ||||
| // 获取模型列表 | |||||
| const requestModelList = async () => { | |||||
| // 获取数据集、模型列表 | |||||
| const requestResourceList = async () => { | |||||
| const params = { | const params = { | ||||
| page: 0, | page: 0, | ||||
| size: 1000, | size: 1000, | ||||
| available_range: 0, // 个人 | |||||
| is_public: false, // 个人 | |||||
| }; | }; | ||||
| const [res] = await to(getModelList(params)); | |||||
| const [res] = await to(config.getList(params)); | |||||
| if (res && res.data) { | if (res && res.data) { | ||||
| setModels(res.data.content || []); | |||||
| setResources(res.data.content || []); | |||||
| } | } | ||||
| }; | }; | ||||
| // 获取模型版本列表 | |||||
| const getModelVersions = async (id: number) => { | |||||
| const [res] = await to(getModelVersionsById(id)); | |||||
| // 获取数据集、模型版本列表 | |||||
| const getRecourceVersions = async (id: number) => { | |||||
| const resource = getSelectedResource(id); | |||||
| if (!resource) { | |||||
| return; | |||||
| } | |||||
| const [res] = await to(config.getVersions(pick(resource, ['identifier', 'owner']))); | |||||
| if (res && res.data) { | if (res && res.data) { | ||||
| setVersions(res.data); | setVersions(res.data); | ||||
| } | } | ||||
| @@ -93,32 +111,32 @@ function ExportModelModal({ path, onOk, ...rest }: ExportModelModalProps) { | |||||
| // 提交 | // 提交 | ||||
| const hanldeFinish = (formData: FormData) => { | const hanldeFinish = (formData: FormData) => { | ||||
| exportToModel(formData); | |||||
| exportToResource(formData); | |||||
| }; | }; | ||||
| // 导出到模型 | |||||
| const exportToModel = async (formData: FormData) => { | |||||
| // 导出到数据集、模型 | |||||
| const exportToResource = async (formData: FormData) => { | |||||
| const id = form.getFieldValue('id'); | |||||
| const resource = getSelectedResource(id); | |||||
| const params = { | const params = { | ||||
| uuid: String(uuid), | |||||
| path, | |||||
| ...formData, | |||||
| identifier: resource?.identifier, | |||||
| name: resource?.name, | |||||
| [config.sourceParamKey]: DataSource.HandExport, | |||||
| train_task: { | |||||
| workflow_id: pipelineId, | |||||
| experiment_id: experimentId, | |||||
| name: experimentName, | |||||
| ins_id: experimentInsId, | |||||
| task_id: pipelineNodeId, | |||||
| }, | |||||
| [config.filePropKey]: [ | |||||
| { | |||||
| url: path, | |||||
| }, | |||||
| ], | |||||
| }; | }; | ||||
| const [res] = await to(exportModelReq(params)); | |||||
| if (res && res.data) { | |||||
| const files = res.data as ExportModelResponce[]; | |||||
| const params: CreateModelVersionParams[] = files.map((item) => ({ | |||||
| ...formData, | |||||
| file_name: item.fileName, | |||||
| file_size: item.fileSize, | |||||
| url: item.url, | |||||
| })); | |||||
| createModelVersion(params); | |||||
| } | |||||
| }; | |||||
| // 创建模型版本 | |||||
| const createModelVersion = async (params: CreateModelVersionParams[]) => { | |||||
| const [res] = await to(addModelsVersionDetail(params)); | |||||
| const [res] = await to(config.addVersion(params)); | |||||
| if (res) { | if (res) { | ||||
| onOk(); | onOk(); | ||||
| } | } | ||||
| @@ -127,7 +145,7 @@ function ExportModelModal({ path, onOk, ...rest }: ExportModelModalProps) { | |||||
| return ( | return ( | ||||
| <KFModal | <KFModal | ||||
| {...rest} | {...rest} | ||||
| title="导出到模型库" | |||||
| title={`导出到${config.name}`} | |||||
| image={editExperimentIcon} | image={editExperimentIcon} | ||||
| okButtonProps={{ | okButtonProps={{ | ||||
| htmlType: 'submit', | htmlType: 'submit', | ||||
| @@ -147,20 +165,20 @@ function ExportModelModal({ path, onOk, ...rest }: ExportModelModalProps) { | |||||
| labelWrap | labelWrap | ||||
| > | > | ||||
| <Form.Item | <Form.Item | ||||
| label="模型名称" | |||||
| name="models_id" | |||||
| rules={[{ required: true, message: '请选择模型' }]} | |||||
| label={`${config.name}名称`} | |||||
| name="id" | |||||
| rules={[{ required: true, message: `请选择${config.name}` }]} | |||||
| > | > | ||||
| <Select | <Select | ||||
| placeholder="请选择模型" | |||||
| onChange={handleModelChange} | |||||
| options={models} | |||||
| placeholder={`请选择${config.name}`} | |||||
| onChange={handleResourceChange} | |||||
| options={resources} | |||||
| fieldNames={{ label: 'name', value: 'id' }} | fieldNames={{ label: 'name', value: 'id' }} | ||||
| allowClear | allowClear | ||||
| ></Select> | ></Select> | ||||
| </Form.Item> | </Form.Item> | ||||
| <Form.Item | <Form.Item | ||||
| label="模型版本" | |||||
| label={`${config.name}版本`} | |||||
| name="version" | name="version" | ||||
| tooltip={ | tooltip={ | ||||
| getTooltip() | getTooltip() | ||||
| @@ -172,11 +190,11 @@ function ExportModelModal({ path, onOk, ...rest }: ExportModelModalProps) { | |||||
| : undefined | : undefined | ||||
| } | } | ||||
| rules={[ | rules={[ | ||||
| { required: true, message: '请输入模型版本' }, | |||||
| { required: true, message: `请输入${config.name}版本` }, | |||||
| { | { | ||||
| validator: (_, value) => { | validator: (_, value) => { | ||||
| if (value && versions.includes(value)) { | |||||
| return Promise.reject('模型版本已存在'); | |||||
| if (value && versions.map((item) => item.name).includes(value)) { | |||||
| return Promise.reject(`${config.name}版本已存在`); | |||||
| } else { | } else { | ||||
| return Promise.resolve(); | return Promise.resolve(); | ||||
| } | } | ||||
| @@ -184,11 +202,11 @@ function ExportModelModal({ path, onOk, ...rest }: ExportModelModalProps) { | |||||
| }, | }, | ||||
| ]} | ]} | ||||
| > | > | ||||
| <Input placeholder="请输入模型版本" maxLength={64} showCount allowClear /> | |||||
| <Input placeholder={`请输入${config.name}版本`} maxLength={64} showCount allowClear /> | |||||
| </Form.Item> | </Form.Item> | ||||
| <Form.Item | <Form.Item | ||||
| label="版本描述" | label="版本描述" | ||||
| name="description" | |||||
| name="version_desc" | |||||
| rules={[{ required: true, message: '请输入版本描述' }]} | rules={[{ required: true, message: '请输入版本描述' }]} | ||||
| > | > | ||||
| <Input.TextArea | <Input.TextArea | ||||
| @@ -70,6 +70,8 @@ function TensorBoardStatusCell({ | |||||
| className={styles['tensorBoard-status__icon']} | className={styles['tensorBoard-status__icon']} | ||||
| src={statusConfig[status].icon} | src={statusConfig[status].icon} | ||||
| onClick={onClick} | onClick={onClick} | ||||
| draggable={false} | |||||
| alt="" | |||||
| /> | /> | ||||
| )} | )} | ||||
| </> | </> | ||||
| @@ -385,6 +385,8 @@ function Experiment() { | |||||
| style={{ width: '17px', marginRight: '6px' }} | style={{ width: '17px', marginRight: '6px' }} | ||||
| key={index} | key={index} | ||||
| src={experimentStatusInfo[item].icon} | src={experimentStatusInfo[item].icon} | ||||
| draggable={false} | |||||
| alt="" | |||||
| /> | /> | ||||
| ); | ); | ||||
| }) | }) | ||||
| @@ -8,6 +8,7 @@ import DateTableCell from '@/components/DateTableCell'; | |||||
| import KFIcon from '@/components/KFIcon'; | import KFIcon from '@/components/KFIcon'; | ||||
| import PageTitle from '@/components/PageTitle'; | import PageTitle from '@/components/PageTitle'; | ||||
| import SubAreaTitle from '@/components/SubAreaTitle'; | import SubAreaTitle from '@/components/SubAreaTitle'; | ||||
| import { MirrorVersionStatus } from '@/enums'; | |||||
| import { useDomSize } from '@/hooks'; | import { useDomSize } from '@/hooks'; | ||||
| import { useCacheState } from '@/hooks/pageCacheState'; | import { useCacheState } from '@/hooks/pageCacheState'; | ||||
| import { | import { | ||||
| @@ -36,7 +37,7 @@ import { useEffect, useMemo, useState } from 'react'; | |||||
| import MirrorStatusCell from '../components/MirrorStatusCell'; | import MirrorStatusCell from '../components/MirrorStatusCell'; | ||||
| import styles from './index.less'; | import styles from './index.less'; | ||||
| type MirrorInfoData = { | |||||
| export type MirrorInfoData = { | |||||
| name?: string; | name?: string; | ||||
| description?: string; | description?: string; | ||||
| version_count?: string; | version_count?: string; | ||||
| @@ -44,13 +45,14 @@ type MirrorInfoData = { | |||||
| image_type?: number; | image_type?: number; | ||||
| }; | }; | ||||
| type MirrorVersionData = { | |||||
| export type MirrorVersionData = { | |||||
| id: number; | id: number; | ||||
| version: string; | version: string; | ||||
| url: string; | url: string; | ||||
| status: string; | |||||
| status: MirrorVersionStatus; | |||||
| file_size: string; | file_size: string; | ||||
| create_time: string; | create_time: string; | ||||
| tag_name: string; | |||||
| }; | }; | ||||
| function MirrorInfo() { | function MirrorInfo() { | ||||
| @@ -1,16 +1,11 @@ | |||||
| .model-evolution { | .model-evolution { | ||||
| width: 100%; | width: 100%; | ||||
| height: 100%; | height: 100%; | ||||
| overflow-x: hidden; | |||||
| background-color: white; | background-color: white; | ||||
| &__top { | |||||
| padding: 30px 0; | |||||
| color: @text-color; | |||||
| font-size: @font-size-content; | |||||
| } | |||||
| &__graph { | &__graph { | ||||
| height: calc(100% - 92px); | |||||
| height: calc(100%); | |||||
| background-color: @background-color; | background-color: @background-color; | ||||
| background-image: url(@/assets/img/pipeline-canvas-bg.png); | background-image: url(@/assets/img/pipeline-canvas-bg.png); | ||||
| background-size: 100% 100%; | background-size: 100% 100%; | ||||
| @@ -5,15 +5,12 @@ | |||||
| */ | */ | ||||
| import { useEffectWhen } from '@/hooks'; | import { useEffectWhen } from '@/hooks'; | ||||
| import { ResourceVersionData } from '@/pages/Dataset/config'; | |||||
| import { getModelAtlasReq } from '@/services/dataset/index.js'; | import { getModelAtlasReq } from '@/services/dataset/index.js'; | ||||
| import themes from '@/styles/theme.less'; | import themes from '@/styles/theme.less'; | ||||
| import { to } from '@/utils/promise'; | import { to } from '@/utils/promise'; | ||||
| import G6, { G6GraphEvent, Graph, INode } from '@antv/g6'; | import G6, { G6GraphEvent, Graph, INode } from '@antv/g6'; | ||||
| // @ts-ignore | |||||
| import { Flex, Select } from 'antd'; | |||||
| import { useEffect, useRef, useState } from 'react'; | import { useEffect, useRef, useState } from 'react'; | ||||
| import GraphLegend from '../GraphLegend'; | |||||
| import NodeTooltips from '../NodeTooltips'; | import NodeTooltips from '../NodeTooltips'; | ||||
| import styles from './index.less'; | import styles from './index.less'; | ||||
| import type { ModelDepsData, ProjectDependency, TrainDataset } from './utils'; | import type { ModelDepsData, ProjectDependency, TrainDataset } from './utils'; | ||||
| @@ -29,7 +26,7 @@ import { | |||||
| type modeModelEvolutionProps = { | type modeModelEvolutionProps = { | ||||
| resourceId: number; | resourceId: number; | ||||
| versionList: ResourceVersionData[]; | |||||
| identifier: string; | |||||
| version?: string; | version?: string; | ||||
| isActive: boolean; | isActive: boolean; | ||||
| onVersionChange: (version: string) => void; | onVersionChange: (version: string) => void; | ||||
| @@ -38,7 +35,7 @@ type modeModelEvolutionProps = { | |||||
| let graph: Graph; | let graph: Graph; | ||||
| function ModelEvolution({ | function ModelEvolution({ | ||||
| resourceId, | resourceId, | ||||
| versionList, | |||||
| identifier, | |||||
| version, | version, | ||||
| isActive, | isActive, | ||||
| onVersionChange, | onVersionChange, | ||||
| @@ -147,14 +144,15 @@ function ModelEvolution({ | |||||
| // 更加缩放,调整 tooltip 位置 | // 更加缩放,调整 tooltip 位置 | ||||
| const offsetX = (nodeWidth * zoom) / 4; | const offsetX = (nodeWidth * zoom) / 4; | ||||
| const offsetY = (nodeHeight * zoom) / 4; | const offsetY = (nodeHeight * zoom) / 4; | ||||
| point.x += offsetX; | |||||
| const canvasWidth = graphRef.current!.clientWidth; | const canvasWidth = graphRef.current!.clientWidth; | ||||
| if (point.x + 300 > canvasWidth) { | |||||
| point.x = canvasWidth - 300; | |||||
| if (point.x + 300 > canvasWidth + 30) { | |||||
| point.x = canvasWidth + 30 - 300; | |||||
| } | } | ||||
| setHoverNodeData(model); | setHoverNodeData(model); | ||||
| setNodeToolTipX(point.x + offsetX); | |||||
| setNodeToolTipX(point.x); | |||||
| setNodeToolTipY(graphRef.current!.clientHeight - point.y + offsetY); | setNodeToolTipY(graphRef.current!.clientHeight - point.y + offsetY); | ||||
| setShowNodeTooltip(true); | setShowNodeTooltip(true); | ||||
| }); | }); | ||||
| @@ -217,7 +215,8 @@ function ModelEvolution({ | |||||
| // 获取模型依赖 | // 获取模型依赖 | ||||
| const getModelAtlas = async () => { | const getModelAtlas = async () => { | ||||
| const params = { | const params = { | ||||
| current_model_id: resourceId, | |||||
| id: resourceId, | |||||
| identifier, | |||||
| version, | version, | ||||
| }; | }; | ||||
| const [res] = await to(getModelAtlasReq(params)); | const [res] = await to(getModelAtlasReq(params)); | ||||
| @@ -249,18 +248,6 @@ function ModelEvolution({ | |||||
| return ( | return ( | ||||
| <div className={styles['model-evolution']}> | <div className={styles['model-evolution']}> | ||||
| <Flex align="center" className={styles['model-evolution__top']}> | |||||
| <span style={{ marginRight: '10px' }}>版本号:</span> | |||||
| <Select | |||||
| placeholder="请选择版本号" | |||||
| style={{ width: '160px', marginRight: '20px' }} | |||||
| value={version} | |||||
| allowClear | |||||
| onChange={onVersionChange} | |||||
| options={versionList} | |||||
| /> | |||||
| <GraphLegend style={{ marginRight: 0, marginLeft: 'auto' }}></GraphLegend> | |||||
| </Flex> | |||||
| <div className={styles['model-evolution__graph']} id="canvas" ref={graphRef}></div> | <div className={styles['model-evolution__graph']} id="canvas" ref={graphRef}></div> | ||||
| {(showNodeTooltip || enterTooltip) && ( | {(showNodeTooltip || enterTooltip) && ( | ||||
| <NodeTooltips | <NodeTooltips | ||||
| @@ -1,3 +1,4 @@ | |||||
| import { TrainTask } from '@/pages/Dataset/config'; | |||||
| import { changePropertyName, fittingString } from '@/utils'; | import { changePropertyName, fittingString } from '@/utils'; | ||||
| import { EdgeConfig, GraphData, LayoutConfig, NodeConfig, TreeGraphData, Util } from '@antv/g6'; | import { EdgeConfig, GraphData, LayoutConfig, NodeConfig, TreeGraphData, Util } from '@antv/g6'; | ||||
| // @ts-ignore | // @ts-ignore | ||||
| @@ -31,16 +32,12 @@ export type Rect = { | |||||
| height: number; | height: number; | ||||
| }; | }; | ||||
| export type TrainTask = { | |||||
| ins_id: number; | |||||
| name: string; | |||||
| task_id: string; | |||||
| }; | |||||
| export interface TrainDataset extends NodeConfig { | export interface TrainDataset extends NodeConfig { | ||||
| dataset_id: number; | |||||
| dataset_name: string; | |||||
| dataset_version: string; | |||||
| repo_id: number; | |||||
| name: string; | |||||
| version: string; | |||||
| identifier: string; | |||||
| owner: string; | |||||
| model_type: NodeType.TestDataset | NodeType.TrainDataset; | model_type: NodeType.TestDataset | NodeType.TrainDataset; | ||||
| } | } | ||||
| @@ -51,34 +48,33 @@ export interface ProjectDependency extends NodeConfig { | |||||
| model_type: NodeType.Project; | model_type: NodeType.Project; | ||||
| } | } | ||||
| export type ModalDetail = { | |||||
| export type ModelMeta = { | |||||
| train_datasets?: TrainDataset[]; | |||||
| test_datasets?: TrainDataset[]; | |||||
| project_depency?: ProjectDependency; | |||||
| train_task?: TrainTask; | |||||
| name: string; | name: string; | ||||
| available_range: number; | |||||
| file_name: string; | |||||
| file_size: string; | |||||
| description: string; | |||||
| model_type_name: string; | |||||
| model_tag_name: string; | |||||
| version: string; | |||||
| model_source: string; | |||||
| model_type: string; | |||||
| create_time: string; | create_time: string; | ||||
| file_size: string; | |||||
| is_public: boolean; | |||||
| }; | }; | ||||
| export interface ModelDepsAPIData { | export interface ModelDepsAPIData { | ||||
| current_model_id: number; | |||||
| repo_id: number; | |||||
| model_name: string; | |||||
| version: string; | version: string; | ||||
| workflow_id: number; | workflow_id: number; | ||||
| exp_ins_id: number; | exp_ins_id: number; | ||||
| model_type: NodeType.Children | NodeType.Current | NodeType.Parent; | model_type: NodeType.Children | NodeType.Current | NodeType.Parent; | ||||
| current_model_name: string; | |||||
| project_dependency?: ProjectDependency; | |||||
| test_dataset: TrainDataset[]; | |||||
| train_dataset: TrainDataset[]; | |||||
| train_task: TrainTask; | |||||
| model_version_dependcy_vo: ModalDetail; | |||||
| children_models: ModelDepsAPIData[]; | |||||
| parent_models: ModelDepsAPIData[]; | |||||
| model_meta: ModelMeta; | |||||
| child_model_list: ModelDepsAPIData[]; | |||||
| parent_model_vo?: ModelDepsAPIData; | |||||
| } | } | ||||
| export interface ModelDepsData extends Omit<ModelDepsAPIData, 'children_models'>, TreeGraphData { | |||||
| export interface ModelDepsData extends Omit<ModelDepsAPIData, 'child_model_list'>, TreeGraphData { | |||||
| children: ModelDepsData[]; | children: ModelDepsData[]; | ||||
| expanded: boolean; // 是否展开 | expanded: boolean; // 是否展开 | ||||
| level: number; // 层级,从 0 开始 | level: number; // 层级,从 0 开始 | ||||
| @@ -92,8 +88,11 @@ export function normalizeChildren(data: ModelDepsData[]) { | |||||
| item.model_type = NodeType.Children; | item.model_type = NodeType.Children; | ||||
| item.expanded = false; | item.expanded = false; | ||||
| item.level = 0; | item.level = 0; | ||||
| item.datasetLen = item.train_dataset.length + item.test_dataset.length; | |||||
| item.id = `$M_${item.current_model_id}_${item.version}`; | |||||
| item.datasetLen = getDatasetLen( | |||||
| item.model_meta.train_datasets, | |||||
| item.model_meta.test_datasets, | |||||
| ); | |||||
| item.id = `$M_${item.repo_id}_${item.version}`; | |||||
| item.label = getLabel(item); | item.label = getLabel(item); | ||||
| item.style = getStyle(NodeType.Children); | item.style = getStyle(NodeType.Children); | ||||
| normalizeChildren(item.children); | normalizeChildren(item.children); | ||||
| @@ -104,16 +103,17 @@ export function normalizeChildren(data: ModelDepsData[]) { | |||||
| // 获取 label | // 获取 label | ||||
| export function getLabel(node: ModelDepsData | ModelDepsAPIData) { | export function getLabel(node: ModelDepsData | ModelDepsAPIData) { | ||||
| return ( | return ( | ||||
| fittingString( | |||||
| `${node.model_version_dependcy_vo.name ?? ''}`, | |||||
| nodeWidth - labelPadding, | |||||
| nodeFontSize, | |||||
| ) + | |||||
| fittingString(`${node.model_name ?? ''}`, nodeWidth - labelPadding, nodeFontSize) + | |||||
| '\n' + | '\n' + | ||||
| fittingString(`${node.version}`, nodeWidth - labelPadding, nodeFontSize) | fittingString(`${node.version}`, nodeWidth - labelPadding, nodeFontSize) | ||||
| ); | ); | ||||
| } | } | ||||
| // 获取数据集数量 | |||||
| export function getDatasetLen(train?: TrainDataset[], test?: TrainDataset[]) { | |||||
| return (train?.length || 0) + (test?.length || 0); | |||||
| } | |||||
| // 获取 style | // 获取 style | ||||
| export function getStyle(model_type: NodeType) { | export function getStyle(model_type: NodeType) { | ||||
| let fill = ''; | let fill = ''; | ||||
| @@ -148,41 +148,43 @@ export function getStyle(model_type: NodeType) { | |||||
| export function normalizeTreeData(apiData: ModelDepsAPIData): ModelDepsData { | export function normalizeTreeData(apiData: ModelDepsAPIData): ModelDepsData { | ||||
| // 将 children_models 转换成 children | // 将 children_models 转换成 children | ||||
| let normalizedData = changePropertyName(apiData, { | let normalizedData = changePropertyName(apiData, { | ||||
| children_models: 'children', | |||||
| child_model_list: 'children', | |||||
| }) as ModelDepsData; | }) as ModelDepsData; | ||||
| // 设置当前模型的数据 | // 设置当前模型的数据 | ||||
| normalizedData.model_type = NodeType.Current; | normalizedData.model_type = NodeType.Current; | ||||
| normalizedData.id = `$M_${normalizedData.current_model_id}_${normalizedData.version}`; | |||||
| normalizedData.id = `$M_${normalizedData.repo_id}_${normalizedData.version}`; | |||||
| normalizedData.label = getLabel(normalizedData); | normalizedData.label = getLabel(normalizedData); | ||||
| normalizedData.style = getStyle(NodeType.Current); | normalizedData.style = getStyle(NodeType.Current); | ||||
| normalizedData.expanded = true; | normalizedData.expanded = true; | ||||
| normalizedData.datasetLen = | |||||
| normalizedData.train_dataset.length + normalizedData.test_dataset.length; | |||||
| normalizedData.datasetLen = getDatasetLen( | |||||
| normalizedData.model_meta.train_datasets, | |||||
| normalizedData.model_meta.test_datasets, | |||||
| ); | |||||
| normalizeChildren(normalizedData.children as ModelDepsData[]); | normalizeChildren(normalizedData.children as ModelDepsData[]); | ||||
| normalizedData.level = 0; | normalizedData.level = 0; | ||||
| // 将 parent_models 转换成树形结构 | // 将 parent_models 转换成树形结构 | ||||
| let parent_models = normalizedData.parent_models || []; | |||||
| while (parent_models.length > 0) { | |||||
| const parent = parent_models[0]; | |||||
| let parent_model = normalizedData.parent_model_vo; | |||||
| while (parent_model) { | |||||
| const parent = parent_model; | |||||
| normalizedData = { | normalizedData = { | ||||
| ...parent, | ...parent, | ||||
| expanded: false, | expanded: false, | ||||
| level: 0, | level: 0, | ||||
| datasetLen: parent.train_dataset.length + parent.test_dataset.length, | |||||
| datasetLen: getDatasetLen(parent.model_meta.train_datasets, parent.model_meta.test_datasets), | |||||
| model_type: NodeType.Parent, | model_type: NodeType.Parent, | ||||
| id: `$M_${parent.current_model_id}_${parent.version}`, | |||||
| id: `$M_${parent.repo_id}_${parent.version}`, | |||||
| label: getLabel(parent), | label: getLabel(parent), | ||||
| style: getStyle(NodeType.Parent), | style: getStyle(NodeType.Parent), | ||||
| children: [ | children: [ | ||||
| { | { | ||||
| ...normalizedData, | ...normalizedData, | ||||
| parent_models: [], | |||||
| parent_model: null, | |||||
| }, | }, | ||||
| ], | ], | ||||
| }; | }; | ||||
| parent_models = normalizedData.parent_models || []; | |||||
| parent_model = normalizedData.parent_model_vo; | |||||
| } | } | ||||
| return normalizedData; | return normalizedData; | ||||
| } | } | ||||
| @@ -195,11 +197,12 @@ export function getGraphData(data: ModelDepsData, hierarchyNodes: ModelDepsData[ | |||||
| getWidth: () => nodeWidth, | getWidth: () => nodeWidth, | ||||
| getVGap: (node: NodeConfig) => { | getVGap: (node: NodeConfig) => { | ||||
| const model = node as ModelDepsData; | const model = node as ModelDepsData; | ||||
| const { model_type, expanded, project_dependency } = model; | |||||
| const { model_type, expanded, model_meta } = model; | |||||
| const { project_depency } = model_meta; | |||||
| if (model_type === NodeType.Current || model_type === NodeType.Parent) { | if (model_type === NodeType.Current || model_type === NodeType.Parent) { | ||||
| return vGap / 2; | return vGap / 2; | ||||
| } | } | ||||
| const selfGap = expanded && project_dependency?.url ? nodeHeight + vGap : 0; | |||||
| const selfGap = expanded && project_depency?.url ? nodeHeight + vGap : 0; | |||||
| const nextNode = getSameHierarchyNextNode(model, hierarchyNodes); | const nextNode = getSameHierarchyNextNode(model, hierarchyNodes); | ||||
| if (!nextNode) { | if (!nextNode) { | ||||
| return vGap / 2; | return vGap / 2; | ||||
| @@ -254,28 +257,35 @@ const addDatasetDependency = ( | |||||
| nodes: NodeConfig[], | nodes: NodeConfig[], | ||||
| edges: EdgeConfig[], | edges: EdgeConfig[], | ||||
| ) => { | ) => { | ||||
| const { train_dataset, test_dataset, id } = data; | |||||
| train_dataset.forEach((item) => { | |||||
| item.id = `$DTrain_${id}_${item.dataset_id}_${item.dataset_version}`; | |||||
| const { repo_id, model_meta } = data; | |||||
| const { train_datasets, test_datasets } = model_meta; | |||||
| train_datasets?.forEach((item) => { | |||||
| if (!item.repo_id) { | |||||
| item.repo_id = item.id; | |||||
| } | |||||
| item.id = `$DTrain_${repo_id}_${item.repo_id}_${item.version}`; | |||||
| item.model_type = NodeType.TrainDataset; | item.model_type = NodeType.TrainDataset; | ||||
| item.style = getStyle(NodeType.TrainDataset); | item.style = getStyle(NodeType.TrainDataset); | ||||
| }); | }); | ||||
| test_dataset.forEach((item) => { | |||||
| item.id = `$DTest_${id}_${item.dataset_id}_${item.dataset_version}`; | |||||
| test_datasets?.forEach((item) => { | |||||
| if (!item.repo_id) { | |||||
| item.repo_id = item.id; | |||||
| } | |||||
| item.id = `$DTest_${repo_id}_${item.repo_id}_${item.version}`; | |||||
| item.model_type = NodeType.TestDataset; | item.model_type = NodeType.TestDataset; | ||||
| item.style = getStyle(NodeType.TestDataset); | item.style = getStyle(NodeType.TestDataset); | ||||
| }); | }); | ||||
| datasetNodes.length = 0; | datasetNodes.length = 0; | ||||
| const len = train_dataset.length + test_dataset.length; | |||||
| [...train_dataset, ...test_dataset].forEach((item, index) => { | |||||
| const len = getDatasetLen(train_datasets, test_datasets); | |||||
| [...(train_datasets ?? []), ...(test_datasets ?? [])].forEach((item, index) => { | |||||
| const node = { ...item }; | const node = { ...item }; | ||||
| node.type = 'ellipse'; | node.type = 'ellipse'; | ||||
| node.size = [ellipseWidth, nodeHeight]; | node.size = [ellipseWidth, nodeHeight]; | ||||
| node.label = | node.label = | ||||
| fittingString(node.dataset_name, ellipseWidth - labelPadding, nodeFontSize) + | |||||
| fittingString(node.name, ellipseWidth - labelPadding, nodeFontSize) + | |||||
| '\n' + | '\n' + | ||||
| fittingString(node.dataset_version, ellipseWidth - labelPadding, nodeFontSize); | |||||
| fittingString(node.version, ellipseWidth - labelPadding, nodeFontSize); | |||||
| const half = len / 2 - 0.5; | const half = len / 2 - 0.5; | ||||
| node.x = currentNode.x! - (half - index) * (ellipseWidth + datasetHGap); | node.x = currentNode.x! - (half - index) * (ellipseWidth + datasetHGap); | ||||
| @@ -299,10 +309,11 @@ const addProjectDependency = ( | |||||
| nodes: NodeConfig[], | nodes: NodeConfig[], | ||||
| edges: EdgeConfig[], | edges: EdgeConfig[], | ||||
| ) => { | ) => { | ||||
| const { project_dependency, id } = data; | |||||
| if (project_dependency?.url) { | |||||
| const node = { ...project_dependency }; | |||||
| node.id = `$P_${id}_${node.url}_${node.branch}`; | |||||
| const { repo_id, model_meta } = data; | |||||
| const { project_depency } = model_meta; | |||||
| if (project_depency?.url) { | |||||
| const node = { ...project_depency }; | |||||
| node.id = `$P_${repo_id}_${node.url}_${node.branch}`; | |||||
| node.model_type = NodeType.Project; | node.model_type = NodeType.Project; | ||||
| node.type = 'rect'; | node.type = 'rect'; | ||||
| node.label = fittingString(node.name, nodeWidth - labelPadding, nodeFontSize); | node.label = fittingString(node.name, nodeWidth - labelPadding, nodeFontSize); | ||||
| @@ -322,6 +333,7 @@ const addProjectDependency = ( | |||||
| } | } | ||||
| }; | }; | ||||
| /* | |||||
| // 判断两个矩形是否相交 | // 判断两个矩形是否相交 | ||||
| function isRectanglesOverlap(rect1: Rect, rect2: Rect) { | function isRectanglesOverlap(rect1: Rect, rect2: Rect) { | ||||
| const a2x = rect1.x + rect1.width / 2; | const a2x = rect1.x + rect1.width / 2; | ||||
| @@ -366,6 +378,7 @@ function adjustDatasetPosition(node: NodeConfig) { | |||||
| }); | }); | ||||
| } | } | ||||
| } | } | ||||
| */ | |||||
| // 层级遍历树结构 | // 层级遍历树结构 | ||||
| export function traverseHierarchically(data: ModelDepsData | undefined): ModelDepsData[] { | export function traverseHierarchically(data: ModelDepsData | undefined): ModelDepsData[] { | ||||
| @@ -2,6 +2,7 @@ | |||||
| position: absolute; | position: absolute; | ||||
| bottom: -100px; | bottom: -100px; | ||||
| left: -300px; | left: -300px; | ||||
| z-index: 10; | |||||
| width: 300px; | width: 300px; | ||||
| padding: 10px; | padding: 10px; | ||||
| background: white; | background: white; | ||||
| @@ -50,6 +51,7 @@ | |||||
| flex: 1; | flex: 1; | ||||
| min-width: 0; | min-width: 0; | ||||
| font-weight: 500; | font-weight: 500; | ||||
| word-break: break-all; | |||||
| &:hover { | &:hover { | ||||
| text-decoration: underline @underline-color; | text-decoration: underline @underline-color; | ||||
| @@ -1,4 +1,4 @@ | |||||
| import { ResourceInfoTabKeys } from '@/pages/Dataset/components/ResourceIntro'; | |||||
| import { ResourceInfoTabKeys } from '@/pages/Dataset/components/ResourceInfo'; | |||||
| import { formatDate } from '@/utils/date'; | import { formatDate } from '@/utils/date'; | ||||
| import { useNavigate } from '@umijs/max'; | import { useNavigate } from '@umijs/max'; | ||||
| import { ModelDepsData, NodeType, ProjectDependency, TrainDataset } from '../ModelEvolution/utils'; | import { ModelDepsData, NodeType, ProjectDependency, TrainDataset } from '../ModelEvolution/utils'; | ||||
| @@ -14,9 +14,9 @@ function ModelInfo({ resourceId, data, onVersionChange }: ModelInfoProps) { | |||||
| const navigate = useNavigate(); | const navigate = useNavigate(); | ||||
| const gotoExperimentPage = () => { | const gotoExperimentPage = () => { | ||||
| if (data.train_task?.ins_id) { | |||||
| if (data.model_meta.train_task?.ins_id) { | |||||
| const { origin } = location; | const { origin } = location; | ||||
| const url = `${origin}/pipeline/experiment/instance/${data.workflow_id}/${data.train_task.ins_id}`; | |||||
| const url = `${origin}/pipeline/experiment/instance/${data.model_meta.train_task.workflow_id}/${data.model_meta.train_task.ins_id}`; | |||||
| window.open(url, '_blank'); | window.open(url, '_blank'); | ||||
| } | } | ||||
| }; | }; | ||||
| @@ -25,10 +25,10 @@ function ModelInfo({ resourceId, data, onVersionChange }: ModelInfoProps) { | |||||
| if (data.model_type === NodeType.Current) { | if (data.model_type === NodeType.Current) { | ||||
| return; | return; | ||||
| } | } | ||||
| if (data.current_model_id === resourceId) { | |||||
| if (data.repo_id === resourceId) { | |||||
| onVersionChange?.(data.version); | onVersionChange?.(data.version); | ||||
| } else { | } else { | ||||
| const path = `/dataset/model/info/${data.current_model_id}?tab=${ResourceInfoTabKeys.Evolution}&version=${data.version}`; | |||||
| const path = `/dataset/model/info/${data.repo_id}?tab=${ResourceInfoTabKeys.Evolution}&version=${data.version}&name=${data.model_name}&owner=${data.owner}&identifier=${data.identifier}`; | |||||
| navigate(path); | navigate(path); | ||||
| } | } | ||||
| }; | }; | ||||
| @@ -40,12 +40,10 @@ function ModelInfo({ resourceId, data, onVersionChange }: ModelInfoProps) { | |||||
| <div className={styles['node-tooltips__row']}> | <div className={styles['node-tooltips__row']}> | ||||
| <span className={styles['node-tooltips__row__title']}>模型名称:</span> | <span className={styles['node-tooltips__row__title']}>模型名称:</span> | ||||
| {data.model_type === NodeType.Current ? ( | {data.model_type === NodeType.Current ? ( | ||||
| <span className={styles['node-tooltips__row__value']}> | |||||
| {data.model_version_dependcy_vo?.name || '--'} | |||||
| </span> | |||||
| <span className={styles['node-tooltips__row__value']}>{data.model_name || '--'}</span> | |||||
| ) : ( | ) : ( | ||||
| <ValueLink | <ValueLink | ||||
| value={data.model_version_dependcy_vo?.name} | |||||
| value={data.model_name} | |||||
| className={styles['node-tooltips__row__link']} | className={styles['node-tooltips__row__link']} | ||||
| nullClassName={styles['node-tooltips__row__value']} | nullClassName={styles['node-tooltips__row__value']} | ||||
| onClick={gotoModelPage} | onClick={gotoModelPage} | ||||
| @@ -59,25 +57,25 @@ function ModelInfo({ resourceId, data, onVersionChange }: ModelInfoProps) { | |||||
| <div className={styles['node-tooltips__row']}> | <div className={styles['node-tooltips__row']}> | ||||
| <span className={styles['node-tooltips__row__title']}>模型框架:</span> | <span className={styles['node-tooltips__row__title']}>模型框架:</span> | ||||
| <span className={styles['node-tooltips__row__value']}> | <span className={styles['node-tooltips__row__value']}> | ||||
| {data.model_version_dependcy_vo?.model_type_name || '--'} | |||||
| {data.model_meta.model_type || '--'} | |||||
| </span> | </span> | ||||
| </div> | </div> | ||||
| <div className={styles['node-tooltips__row']}> | |||||
| {/* <div className={styles['node-tooltips__row']}> | |||||
| <span className={styles['node-tooltips__row__title']}>模型大小:</span> | <span className={styles['node-tooltips__row__title']}>模型大小:</span> | ||||
| <span className={styles['node-tooltips__row__value']}> | <span className={styles['node-tooltips__row__value']}> | ||||
| {data.model_version_dependcy_vo?.file_size || '--'} | |||||
| {data.model_meta.file_size || '--'} | |||||
| </span> | </span> | ||||
| </div> | |||||
| </div> */} | |||||
| <div className={styles['node-tooltips__row']}> | <div className={styles['node-tooltips__row']}> | ||||
| <span className={styles['node-tooltips__row__title']}>创建时间:</span> | <span className={styles['node-tooltips__row__title']}>创建时间:</span> | ||||
| <span className={styles['node-tooltips__row__value']}> | <span className={styles['node-tooltips__row__value']}> | ||||
| {formatDate(data.model_version_dependcy_vo?.create_time)} | |||||
| {formatDate(data.model_meta.create_time || '--')} | |||||
| </span> | </span> | ||||
| </div> | </div> | ||||
| <div className={styles['node-tooltips__row']}> | <div className={styles['node-tooltips__row']}> | ||||
| <span className={styles['node-tooltips__row__title']}>模型权限:</span> | <span className={styles['node-tooltips__row__title']}>模型权限:</span> | ||||
| <span className={styles['node-tooltips__row__value']}> | <span className={styles['node-tooltips__row__value']}> | ||||
| {data.model_version_dependcy_vo?.available_range === 1 ? '公开' : '私有'} | |||||
| {data.model_meta.is_public ? '公开' : '私有'} | |||||
| </span> | </span> | ||||
| </div> | </div> | ||||
| </div> | </div> | ||||
| @@ -86,7 +84,7 @@ function ModelInfo({ resourceId, data, onVersionChange }: ModelInfoProps) { | |||||
| <div className={styles['node-tooltips__row']}> | <div className={styles['node-tooltips__row']}> | ||||
| <span className={styles['node-tooltips__row__title']}>训练任务:</span> | <span className={styles['node-tooltips__row__title']}>训练任务:</span> | ||||
| <ValueLink | <ValueLink | ||||
| value={data.train_task?.name} | |||||
| value={data.model_meta.train_task?.name} | |||||
| className={styles['node-tooltips__row__link']} | className={styles['node-tooltips__row__link']} | ||||
| nullClassName={styles['node-tooltips__row__value']} | nullClassName={styles['node-tooltips__row__value']} | ||||
| onClick={gotoExperimentPage} | onClick={gotoExperimentPage} | ||||
| @@ -100,7 +98,7 @@ function ModelInfo({ resourceId, data, onVersionChange }: ModelInfoProps) { | |||||
| function DatasetInfo({ data }: { data: TrainDataset }) { | function DatasetInfo({ data }: { data: TrainDataset }) { | ||||
| const gotoDatasetPage = () => { | const gotoDatasetPage = () => { | ||||
| const { origin } = location; | const { origin } = location; | ||||
| const url = `${origin}/dataset/dataset/info/${data.dataset_id}?tab=${ResourceInfoTabKeys.Version}&version=${data.dataset_version}`; | |||||
| const url = `${origin}/dataset/dataset/info/${data.repo_id}?tab=${ResourceInfoTabKeys.Version}&version=${data.version}&name=${data.name}&owner=${data.owner}&identifier=${data.identifier}`; | |||||
| window.open(url, '_blank'); | window.open(url, '_blank'); | ||||
| }; | }; | ||||
| @@ -111,7 +109,7 @@ function DatasetInfo({ data }: { data: TrainDataset }) { | |||||
| <div className={styles['node-tooltips__row']}> | <div className={styles['node-tooltips__row']}> | ||||
| <span className={styles['node-tooltips__row__title']}>数据集名称:</span> | <span className={styles['node-tooltips__row__title']}>数据集名称:</span> | ||||
| <ValueLink | <ValueLink | ||||
| value={data.dataset_name} | |||||
| value={data.name} | |||||
| className={styles['node-tooltips__row__link']} | className={styles['node-tooltips__row__link']} | ||||
| nullClassName={styles['node-tooltips__row__value']} | nullClassName={styles['node-tooltips__row__value']} | ||||
| onClick={gotoDatasetPage} | onClick={gotoDatasetPage} | ||||
| @@ -119,9 +117,7 @@ function DatasetInfo({ data }: { data: TrainDataset }) { | |||||
| </div> | </div> | ||||
| <div className={styles['node-tooltips__row']}> | <div className={styles['node-tooltips__row']}> | ||||
| <span className={styles['node-tooltips__row__title']}>数据集版本:</span> | <span className={styles['node-tooltips__row__title']}>数据集版本:</span> | ||||
| <span className={styles['node-tooltips__row__value']}> | |||||
| {data.dataset_version || '--'} | |||||
| </span> | |||||
| <span className={styles['node-tooltips__row__value']}>{data.version || '--'}</span> | |||||
| </div> | </div> | ||||
| </div> | </div> | ||||
| </> | </> | ||||
| @@ -130,8 +126,12 @@ function DatasetInfo({ data }: { data: TrainDataset }) { | |||||
| function ProjectInfo({ data }: { data: ProjectDependency }) { | function ProjectInfo({ data }: { data: ProjectDependency }) { | ||||
| const gotoProjectPage = () => { | const gotoProjectPage = () => { | ||||
| const { url } = data; | |||||
| window.open(url, '_blank'); | |||||
| const { url, branch } = data; | |||||
| let projectUrl = url; | |||||
| if (url.endsWith('.git')) { | |||||
| projectUrl = `${url.substring(0, url.length - 4)}/tree/${branch}`; | |||||
| } | |||||
| window.open(projectUrl, '_blank'); | |||||
| }; | }; | ||||
| return ( | return ( | ||||
| @@ -1,8 +1,8 @@ | |||||
| import ResourceIntro from '@/pages/Dataset/components/ResourceIntro'; | |||||
| import ResourceInfo from '@/pages/Dataset/components/ResourceInfo'; | |||||
| import { ResourceType } from '@/pages/Dataset/config'; | import { ResourceType } from '@/pages/Dataset/config'; | ||||
| function ModelIntro() { | |||||
| return <ResourceIntro resourceType={ResourceType.Model} />; | |||||
| function ModelInfo() { | |||||
| return <ResourceInfo resourceType={ResourceType.Model} />; | |||||
| } | } | ||||
| export default ModelIntro; | |||||
| export default ModelInfo; | |||||
| @@ -0,0 +1,182 @@ | |||||
| /* | |||||
| * @Author: 赵伟 | |||||
| * @Date: 2024-04-16 13:58:08 | |||||
| * @Description: 创建推理服务 | |||||
| */ | |||||
| import PageTitle from '@/components/PageTitle'; | |||||
| import SubAreaTitle from '@/components/SubAreaTitle'; | |||||
| import { CommonTabKeys, serviceTypeOptions } from '@/enums'; | |||||
| import { createServiceReq, updateServiceReq } from '@/services/modelDeployment'; | |||||
| import { to } from '@/utils/promise'; | |||||
| import { | |||||
| getSessionStorageItem, | |||||
| removeSessionStorageItem, | |||||
| serviceInfoKey, | |||||
| } from '@/utils/sessionStorage'; | |||||
| import { useNavigate } from '@umijs/max'; | |||||
| import { App, Button, Col, Form, Input, Row, Select } from 'antd'; | |||||
| import { pick } from 'lodash'; | |||||
| import { useEffect, useState } from 'react'; | |||||
| import { ServiceData, ServiceOperationType } from '../types'; | |||||
| import styles from './index.less'; | |||||
| // 表单数据 | |||||
| export type FormData = { | |||||
| service_name: string; // 服务名称 | |||||
| service_type: string; // 服务类型 | |||||
| description: string; // 描述 | |||||
| }; | |||||
| function CreateService() { | |||||
| const navigate = useNavigate(); | |||||
| const [form] = Form.useForm(); | |||||
| const [operationType, setOperationType] = useState(ServiceOperationType.Create); | |||||
| const [serviceInfo, setServiceInfo] = useState<ServiceData | undefined>(undefined); | |||||
| const { message } = App.useApp(); | |||||
| useEffect(() => { | |||||
| const res = getSessionStorageItem(serviceInfoKey, true); | |||||
| if (res) { | |||||
| setOperationType(res.operationType); | |||||
| setServiceInfo(res); | |||||
| form.setFieldsValue(pick(res, ['service_name', 'service_type', 'description'])); | |||||
| } | |||||
| return () => { | |||||
| removeSessionStorageItem(serviceInfoKey); | |||||
| }; | |||||
| }, []); | |||||
| // 创建、更新服务 | |||||
| const createService = async (formData: FormData) => { | |||||
| const request = | |||||
| operationType === ServiceOperationType.Create ? createServiceReq : updateServiceReq; | |||||
| const params = | |||||
| operationType === ServiceOperationType.Create | |||||
| ? formData | |||||
| : { | |||||
| id: serviceInfo?.id, | |||||
| ...formData, | |||||
| }; | |||||
| const [res] = await to(request(params)); | |||||
| if (res) { | |||||
| message.success('操作成功'); | |||||
| navigate(-1); | |||||
| } | |||||
| }; | |||||
| // 提交 | |||||
| const handleSubmit = (values: FormData) => { | |||||
| createService(values); | |||||
| }; | |||||
| // 取消 | |||||
| const cancel = () => { | |||||
| navigate(-1); | |||||
| }; | |||||
| const disabled = operationType !== ServiceOperationType.Create; | |||||
| const title = operationType === ServiceOperationType.Create ? '创建推理服务' : '更新推理服务'; | |||||
| return ( | |||||
| <div className={styles['model-deployment-create']}> | |||||
| <PageTitle title={title}></PageTitle> | |||||
| <div className={styles['model-deployment-create__content']}> | |||||
| <div> | |||||
| <Form | |||||
| name="model-deployment-create" | |||||
| labelCol={{ flex: '100px' }} | |||||
| labelAlign="left" | |||||
| form={form} | |||||
| initialValues={{ upload_type: CommonTabKeys.Public }} | |||||
| onFinish={handleSubmit} | |||||
| size="large" | |||||
| autoComplete="off" | |||||
| > | |||||
| <SubAreaTitle | |||||
| title="基本信息" | |||||
| image={require('@/assets/img/mirror-basic.png')} | |||||
| style={{ marginBottom: '26px' }} | |||||
| ></SubAreaTitle> | |||||
| <Row gutter={8}> | |||||
| <Col span={10}> | |||||
| <Form.Item | |||||
| label="服务名称" | |||||
| name="service_name" | |||||
| rules={[ | |||||
| { | |||||
| required: true, | |||||
| message: '请输入服务名称', | |||||
| }, | |||||
| ]} | |||||
| > | |||||
| <Input | |||||
| placeholder="请输入服务名称" | |||||
| disabled={disabled} | |||||
| maxLength={30} | |||||
| showCount | |||||
| allowClear | |||||
| /> | |||||
| </Form.Item> | |||||
| </Col> | |||||
| </Row> | |||||
| <Row gutter={8}> | |||||
| <Col span={10}> | |||||
| <Form.Item | |||||
| label="服务类型" | |||||
| name="service_type" | |||||
| rules={[ | |||||
| { | |||||
| required: true, | |||||
| message: '请选择服务类型', | |||||
| }, | |||||
| ]} | |||||
| > | |||||
| <Select placeholder="请选择服务类型" options={serviceTypeOptions} allowClear /> | |||||
| </Form.Item> | |||||
| </Col> | |||||
| </Row> | |||||
| <Row gutter={8}> | |||||
| <Col span={20}> | |||||
| <Form.Item | |||||
| label="描 述" | |||||
| name="description" | |||||
| rules={[ | |||||
| { | |||||
| required: true, | |||||
| message: '请输入描述', | |||||
| }, | |||||
| ]} | |||||
| > | |||||
| <Input.TextArea | |||||
| autoSize={{ minRows: 2, maxRows: 6 }} | |||||
| placeholder="请输入描述,最长128字符" | |||||
| maxLength={128} | |||||
| showCount | |||||
| allowClear | |||||
| /> | |||||
| </Form.Item> | |||||
| </Col> | |||||
| </Row> | |||||
| <Form.Item wrapperCol={{ offset: 0, span: 16 }}> | |||||
| <Button type="primary" htmlType="submit"> | |||||
| 确定 | |||||
| </Button> | |||||
| <Button | |||||
| type="default" | |||||
| htmlType="button" | |||||
| onClick={cancel} | |||||
| style={{ marginLeft: '20px' }} | |||||
| > | |||||
| 取消 | |||||
| </Button> | |||||
| </Form.Item> | |||||
| </Form> | |||||
| </div> | |||||
| </div> | |||||
| </div> | |||||
| ); | |||||
| } | |||||
| export default CreateService; | |||||
| @@ -0,0 +1,19 @@ | |||||
| .create-service-version { | |||||
| height: 100%; | |||||
| &__content { | |||||
| height: calc(100% - 60px); | |||||
| margin-top: 10px; | |||||
| padding: 30px 30px 10px; | |||||
| overflow: auto; | |||||
| color: @text-color; | |||||
| font-size: @font-size-content; | |||||
| background-color: white; | |||||
| border-radius: 10px; | |||||
| &__type { | |||||
| color: @text-color; | |||||
| font-size: @font-size-input-lg; | |||||
| } | |||||
| } | |||||
| } | |||||
| @@ -1,108 +1,158 @@ | |||||
| /* | /* | ||||
| * @Author: 赵伟 | * @Author: 赵伟 | ||||
| * @Date: 2024-04-16 13:58:08 | * @Date: 2024-04-16 13:58:08 | ||||
| * @Description: 创建模型部署 | |||||
| * @Description: 创建服务版本 | |||||
| */ | */ | ||||
| import CodeSelect from '@/components/CodeSelect'; | |||||
| import KFIcon from '@/components/KFIcon'; | import KFIcon from '@/components/KFIcon'; | ||||
| import PageTitle from '@/components/PageTitle'; | import PageTitle from '@/components/PageTitle'; | ||||
| import ResourceSelect, { | import ResourceSelect, { | ||||
| requiredValidator, | requiredValidator, | ||||
| ResourceSelectorType, | |||||
| type ParameterInputObject, | type ParameterInputObject, | ||||
| } from '@/components/ResourceSelect'; | } from '@/components/ResourceSelect'; | ||||
| import SubAreaTitle from '@/components/SubAreaTitle'; | import SubAreaTitle from '@/components/SubAreaTitle'; | ||||
| import { CommonTabKeys } from '@/enums'; | |||||
| import { useComputingResource } from '@/hooks/resource'; | import { useComputingResource } from '@/hooks/resource'; | ||||
| import { ResourceSelectorType } from '@/pages/Pipeline/components/ResourceSelectorModal'; | |||||
| import { | import { | ||||
| createModelDeploymentReq, | |||||
| restartModelDeploymentReq, | |||||
| updateModelDeploymentReq, | |||||
| createServiceVersionReq, | |||||
| getServiceInfoReq, | |||||
| updateServiceVersionReq, | |||||
| } from '@/services/modelDeployment'; | } from '@/services/modelDeployment'; | ||||
| import { camelCaseToUnderscore, underscoreToCamelCase } from '@/utils'; | |||||
| import { changePropertyName } from '@/utils'; | |||||
| import { to } from '@/utils/promise'; | import { to } from '@/utils/promise'; | ||||
| import { | import { | ||||
| getSessionStorageItem, | getSessionStorageItem, | ||||
| modelDeploymentInfoKey, | |||||
| removeSessionStorageItem, | removeSessionStorageItem, | ||||
| serviceVersionInfoKey, | |||||
| } from '@/utils/sessionStorage'; | } from '@/utils/sessionStorage'; | ||||
| import { modalConfirm } from '@/utils/ui'; | import { modalConfirm } from '@/utils/ui'; | ||||
| import { useNavigate } from '@umijs/max'; | |||||
| import { PlusOutlined } from '@ant-design/icons'; | |||||
| import { useNavigate, useParams } from '@umijs/max'; | |||||
| import { App, Button, Col, Flex, Form, Input, Row, Select } from 'antd'; | import { App, Button, Col, Flex, Form, Input, Row, Select } from 'antd'; | ||||
| import { omit, pick } from 'lodash'; | import { omit, pick } from 'lodash'; | ||||
| import { useEffect, useState } from 'react'; | import { useEffect, useState } from 'react'; | ||||
| import { ModelDeploymentData, ModelDeploymentOperationType } from '../types'; | |||||
| import { ServiceData, ServiceOperationType, ServiceVersionData } from '../types'; | |||||
| import styles from './index.less'; | import styles from './index.less'; | ||||
| // 表单数据 | // 表单数据 | ||||
| export type FormData = { | export type FormData = { | ||||
| serviceName: string; // 服务名称 | |||||
| service_name: string; // 服务名称 | |||||
| version: string; // 服务版本 | |||||
| description: string; // 描述 | description: string; // 描述 | ||||
| model: ParameterInputObject; // 模型 | model: ParameterInputObject; // 模型 | ||||
| image: ParameterInputObject; // 镜像 | image: ParameterInputObject; // 镜像 | ||||
| code_config: ParameterInputObject; // 代码 | |||||
| resource: string; // 资源规格 | resource: string; // 资源规格 | ||||
| replicas: string; // 副本数量 | replicas: string; // 副本数量 | ||||
| modelPath: string; // 模型路径 | |||||
| env: { key: string; value: string }[]; // 环境变量 | |||||
| mount_path: string; // 模型路径 | |||||
| env_variables: { key: string; value: string }[]; // 环境变量 | |||||
| }; | }; | ||||
| function ModelDeploymentCreate() { | |||||
| function CreateServiceVersion() { | |||||
| const navigate = useNavigate(); | const navigate = useNavigate(); | ||||
| const [form] = Form.useForm(); | const [form] = Form.useForm(); | ||||
| const [resourceStandardList, filterResourceStandard] = useComputingResource(); | const [resourceStandardList, filterResourceStandard] = useComputingResource(); | ||||
| const [operationType, setOperationType] = useState(ModelDeploymentOperationType.Create); | |||||
| const [modelDeploymentInfo, setModelDeploymentInfo] = useState<ModelDeploymentData | undefined>( | |||||
| undefined, | |||||
| ); | |||||
| const [operationType, setOperationType] = useState(ServiceOperationType.Create); | |||||
| const { message } = App.useApp(); | const { message } = App.useApp(); | ||||
| const [serviceInfo, setServiceInfo] = useState<ServiceData | undefined>(undefined); | |||||
| const [versionInfo, setVersionInfo] = useState<ServiceVersionData | undefined>(undefined); | |||||
| const params = useParams(); | |||||
| const id = params.id; | |||||
| useEffect(() => { | useEffect(() => { | ||||
| const res = getSessionStorageItem(modelDeploymentInfoKey, true); | |||||
| const res: (ServiceVersionData & { operationType: ServiceOperationType }) | undefined = | |||||
| getSessionStorageItem(serviceVersionInfoKey, true); | |||||
| if (res) { | if (res) { | ||||
| setOperationType(res.operationType); | setOperationType(res.operationType); | ||||
| setModelDeploymentInfo(res); | |||||
| const formData = underscoreToCamelCase(res) as FormData; | |||||
| setVersionInfo(res); | |||||
| let model, codeConfig, envVariables; | |||||
| if (res.model && typeof res.model === 'object') { | |||||
| model = changePropertyName(res.model, { show_value: 'showValue' }); | |||||
| // 接口返回是数据没有 value 值,但是 form 需要 value | |||||
| model.value = model.showValue; | |||||
| } | |||||
| if (res.code_config && typeof res.code_config === 'object') { | |||||
| codeConfig = changePropertyName(res.code_config, { show_value: 'showValue' }); | |||||
| // 接口返回是数据没有 value 值,但是 form 需要 value | |||||
| codeConfig.value = codeConfig.showValue; | |||||
| } | |||||
| if (res.env_variables && typeof res.env_variables === 'object') { | |||||
| envVariables = Object.entries(res.env_variables).map(([key, value]) => ({ | |||||
| key, | |||||
| value, | |||||
| })); | |||||
| } | |||||
| const formData = { | |||||
| ...omit(res, 'model', 'code_config', 'env_variables'), | |||||
| model: model, | |||||
| code_config: codeConfig, | |||||
| env_variables: envVariables, | |||||
| }; | |||||
| form.setFieldsValue(formData); | form.setFieldsValue(formData); | ||||
| } | } | ||||
| return () => { | return () => { | ||||
| removeSessionStorageItem(modelDeploymentInfoKey); | |||||
| removeSessionStorageItem(serviceVersionInfoKey); | |||||
| }; | }; | ||||
| }, []); | }, []); | ||||
| // 创建 | |||||
| const createModelDeployment = async (formData: FormData) => { | |||||
| const envList = formData['env'] ?? []; | |||||
| useEffect(() => { | |||||
| getServiceInfo(); | |||||
| }, []); | |||||
| // 获取服务详情 | |||||
| const getServiceInfo = async () => { | |||||
| const [res] = await to(getServiceInfoReq(id)); | |||||
| if (res && res.data) { | |||||
| setServiceInfo(res.data); | |||||
| form.setFieldsValue({ | |||||
| service_name: res.data.service_name, | |||||
| }); | |||||
| } | |||||
| }; | |||||
| // 创建版本 | |||||
| const createServiceVersion = async (formData: FormData) => { | |||||
| const envList = formData['env_variables'] ?? []; | |||||
| const image = formData['image']; | const image = formData['image']; | ||||
| const model = formData['model']; | const model = formData['model']; | ||||
| const env = envList.reduce((acc, cur) => { | |||||
| const codeConfig = formData['code_config']; | |||||
| const envVariables = envList.reduce((acc, cur) => { | |||||
| acc[cur.key] = cur.value; | acc[cur.key] = cur.value; | ||||
| return acc; | return acc; | ||||
| }, {} as Record<string, string>); | }, {} as Record<string, string>); | ||||
| // 根据后台要求,修改表单数据 | // 根据后台要求,修改表单数据 | ||||
| const object = camelCaseToUnderscore({ | |||||
| ...omit(formData, ['replicas', 'env', 'image', 'model']), | |||||
| const object = { | |||||
| ...omit(formData, ['replicas', 'env_variables', 'image', 'model', 'code_config']), | |||||
| replicas: Number(formData.replicas), | replicas: Number(formData.replicas), | ||||
| env, | |||||
| env_variables: envVariables, | |||||
| image: image.value, | image: image.value, | ||||
| model: pick(model, ['id', 'version', 'path', 'showValue']), | |||||
| }); | |||||
| model: changePropertyName( | |||||
| pick(model, ['id', 'name', 'version', 'path', 'identifier', 'owner', 'showValue']), | |||||
| { showValue: 'show_value' }, | |||||
| ), | |||||
| code_config: changePropertyName(pick(codeConfig, ['code_path', 'branch', 'showValue']), { | |||||
| showValue: 'show_value', | |||||
| }), | |||||
| service_id: serviceInfo?.id, | |||||
| }; | |||||
| const params = | const params = | ||||
| operationType === ModelDeploymentOperationType.Create | |||||
| operationType === ServiceOperationType.Create | |||||
| ? object | ? object | ||||
| : { | : { | ||||
| ...pick(modelDeploymentInfo, ['service_id', 'service_ins_id']), | |||||
| update_model: { | |||||
| ...pick(object, ['description', 'env', 'replicas', 'resource', 'image']), | |||||
| }, | |||||
| id: versionInfo?.id, | |||||
| rerun: operationType === ServiceOperationType.Restart ? true : false, | |||||
| deployment_name: versionInfo?.deployment_name, | |||||
| ...object, | |||||
| }; | }; | ||||
| let request = createModelDeploymentReq; | |||||
| if (operationType === ModelDeploymentOperationType.Restart) { | |||||
| request = restartModelDeploymentReq; | |||||
| } else if (operationType === ModelDeploymentOperationType.Update) { | |||||
| request = updateModelDeploymentReq; | |||||
| } | |||||
| const request = | |||||
| operationType === ServiceOperationType.Create | |||||
| ? createServiceVersionReq | |||||
| : updateServiceVersionReq; | |||||
| const [res] = await to(request(params)); | const [res] = await to(request(params)); | ||||
| if (res) { | if (res) { | ||||
| message.success('操作成功'); | message.success('操作成功'); | ||||
| @@ -112,7 +162,7 @@ function ModelDeploymentCreate() { | |||||
| // 提交 | // 提交 | ||||
| const handleSubmit = (values: FormData) => { | const handleSubmit = (values: FormData) => { | ||||
| createModelDeployment(values); | |||||
| createServiceVersion(values); | |||||
| }; | }; | ||||
| // 取消 | // 取消 | ||||
| @@ -120,25 +170,27 @@ function ModelDeploymentCreate() { | |||||
| navigate(-1); | navigate(-1); | ||||
| }; | }; | ||||
| const disabled = operationType !== ModelDeploymentOperationType.Create; | |||||
| const disabled = operationType !== ServiceOperationType.Create; | |||||
| let buttonText = '新建'; | let buttonText = '新建'; | ||||
| if (operationType === ModelDeploymentOperationType.Update) { | |||||
| let title = '新增服务版本'; | |||||
| if (operationType === ServiceOperationType.Update) { | |||||
| title = '更新服务版本'; | |||||
| buttonText = '更新'; | buttonText = '更新'; | ||||
| } else if (operationType === ModelDeploymentOperationType.Restart) { | |||||
| } else if (operationType === ServiceOperationType.Restart) { | |||||
| title = '重启服务版本'; | |||||
| buttonText = '重启'; | buttonText = '重启'; | ||||
| } | } | ||||
| return ( | return ( | ||||
| <div className={styles['model-deployment-create']}> | |||||
| <PageTitle title="创建推理服务"></PageTitle> | |||||
| <div className={styles['model-deployment-create__content']}> | |||||
| <div className={styles['create-service-version']}> | |||||
| <PageTitle title={title}></PageTitle> | |||||
| <div className={styles['create-service-version__content']}> | |||||
| <div> | <div> | ||||
| <Form | <Form | ||||
| name="model-deployment-create" | |||||
| name="create-service-version" | |||||
| labelCol={{ flex: '100px' }} | labelCol={{ flex: '100px' }} | ||||
| labelAlign="left" | labelAlign="left" | ||||
| form={form} | form={form} | ||||
| initialValues={{ upload_type: CommonTabKeys.Public }} | |||||
| onFinish={handleSubmit} | onFinish={handleSubmit} | ||||
| size="large" | size="large" | ||||
| autoComplete="off" | autoComplete="off" | ||||
| @@ -152,7 +204,7 @@ function ModelDeploymentCreate() { | |||||
| <Col span={10}> | <Col span={10}> | ||||
| <Form.Item | <Form.Item | ||||
| label="服务名称" | label="服务名称" | ||||
| name="serviceName" | |||||
| name="service_name" | |||||
| rules={[ | rules={[ | ||||
| { | { | ||||
| required: true, | required: true, | ||||
| @@ -162,8 +214,34 @@ function ModelDeploymentCreate() { | |||||
| > | > | ||||
| <Input | <Input | ||||
| placeholder="请输入服务名称" | placeholder="请输入服务名称" | ||||
| disabled={disabled} | |||||
| maxLength={30} | maxLength={30} | ||||
| disabled | |||||
| showCount | |||||
| allowClear | |||||
| /> | |||||
| </Form.Item> | |||||
| </Col> | |||||
| </Row> | |||||
| <Row gutter={8}> | |||||
| <Col span={10}> | |||||
| <Form.Item | |||||
| label="服务版本" | |||||
| name="version" | |||||
| rules={[ | |||||
| { | |||||
| required: true, | |||||
| message: '请输入服务版本', | |||||
| }, | |||||
| { | |||||
| pattern: /^[a-zA-Z0-9._-]+$/, | |||||
| message: '版本只支持字母、数字、下划线、点、横杠', | |||||
| }, | |||||
| ]} | |||||
| > | |||||
| <Input | |||||
| placeholder="请输入服务版本" | |||||
| maxLength={30} | |||||
| disabled={disabled} | |||||
| showCount | showCount | ||||
| allowClear | allowClear | ||||
| /> | /> | ||||
| @@ -173,18 +251,18 @@ function ModelDeploymentCreate() { | |||||
| <Row gutter={8}> | <Row gutter={8}> | ||||
| <Col span={20}> | <Col span={20}> | ||||
| <Form.Item | <Form.Item | ||||
| label="描 述" | |||||
| label="版本描述" | |||||
| name="description" | name="description" | ||||
| rules={[ | rules={[ | ||||
| { | { | ||||
| required: true, | required: true, | ||||
| message: '请输入描述', | |||||
| message: '请输入版本描述', | |||||
| }, | }, | ||||
| ]} | ]} | ||||
| > | > | ||||
| <Input.TextArea | <Input.TextArea | ||||
| autoSize={{ minRows: 2, maxRows: 6 }} | autoSize={{ minRows: 2, maxRows: 6 }} | ||||
| placeholder="请输入描述,最长128字符" | |||||
| placeholder="请输入版本描述,最长128字符" | |||||
| maxLength={128} | maxLength={128} | ||||
| showCount | showCount | ||||
| allowClear | allowClear | ||||
| @@ -238,6 +316,29 @@ function ModelDeploymentCreate() { | |||||
| placeholder="请选择镜像" | placeholder="请选择镜像" | ||||
| canInput={false} | canInput={false} | ||||
| size="large" | size="large" | ||||
| disabled={disabled} | |||||
| /> | |||||
| </Form.Item> | |||||
| </Col> | |||||
| </Row> | |||||
| <Row gutter={8}> | |||||
| <Col span={10}> | |||||
| <Form.Item | |||||
| label="代码配置" | |||||
| name="code_config" | |||||
| rules={[ | |||||
| { | |||||
| validator: requiredValidator, | |||||
| message: '请选择代码配置', | |||||
| }, | |||||
| ]} | |||||
| required | |||||
| > | |||||
| <CodeSelect | |||||
| placeholder="请选择代码配置" | |||||
| canInput={false} | |||||
| size="large" | |||||
| disabled={disabled} | |||||
| /> | /> | ||||
| </Form.Item> | </Form.Item> | ||||
| </Col> | </Col> | ||||
| @@ -292,12 +393,16 @@ function ModelDeploymentCreate() { | |||||
| <Col span={10}> | <Col span={10}> | ||||
| <Form.Item | <Form.Item | ||||
| label="挂载路径" | label="挂载路径" | ||||
| name="modelPath" | |||||
| name="mount_path" | |||||
| rules={[ | rules={[ | ||||
| { | { | ||||
| required: true, | required: true, | ||||
| message: '请输入模型挂载路径', | message: '请输入模型挂载路径', | ||||
| }, | }, | ||||
| { | |||||
| pattern: /^\/[a-zA-Z0-9._/-]+$/, | |||||
| message: '请输入正确的挂载绝对路径', | |||||
| }, | |||||
| ]} | ]} | ||||
| > | > | ||||
| <Input | <Input | ||||
| @@ -311,15 +416,22 @@ function ModelDeploymentCreate() { | |||||
| </Col> | </Col> | ||||
| </Row> | </Row> | ||||
| <Form.List name="env"> | |||||
| <Form.List name="env_variables"> | |||||
| {(fields, { add, remove }) => ( | {(fields, { add, remove }) => ( | ||||
| <> | <> | ||||
| <Row gutter={8}> | <Row gutter={8}> | ||||
| <Col span={10}> | <Col span={10}> | ||||
| <Form.Item label="环境变量"> | <Form.Item label="环境变量"> | ||||
| <Button type="link" style={{ padding: '0' }} onClick={() => add()}> | |||||
| 添加环境变量 | |||||
| </Button> | |||||
| {fields.length === 0 ? ( | |||||
| <Button | |||||
| type="link" | |||||
| style={{ padding: '0' }} | |||||
| onClick={() => add()} | |||||
| disabled={disabled} | |||||
| > | |||||
| 添加环境变量 | |||||
| </Button> | |||||
| ) : null} | |||||
| </Form.Item> | </Form.Item> | ||||
| </Col> | </Col> | ||||
| </Row> | </Row> | ||||
| @@ -329,9 +441,16 @@ function ModelDeploymentCreate() { | |||||
| {...restField} | {...restField} | ||||
| name={[name, 'key']} | name={[name, 'key']} | ||||
| style={{ flex: 1 }} | style={{ flex: 1 }} | ||||
| rules={[{ required: true, message: '请输入变量名' }]} | |||||
| rules={[ | |||||
| { required: true, message: '请输入变量名' }, | |||||
| { | |||||
| pattern: /^[a-zA-Z_][a-zA-Z0-9_-]*$/, | |||||
| message: | |||||
| '变量名只支持字母、数字、下划线、中横线且开头必须是字母或下划线', | |||||
| }, | |||||
| ]} | |||||
| > | > | ||||
| <Input placeholder="请输入变量名" /> | |||||
| <Input placeholder="请输入变量名" disabled={disabled} /> | |||||
| </Form.Item> | </Form.Item> | ||||
| <span style={{ marginBottom: '24px' }}>=</span> | <span style={{ marginBottom: '24px' }}>=</span> | ||||
| <Form.Item | <Form.Item | ||||
| @@ -340,15 +459,17 @@ function ModelDeploymentCreate() { | |||||
| style={{ flex: 1 }} | style={{ flex: 1 }} | ||||
| rules={[{ required: true, message: '请输入变量值' }]} | rules={[{ required: true, message: '请输入变量值' }]} | ||||
| > | > | ||||
| <Input placeholder="请输入变量值" /> | |||||
| <Input placeholder="请输入变量值" disabled={disabled} /> | |||||
| </Form.Item> | </Form.Item> | ||||
| <Button | <Button | ||||
| type="link" | type="link" | ||||
| style={{ marginBottom: '24px' }} | style={{ marginBottom: '24px' }} | ||||
| icon={<KFIcon type="icon-shanchu" font={16} />} | icon={<KFIcon type="icon-shanchu" font={16} />} | ||||
| disabled={disabled} | |||||
| onClick={() => { | onClick={() => { | ||||
| modalConfirm({ | modalConfirm({ | ||||
| content: '是否确认删除?', | |||||
| title: '删除', | |||||
| content: '是否确认要删除该环境变量?', | |||||
| onOk: () => { | onOk: () => { | ||||
| remove(name); | remove(name); | ||||
| }, | }, | ||||
| @@ -357,10 +478,20 @@ function ModelDeploymentCreate() { | |||||
| ></Button> | ></Button> | ||||
| </Flex> | </Flex> | ||||
| ))} | ))} | ||||
| {fields.length > 0 ? ( | |||||
| <Button | |||||
| type="link" | |||||
| style={{ padding: '0', margin: '-24px 0 24px' }} | |||||
| onClick={() => add()} | |||||
| icon={<PlusOutlined />} | |||||
| disabled={disabled} | |||||
| > | |||||
| 环境变量 | |||||
| </Button> | |||||
| ) : null} | |||||
| </> | </> | ||||
| )} | )} | ||||
| </Form.List> | </Form.List> | ||||
| <Form.Item wrapperCol={{ offset: 0, span: 16 }}> | <Form.Item wrapperCol={{ offset: 0, span: 16 }}> | ||||
| <Button type="primary" htmlType="submit"> | <Button type="primary" htmlType="submit"> | ||||
| {buttonText} | {buttonText} | ||||
| @@ -381,4 +512,4 @@ function ModelDeploymentCreate() { | |||||
| ); | ); | ||||
| } | } | ||||
| export default ModelDeploymentCreate; | |||||
| export default CreateServiceVersion; | |||||
| @@ -1,22 +1,18 @@ | |||||
| /* | /* | ||||
| * @Author: 赵伟 | * @Author: 赵伟 | ||||
| * @Date: 2024-04-16 13:58:08 | * @Date: 2024-04-16 13:58:08 | ||||
| * @Description: 模型部署列表 | |||||
| * @Description: 模型部署服务列表 | |||||
| */ | */ | ||||
| import CommonTableCell from '@/components/CommonTableCell'; | import CommonTableCell from '@/components/CommonTableCell'; | ||||
| import DateTableCell from '@/components/DateTableCell'; | import DateTableCell from '@/components/DateTableCell'; | ||||
| import KFIcon from '@/components/KFIcon'; | import KFIcon from '@/components/KFIcon'; | ||||
| import PageTitle from '@/components/PageTitle'; | import PageTitle from '@/components/PageTitle'; | ||||
| import { ModelDeploymentStatus, modelDeploymentStatusOptions } from '@/enums'; | |||||
| import { serviceTypeOptions } from '@/enums'; | |||||
| import { useCacheState } from '@/hooks/pageCacheState'; | import { useCacheState } from '@/hooks/pageCacheState'; | ||||
| import { | |||||
| deleteModelDeploymentReq, | |||||
| getModelDeploymentListReq, | |||||
| stopModelDeploymentReq, | |||||
| } from '@/services/modelDeployment'; | |||||
| import { deleteServiceReq, getServiceListReq } from '@/services/modelDeployment'; | |||||
| import themes from '@/styles/theme.less'; | import themes from '@/styles/theme.less'; | ||||
| import { to } from '@/utils/promise'; | import { to } from '@/utils/promise'; | ||||
| import { modelDeploymentInfoKey, setSessionStorageItem } from '@/utils/sessionStorage'; | |||||
| import { serviceInfoKey, setSessionStorageItem } from '@/utils/sessionStorage'; | |||||
| import { modalConfirm } from '@/utils/ui'; | import { modalConfirm } from '@/utils/ui'; | ||||
| import { useNavigate } from '@umijs/max'; | import { useNavigate } from '@umijs/max'; | ||||
| import { | import { | ||||
| @@ -31,20 +27,20 @@ import { | |||||
| } from 'antd'; | } from 'antd'; | ||||
| import { type SearchProps } from 'antd/es/input'; | import { type SearchProps } from 'antd/es/input'; | ||||
| import classNames from 'classnames'; | import classNames from 'classnames'; | ||||
| import { pick } from 'lodash'; | |||||
| import { useEffect, useState } from 'react'; | import { useEffect, useState } from 'react'; | ||||
| import ModelDeploymentStatusCell from '../components/ModelDeployStatusCell'; | |||||
| import { ModelDeploymentData, ModelDeploymentOperationType } from '../types'; | |||||
| import { ServiceData, ServiceOperationType } from '../types'; | |||||
| import styles from './index.less'; | import styles from './index.less'; | ||||
| const allServiceTypeOptions = [{ label: '全部', value: '' }, ...serviceTypeOptions]; | |||||
| function ModelDeployment() { | function ModelDeployment() { | ||||
| const navigate = useNavigate(); | const navigate = useNavigate(); | ||||
| const { message } = App.useApp(); | const { message } = App.useApp(); | ||||
| const [cacheState, setCacheState] = useCacheState(); | const [cacheState, setCacheState] = useCacheState(); | ||||
| const [searchStatus, setSearchStatus] = useState(cacheState?.searchStatus ?? ''); | |||||
| const [serviceType, setServiceType] = useState(cacheState?.serviceType ?? ''); | |||||
| const [searchText, setSearchText] = useState(cacheState?.searchText); | const [searchText, setSearchText] = useState(cacheState?.searchText); | ||||
| const [inputText, setInputText] = useState(cacheState?.searchText); | const [inputText, setInputText] = useState(cacheState?.searchText); | ||||
| const [tableData, setTableData] = useState<ModelDeploymentData[]>([]); | |||||
| const [tableData, setTableData] = useState<ServiceData[]>([]); | |||||
| const [total, setTotal] = useState(0); | const [total, setTotal] = useState(0); | ||||
| const [pagination, setPagination] = useState<TablePaginationConfig>( | const [pagination, setPagination] = useState<TablePaginationConfig>( | ||||
| cacheState?.pagination ?? { | cacheState?.pagination ?? { | ||||
| @@ -54,29 +50,28 @@ function ModelDeployment() { | |||||
| ); | ); | ||||
| useEffect(() => { | useEffect(() => { | ||||
| getModelDeploymentList(); | |||||
| }, [pagination, searchText, searchStatus]); | |||||
| getServiceList(); | |||||
| }, [pagination, searchText, serviceType]); | |||||
| // 获取模型部署列表 | |||||
| const getModelDeploymentList = async () => { | |||||
| // 获取模型部署服务列表 | |||||
| const getServiceList = async () => { | |||||
| const params: Record<string, any> = { | const params: Record<string, any> = { | ||||
| page: pagination.current!, | |||||
| page: pagination.current! - 1, | |||||
| size: pagination.pageSize, | size: pagination.pageSize, | ||||
| service_name: searchText, | service_name: searchText, | ||||
| status: searchStatus, | |||||
| service_type: serviceType, | |||||
| }; | }; | ||||
| const [res] = await to(getModelDeploymentListReq(params)); | |||||
| const [res] = await to(getServiceListReq(params)); | |||||
| if (res && res.data) { | if (res && res.data) { | ||||
| const { service_list = [], total = 0 } = res.data; | |||||
| setTableData(service_list); | |||||
| setTotal(total); | |||||
| const { content = [], totalElements = 0 } = res.data; | |||||
| setTableData(content); | |||||
| setTotal(totalElements); | |||||
| } | } | ||||
| }; | }; | ||||
| // 删除模型部署 | // 删除模型部署 | ||||
| const deleteModelDeploy = async (record: ModelDeploymentData) => { | |||||
| const params = pick(record, ['service_id', 'service_ins_id']); | |||||
| const [res] = await to(deleteModelDeploymentReq(params)); | |||||
| const deleteService = async (record: ServiceData) => { | |||||
| const [res] = await to(deleteServiceReq(record.id)); | |||||
| if (res) { | if (res) { | ||||
| message.success('删除成功'); | message.success('删除成功'); | ||||
| // 如果是一页的唯一数据,删除时,请求第一页的数据 | // 如果是一页的唯一数据,删除时,请求第一页的数据 | ||||
| @@ -88,54 +83,31 @@ function ModelDeployment() { | |||||
| current: 1, | current: 1, | ||||
| })); | })); | ||||
| } else { | } else { | ||||
| getModelDeploymentList(); | |||||
| getServiceList(); | |||||
| } | } | ||||
| } | } | ||||
| }; | }; | ||||
| // 停止模型部署 | |||||
| const stopModelDeploy = async (record: ModelDeploymentData) => { | |||||
| const params = pick(record, ['service_id', 'service_ins_id']); | |||||
| const [res] = await to(stopModelDeploymentReq(params)); | |||||
| if (res) { | |||||
| message.success('操作成功'); | |||||
| getModelDeploymentList(); | |||||
| } | |||||
| }; | |||||
| // 搜索 | // 搜索 | ||||
| const onSearch: SearchProps['onSearch'] = (value) => { | const onSearch: SearchProps['onSearch'] = (value) => { | ||||
| setSearchText(value); | setSearchText(value); | ||||
| }; | }; | ||||
| // 处理删除 | // 处理删除 | ||||
| const handleModelDeployDelete = (record: ModelDeploymentData) => { | |||||
| const handleServiceDelete = (record: ServiceData) => { | |||||
| modalConfirm({ | modalConfirm({ | ||||
| title: '删除后,该模型部署将不可恢复', | |||||
| title: '删除后,该服务将不可恢复', | |||||
| content: '是否确认删除?', | content: '是否确认删除?', | ||||
| onOk: () => { | onOk: () => { | ||||
| deleteModelDeploy(record); | |||||
| deleteService(record); | |||||
| }, | }, | ||||
| }); | }); | ||||
| }; | }; | ||||
| // 处理停止 | |||||
| const handleModelDeployStop = async (record: ModelDeploymentData) => { | |||||
| modalConfirm({ | |||||
| content: '是否确认停止?', | |||||
| onOk: () => { | |||||
| stopModelDeploy(record); | |||||
| }, | |||||
| }); | |||||
| }; | |||||
| // 创建、更新、重启模型部署 | |||||
| const createModelDeployment = ( | |||||
| type: ModelDeploymentOperationType, | |||||
| record?: ModelDeploymentData, | |||||
| ) => { | |||||
| // 创建、更新服务 | |||||
| const createService = (type: ServiceOperationType, record?: ServiceData) => { | |||||
| setSessionStorageItem( | setSessionStorageItem( | ||||
| modelDeploymentInfoKey, | |||||
| serviceInfoKey, | |||||
| { | { | ||||
| ...record, | ...record, | ||||
| operationType: type, | operationType: type, | ||||
| @@ -146,23 +118,23 @@ function ModelDeployment() { | |||||
| setCacheState({ | setCacheState({ | ||||
| pagination, | pagination, | ||||
| searchText, | searchText, | ||||
| searchStatus, | |||||
| serviceType: serviceType, | |||||
| }); | }); | ||||
| navigate(`/modelDeployment/create`); | |||||
| navigate(`/modelDeployment/createService`); | |||||
| }; | }; | ||||
| // 查看详情 | // 查看详情 | ||||
| const toDetail = (record: ModelDeploymentData) => { | |||||
| setSessionStorageItem(modelDeploymentInfoKey, record, true); | |||||
| const toDetail = (record: ServiceData) => { | |||||
| setSessionStorageItem(serviceInfoKey, record, true); | |||||
| setCacheState({ | setCacheState({ | ||||
| pagination, | pagination, | ||||
| searchText, | searchText, | ||||
| searchStatus, | |||||
| serviceType: serviceType, | |||||
| }); | }); | ||||
| navigate(`/modelDeployment/info/${record.service_id}`); | |||||
| navigate(`/modelDeployment/serviceInfo/${record.id}`); | |||||
| }; | }; | ||||
| // 分页切换 | // 分页切换 | ||||
| @@ -173,7 +145,7 @@ function ModelDeployment() { | |||||
| // console.log(pagination, filters, sorter, action); | // console.log(pagination, filters, sorter, action); | ||||
| }; | }; | ||||
| const columns: TableProps<ModelDeploymentData>['columns'] = [ | |||||
| const columns: TableProps<ServiceData>['columns'] = [ | |||||
| { | { | ||||
| title: '序号', | title: '序号', | ||||
| dataIndex: 'index', | dataIndex: 'index', | ||||
| @@ -197,23 +169,23 @@ function ModelDeployment() { | |||||
| }, | }, | ||||
| }, | }, | ||||
| { | { | ||||
| title: '模型', | |||||
| dataIndex: ['model', 'show_value'], | |||||
| key: 'model', | |||||
| title: '服务类型', | |||||
| dataIndex: 'service_type_name', | |||||
| key: 'service_type_name', | |||||
| width: '20%', | width: '20%', | ||||
| render: CommonTableCell(), | render: CommonTableCell(), | ||||
| }, | }, | ||||
| { | { | ||||
| title: '状态', | |||||
| dataIndex: 'status', | |||||
| key: 'status', | |||||
| title: '版本数量', | |||||
| dataIndex: 'version_count', | |||||
| key: 'version_count', | |||||
| width: '20%', | width: '20%', | ||||
| render: ModelDeploymentStatusCell, | |||||
| render: CommonTableCell(), | |||||
| }, | }, | ||||
| { | { | ||||
| title: '创建人', | |||||
| dataIndex: 'created_by', | |||||
| key: 'created_by', | |||||
| title: '服务描述', | |||||
| dataIndex: 'description', | |||||
| key: 'description', | |||||
| render: CommonTableCell(), | render: CommonTableCell(), | ||||
| width: '20%', | width: '20%', | ||||
| }, | }, | ||||
| @@ -227,44 +199,28 @@ function ModelDeployment() { | |||||
| { | { | ||||
| title: '操作', | title: '操作', | ||||
| dataIndex: 'operation', | dataIndex: 'operation', | ||||
| width: 250, | |||||
| width: 300, | |||||
| key: 'operation', | key: 'operation', | ||||
| render: (_: any, record: ModelDeploymentData) => ( | |||||
| render: (_: any, record: ServiceData) => ( | |||||
| <div> | <div> | ||||
| <Button | <Button | ||||
| type="link" | type="link" | ||||
| size="small" | size="small" | ||||
| key="edit" | key="edit" | ||||
| icon={<KFIcon type="icon-bianji" />} | icon={<KFIcon type="icon-bianji" />} | ||||
| onClick={() => createModelDeployment(ModelDeploymentOperationType.Update, record)} | |||||
| onClick={() => createService(ServiceOperationType.Update, record)} | |||||
| > | > | ||||
| 更新 | |||||
| 编辑 | |||||
| </Button> | |||||
| <Button | |||||
| type="link" | |||||
| size="small" | |||||
| key="run" | |||||
| icon={<KFIcon type="icon-xiangqing" />} | |||||
| onClick={() => toDetail(record)} | |||||
| > | |||||
| 查看详情 | |||||
| </Button> | </Button> | ||||
| {(record.status === ModelDeploymentStatus.Failed || | |||||
| record.status === ModelDeploymentStatus.Stopped) && ( | |||||
| <Button | |||||
| type="link" | |||||
| size="small" | |||||
| key="run" | |||||
| icon={<KFIcon type="icon-yunhang" />} | |||||
| onClick={() => createModelDeployment(ModelDeploymentOperationType.Restart, record)} | |||||
| > | |||||
| 重启 | |||||
| </Button> | |||||
| )} | |||||
| {(record.status === ModelDeploymentStatus.Running || | |||||
| record.status === ModelDeploymentStatus.Init || | |||||
| record.status === ModelDeploymentStatus.Pending) && ( | |||||
| <Button | |||||
| type="link" | |||||
| size="small" | |||||
| key="stop" | |||||
| icon={<KFIcon type="icon-tingzhi" />} | |||||
| onClick={() => handleModelDeployStop(record)} | |||||
| > | |||||
| 停止 | |||||
| </Button> | |||||
| )} | |||||
| <ConfigProvider | <ConfigProvider | ||||
| theme={{ | theme={{ | ||||
| token: { | token: { | ||||
| @@ -277,7 +233,7 @@ function ModelDeployment() { | |||||
| size="small" | size="small" | ||||
| key="remove" | key="remove" | ||||
| icon={<KFIcon type="icon-shanchu" />} | icon={<KFIcon type="icon-shanchu" />} | ||||
| onClick={() => handleModelDeployDelete(record)} | |||||
| onClick={() => handleServiceDelete(record)} | |||||
| > | > | ||||
| 删除 | 删除 | ||||
| </Button> | </Button> | ||||
| @@ -289,11 +245,11 @@ function ModelDeployment() { | |||||
| return ( | return ( | ||||
| <div className={styles['model-deployment']}> | <div className={styles['model-deployment']}> | ||||
| <PageTitle title="模型列表"></PageTitle> | |||||
| <PageTitle title="服务列表"></PageTitle> | |||||
| <div className={styles['model-deployment__content']}> | <div className={styles['model-deployment__content']}> | ||||
| <div className={styles['model-deployment__content__filter']}> | <div className={styles['model-deployment__content__filter']}> | ||||
| <Input.Search | <Input.Search | ||||
| placeholder="按模型服务名称筛选" | |||||
| placeholder="按服务名称筛选" | |||||
| onSearch={onSearch} | onSearch={onSearch} | ||||
| onChange={(e) => setInputText(e.target.value)} | onChange={(e) => setInputText(e.target.value)} | ||||
| style={{ width: 300 }} | style={{ width: 300 }} | ||||
| @@ -303,27 +259,19 @@ function ModelDeployment() { | |||||
| <Select | <Select | ||||
| style={{ width: 100, marginLeft: '20px' }} | style={{ width: 100, marginLeft: '20px' }} | ||||
| placeholder="请选择" | placeholder="请选择" | ||||
| onChange={(value) => setSearchStatus(value)} | |||||
| options={modelDeploymentStatusOptions} | |||||
| value={searchStatus} | |||||
| onChange={(value) => setServiceType(value ?? '')} | |||||
| options={allServiceTypeOptions} | |||||
| value={serviceType} | |||||
| allowClear | allowClear | ||||
| ></Select> | ></Select> | ||||
| <Button | <Button | ||||
| style={{ marginLeft: '20px' }} | |||||
| style={{ marginLeft: 'auto', marginRight: '20px' }} | |||||
| type="default" | type="default" | ||||
| onClick={() => createModelDeployment(ModelDeploymentOperationType.Create)} | |||||
| onClick={() => createService(ServiceOperationType.Create)} | |||||
| icon={<KFIcon type="icon-xinjian2" />} | icon={<KFIcon type="icon-xinjian2" />} | ||||
| > | > | ||||
| 创建推理服务 | 创建推理服务 | ||||
| </Button> | </Button> | ||||
| <Button | |||||
| style={{ marginRight: 0, marginLeft: 'auto' }} | |||||
| type="default" | |||||
| onClick={getModelDeploymentList} | |||||
| icon={<KFIcon type="icon-shuaxin" />} | |||||
| > | |||||
| 刷新 | |||||
| </Button> | |||||
| </div> | </div> | ||||
| <div | <div | ||||
| className={classNames( | className={classNames( | ||||
| @@ -343,7 +291,7 @@ function ModelDeployment() { | |||||
| showTotal: () => `共${total}条`, | showTotal: () => `共${total}条`, | ||||
| }} | }} | ||||
| onChange={handleTableChange} | onChange={handleTableChange} | ||||
| rowKey="service_id" | |||||
| rowKey="id" | |||||
| /> | /> | ||||
| </div> | </div> | ||||
| </div> | </div> | ||||
| @@ -0,0 +1,24 @@ | |||||
| .service-info { | |||||
| height: 100%; | |||||
| &__content { | |||||
| display: flex; | |||||
| flex-direction: column; | |||||
| height: calc(100% - 60px); | |||||
| margin-top: 10px; | |||||
| padding: 20px 30px 0; | |||||
| background-color: white; | |||||
| border-radius: 10px; | |||||
| &__filter { | |||||
| display: flex; | |||||
| flex: none; | |||||
| align-items: center; | |||||
| justify-content: space-between; | |||||
| } | |||||
| &__table { | |||||
| flex: 1; | |||||
| margin-top: 24px; | |||||
| } | |||||
| } | |||||
| } | |||||
| @@ -0,0 +1,431 @@ | |||||
| /* | |||||
| * @Author: 赵伟 | |||||
| * @Date: 2024-04-16 13:58:08 | |||||
| * @Description: 模型部署列表 | |||||
| */ | |||||
| import BasicInfo from '@/components/BasicInfo'; | |||||
| import CommonTableCell from '@/components/CommonTableCell'; | |||||
| import KFIcon from '@/components/KFIcon'; | |||||
| import PageTitle from '@/components/PageTitle'; | |||||
| import SubAreaTitle from '@/components/SubAreaTitle'; | |||||
| import { ServiceRunStatus, serviceStatusOptions } from '@/enums'; | |||||
| import { useCacheState } from '@/hooks/pageCacheState'; | |||||
| import { useComputingResource } from '@/hooks/resource'; | |||||
| import { | |||||
| deleteServiceVersionReq, | |||||
| getServiceInfoReq, | |||||
| getServiceVersionsReq, | |||||
| stopServiceVersionReq, | |||||
| } from '@/services/modelDeployment'; | |||||
| import themes from '@/styles/theme.less'; | |||||
| import { formatDate } from '@/utils/date'; | |||||
| import { to } from '@/utils/promise'; | |||||
| import { serviceVersionInfoKey, setSessionStorageItem } from '@/utils/sessionStorage'; | |||||
| import { modalConfirm } from '@/utils/ui'; | |||||
| import { useNavigate, useParams } from '@umijs/max'; | |||||
| import { | |||||
| App, | |||||
| Button, | |||||
| ConfigProvider, | |||||
| Input, | |||||
| Select, | |||||
| 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 ServiceRunStatusCell from '../components/ModelDeployStatusCell'; | |||||
| import { ServiceData, ServiceOperationType, ServiceVersionData } from '../types'; | |||||
| import styles from './index.less'; | |||||
| const allServiceStatusOptions = [{ label: '全部', value: '' }, ...serviceStatusOptions]; | |||||
| function ServiceInfo() { | |||||
| const navigate = useNavigate(); | |||||
| const { message } = App.useApp(); | |||||
| const [cacheState, setCacheState] = useCacheState(); | |||||
| const [serviceStatus, setServiceStatus] = useState(cacheState?.serviceStatus ?? ''); | |||||
| const [searchText, setSearchText] = useState(cacheState?.searchText); | |||||
| const [inputText, setInputText] = useState(cacheState?.searchText); | |||||
| const [tableData, setTableData] = useState<ServiceVersionData[]>([]); | |||||
| const [total, setTotal] = useState(0); | |||||
| const [pagination, setPagination] = useState<TablePaginationConfig>( | |||||
| cacheState?.pagination ?? { | |||||
| current: 1, | |||||
| pageSize: 10, | |||||
| }, | |||||
| ); | |||||
| const params = useParams(); | |||||
| const id = params.id; | |||||
| const [serviceInfo, setServiceInfo] = useState<ServiceData | undefined>(undefined); | |||||
| const basicInfo = [ | |||||
| { | |||||
| label: '服务名称', | |||||
| value: serviceInfo?.service_name, | |||||
| }, | |||||
| { | |||||
| label: '服务描述', | |||||
| value: serviceInfo?.description, | |||||
| }, | |||||
| { | |||||
| label: '版本数量', | |||||
| value: serviceInfo?.version_count, | |||||
| }, | |||||
| { | |||||
| label: '创建时间', | |||||
| value: serviceInfo?.create_time, | |||||
| format: formatDate, | |||||
| }, | |||||
| ]; | |||||
| const getResourceDescription = useComputingResource()[2]; | |||||
| useEffect(() => { | |||||
| getServiceInfo(); | |||||
| }, []); | |||||
| useEffect(() => { | |||||
| getServiceVersions(); | |||||
| }, [pagination, searchText, serviceStatus]); | |||||
| // 获取服务详情 | |||||
| const getServiceInfo = async () => { | |||||
| const [res] = await to(getServiceInfoReq(id)); | |||||
| if (res && res.data) { | |||||
| setServiceInfo(res.data); | |||||
| } | |||||
| }; | |||||
| // 获取服务版本列表 | |||||
| const getServiceVersions = async () => { | |||||
| const params: Record<string, any> = { | |||||
| page: pagination.current! - 1, | |||||
| size: pagination.pageSize, | |||||
| version: searchText, | |||||
| run_state: serviceStatus, | |||||
| service_id: id, | |||||
| }; | |||||
| const [res] = await to(getServiceVersionsReq(params)); | |||||
| if (res && res.data) { | |||||
| const { content = [], totalElements = 0 } = res.data; | |||||
| setTableData(content); | |||||
| setTotal(totalElements); | |||||
| } | |||||
| }; | |||||
| // 删除模型部署 | |||||
| const deleteServiceVersion = async (record: ServiceVersionData) => { | |||||
| const [res] = await to(deleteServiceVersionReq(record.id)); | |||||
| if (res) { | |||||
| message.success('删除成功'); | |||||
| // 如果是一页的唯一数据,删除时,请求第一页的数据 | |||||
| // 否则直接刷新这一页的数据 | |||||
| // 避免回到第一页 | |||||
| if (tableData.length > 1) { | |||||
| setPagination((prev) => ({ | |||||
| ...prev, | |||||
| current: 1, | |||||
| })); | |||||
| } else { | |||||
| getServiceInfo(); | |||||
| getServiceVersions(); | |||||
| } | |||||
| } | |||||
| }; | |||||
| // 停止模型部署 | |||||
| const stopServiceVersion = async (record: ServiceVersionData) => { | |||||
| const [res] = await to(stopServiceVersionReq(record.id)); | |||||
| if (res) { | |||||
| message.success('操作成功'); | |||||
| getServiceVersions(); | |||||
| } | |||||
| }; | |||||
| // 搜索 | |||||
| const onSearch: SearchProps['onSearch'] = (value) => { | |||||
| setSearchText(value); | |||||
| }; | |||||
| // 处理删除 | |||||
| const handleServiceVersionDelete = (record: ServiceVersionData) => { | |||||
| modalConfirm({ | |||||
| title: '删除后,该服务版本将不可恢复', | |||||
| content: '是否确认删除?', | |||||
| onOk: () => { | |||||
| deleteServiceVersion(record); | |||||
| }, | |||||
| }); | |||||
| }; | |||||
| // 处理停止 | |||||
| const handleServiceVersionStop = async (record: ServiceVersionData) => { | |||||
| modalConfirm({ | |||||
| title: '停止', | |||||
| content: '是否确认停止该服务?', | |||||
| onOk: () => { | |||||
| stopServiceVersion(record); | |||||
| }, | |||||
| }); | |||||
| }; | |||||
| // 创建、更新、重启模型部署 | |||||
| const createServiceVersion = (type: ServiceOperationType, record?: ServiceVersionData) => { | |||||
| setSessionStorageItem( | |||||
| serviceVersionInfoKey, | |||||
| { | |||||
| ...record, | |||||
| operationType: type, | |||||
| }, | |||||
| true, | |||||
| ); | |||||
| setCacheState({ | |||||
| pagination, | |||||
| searchText, | |||||
| serviceStatus: serviceStatus, | |||||
| }); | |||||
| navigate(`/modelDeployment/addVersion/${id}`); | |||||
| }; | |||||
| // 查看详情 | |||||
| const toDetail = (record: ServiceVersionData) => { | |||||
| setSessionStorageItem(serviceVersionInfoKey, record, true); | |||||
| setCacheState({ | |||||
| pagination, | |||||
| searchText, | |||||
| serviceStatus: serviceStatus, | |||||
| }); | |||||
| navigate(`/modelDeployment/versionInfo/${record.id}`); | |||||
| }; | |||||
| // 分页切换 | |||||
| const handleTableChange: TableProps['onChange'] = (pagination, _filters, _sorter, { action }) => { | |||||
| if (action === 'paginate') { | |||||
| setPagination(pagination); | |||||
| } | |||||
| // console.log(pagination, filters, sorter, action); | |||||
| }; | |||||
| const columns: TableProps<ServiceVersionData>['columns'] = [ | |||||
| { | |||||
| title: '序号', | |||||
| dataIndex: 'index', | |||||
| key: 'index', | |||||
| width: '20%', | |||||
| render(_text, _record, index) { | |||||
| return <span>{(pagination.current! - 1) * pagination.pageSize! + index + 1}</span>; | |||||
| }, | |||||
| }, | |||||
| { | |||||
| title: '服务版本', | |||||
| dataIndex: 'version', | |||||
| key: 'version', | |||||
| width: '20%', | |||||
| render: CommonTableCell(), | |||||
| }, | |||||
| { | |||||
| title: '模型版本', | |||||
| dataIndex: 'model', | |||||
| key: 'model', | |||||
| width: '20%', | |||||
| render: (_text: string, record: ServiceVersionData) => ( | |||||
| <Tooltip | |||||
| title={record.model.show_value} | |||||
| placement="topLeft" | |||||
| overlayStyle={{ maxWidth: '400px' }} | |||||
| > | |||||
| <span>{record.model.show_value}</span> | |||||
| </Tooltip> | |||||
| ), | |||||
| ellipsis: { showTitle: false }, | |||||
| }, | |||||
| { | |||||
| title: '状态', | |||||
| dataIndex: 'run_state', | |||||
| key: 'run_state', | |||||
| width: '20%', | |||||
| render: ServiceRunStatusCell, | |||||
| }, | |||||
| { | |||||
| title: '版本镜像', | |||||
| dataIndex: 'image', | |||||
| key: 'image', | |||||
| width: '20%', | |||||
| render: CommonTableCell(true), | |||||
| ellipsis: { showTitle: false }, | |||||
| }, | |||||
| { | |||||
| title: '副本数量', | |||||
| dataIndex: 'replicas', | |||||
| key: 'replicas', | |||||
| render: CommonTableCell(), | |||||
| width: '20%', | |||||
| }, | |||||
| { | |||||
| title: '资源规格', | |||||
| dataIndex: 'resource', | |||||
| key: 'resource', | |||||
| width: '20%', | |||||
| render: (resource: string) => ( | |||||
| <Tooltip | |||||
| title={getResourceDescription(resource)} | |||||
| placement="topLeft" | |||||
| overlayStyle={{ maxWidth: '400px' }} | |||||
| > | |||||
| <span>{resource ? getResourceDescription(resource) : '--'}</span> | |||||
| </Tooltip> | |||||
| ), | |||||
| ellipsis: { showTitle: false }, | |||||
| }, | |||||
| { | |||||
| title: '操作', | |||||
| dataIndex: 'operation', | |||||
| width: 320, | |||||
| key: 'operation', | |||||
| render: (_: any, record: ServiceVersionData) => ( | |||||
| <div> | |||||
| <Button | |||||
| type="link" | |||||
| size="small" | |||||
| key="info" | |||||
| icon={<KFIcon type="icon-xiangqing" />} | |||||
| onClick={() => toDetail(record)} | |||||
| > | |||||
| 详情 | |||||
| </Button> | |||||
| <Button | |||||
| type="link" | |||||
| size="small" | |||||
| key="edit" | |||||
| icon={<KFIcon type="icon-bianji" />} | |||||
| onClick={() => createServiceVersion(ServiceOperationType.Update, record)} | |||||
| > | |||||
| 更新 | |||||
| </Button> | |||||
| {(record.run_state === ServiceRunStatus.Failed || | |||||
| record.run_state === ServiceRunStatus.Stopped) && ( | |||||
| <Button | |||||
| type="link" | |||||
| size="small" | |||||
| key="run" | |||||
| icon={<KFIcon type="icon-yunhang" />} | |||||
| onClick={() => createServiceVersion(ServiceOperationType.Restart, record)} | |||||
| > | |||||
| 重启 | |||||
| </Button> | |||||
| )} | |||||
| {(record.run_state === ServiceRunStatus.Running || | |||||
| record.run_state === ServiceRunStatus.Init || | |||||
| record.run_state === ServiceRunStatus.Pending) && ( | |||||
| <Button | |||||
| type="link" | |||||
| size="small" | |||||
| key="stop" | |||||
| icon={<KFIcon type="icon-tingzhi" />} | |||||
| onClick={() => handleServiceVersionStop(record)} | |||||
| > | |||||
| 停止 | |||||
| </Button> | |||||
| )} | |||||
| <ConfigProvider | |||||
| theme={{ | |||||
| token: { | |||||
| colorLink: themes['warningColor'], | |||||
| }, | |||||
| }} | |||||
| > | |||||
| <Button | |||||
| type="link" | |||||
| size="small" | |||||
| key="remove" | |||||
| icon={<KFIcon type="icon-shanchu" />} | |||||
| onClick={() => handleServiceVersionDelete(record)} | |||||
| > | |||||
| 删除 | |||||
| </Button> | |||||
| </ConfigProvider> | |||||
| </div> | |||||
| ), | |||||
| }, | |||||
| ]; | |||||
| return ( | |||||
| <div className={styles['service-info']}> | |||||
| <PageTitle title="服务详情"></PageTitle> | |||||
| <div className={styles['service-info__content']}> | |||||
| <SubAreaTitle | |||||
| title="基本信息" | |||||
| image={require('@/assets/img/mirror-basic.png')} | |||||
| style={{ marginBottom: '26px', flex: 'none' }} | |||||
| ></SubAreaTitle> | |||||
| <BasicInfo datas={basicInfo} labelWidth={66} style={{ flex: 'none' }}></BasicInfo> | |||||
| <SubAreaTitle | |||||
| title="服务版本" | |||||
| image={require('@/assets/img/service-version.png')} | |||||
| style={{ margin: '40px 0 26px', flex: 'none' }} | |||||
| ></SubAreaTitle> | |||||
| <div className={styles['service-info__content__filter']}> | |||||
| <Input.Search | |||||
| placeholder="按服务版本筛选" | |||||
| onSearch={onSearch} | |||||
| onChange={(e) => setInputText(e.target.value)} | |||||
| style={{ width: 300 }} | |||||
| value={inputText} | |||||
| allowClear | |||||
| /> | |||||
| <Select | |||||
| style={{ width: 100, marginLeft: '20px' }} | |||||
| placeholder="请选择" | |||||
| onChange={(value) => setServiceStatus(value ?? '')} | |||||
| options={allServiceStatusOptions} | |||||
| value={serviceStatus} | |||||
| allowClear | |||||
| ></Select> | |||||
| <Button | |||||
| style={{ marginRight: '20px', marginLeft: 'auto' }} | |||||
| type="default" | |||||
| onClick={() => createServiceVersion(ServiceOperationType.Create)} | |||||
| icon={<KFIcon type="icon-xinjian2" />} | |||||
| > | |||||
| 新增版本 | |||||
| </Button> | |||||
| <Button | |||||
| style={{ marginRight: 0 }} | |||||
| type="default" | |||||
| onClick={getServiceVersions} | |||||
| icon={<KFIcon type="icon-shuaxin" />} | |||||
| > | |||||
| 刷新 | |||||
| </Button> | |||||
| </div> | |||||
| <div | |||||
| className={classNames('vertical-scroll-table', styles['service-info__content__table'])} | |||||
| > | |||||
| <Table | |||||
| dataSource={tableData} | |||||
| columns={columns} | |||||
| scroll={{ y: 'calc(100% - 55px)' }} | |||||
| pagination={{ | |||||
| ...pagination, | |||||
| total: total, | |||||
| showSizeChanger: true, | |||||
| showQuickJumper: true, | |||||
| showTotal: () => `共${total}条`, | |||||
| }} | |||||
| onChange={handleTableChange} | |||||
| rowKey="id" | |||||
| /> | |||||
| </div> | |||||
| </div> | |||||
| </div> | |||||
| ); | |||||
| } | |||||
| export default ServiceInfo; | |||||
| @@ -1,4 +1,4 @@ | |||||
| .model-deployment-info { | |||||
| .service-version-info { | |||||
| height: 100%; | height: 100%; | ||||
| &__content { | &__content { | ||||
| @@ -1,19 +1,20 @@ | |||||
| /* | /* | ||||
| * @Author: 赵伟 | * @Author: 赵伟 | ||||
| * @Date: 2024-04-16 13:58:08 | * @Date: 2024-04-16 13:58:08 | ||||
| * @Description: 镜像详情 | |||||
| * @Description: 服务版本详情 | |||||
| */ | */ | ||||
| import KFIcon from '@/components/KFIcon'; | import KFIcon from '@/components/KFIcon'; | ||||
| import PageTitle from '@/components/PageTitle'; | import PageTitle from '@/components/PageTitle'; | ||||
| import SubAreaTitle from '@/components/SubAreaTitle'; | import SubAreaTitle from '@/components/SubAreaTitle'; | ||||
| import { useSessionStorage } from '@/hooks/sessionStorage'; | |||||
| import { modelDeploymentInfoKey } from '@/utils/sessionStorage'; | |||||
| import { getServiceVersionInfoReq } from '@/services/modelDeployment'; | |||||
| import { to } from '@/utils/promise'; | |||||
| import { useParams } from '@umijs/max'; | |||||
| import { Tabs, type TabsProps } from 'antd'; | import { Tabs, type TabsProps } from 'antd'; | ||||
| import { useState } from 'react'; | |||||
| import { useEffect, useState } from 'react'; | |||||
| import BasicInfo from '../components/BasicInfo'; | import BasicInfo from '../components/BasicInfo'; | ||||
| import ServerLog from '../components/ServerLog'; | import ServerLog from '../components/ServerLog'; | ||||
| import UserGuide from '../components/UserGuide'; | import UserGuide from '../components/UserGuide'; | ||||
| import { ModelDeploymentData } from '../types'; | |||||
| import { ServiceVersionData } from '../types'; | |||||
| import styles from './index.less'; | import styles from './index.less'; | ||||
| export enum ModelDeploymentTabKey { | export enum ModelDeploymentTabKey { | ||||
| @@ -22,13 +23,23 @@ export enum ModelDeploymentTabKey { | |||||
| Log = 'Log', // 服务日志 | Log = 'Log', // 服务日志 | ||||
| } | } | ||||
| function ModelDeploymentInfo() { | |||||
| function ServiceVersionInfo() { | |||||
| const [activeTab, setActiveTab] = useState<string>(ModelDeploymentTabKey.Predict); | const [activeTab, setActiveTab] = useState<string>(ModelDeploymentTabKey.Predict); | ||||
| const [modelDeployementInfo] = useSessionStorage<ModelDeploymentData | undefined>( | |||||
| modelDeploymentInfoKey, | |||||
| true, | |||||
| undefined, | |||||
| ); | |||||
| const [versionInfo, setVersionInfo] = useState<ServiceVersionData | undefined>(undefined); | |||||
| const params = useParams(); | |||||
| const id = params.id; | |||||
| useEffect(() => { | |||||
| getServiceVersionInfo(); | |||||
| }, []); | |||||
| // 获取服务版本详情 | |||||
| const getServiceVersionInfo = async () => { | |||||
| const [res] = await to(getServiceVersionInfoReq(id)); | |||||
| if (res && res.data) { | |||||
| setVersionInfo(res.data); | |||||
| } | |||||
| }; | |||||
| const tabItems = [ | const tabItems = [ | ||||
| { | { | ||||
| @@ -40,13 +51,13 @@ function ModelDeploymentInfo() { | |||||
| key: ModelDeploymentTabKey.Guide, | key: ModelDeploymentTabKey.Guide, | ||||
| label: '调用指南', | label: '调用指南', | ||||
| icon: <KFIcon type="icon-tiaoyongzhinan" />, | icon: <KFIcon type="icon-tiaoyongzhinan" />, | ||||
| children: <UserGuide info={modelDeployementInfo}></UserGuide>, | |||||
| children: <UserGuide info={versionInfo}></UserGuide>, | |||||
| }, | }, | ||||
| { | { | ||||
| key: ModelDeploymentTabKey.Log, | key: ModelDeploymentTabKey.Log, | ||||
| label: '服务日志', | label: '服务日志', | ||||
| icon: <KFIcon type="icon-fuwurizhi" />, | icon: <KFIcon type="icon-fuwurizhi" />, | ||||
| children: <ServerLog info={modelDeployementInfo}></ServerLog>, | |||||
| children: <ServerLog info={versionInfo}></ServerLog>, | |||||
| }, | }, | ||||
| ]; | ]; | ||||
| @@ -56,16 +67,16 @@ function ModelDeploymentInfo() { | |||||
| }; | }; | ||||
| return ( | return ( | ||||
| <div className={styles['model-deployment-info']}> | |||||
| <PageTitle title="服务详情"></PageTitle> | |||||
| <div className={styles['model-deployment-info__content']}> | |||||
| <div className={styles['service-version-info']}> | |||||
| <PageTitle title="服务版本详情"></PageTitle> | |||||
| <div className={styles['service-version-info__content']}> | |||||
| <SubAreaTitle | <SubAreaTitle | ||||
| title="基本信息" | title="基本信息" | ||||
| image={require('@/assets/img/mirror-basic.png')} | image={require('@/assets/img/mirror-basic.png')} | ||||
| style={{ marginBottom: '26px' }} | style={{ marginBottom: '26px' }} | ||||
| ></SubAreaTitle> | ></SubAreaTitle> | ||||
| <BasicInfo info={modelDeployementInfo} /> | |||||
| <div className={styles['model-deployment-info__content__tabs']}> | |||||
| <BasicInfo info={versionInfo} /> | |||||
| <div className={styles['service-version-info__content__tabs']}> | |||||
| <Tabs activeKey={activeTab} items={tabItems} onChange={hanleTabChange} /> | <Tabs activeKey={activeTab} items={tabItems} onChange={hanleTabChange} /> | ||||
| </div> | </div> | ||||
| </div> | </div> | ||||
| @@ -73,4 +84,4 @@ function ModelDeploymentInfo() { | |||||
| ); | ); | ||||
| } | } | ||||
| export default ModelDeploymentInfo; | |||||
| export default ServiceVersionInfo; | |||||
| @@ -1,12 +1,13 @@ | |||||
| import LabelValue from '@/components/LabelValue'; | import LabelValue from '@/components/LabelValue'; | ||||
| import { useComputingResource } from '@/hooks/resource'; | import { useComputingResource } from '@/hooks/resource'; | ||||
| import { ModelDeploymentData } from '@/pages/ModelDeployment/types'; | |||||
| import { ServiceVersionData } from '@/pages/ModelDeployment/types'; | |||||
| import { formatDate } from '@/utils/date'; | import { formatDate } from '@/utils/date'; | ||||
| import { Link } from '@umijs/max'; | |||||
| import { Col, Row } from 'antd'; | import { Col, Row } from 'antd'; | ||||
| import ModelDeploymentStatusCell from '../ModelDeployStatusCell'; | |||||
| import ServiceRunStatusCell from '../ModelDeployStatusCell'; | |||||
| type BasicInfoProps = { | type BasicInfoProps = { | ||||
| info?: ModelDeploymentData; | |||||
| info?: ServiceVersionData; | |||||
| }; | }; | ||||
| function BasicInfo({ info }: BasicInfoProps) { | function BasicInfo({ info }: BasicInfoProps) { | ||||
| @@ -14,42 +15,75 @@ function BasicInfo({ info }: BasicInfoProps) { | |||||
| // 格式化环境变量 | // 格式化环境变量 | ||||
| const formatEnvText = () => { | const formatEnvText = () => { | ||||
| if (!info?.env) { | |||||
| if (!info?.env_variables) { | |||||
| return '--'; | return '--'; | ||||
| } | } | ||||
| const env = info.env; | |||||
| const env = info.env_variables; | |||||
| return Object.entries(env) | return Object.entries(env) | ||||
| .map(([key, value]) => `${key}: ${value}`) | .map(([key, value]) => `${key}: ${value}`) | ||||
| .join('\n'); | .join('\n'); | ||||
| }; | }; | ||||
| const formatCodeConfig = () => { | |||||
| if (info && info.code_config) { | |||||
| const url = `${info.code_config.code_path}/tree/${info.code_config.branch}`; | |||||
| return ( | |||||
| <a href={url} target="_blank" rel="noreferrer"> | |||||
| {info?.code_config?.show_value} | |||||
| </a> | |||||
| ); | |||||
| } | |||||
| return undefined; | |||||
| }; | |||||
| const formatResource = () => { | |||||
| if (info && info.resource) { | |||||
| return getResourceDescription(info.resource); | |||||
| } | |||||
| return undefined; | |||||
| }; | |||||
| const formatModel = () => { | |||||
| if (info && info.model) { | |||||
| const model = info.model; | |||||
| const path = `/dataset/model/info/${model.id}?version=${model.version}&name=${model.name}&owner=${model.owner}&identifier=${model.identifier}`; | |||||
| return <Link to={path}>{info?.model?.show_value}</Link>; | |||||
| } | |||||
| return undefined; | |||||
| }; | |||||
| return ( | return ( | ||||
| <div> | <div> | ||||
| <Row gutter={40} style={{ marginBottom: '20px' }}> | <Row gutter={40} style={{ marginBottom: '20px' }}> | ||||
| <Col span={10}> | <Col span={10}> | ||||
| <LabelValue label="服务名称:" value={info?.service_name}></LabelValue> | <LabelValue label="服务名称:" value={info?.service_name}></LabelValue> | ||||
| </Col> | </Col> | ||||
| <Col span={10}> | |||||
| <LabelValue label="版本名称:" value={info?.version}></LabelValue> | |||||
| </Col> | |||||
| </Row> | |||||
| <Row gutter={40} style={{ marginBottom: '20px' }}> | |||||
| <Col span={10}> | |||||
| <LabelValue label="代码配置" value={formatCodeConfig()}></LabelValue> | |||||
| </Col> | |||||
| <Col span={10}> | <Col span={10}> | ||||
| <LabelValue label="镜 像:" value={info?.image}></LabelValue> | <LabelValue label="镜 像:" value={info?.image}></LabelValue> | ||||
| </Col> | </Col> | ||||
| </Row> | </Row> | ||||
| <Row gutter={40} style={{ marginBottom: '20px' }}> | <Row gutter={40} style={{ marginBottom: '20px' }}> | ||||
| <Col span={10}> | <Col span={10}> | ||||
| <LabelValue | |||||
| label="状 态:" | |||||
| value={ModelDeploymentStatusCell(info?.status)} | |||||
| ></LabelValue> | |||||
| <LabelValue label="状 态:" value={ServiceRunStatusCell(info?.run_state)}></LabelValue> | |||||
| </Col> | </Col> | ||||
| <Col span={10}> | <Col span={10}> | ||||
| <LabelValue label="模 型:" value={info?.model?.show_value}></LabelValue> | |||||
| <LabelValue label="模 型:" value={formatModel()}></LabelValue> | |||||
| </Col> | </Col> | ||||
| </Row> | </Row> | ||||
| <Row gutter={40} style={{ marginBottom: '20px' }}> | <Row gutter={40} style={{ marginBottom: '20px' }}> | ||||
| <Col span={10}> | <Col span={10}> | ||||
| <LabelValue label="创建人:" value={info?.created_by}></LabelValue> | |||||
| <LabelValue label="资源规格:" value={formatResource()}></LabelValue> | |||||
| </Col> | </Col> | ||||
| <Col span={10}> | <Col span={10}> | ||||
| <LabelValue label="挂载路径:" value={info?.model_path}></LabelValue> | |||||
| <LabelValue label="挂载路径:" value={info?.mount_path}></LabelValue> | |||||
| </Col> | </Col> | ||||
| </Row> | </Row> | ||||
| <Row gutter={40} style={{ marginBottom: '20px' }}> | <Row gutter={40} style={{ marginBottom: '20px' }}> | ||||
| @@ -68,19 +102,11 @@ function BasicInfo({ info }: BasicInfoProps) { | |||||
| <LabelValue label="更新时间:" value={formatDate(info?.update_time)}></LabelValue> | <LabelValue label="更新时间:" value={formatDate(info?.update_time)}></LabelValue> | ||||
| </Col> | </Col> | ||||
| </Row> | </Row> | ||||
| <Row gutter={40} style={{ marginBottom: '20px' }}> | |||||
| <Row gutter={40}> | |||||
| <Col span={10}> | <Col span={10}> | ||||
| <LabelValue label="环境变量:" value={formatEnvText()}></LabelValue> | <LabelValue label="环境变量:" value={formatEnvText()}></LabelValue> | ||||
| </Col> | </Col> | ||||
| <Col span={10}> | <Col span={10}> | ||||
| <LabelValue | |||||
| label="资源规格:" | |||||
| value={info?.resource ? getResourceDescription(info.resource) : '--'} | |||||
| ></LabelValue> | |||||
| </Col> | |||||
| </Row> | |||||
| <Row gutter={40}> | |||||
| <Col span={18}> | |||||
| <LabelValue label="描 述:" value={info?.description}></LabelValue> | <LabelValue label="描 述:" value={info?.description}></LabelValue> | ||||
| </Col> | </Col> | ||||
| </Row> | </Row> | ||||
| @@ -3,42 +3,42 @@ | |||||
| * @Date: 2024-04-18 18:35:41 | * @Date: 2024-04-18 18:35:41 | ||||
| * @Description: 模型部署状态 | * @Description: 模型部署状态 | ||||
| */ | */ | ||||
| import { ModelDeploymentStatus } from '@/enums'; | |||||
| import { ServiceRunStatus } from '@/enums'; | |||||
| import styles from './index.less'; | import styles from './index.less'; | ||||
| export type ModelDeploymentStatusInfo = { | |||||
| export type ServiceRunStatusInfo = { | |||||
| text: string; | text: string; | ||||
| classname: string; | classname: string; | ||||
| }; | }; | ||||
| export const statusInfo: Record<ModelDeploymentStatus, ModelDeploymentStatusInfo> = { | |||||
| [ModelDeploymentStatus.Init]: { | |||||
| export const statusInfo: Record<ServiceRunStatus, ServiceRunStatusInfo> = { | |||||
| [ServiceRunStatus.Init]: { | |||||
| text: '启动中', | text: '启动中', | ||||
| classname: styles['model-deployment-status-cell'], | classname: styles['model-deployment-status-cell'], | ||||
| }, | }, | ||||
| [ModelDeploymentStatus.Running]: { | |||||
| [ServiceRunStatus.Running]: { | |||||
| classname: styles['model-deployment-status-cell--running'], | classname: styles['model-deployment-status-cell--running'], | ||||
| text: '运行中', | text: '运行中', | ||||
| }, | }, | ||||
| [ModelDeploymentStatus.Stopped]: { | |||||
| [ServiceRunStatus.Stopped]: { | |||||
| classname: styles['model-deployment-status-cell--stopped'], | classname: styles['model-deployment-status-cell--stopped'], | ||||
| text: '已停止', | text: '已停止', | ||||
| }, | }, | ||||
| [ModelDeploymentStatus.Failed]: { | |||||
| [ServiceRunStatus.Failed]: { | |||||
| classname: styles['model-deployment-status-cell--error'], | classname: styles['model-deployment-status-cell--error'], | ||||
| text: '失败', | text: '失败', | ||||
| }, | }, | ||||
| [ModelDeploymentStatus.Pending]: { | |||||
| [ServiceRunStatus.Pending]: { | |||||
| classname: styles['model-deployment-status-cell--pending'], | classname: styles['model-deployment-status-cell--pending'], | ||||
| text: '挂起中', | text: '挂起中', | ||||
| }, | }, | ||||
| }; | }; | ||||
| function ModelDeploymentStatusCell(status?: ModelDeploymentStatus | null) { | |||||
| function ServiceRunStatusCell(status?: ServiceRunStatus | null) { | |||||
| if (status === null || status === undefined || !statusInfo[status]) { | if (status === null || status === undefined || !statusInfo[status]) { | ||||
| return <span>--</span>; | return <span>--</span>; | ||||
| } | } | ||||
| return <span className={statusInfo[status].classname}>{statusInfo[status].text}</span>; | return <span className={statusInfo[status].classname}>{statusInfo[status].text}</span>; | ||||
| } | } | ||||
| export default ModelDeploymentStatusCell; | |||||
| export default ServiceRunStatusCell; | |||||
| @@ -1,10 +1,9 @@ | |||||
| import { ModelDeploymentData } from '@/pages/ModelDeployment/types'; | |||||
| import { getModelDeploymentLogReq } from '@/services/modelDeployment'; | |||||
| import { ServiceVersionData } from '@/pages/ModelDeployment/types'; | |||||
| import { getServiceVersionLogReq } from '@/services/modelDeployment'; | |||||
| import { to } from '@/utils/promise'; | import { to } from '@/utils/promise'; | ||||
| import { DoubleRightOutlined } from '@ant-design/icons'; | import { DoubleRightOutlined } from '@ant-design/icons'; | ||||
| import { Button, DatePicker, type TimeRangePickerProps } from 'antd'; | import { Button, DatePicker, type TimeRangePickerProps } from 'antd'; | ||||
| import dayjs from 'dayjs'; | import dayjs from 'dayjs'; | ||||
| import { pick } from 'lodash'; | |||||
| import { useEffect, useState } from 'react'; | import { useEffect, useState } from 'react'; | ||||
| import styles from './index.less'; | import styles from './index.less'; | ||||
| const { RangePicker } = DatePicker; | const { RangePicker } = DatePicker; | ||||
| @@ -28,7 +27,7 @@ type LogData = { | |||||
| }; | }; | ||||
| type ServerLogProps = { | type ServerLogProps = { | ||||
| info?: ModelDeploymentData; | |||||
| info?: ServiceVersionData; | |||||
| }; | }; | ||||
| function ServerLog({ info }: ServerLogProps) { | function ServerLog({ info }: ServerLogProps) { | ||||
| @@ -64,9 +63,9 @@ function ServerLog({ info }: ServerLogProps) { | |||||
| const params = { | const params = { | ||||
| start_time: logTime[0], | start_time: logTime[0], | ||||
| end_time: logTime[1], | end_time: logTime[1], | ||||
| ...pick(info, ['service_id', 'service_ins_id']), | |||||
| id: info.id, | |||||
| }; | }; | ||||
| const [res] = await to(getModelDeploymentLogReq(params)); | |||||
| const [res] = await to(getServiceVersionLogReq(params)); | |||||
| if (res && res.data) { | if (res && res.data) { | ||||
| setLogData((prev) => [...prev, res.data]); | setLogData((prev) => [...prev, res.data]); | ||||
| setHasMore(!!res.data.log_content); | setHasMore(!!res.data.log_content); | ||||