From ea3c20e3bb023e48c136b8a62106876a9f717543 Mon Sep 17 00:00:00 2001 From: chenzhihang <709011834@qq.com> Date: Tue, 29 Oct 2024 13:40:49 +0800 Subject: [PATCH 01/59] =?UTF-8?q?=E6=B7=BB=E5=8A=A0=E8=87=AA=E5=8A=A8?= =?UTF-8?q?=E6=9C=BA=E5=99=A8=E5=AD=A6=E4=B9=A0=E5=8A=9F=E8=83=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../controller/autoML/AutoMLController.java | 35 +++++++++++++++++++ .../codeConfig/CodeConfigController.java | 1 - .../com/ruoyi/platform/domain/AutoMl.java | 31 ++++++++++++++++ .../com/ruoyi/platform/mapper/AutoMLDao.java | 13 +++++++ .../ruoyi/platform/service/AutoMLService.java | 9 +++++ .../service/impl/AutoMLServiceImpl.java | 25 +++++++++++++ .../managementPlatform/AutoMLDaoMapper.xml | 23 ++++++++++++ 7 files changed, 136 insertions(+), 1 deletion(-) create mode 100644 ruoyi-modules/management-platform/src/main/java/com/ruoyi/platform/controller/autoML/AutoMLController.java create mode 100644 ruoyi-modules/management-platform/src/main/java/com/ruoyi/platform/domain/AutoMl.java create mode 100644 ruoyi-modules/management-platform/src/main/java/com/ruoyi/platform/mapper/AutoMLDao.java create mode 100644 ruoyi-modules/management-platform/src/main/java/com/ruoyi/platform/service/AutoMLService.java create mode 100644 ruoyi-modules/management-platform/src/main/java/com/ruoyi/platform/service/impl/AutoMLServiceImpl.java create mode 100644 ruoyi-modules/management-platform/src/main/resources/mapper/managementPlatform/AutoMLDaoMapper.xml diff --git a/ruoyi-modules/management-platform/src/main/java/com/ruoyi/platform/controller/autoML/AutoMLController.java b/ruoyi-modules/management-platform/src/main/java/com/ruoyi/platform/controller/autoML/AutoMLController.java new file mode 100644 index 00000000..48e666fd --- /dev/null +++ b/ruoyi-modules/management-platform/src/main/java/com/ruoyi/platform/controller/autoML/AutoMLController.java @@ -0,0 +1,35 @@ +package com.ruoyi.platform.controller.autoML; + +import com.ruoyi.common.core.web.controller.BaseController; +import com.ruoyi.common.core.web.domain.GenericsAjaxResult; +import com.ruoyi.platform.domain.AutoMl; +import com.ruoyi.platform.service.AutoMLService; +import io.swagger.annotations.Api; +import io.swagger.annotations.ApiOperation; +import org.springframework.data.domain.Page; +import org.springframework.data.domain.PageRequest; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RequestParam; +import org.springframework.web.bind.annotation.RestController; + +import javax.annotation.Resource; + +@RestController +@RequestMapping("autoML") +@Api("自动机器学习") +public class AutoMLController extends BaseController { + + @Resource + private AutoMLService autoMLService; + + @GetMapping + @ApiOperation("分页查询") + public GenericsAjaxResult> queryByPage(@RequestParam("page") int page, + @RequestParam("size") int size, + @RequestParam(value = "mlName", required = false) String mlName) { + PageRequest pageRequest = PageRequest.of(page, size); + return genericsSuccess(this.autoMLService.queryByPage(mlName, pageRequest)); + + } +} diff --git a/ruoyi-modules/management-platform/src/main/java/com/ruoyi/platform/controller/codeConfig/CodeConfigController.java b/ruoyi-modules/management-platform/src/main/java/com/ruoyi/platform/controller/codeConfig/CodeConfigController.java index 3aa6c37f..b445a232 100644 --- a/ruoyi-modules/management-platform/src/main/java/com/ruoyi/platform/controller/codeConfig/CodeConfigController.java +++ b/ruoyi-modules/management-platform/src/main/java/com/ruoyi/platform/controller/codeConfig/CodeConfigController.java @@ -7,7 +7,6 @@ import com.ruoyi.platform.service.CodeConfigService; import io.swagger.annotations.Api; import org.springframework.data.domain.Page; import org.springframework.data.domain.PageRequest; -import org.springframework.http.ResponseEntity; import org.springframework.web.bind.annotation.*; import javax.annotation.Resource; diff --git a/ruoyi-modules/management-platform/src/main/java/com/ruoyi/platform/domain/AutoMl.java b/ruoyi-modules/management-platform/src/main/java/com/ruoyi/platform/domain/AutoMl.java new file mode 100644 index 00000000..409e5726 --- /dev/null +++ b/ruoyi-modules/management-platform/src/main/java/com/ruoyi/platform/domain/AutoMl.java @@ -0,0 +1,31 @@ +package com.ruoyi.platform.domain; + +import com.fasterxml.jackson.databind.PropertyNamingStrategy; +import com.fasterxml.jackson.databind.annotation.JsonNaming; +import lombok.Data; + +import java.util.Date; + +@Data +@JsonNaming(PropertyNamingStrategy.SnakeCaseStrategy.class) +public class AutoMl { + private Long id; + + private String mlName; + + private String mlDescription; + + private Integer state; + + private String runState; + + private Double progress; + + private String createBy; + + private Date createTime; + + private String updateBy; + + private Date updateTime; +} diff --git a/ruoyi-modules/management-platform/src/main/java/com/ruoyi/platform/mapper/AutoMLDao.java b/ruoyi-modules/management-platform/src/main/java/com/ruoyi/platform/mapper/AutoMLDao.java new file mode 100644 index 00000000..3334999b --- /dev/null +++ b/ruoyi-modules/management-platform/src/main/java/com/ruoyi/platform/mapper/AutoMLDao.java @@ -0,0 +1,13 @@ +package com.ruoyi.platform.mapper; + +import com.ruoyi.platform.domain.AutoMl; +import org.apache.ibatis.annotations.Param; +import org.springframework.data.domain.Pageable; + +import java.util.List; + +public interface AutoMLDao { + long count(@Param("mlName") String mlName); + + List queryByPage(@Param("mlName") String mlName, @Param("pageable") Pageable pageable); +} diff --git a/ruoyi-modules/management-platform/src/main/java/com/ruoyi/platform/service/AutoMLService.java b/ruoyi-modules/management-platform/src/main/java/com/ruoyi/platform/service/AutoMLService.java new file mode 100644 index 00000000..37de7921 --- /dev/null +++ b/ruoyi-modules/management-platform/src/main/java/com/ruoyi/platform/service/AutoMLService.java @@ -0,0 +1,9 @@ +package com.ruoyi.platform.service; + +import com.ruoyi.platform.domain.AutoMl; +import org.springframework.data.domain.Page; +import org.springframework.data.domain.PageRequest; + +public interface AutoMLService { + Page queryByPage(String mlName, PageRequest pageRequest); +} diff --git a/ruoyi-modules/management-platform/src/main/java/com/ruoyi/platform/service/impl/AutoMLServiceImpl.java b/ruoyi-modules/management-platform/src/main/java/com/ruoyi/platform/service/impl/AutoMLServiceImpl.java new file mode 100644 index 00000000..4fe834ff --- /dev/null +++ b/ruoyi-modules/management-platform/src/main/java/com/ruoyi/platform/service/impl/AutoMLServiceImpl.java @@ -0,0 +1,25 @@ +package com.ruoyi.platform.service.impl; + +import com.ruoyi.platform.domain.AutoMl; +import com.ruoyi.platform.mapper.AutoMLDao; +import com.ruoyi.platform.service.AutoMLService; +import org.springframework.data.domain.Page; +import org.springframework.data.domain.PageImpl; +import org.springframework.data.domain.PageRequest; +import org.springframework.stereotype.Service; + +import javax.annotation.Resource; +import java.util.List; + +@Service("autoMLService") +public class AutoMLServiceImpl implements AutoMLService { + @Resource + private AutoMLDao autoMLDao; + + @Override + public Page queryByPage(String mlName, PageRequest pageRequest) { + long total = autoMLDao.count(mlName); + List autoMls = autoMLDao.queryByPage(mlName, pageRequest); + return new PageImpl<>(autoMls, pageRequest, total); + } +} diff --git a/ruoyi-modules/management-platform/src/main/resources/mapper/managementPlatform/AutoMLDaoMapper.xml b/ruoyi-modules/management-platform/src/main/resources/mapper/managementPlatform/AutoMLDaoMapper.xml new file mode 100644 index 00000000..9d39b807 --- /dev/null +++ b/ruoyi-modules/management-platform/src/main/resources/mapper/managementPlatform/AutoMLDaoMapper.xml @@ -0,0 +1,23 @@ + + + + + + + + + + + state = 1 + + and ml_name like concat('%', #{mlName}, '%') + + + + \ No newline at end of file From abd5c094740817fbaecd0e8aafd2fe1fcbec9837 Mon Sep 17 00:00:00 2001 From: chenzhihang <709011834@qq.com> Date: Wed, 30 Oct 2024 10:15:29 +0800 Subject: [PATCH 02/59] =?UTF-8?q?=E8=87=AA=E5=8A=A8=E6=9C=BA=E5=99=A8?= =?UTF-8?q?=E5=AD=A6=E4=B9=A0=E6=8E=A5=E5=8F=A3=E5=BC=80=E5=8F=91?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../controller/autoML/AutoMLController.java | 23 ++++++++++--- .../com/ruoyi/platform/mapper/AutoMLDao.java | 6 ++++ .../ruoyi/platform/service/AutoMLService.java | 6 ++++ .../service/impl/AutoMLServiceImpl.java | 34 +++++++++++++++++++ .../src/main/resources/bootstrap.yml | 10 +++--- .../managementPlatform/AutoMLDaoMapper.xml | 30 ++++++++++++++++ 6 files changed, 100 insertions(+), 9 deletions(-) diff --git a/ruoyi-modules/management-platform/src/main/java/com/ruoyi/platform/controller/autoML/AutoMLController.java b/ruoyi-modules/management-platform/src/main/java/com/ruoyi/platform/controller/autoML/AutoMLController.java index 48e666fd..8b0c9a74 100644 --- a/ruoyi-modules/management-platform/src/main/java/com/ruoyi/platform/controller/autoML/AutoMLController.java +++ b/ruoyi-modules/management-platform/src/main/java/com/ruoyi/platform/controller/autoML/AutoMLController.java @@ -8,10 +8,7 @@ import io.swagger.annotations.Api; import io.swagger.annotations.ApiOperation; import org.springframework.data.domain.Page; import org.springframework.data.domain.PageRequest; -import org.springframework.web.bind.annotation.GetMapping; -import org.springframework.web.bind.annotation.RequestMapping; -import org.springframework.web.bind.annotation.RequestParam; -import org.springframework.web.bind.annotation.RestController; +import org.springframework.web.bind.annotation.*; import javax.annotation.Resource; @@ -30,6 +27,24 @@ public class AutoMLController extends BaseController { @RequestParam(value = "mlName", required = false) String mlName) { PageRequest pageRequest = PageRequest.of(page, size); return genericsSuccess(this.autoMLService.queryByPage(mlName, pageRequest)); + } + + @PostMapping + @ApiOperation("新增自动机器学习") + public GenericsAjaxResult addAutoMl(@RequestBody AutoMl autoMl) { + return genericsSuccess(this.autoMLService.save(autoMl)); + } + @PutMapping + @ApiOperation("编辑自动机器学习") + public GenericsAjaxResult editAutoMl(@RequestBody AutoMl autoMl){ + return genericsSuccess(this.autoMLService.edit(autoMl)); } + + @DeleteMapping("{id}") + @ApiOperation("删除自动机器学习") + public GenericsAjaxResult deleteAutoMl(@PathVariable("id") Long id){ + return genericsSuccess(this.autoMLService.delete(id)); + } + } diff --git a/ruoyi-modules/management-platform/src/main/java/com/ruoyi/platform/mapper/AutoMLDao.java b/ruoyi-modules/management-platform/src/main/java/com/ruoyi/platform/mapper/AutoMLDao.java index 3334999b..ed68d8a9 100644 --- a/ruoyi-modules/management-platform/src/main/java/com/ruoyi/platform/mapper/AutoMLDao.java +++ b/ruoyi-modules/management-platform/src/main/java/com/ruoyi/platform/mapper/AutoMLDao.java @@ -10,4 +10,10 @@ public interface AutoMLDao { long count(@Param("mlName") String mlName); List queryByPage(@Param("mlName") String mlName, @Param("pageable") Pageable pageable); + + AutoMl getAutoById(@Param("id") Long id); + + int save(@Param("autoMl") AutoMl autoMl); + + int edit(@Param("autoMl") AutoMl autoMl); } diff --git a/ruoyi-modules/management-platform/src/main/java/com/ruoyi/platform/service/AutoMLService.java b/ruoyi-modules/management-platform/src/main/java/com/ruoyi/platform/service/AutoMLService.java index 37de7921..ce06b9c4 100644 --- a/ruoyi-modules/management-platform/src/main/java/com/ruoyi/platform/service/AutoMLService.java +++ b/ruoyi-modules/management-platform/src/main/java/com/ruoyi/platform/service/AutoMLService.java @@ -6,4 +6,10 @@ import org.springframework.data.domain.PageRequest; public interface AutoMLService { Page queryByPage(String mlName, PageRequest pageRequest); + + AutoMl save(AutoMl autoMl); + + String edit(AutoMl autoMl); + + String delete(Long id); } diff --git a/ruoyi-modules/management-platform/src/main/java/com/ruoyi/platform/service/impl/AutoMLServiceImpl.java b/ruoyi-modules/management-platform/src/main/java/com/ruoyi/platform/service/impl/AutoMLServiceImpl.java index 4fe834ff..4504c4f7 100644 --- a/ruoyi-modules/management-platform/src/main/java/com/ruoyi/platform/service/impl/AutoMLServiceImpl.java +++ b/ruoyi-modules/management-platform/src/main/java/com/ruoyi/platform/service/impl/AutoMLServiceImpl.java @@ -1,8 +1,12 @@ package com.ruoyi.platform.service.impl; +import com.ruoyi.common.security.utils.SecurityUtils; +import com.ruoyi.platform.constant.Constant; import com.ruoyi.platform.domain.AutoMl; import com.ruoyi.platform.mapper.AutoMLDao; import com.ruoyi.platform.service.AutoMLService; +import com.ruoyi.system.api.model.LoginUser; +import org.apache.commons.lang3.StringUtils; import org.springframework.data.domain.Page; import org.springframework.data.domain.PageImpl; import org.springframework.data.domain.PageRequest; @@ -22,4 +26,34 @@ public class AutoMLServiceImpl implements AutoMLService { List autoMls = autoMLDao.queryByPage(mlName, pageRequest); return new PageImpl<>(autoMls, pageRequest, total); } + + @Override + public AutoMl save(AutoMl autoMl) { + autoMLDao.save(autoMl); + return autoMl; + } + + @Override + public String edit(AutoMl autoMl) { + autoMLDao.edit(autoMl); + return "修改成功"; + } + + @Override + public String delete(Long id) { + AutoMl autoMl = autoMLDao.getAutoById(id); + if (autoMl == null) { + throw new RuntimeException("服务不存在"); + } + + LoginUser loginUser = SecurityUtils.getLoginUser(); + String username = loginUser.getUsername(); + String createBy = autoMl.getCreateBy(); + if (!(StringUtils.equals(username, "admin") || StringUtils.equals(username, createBy))) { + throw new RuntimeException("无权限删除该服务"); + } + + autoMl.setState(Constant.State_invalid); + return autoMLDao.edit(autoMl) > 0 ? "删除成功" : "删除失败"; + } } diff --git a/ruoyi-modules/management-platform/src/main/resources/bootstrap.yml b/ruoyi-modules/management-platform/src/main/resources/bootstrap.yml index 22eaf828..29dd7cd4 100644 --- a/ruoyi-modules/management-platform/src/main/resources/bootstrap.yml +++ b/ruoyi-modules/management-platform/src/main/resources/bootstrap.yml @@ -14,16 +14,16 @@ spring: nacos: discovery: # 服务注册地址 - server-addr: nacos-ci4s.argo.svc:8848 + server-addr: 172.20.32.181:18848 username: nacos - password: h1n2x3j4y5@ + password: nacos retry: enabled: true +# namespace: 6caf5d79-c4ce-4e3b-a357-141b74e52a01 config: - username: nacos - password: h1n2x3j4y5@ +# namespace: 6caf5d79-c4ce-4e3b-a357-141b74e52a01 # 配置中心地址 - server-addr: nacos-ci4s.argo.svc:8848 + server-addr: 172.20.32.181:18848 # 配置文件格式 file-extension: yml # 共享配置 diff --git a/ruoyi-modules/management-platform/src/main/resources/mapper/managementPlatform/AutoMLDaoMapper.xml b/ruoyi-modules/management-platform/src/main/resources/mapper/managementPlatform/AutoMLDaoMapper.xml index 9d39b807..788c2dec 100644 --- a/ruoyi-modules/management-platform/src/main/resources/mapper/managementPlatform/AutoMLDaoMapper.xml +++ b/ruoyi-modules/management-platform/src/main/resources/mapper/managementPlatform/AutoMLDaoMapper.xml @@ -1,6 +1,32 @@ + + insert into auto_ml(ml_name, ml_description) + values (#{autoMl.mlName}, #{autoMl.mlDescription}) + + + + update auto_ml + + + ml_name = #{autoMl.mlName}, + + + ml_description = #{autoMl.mlDescription}, + + + run_state = #{autoMl.runState}, + + + progress = #{autoMl.progress}, + + + state = #{autoMl.state}, + + + where id = #{autoMl.id} + + + state = 1 From 27ff585924b9c74a3138c9d7468921d44409134f Mon Sep 17 00:00:00 2001 From: cp3hnu Date: Sat, 2 Nov 2024 09:24:00 +0800 Subject: [PATCH 03/59] =?UTF-8?q?feat:=20=E5=AE=8C=E6=88=90=E8=87=AA?= =?UTF-8?q?=E5=8A=A8=E5=AD=A6=E4=B9=A0UI?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .gitignore | 5 +- package-lock.json | 45 +++ package.json | 5 + react-ui/config/routes.ts | 21 ++ react-ui/src/app.tsx | 1 + react-ui/src/assets/img/editor-parameter.png | Bin 1264 -> 1848 bytes react-ui/src/assets/img/mirror-basic.png | Bin 468 -> 1470 bytes react-ui/src/assets/img/model-deployment.png | Bin 1595 -> 1689 bytes .../src/assets/img/search-config-icon.png | Bin 0 -> 1567 bytes react-ui/src/assets/img/trial-config-icon.png | Bin 0 -> 1692 bytes react-ui/src/components/BasicInfo/index.tsx | 18 +- react-ui/src/components/KFIcon/index.tsx | 4 +- react-ui/src/iconfont/iconfont.js | 2 +- react-ui/src/pages/AutoML/Create/index.less | 47 +++ react-ui/src/pages/AutoML/Create/index.tsx | 220 +++++++++++++ react-ui/src/pages/AutoML/Info/index.less | 40 +++ react-ui/src/pages/AutoML/Info/index.tsx | 61 ++++ react-ui/src/pages/AutoML/List/index.less | 20 ++ react-ui/src/pages/AutoML/List/index.tsx | 311 ++++++++++++++++++ .../AutoML/components/AutoMLBasic/index.less | 7 + .../AutoML/components/AutoMLBasic/index.tsx | 81 +++++ .../AutoML/components/AutoMLTable/index.less | 15 + .../AutoML/components/AutoMLTable/index.tsx | 305 +++++++++++++++++ .../AutoML/components/ConfigInfo/index.less | 42 +++ .../AutoML/components/ConfigInfo/index.tsx | 47 +++ .../AutoML/components/ConfigTitle/index.less | 38 +++ .../AutoML/components/ConfigTitle/index.tsx | 22 ++ .../AutoML/components/CopyingText/index.less | 18 + .../AutoML/components/CopyingText/index.tsx | 33 ++ .../components/CreateForm/BasicConfig.tsx | 85 +++++ .../components/CreateForm/ExecuteConfig.tsx | 134 ++++++++ .../CreateForm/ExecuteConfigDLC.tsx | 258 +++++++++++++++ .../components/CreateForm/ExecuteConfigMC.tsx | 82 +++++ .../components/CreateForm/SearchConfig.tsx | 119 +++++++ .../components/CreateForm/TrialConfig.tsx | 193 +++++++++++ .../AutoML/components/CreateForm/index.less | 130 ++++++++ .../components/ExecuteScheduleCell/index.less | 30 ++ .../components/ExecuteScheduleCell/index.tsx | 25 ++ .../components/RunStatusCell/index.less | 19 ++ .../AutoML/components/RunStatusCell/index.tsx | 44 +++ .../AutoML/components/StatusChart/index.less | 8 + .../AutoML/components/StatusChart/index.tsx | 217 ++++++++++++ react-ui/src/pages/AutoML/types.ts | 6 + react-ui/src/utils/clipboard.js | 12 + 44 files changed, 2763 insertions(+), 7 deletions(-) create mode 100644 package-lock.json create mode 100644 package.json create mode 100644 react-ui/src/assets/img/search-config-icon.png create mode 100644 react-ui/src/assets/img/trial-config-icon.png create mode 100644 react-ui/src/pages/AutoML/Create/index.less create mode 100644 react-ui/src/pages/AutoML/Create/index.tsx create mode 100644 react-ui/src/pages/AutoML/Info/index.less create mode 100644 react-ui/src/pages/AutoML/Info/index.tsx create mode 100644 react-ui/src/pages/AutoML/List/index.less create mode 100644 react-ui/src/pages/AutoML/List/index.tsx create mode 100644 react-ui/src/pages/AutoML/components/AutoMLBasic/index.less create mode 100644 react-ui/src/pages/AutoML/components/AutoMLBasic/index.tsx create mode 100644 react-ui/src/pages/AutoML/components/AutoMLTable/index.less create mode 100644 react-ui/src/pages/AutoML/components/AutoMLTable/index.tsx create mode 100644 react-ui/src/pages/AutoML/components/ConfigInfo/index.less create mode 100644 react-ui/src/pages/AutoML/components/ConfigInfo/index.tsx create mode 100644 react-ui/src/pages/AutoML/components/ConfigTitle/index.less create mode 100644 react-ui/src/pages/AutoML/components/ConfigTitle/index.tsx create mode 100644 react-ui/src/pages/AutoML/components/CopyingText/index.less create mode 100644 react-ui/src/pages/AutoML/components/CopyingText/index.tsx create mode 100644 react-ui/src/pages/AutoML/components/CreateForm/BasicConfig.tsx create mode 100644 react-ui/src/pages/AutoML/components/CreateForm/ExecuteConfig.tsx create mode 100644 react-ui/src/pages/AutoML/components/CreateForm/ExecuteConfigDLC.tsx create mode 100644 react-ui/src/pages/AutoML/components/CreateForm/ExecuteConfigMC.tsx create mode 100644 react-ui/src/pages/AutoML/components/CreateForm/SearchConfig.tsx create mode 100644 react-ui/src/pages/AutoML/components/CreateForm/TrialConfig.tsx create mode 100644 react-ui/src/pages/AutoML/components/CreateForm/index.less create mode 100644 react-ui/src/pages/AutoML/components/ExecuteScheduleCell/index.less create mode 100644 react-ui/src/pages/AutoML/components/ExecuteScheduleCell/index.tsx create mode 100644 react-ui/src/pages/AutoML/components/RunStatusCell/index.less create mode 100644 react-ui/src/pages/AutoML/components/RunStatusCell/index.tsx create mode 100644 react-ui/src/pages/AutoML/components/StatusChart/index.less create mode 100644 react-ui/src/pages/AutoML/components/StatusChart/index.tsx create mode 100644 react-ui/src/pages/AutoML/types.ts create mode 100644 react-ui/src/utils/clipboard.js diff --git a/.gitignore b/.gitignore index 0203dc65..04212571 100644 --- a/.gitignore +++ b/.gitignore @@ -50,5 +50,6 @@ Thumbs.db mvnw.cmd mvnw -# Files or folders need to be retained -# ... \ No newline at end of file +# web +**/node_modules + diff --git a/package-lock.json b/package-lock.json new file mode 100644 index 00000000..d579dd5a --- /dev/null +++ b/package-lock.json @@ -0,0 +1,45 @@ +{ + "name": "ci4sManagement-cloud", + "lockfileVersion": 3, + "requires": true, + "packages": { + "": { + "dependencies": { + "clipboard": "~2.0.11" + } + }, + "node_modules/clipboard": { + "version": "2.0.11", + "resolved": "https://registry.npmjs.org/clipboard/-/clipboard-2.0.11.tgz", + "integrity": "sha512-C+0bbOqkezLIsmWSvlsXS0Q0bmkugu7jcfMIACB+RDEntIzQIkdr148we28AfSloQLRdZlYL/QYyrq05j/3Faw==", + "dependencies": { + "good-listener": "^1.2.2", + "select": "^1.1.2", + "tiny-emitter": "^2.0.0" + } + }, + "node_modules/delegate": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/delegate/-/delegate-3.2.0.tgz", + "integrity": "sha512-IofjkYBZaZivn0V8nnsMJGBr4jVLxHDheKSW88PyxS5QC4Vo9ZbZVvhzlSxY87fVq3STR6r+4cGepyHkcWOQSw==" + }, + "node_modules/good-listener": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/good-listener/-/good-listener-1.2.2.tgz", + "integrity": "sha512-goW1b+d9q/HIwbVYZzZ6SsTr4IgE+WA44A0GmPIQstuOrgsFcT7VEJ48nmr9GaRtNu0XTKacFLGnBPAM6Afouw==", + "dependencies": { + "delegate": "^3.1.2" + } + }, + "node_modules/select": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/select/-/select-1.1.2.tgz", + "integrity": "sha512-OwpTSOfy6xSs1+pwcNrv0RBMOzI39Lp3qQKUTPVVPRjCdNa5JH/oPRiqsesIskK8TVgmRiHwO4KXlV2Li9dANA==" + }, + "node_modules/tiny-emitter": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/tiny-emitter/-/tiny-emitter-2.1.0.tgz", + "integrity": "sha512-NB6Dk1A9xgQPMoGqC5CVXn123gWyte215ONT5Pp5a0yt4nlEoO1ZWeCwpncaekPHXO60i47ihFnZPiRPjRMq4Q==" + } + } +} diff --git a/package.json b/package.json new file mode 100644 index 00000000..32560620 --- /dev/null +++ b/package.json @@ -0,0 +1,5 @@ +{ + "dependencies": { + "clipboard": "~2.0.11" + } +} diff --git a/react-ui/config/routes.ts b/react-ui/config/routes.ts index 66de518b..1cdbd027 100644 --- a/react-ui/config/routes.ts +++ b/react-ui/config/routes.ts @@ -135,6 +135,27 @@ export default [ }, ], }, + { + name: '自动机器学习', + path: 'automl', + routes: [ + { + name: '自动机器学习', + path: '', + component: './AutoML/List/index', + }, + { + name: '自动机器学习详情', + path: 'info/:id', + component: './AutoML/Info/index', + }, + { + name: '创建自动机器学习', + path: 'create', + component: './AutoML/Create/index', + }, + ], + }, ], }, { diff --git a/react-ui/src/app.tsx b/react-ui/src/app.tsx index 4f911013..e10540ea 100644 --- a/react-ui/src/app.tsx +++ b/react-ui/src/app.tsx @@ -21,6 +21,7 @@ import './styles/menu.less'; export { requestConfig as request } from './requestConfig'; // const isDev = process.env.NODE_ENV === 'development'; import { type GlobalInitialState } from '@/types'; +import '@/utils/clipboard'; import { menuItemRender } from '@/utils/menuRender'; import ErrorBoundary from './components/ErrorBoundary'; import { gotoLoginPage } from './utils/ui'; diff --git a/react-ui/src/assets/img/editor-parameter.png b/react-ui/src/assets/img/editor-parameter.png index b5fd9f41a4a8abcc2174182549f408e41f6d3995..06fb4f2bc1237804de27b35cf63b2dc4a9e3cb25 100644 GIT binary patch literal 1848 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 literal 1595 zcmV-B2E_S^P)0PiSmPH}REDca2S)%oX(hH0DnlrVI&2GvD!SaDcxl;fB+80qaKg} zIqD+N#u@-D{ZClH`iNO9g!1bo)&j&-{GAibI{V^I1~LFIo$_zg#tT^dwrLWKWF7&; zPhA!T07Cu_lb(n7E;5FW#UmJt3*&J?FFT+df7Fzy~o_Sco6= zRA3#`Ob_z?sglx3}XU;qvVz#>=&AQQ2#mP_>Q)P<6>z%~&OYXji5!HT#XOTs?H z+G>r$cnu~TQ=xck08&Z<3WfmX<9VTX81n5^V81AURm$|~9}EDu?gcmZQDEqt!2nR; zYl2{(Sq215D~gX2HfgHHpqo;w)z>hn34m?DnHp4MHse}pds2)xx9TbS{j;2G)rmX+ zEDllPV

{19R-=y*KR~6vug&?-$_nbcA_RP#A$S2 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 RIl}+|002ovPDHLkV1hqb0MGyc literal 0 HcmV?d00001 diff --git a/react-ui/src/assets/img/trial-config-icon.png b/react-ui/src/assets/img/trial-config-icon.png new file mode 100644 index 0000000000000000000000000000000000000000..69408563ddae054c4aee600475948b043d8c0999 GIT binary patch literal 1692 zcmV;N24ne&P)9(E6h3#}yy+jbrGNGphG;0bv9&9rks)yjp)6QPf{eCc zVoa#S1um3nqS1|^Y)o2;v=ZaOC@o=Qx}dlaV&V@P(n3V0Ka|=|+o{uHr_*`YbDw?V z?YzJH=9Q@-yyPXF_s%`{`_4J{+;i?Lfyd|~j}aGmLZXZxrQ&UzVZR6je%-;;H-h!@p#clqs2# zR$A);c*+b6^MpLw-AQP2v!M(j^FV2EY%JWDEkcH5*aD}^P;UhyZ=n7%D9hD0K%}_T6tu%FMe0R#)*UQsGbWmv8dmA!l%R$P=I3_b&l5KQ?wxU3WD#{suze z0^SWS$wd_f+Hp;G@OpHfsjKs2mKDF0f@iwoFAj9^Bjn%SY4T2d-Kjl8d%*7`$8 zB;`@@S&St$z1mwHnc8jjLqv)16rG6X)qT12(s!S%A@lu#!`|4hC)3HGXM!FNToBc5 zB}XaT93Ky_@vx3eS@ZRP?~k(qvK*;Wl@<8!DBhwNuULv)JxrEo_poP_NU_A$);E$n zPu*d^rO}Jk@^JeXT9%`p+}qn!wZZd8q#?mzI zJ0~#J`d8(m_;(-f?7f*pg=Gs+S_C^pGMjE<01reD`=F7+PoXH`~nv=qgVN>^vTZG&ng75lYP>0G-O<+UdbDN6pp)dMmKLo2FBQ4&_-J%|c7t zTUaZMDh3x8Ae7!kim2W&&xn9?D)WSlQ;TIweqB6Mc8u=vPzW&4?Bpbog#Ks`S#xrd z4MMKSl(x`jY)8*vk?H5bA|Xa3G<}}10y1k?#$?K0r$J(KpvS6VB($#8gq3A7BEvpF z`Mkgi(*G_BBY3VG#WedI{}?;Yy^(G*m<{(Bf6_Qc8xWXju}LU2OATiBXG$r5jD}^<~rR?_VT*)WLgoPv#TA!GVrO3c?VnVtJ(vNOnD0xsmX+A*Ys)w zbYyVC9<-xb&7_|QPgADOlY{&V9yD5J9xbT$GUXV)XwpG01cia83!=$;F}VaSJtLwW zE#HQg=MN-8Xr*=)a0@-bdJRpF(WD}CWFnJsHj};Be+YD>ltAP3Ig*vH&KZ#r4H-Vm zWnyWkV=^Yg5$ZrC{P7S=H6!?M8f`P@N!C7_Gd!-yXqH?34C2T2QH+Os;cSIfy64t~ mjMH# ); + } else if (React.isValidElement(formatValue)) { + // 这个判断必须在下面的判断之前 + valueComponent = ( + + ); } else if (typeof formatValue === 'object' && formatValue) { valueComponent = ( ); + } else if (React.isValidElement(value)) { + return value; } else { component = {value ?? '--'}; } return (

- + {component}
diff --git a/react-ui/src/components/KFIcon/index.tsx b/react-ui/src/components/KFIcon/index.tsx index e50dabec..d84257a7 100644 --- a/react-ui/src/components/KFIcon/index.tsx +++ b/react-ui/src/components/KFIcon/index.tsx @@ -21,13 +21,13 @@ interface KFIconProps extends IconFontProps { className?: string; } -function KFIcon({ type, font = 15, color = '', style = {}, className }: KFIconProps) { +function KFIcon({ type, font = 15, color = '', style = {}, className, ...rest }: KFIconProps) { const iconStyle = { ...style, fontSize: font, color, }; - return ; + return ; } export default KFIcon; diff --git a/react-ui/src/iconfont/iconfont.js b/react-ui/src/iconfont/iconfont.js index c9da6580..0e40d9d6 100644 --- a/react-ui/src/iconfont/iconfont.js +++ b/react-ui/src/iconfont/iconfont.js @@ -1 +1 @@ -window._iconfont_svg_string_4511447='',(t=>{var a=(h=(h=document.getElementsByTagName("script"))[h.length-1]).getAttribute("data-injectcss"),h=h.getAttribute("data-disable-injectsvg");if(!h){var l,v,z,i,o,m=function(a,h){h.parentNode.insertBefore(a,h)};if(a&&!t.__iconfont__svg__cssinject__){t.__iconfont__svg__cssinject__=!0;try{document.write("")}catch(a){console&&console.log(a)}}l=function(){var a,h=document.createElement("div");h.innerHTML=t._iconfont_svg_string_4511447,(h=h.getElementsByTagName("svg")[0])&&(h.setAttribute("aria-hidden","true"),h.style.position="absolute",h.style.width=0,h.style.height=0,h.style.overflow="hidden",h=h,(a=document.body).firstChild?m(h,a.firstChild):a.appendChild(h))},document.addEventListener?~["complete","loaded","interactive"].indexOf(document.readyState)?setTimeout(l,0):(v=function(){document.removeEventListener("DOMContentLoaded",v,!1),l()},document.addEventListener("DOMContentLoaded",v,!1)):document.attachEvent&&(z=l,i=t.document,o=!1,d(),i.onreadystatechange=function(){"complete"==i.readyState&&(i.onreadystatechange=null,p())})}function p(){o||(o=!0,z())}function d(){try{i.documentElement.doScroll("left")}catch(a){return void setTimeout(d,50)}p()}})(window); \ No newline at end of file +window._iconfont_svg_string_4511447='',(t=>{var a=(h=(h=document.getElementsByTagName("script"))[h.length-1]).getAttribute("data-injectcss"),h=h.getAttribute("data-disable-injectsvg");if(!h){var l,v,z,i,o,m=function(a,h){h.parentNode.insertBefore(a,h)};if(a&&!t.__iconfont__svg__cssinject__){t.__iconfont__svg__cssinject__=!0;try{document.write("")}catch(a){console&&console.log(a)}}l=function(){var a,h=document.createElement("div");h.innerHTML=t._iconfont_svg_string_4511447,(h=h.getElementsByTagName("svg")[0])&&(h.setAttribute("aria-hidden","true"),h.style.position="absolute",h.style.width=0,h.style.height=0,h.style.overflow="hidden",h=h,(a=document.body).firstChild?m(h,a.firstChild):a.appendChild(h))},document.addEventListener?~["complete","loaded","interactive"].indexOf(document.readyState)?setTimeout(l,0):(v=function(){document.removeEventListener("DOMContentLoaded",v,!1),l()},document.addEventListener("DOMContentLoaded",v,!1)):document.attachEvent&&(z=l,i=t.document,o=!1,d(),i.onreadystatechange=function(){"complete"==i.readyState&&(i.onreadystatechange=null,p())})}function p(){o||(o=!0,z())}function d(){try{i.documentElement.doScroll("left")}catch(a){return void setTimeout(d,50)}p()}})(window); \ No newline at end of file diff --git a/react-ui/src/pages/AutoML/Create/index.less b/react-ui/src/pages/AutoML/Create/index.less new file mode 100644 index 00000000..ca173288 --- /dev/null +++ b/react-ui/src/pages/AutoML/Create/index.less @@ -0,0 +1,47 @@ +.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; + } + + :global { + .ant-input-number { + width: 100%; + } + + .ant-form-item { + margin-bottom: 20px; + } + + .image-url { + margin-top: -15px; + .ant-form-item-label > label::after { + content: ''; + } + } + + .ant-btn.ant-btn-icon-only .anticon { + color: #565658; + font-size: 20px; + } + + .anticon-question-circle { + margin-top: -12px; + color: @text-color-tertiary !important; + font-size: 12px !important; + } + } + } +} diff --git a/react-ui/src/pages/AutoML/Create/index.tsx b/react-ui/src/pages/AutoML/Create/index.tsx new file mode 100644 index 00000000..9ac79d57 --- /dev/null +++ b/react-ui/src/pages/AutoML/Create/index.tsx @@ -0,0 +1,220 @@ +/* + * @Author: 赵伟 + * @Date: 2024-04-16 13:58:08 + * @Description: 创建服务版本 + */ +import PageTitle from '@/components/PageTitle'; +import { type ParameterInputObject } from '@/components/ResourceSelect'; +import { useComputingResource } from '@/hooks/resource'; +import { + createServiceVersionReq, + getServiceInfoReq, + updateServiceVersionReq, +} from '@/services/modelDeployment'; +import { changePropertyName } from '@/utils'; +import { to } from '@/utils/promise'; +import SessionStorage from '@/utils/sessionStorage'; +import { useNavigate, useParams } from '@umijs/max'; +import { App, Button, Form } from 'antd'; +import { omit, pick } from 'lodash'; +import { useEffect, useState } from 'react'; +import BasicConfig from '../components/CreateForm/BasicConfig'; +import ExecuteConfig from '../components/CreateForm/ExecuteConfig'; +import SearchConfig from '../components/CreateForm/SearchConfig'; +import TrialConfig from '../components/CreateForm/TrialConfig'; +import { ServiceData, ServiceOperationType, ServiceVersionData } from '../types'; +import styles from './index.less'; + +// 表单数据 +export type FormData = { + service_name: string; // 服务名称 + version: string; // 服务版本 + description: string; // 描述 + model: ParameterInputObject; // 模型 + image: ParameterInputObject; // 镜像 + code_config: ParameterInputObject; // 代码 + resource: string; // 资源规格 + replicas: string; // 副本数量 + mount_path: string; // 模型路径 + env_variables: { key: string; value: string }[]; // 环境变量 +}; + +function CreateAutoML() { + const navigate = useNavigate(); + const [form] = Form.useForm(); + const [resourceStandardList, filterResourceStandard] = useComputingResource(); + const [operationType, setOperationType] = useState(ServiceOperationType.Create); + + const { message } = App.useApp(); + const [serviceInfo, setServiceInfo] = useState(undefined); + const [versionInfo, setVersionInfo] = useState(undefined); + const params = useParams(); + const id = params.id; + + useEffect(() => { + const res: + | (ServiceVersionData & { + operationType: ServiceOperationType; + }) + | undefined = SessionStorage.getItem(SessionStorage.serviceVersionInfoKey, true); + if (res) { + setOperationType(res.operationType); + + 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); + } + return () => { + SessionStorage.removeItem(SessionStorage.serviceVersionInfoKey); + }; + }, []); + + // 获取服务详情 + 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 model = formData['model']; + const codeConfig = formData['code_config']; + const envVariables = envList.reduce((acc, cur) => { + acc[cur.key] = cur.value; + return acc; + }, {} as Record); + + // 根据后台要求,修改表单数据 + const object = { + ...omit(formData, ['replicas', 'env_variables', 'image', 'model', 'code_config']), + replicas: Number(formData.replicas), + env_variables: envVariables, + image: image.value, + 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 = + operationType === ServiceOperationType.Create + ? object + : { + id: versionInfo?.id, + rerun: operationType === ServiceOperationType.Restart ? true : false, + deployment_name: versionInfo?.deployment_name, + ...object, + }; + + const request = + operationType === ServiceOperationType.Create + ? createServiceVersionReq + : updateServiceVersionReq; + + const [res] = await to(request(params)); + if (res) { + message.success('操作成功'); + navigate(-1); + } + }; + + // 提交 + const handleSubmit = (values: FormData) => { + console.log('values', values); + }; + + // 取消 + const cancel = () => { + navigate(-1); + }; + + const disabled = operationType !== ServiceOperationType.Create; + let buttonText = '新建'; + let title = '新增服务版本'; + if (operationType === ServiceOperationType.Update) { + title = '更新服务版本'; + buttonText = '更新'; + } else if (operationType === ServiceOperationType.Restart) { + title = '重启服务版本'; + buttonText = '重启'; + } + + return ( +
+ +
+
+
+ + + + + + + + + + +
+
+
+ ); +} + +export default CreateAutoML; diff --git a/react-ui/src/pages/AutoML/Info/index.less b/react-ui/src/pages/AutoML/Info/index.less new file mode 100644 index 00000000..e27756ef --- /dev/null +++ b/react-ui/src/pages/AutoML/Info/index.less @@ -0,0 +1,40 @@ +.auto-ml-info { + position: relative; + height: 100%; + &__tabs { + height: 50px; + padding-left: 25px; + background-image: url(@/assets/img/page-title-bg.png); + background-repeat: no-repeat; + background-position: top center; + background-size: 100% 100%; + } + + &__content { + height: calc(100% - 60px); + margin-top: 10px; + } + + &__tips { + position: absolute; + top: 11px; + left: 256px; + padding: 3px 12px; + color: #565658; + font-size: @font-size-content; + background: .addAlpha(@primary-color, 0.09) []; + border-radius: 4px; + + &::before { + position: absolute; + top: 10px; + left: -6px; + width: 0; + height: 0; + border-top: 4px solid transparent; + border-right: 6px solid .addAlpha(@primary-color, 0.09) []; + border-bottom: 4px solid transparent; + content: ''; + } + } +} diff --git a/react-ui/src/pages/AutoML/Info/index.tsx b/react-ui/src/pages/AutoML/Info/index.tsx new file mode 100644 index 00000000..c167af97 --- /dev/null +++ b/react-ui/src/pages/AutoML/Info/index.tsx @@ -0,0 +1,61 @@ +/* + * @Author: 赵伟 + * @Date: 2024-04-16 13:58:08 + * @Description: 自主机器学习详情 + */ +import KFIcon from '@/components/KFIcon'; +import { CommonTabKeys } from '@/enums'; +import { useCacheState } from '@/hooks/pageCacheState'; +import themes from '@/styles/theme.less'; +import { useNavigate } from '@umijs/max'; +import { Tabs } from 'antd'; +import { useState } from 'react'; +import AutoMLBasic from '../components/AutoMLBasic'; +import AutoMLTable from '../components/AutoMLTable'; +import styles from './index.less'; + +export type MirrorData = { + id: number; + name: string; + description: string; + create_time: string; +}; + +function AutoMLInfo() { + const navigate = useNavigate(); + const [cacheState, setCacheState] = useCacheState(); + const [activeTab, setActiveTab] = useState(cacheState?.activeTab ?? CommonTabKeys.Public); + + const tabItems = [ + { + key: CommonTabKeys.Public, + label: '基本信息', + icon: , + }, + { + key: CommonTabKeys.Private, + label: 'Trial列表', + icon: , + }, + ]; + + return ( +
+
+ +
+
+ {activeTab === CommonTabKeys.Public && } + {activeTab === CommonTabKeys.Private && } +
+ {activeTab === CommonTabKeys.Private && ( +
+ + Trial是一次独立的尝试,他会使用某组超参来运行 +
+ )} +
+ ); +} + +export default AutoMLInfo; diff --git a/react-ui/src/pages/AutoML/List/index.less b/react-ui/src/pages/AutoML/List/index.less new file mode 100644 index 00000000..735e3442 --- /dev/null +++ b/react-ui/src/pages/AutoML/List/index.less @@ -0,0 +1,20 @@ +.auto-ml-list { + height: 100%; + &__content { + height: calc(100% - 60px); + margin-top: 10px; + padding: 20px @content-padding 0; + background-color: white; + border-radius: 10px; + + &__filter { + display: flex; + align-items: center; + } + + &__table { + height: calc(100% - 32px - 28px); + margin-top: 28px; + } + } +} diff --git a/react-ui/src/pages/AutoML/List/index.tsx b/react-ui/src/pages/AutoML/List/index.tsx new file mode 100644 index 00000000..aa02ce74 --- /dev/null +++ b/react-ui/src/pages/AutoML/List/index.tsx @@ -0,0 +1,311 @@ +/* + * @Author: 赵伟 + * @Date: 2024-04-16 13:58:08 + * @Description: 自主机器学习 + */ +import KFIcon from '@/components/KFIcon'; +import PageTitle from '@/components/PageTitle'; +import { useCacheState } from '@/hooks/pageCacheState'; +import { deleteServiceReq, getServiceListReq } from '@/services/modelDeployment'; +import themes from '@/styles/theme.less'; +import { to } from '@/utils/promise'; +import SessionStorage from '@/utils/sessionStorage'; +import tableCellRender, { TableCellValueType } from '@/utils/table'; +import { modalConfirm } from '@/utils/ui'; +import { useNavigate } from '@umijs/max'; +import { + App, + Button, + ConfigProvider, + Input, + Table, + type TablePaginationConfig, + type TableProps, +} from 'antd'; +import { type SearchProps } from 'antd/es/input'; +import classNames from 'classnames'; +import { useEffect, useState } from 'react'; +import ExecuteScheduleCell from '../components/ExecuteScheduleCell'; +import RunStatusCell from '../components/RunStatusCell'; +import { ServiceData, ServiceOperationType } from '../types'; +import styles from './index.less'; + +function AutoMLList() { + const navigate = useNavigate(); + const { message } = App.useApp(); + const [cacheState, setCacheState] = useCacheState(); + const [searchText, setSearchText] = useState(cacheState?.searchText); + const [inputText, setInputText] = useState(cacheState?.searchText); + const [tableData, setTableData] = useState([]); + const [total, setTotal] = useState(0); + const [pagination, setPagination] = useState( + cacheState?.pagination ?? { + current: 1, + pageSize: 10, + }, + ); + + useEffect(() => { + getServiceList(); + }, [pagination, searchText]); + + // 获取模型部署服务列表 + const getServiceList = async () => { + const params: Record = { + page: pagination.current! - 1, + size: pagination.pageSize, + service_name: searchText, + }; + const [res] = await to(getServiceListReq(params)); + if (res && res.data) { + const { content = [], totalElements = 0 } = res.data; + setTableData(content); + setTotal(totalElements); + } + }; + + // 删除模型部署 + const deleteService = async (record: ServiceData) => { + const [res] = await to(deleteServiceReq(record.id)); + if (res) { + message.success('删除成功'); + // 如果是一页的唯一数据,删除时,请求第一页的数据 + // 否则直接刷新这一页的数据 + // 避免回到第一页 + if (tableData.length > 1) { + setPagination((prev) => ({ + ...prev, + current: 1, + })); + } else { + getServiceList(); + } + } + }; + + // 搜索 + const onSearch: SearchProps['onSearch'] = (value) => { + setSearchText(value); + }; + + // 处理删除 + const handleServiceDelete = (record: ServiceData) => { + modalConfirm({ + title: '删除后,该服务将不可恢复', + content: '是否确认删除?', + onOk: () => { + deleteService(record); + }, + }); + }; + + // 创建、更新服务 + const createService = (type: ServiceOperationType, record?: ServiceData) => { + SessionStorage.setItem( + SessionStorage.serviceInfoKey, + { + ...record, + operationType: type, + }, + true, + ); + + setCacheState({ + pagination, + searchText, + }); + + navigate(`/pipeline/autoML/create`); + }; + + // 查看详情 + const toDetail = (record: ServiceData) => { + setCacheState({ + pagination, + searchText, + }); + + navigate(`/pipeline/autoML/info/${record.id}`); + }; + + // 分页切换 + const handleTableChange: TableProps['onChange'] = ( + pagination, + _filters, + _sorter, + { action }, + ) => { + if (action === 'paginate') { + setPagination(pagination); + } + }; + + const columns: TableProps['columns'] = [ + { + title: '序号', + dataIndex: 'index', + key: 'index', + width: '20%', + render: tableCellRender(false, TableCellValueType.Index, { + page: pagination.current! - 1, + pageSize: pagination.pageSize!, + }), + }, + { + title: '实验名称', + dataIndex: 'service_name', + key: 'service_name', + width: '20%', + render: tableCellRender(false, TableCellValueType.Link, { + onClick: toDetail, + }), + }, + { + title: '实验描述', + dataIndex: 'service_type_name', + key: 'service_type_name', + width: '20%', + render: tableCellRender(true), + ellipsis: { showTitle: false }, + }, + { + title: '状态', + dataIndex: 'run_status', + key: 'run_status', + width: '20%', + render: RunStatusCell, + }, + { + title: '实验实例执行进度', + dataIndex: 'description', + key: 'description', + render: ExecuteScheduleCell, + width: 180, + }, + { + title: '创建时间', + dataIndex: 'update_time', + key: 'update_time', + width: '20%', + render: tableCellRender(true, TableCellValueType.Date), + ellipsis: { showTitle: false }, + }, + { + title: '修改时间', + dataIndex: 'update_time', + key: 'update_time', + width: '20%', + render: tableCellRender(true, TableCellValueType.Date), + ellipsis: { showTitle: false }, + }, + { + title: '操作', + dataIndex: 'operation', + width: 400, + key: 'operation', + render: (_: any, record: ServiceData) => ( +
+ + + + + + + +
+ ), + }, + ]; + + return ( +
+ +
+
+ setInputText(e.target.value)} + style={{ width: 300 }} + value={inputText} + allowClear + /> + +
+
+ `共${total}条`, + }} + onChange={handleTableChange} + rowKey="id" + /> + + + + ); +} + +export default AutoMLList; diff --git a/react-ui/src/pages/AutoML/components/AutoMLBasic/index.less b/react-ui/src/pages/AutoML/components/AutoMLBasic/index.less new file mode 100644 index 00000000..80de7592 --- /dev/null +++ b/react-ui/src/pages/AutoML/components/AutoMLBasic/index.less @@ -0,0 +1,7 @@ +.auto-ml-basic { + height: 100%; + padding: 20px @content-padding; + overflow-y: auto; + background-color: white; + border-radius: 10px; +} diff --git a/react-ui/src/pages/AutoML/components/AutoMLBasic/index.tsx b/react-ui/src/pages/AutoML/components/AutoMLBasic/index.tsx new file mode 100644 index 00000000..32968243 --- /dev/null +++ b/react-ui/src/pages/AutoML/components/AutoMLBasic/index.tsx @@ -0,0 +1,81 @@ +import { Flex } from 'antd'; +import { useEffect } from 'react'; +import ConfigInfo, { type BasicInfoData } from '../ConfigInfo'; +import CopyingText from '../CopyingText'; +import StatusChart from '../StatusChart'; +import styles from './index.less'; + +function AutoMLBasic() { + useEffect(() => {}, []); + + const datas: BasicInfoData[] = [ + { + label: '项目名称', + value: '测试项目名称', + ellipsis: true, + }, + { + label: '项目名称', + value: '测试项目名称', + ellipsis: true, + }, + { + label: '项目名称', + value: '测试项目名称', + ellipsis: true, + }, + { + label: '项目名称', + value: '测试项目名称', + ellipsis: true, + }, + { + label: '项目名称', + value: '测试项目名称', + ellipsis: true, + }, + { + label: '项目名称', + value: '测试项目名称', + ellipsis: true, + }, + { + label: '项目名称', + value: '测试项目名称', + ellipsis: true, + }, + { + label: '项目名称', + value: , + ellipsis: false, + }, + ]; + + return ( +
+ + + + + + + +
+ ); +} + +export default AutoMLBasic; diff --git a/react-ui/src/pages/AutoML/components/AutoMLTable/index.less b/react-ui/src/pages/AutoML/components/AutoMLTable/index.less new file mode 100644 index 00000000..2d7d326f --- /dev/null +++ b/react-ui/src/pages/AutoML/components/AutoMLTable/index.less @@ -0,0 +1,15 @@ +.auto-ml-table { + height: 100%; + padding: 20px @content-padding 0; + background-color: white; + border-radius: 10px; + &__filter { + display: flex; + align-items: center; + } + + &__table { + height: calc(100% - 32px - 28px); + margin-top: 28px; + } +} diff --git a/react-ui/src/pages/AutoML/components/AutoMLTable/index.tsx b/react-ui/src/pages/AutoML/components/AutoMLTable/index.tsx new file mode 100644 index 00000000..e4e0f00f --- /dev/null +++ b/react-ui/src/pages/AutoML/components/AutoMLTable/index.tsx @@ -0,0 +1,305 @@ +/* + * @Author: 赵伟 + * @Date: 2024-04-16 13:58:08 + * @Description: 自主机器学习 + */ +import KFIcon from '@/components/KFIcon'; +import { useCacheState } from '@/hooks/pageCacheState'; +import { deleteServiceReq, getServiceListReq } from '@/services/modelDeployment'; +import themes from '@/styles/theme.less'; +import { to } from '@/utils/promise'; +import SessionStorage from '@/utils/sessionStorage'; +import tableCellRender, { TableCellValueType } from '@/utils/table'; +import { modalConfirm } from '@/utils/ui'; +import { useNavigate } from '@umijs/max'; +import { + App, + Button, + ConfigProvider, + Input, + Table, + type TablePaginationConfig, + type TableProps, +} from 'antd'; +import { type SearchProps } from 'antd/es/input'; +import classNames from 'classnames'; +import { useEffect, useState } from 'react'; +// import ExecuteScheduleCell from '../components/ExecuteScheduleCell'; +import { ServiceData, ServiceOperationType } from '@/pages/AutoML/types'; +import RunStatusCell from '../RunStatusCell'; +import styles from './index.less'; + +function AutoMLTable() { + const navigate = useNavigate(); + const { message } = App.useApp(); + const [cacheState, setCacheState] = useCacheState(); + const [searchText, setSearchText] = useState(cacheState?.searchText); + const [inputText, setInputText] = useState(cacheState?.searchText); + const [tableData, setTableData] = useState([]); + const [total, setTotal] = useState(0); + const [pagination, setPagination] = useState( + cacheState?.pagination ?? { + current: 1, + pageSize: 10, + }, + ); + + useEffect(() => { + getServiceList(); + }, [pagination, searchText]); + + // 获取模型部署服务列表 + const getServiceList = async () => { + const params: Record = { + page: pagination.current! - 1, + size: pagination.pageSize, + service_name: searchText, + }; + const [res] = await to(getServiceListReq(params)); + if (res && res.data) { + const { content = [], totalElements = 0 } = res.data; + setTableData(content); + setTotal(totalElements); + } + }; + + // 删除模型部署 + const deleteService = async (record: ServiceData) => { + const [res] = await to(deleteServiceReq(record.id)); + if (res) { + message.success('删除成功'); + // 如果是一页的唯一数据,删除时,请求第一页的数据 + // 否则直接刷新这一页的数据 + // 避免回到第一页 + if (tableData.length > 1) { + setPagination((prev) => ({ + ...prev, + current: 1, + })); + } else { + getServiceList(); + } + } + }; + + // 搜索 + const onSearch: SearchProps['onSearch'] = (value) => { + setSearchText(value); + }; + + // 处理删除 + const handleServiceDelete = (record: ServiceData) => { + modalConfirm({ + title: '删除后,该服务将不可恢复', + content: '是否确认删除?', + onOk: () => { + deleteService(record); + }, + }); + }; + + // 创建、更新服务 + const createService = (type: ServiceOperationType, record?: ServiceData) => { + SessionStorage.setItem( + SessionStorage.serviceInfoKey, + { + ...record, + operationType: type, + }, + true, + ); + + setCacheState({ + pagination, + searchText, + }); + + navigate(`/modelDeployment/createService`); + }; + + // 查看详情 + const toDetail = (record: ServiceData) => { + setCacheState({ + pagination, + searchText, + }); + + navigate(`/modelDeployment/serviceInfo/${record.id}`); + }; + + // 分页切换 + const handleTableChange: TableProps['onChange'] = ( + pagination, + _filters, + _sorter, + { action }, + ) => { + if (action === 'paginate') { + setPagination(pagination); + } + }; + + const columns: TableProps['columns'] = [ + { + title: '序号', + dataIndex: 'index', + key: 'index', + width: '20%', + render: tableCellRender(false, TableCellValueType.Index, { + page: pagination.current! - 1, + pageSize: pagination.pageSize!, + }), + }, + { + title: 'Trial ID', + dataIndex: 'service_name', + key: 'service_name', + width: '20%', + render: tableCellRender(false, TableCellValueType.Link, { + onClick: toDetail, + }), + }, + { + title: '状态', + dataIndex: 'run_status', + key: 'run_status', + width: '20%', + render: RunStatusCell, + }, + { + title: '最终指标', + dataIndex: 'service_type_name', + key: 'service_type_name', + width: '20%', + render: tableCellRender(true), + ellipsis: { showTitle: false }, + }, + { + title: '当前指标', + dataIndex: 'description', + key: 'description', + width: '20%', + render: tableCellRender(true), + ellipsis: { showTitle: false }, + }, + { + title: 'lr', + dataIndex: 'description', + key: 'description', + width: '20%', + render: tableCellRender(true), + ellipsis: { showTitle: false }, + }, + { + title: 'batch_size', + dataIndex: 'description', + key: 'description', + width: '20%', + render: tableCellRender(true), + ellipsis: { showTitle: false }, + }, + { + title: '修改时间', + dataIndex: 'update_time', + key: 'update_time', + width: '20%', + render: tableCellRender(true, TableCellValueType.Date), + ellipsis: { showTitle: false }, + }, + { + title: '执行时长', + dataIndex: 'description', + key: 'description', + width: '20%', + render: tableCellRender(true), + ellipsis: { showTitle: false }, + }, + { + title: '操作', + dataIndex: 'operation', + width: 400, + key: 'operation', + render: (_: any, record: ServiceData) => ( +
+ + + + + + +
+ ), + }, + ]; + + return ( +
+
+ setInputText(e.target.value)} + style={{ width: 300 }} + value={inputText} + allowClear + /> + +
+
+
`共${total}条`, + }} + onChange={handleTableChange} + rowKey="id" + /> + + + ); +} + +export default AutoMLTable; diff --git a/react-ui/src/pages/AutoML/components/ConfigInfo/index.less b/react-ui/src/pages/AutoML/components/ConfigInfo/index.less new file mode 100644 index 00000000..a51e7ddb --- /dev/null +++ b/react-ui/src/pages/AutoML/components/ConfigInfo/index.less @@ -0,0 +1,42 @@ +.config-info { + width: 100%; + border: 1px solid @border-color-base; + border-radius: 4px; + + &__content { + padding: 20px; + padding: 20px @content-padding; + background-color: white; + } + + :global { + .kf-basic-info { + gap: 15px 40px; + width: 100%; + + &__item { + &__label { + font-size: @font-size; + text-align: left; + text-align-last: left; + &::after { + display: none; + } + } + &__value { + font-size: @font-size; + } + } + } + } + + &--three-column { + :global { + .kf-basic-info { + &__item { + width: calc((100% - 80px) / 3); + } + } + } + } +} diff --git a/react-ui/src/pages/AutoML/components/ConfigInfo/index.tsx b/react-ui/src/pages/AutoML/components/ConfigInfo/index.tsx new file mode 100644 index 00000000..e12b291b --- /dev/null +++ b/react-ui/src/pages/AutoML/components/ConfigInfo/index.tsx @@ -0,0 +1,47 @@ +import BasicInfo, { type BasicInfoData } from '@/components/BasicInfo'; +import classNames from 'classnames'; +import { useEffect } from 'react'; +import ConfigTitle from '../ConfigTitle'; +import styles from './index.less'; +export { type BasicInfoData }; + +type ConfigInfoProps = { + title: string; + data: BasicInfoData[]; + className?: string; + style?: React.CSSProperties; + children?: React.ReactNode; + labelWidth: number; + threeColumn?: boolean; +}; + +function ConfigInfo({ + title, + data, + className, + style, + children, + labelWidth, + threeColumn = false, +}: ConfigInfoProps) { + useEffect(() => {}, []); + + return ( +
+ +
+ + {children} +
+
+ ); +} + +export default ConfigInfo; diff --git a/react-ui/src/pages/AutoML/components/ConfigTitle/index.less b/react-ui/src/pages/AutoML/components/ConfigTitle/index.less new file mode 100644 index 00000000..71e55252 --- /dev/null +++ b/react-ui/src/pages/AutoML/components/ConfigTitle/index.less @@ -0,0 +1,38 @@ +.config-title { + width: 100%; + height: 56px; + padding-left: @content-padding; + background: linear-gradient( + 179.03deg, + rgba(199, 223, 255, 0.12) 0%, + rgba(22, 100, 255, 0.04) 100% + ); + border: 1px solid #e8effb; + + &__img { + width: 16px; + height: 16px; + margin-right: 10px; + } + + &__text { + position: relative; + color: @text-color; + font-weight: 500; + font-size: @font-size-title; + + &::after { + position: absolute; + bottom: 6px; + left: 0; + width: 100%; + height: 6px; + background: linear-gradient( + to right, + .addAlpha(@primary-color, 0.4) [] 0, + .addAlpha(@primary-color, 0) [] 100% + ); + content: ''; + } + } +} diff --git a/react-ui/src/pages/AutoML/components/ConfigTitle/index.tsx b/react-ui/src/pages/AutoML/components/ConfigTitle/index.tsx new file mode 100644 index 00000000..546eca88 --- /dev/null +++ b/react-ui/src/pages/AutoML/components/ConfigTitle/index.tsx @@ -0,0 +1,22 @@ +import { Flex } from 'antd'; +import styles from './index.less'; + +type ConfigTitleProps = { + title: string; +}; + +function ConfigTitle({ title }: ConfigTitleProps) { + return ( + + + {title} + + ); +} + +export default ConfigTitle; diff --git a/react-ui/src/pages/AutoML/components/CopyingText/index.less b/react-ui/src/pages/AutoML/components/CopyingText/index.less new file mode 100644 index 00000000..951b37dd --- /dev/null +++ b/react-ui/src/pages/AutoML/components/CopyingText/index.less @@ -0,0 +1,18 @@ +.copying-text { + display: flex; + flex: 1; + align-items: center; + min-width: 0; + margin-left: 16px; + + &__text { + color: @text-color; + font-size: 15px; + } + + &__icon { + margin-left: 6px; + font-size: 14px; + cursor: pointer; + } +} diff --git a/react-ui/src/pages/AutoML/components/CopyingText/index.tsx b/react-ui/src/pages/AutoML/components/CopyingText/index.tsx new file mode 100644 index 00000000..1df718dd --- /dev/null +++ b/react-ui/src/pages/AutoML/components/CopyingText/index.tsx @@ -0,0 +1,33 @@ +import KFIcon from '@/components/KFIcon'; +import { Typography } from 'antd'; + +import styles from './index.less'; + +export type CopyingTextProps = { + text: string; + onCopySuccess?: () => void; + onCopyFailed?: () => void; +}; + +function CopyingText({ text }: CopyingTextProps) { + return ( +
+ + {text} + + +
+ ); +} + +export default CopyingText; diff --git a/react-ui/src/pages/AutoML/components/CreateForm/BasicConfig.tsx b/react-ui/src/pages/AutoML/components/CreateForm/BasicConfig.tsx new file mode 100644 index 00000000..e181d890 --- /dev/null +++ b/react-ui/src/pages/AutoML/components/CreateForm/BasicConfig.tsx @@ -0,0 +1,85 @@ +import SubAreaTitle from '@/components/SubAreaTitle'; +import { Col, Form, Input, Row, Select } from 'antd'; +function BasicConfig() { + return ( + <> + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + {index === fields.length - 1 && ( + + )} +
+ + ))} + {fields.length === 0 && ( +
+ +
+ )} + + + )} + + + ); +} + +export default ExecuteConfig; diff --git a/react-ui/src/pages/AutoML/components/CreateForm/ExecuteConfigDLC.tsx b/react-ui/src/pages/AutoML/components/CreateForm/ExecuteConfigDLC.tsx new file mode 100644 index 00000000..d96a28e6 --- /dev/null +++ b/react-ui/src/pages/AutoML/components/CreateForm/ExecuteConfigDLC.tsx @@ -0,0 +1,258 @@ +import CodeSelect from '@/components/CodeSelect'; +import ResourceSelect, { ResourceSelectorType } from '@/components/ResourceSelect'; +import { MinusCircleOutlined, PlusCircleOutlined } from '@ant-design/icons'; +import { Button, Col, Flex, Form, Input, InputNumber, Radio, Row, Select } from 'antd'; +import styles from './index.less'; + +type ExecuteConfigDLCProps = { + disabled?: boolean; +}; + +function ExecuteConfigDLC({ disabled = false }: ExecuteConfigDLCProps) { + return ( + <> + + + + + + + + + + + + + + + + + + + + + + + + + + 官方镜像 + 自定义镜像 + 镜像地址 + + + + + + + + {({ getFieldValue }) => { + const imageType = getFieldValue('image_type'); + if (imageType === 1 || imageType === 2) { + return ( + + + + ); + } + }} + + + + + + + + + + + + + + + {(fields, { add, remove }) => ( + <> + {fields.map(({ key, name, ...restField }, index) => ( + + + + + : + + + +
+ + {index === fields.length - 1 && ( + + )} +
+
+ ))} + {fields.length === 0 && ( + + + + )} + + )} +
+
+ + + + + + + + + + + ); +} + +export default ExecuteConfigDLC; diff --git a/react-ui/src/pages/AutoML/components/CreateForm/ExecuteConfigMC.tsx b/react-ui/src/pages/AutoML/components/CreateForm/ExecuteConfigMC.tsx new file mode 100644 index 00000000..8f8b7078 --- /dev/null +++ b/react-ui/src/pages/AutoML/components/CreateForm/ExecuteConfigMC.tsx @@ -0,0 +1,82 @@ +import KFIcon from '@/components/KFIcon'; +import { MinusCircleOutlined, PlusCircleOutlined } from '@ant-design/icons'; +import { Button, Col, Flex, Form, Input, Row } from 'antd'; +import styles from './index.less'; + +type ExecuteConfigMCProps = { + disabled?: boolean; +}; + +function ExecuteConfigMC({ disabled = false }: ExecuteConfigMCProps) { + return ( + <> + + {(fields, { add, remove }) => ( + <> + + + + + +
+ +
Key
+
命令
+
操作
+
+ + {fields.map(({ key, name, ...restField }, index) => ( + + cmd{index + 1} + + + +
+ + {index === fields.length - 1 && ( + + )} +
+
+ ))} + {fields.length === 0 && ( +
+ +
+ )} +
+ + )} + + + ); +} + +export default ExecuteConfigMC; diff --git a/react-ui/src/pages/AutoML/components/CreateForm/SearchConfig.tsx b/react-ui/src/pages/AutoML/components/CreateForm/SearchConfig.tsx new file mode 100644 index 00000000..255a6657 --- /dev/null +++ b/react-ui/src/pages/AutoML/components/CreateForm/SearchConfig.tsx @@ -0,0 +1,119 @@ +import SubAreaTitle from '@/components/SubAreaTitle'; +import { Col, Form, InputNumber, Row, Select, Switch } from 'antd'; +function SearchConfig() { + return ( + <> + + + + + + + + + + + + + + : + + + +
+ + {index === fields.length - 1 && ( + + )} +
+ + ))} + {fields.length === 0 && ( + + + + )} + + )} + + + + + + + + + + + + + + + + + 越大越好 + 越小越好 + + + + + + + + + + + + + + + ); +} + +export default TrialConfig; diff --git a/react-ui/src/pages/AutoML/components/CreateForm/index.less b/react-ui/src/pages/AutoML/components/CreateForm/index.less new file mode 100644 index 00000000..57a44b79 --- /dev/null +++ b/react-ui/src/pages/AutoML/components/CreateForm/index.less @@ -0,0 +1,130 @@ +.advanced-config { + margin-bottom: 20px; + + &:last-child { + margin-bottom: 0; + } +} + +.command { + width: 83.33%; + margin-bottom: 20px; + border: 1px solid rgba(234, 234, 234, 0.8); + border-radius: 4px; + &__header { + height: 50px; + padding-left: 8px; + color: @text-color; + font-size: @font-size; + background: #f8f8f9; + border-radius: 4px 4px 0px 0px; + + &__name { + flex: none; + width: 100px; + } + &__command { + flex: 1; + margin-right: 15px; + } + + &__operation { + flex: none; + width: 100px; + } + } + &__body { + padding: 8px; + border-bottom: 1px solid rgba(234, 234, 234, 0.8); + + &:last-child { + border-bottom: none; + } + + &__name { + flex: none; + width: 100px; + } + + &__command { + flex: 1; + margin-right: 15px; + margin-bottom: 0 !important; + } + + &__operation { + flex: none; + width: 100px; + } + } + + &__add { + display: flex; + align-items: center; + justify-content: center; + padding: 15px 0; + } +} + +.hyper-parameter { + width: 83.33%; + margin-bottom: 20px; + border: 1px solid rgba(234, 234, 234, 0.8); + border-radius: 4px; + &__header { + height: 50px; + padding-left: 8px; + color: @text-color; + font-size: @font-size; + background: #f8f8f9; + border-radius: 4px 4px 0px 0px; + + &__name, + &__type, + &__space { + flex: 1; + margin-right: 15px; + } + + &__operation { + flex: none; + width: 100px; + } + } + &__body { + padding: 8px; + border-bottom: 1px solid rgba(234, 234, 234, 0.8); + + &:last-child { + border-bottom: none; + } + + &__name, + &__type, + &__space { + flex: 1; + margin-right: 15px; + margin-bottom: 0 !important; + } + + &__operation { + flex: none; + width: 100px; + } + } + + &__add { + display: flex; + align-items: center; + justify-content: center; + padding: 15px 0; + } +} + +.trial-metrics { + width: calc(41.67% - 6px); + margin-bottom: 20px; + padding: 0 20px; + border: 1px dashed #e0e0e0; + border-radius: 8px; +} diff --git a/react-ui/src/pages/AutoML/components/ExecuteScheduleCell/index.less b/react-ui/src/pages/AutoML/components/ExecuteScheduleCell/index.less new file mode 100644 index 00000000..707a9b0d --- /dev/null +++ b/react-ui/src/pages/AutoML/components/ExecuteScheduleCell/index.less @@ -0,0 +1,30 @@ +.execute-schedule-cell { + display: flex; + flex-direction: row; + align-items: center; + + &__progress { + width: 112px; + height: 16px; + padding: 4px 5px; + background: rgba(96, 107, 122, 0.15); + border-radius: 8px; + + &__bar { + height: 7px; + background: linear-gradient(270deg, #1664ff 0%, rgba(22, 100, 255, 0.1) 100%); + border-radius: 9px; + } + } + + &__text { + margin-left: 6px; + color: @text-color-tertiary; + font-size: 12px; + letter-spacing: 2px; + + strong { + color: @text-color; + } + } +} diff --git a/react-ui/src/pages/AutoML/components/ExecuteScheduleCell/index.tsx b/react-ui/src/pages/AutoML/components/ExecuteScheduleCell/index.tsx new file mode 100644 index 00000000..b1b9ae29 --- /dev/null +++ b/react-ui/src/pages/AutoML/components/ExecuteScheduleCell/index.tsx @@ -0,0 +1,25 @@ +/* + * @Author: 赵伟 + * @Date: 2024-10-29 18:35:41 + * @Description: 实验实例执行进度 + */ + +import styles from './index.less'; + +function ExecuteScheduleCell(status?: any) { + return ( +
+
+
+
+ + 1/2 + +
+ ); +} + +export default ExecuteScheduleCell; diff --git a/react-ui/src/pages/AutoML/components/RunStatusCell/index.less b/react-ui/src/pages/AutoML/components/RunStatusCell/index.less new file mode 100644 index 00000000..b6aba701 --- /dev/null +++ b/react-ui/src/pages/AutoML/components/RunStatusCell/index.less @@ -0,0 +1,19 @@ +.run-status-cell { + color: @text-color; + + &--running { + color: @primary-color; + } + + &--stopped { + color: @abort-color; + } + + &--error { + color: @error-color; + } + + &--pending { + color: @warning-color; + } +} diff --git a/react-ui/src/pages/AutoML/components/RunStatusCell/index.tsx b/react-ui/src/pages/AutoML/components/RunStatusCell/index.tsx new file mode 100644 index 00000000..a20a2fb6 --- /dev/null +++ b/react-ui/src/pages/AutoML/components/RunStatusCell/index.tsx @@ -0,0 +1,44 @@ +/* + * @Author: 赵伟 + * @Date: 2024-04-18 18:35:41 + * @Description: 实验运行状态 + */ +import { ServiceRunStatus } from '@/enums'; +import styles from './index.less'; + +export type ServiceRunStatusInfo = { + text: string; + classname: string; +}; + +export const statusInfo: Record = { + [ServiceRunStatus.Init]: { + text: '启动中', + classname: styles['run-status-cell'], + }, + [ServiceRunStatus.Running]: { + classname: styles['run-status-cell--running'], + text: '运行中', + }, + [ServiceRunStatus.Stopped]: { + classname: styles['run-status-cell--stopped'], + text: '已停止', + }, + [ServiceRunStatus.Failed]: { + classname: styles['run-status-cell--error'], + text: '失败', + }, + [ServiceRunStatus.Pending]: { + classname: styles['run-status-cell--pending'], + text: '挂起中', + }, +}; + +function RunStatusCell(status?: ServiceRunStatus | null) { + if (status === null || status === undefined || !statusInfo[status]) { + return --; + } + return {statusInfo[status].text}; +} + +export default RunStatusCell; diff --git a/react-ui/src/pages/AutoML/components/StatusChart/index.less b/react-ui/src/pages/AutoML/components/StatusChart/index.less new file mode 100644 index 00000000..be1c816e --- /dev/null +++ b/react-ui/src/pages/AutoML/components/StatusChart/index.less @@ -0,0 +1,8 @@ +.status-chart { + flex: none; + width: 380px; + min-width: 380px; + background: #ffffff; + border: 1px solid @border-color-base; + border-radius: 4px; +} diff --git a/react-ui/src/pages/AutoML/components/StatusChart/index.tsx b/react-ui/src/pages/AutoML/components/StatusChart/index.tsx new file mode 100644 index 00000000..db9a3cdd --- /dev/null +++ b/react-ui/src/pages/AutoML/components/StatusChart/index.tsx @@ -0,0 +1,217 @@ +import themes from '@/styles/theme.less'; +import * as echarts from 'echarts'; +import React, { useEffect, useRef } from 'react'; +import ConfigTitle from '../ConfigTitle'; +import styles from './index.less'; + +const color1 = new echarts.graphic.LinearGradient( + 0, + 0, + 0, + 1, + [ + { offset: 0, color: '#c73131' }, + { offset: 1, color: '#ff7e96' }, + ], + false, +); + +const color2 = new echarts.graphic.LinearGradient( + 0, + 0, + 0, + 1, + [ + { offset: 0, color: '#6ac21d' }, + { offset: 1, color: '#96e850' }, + ], + false, +); +const color3 = new echarts.graphic.LinearGradient( + 0, + 0, + 0, + 1, + [ + { offset: 0, color: '#8c8c8c' }, + { offset: 1, color: '#c8c6c6' }, + ], + false, +); +const color4 = new echarts.graphic.LinearGradient( + 0, + 0, + 0, + 1, + [ + { offset: 0, color: '#ecb934' }, + { offset: 1, color: '#f0864d' }, + ], + false, +); + +const color5 = new echarts.graphic.LinearGradient( + 0, + 0, + 0, + 1, + [ + { offset: 0, color: '#7ea9fe' }, + { offset: 1, color: '#1664ff' }, + ], + false, +); + +const color6 = new echarts.graphic.LinearGradient( + 0, + 0, + 0, + 1, + [ + { offset: 0, color: 'rgba(255, 255, 255, 0.62)' }, + { offset: 1, color: '#ebf2ff ' }, + ], + false, +); + +export type ExperimentStatistics = { + Failed: number; + Pending: number; + Running: number; + Succeeded: number; + Terminated: number; +}; + +type ExperimentChartProps = { + style?: React.CSSProperties; + chartData: ExperimentStatistics; +}; + +function StatusChart({ chartData, style }: ExperimentChartProps) { + const chartRef = useRef(null); + const total = + chartData.Failed + + chartData.Pending + + chartData.Running + + chartData.Succeeded + + chartData.Terminated; + const options: echarts.EChartsOption = { + title: { + show: true, + left: '29%', + top: 'center', + textAlign: 'center', + text: [`{a|${total}}`, '{b|Trials}'].join('\n'), + textStyle: { + rich: { + a: { + color: themes['textColor'], + fontSize: 20, + fontWeight: 700, + lineHeight: 28, + }, + b: { + color: themes['textColorSecondary'], + fontSize: 10, + fontWeight: 'normal', + }, + }, + }, + }, + tooltip: { + trigger: 'item', + }, + legend: { + top: 'center', + right: '5%', + orient: 'vertical', + icon: 'circle', + itemWidth: 6, + itemGap: 20, + height: 100, + }, + color: [color1, color2, color3, color4, color5], + series: [ + { + type: 'pie', + radius: ['70%', '80%'], + center: ['30%', '50%'], + avoidLabelOverlap: false, + padAngle: 3, + itemStyle: { + borderRadius: 3, + }, + minAngle: 5, + label: { + show: false, + }, + emphasis: { + label: { + show: false, + }, + }, + labelLine: { + show: false, + }, + data: [ + { value: chartData.Failed > 0 ? chartData.Failed : undefined, name: '失败' }, + { value: chartData.Succeeded > 0 ? chartData.Succeeded : undefined, name: '成功' }, + { value: chartData.Terminated > 0 ? chartData.Terminated : undefined, name: '中止' }, + { value: chartData.Pending > 0 ? chartData.Pending : undefined, name: '等待' }, + { value: chartData.Running > 0 ? chartData.Running : undefined, name: '运行中' }, + ], + }, + { + type: 'pie', + radius: '60%', + center: ['30%', '50%'], + avoidLabelOverlap: false, + label: { + show: false, + }, + tooltip: { + show: false, + }, + emphasis: { + label: { + show: false, + }, + disabled: true, + }, + animation: false, + labelLine: { + show: false, + }, + data: [ + { + value: 100, + itemStyle: { color: color6, borderColor: 'rgba(22, 100, 255, 0.08)', borderWidth: 1 }, + }, + ], + }, + ], + }; + + useEffect(() => { + // 创建一个echarts实例,返回echarts实例 + const chart = echarts.init(chartRef.current); + + // 设置图表实例的配置项和数据 + chart.setOption(options); + + // 组件卸载 + return () => { + // myChart.dispose() 销毁实例 + chart.dispose(); + }; + }, []); + + return ( +
+ +
+
+ ); +} + +export default StatusChart; diff --git a/react-ui/src/pages/AutoML/types.ts b/react-ui/src/pages/AutoML/types.ts new file mode 100644 index 00000000..56582f9e --- /dev/null +++ b/react-ui/src/pages/AutoML/types.ts @@ -0,0 +1,6 @@ +// 操作类型 +export enum ServiceOperationType { + Create = 'Create', // 创建 + Update = 'Update', // 更新 + Restart = 'Restart', // 重启 +} diff --git a/react-ui/src/utils/clipboard.js b/react-ui/src/utils/clipboard.js new file mode 100644 index 00000000..177fcbce --- /dev/null +++ b/react-ui/src/utils/clipboard.js @@ -0,0 +1,12 @@ +import ClipboardJS from 'clipboard'; +import { message } from "antd"; + +const clipboard = new ClipboardJS('#copying'); + +clipboard.on('success', () => { + message.success('复制成功'); +}); + +clipboard.on('error', () => { + message.error('复制失败'); +}); \ No newline at end of file From 4f8e09a591ee11a15fec58daa38a7a4587b53355 Mon Sep 17 00:00:00 2001 From: cp3hnu Date: Wed, 6 Nov 2024 14:51:58 +0800 Subject: [PATCH 04/59] =?UTF-8?q?style:=20=E4=BF=AE=E6=94=B9=E8=87=AA?= =?UTF-8?q?=E5=8A=A8=E5=AD=A6=E4=B9=A0=E6=A0=B7=E5=BC=8F?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../src/components/KFBreadcrumb/index.tsx | 6 +++ react-ui/src/pages/Application/index.tsx | 6 +++ react-ui/src/pages/AutoML/Create/index.less | 10 +++- .../AutoML/components/AutoMLTable/index.tsx | 2 +- .../AutoML/components/ConfigInfo/index.less | 7 ++- .../AutoML/components/StatusChart/index.tsx | 50 ++++++++++++++++--- 6 files changed, 71 insertions(+), 10 deletions(-) diff --git a/react-ui/src/components/KFBreadcrumb/index.tsx b/react-ui/src/components/KFBreadcrumb/index.tsx index c6322025..dc87efc2 100644 --- a/react-ui/src/components/KFBreadcrumb/index.tsx +++ b/react-ui/src/components/KFBreadcrumb/index.tsx @@ -1,3 +1,9 @@ +/* + * @Author: 赵伟 + * @Date: 2024-09-02 08:42:57 + * @Description: 自定义面包屑,暂时不用,使用了 ProBreadcrumb + */ + import { Breadcrumb, type BreadcrumbProps } from 'antd'; import { Link, matchPath, useLocation } from 'umi'; // import routes from '../../../config/config'; // 导入你的路由配置 diff --git a/react-ui/src/pages/Application/index.tsx b/react-ui/src/pages/Application/index.tsx index 65a1efae..20147094 100644 --- a/react-ui/src/pages/Application/index.tsx +++ b/react-ui/src/pages/Application/index.tsx @@ -1,3 +1,9 @@ +/* + * @Author: 赵伟 + * @Date: 2024-09-02 08:42:57 + * @Description: 应用开发 + */ + import IframePage, { IframePageType } from '@/components/IFramePage'; function Application() { diff --git a/react-ui/src/pages/AutoML/Create/index.less b/react-ui/src/pages/AutoML/Create/index.less index ca173288..7c88312e 100644 --- a/react-ui/src/pages/AutoML/Create/index.less +++ b/react-ui/src/pages/AutoML/Create/index.less @@ -32,13 +32,21 @@ } } - .ant-btn.ant-btn-icon-only .anticon { + .ant-btn-variant-text:disabled { + color: rgba(0, 0, 0, 0.25); + } + + .ant-btn-variant-text { color: #565658; + } + + .ant-btn.ant-btn-icon-only .anticon { font-size: 20px; } .anticon-question-circle { margin-top: -12px; + margin-left: 1px !important; color: @text-color-tertiary !important; font-size: 12px !important; } diff --git a/react-ui/src/pages/AutoML/components/AutoMLTable/index.tsx b/react-ui/src/pages/AutoML/components/AutoMLTable/index.tsx index e4e0f00f..6fd78790 100644 --- a/react-ui/src/pages/AutoML/components/AutoMLTable/index.tsx +++ b/react-ui/src/pages/AutoML/components/AutoMLTable/index.tsx @@ -217,7 +217,7 @@ function AutoMLTable() { { title: '操作', dataIndex: 'operation', - width: 400, + width: 250, key: 'operation', render: (_: any, record: ServiceData) => (
diff --git a/react-ui/src/pages/AutoML/components/ConfigInfo/index.less b/react-ui/src/pages/AutoML/components/ConfigInfo/index.less index a51e7ddb..babbac65 100644 --- a/react-ui/src/pages/AutoML/components/ConfigInfo/index.less +++ b/react-ui/src/pages/AutoML/components/ConfigInfo/index.less @@ -1,5 +1,6 @@ .config-info { - width: 100%; + flex: 1; + min-width: 0; border: 1px solid @border-color-base; border-radius: 4px; @@ -11,10 +12,11 @@ :global { .kf-basic-info { - gap: 15px 40px; + gap: 15px; width: 100%; &__item { + width: calc((100% - 15px) / 2); &__label { font-size: @font-size; text-align: left; @@ -24,6 +26,7 @@ } } &__value { + min-width: 0; font-size: @font-size; } } diff --git a/react-ui/src/pages/AutoML/components/StatusChart/index.tsx b/react-ui/src/pages/AutoML/components/StatusChart/index.tsx index db9a3cdd..2c8b8aa4 100644 --- a/react-ui/src/pages/AutoML/components/StatusChart/index.tsx +++ b/react-ui/src/pages/AutoML/components/StatusChart/index.tsx @@ -4,6 +4,8 @@ import React, { useEffect, useRef } from 'react'; import ConfigTitle from '../ConfigTitle'; import styles from './index.less'; +const colors = ['#c73131', '#6ac21d', '#1664ff', '#f0864d', '#8a8a8a']; + const color1 = new echarts.graphic.LinearGradient( 0, 0, @@ -62,7 +64,7 @@ const color5 = new echarts.graphic.LinearGradient( false, ); -const color6 = new echarts.graphic.LinearGradient( +const circleBgColor = new echarts.graphic.LinearGradient( 0, 0, 0, @@ -130,16 +132,48 @@ function StatusChart({ chartData, style }: ExperimentChartProps) { itemGap: 20, height: 100, }, - color: [color1, color2, color3, color4, color5], + color: colors, //[color1, color2, color3, color4, color5], series: [ { type: 'pie', - radius: ['70%', '80%'], + radius: '80%', + center: ['30%', '50%'], + avoidLabelOverlap: false, + label: { + show: false, + }, + tooltip: { + show: false, + }, + emphasis: { + label: { + show: false, + }, + disabled: true, + }, + animation: false, + labelLine: { + show: false, + }, + data: [ + { + value: 100, + itemStyle: { + color: circleBgColor, + borderColor: 'rgba(22, 100, 255, 0.08)', + borderWidth: 1, + }, + }, + ], + }, + { + type: 'pie', + radius: ['50%', '70%'], center: ['30%', '50%'], avoidLabelOverlap: false, padAngle: 3, itemStyle: { - borderRadius: 3, + borderRadius: 0, }, minAngle: 5, label: { @@ -163,7 +197,7 @@ function StatusChart({ chartData, style }: ExperimentChartProps) { }, { type: 'pie', - radius: '60%', + radius: '40%', center: ['30%', '50%'], avoidLabelOverlap: false, label: { @@ -185,7 +219,11 @@ function StatusChart({ chartData, style }: ExperimentChartProps) { data: [ { value: 100, - itemStyle: { color: color6, borderColor: 'rgba(22, 100, 255, 0.08)', borderWidth: 1 }, + itemStyle: { + color: circleBgColor, + borderColor: 'rgba(22, 100, 255, 0.08)', + borderWidth: 1, + }, }, ], }, From fe9e9ed2caadd59434e6ce72d33ca42b4b625fcf Mon Sep 17 00:00:00 2001 From: chenzhihang <709011834@qq.com> Date: Sat, 16 Nov 2024 16:21:55 +0800 Subject: [PATCH 05/59] =?UTF-8?q?=E8=87=AA=E5=8A=A8=E6=9C=BA=E5=99=A8?= =?UTF-8?q?=E5=AD=A6=E4=B9=A0=E5=BC=80=E5=8F=91?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../controller/autoML/AutoMLController.java | 48 +++- .../com/ruoyi/platform/domain/AutoMlIns.java | 77 ++++++ .../com/ruoyi/platform/mapper/AutoMLDao.java | 2 +- .../ruoyi/platform/mapper/AutoMLInsDao.java | 21 ++ .../ruoyi/platform/service/AutoMLService.java | 19 +- .../service/impl/AutoMLServiceImpl.java | 223 +++++++++++++++++- .../service/impl/JupyterServiceImpl.java | 2 - .../managementPlatform/AutoMLDaoMapper.xml | 2 +- .../managementPlatform/AutoMLInsDaoMapper.xml | 132 +++++++++++ 9 files changed, 514 insertions(+), 12 deletions(-) create mode 100644 ruoyi-modules/management-platform/src/main/java/com/ruoyi/platform/domain/AutoMlIns.java create mode 100644 ruoyi-modules/management-platform/src/main/java/com/ruoyi/platform/mapper/AutoMLInsDao.java create mode 100644 ruoyi-modules/management-platform/src/main/resources/mapper/managementPlatform/AutoMLInsDaoMapper.xml diff --git a/ruoyi-modules/management-platform/src/main/java/com/ruoyi/platform/controller/autoML/AutoMLController.java b/ruoyi-modules/management-platform/src/main/java/com/ruoyi/platform/controller/autoML/AutoMLController.java index 8b0c9a74..47e291ee 100644 --- a/ruoyi-modules/management-platform/src/main/java/com/ruoyi/platform/controller/autoML/AutoMLController.java +++ b/ruoyi-modules/management-platform/src/main/java/com/ruoyi/platform/controller/autoML/AutoMLController.java @@ -1,14 +1,17 @@ package com.ruoyi.platform.controller.autoML; import com.ruoyi.common.core.web.controller.BaseController; +import com.ruoyi.common.core.web.domain.AjaxResult; import com.ruoyi.common.core.web.domain.GenericsAjaxResult; import com.ruoyi.platform.domain.AutoMl; +import com.ruoyi.platform.domain.AutoMlIns; import com.ruoyi.platform.service.AutoMLService; import io.swagger.annotations.Api; import io.swagger.annotations.ApiOperation; import org.springframework.data.domain.Page; import org.springframework.data.domain.PageRequest; import org.springframework.web.bind.annotation.*; +import org.springframework.web.multipart.MultipartFile; import javax.annotation.Resource; @@ -24,7 +27,7 @@ public class AutoMLController extends BaseController { @ApiOperation("分页查询") public GenericsAjaxResult> queryByPage(@RequestParam("page") int page, @RequestParam("size") int size, - @RequestParam(value = "mlName", required = false) String mlName) { + @RequestParam(value = "ml_name", required = false) String mlName) { PageRequest pageRequest = PageRequest.of(page, size); return genericsSuccess(this.autoMLService.queryByPage(mlName, pageRequest)); } @@ -37,14 +40,53 @@ public class AutoMLController extends BaseController { @PutMapping @ApiOperation("编辑自动机器学习") - public GenericsAjaxResult editAutoMl(@RequestBody AutoMl autoMl){ + public GenericsAjaxResult editAutoMl(@RequestBody AutoMl autoMl) { return genericsSuccess(this.autoMLService.edit(autoMl)); } @DeleteMapping("{id}") @ApiOperation("删除自动机器学习") - public GenericsAjaxResult deleteAutoMl(@PathVariable("id") Long id){ + public GenericsAjaxResult deleteAutoMl(@PathVariable("id") Long id) { return genericsSuccess(this.autoMLService.delete(id)); } + @GetMapping("/autoMlIns") + @ApiOperation("分页查询自动机器学习实例") + public GenericsAjaxResult> queryByPage(@RequestParam("page") int page, + @RequestParam("size") int size, + @RequestParam("auto_ml_id") Long autoMlId) { + PageRequest pageRequest = PageRequest.of(page, size); + return genericsSuccess(this.autoMLService.queryAutoMlInsByPage(autoMlId, pageRequest)); + } + + @PostMapping("/autoMlIns") + @ApiOperation("新增自动机器学习实例") + public GenericsAjaxResult addAutoMlIns(@RequestBody AutoMlIns autoMlIns) throws Exception { + return genericsSuccess(this.autoMLService.saveAutoMlIns(autoMlIns)); + } + + @PutMapping("/autoMlIns") + @ApiOperation("编辑自动机器学习实例") + public GenericsAjaxResult editAutoMlIns(@RequestBody AutoMlIns autoMlIns) throws Exception { + return genericsSuccess(this.autoMLService.editAutoMlIns(autoMlIns)); + } + + @DeleteMapping("/autoMlIns/{id}") + @ApiOperation("删除自动机器学习实例") + public GenericsAjaxResult deleteAutoMlIns(@PathVariable("id") Long id) { + return genericsSuccess(this.autoMLService.deleteAutoMlIns(id)); + } + + @CrossOrigin(origins = "*", allowedHeaders = "*") + @PostMapping("/upload") + @ApiOperation(value = "上传数据文件csv", notes = "上传数据文件csv,并将信息存入数据库。") + public AjaxResult upload(@RequestParam("file") MultipartFile file, @RequestParam("uuid") String uuid) throws Exception { + return AjaxResult.success(this.autoMLService.upload(file, uuid)); + } + + @PostMapping("{id}") + @ApiOperation("运行自动机器学习实验") + public GenericsAjaxResult runAutoML(@PathVariable("id") Long id) throws Exception { + return genericsSuccess(this.autoMLService.runAutoMlIns(id)); + } } diff --git a/ruoyi-modules/management-platform/src/main/java/com/ruoyi/platform/domain/AutoMlIns.java b/ruoyi-modules/management-platform/src/main/java/com/ruoyi/platform/domain/AutoMlIns.java new file mode 100644 index 00000000..653c212d --- /dev/null +++ b/ruoyi-modules/management-platform/src/main/java/com/ruoyi/platform/domain/AutoMlIns.java @@ -0,0 +1,77 @@ +package com.ruoyi.platform.domain; + +import com.baomidou.mybatisplus.annotation.TableField; +import com.fasterxml.jackson.databind.PropertyNamingStrategy; +import com.fasterxml.jackson.databind.annotation.JsonNaming; +import com.ruoyi.platform.vo.VersionVo; +import lombok.Data; + +import java.util.Date; + +@Data +@JsonNaming(PropertyNamingStrategy.SnakeCaseStrategy.class) +public class AutoMlIns { + private Long id; + + private Long autoMlId; + + private String taskType; + + private String datasetName; + + private Integer timeLeftForThisTask; + + private Integer perRunTimeLimit; + + private Integer ensembleSize; + + private String ensembleClass; + + private Integer ensembleNbest; + + private Integer maxModelsOnDisc; + + private Integer seed; + + private Integer memoryLimit; + + private String includeClassifier; + + private String includeFeaturePreprocessor; + + private String includeRegressor; + + private String excludeClassifier; + + private String excludeRegressor; + + private String excludeFeaturePreprocessor; + + private String resamplingStrategy; + + private Float trainSize; + + private Boolean shuffle; + + private Integer folds; + + private Boolean deleteTmpFolderAfterTerminate; + + private String dataCsv; + + private String targetColumns; + + private Integer state; + + private String createBy; + + private String updateBy; + + private Date createTime; + + private Date updateTime; + + @TableField(exist = false) + private VersionVo versionVo; + +} diff --git a/ruoyi-modules/management-platform/src/main/java/com/ruoyi/platform/mapper/AutoMLDao.java b/ruoyi-modules/management-platform/src/main/java/com/ruoyi/platform/mapper/AutoMLDao.java index ed68d8a9..b10f5235 100644 --- a/ruoyi-modules/management-platform/src/main/java/com/ruoyi/platform/mapper/AutoMLDao.java +++ b/ruoyi-modules/management-platform/src/main/java/com/ruoyi/platform/mapper/AutoMLDao.java @@ -11,7 +11,7 @@ public interface AutoMLDao { List queryByPage(@Param("mlName") String mlName, @Param("pageable") Pageable pageable); - AutoMl getAutoById(@Param("id") Long id); + AutoMl getAutoMlById(@Param("id") Long id); int save(@Param("autoMl") AutoMl autoMl); diff --git a/ruoyi-modules/management-platform/src/main/java/com/ruoyi/platform/mapper/AutoMLInsDao.java b/ruoyi-modules/management-platform/src/main/java/com/ruoyi/platform/mapper/AutoMLInsDao.java new file mode 100644 index 00000000..a541fc53 --- /dev/null +++ b/ruoyi-modules/management-platform/src/main/java/com/ruoyi/platform/mapper/AutoMLInsDao.java @@ -0,0 +1,21 @@ +package com.ruoyi.platform.mapper; + +import com.ruoyi.platform.domain.AutoMlIns; +import org.apache.ibatis.annotations.Param; +import org.springframework.data.domain.Pageable; + +import java.util.List; + +public interface AutoMLInsDao { + long count(@Param("autoMlId") Long autoMlId); + + List queryByPage(@Param("autoMlId") Long autoMlId, @Param("pageable") Pageable pageable); + + int save(@Param("autoMlIns") AutoMlIns autoMlIns); + + int edit(@Param("autoMlIns") AutoMlIns autoMlIns); + + AutoMlIns getById(@Param("id") Long id); + + AutoMlIns findByDatasetName(@Param("datasetName") String datasetName, @Param("autoMlId") Long autoMlId); +} diff --git a/ruoyi-modules/management-platform/src/main/java/com/ruoyi/platform/service/AutoMLService.java b/ruoyi-modules/management-platform/src/main/java/com/ruoyi/platform/service/AutoMLService.java index ce06b9c4..d475d5a3 100644 --- a/ruoyi-modules/management-platform/src/main/java/com/ruoyi/platform/service/AutoMLService.java +++ b/ruoyi-modules/management-platform/src/main/java/com/ruoyi/platform/service/AutoMLService.java @@ -1,15 +1,32 @@ package com.ruoyi.platform.service; import com.ruoyi.platform.domain.AutoMl; +import com.ruoyi.platform.domain.AutoMlIns; import org.springframework.data.domain.Page; import org.springframework.data.domain.PageRequest; +import org.springframework.web.multipart.MultipartFile; + +import java.util.List; +import java.util.Map; public interface AutoMLService { - Page queryByPage(String mlName, PageRequest pageRequest); + Page queryByPage(String mlName, PageRequest pageRequest); AutoMl save(AutoMl autoMl); String edit(AutoMl autoMl); String delete(Long id); + + Page queryAutoMlInsByPage(Long autoMlId, PageRequest pageRequest); + + AutoMlIns saveAutoMlIns(AutoMlIns autoMlIns) throws Exception; + + String editAutoMlIns(AutoMlIns autoMlIns) throws Exception; + + String deleteAutoMlIns(Long id); + + Map upload(MultipartFile file, String uuid) throws Exception; + + String runAutoMlIns(Long id) throws Exception; } diff --git a/ruoyi-modules/management-platform/src/main/java/com/ruoyi/platform/service/impl/AutoMLServiceImpl.java b/ruoyi-modules/management-platform/src/main/java/com/ruoyi/platform/service/impl/AutoMLServiceImpl.java index 4504c4f7..19bb878b 100644 --- a/ruoyi-modules/management-platform/src/main/java/com/ruoyi/platform/service/impl/AutoMLServiceImpl.java +++ b/ruoyi-modules/management-platform/src/main/java/com/ruoyi/platform/service/impl/AutoMLServiceImpl.java @@ -3,22 +3,57 @@ package com.ruoyi.platform.service.impl; import com.ruoyi.common.security.utils.SecurityUtils; import com.ruoyi.platform.constant.Constant; import com.ruoyi.platform.domain.AutoMl; +import com.ruoyi.platform.domain.AutoMlIns; import com.ruoyi.platform.mapper.AutoMLDao; +import com.ruoyi.platform.mapper.AutoMLInsDao; import com.ruoyi.platform.service.AutoMLService; -import com.ruoyi.system.api.model.LoginUser; +import com.ruoyi.platform.utils.DVCUtils; +import com.ruoyi.platform.utils.FileUtil; +import com.ruoyi.platform.utils.K8sClientUtil; +import io.kubernetes.client.openapi.models.V1Pod; +import org.apache.commons.io.FileUtils; import org.apache.commons.lang3.StringUtils; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.beans.factory.annotation.Value; import org.springframework.data.domain.Page; import org.springframework.data.domain.PageImpl; import org.springframework.data.domain.PageRequest; import org.springframework.stereotype.Service; +import org.springframework.web.multipart.MultipartFile; import javax.annotation.Resource; +import java.io.File; +import java.util.HashMap; import java.util.List; +import java.util.Map; +import java.util.concurrent.CompletableFuture; @Service("autoMLService") public class AutoMLServiceImpl implements AutoMLService { + @Value("${harbor.serviceNS}") + private String serviceNS; + @Value("${dockerpush.proxyUrl}") + private String proxyUrl; + @Value("${dockerpush.mountPath}") + private String mountPath; + @Value("${minio.pvcName}") + private String pvcName; + @Value("${automl.image}") + private String image; + @Value("${git.localPath}") + String localPath; + + private static final Logger logger = LoggerFactory.getLogger(ModelsServiceImpl.class); + @Resource private AutoMLDao autoMLDao; + @Resource + private AutoMLInsDao autoMLInsDao; + @Resource + private K8sClientUtil k8sClientUtil; + @Resource + private DVCUtils dvcUtils; @Override public Page queryByPage(String mlName, PageRequest pageRequest) { @@ -29,25 +64,29 @@ public class AutoMLServiceImpl implements AutoMLService { @Override public AutoMl save(AutoMl autoMl) { + String username = SecurityUtils.getLoginUser().getUsername(); + autoMl.setCreateBy(username); + autoMl.setUpdateBy(username); autoMLDao.save(autoMl); return autoMl; } @Override public String edit(AutoMl autoMl) { + String username = SecurityUtils.getLoginUser().getUsername(); + autoMl.setUpdateBy(username); autoMLDao.edit(autoMl); return "修改成功"; } @Override public String delete(Long id) { - AutoMl autoMl = autoMLDao.getAutoById(id); + AutoMl autoMl = autoMLDao.getAutoMlById(id); if (autoMl == null) { throw new RuntimeException("服务不存在"); } - LoginUser loginUser = SecurityUtils.getLoginUser(); - String username = loginUser.getUsername(); + String username = SecurityUtils.getLoginUser().getUsername(); String createBy = autoMl.getCreateBy(); if (!(StringUtils.equals(username, "admin") || StringUtils.equals(username, createBy))) { throw new RuntimeException("无权限删除该服务"); @@ -56,4 +95,180 @@ public class AutoMLServiceImpl implements AutoMLService { autoMl.setState(Constant.State_invalid); return autoMLDao.edit(autoMl) > 0 ? "删除成功" : "删除失败"; } + + @Override + public Page queryAutoMlInsByPage(Long autoMlId, PageRequest pageRequest) { + long total = autoMLInsDao.count(autoMlId); + List autoMlIns = autoMLInsDao.queryByPage(autoMlId, pageRequest); + return new PageImpl<>(autoMlIns, pageRequest, total); + } + + @Override + public AutoMlIns saveAutoMlIns(AutoMlIns autoMlIns) throws Exception { + String ci4sUsername = SecurityUtils.getLoginUser().getUsername(); + AutoMlIns oldAutoMlIns = autoMLInsDao.findByDatasetName(autoMlIns.getDatasetName(), autoMlIns.getAutoMlId()); + if (oldAutoMlIns != null) { + throw new RuntimeException("数据集名称已存在"); + } + + String sourcePath = autoMlIns.getVersionVo().getUrl(); + String rootPath = localPath + ci4sUsername + "/automl/" + autoMlIns.getAutoMlId() + "/" + autoMlIns.getDatasetName(); + dvcUtils.moveFiles(sourcePath, rootPath); + autoMlIns.setDataCsv(rootPath + "/" + autoMlIns.getVersionVo().getFileName()); + autoMlIns.setCreateBy(ci4sUsername); + autoMlIns.setUpdateBy(ci4sUsername); + autoMLInsDao.save(autoMlIns); + return autoMlIns; + } + + @Override + public String editAutoMlIns(AutoMlIns autoMlIns) throws Exception { + AutoMlIns oldAutoMlIns = autoMLInsDao.findByDatasetName(autoMlIns.getDatasetName(), autoMlIns.getAutoMlId()); + if (oldAutoMlIns != null && !oldAutoMlIns.getId().equals(autoMlIns.getId())) { + throw new RuntimeException("数据集名称已存在"); + } + String ci4sUsername = SecurityUtils.getLoginUser().getUsername(); + autoMlIns.setUpdateBy(ci4sUsername); + if (autoMlIns.getVersionVo() != null && StringUtils.isNotEmpty(autoMlIns.getVersionVo().getUrl())) { + String sourcePath = autoMlIns.getVersionVo().getUrl(); + String rootPath = localPath + ci4sUsername + "/automl/" + autoMlIns.getAutoMlId() + "/" + autoMlIns.getDatasetName(); + dvcUtils.moveFiles(sourcePath, rootPath); + autoMlIns.setDataCsv(rootPath + "/" + autoMlIns.getVersionVo().getFileName()); + } + autoMLInsDao.edit(autoMlIns); + return "修改成功"; + } + + @Override + public String deleteAutoMlIns(Long id) { + AutoMlIns autoMlIns = autoMLInsDao.getById(id); + if (autoMlIns == null) { + throw new RuntimeException("实例不存在"); + } + String username = SecurityUtils.getLoginUser().getUsername(); + String createBy = autoMlIns.getCreateBy(); + if (!(StringUtils.equals(username, "admin") || StringUtils.equals(username, createBy))) { + throw new RuntimeException("无权限删除该实例"); + } + autoMlIns.setState(Constant.State_invalid); + return autoMLInsDao.edit(autoMlIns) > 0 ? "删除成功" : "删除失败"; + } + + @Override + public Map upload(MultipartFile file, String uuid) throws Exception { + Map result = new HashMap<>(); + + String username = SecurityUtils.getLoginUser().getUsername(); + String fileName = file.getOriginalFilename(); + String path = localPath + "temp/" + username + "/automl_data/" + uuid; + long sizeInBytes = file.getSize(); + String formattedSize = FileUtil.formatFileSize(sizeInBytes); + File targetFile = new File(path, file.getOriginalFilename()); + // 确保目录存在 + targetFile.getParentFile().mkdirs(); + // 保存文件到目标路径 + FileUtils.copyInputStreamToFile(file.getInputStream(), targetFile); + // 返回上传文件的路径 + result.put("fileName", fileName); + result.put("url", path); // objectName根据实际情况定义 + result.put("fileSize", formattedSize); + return result; + } + + @Override + public String runAutoMlIns(Long id) throws Exception { + AutoMlIns autoMlIns = autoMLInsDao.getById(id); + if (autoMlIns == null) { + throw new Exception("开发环境配置不存在"); + } + + StringBuffer command = new StringBuffer(); + command.append("nohup python /opt/automl.py --task_type " + autoMlIns.getTaskType()); + if (StringUtils.isNotEmpty(autoMlIns.getDataCsv())) { + command.append(" --data_csv " + autoMlIns.getDataCsv()); + } else { + throw new Exception("训练数据为空"); + } + if (StringUtils.isNotEmpty(autoMlIns.getTargetColumns())) { + command.append(" --target_columns " + autoMlIns.getTargetColumns()); + } else { + throw new Exception("目标列为空"); + } + if (StringUtils.isNotEmpty(autoMlIns.getDatasetName())) { + command.append(" --dataset_name " + autoMlIns.getDatasetName()); + } else { + throw new Exception("数据集名称为空"); + } + +// String username = SecurityUtils.getLoginUser().getUsername().toLowerCase(); + String username = "admin"; + //构造pod名称 + String podName = username + "-autoMlIns-pod-" + id; + V1Pod pod = k8sClientUtil.createPodWithEnv(podName, serviceNS, proxyUrl, mountPath, pvcName, image); + + if (autoMlIns.getTimeLeftForThisTask() != null) { + command.append(" --time_left_for_this_task " + autoMlIns.getTimeLeftForThisTask()); + } + if (autoMlIns.getPerRunTimeLimit() != null) { + command.append(" --per_run_time_limit " + autoMlIns.getPerRunTimeLimit()); + } + if (autoMlIns.getEnsembleSize() != null) { + command.append(" --ensemble_size " + autoMlIns.getEnsembleSize()); + } + if (StringUtils.isNotEmpty(autoMlIns.getEnsembleClass())) { + command.append(" --ensemble_class " + autoMlIns.getEnsembleClass()); + } + if (autoMlIns.getEnsembleNbest() != null) { + command.append(" --ensemble_nbest " + autoMlIns.getEnsembleNbest()); + } + if (autoMlIns.getMaxModelsOnDisc() != null) { + command.append(" --max_models_on_disc " + autoMlIns.getMaxModelsOnDisc()); + } + if (autoMlIns.getSeed() != null) { + command.append(" --seed " + autoMlIns.getSeed()); + } + if (autoMlIns.getMemoryLimit() != null) { + command.append(" --memory_limit " + autoMlIns.getMemoryLimit()); + } + if (StringUtils.isNotEmpty(autoMlIns.getIncludeClassifier())) { + command.append(" --include_classifier " + autoMlIns.getIncludeClassifier()); + } + if (StringUtils.isNotEmpty(autoMlIns.getIncludeRegressor())) { + command.append(" --include_regressor " + autoMlIns.getIncludeRegressor()); + } + if (StringUtils.isNotEmpty(autoMlIns.getIncludeFeaturePreprocessor())) { + command.append(" --include_feature_preprocessor " + autoMlIns.getIncludeFeaturePreprocessor()); + } + if (StringUtils.isNotEmpty(autoMlIns.getExcludeClassifier())) { + command.append(" --exclude_classifier " + autoMlIns.getExcludeClassifier()); + } + if (StringUtils.isNotEmpty(autoMlIns.getExcludeRegressor())) { + command.append(" --exclude_regressor " + autoMlIns.getExcludeRegressor()); + } + if (StringUtils.isNotEmpty(autoMlIns.getExcludeFeaturePreprocessor())) { + command.append(" --exclude_feature_preprocessor " + autoMlIns.getExcludeFeaturePreprocessor()); + } + if (StringUtils.isNotEmpty(autoMlIns.getResamplingStrategy())) { + command.append(" --resampling_strategy " + autoMlIns.getResamplingStrategy()); + } + if (autoMlIns.getTrainSize() != null) { + command.append(" --train_size " + autoMlIns.getTrainSize()); + } + if (autoMlIns.getShuffle() != null) { + command.append(" --shuffle " + autoMlIns.getShuffle()); + } + if (autoMlIns.getFolds() != null) { + command.append(" --folds " + autoMlIns.getFolds()); + } + command.append(" &"); + CompletableFuture.supplyAsync(() -> { + try { + String log = k8sClientUtil.executeCommand(pod, String.valueOf(command)); + } catch (Exception e) { + logger.error(e.getMessage(), e); + } + return null; + }); + return "执行成功"; + } } diff --git a/ruoyi-modules/management-platform/src/main/java/com/ruoyi/platform/service/impl/JupyterServiceImpl.java b/ruoyi-modules/management-platform/src/main/java/com/ruoyi/platform/service/impl/JupyterServiceImpl.java index 87111365..02cb59a2 100644 --- a/ruoyi-modules/management-platform/src/main/java/com/ruoyi/platform/service/impl/JupyterServiceImpl.java +++ b/ruoyi-modules/management-platform/src/main/java/com/ruoyi/platform/service/impl/JupyterServiceImpl.java @@ -104,8 +104,6 @@ public class JupyterServiceImpl implements JupyterService { // String pvcName = loginUser.getUsername().toLowerCase() + "-editor-pvc"; // V1PersistentVolumeClaim pvc = k8sClientUtil.createPvc(namespace, pvcName, storage, storageClassName); - //TODO 设置镜像可配置,这里先用默认镜像启动pod - // 调用修改后的 createPod 方法,传入额外的参数 // Integer podPort = k8sClientUtil.createConfiguredPod(podName, namespace, port, mountPath, pvc, devEnvironment, minioPvcName, datasetPath, modelPath); Integer podPort = k8sClientUtil.createConfiguredPod(podName, namespace, port, mountPath, null, devEnvironment, minioPvcName, datasetPath, modelPath); diff --git a/ruoyi-modules/management-platform/src/main/resources/mapper/managementPlatform/AutoMLDaoMapper.xml b/ruoyi-modules/management-platform/src/main/resources/mapper/managementPlatform/AutoMLDaoMapper.xml index 788c2dec..8f4bc742 100644 --- a/ruoyi-modules/management-platform/src/main/resources/mapper/managementPlatform/AutoMLDaoMapper.xml +++ b/ruoyi-modules/management-platform/src/main/resources/mapper/managementPlatform/AutoMLDaoMapper.xml @@ -38,7 +38,7 @@ - select * from auto_ml where id = #{id} diff --git a/ruoyi-modules/management-platform/src/main/resources/mapper/managementPlatform/AutoMLInsDaoMapper.xml b/ruoyi-modules/management-platform/src/main/resources/mapper/managementPlatform/AutoMLInsDaoMapper.xml new file mode 100644 index 00000000..7f85a9d5 --- /dev/null +++ b/ruoyi-modules/management-platform/src/main/resources/mapper/managementPlatform/AutoMLInsDaoMapper.xml @@ -0,0 +1,132 @@ + + + + + insert into auto_ml_ins(auto_ml_id, task_type, dataset_name, time_left_for_this_task, per_run_time_limit, + ensemble_size, ensemble_class, ensemble_nbest, max_models_on_disc, seed, memory_limit, + include_classifier, include_feature_preprocessor, include_regressor, exclude_classifier, + exclude_regressor, exclude_feature_preprocessor, resampling_strategy, train_size, + shuffle, folds, delete_tmp_folder_after_terminate, data_csv, target_columns, create_by, + update_by) + values (#{autoMlIns.autoMlId}, #{autoMlIns.taskType}, #{autoMlIns.datasetName}, + #{autoMlIns.timeLeftForThisTask}, #{autoMlIns.perRunTimeLimit}, + #{autoMlIns.ensembleSize}, #{autoMlIns.ensembleClass}, #{autoMlIns.ensembleNbest}, + #{autoMlIns.maxModelsOnDisc}, #{autoMlIns.seed}, + #{autoMlIns.memoryLimit}, #{autoMlIns.includeClassifier}, #{autoMlIns.includeFeaturePreprocessor}, + #{autoMlIns.includeRegressor}, #{autoMlIns.excludeClassifier}, + #{autoMlIns.excludeRegressor}, #{autoMlIns.excludeFeaturePreprocessor}, #{autoMlIns.resamplingStrategy}, + #{autoMlIns.trainSize}, #{autoMlIns.shuffle}, + #{autoMlIns.folds}, #{autoMlIns.deleteTmpFolderAfterTerminate}, #{autoMlIns.dataCsv}, + #{autoMlIns.targetColumns}, #{autoMlIns.createBy}, #{autoMlIns.updateBy}) + + + + update auto_ml_ins + + + task_type = #{autoMlIns.taskType}, + + + dataset_name = #{autoMlIns.datasetName}, + + + time_left_for_this_task = #{autoMlIns.timeLeftForThisTask}, + + + per_run_time_limit = #{autoMlIns.perRunTimeLimit}, + + + ensemble_size = #{autoMlIns.ensembleSize}, + + + ensemble_class = #{autoMlIns.ensembleClass}, + + + ensemble_nbest = #{autoMlIns.ensembleNbest}, + + + max_models_on_disc = #{autoMlIns.maxModelsOnDisc}, + + + seed = #{autoMlIns.seed}, + + + memory_limit = #{autoMlIns.memoryLimit}, + + + include_classifier = #{autoMlIns.includeClassifier}, + + + include_feature_preprocessor = #{autoMlIns.includeFeaturePreprocessor}, + + + include_regressor = #{autoMlIns.includeRegressor}, + + + exclude_classifier = #{autoMlIns.excludeClassifier}, + + + exclude_regressor = #{autoMlIns.excludeRegressor}, + + + exclude_feature_preprocessor = #{autoMlIns.excludeFeaturePreprocessor}, + + + resampling_strategy = #{autoMlIns.resamplingStrategy}, + + + train_size = #{autoMlIns.trainSize}, + + + shuffle = #{autoMlIns.shuffle}, + + + folds = #{autoMlIns.folds}, + + + delete_tmp_folder_after_terminate = #{autoMlIns.deleteTmpFolderAfterTerminate}, + + + data_csv = #{autoMlIns.dataCsv}, + + + target_columns = #{autoMlIns.targetColumns}, + + + state = #{autoMlIns.state}, + + + update_by = #{autoMlIns.updateBy}, + + + where id = #{autoMlIns.id} + + + + + + + + + + + + + state = 1 and auto_ml_id = #{autoMlId} + + + \ No newline at end of file From 9eeed3b52dbf8e1ec2f805bc858ff5e8be99544b Mon Sep 17 00:00:00 2001 From: cp3hnu Date: Mon, 18 Nov 2024 08:57:26 +0800 Subject: [PATCH 06/59] =?UTF-8?q?fix:=20=E6=B5=81=E6=B0=B4=E7=BA=BF?= =?UTF-8?q?=E5=A6=82=E6=9E=9C=E6=B2=A1=E6=9C=89=E8=BE=93=E5=85=A5=E5=8F=82?= =?UTF-8?q?=E6=95=B0=EF=BC=8C=E9=9A=90=E8=97=8F=E5=85=B6=E6=A0=87=E9=A2=98?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../components/PipelineNodeDrawer/index.tsx | 112 ++++++++++-------- 1 file changed, 61 insertions(+), 51 deletions(-) diff --git a/react-ui/src/pages/Pipeline/components/PipelineNodeDrawer/index.tsx b/react-ui/src/pages/Pipeline/components/PipelineNodeDrawer/index.tsx index 2eae5c64..ba1fe04d 100644 --- a/react-ui/src/pages/Pipeline/components/PipelineNodeDrawer/index.tsx +++ b/react-ui/src/pages/Pipeline/components/PipelineNodeDrawer/index.tsx @@ -491,59 +491,69 @@ const PipelineNodeParameter = forwardRef(({ onFormChange }: PipelineNodeParamete ))} -
- -
- {inParametersList.map((item) => ( - -
- - {item.value.type === 'select' ? ( - - ) : ( - - )} + {/* 输入参数 */} + {inParametersList.length > 0 && ( + <> +
+ +
+ {inParametersList.map((item) => ( + +
+ + {item.value.type === 'select' ? ( + + ) : ( + + )} + + {item.value.type === 'ref' && ( + + + + )} +
- {item.value.type === 'ref' && ( - - - - )} + ))} + + )} + {/* 输出参数 */} + {outParametersList.length > 0 && ( + <> +
+
-
- ))} -
- -
- {outParametersList.map((item) => ( - - - - ))} + {outParametersList.map((item) => ( + + + + ))} + + )} ); From b07f850c19447b6e986e1bbc7ab62ced772a337e Mon Sep 17 00:00:00 2001 From: cp3hnu Date: Tue, 19 Nov 2024 17:08:48 +0800 Subject: [PATCH 07/59] =?UTF-8?q?fix:=20=E4=BF=AE=E5=A4=8D=E8=8F=9C?= =?UTF-8?q?=E5=8D=95=E5=8D=87=E7=BA=A7=E4=B9=8B=E5=90=8E=E7=9A=84bug?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- react-ui/src/overrides.less | 8 ++++++++ react-ui/src/styles/menu.less | 33 +++++++++++++++++---------------- 2 files changed, 25 insertions(+), 16 deletions(-) diff --git a/react-ui/src/overrides.less b/react-ui/src/overrides.less index f676890e..e129b4a7 100644 --- a/react-ui/src/overrides.less +++ b/react-ui/src/overrides.less @@ -204,6 +204,14 @@ margin-inline-start: 12px; } +.ant-pro-layout .ant-pro-sider-logo-collapsed { + padding: 16px 12px; +} + +.ant-pro-base-menu-inline .ant-pro-base-menu-inline-menu-item { + transition: padding 0.1s !important; +} + // PageContainer 里的 ProTable 只滑动内容区域 .system-menu.ant-pro-page-container { height: 100%; diff --git a/react-ui/src/styles/menu.less b/react-ui/src/styles/menu.less index 1d547ff1..02fe0c17 100644 --- a/react-ui/src/styles/menu.less +++ b/react-ui/src/styles/menu.less @@ -15,16 +15,6 @@ display: none !important; margin-left: 0 !important; } - - &:hover { - .anticon.kf-menu-item__default-icon { - display: none !important; - } - .anticon.kf-menu-item__active-icon { - display: inline !important; - opacity: 1; - } - } } } @@ -42,18 +32,29 @@ } } +.ant-menu-submenu .ant-menu-submenu-title:hover, +.ant-menu-item:hover { + color: @primary-color !important; + + .kf-menu-item { + .anticon.kf-menu-item__default-icon { + display: none !important; + } + + .anticon.kf-menu-item__active-icon { + display: inline !important; + opacity: 1; + } + } +} + .ant-pro-base-menu-vertical-collapsed { .kf-menu-item { justify-content: center; + width: 100%; .kf-menu-item__name { display: none !important; } } } - -.ant-menu-submenu { - .ant-menu-submenu-title:hover { - color: @primary-color !important; - } -} From ce32d520e7259b7b68c0f989f1319a7c847167b6 Mon Sep 17 00:00:00 2001 From: chenzhihang <709011834@qq.com> Date: Tue, 19 Nov 2024 17:30:16 +0800 Subject: [PATCH 08/59] =?UTF-8?q?=E8=87=AA=E5=8A=A8=E6=9C=BA=E5=99=A8?= =?UTF-8?q?=E5=AD=A6=E4=B9=A0=E5=BC=80=E5=8F=91?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../controller/autoML/AutoMLController.java | 32 +-- .../com/ruoyi/platform/domain/AutoMl.java | 57 ++++++ .../com/ruoyi/platform/domain/AutoMlIns.java | 77 -------- .../com/ruoyi/platform/mapper/AutoMLDao.java | 2 + .../ruoyi/platform/mapper/AutoMLInsDao.java | 21 -- .../ruoyi/platform/service/AutoMLService.java | 16 +- .../service/impl/AutoMLServiceImpl.java | 184 +++++++----------- .../managementPlatform/AutoMLDaoMapper.xml | 104 +++++++++- .../managementPlatform/AutoMLInsDaoMapper.xml | 132 ------------- 9 files changed, 234 insertions(+), 391 deletions(-) delete mode 100644 ruoyi-modules/management-platform/src/main/java/com/ruoyi/platform/domain/AutoMlIns.java delete mode 100644 ruoyi-modules/management-platform/src/main/java/com/ruoyi/platform/mapper/AutoMLInsDao.java delete mode 100644 ruoyi-modules/management-platform/src/main/resources/mapper/managementPlatform/AutoMLInsDaoMapper.xml diff --git a/ruoyi-modules/management-platform/src/main/java/com/ruoyi/platform/controller/autoML/AutoMLController.java b/ruoyi-modules/management-platform/src/main/java/com/ruoyi/platform/controller/autoML/AutoMLController.java index 47e291ee..4bd1a36e 100644 --- a/ruoyi-modules/management-platform/src/main/java/com/ruoyi/platform/controller/autoML/AutoMLController.java +++ b/ruoyi-modules/management-platform/src/main/java/com/ruoyi/platform/controller/autoML/AutoMLController.java @@ -4,7 +4,6 @@ import com.ruoyi.common.core.web.controller.BaseController; import com.ruoyi.common.core.web.domain.AjaxResult; import com.ruoyi.common.core.web.domain.GenericsAjaxResult; import com.ruoyi.platform.domain.AutoMl; -import com.ruoyi.platform.domain.AutoMlIns; import com.ruoyi.platform.service.AutoMLService; import io.swagger.annotations.Api; import io.swagger.annotations.ApiOperation; @@ -34,13 +33,13 @@ public class AutoMLController extends BaseController { @PostMapping @ApiOperation("新增自动机器学习") - public GenericsAjaxResult addAutoMl(@RequestBody AutoMl autoMl) { + public GenericsAjaxResult addAutoMl(@RequestBody AutoMl autoMl) throws Exception { return genericsSuccess(this.autoMLService.save(autoMl)); } @PutMapping @ApiOperation("编辑自动机器学习") - public GenericsAjaxResult editAutoMl(@RequestBody AutoMl autoMl) { + public GenericsAjaxResult editAutoMl(@RequestBody AutoMl autoMl) throws Exception { return genericsSuccess(this.autoMLService.edit(autoMl)); } @@ -50,33 +49,6 @@ public class AutoMLController extends BaseController { return genericsSuccess(this.autoMLService.delete(id)); } - @GetMapping("/autoMlIns") - @ApiOperation("分页查询自动机器学习实例") - public GenericsAjaxResult> queryByPage(@RequestParam("page") int page, - @RequestParam("size") int size, - @RequestParam("auto_ml_id") Long autoMlId) { - PageRequest pageRequest = PageRequest.of(page, size); - return genericsSuccess(this.autoMLService.queryAutoMlInsByPage(autoMlId, pageRequest)); - } - - @PostMapping("/autoMlIns") - @ApiOperation("新增自动机器学习实例") - public GenericsAjaxResult addAutoMlIns(@RequestBody AutoMlIns autoMlIns) throws Exception { - return genericsSuccess(this.autoMLService.saveAutoMlIns(autoMlIns)); - } - - @PutMapping("/autoMlIns") - @ApiOperation("编辑自动机器学习实例") - public GenericsAjaxResult editAutoMlIns(@RequestBody AutoMlIns autoMlIns) throws Exception { - return genericsSuccess(this.autoMLService.editAutoMlIns(autoMlIns)); - } - - @DeleteMapping("/autoMlIns/{id}") - @ApiOperation("删除自动机器学习实例") - public GenericsAjaxResult deleteAutoMlIns(@PathVariable("id") Long id) { - return genericsSuccess(this.autoMLService.deleteAutoMlIns(id)); - } - @CrossOrigin(origins = "*", allowedHeaders = "*") @PostMapping("/upload") @ApiOperation(value = "上传数据文件csv", notes = "上传数据文件csv,并将信息存入数据库。") diff --git a/ruoyi-modules/management-platform/src/main/java/com/ruoyi/platform/domain/AutoMl.java b/ruoyi-modules/management-platform/src/main/java/com/ruoyi/platform/domain/AutoMl.java index 409e5726..43b8255f 100644 --- a/ruoyi-modules/management-platform/src/main/java/com/ruoyi/platform/domain/AutoMl.java +++ b/ruoyi-modules/management-platform/src/main/java/com/ruoyi/platform/domain/AutoMl.java @@ -1,20 +1,74 @@ package com.ruoyi.platform.domain; +import com.baomidou.mybatisplus.annotation.TableField; import com.fasterxml.jackson.databind.PropertyNamingStrategy; import com.fasterxml.jackson.databind.annotation.JsonNaming; +import com.ruoyi.platform.vo.VersionVo; +import io.swagger.annotations.ApiModel; +import io.swagger.annotations.ApiModelProperty; import lombok.Data; import java.util.Date; @Data @JsonNaming(PropertyNamingStrategy.SnakeCaseStrategy.class) +@ApiModel(description = "自动机器学习") public class AutoMl { private Long id; + @ApiModelProperty(value = "实验名称") private String mlName; + @ApiModelProperty(value = "实验描述") private String mlDescription; + @ApiModelProperty(value = "任务类型") + private String taskType; + + private String datasetName; + + private Integer timeLeftForThisTask; + + private Integer perRunTimeLimit; + + private Integer ensembleSize; + + private String ensembleClass; + + private Integer ensembleNbest; + + private Integer maxModelsOnDisc; + + private Integer seed; + + private Integer memoryLimit; + + private String includeClassifier; + + private String includeFeaturePreprocessor; + + private String includeRegressor; + + private String excludeClassifier; + + private String excludeRegressor; + + private String excludeFeaturePreprocessor; + + private String resamplingStrategy; + + private Float trainSize; + + private Boolean shuffle; + + private Integer folds; + + private Boolean deleteTmpFolderAfterTerminate; + + private String dataCsv; + + private String targetColumns; + private Integer state; private String runState; @@ -28,4 +82,7 @@ public class AutoMl { private String updateBy; private Date updateTime; + + @TableField(exist = false) + private VersionVo versionVo; } diff --git a/ruoyi-modules/management-platform/src/main/java/com/ruoyi/platform/domain/AutoMlIns.java b/ruoyi-modules/management-platform/src/main/java/com/ruoyi/platform/domain/AutoMlIns.java deleted file mode 100644 index 653c212d..00000000 --- a/ruoyi-modules/management-platform/src/main/java/com/ruoyi/platform/domain/AutoMlIns.java +++ /dev/null @@ -1,77 +0,0 @@ -package com.ruoyi.platform.domain; - -import com.baomidou.mybatisplus.annotation.TableField; -import com.fasterxml.jackson.databind.PropertyNamingStrategy; -import com.fasterxml.jackson.databind.annotation.JsonNaming; -import com.ruoyi.platform.vo.VersionVo; -import lombok.Data; - -import java.util.Date; - -@Data -@JsonNaming(PropertyNamingStrategy.SnakeCaseStrategy.class) -public class AutoMlIns { - private Long id; - - private Long autoMlId; - - private String taskType; - - private String datasetName; - - private Integer timeLeftForThisTask; - - private Integer perRunTimeLimit; - - private Integer ensembleSize; - - private String ensembleClass; - - private Integer ensembleNbest; - - private Integer maxModelsOnDisc; - - private Integer seed; - - private Integer memoryLimit; - - private String includeClassifier; - - private String includeFeaturePreprocessor; - - private String includeRegressor; - - private String excludeClassifier; - - private String excludeRegressor; - - private String excludeFeaturePreprocessor; - - private String resamplingStrategy; - - private Float trainSize; - - private Boolean shuffle; - - private Integer folds; - - private Boolean deleteTmpFolderAfterTerminate; - - private String dataCsv; - - private String targetColumns; - - private Integer state; - - private String createBy; - - private String updateBy; - - private Date createTime; - - private Date updateTime; - - @TableField(exist = false) - private VersionVo versionVo; - -} diff --git a/ruoyi-modules/management-platform/src/main/java/com/ruoyi/platform/mapper/AutoMLDao.java b/ruoyi-modules/management-platform/src/main/java/com/ruoyi/platform/mapper/AutoMLDao.java index b10f5235..f99f2156 100644 --- a/ruoyi-modules/management-platform/src/main/java/com/ruoyi/platform/mapper/AutoMLDao.java +++ b/ruoyi-modules/management-platform/src/main/java/com/ruoyi/platform/mapper/AutoMLDao.java @@ -13,6 +13,8 @@ public interface AutoMLDao { AutoMl getAutoMlById(@Param("id") Long id); + AutoMl getAutoMlByName(@Param("mlName") String mlName); + int save(@Param("autoMl") AutoMl autoMl); int edit(@Param("autoMl") AutoMl autoMl); diff --git a/ruoyi-modules/management-platform/src/main/java/com/ruoyi/platform/mapper/AutoMLInsDao.java b/ruoyi-modules/management-platform/src/main/java/com/ruoyi/platform/mapper/AutoMLInsDao.java deleted file mode 100644 index a541fc53..00000000 --- a/ruoyi-modules/management-platform/src/main/java/com/ruoyi/platform/mapper/AutoMLInsDao.java +++ /dev/null @@ -1,21 +0,0 @@ -package com.ruoyi.platform.mapper; - -import com.ruoyi.platform.domain.AutoMlIns; -import org.apache.ibatis.annotations.Param; -import org.springframework.data.domain.Pageable; - -import java.util.List; - -public interface AutoMLInsDao { - long count(@Param("autoMlId") Long autoMlId); - - List queryByPage(@Param("autoMlId") Long autoMlId, @Param("pageable") Pageable pageable); - - int save(@Param("autoMlIns") AutoMlIns autoMlIns); - - int edit(@Param("autoMlIns") AutoMlIns autoMlIns); - - AutoMlIns getById(@Param("id") Long id); - - AutoMlIns findByDatasetName(@Param("datasetName") String datasetName, @Param("autoMlId") Long autoMlId); -} diff --git a/ruoyi-modules/management-platform/src/main/java/com/ruoyi/platform/service/AutoMLService.java b/ruoyi-modules/management-platform/src/main/java/com/ruoyi/platform/service/AutoMLService.java index d475d5a3..6d5fd355 100644 --- a/ruoyi-modules/management-platform/src/main/java/com/ruoyi/platform/service/AutoMLService.java +++ b/ruoyi-modules/management-platform/src/main/java/com/ruoyi/platform/service/AutoMLService.java @@ -1,32 +1,22 @@ package com.ruoyi.platform.service; import com.ruoyi.platform.domain.AutoMl; -import com.ruoyi.platform.domain.AutoMlIns; import org.springframework.data.domain.Page; import org.springframework.data.domain.PageRequest; import org.springframework.web.multipart.MultipartFile; -import java.util.List; import java.util.Map; public interface AutoMLService { Page queryByPage(String mlName, PageRequest pageRequest); - AutoMl save(AutoMl autoMl); + AutoMl save(AutoMl autoMl) throws Exception; - String edit(AutoMl autoMl); + String edit(AutoMl autoMl) throws Exception; String delete(Long id); - Page queryAutoMlInsByPage(Long autoMlId, PageRequest pageRequest); - - AutoMlIns saveAutoMlIns(AutoMlIns autoMlIns) throws Exception; - - String editAutoMlIns(AutoMlIns autoMlIns) throws Exception; - - String deleteAutoMlIns(Long id); - - Map upload(MultipartFile file, String uuid) throws Exception; + Map upload(MultipartFile file, String uuid) throws Exception; String runAutoMlIns(Long id) throws Exception; } diff --git a/ruoyi-modules/management-platform/src/main/java/com/ruoyi/platform/service/impl/AutoMLServiceImpl.java b/ruoyi-modules/management-platform/src/main/java/com/ruoyi/platform/service/impl/AutoMLServiceImpl.java index 19bb878b..9cdd3b40 100644 --- a/ruoyi-modules/management-platform/src/main/java/com/ruoyi/platform/service/impl/AutoMLServiceImpl.java +++ b/ruoyi-modules/management-platform/src/main/java/com/ruoyi/platform/service/impl/AutoMLServiceImpl.java @@ -3,9 +3,7 @@ package com.ruoyi.platform.service.impl; import com.ruoyi.common.security.utils.SecurityUtils; import com.ruoyi.platform.constant.Constant; import com.ruoyi.platform.domain.AutoMl; -import com.ruoyi.platform.domain.AutoMlIns; import com.ruoyi.platform.mapper.AutoMLDao; -import com.ruoyi.platform.mapper.AutoMLInsDao; import com.ruoyi.platform.service.AutoMLService; import com.ruoyi.platform.utils.DVCUtils; import com.ruoyi.platform.utils.FileUtil; @@ -49,8 +47,6 @@ public class AutoMLServiceImpl implements AutoMLService { @Resource private AutoMLDao autoMLDao; @Resource - private AutoMLInsDao autoMLInsDao; - @Resource private K8sClientUtil k8sClientUtil; @Resource private DVCUtils dvcUtils; @@ -63,18 +59,40 @@ public class AutoMLServiceImpl implements AutoMLService { } @Override - public AutoMl save(AutoMl autoMl) { + public AutoMl save(AutoMl autoMl) throws Exception { + AutoMl autoMlByName = autoMLDao.getAutoMlByName(autoMl.getMlName()); + if (autoMlByName != null) { + throw new RuntimeException("实验名称已存在"); + } + String username = SecurityUtils.getLoginUser().getUsername(); autoMl.setCreateBy(username); autoMl.setUpdateBy(username); + + String sourcePath = autoMl.getVersionVo().getUrl(); + String rootPath = localPath + username + "/automl/" + autoMl.getMlName() + "/" + autoMl.getDatasetName(); + dvcUtils.moveFiles(sourcePath, rootPath); + autoMl.setDataCsv(rootPath + "/" + autoMl.getVersionVo().getFileName()); autoMLDao.save(autoMl); return autoMl; } @Override - public String edit(AutoMl autoMl) { + public String edit(AutoMl autoMl) throws Exception { + AutoMl oldAutoMl = autoMLDao.getAutoMlByName(autoMl.getMlName()); + if (oldAutoMl != null && !oldAutoMl.getId().equals(autoMl.getId())) { + throw new RuntimeException("实验名称已存在"); + } + String username = SecurityUtils.getLoginUser().getUsername(); autoMl.setUpdateBy(username); + if (autoMl.getVersionVo() != null && StringUtils.isNotEmpty(autoMl.getVersionVo().getUrl())) { + String sourcePath = autoMl.getVersionVo().getUrl(); + String rootPath = localPath + username + "/automl/" + autoMl.getMlName() + "/" + autoMl.getDatasetName(); + dvcUtils.moveFiles(sourcePath, rootPath); + autoMl.setDataCsv(rootPath + "/" + autoMl.getVersionVo().getFileName()); + } + autoMLDao.edit(autoMl); return "修改成功"; } @@ -86,7 +104,8 @@ public class AutoMLServiceImpl implements AutoMLService { throw new RuntimeException("服务不存在"); } - String username = SecurityUtils.getLoginUser().getUsername(); +// String username = SecurityUtils.getLoginUser().getUsername(); + String username = "admin"; String createBy = autoMl.getCreateBy(); if (!(StringUtils.equals(username, "admin") || StringUtils.equals(username, createBy))) { throw new RuntimeException("无权限删除该服务"); @@ -96,69 +115,12 @@ public class AutoMLServiceImpl implements AutoMLService { return autoMLDao.edit(autoMl) > 0 ? "删除成功" : "删除失败"; } - @Override - public Page queryAutoMlInsByPage(Long autoMlId, PageRequest pageRequest) { - long total = autoMLInsDao.count(autoMlId); - List autoMlIns = autoMLInsDao.queryByPage(autoMlId, pageRequest); - return new PageImpl<>(autoMlIns, pageRequest, total); - } - - @Override - public AutoMlIns saveAutoMlIns(AutoMlIns autoMlIns) throws Exception { - String ci4sUsername = SecurityUtils.getLoginUser().getUsername(); - AutoMlIns oldAutoMlIns = autoMLInsDao.findByDatasetName(autoMlIns.getDatasetName(), autoMlIns.getAutoMlId()); - if (oldAutoMlIns != null) { - throw new RuntimeException("数据集名称已存在"); - } - - String sourcePath = autoMlIns.getVersionVo().getUrl(); - String rootPath = localPath + ci4sUsername + "/automl/" + autoMlIns.getAutoMlId() + "/" + autoMlIns.getDatasetName(); - dvcUtils.moveFiles(sourcePath, rootPath); - autoMlIns.setDataCsv(rootPath + "/" + autoMlIns.getVersionVo().getFileName()); - autoMlIns.setCreateBy(ci4sUsername); - autoMlIns.setUpdateBy(ci4sUsername); - autoMLInsDao.save(autoMlIns); - return autoMlIns; - } - - @Override - public String editAutoMlIns(AutoMlIns autoMlIns) throws Exception { - AutoMlIns oldAutoMlIns = autoMLInsDao.findByDatasetName(autoMlIns.getDatasetName(), autoMlIns.getAutoMlId()); - if (oldAutoMlIns != null && !oldAutoMlIns.getId().equals(autoMlIns.getId())) { - throw new RuntimeException("数据集名称已存在"); - } - String ci4sUsername = SecurityUtils.getLoginUser().getUsername(); - autoMlIns.setUpdateBy(ci4sUsername); - if (autoMlIns.getVersionVo() != null && StringUtils.isNotEmpty(autoMlIns.getVersionVo().getUrl())) { - String sourcePath = autoMlIns.getVersionVo().getUrl(); - String rootPath = localPath + ci4sUsername + "/automl/" + autoMlIns.getAutoMlId() + "/" + autoMlIns.getDatasetName(); - dvcUtils.moveFiles(sourcePath, rootPath); - autoMlIns.setDataCsv(rootPath + "/" + autoMlIns.getVersionVo().getFileName()); - } - autoMLInsDao.edit(autoMlIns); - return "修改成功"; - } - - @Override - public String deleteAutoMlIns(Long id) { - AutoMlIns autoMlIns = autoMLInsDao.getById(id); - if (autoMlIns == null) { - throw new RuntimeException("实例不存在"); - } - String username = SecurityUtils.getLoginUser().getUsername(); - String createBy = autoMlIns.getCreateBy(); - if (!(StringUtils.equals(username, "admin") || StringUtils.equals(username, createBy))) { - throw new RuntimeException("无权限删除该实例"); - } - autoMlIns.setState(Constant.State_invalid); - return autoMLInsDao.edit(autoMlIns) > 0 ? "删除成功" : "删除失败"; - } - @Override public Map upload(MultipartFile file, String uuid) throws Exception { Map result = new HashMap<>(); - String username = SecurityUtils.getLoginUser().getUsername(); +// String username = SecurityUtils.getLoginUser().getUsername(); + String username = "admin"; String fileName = file.getOriginalFilename(); String path = localPath + "temp/" + username + "/automl_data/" + uuid; long sizeInBytes = file.getSize(); @@ -177,25 +139,25 @@ public class AutoMLServiceImpl implements AutoMLService { @Override public String runAutoMlIns(Long id) throws Exception { - AutoMlIns autoMlIns = autoMLInsDao.getById(id); - if (autoMlIns == null) { + AutoMl autoMl = autoMLDao.getAutoMlById(id); + if (autoMl == null) { throw new Exception("开发环境配置不存在"); } StringBuffer command = new StringBuffer(); - command.append("nohup python /opt/automl.py --task_type " + autoMlIns.getTaskType()); - if (StringUtils.isNotEmpty(autoMlIns.getDataCsv())) { - command.append(" --data_csv " + autoMlIns.getDataCsv()); + command.append("nohup python /opt/automl.py --task_type " + autoMl.getTaskType()); + if (StringUtils.isNotEmpty(autoMl.getDataCsv())) { + command.append(" --data_csv " + autoMl.getDataCsv()); } else { throw new Exception("训练数据为空"); } - if (StringUtils.isNotEmpty(autoMlIns.getTargetColumns())) { - command.append(" --target_columns " + autoMlIns.getTargetColumns()); + if (StringUtils.isNotEmpty(autoMl.getTargetColumns())) { + command.append(" --target_columns " + autoMl.getTargetColumns()); } else { throw new Exception("目标列为空"); } - if (StringUtils.isNotEmpty(autoMlIns.getDatasetName())) { - command.append(" --dataset_name " + autoMlIns.getDatasetName()); + if (StringUtils.isNotEmpty(autoMl.getDatasetName())) { + command.append(" --dataset_name " + autoMl.getDatasetName()); } else { throw new Exception("数据集名称为空"); } @@ -206,59 +168,59 @@ public class AutoMLServiceImpl implements AutoMLService { String podName = username + "-autoMlIns-pod-" + id; V1Pod pod = k8sClientUtil.createPodWithEnv(podName, serviceNS, proxyUrl, mountPath, pvcName, image); - if (autoMlIns.getTimeLeftForThisTask() != null) { - command.append(" --time_left_for_this_task " + autoMlIns.getTimeLeftForThisTask()); + if (autoMl.getTimeLeftForThisTask() != null) { + command.append(" --time_left_for_this_task " + autoMl.getTimeLeftForThisTask()); } - if (autoMlIns.getPerRunTimeLimit() != null) { - command.append(" --per_run_time_limit " + autoMlIns.getPerRunTimeLimit()); + if (autoMl.getPerRunTimeLimit() != null) { + command.append(" --per_run_time_limit " + autoMl.getPerRunTimeLimit()); } - if (autoMlIns.getEnsembleSize() != null) { - command.append(" --ensemble_size " + autoMlIns.getEnsembleSize()); + if (autoMl.getEnsembleSize() != null) { + command.append(" --ensemble_size " + autoMl.getEnsembleSize()); } - if (StringUtils.isNotEmpty(autoMlIns.getEnsembleClass())) { - command.append(" --ensemble_class " + autoMlIns.getEnsembleClass()); + if (StringUtils.isNotEmpty(autoMl.getEnsembleClass())) { + command.append(" --ensemble_class " + autoMl.getEnsembleClass()); } - if (autoMlIns.getEnsembleNbest() != null) { - command.append(" --ensemble_nbest " + autoMlIns.getEnsembleNbest()); + if (autoMl.getEnsembleNbest() != null) { + command.append(" --ensemble_nbest " + autoMl.getEnsembleNbest()); } - if (autoMlIns.getMaxModelsOnDisc() != null) { - command.append(" --max_models_on_disc " + autoMlIns.getMaxModelsOnDisc()); + if (autoMl.getMaxModelsOnDisc() != null) { + command.append(" --max_models_on_disc " + autoMl.getMaxModelsOnDisc()); } - if (autoMlIns.getSeed() != null) { - command.append(" --seed " + autoMlIns.getSeed()); + if (autoMl.getSeed() != null) { + command.append(" --seed " + autoMl.getSeed()); } - if (autoMlIns.getMemoryLimit() != null) { - command.append(" --memory_limit " + autoMlIns.getMemoryLimit()); + if (autoMl.getMemoryLimit() != null) { + command.append(" --memory_limit " + autoMl.getMemoryLimit()); } - if (StringUtils.isNotEmpty(autoMlIns.getIncludeClassifier())) { - command.append(" --include_classifier " + autoMlIns.getIncludeClassifier()); + if (StringUtils.isNotEmpty(autoMl.getIncludeClassifier())) { + command.append(" --include_classifier " + autoMl.getIncludeClassifier()); } - if (StringUtils.isNotEmpty(autoMlIns.getIncludeRegressor())) { - command.append(" --include_regressor " + autoMlIns.getIncludeRegressor()); + if (StringUtils.isNotEmpty(autoMl.getIncludeRegressor())) { + command.append(" --include_regressor " + autoMl.getIncludeRegressor()); } - if (StringUtils.isNotEmpty(autoMlIns.getIncludeFeaturePreprocessor())) { - command.append(" --include_feature_preprocessor " + autoMlIns.getIncludeFeaturePreprocessor()); + if (StringUtils.isNotEmpty(autoMl.getIncludeFeaturePreprocessor())) { + command.append(" --include_feature_preprocessor " + autoMl.getIncludeFeaturePreprocessor()); } - if (StringUtils.isNotEmpty(autoMlIns.getExcludeClassifier())) { - command.append(" --exclude_classifier " + autoMlIns.getExcludeClassifier()); + if (StringUtils.isNotEmpty(autoMl.getExcludeClassifier())) { + command.append(" --exclude_classifier " + autoMl.getExcludeClassifier()); } - if (StringUtils.isNotEmpty(autoMlIns.getExcludeRegressor())) { - command.append(" --exclude_regressor " + autoMlIns.getExcludeRegressor()); + if (StringUtils.isNotEmpty(autoMl.getExcludeRegressor())) { + command.append(" --exclude_regressor " + autoMl.getExcludeRegressor()); } - if (StringUtils.isNotEmpty(autoMlIns.getExcludeFeaturePreprocessor())) { - command.append(" --exclude_feature_preprocessor " + autoMlIns.getExcludeFeaturePreprocessor()); + if (StringUtils.isNotEmpty(autoMl.getExcludeFeaturePreprocessor())) { + command.append(" --exclude_feature_preprocessor " + autoMl.getExcludeFeaturePreprocessor()); } - if (StringUtils.isNotEmpty(autoMlIns.getResamplingStrategy())) { - command.append(" --resampling_strategy " + autoMlIns.getResamplingStrategy()); + if (StringUtils.isNotEmpty(autoMl.getResamplingStrategy())) { + command.append(" --resampling_strategy " + autoMl.getResamplingStrategy()); } - if (autoMlIns.getTrainSize() != null) { - command.append(" --train_size " + autoMlIns.getTrainSize()); + if (autoMl.getTrainSize() != null) { + command.append(" --train_size " + autoMl.getTrainSize()); } - if (autoMlIns.getShuffle() != null) { - command.append(" --shuffle " + autoMlIns.getShuffle()); + if (autoMl.getShuffle() != null) { + command.append(" --shuffle " + autoMl.getShuffle()); } - if (autoMlIns.getFolds() != null) { - command.append(" --folds " + autoMlIns.getFolds()); + if (autoMl.getFolds() != null) { + command.append(" --folds " + autoMl.getFolds()); } command.append(" &"); CompletableFuture.supplyAsync(() -> { diff --git a/ruoyi-modules/management-platform/src/main/resources/mapper/managementPlatform/AutoMLDaoMapper.xml b/ruoyi-modules/management-platform/src/main/resources/mapper/managementPlatform/AutoMLDaoMapper.xml index 8f4bc742..0af3beb7 100644 --- a/ruoyi-modules/management-platform/src/main/resources/mapper/managementPlatform/AutoMLDaoMapper.xml +++ b/ruoyi-modules/management-platform/src/main/resources/mapper/managementPlatform/AutoMLDaoMapper.xml @@ -2,8 +2,23 @@ - insert into auto_ml(ml_name, ml_description) - values (#{autoMl.mlName}, #{autoMl.mlDescription}) + insert into auto_ml(ml_name, ml_description, task_type, dataset_name, time_left_for_this_task, + per_run_time_limit, ensemble_size, ensemble_class, ensemble_nbest, max_models_on_disc, seed, + memory_limit, + include_classifier, include_feature_preprocessor, include_regressor, exclude_classifier, + exclude_regressor, exclude_feature_preprocessor, resampling_strategy, train_size, + shuffle, folds, data_csv, target_columns, create_by, + update_by) + values (#{autoMl.mlName}, #{autoMl.mlDescription}, #{autoMl.taskType}, #{autoMl.datasetName}, + #{autoMl.timeLeftForThisTask}, #{autoMl.perRunTimeLimit}, + #{autoMl.ensembleSize}, #{autoMl.ensembleClass}, #{autoMl.ensembleNbest}, + #{autoMl.maxModelsOnDisc}, #{autoMl.seed}, + #{autoMl.memoryLimit}, #{autoMl.includeClassifier}, #{autoMl.includeFeaturePreprocessor}, + #{autoMl.includeRegressor}, #{autoMl.excludeClassifier}, + #{autoMl.excludeRegressor}, #{autoMl.excludeFeaturePreprocessor}, #{autoMl.resamplingStrategy}, + #{autoMl.trainSize}, #{autoMl.shuffle}, + #{autoMl.folds}, #{autoMl.dataCsv}, + #{autoMl.targetColumns}, #{autoMl.createBy}, #{autoMl.updateBy}) @@ -15,11 +30,77 @@ ml_description = #{autoMl.mlDescription}, - - run_state = #{autoMl.runState}, + + + + + + + + task_type = #{autoMl.taskType}, - - progress = #{autoMl.progress}, + + dataset_name = #{autoMl.datasetName}, + + + time_left_for_this_task = #{autoMl.timeLeftForThisTask}, + + + per_run_time_limit = #{autoMl.perRunTimeLimit}, + + + ensemble_size = #{autoMl.ensembleSize}, + + + ensemble_class = #{autoMl.ensembleClass}, + + + ensemble_nbest = #{autoMl.ensembleNbest}, + + + max_models_on_disc = #{autoMl.maxModelsOnDisc}, + + + seed = #{autoMl.seed}, + + + memory_limit = #{autoMl.memoryLimit}, + + + include_classifier = #{autoMl.includeClassifier}, + + + include_feature_preprocessor = #{autoMl.includeFeaturePreprocessor}, + + + include_regressor = #{autoMl.includeRegressor}, + + + exclude_classifier = #{autoMl.excludeClassifier}, + + + exclude_regressor = #{autoMl.excludeRegressor}, + + + exclude_feature_preprocessor = #{autoMl.excludeFeaturePreprocessor}, + + + resampling_strategy = #{autoMl.resamplingStrategy}, + + + train_size = #{autoMl.trainSize}, + + + shuffle = #{autoMl.shuffle}, + + + folds = #{autoMl.folds}, + + + data_csv = #{autoMl.dataCsv}, + + + target_columns = #{autoMl.targetColumns}, state = #{autoMl.state}, @@ -39,7 +120,16 @@ + + diff --git a/ruoyi-modules/management-platform/src/main/resources/mapper/managementPlatform/AutoMLInsDaoMapper.xml b/ruoyi-modules/management-platform/src/main/resources/mapper/managementPlatform/AutoMLInsDaoMapper.xml deleted file mode 100644 index 7f85a9d5..00000000 --- a/ruoyi-modules/management-platform/src/main/resources/mapper/managementPlatform/AutoMLInsDaoMapper.xml +++ /dev/null @@ -1,132 +0,0 @@ - - - - - insert into auto_ml_ins(auto_ml_id, task_type, dataset_name, time_left_for_this_task, per_run_time_limit, - ensemble_size, ensemble_class, ensemble_nbest, max_models_on_disc, seed, memory_limit, - include_classifier, include_feature_preprocessor, include_regressor, exclude_classifier, - exclude_regressor, exclude_feature_preprocessor, resampling_strategy, train_size, - shuffle, folds, delete_tmp_folder_after_terminate, data_csv, target_columns, create_by, - update_by) - values (#{autoMlIns.autoMlId}, #{autoMlIns.taskType}, #{autoMlIns.datasetName}, - #{autoMlIns.timeLeftForThisTask}, #{autoMlIns.perRunTimeLimit}, - #{autoMlIns.ensembleSize}, #{autoMlIns.ensembleClass}, #{autoMlIns.ensembleNbest}, - #{autoMlIns.maxModelsOnDisc}, #{autoMlIns.seed}, - #{autoMlIns.memoryLimit}, #{autoMlIns.includeClassifier}, #{autoMlIns.includeFeaturePreprocessor}, - #{autoMlIns.includeRegressor}, #{autoMlIns.excludeClassifier}, - #{autoMlIns.excludeRegressor}, #{autoMlIns.excludeFeaturePreprocessor}, #{autoMlIns.resamplingStrategy}, - #{autoMlIns.trainSize}, #{autoMlIns.shuffle}, - #{autoMlIns.folds}, #{autoMlIns.deleteTmpFolderAfterTerminate}, #{autoMlIns.dataCsv}, - #{autoMlIns.targetColumns}, #{autoMlIns.createBy}, #{autoMlIns.updateBy}) - - - - update auto_ml_ins - - - task_type = #{autoMlIns.taskType}, - - - dataset_name = #{autoMlIns.datasetName}, - - - time_left_for_this_task = #{autoMlIns.timeLeftForThisTask}, - - - per_run_time_limit = #{autoMlIns.perRunTimeLimit}, - - - ensemble_size = #{autoMlIns.ensembleSize}, - - - ensemble_class = #{autoMlIns.ensembleClass}, - - - ensemble_nbest = #{autoMlIns.ensembleNbest}, - - - max_models_on_disc = #{autoMlIns.maxModelsOnDisc}, - - - seed = #{autoMlIns.seed}, - - - memory_limit = #{autoMlIns.memoryLimit}, - - - include_classifier = #{autoMlIns.includeClassifier}, - - - include_feature_preprocessor = #{autoMlIns.includeFeaturePreprocessor}, - - - include_regressor = #{autoMlIns.includeRegressor}, - - - exclude_classifier = #{autoMlIns.excludeClassifier}, - - - exclude_regressor = #{autoMlIns.excludeRegressor}, - - - exclude_feature_preprocessor = #{autoMlIns.excludeFeaturePreprocessor}, - - - resampling_strategy = #{autoMlIns.resamplingStrategy}, - - - train_size = #{autoMlIns.trainSize}, - - - shuffle = #{autoMlIns.shuffle}, - - - folds = #{autoMlIns.folds}, - - - delete_tmp_folder_after_terminate = #{autoMlIns.deleteTmpFolderAfterTerminate}, - - - data_csv = #{autoMlIns.dataCsv}, - - - target_columns = #{autoMlIns.targetColumns}, - - - state = #{autoMlIns.state}, - - - update_by = #{autoMlIns.updateBy}, - - - where id = #{autoMlIns.id} - - - - - - - - - - - - - state = 1 and auto_ml_id = #{autoMlId} - - - \ No newline at end of file From 0ede04ec79c6fab3c7fa22fbc91ce7ab8e24bbf7 Mon Sep 17 00:00:00 2001 From: chenzhihang <709011834@qq.com> Date: Fri, 22 Nov 2024 08:49:46 +0800 Subject: [PATCH 09/59] =?UTF-8?q?=E8=87=AA=E5=8A=A8=E6=9C=BA=E5=99=A8?= =?UTF-8?q?=E5=AD=A6=E4=B9=A0=E5=BC=80=E5=8F=91?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../com/ruoyi/platform/domain/AutoMl.java | 100 +++++++++++++++++- .../managementPlatform/AutoMLDaoMapper.xml | 22 +++- 2 files changed, 117 insertions(+), 5 deletions(-) diff --git a/ruoyi-modules/management-platform/src/main/java/com/ruoyi/platform/domain/AutoMl.java b/ruoyi-modules/management-platform/src/main/java/com/ruoyi/platform/domain/AutoMl.java index 43b8255f..e1aa5506 100644 --- a/ruoyi-modules/management-platform/src/main/java/com/ruoyi/platform/domain/AutoMl.java +++ b/ruoyi-modules/management-platform/src/main/java/com/ruoyi/platform/domain/AutoMl.java @@ -22,31 +22,86 @@ public class AutoMl { @ApiModelProperty(value = "实验描述") private String mlDescription; - @ApiModelProperty(value = "任务类型") + @ApiModelProperty(value = "任务类型:classification或regression") private String taskType; + @ApiModelProperty(value = "数据集名称") private String datasetName; + @ApiModelProperty(value = "搜索合适模型的时间限制(以秒为单位)。通过增加这个值,auto-sklearn有更高的机会找到更好的模型。默认3600,非必传。") private Integer timeLeftForThisTask; + @ApiModelProperty(value = "单次调用机器学习模型的时间限制(以秒为单位)。如果机器学习算法运行超过时间限制,将终止模型拟合。将这个值设置得足够高,这样典型的机器学习算法就可以适用于训练数据。默认600,非必传。") private Integer perRunTimeLimit; + @ApiModelProperty(value = "集成模型数量,如果设置为0,则没有集成。默认50,非必传。") private Integer ensembleSize; + @ApiModelProperty(value = "设置为None将禁用集成构建,设置为SingleBest仅使用单个最佳模型而不是集成,设置为default,它将对单目标问题使用EnsembleSelection,对多目标问题使用MultiObjectiveDummyEnsemble。默认default,非必传。") private String ensembleClass; + @ApiModelProperty(value = "在构建集成时只考虑ensemble_nbest模型。这是受到了“最大限度地利用集成选择”中引入的库修剪概念的启发。这是独立于ensemble_class参数的,并且这个修剪步骤是在构造集成之前完成的。默认50,非必传。") private Integer ensembleNbest; + @ApiModelProperty(value = "定义在磁盘中保存的模型的最大数量。额外的模型数量将被永久删除。由于这个变量的性质,它设置了一个集成可以使用多少个模型的上限。必须是大于等于1的整数。如果设置为None,则所有模型都保留在磁盘上。默认50,非必传。") private Integer maxModelsOnDisc; + @ApiModelProperty(value = "随机种子,将决定输出文件名。默认1,非必传。") private Integer seed; + @ApiModelProperty(value = "机器学习算法的内存限制(MB)。如果auto-sklearn试图分配超过memory_limit MB,它将停止拟合机器学习算法。默认3072,非必传。") private Integer memoryLimit; + @ApiModelProperty(value = "如果为None,则使用所有可能的分类算法。否则,指定搜索中包含的步骤和组件。有关可用组件,请参见/pipeline/components//*。与参数exclude不兼容。多选,逗号分隔。包含:adaboost\n" + + "bernoulli_nb\n" + + "decision_tree\n" + + "extra_trees\n" + + "gaussian_nb\n" + + "gradient_boosting\n" + + "k_nearest_neighbors\n" + + "lda\n" + + "liblinear_svc\n" + + "libsvm_svc\n" + + "mlp\n" + + "multinomial_nb\n" + + "passive_aggressive\n" + + "qda\n" + + "random_forest\n" + + "sgd") private String includeClassifier; + @ApiModelProperty(value = "如果为None,则使用所有可能的特征预处理算法。否则,指定搜索中包含的步骤和组件。有关可用组件,请参见/pipeline/components//*。与参数exclude不兼容。多选,逗号分隔。包含:densifier\n" + + "extra_trees_preproc_for_classification\n" + + "extra_trees_preproc_for_regression\n" + + "fast_ica\n" + + "feature_agglomeration\n" + + "kernel_pca\n" + + "kitchen_sinks\n" + + "liblinear_svc_preprocessor\n" + + "no_preprocessing\n" + + "nystroem_sampler\n" + + "pca\n" + + "polynomial\n" + + "random_trees_embedding\n" + + "select_percentile_classification\n" + + "select_percentile_regression\n" + + "select_rates_classification\n" + + "select_rates_regression\n" + + "truncatedSVD") private String includeFeaturePreprocessor; + @ApiModelProperty(value = "如果为None,则使用所有可能的回归算法。否则,指定搜索中包含的步骤和组件。有关可用组件,请参见/pipeline/components//*。与参数exclude不兼容。多选,逗号分隔。包含:adaboost,\n" + + "ard_regression,\n" + + "decision_tree,\n" + + "extra_trees,\n" + + "gaussian_process,\n" + + "gradient_boosting,\n" + + "k_nearest_neighbors,\n" + + "liblinear_svr,\n" + + "libsvm_svr,\n" + + "mlp,\n" + + "random_forest,\n" + + "sgd") private String includeRegressor; private String excludeClassifier; @@ -55,20 +110,61 @@ public class AutoMl { private String excludeFeaturePreprocessor; + @ApiModelProperty(value = "如何处理过拟合,如果使用基于“cv”的方法或Splitter对象,可能需要使用resampling_strategy_arguments。holdout或crossValid") private String resamplingStrategy; + @ApiModelProperty(value = "训练集的比率,0到1之间") private Float trainSize; + @ApiModelProperty(value = "拆分数据前是否进行shuffle") private Boolean shuffle; + @ApiModelProperty(value = "当resamplingStrategy为crossValid时,此项必填。为整数") private Integer folds; - private Boolean deleteTmpFolderAfterTerminate; + @ApiModelProperty(value = "文件夹存放配置输出和日志文件,默认/tmp/automl") + private String tmpFolder; + @ApiModelProperty(value = "数据集csv文件路径") private String dataCsv; + @ApiModelProperty(value = "数据集csv文件中哪几列是预测目标列,逗号分隔") private String targetColumns; + @ApiModelProperty(value = "自定义指标名称") + private String metricName; + + @ApiModelProperty(value = "模型优化目标指标及权重,json格式。分类的指标包含:accuracy\n" + + "balanced_accuracy\n" + + "roc_auc\n" + + "average_precision\n" + + "log_loss\n" + + "precision_macro\n" + + "precision_micro\n" + + "precision_samples\n" + + "precision_weighted\n" + + "recall_macro\n" + + "recall_micro\n" + + "recall_samples\n" + + "recall_weighted\n" + + "f1_macro\n" + + "f1_micro\n" + + "f1_samples\n" + + "f1_weighted\n" + + "回归的指标包含:mean_absolute_error\n" + + "mean_squared_error\n" + + "root_mean_squared_error\n" + + "mean_squared_log_error\n" + + "median_absolute_error\n" + + "r2") + private String metrics; + + @ApiModelProperty(value = "指标优化方向,是越大越好还是越小越好") + private Boolean greaterIsBetter; + + @ApiModelProperty(value = "模型计算并打印指标") + private String scoringFunctions; + private Integer state; private String runState; diff --git a/ruoyi-modules/management-platform/src/main/resources/mapper/managementPlatform/AutoMLDaoMapper.xml b/ruoyi-modules/management-platform/src/main/resources/mapper/managementPlatform/AutoMLDaoMapper.xml index 0af3beb7..55f8e8f5 100644 --- a/ruoyi-modules/management-platform/src/main/resources/mapper/managementPlatform/AutoMLDaoMapper.xml +++ b/ruoyi-modules/management-platform/src/main/resources/mapper/managementPlatform/AutoMLDaoMapper.xml @@ -7,8 +7,8 @@ memory_limit, include_classifier, include_feature_preprocessor, include_regressor, exclude_classifier, exclude_regressor, exclude_feature_preprocessor, resampling_strategy, train_size, - shuffle, folds, data_csv, target_columns, create_by, - update_by) + shuffle, folds, data_csv, target_columns, metric_name, metrics,greater_is_better,scoring_functions,tmp_folder, + create_by,update_by) values (#{autoMl.mlName}, #{autoMl.mlDescription}, #{autoMl.taskType}, #{autoMl.datasetName}, #{autoMl.timeLeftForThisTask}, #{autoMl.perRunTimeLimit}, #{autoMl.ensembleSize}, #{autoMl.ensembleClass}, #{autoMl.ensembleNbest}, @@ -18,7 +18,8 @@ #{autoMl.excludeRegressor}, #{autoMl.excludeFeaturePreprocessor}, #{autoMl.resamplingStrategy}, #{autoMl.trainSize}, #{autoMl.shuffle}, #{autoMl.folds}, #{autoMl.dataCsv}, - #{autoMl.targetColumns}, #{autoMl.createBy}, #{autoMl.updateBy}) + #{autoMl.targetColumns}, #{autoMl.metricName}, #{autoMl.metrics},#{autoMl.greaterIsBetter},#{autoMl.scoringFunctions},#{autoMl.tmpFolder}, + #{autoMl.createBy}, #{autoMl.updateBy}) @@ -99,6 +100,21 @@ data_csv = #{autoMl.dataCsv}, + + tmp_folder = #{autoMl.tmpFolder}, + + + metric_name = #{autoMl.metricName}, + + + metrics = #{autoMl.metrics}, + + + greater_is_better = #{autoMl.greaterIsBetter}, + + + scoring_functions = #{autoMl.scoringFunctions}, + target_columns = #{autoMl.targetColumns}, From 468b812f24598f168dbba51b722cf1895a48aae0 Mon Sep 17 00:00:00 2001 From: chenzhihang <709011834@qq.com> Date: Fri, 22 Nov 2024 11:22:53 +0800 Subject: [PATCH 10/59] =?UTF-8?q?=E8=87=AA=E5=8A=A8=E6=9C=BA=E5=99=A8?= =?UTF-8?q?=E5=AD=A6=E4=B9=A0=E5=BC=80=E5=8F=91?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../src/main/java/com/ruoyi/platform/domain/AutoMl.java | 7 +++++-- .../mapper/managementPlatform/AutoMLDaoMapper.xml | 7 +++++-- 2 files changed, 10 insertions(+), 4 deletions(-) diff --git a/ruoyi-modules/management-platform/src/main/java/com/ruoyi/platform/domain/AutoMl.java b/ruoyi-modules/management-platform/src/main/java/com/ruoyi/platform/domain/AutoMl.java index e1aa5506..ec9b5931 100644 --- a/ruoyi-modules/management-platform/src/main/java/com/ruoyi/platform/domain/AutoMl.java +++ b/ruoyi-modules/management-platform/src/main/java/com/ruoyi/platform/domain/AutoMl.java @@ -110,16 +110,19 @@ public class AutoMl { private String excludeFeaturePreprocessor; + @ApiModelProperty(value = "训练集的比率,0到1之间") + private Float testSize; + @ApiModelProperty(value = "如何处理过拟合,如果使用基于“cv”的方法或Splitter对象,可能需要使用resampling_strategy_arguments。holdout或crossValid") private String resamplingStrategy; - @ApiModelProperty(value = "训练集的比率,0到1之间") + @ApiModelProperty(value = "重采样划分训练集和验证集,训练集的比率,0到1之间") private Float trainSize; @ApiModelProperty(value = "拆分数据前是否进行shuffle") private Boolean shuffle; - @ApiModelProperty(value = "当resamplingStrategy为crossValid时,此项必填。为整数") + @ApiModelProperty(value = "交叉验证的折数,当resamplingStrategy为crossValid时,此项必填,为整数") private Integer folds; @ApiModelProperty(value = "文件夹存放配置输出和日志文件,默认/tmp/automl") diff --git a/ruoyi-modules/management-platform/src/main/resources/mapper/managementPlatform/AutoMLDaoMapper.xml b/ruoyi-modules/management-platform/src/main/resources/mapper/managementPlatform/AutoMLDaoMapper.xml index 55f8e8f5..e5fecd5a 100644 --- a/ruoyi-modules/management-platform/src/main/resources/mapper/managementPlatform/AutoMLDaoMapper.xml +++ b/ruoyi-modules/management-platform/src/main/resources/mapper/managementPlatform/AutoMLDaoMapper.xml @@ -6,7 +6,7 @@ per_run_time_limit, ensemble_size, ensemble_class, ensemble_nbest, max_models_on_disc, seed, memory_limit, include_classifier, include_feature_preprocessor, include_regressor, exclude_classifier, - exclude_regressor, exclude_feature_preprocessor, resampling_strategy, train_size, + exclude_regressor, exclude_feature_preprocessor, test_size, resampling_strategy, train_size, shuffle, folds, data_csv, target_columns, metric_name, metrics,greater_is_better,scoring_functions,tmp_folder, create_by,update_by) values (#{autoMl.mlName}, #{autoMl.mlDescription}, #{autoMl.taskType}, #{autoMl.datasetName}, @@ -15,7 +15,7 @@ #{autoMl.maxModelsOnDisc}, #{autoMl.seed}, #{autoMl.memoryLimit}, #{autoMl.includeClassifier}, #{autoMl.includeFeaturePreprocessor}, #{autoMl.includeRegressor}, #{autoMl.excludeClassifier}, - #{autoMl.excludeRegressor}, #{autoMl.excludeFeaturePreprocessor}, #{autoMl.resamplingStrategy}, + #{autoMl.excludeRegressor}, #{autoMl.excludeFeaturePreprocessor}, #{autoMl.testSize}, #{autoMl.resamplingStrategy}, #{autoMl.trainSize}, #{autoMl.shuffle}, #{autoMl.folds}, #{autoMl.dataCsv}, #{autoMl.targetColumns}, #{autoMl.metricName}, #{autoMl.metrics},#{autoMl.greaterIsBetter},#{autoMl.scoringFunctions},#{autoMl.tmpFolder}, @@ -85,6 +85,9 @@ exclude_feature_preprocessor = #{autoMl.excludeFeaturePreprocessor}, + + test_size = #{autoMl.testSize}, + resampling_strategy = #{autoMl.resamplingStrategy}, From 72744f287db4c92e5119654577b962c17960d4d9 Mon Sep 17 00:00:00 2001 From: chenzhihang <709011834@qq.com> Date: Fri, 22 Nov 2024 11:23:35 +0800 Subject: [PATCH 11/59] =?UTF-8?q?=E8=87=AA=E5=8A=A8=E6=9C=BA=E5=99=A8?= =?UTF-8?q?=E5=AD=A6=E4=B9=A0=E5=BC=80=E5=8F=91?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../src/main/java/com/ruoyi/platform/domain/AutoMl.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ruoyi-modules/management-platform/src/main/java/com/ruoyi/platform/domain/AutoMl.java b/ruoyi-modules/management-platform/src/main/java/com/ruoyi/platform/domain/AutoMl.java index ec9b5931..e37f6bbb 100644 --- a/ruoyi-modules/management-platform/src/main/java/com/ruoyi/platform/domain/AutoMl.java +++ b/ruoyi-modules/management-platform/src/main/java/com/ruoyi/platform/domain/AutoMl.java @@ -110,7 +110,7 @@ public class AutoMl { private String excludeFeaturePreprocessor; - @ApiModelProperty(value = "训练集的比率,0到1之间") + @ApiModelProperty(value = "测试集的比率,0到1之间") private Float testSize; @ApiModelProperty(value = "如何处理过拟合,如果使用基于“cv”的方法或Splitter对象,可能需要使用resampling_strategy_arguments。holdout或crossValid") From 9976c8e972bd80179979d22960d3818fc883fdfd Mon Sep 17 00:00:00 2001 From: chenzhihang <709011834@qq.com> Date: Mon, 25 Nov 2024 10:06:13 +0800 Subject: [PATCH 12/59] =?UTF-8?q?=E8=87=AA=E5=8A=A8=E6=9C=BA=E5=99=A8?= =?UTF-8?q?=E5=AD=A6=E4=B9=A0=E5=BC=80=E5=8F=91?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../ruoyi/platform/controller/autoML/AutoMLController.java | 5 +++++ .../main/java/com/ruoyi/platform/service/AutoMLService.java | 2 ++ .../com/ruoyi/platform/service/impl/AutoMLServiceImpl.java | 5 +++++ 3 files changed, 12 insertions(+) diff --git a/ruoyi-modules/management-platform/src/main/java/com/ruoyi/platform/controller/autoML/AutoMLController.java b/ruoyi-modules/management-platform/src/main/java/com/ruoyi/platform/controller/autoML/AutoMLController.java index 4bd1a36e..10b5a217 100644 --- a/ruoyi-modules/management-platform/src/main/java/com/ruoyi/platform/controller/autoML/AutoMLController.java +++ b/ruoyi-modules/management-platform/src/main/java/com/ruoyi/platform/controller/autoML/AutoMLController.java @@ -42,6 +42,11 @@ public class AutoMLController extends BaseController { public GenericsAjaxResult editAutoMl(@RequestBody AutoMl autoMl) throws Exception { return genericsSuccess(this.autoMLService.edit(autoMl)); } + @GetMapping("/getAutoMlDetail") + @ApiOperation("获取自动机器学习详细信息") + public GenericsAjaxResult getAutoMlDetail(@RequestParam("id") Long id){ + return genericsSuccess(this.autoMLService.getAutoMlDetail(id)); + } @DeleteMapping("{id}") @ApiOperation("删除自动机器学习") diff --git a/ruoyi-modules/management-platform/src/main/java/com/ruoyi/platform/service/AutoMLService.java b/ruoyi-modules/management-platform/src/main/java/com/ruoyi/platform/service/AutoMLService.java index 6d5fd355..2b678382 100644 --- a/ruoyi-modules/management-platform/src/main/java/com/ruoyi/platform/service/AutoMLService.java +++ b/ruoyi-modules/management-platform/src/main/java/com/ruoyi/platform/service/AutoMLService.java @@ -16,6 +16,8 @@ public interface AutoMLService { String delete(Long id); + AutoMl getAutoMlDetail(Long id); + Map upload(MultipartFile file, String uuid) throws Exception; String runAutoMlIns(Long id) throws Exception; diff --git a/ruoyi-modules/management-platform/src/main/java/com/ruoyi/platform/service/impl/AutoMLServiceImpl.java b/ruoyi-modules/management-platform/src/main/java/com/ruoyi/platform/service/impl/AutoMLServiceImpl.java index 9cdd3b40..f052f32e 100644 --- a/ruoyi-modules/management-platform/src/main/java/com/ruoyi/platform/service/impl/AutoMLServiceImpl.java +++ b/ruoyi-modules/management-platform/src/main/java/com/ruoyi/platform/service/impl/AutoMLServiceImpl.java @@ -115,6 +115,11 @@ public class AutoMLServiceImpl implements AutoMLService { return autoMLDao.edit(autoMl) > 0 ? "删除成功" : "删除失败"; } + @Override + public AutoMl getAutoMlDetail(Long id) { + return autoMLDao.getAutoMlById(id); + } + @Override public Map upload(MultipartFile file, String uuid) throws Exception { Map result = new HashMap<>(); From e844fc614e39164a323ab79ebbc76de88fb36ff4 Mon Sep 17 00:00:00 2001 From: cp3hnu Date: Mon, 25 Nov 2024 15:48:42 +0800 Subject: [PATCH 13/59] =?UTF-8?q?feat:=20=E5=AE=8C=E6=88=90=E8=87=AA?= =?UTF-8?q?=E5=8A=A8=E6=9C=BA=E5=99=A8=E5=AD=A6=E4=B9=A0=E6=8E=A5=E5=8F=A3?= =?UTF-8?q?=E8=81=94=E8=B0=83?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- react-ui/config/routes.ts | 7 +- react-ui/src/enums/index.ts | 24 + react-ui/src/pages/Authorize/index.tsx | 1 - react-ui/src/pages/AutoML/Create/index.tsx | 214 ++++---- react-ui/src/pages/AutoML/List/index.tsx | 80 ++- .../AutoML/components/AutoMLTable/index.tsx | 6 +- .../components/CreateForm/BasicConfig.tsx | 31 +- .../components/CreateForm/DatasetConfig.tsx | 59 +++ .../components/CreateForm/ExecuteConfig.tsx | 455 +++++++++++++++++- .../CreateForm/ExecuteConfigDLC.tsx | 29 +- .../components/CreateForm/TrialConfig.tsx | 263 ++++------ .../components/CreateForm/UploadConfig.tsx | 80 +++ .../AutoML/components/CreateForm/index.less | 11 + react-ui/src/pages/AutoML/types.ts | 52 +- react-ui/src/services/autoML.js | 55 +++ react-ui/src/utils/functional.ts | 10 + react-ui/src/utils/index.ts | 15 + 17 files changed, 995 insertions(+), 397 deletions(-) create mode 100644 react-ui/src/pages/AutoML/components/CreateForm/DatasetConfig.tsx create mode 100644 react-ui/src/pages/AutoML/components/CreateForm/UploadConfig.tsx create mode 100644 react-ui/src/services/autoML.js diff --git a/react-ui/config/routes.ts b/react-ui/config/routes.ts index 189efc8a..c58b182e 100644 --- a/react-ui/config/routes.ts +++ b/react-ui/config/routes.ts @@ -160,10 +160,15 @@ export default [ component: './AutoML/Info/index', }, { - name: '创建自动机器学习', + name: '创建实验', path: 'create', component: './AutoML/Create/index', }, + { + name: '编辑实验', + path: 'edit/:id', + component: './AutoML/Create/index', + }, ], }, ], diff --git a/react-ui/src/enums/index.ts b/react-ui/src/enums/index.ts index e489515c..05e14b34 100644 --- a/react-ui/src/enums/index.ts +++ b/react-ui/src/enums/index.ts @@ -71,6 +71,7 @@ export enum DevEditorStatus { Unknown = 'Unknown', // 未启动 } +// 服务类型 export enum ServiceType { Video = 'video', Image = 'image', @@ -84,3 +85,26 @@ export const serviceTypeOptions = [ { label: '音频', value: ServiceType.Audio }, { label: '文本', value: ServiceType.Text }, ]; + +// 自动化任务类型 +export enum AutoMLTaskType { + Classification = 'classification', + Regression = 'regression', +} + +// 自动化任务集成策略 +export enum AutoMLEnsembleClass { + Default = 'default', + SingleBest = 'SingleBest', +} + +// 自动化任务重采样策略 +export enum AutoMLResamplingStrategy { + Holdout = 'holdout', + CrossValid = 'crossValid', +} + +export const resamplingStrategyOptions = [ + { label: 'holdout', value: AutoMLResamplingStrategy.Holdout }, + { label: 'crossValid', value: AutoMLResamplingStrategy.CrossValid }, +]; diff --git a/react-ui/src/pages/Authorize/index.tsx b/react-ui/src/pages/Authorize/index.tsx index f3624f32..e42a0f1b 100644 --- a/react-ui/src/pages/Authorize/index.tsx +++ b/react-ui/src/pages/Authorize/index.tsx @@ -22,7 +22,6 @@ function Authorize() { code, }; const [res] = await to(loginByOauth2Req(params)); - debugger; if (res && res.data) { const { access_token, expires_in } = res.data; setSessionToken(access_token, access_token, expires_in); diff --git a/react-ui/src/pages/AutoML/Create/index.tsx b/react-ui/src/pages/AutoML/Create/index.tsx index 9ac79d57..743d8377 100644 --- a/react-ui/src/pages/AutoML/Create/index.tsx +++ b/react-ui/src/pages/AutoML/Create/index.tsx @@ -4,147 +4,116 @@ * @Description: 创建服务版本 */ import PageTitle from '@/components/PageTitle'; -import { type ParameterInputObject } from '@/components/ResourceSelect'; -import { useComputingResource } from '@/hooks/resource'; -import { - createServiceVersionReq, - getServiceInfoReq, - updateServiceVersionReq, -} from '@/services/modelDeployment'; -import { changePropertyName } from '@/utils'; + +import { AutoMLTaskType } from '@/enums'; +import { addAutoMLReq, getDatasetInfoReq, updateAutoMLReq } from '@/services/autoML'; +import { parseJsonText, trimCharacter } from '@/utils'; +import { safeInvoke } from '@/utils/functional'; import { to } from '@/utils/promise'; -import SessionStorage from '@/utils/sessionStorage'; import { useNavigate, useParams } from '@umijs/max'; import { App, Button, Form } from 'antd'; -import { omit, pick } from 'lodash'; -import { useEffect, useState } from 'react'; +import { omit } from 'lodash'; +import { useEffect } from 'react'; import BasicConfig from '../components/CreateForm/BasicConfig'; +import DatasetConfig from '../components/CreateForm/DatasetConfig'; import ExecuteConfig from '../components/CreateForm/ExecuteConfig'; -import SearchConfig from '../components/CreateForm/SearchConfig'; import TrialConfig from '../components/CreateForm/TrialConfig'; -import { ServiceData, ServiceOperationType, ServiceVersionData } from '../types'; +import { AutoMLData, FormData } from '../types'; import styles from './index.less'; -// 表单数据 -export type FormData = { - service_name: string; // 服务名称 - version: string; // 服务版本 - description: string; // 描述 - model: ParameterInputObject; // 模型 - image: ParameterInputObject; // 镜像 - code_config: ParameterInputObject; // 代码 - resource: string; // 资源规格 - replicas: string; // 副本数量 - mount_path: string; // 模型路径 - env_variables: { key: string; value: string }[]; // 环境变量 -}; - function CreateAutoML() { const navigate = useNavigate(); const [form] = Form.useForm(); - const [resourceStandardList, filterResourceStandard] = useComputingResource(); - const [operationType, setOperationType] = useState(ServiceOperationType.Create); - const { message } = App.useApp(); - const [serviceInfo, setServiceInfo] = useState(undefined); - const [versionInfo, setVersionInfo] = useState(undefined); const params = useParams(); - const id = params.id; + const id = safeInvoke(Number)(params.id); useEffect(() => { - const res: - | (ServiceVersionData & { - operationType: ServiceOperationType; - }) - | undefined = SessionStorage.getItem(SessionStorage.serviceVersionInfoKey, true); - if (res) { - setOperationType(res.operationType); - - 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); + if (id) { + getAutoMLInfo(); } - return () => { - SessionStorage.removeItem(SessionStorage.serviceVersionInfoKey); - }; - }, []); + }, [id]); // 获取服务详情 - const getServiceInfo = async () => { - const [res] = await to(getServiceInfoReq(id)); + const getAutoMLInfo = async () => { + const [res] = await to(getDatasetInfoReq({ id })); if (res && res.data) { - setServiceInfo(res.data); - form.setFieldsValue({ - service_name: res.data.service_name, - }); + const autoMLInfo: AutoMLData = res.data; + const { + include_classifier: include_classifier_str, + include_feature_preprocessor: include_feature_preprocessor_str, + include_regressor: include_regressor_str, + exclude_classifier: exclude_classifier_str, + exclude_feature_preprocessor: exclude_feature_preprocessor_str, + exclude_regressor: exclude_regressor_str, + metrics: metrics_str, + } = autoMLInfo; + const include_classifier = include_classifier_str?.split(',').filter(Boolean); + const include_feature_preprocessor = include_feature_preprocessor_str + ?.split(',') + .filter(Boolean); + const include_regressor = include_regressor_str?.split(',').filter(Boolean); + const exclude_classifier = exclude_classifier_str?.split(',').filter(Boolean); + const exclude_feature_preprocessor = exclude_feature_preprocessor_str + ?.split(',') + .filter(Boolean); + const exclude_regressor = exclude_regressor_str?.split(',').filter(Boolean); + const metricsObj = safeInvoke(parseJsonText)(metrics_str) ?? {}; + const metrics = Object.entries(metricsObj).map(([key, value]) => ({ + name: key, + value, + })); + + const formData = { + ...autoMLInfo, + include_classifier, + include_feature_preprocessor, + include_regressor, + exclude_classifier, + exclude_feature_preprocessor, + exclude_regressor, + metrics, + }; + + form.setFieldsValue(formData); } }; // 创建版本 - const createServiceVersion = async (formData: FormData) => { - const envList = formData['env_variables'] ?? []; - const image = formData['image']; - const model = formData['model']; - const codeConfig = formData['code_config']; - const envVariables = envList.reduce((acc, cur) => { - acc[cur.key] = cur.value; + const createExperiment = async (formData: FormData) => { + const include_classifier = formData['include_classifier']?.join(','); + const include_feature_preprocessor = formData['include_feature_preprocessor']?.join(','); + const include_regressor = formData['include_regressor']?.join(','); + const exclude_classifier = formData['exclude_classifier']?.join(','); + const exclude_feature_preprocessor = formData['exclude_feature_preprocessor']?.join(','); + const exclude_regressor = formData['exclude_regressor']?.join(','); + const metrics = formData['metrics']?.reduce((acc, cur) => { + acc[cur.name] = cur.value; return acc; - }, {} as Record); + }, {} as Record); + const target_columns = trimCharacter(formData['target_columns'], ','); // 根据后台要求,修改表单数据 const object = { - ...omit(formData, ['replicas', 'env_variables', 'image', 'model', 'code_config']), - replicas: Number(formData.replicas), - env_variables: envVariables, - image: image.value, - 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, + ...omit(formData), + include_classifier, + include_feature_preprocessor, + include_regressor, + exclude_classifier, + exclude_feature_preprocessor, + exclude_regressor, + metrics: metrics ? JSON.stringify(metrics) : undefined, + target_columns, }; - const params = - operationType === ServiceOperationType.Create - ? object - : { - id: versionInfo?.id, - rerun: operationType === ServiceOperationType.Restart ? true : false, - deployment_name: versionInfo?.deployment_name, - ...object, - }; - - const request = - operationType === ServiceOperationType.Create - ? createServiceVersionReq - : updateServiceVersionReq; + const params = id + ? { + id: id, + ...object, + } + : object; + const request = id ? updateAutoMLReq : addAutoMLReq; const [res] = await to(request(params)); if (res) { message.success('操作成功'); @@ -154,7 +123,7 @@ function CreateAutoML() { // 提交 const handleSubmit = (values: FormData) => { - console.log('values', values); + createExperiment(values); }; // 取消 @@ -162,15 +131,12 @@ function CreateAutoML() { navigate(-1); }; - const disabled = operationType !== ServiceOperationType.Create; + const disabled = id !== null || id !== undefined; let buttonText = '新建'; - let title = '新增服务版本'; - if (operationType === ServiceOperationType.Update) { - title = '更新服务版本'; + let title = '新增实验'; + if (id) { + title = '更新实验'; buttonText = '更新'; - } else if (operationType === ServiceOperationType.Restart) { - title = '重启服务版本'; - buttonText = '重启'; } return ( @@ -180,22 +146,22 @@ function CreateAutoML() {
- + @@ -279,7 +267,7 @@ function AutoMLList() { diff --git a/react-ui/src/pages/AutoML/components/CreateForm/BasicConfig.tsx b/react-ui/src/pages/AutoML/components/CreateForm/BasicConfig.tsx index e181d890..dbf32e32 100644 --- a/react-ui/src/pages/AutoML/components/CreateForm/BasicConfig.tsx +++ b/react-ui/src/pages/AutoML/components/CreateForm/BasicConfig.tsx @@ -1,5 +1,5 @@ import SubAreaTitle from '@/components/SubAreaTitle'; -import { Col, Form, Input, Row, Select } from 'antd'; +import { Col, Form, Input, Row } from 'antd'; function BasicConfig() { return ( <> @@ -11,43 +11,34 @@ function BasicConfig() {
- + - + {/* - + */} ); } diff --git a/react-ui/src/pages/AutoML/components/CreateForm/DatasetConfig.tsx b/react-ui/src/pages/AutoML/components/CreateForm/DatasetConfig.tsx new file mode 100644 index 00000000..79984a41 --- /dev/null +++ b/react-ui/src/pages/AutoML/components/CreateForm/DatasetConfig.tsx @@ -0,0 +1,59 @@ +import ResourceSelect, { + ResourceSelectorType, + requiredValidator, +} from '@/components/ResourceSelect'; +import SubAreaTitle from '@/components/SubAreaTitle'; +import { Col, Form, Input, Row } from 'antd'; + +function DatasetConfig() { + return ( + <> + + + + + + + + + + + + + + + + + ); +} + +export default DatasetConfig; diff --git a/react-ui/src/pages/AutoML/components/CreateForm/ExecuteConfig.tsx b/react-ui/src/pages/AutoML/components/CreateForm/ExecuteConfig.tsx index 4ce0143e..e8ef7d65 100644 --- a/react-ui/src/pages/AutoML/components/CreateForm/ExecuteConfig.tsx +++ b/react-ui/src/pages/AutoML/components/CreateForm/ExecuteConfig.tsx @@ -1,19 +1,110 @@ -import KFIcon from '@/components/KFIcon'; import SubAreaTitle from '@/components/SubAreaTitle'; -import { MinusCircleOutlined, PlusCircleOutlined } from '@ant-design/icons'; -import { Button, Col, Flex, Form, Input, Radio, Row } from 'antd'; -import ExecuteConfigDLC from './ExecuteConfigDLC'; -import ExecuteConfigMC from './ExecuteConfigMC'; -import styles from './index.less'; +import { + AutoMLEnsembleClass, + AutoMLResamplingStrategy, + AutoMLTaskType, + resamplingStrategyOptions, +} from '@/enums'; +import { Col, Form, Input, InputNumber, Radio, Row, Select, Switch } from 'antd'; -type ExecuteConfigProps = { - disabled?: boolean; -}; +// 分类算法 +const classificationAlgorithms = [ + 'adaboost', + 'bernoulli_nb', + 'decision_tree', + 'extra_trees', + 'gaussian_nb', + 'gradient_boosting', + 'k_nearest_neighbors', + 'lda', + 'liblinear_svc', + 'libsvm_svc', + 'mlp', + 'multinomial_nb', + 'passive_aggressive', + 'qda', + 'random_forest', + 'sgd', +].map((name) => ({ label: name, value: name })); -function ExecuteConfig({ disabled = false }: ExecuteConfigProps) { +// 回归算法 +const regressorAlgorithms = [ + 'adaboost', + 'ard_regression', + 'decision_tree', + 'extra_trees', + 'gaussian_process', + 'gradient_boosting', + 'k_nearest_neighbors', + 'liblinear_svr', + 'libsvm_svr', + 'mlp', + 'random_forest', + 'sgd', +].map((name) => ({ label: name, value: name })); + +// 特征预处理算法 +const featureAlgorithms = [ + 'densifier', + 'extra_trees_preproc_for_classification', + 'extra_trees_preproc_for_regression', + 'fast_ica', + 'feature_agglomeration', + 'kernel_pca', + 'kitchen_sinks', + 'liblinear_svc_preprocessor', + 'no_preprocessing', + 'nystroem_sampler', + 'pca', + 'polynomial', + 'random_trees_embedding', + 'select_percentile_classification', + 'select_percentile_regression', + 'select_rates_classification', + 'select_rates_regression', + 'truncatedSVD', +].map((name) => ({ label: name, value: name })); + +// 分类指标 +export const classificationMetrics = [ + 'accuracy', + 'balanced_accuracy', + 'roc_auc', + 'average_precision', + 'log_loss', + 'precision_macro', + 'precision_micro', + 'precision_samples', + 'precision_weighted', + 'recall_macro', + 'recall_micro', + 'recall_samples', + 'recall_weighted', + 'f1_macro', + 'f1_micro', + 'f1_samples', + 'f1_weighted', +].map((name) => ({ label: name, value: name })); + +// 回归指标 +export const regressionMetrics = [ + 'mean_absolute_error', + 'mean_squared_error', + 'root_mean_squared_error', + 'mean_squared_log_error', + 'median_absolute_error', + 'r2', +].map((name) => ({ label: name, value: name })); + +function ExecuteConfig() { const form = Form.useFormInstance(); - const image_type = Form.useWatch('image_type', form); - console.log(image_type); + const task_type = Form.useWatch('task_type', form); + const include_classifier = Form.useWatch('include_classifier', form); + const exclude_classifier = Form.useWatch('exclude_classifier', form); + const include_regressor = Form.useWatch('include_regressor', form); + const exclude_regressor = Form.useWatch('exclude_regressor', form); + const include_feature_preprocessor = Form.useWatch('include_feature_preprocessor', form); + const exclude_feature_preprocessor = Form.useWatch('exclude_feature_preprocessor', form); return ( <> @@ -26,27 +117,349 @@ function ExecuteConfig({ disabled = false }: ExecuteConfigProps) { - DLC - MaxCompute + 分类 + 回归 - + + + + + 0} + mode="multiple" + showSearch + /> + + + + + {({ getFieldValue }) => { - return getFieldValue('execute_type') === 'DLC' ? ( - + return getFieldValue('task_type') === AutoMLTaskType.Classification ? ( + <> + + + + 0} + showSearch + /> + + + + ) : ( - + <> + + + + 0} + showSearch + /> + + + + ); }} - + + + + + 集成模型 + 单一最佳模型 + + + + + + + {({ getFieldValue }) => { + return getFieldValue('ensemble_class') === AutoMLEnsembleClass.Default ? ( + <> + + + + + + + + + + + + + + + + + ) : null; + }} + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + {/* {(fields, { add, remove }) => ( <> @@ -126,7 +539,7 @@ function ExecuteConfig({ disabled = false }: ExecuteConfigProps) { )} - + */} ); } diff --git a/react-ui/src/pages/AutoML/components/CreateForm/ExecuteConfigDLC.tsx b/react-ui/src/pages/AutoML/components/CreateForm/ExecuteConfigDLC.tsx index d96a28e6..6b2c63e6 100644 --- a/react-ui/src/pages/AutoML/components/CreateForm/ExecuteConfigDLC.tsx +++ b/react-ui/src/pages/AutoML/components/CreateForm/ExecuteConfigDLC.tsx @@ -1,5 +1,8 @@ import CodeSelect from '@/components/CodeSelect'; -import ResourceSelect, { ResourceSelectorType } from '@/components/ResourceSelect'; +import ResourceSelect, { + requiredValidator, + ResourceSelectorType, +} from '@/components/ResourceSelect'; import { MinusCircleOutlined, PlusCircleOutlined } from '@ant-design/icons'; import { Button, Col, Flex, Form, Input, InputNumber, Radio, Row, Select } from 'antd'; import styles from './index.less'; @@ -62,12 +65,12 @@ function ExecuteConfigDLC({ disabled = false }: ExecuteConfigDLCProps) { -
- -
- - - - - - - - - - - - - - - {(fields, { add, remove }) => ( - <> - {fields.map(({ key, name, ...restField }, index) => ( - - - - - : - - - -
+ +
+ + + + + + + + + + + {(fields, { add, remove }) => ( + <> + {fields.map(({ key, name, ...restField }, index) => ( + + + - - - - - - - - - 越大越好 - 越小越好 - - - - - + )} + + + ))} + {fields.length === 0 && ( + + + + )} + + )} + + + + - - + + - + + 越大越好 + 越小越好 + diff --git a/react-ui/src/pages/AutoML/components/CreateForm/UploadConfig.tsx b/react-ui/src/pages/AutoML/components/CreateForm/UploadConfig.tsx new file mode 100644 index 00000000..429be535 --- /dev/null +++ b/react-ui/src/pages/AutoML/components/CreateForm/UploadConfig.tsx @@ -0,0 +1,80 @@ +import { getAccessToken } from '@/access'; +import KFIcon from '@/components/KFIcon'; +import SubAreaTitle from '@/components/SubAreaTitle'; +import { getFileListFromEvent } from '@/utils/ui'; +import { Button, Col, Form, Input, Row, Upload, type UploadProps } from 'antd'; +import { useState } from 'react'; +import styles from './index.less'; + +function UploadConfig() { + const [uuid] = useState(Date.now()); + // 上传组件参数 + const uploadProps: UploadProps = { + action: '/api/mmp/autoML/upload', + headers: { + Authorization: getAccessToken() || '', + }, + defaultFileList: [], + }; + + return ( + <> + + + + + + + + + + + + + + + + + + +
只允许上传 .csv 格式文件
+
+
+ + ); +} + +export default UploadConfig; diff --git a/react-ui/src/pages/AutoML/components/CreateForm/index.less b/react-ui/src/pages/AutoML/components/CreateForm/index.less index 57a44b79..8e8414a5 100644 --- a/react-ui/src/pages/AutoML/components/CreateForm/index.less +++ b/react-ui/src/pages/AutoML/components/CreateForm/index.less @@ -128,3 +128,14 @@ border: 1px dashed #e0e0e0; border-radius: 8px; } + +.upload-tip { + margin-top: 5px; + color: @text-color-secondary; + font-size: 14px; +} + +.upload-button { + height: 46px; + font-size: 15px; +} diff --git a/react-ui/src/pages/AutoML/types.ts b/react-ui/src/pages/AutoML/types.ts index 56582f9e..59496c64 100644 --- a/react-ui/src/pages/AutoML/types.ts +++ b/react-ui/src/pages/AutoML/types.ts @@ -1,6 +1,54 @@ +import { type ParameterInputObject } from '@/components/ResourceSelect'; + // 操作类型 -export enum ServiceOperationType { +export enum OperationType { Create = 'Create', // 创建 Update = 'Update', // 更新 - Restart = 'Restart', // 重启 } + +// 表单数据 +export type FormData = { + ml_name: string; // 实验名称 + ml_description: string; // 实验描述 + ensemble_class?: string; // 集成构建 + ensemble_nbest?: string; + ensemble_size?: number; + include_classifier?: string[]; + include_feature_preprocessor?: string[]; + include_regressor?: string[]; + exclude_classifier?: string[]; + exclude_feature_preprocessor?: string[]; + exclude_regressor?: string[]; + max_models_on_disc?: number; + memory_limit?: number; + metric_name?: string; + greater_is_better: boolean; + per_run_time_limit?: number; + resampling_strategy?: string; + scoring_functions?: string; + shuffle?: boolean; + seed?: number; + target_columns: string; + task_type: string; + test_size?: number; + train_size?: number; + time_left_for_this_task: number; + tmp_folder?: string; + metrics?: { name: string; value: number }[]; + dataset: ParameterInputObject; // 模型 +}; + +export type AutoMLData = { + id: string; + progress: number; + run_state: string; + state: number; + metrics?: string; + include_classifier?: string; + include_feature_preprocessor?: string; + include_regressor?: string; + exclude_classifier?: string; + exclude_feature_preprocessor?: string; + exclude_regressor?: string; + dataset?: string; +}; diff --git a/react-ui/src/services/autoML.js b/react-ui/src/services/autoML.js new file mode 100644 index 00000000..63f4cd70 --- /dev/null +++ b/react-ui/src/services/autoML.js @@ -0,0 +1,55 @@ +/* + * @Author: 赵伟 + * @Date: 2024-11-18 10:18:27 + * @Description: 自动机器学习请求 + */ + +import { request } from '@umijs/max'; + + +// 分页查询自动学习 +export function getAutoMLListReq(params) { + return request(`/api/mmp/autoML`, { + method: 'GET', + params, + }); +} + +// 查询自动学习详情 +export function getDatasetInfoReq(params) { + return request(`/api/mmp/autoML/getAutoMlDetail`, { + method: 'GET', + params, + }); +} + +// 新增自动学习 +export function addAutoMLReq(data) { + return request(`/api/mmp/autoML`, { + method: 'POST', + data, + }); +} + +// 编辑自动学习 +export function updateAutoMLReq(data) { + return request(`/api/mmp/autoML`, { + method: 'PUT', + data, + }); +} + +// 删除自动学习 +export function deleteAutoMLReq(id) { + return request(`/api/mmp/autoML/${id}`, { + method: 'DELETE', + }); +} + +// 运行自动学习 +export function runAutoMLReq(id) { + return request(`/api/mmp/autoML/${id}`, { + method: 'POST', + params, + }); +} \ No newline at end of file diff --git a/react-ui/src/utils/functional.ts b/react-ui/src/utils/functional.ts index 01514db7..6128c897 100644 --- a/react-ui/src/utils/functional.ts +++ b/react-ui/src/utils/functional.ts @@ -4,6 +4,16 @@ * @Description: 函数式编程 */ +/** + * Safely invokes a function with a given value, returning the result of the + * function or the provided value if it is `undefined` or `null`. + * + * @template T - The type of the input value. + * @template M - The type of the output value. + * @param {function} fn - The function to be invoked with the input value. + * @returns {function} A function that takes a value, invokes `fn` with it if + * it's not `undefined` or `null`, and returns the result or the original value. + */ export function safeInvoke( fn: (value: T) => M | undefined | null, ): (value: T | undefined | null) => M | undefined | null { diff --git a/react-ui/src/utils/index.ts b/react-ui/src/utils/index.ts index 67ca10d2..8feb6d51 100644 --- a/react-ui/src/utils/index.ts +++ b/react-ui/src/utils/index.ts @@ -241,3 +241,18 @@ export const tableSorter = (a: any, b: any) => { } return 0; }; + +/** + * Trim the given character from both ends of the given string. + * + * @param {string} ch - the character to trim + * @param {string} str - the string to trim + * @return {string} the trimmed string + */ +export const trimCharacter = (str: string, ch: string): string => { + if (str === null || str === undefined) { + return str; + } + const reg = new RegExp(`^${ch}|${ch}$`, 'g'); + return str.trim().replace(reg, ''); +}; From 93057f735895a9831f453fc7066ed39b0436304a Mon Sep 17 00:00:00 2001 From: chenzhihang <709011834@qq.com> Date: Mon, 25 Nov 2024 16:01:11 +0800 Subject: [PATCH 14/59] =?UTF-8?q?=E8=87=AA=E5=8A=A8=E6=9C=BA=E5=99=A8?= =?UTF-8?q?=E5=AD=A6=E4=B9=A0=E5=BC=80=E5=8F=91?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../controller/autoML/AutoMLController.java | 12 +- .../com/ruoyi/platform/domain/AutoMl.java | 11 +- .../ruoyi/platform/service/AutoMLService.java | 8 +- .../service/impl/AutoMLServiceImpl.java | 64 +++--- .../java/com/ruoyi/platform/vo/AutoMlVo.java | 183 ++++++++++++++++++ .../managementPlatform/AutoMLDaoMapper.xml | 54 ++---- 6 files changed, 246 insertions(+), 86 deletions(-) create mode 100644 ruoyi-modules/management-platform/src/main/java/com/ruoyi/platform/vo/AutoMlVo.java diff --git a/ruoyi-modules/management-platform/src/main/java/com/ruoyi/platform/controller/autoML/AutoMLController.java b/ruoyi-modules/management-platform/src/main/java/com/ruoyi/platform/controller/autoML/AutoMLController.java index 10b5a217..d859e491 100644 --- a/ruoyi-modules/management-platform/src/main/java/com/ruoyi/platform/controller/autoML/AutoMLController.java +++ b/ruoyi-modules/management-platform/src/main/java/com/ruoyi/platform/controller/autoML/AutoMLController.java @@ -5,6 +5,7 @@ import com.ruoyi.common.core.web.domain.AjaxResult; import com.ruoyi.common.core.web.domain.GenericsAjaxResult; import com.ruoyi.platform.domain.AutoMl; import com.ruoyi.platform.service.AutoMLService; +import com.ruoyi.platform.vo.AutoMlVo; import io.swagger.annotations.Api; import io.swagger.annotations.ApiOperation; import org.springframework.data.domain.Page; @@ -13,6 +14,7 @@ import org.springframework.web.bind.annotation.*; import org.springframework.web.multipart.MultipartFile; import javax.annotation.Resource; +import java.io.IOException; @RestController @RequestMapping("autoML") @@ -33,18 +35,18 @@ public class AutoMLController extends BaseController { @PostMapping @ApiOperation("新增自动机器学习") - public GenericsAjaxResult addAutoMl(@RequestBody AutoMl autoMl) throws Exception { - return genericsSuccess(this.autoMLService.save(autoMl)); + public GenericsAjaxResult addAutoMl(@RequestBody AutoMlVo autoMlVo) throws Exception { + return genericsSuccess(this.autoMLService.save(autoMlVo)); } @PutMapping @ApiOperation("编辑自动机器学习") - public GenericsAjaxResult editAutoMl(@RequestBody AutoMl autoMl) throws Exception { - return genericsSuccess(this.autoMLService.edit(autoMl)); + public GenericsAjaxResult editAutoMl(@RequestBody AutoMlVo autoMlVo) throws Exception { + return genericsSuccess(this.autoMLService.edit(autoMlVo)); } @GetMapping("/getAutoMlDetail") @ApiOperation("获取自动机器学习详细信息") - public GenericsAjaxResult getAutoMlDetail(@RequestParam("id") Long id){ + public GenericsAjaxResult getAutoMlDetail(@RequestParam("id") Long id) throws IOException { return genericsSuccess(this.autoMLService.getAutoMlDetail(id)); } diff --git a/ruoyi-modules/management-platform/src/main/java/com/ruoyi/platform/domain/AutoMl.java b/ruoyi-modules/management-platform/src/main/java/com/ruoyi/platform/domain/AutoMl.java index e37f6bbb..811168da 100644 --- a/ruoyi-modules/management-platform/src/main/java/com/ruoyi/platform/domain/AutoMl.java +++ b/ruoyi-modules/management-platform/src/main/java/com/ruoyi/platform/domain/AutoMl.java @@ -9,6 +9,7 @@ import io.swagger.annotations.ApiModelProperty; import lombok.Data; import java.util.Date; +import java.util.Map; @Data @JsonNaming(PropertyNamingStrategy.SnakeCaseStrategy.class) @@ -25,9 +26,6 @@ public class AutoMl { @ApiModelProperty(value = "任务类型:classification或regression") private String taskType; - @ApiModelProperty(value = "数据集名称") - private String datasetName; - @ApiModelProperty(value = "搜索合适模型的时间限制(以秒为单位)。通过增加这个值,auto-sklearn有更高的机会找到更好的模型。默认3600,非必传。") private Integer timeLeftForThisTask; @@ -128,9 +126,6 @@ public class AutoMl { @ApiModelProperty(value = "文件夹存放配置输出和日志文件,默认/tmp/automl") private String tmpFolder; - @ApiModelProperty(value = "数据集csv文件路径") - private String dataCsv; - @ApiModelProperty(value = "数据集csv文件中哪几列是预测目标列,逗号分隔") private String targetColumns; @@ -182,6 +177,6 @@ public class AutoMl { private Date updateTime; - @TableField(exist = false) - private VersionVo versionVo; + private String dataset; + } diff --git a/ruoyi-modules/management-platform/src/main/java/com/ruoyi/platform/service/AutoMLService.java b/ruoyi-modules/management-platform/src/main/java/com/ruoyi/platform/service/AutoMLService.java index 2b678382..78e25480 100644 --- a/ruoyi-modules/management-platform/src/main/java/com/ruoyi/platform/service/AutoMLService.java +++ b/ruoyi-modules/management-platform/src/main/java/com/ruoyi/platform/service/AutoMLService.java @@ -1,22 +1,24 @@ package com.ruoyi.platform.service; import com.ruoyi.platform.domain.AutoMl; +import com.ruoyi.platform.vo.AutoMlVo; import org.springframework.data.domain.Page; import org.springframework.data.domain.PageRequest; import org.springframework.web.multipart.MultipartFile; +import java.io.IOException; import java.util.Map; public interface AutoMLService { Page queryByPage(String mlName, PageRequest pageRequest); - AutoMl save(AutoMl autoMl) throws Exception; + AutoMl save(AutoMlVo autoMlVo) throws Exception; - String edit(AutoMl autoMl) throws Exception; + String edit(AutoMlVo autoMlVo) throws Exception; String delete(Long id); - AutoMl getAutoMlDetail(Long id); + AutoMlVo getAutoMlDetail(Long id) throws IOException; Map upload(MultipartFile file, String uuid) throws Exception; diff --git a/ruoyi-modules/management-platform/src/main/java/com/ruoyi/platform/service/impl/AutoMLServiceImpl.java b/ruoyi-modules/management-platform/src/main/java/com/ruoyi/platform/service/impl/AutoMLServiceImpl.java index f052f32e..40fd4ce2 100644 --- a/ruoyi-modules/management-platform/src/main/java/com/ruoyi/platform/service/impl/AutoMLServiceImpl.java +++ b/ruoyi-modules/management-platform/src/main/java/com/ruoyi/platform/service/impl/AutoMLServiceImpl.java @@ -5,14 +5,14 @@ import com.ruoyi.platform.constant.Constant; import com.ruoyi.platform.domain.AutoMl; import com.ruoyi.platform.mapper.AutoMLDao; import com.ruoyi.platform.service.AutoMLService; -import com.ruoyi.platform.utils.DVCUtils; -import com.ruoyi.platform.utils.FileUtil; -import com.ruoyi.platform.utils.K8sClientUtil; +import com.ruoyi.platform.utils.*; +import com.ruoyi.platform.vo.AutoMlVo; import io.kubernetes.client.openapi.models.V1Pod; import org.apache.commons.io.FileUtils; import org.apache.commons.lang3.StringUtils; import org.slf4j.Logger; import org.slf4j.LoggerFactory; +import org.springframework.beans.BeanUtils; import org.springframework.beans.factory.annotation.Value; import org.springframework.data.domain.Page; import org.springframework.data.domain.PageImpl; @@ -22,6 +22,7 @@ import org.springframework.web.multipart.MultipartFile; import javax.annotation.Resource; import java.io.File; +import java.io.IOException; import java.util.HashMap; import java.util.List; import java.util.Map; @@ -59,39 +60,35 @@ public class AutoMLServiceImpl implements AutoMLService { } @Override - public AutoMl save(AutoMl autoMl) throws Exception { - AutoMl autoMlByName = autoMLDao.getAutoMlByName(autoMl.getMlName()); + public AutoMl save(AutoMlVo autoMlVo) throws Exception { + AutoMl autoMlByName = autoMLDao.getAutoMlByName(autoMlVo.getMlName()); if (autoMlByName != null) { throw new RuntimeException("实验名称已存在"); } - + AutoMl autoMl = new AutoMl(); + BeanUtils.copyProperties(autoMlVo, autoMl); String username = SecurityUtils.getLoginUser().getUsername(); autoMl.setCreateBy(username); autoMl.setUpdateBy(username); - - String sourcePath = autoMl.getVersionVo().getUrl(); - String rootPath = localPath + username + "/automl/" + autoMl.getMlName() + "/" + autoMl.getDatasetName(); - dvcUtils.moveFiles(sourcePath, rootPath); - autoMl.setDataCsv(rootPath + "/" + autoMl.getVersionVo().getFileName()); + String datasetJson = JacksonUtil.toJSONString(autoMlVo.getDataset()); + autoMl.setDataset(datasetJson); autoMLDao.save(autoMl); return autoMl; } @Override - public String edit(AutoMl autoMl) throws Exception { - AutoMl oldAutoMl = autoMLDao.getAutoMlByName(autoMl.getMlName()); - if (oldAutoMl != null && !oldAutoMl.getId().equals(autoMl.getId())) { + public String edit(AutoMlVo autoMlVo) throws Exception { + AutoMl oldAutoMl = autoMLDao.getAutoMlByName(autoMlVo.getMlName()); + if (oldAutoMl != null && !oldAutoMl.getId().equals(autoMlVo.getId())) { throw new RuntimeException("实验名称已存在"); } - - String username = SecurityUtils.getLoginUser().getUsername(); + AutoMl autoMl = new AutoMl(); + BeanUtils.copyProperties(autoMlVo, autoMl); +// String username = SecurityUtils.getLoginUser().getUsername(); + String username = "admin"; autoMl.setUpdateBy(username); - if (autoMl.getVersionVo() != null && StringUtils.isNotEmpty(autoMl.getVersionVo().getUrl())) { - String sourcePath = autoMl.getVersionVo().getUrl(); - String rootPath = localPath + username + "/automl/" + autoMl.getMlName() + "/" + autoMl.getDatasetName(); - dvcUtils.moveFiles(sourcePath, rootPath); - autoMl.setDataCsv(rootPath + "/" + autoMl.getVersionVo().getFileName()); - } + String datasetJson = JacksonUtil.toJSONString(autoMlVo.getDataset()); + autoMl.setDataset(datasetJson); autoMLDao.edit(autoMl); return "修改成功"; @@ -104,8 +101,7 @@ public class AutoMLServiceImpl implements AutoMLService { throw new RuntimeException("服务不存在"); } -// String username = SecurityUtils.getLoginUser().getUsername(); - String username = "admin"; + String username = SecurityUtils.getLoginUser().getUsername(); String createBy = autoMl.getCreateBy(); if (!(StringUtils.equals(username, "admin") || StringUtils.equals(username, createBy))) { throw new RuntimeException("无权限删除该服务"); @@ -116,8 +112,14 @@ public class AutoMLServiceImpl implements AutoMLService { } @Override - public AutoMl getAutoMlDetail(Long id) { - return autoMLDao.getAutoMlById(id); + public AutoMlVo getAutoMlDetail(Long id) throws IOException { + AutoMl autoMl = autoMLDao.getAutoMlById(id); + AutoMlVo autoMlVo = new AutoMlVo(); + BeanUtils.copyProperties(autoMl, autoMlVo); + if (StringUtils.isNotEmpty(autoMl.getDataset())) { + autoMlVo.setDataset(JsonUtils.jsonToMap(autoMl.getDataset())); + } + return autoMlVo; } @Override @@ -151,21 +153,11 @@ public class AutoMLServiceImpl implements AutoMLService { StringBuffer command = new StringBuffer(); command.append("nohup python /opt/automl.py --task_type " + autoMl.getTaskType()); - if (StringUtils.isNotEmpty(autoMl.getDataCsv())) { - command.append(" --data_csv " + autoMl.getDataCsv()); - } else { - throw new Exception("训练数据为空"); - } if (StringUtils.isNotEmpty(autoMl.getTargetColumns())) { command.append(" --target_columns " + autoMl.getTargetColumns()); } else { throw new Exception("目标列为空"); } - if (StringUtils.isNotEmpty(autoMl.getDatasetName())) { - command.append(" --dataset_name " + autoMl.getDatasetName()); - } else { - throw new Exception("数据集名称为空"); - } // String username = SecurityUtils.getLoginUser().getUsername().toLowerCase(); String username = "admin"; diff --git a/ruoyi-modules/management-platform/src/main/java/com/ruoyi/platform/vo/AutoMlVo.java b/ruoyi-modules/management-platform/src/main/java/com/ruoyi/platform/vo/AutoMlVo.java new file mode 100644 index 00000000..3fbe5cc4 --- /dev/null +++ b/ruoyi-modules/management-platform/src/main/java/com/ruoyi/platform/vo/AutoMlVo.java @@ -0,0 +1,183 @@ +package com.ruoyi.platform.vo; + +import com.baomidou.mybatisplus.annotation.TableField; +import com.fasterxml.jackson.databind.PropertyNamingStrategy; +import com.fasterxml.jackson.databind.annotation.JsonNaming; +import io.swagger.annotations.ApiModel; +import io.swagger.annotations.ApiModelProperty; +import lombok.Data; + +import java.util.Date; +import java.util.Map; + +@Data +@JsonNaming(PropertyNamingStrategy.SnakeCaseStrategy.class) +@ApiModel(description = "自动机器学习") +public class AutoMlVo { + private Long id; + + @ApiModelProperty(value = "实验名称") + private String mlName; + + @ApiModelProperty(value = "实验描述") + private String mlDescription; + + @ApiModelProperty(value = "任务类型:classification或regression") + private String taskType; + + @ApiModelProperty(value = "搜索合适模型的时间限制(以秒为单位)。通过增加这个值,auto-sklearn有更高的机会找到更好的模型。默认3600,非必传。") + private Integer timeLeftForThisTask; + + @ApiModelProperty(value = "单次调用机器学习模型的时间限制(以秒为单位)。如果机器学习算法运行超过时间限制,将终止模型拟合。将这个值设置得足够高,这样典型的机器学习算法就可以适用于训练数据。默认600,非必传。") + private Integer perRunTimeLimit; + + @ApiModelProperty(value = "集成模型数量,如果设置为0,则没有集成。默认50,非必传。") + private Integer ensembleSize; + + @ApiModelProperty(value = "设置为None将禁用集成构建,设置为SingleBest仅使用单个最佳模型而不是集成,设置为default,它将对单目标问题使用EnsembleSelection,对多目标问题使用MultiObjectiveDummyEnsemble。默认default,非必传。") + private String ensembleClass; + + @ApiModelProperty(value = "在构建集成时只考虑ensemble_nbest模型。这是受到了“最大限度地利用集成选择”中引入的库修剪概念的启发。这是独立于ensemble_class参数的,并且这个修剪步骤是在构造集成之前完成的。默认50,非必传。") + private Integer ensembleNbest; + + @ApiModelProperty(value = "定义在磁盘中保存的模型的最大数量。额外的模型数量将被永久删除。由于这个变量的性质,它设置了一个集成可以使用多少个模型的上限。必须是大于等于1的整数。如果设置为None,则所有模型都保留在磁盘上。默认50,非必传。") + private Integer maxModelsOnDisc; + + @ApiModelProperty(value = "随机种子,将决定输出文件名。默认1,非必传。") + private Integer seed; + + @ApiModelProperty(value = "机器学习算法的内存限制(MB)。如果auto-sklearn试图分配超过memory_limit MB,它将停止拟合机器学习算法。默认3072,非必传。") + private Integer memoryLimit; + + @ApiModelProperty(value = "如果为None,则使用所有可能的分类算法。否则,指定搜索中包含的步骤和组件。有关可用组件,请参见/pipeline/components//*。与参数exclude不兼容。多选,逗号分隔。包含:adaboost\n" + + "bernoulli_nb\n" + + "decision_tree\n" + + "extra_trees\n" + + "gaussian_nb\n" + + "gradient_boosting\n" + + "k_nearest_neighbors\n" + + "lda\n" + + "liblinear_svc\n" + + "libsvm_svc\n" + + "mlp\n" + + "multinomial_nb\n" + + "passive_aggressive\n" + + "qda\n" + + "random_forest\n" + + "sgd") + private String includeClassifier; + + @ApiModelProperty(value = "如果为None,则使用所有可能的特征预处理算法。否则,指定搜索中包含的步骤和组件。有关可用组件,请参见/pipeline/components//*。与参数exclude不兼容。多选,逗号分隔。包含:densifier\n" + + "extra_trees_preproc_for_classification\n" + + "extra_trees_preproc_for_regression\n" + + "fast_ica\n" + + "feature_agglomeration\n" + + "kernel_pca\n" + + "kitchen_sinks\n" + + "liblinear_svc_preprocessor\n" + + "no_preprocessing\n" + + "nystroem_sampler\n" + + "pca\n" + + "polynomial\n" + + "random_trees_embedding\n" + + "select_percentile_classification\n" + + "select_percentile_regression\n" + + "select_rates_classification\n" + + "select_rates_regression\n" + + "truncatedSVD") + private String includeFeaturePreprocessor; + + @ApiModelProperty(value = "如果为None,则使用所有可能的回归算法。否则,指定搜索中包含的步骤和组件。有关可用组件,请参见/pipeline/components//*。与参数exclude不兼容。多选,逗号分隔。包含:adaboost,\n" + + "ard_regression,\n" + + "decision_tree,\n" + + "extra_trees,\n" + + "gaussian_process,\n" + + "gradient_boosting,\n" + + "k_nearest_neighbors,\n" + + "liblinear_svr,\n" + + "libsvm_svr,\n" + + "mlp,\n" + + "random_forest,\n" + + "sgd") + private String includeRegressor; + + private String excludeClassifier; + + private String excludeRegressor; + + private String excludeFeaturePreprocessor; + + @ApiModelProperty(value = "测试集的比率,0到1之间") + private Float testSize; + + @ApiModelProperty(value = "如何处理过拟合,如果使用基于“cv”的方法或Splitter对象,可能需要使用resampling_strategy_arguments。holdout或crossValid") + private String resamplingStrategy; + + @ApiModelProperty(value = "重采样划分训练集和验证集,训练集的比率,0到1之间") + private Float trainSize; + + @ApiModelProperty(value = "拆分数据前是否进行shuffle") + private Boolean shuffle; + + @ApiModelProperty(value = "交叉验证的折数,当resamplingStrategy为crossValid时,此项必填,为整数") + private Integer folds; + + @ApiModelProperty(value = "文件夹存放配置输出和日志文件,默认/tmp/automl") + private String tmpFolder; + + @ApiModelProperty(value = "数据集csv文件中哪几列是预测目标列,逗号分隔") + private String targetColumns; + + @ApiModelProperty(value = "自定义指标名称") + private String metricName; + + @ApiModelProperty(value = "模型优化目标指标及权重,json格式。分类的指标包含:accuracy\n" + + "balanced_accuracy\n" + + "roc_auc\n" + + "average_precision\n" + + "log_loss\n" + + "precision_macro\n" + + "precision_micro\n" + + "precision_samples\n" + + "precision_weighted\n" + + "recall_macro\n" + + "recall_micro\n" + + "recall_samples\n" + + "recall_weighted\n" + + "f1_macro\n" + + "f1_micro\n" + + "f1_samples\n" + + "f1_weighted\n" + + "回归的指标包含:mean_absolute_error\n" + + "mean_squared_error\n" + + "root_mean_squared_error\n" + + "mean_squared_log_error\n" + + "median_absolute_error\n" + + "r2") + private String metrics; + + @ApiModelProperty(value = "指标优化方向,是越大越好还是越小越好") + private Boolean greaterIsBetter; + + @ApiModelProperty(value = "模型计算并打印指标") + private String scoringFunctions; + + private Integer state; + + private String runState; + + private Double progress; + + private String createBy; + + private Date createTime; + + private String updateBy; + + private Date updateTime; + + /** + * 对应数据集 + */ + private Map dataset; +} diff --git a/ruoyi-modules/management-platform/src/main/resources/mapper/managementPlatform/AutoMLDaoMapper.xml b/ruoyi-modules/management-platform/src/main/resources/mapper/managementPlatform/AutoMLDaoMapper.xml index e5fecd5a..73a37ff0 100644 --- a/ruoyi-modules/management-platform/src/main/resources/mapper/managementPlatform/AutoMLDaoMapper.xml +++ b/ruoyi-modules/management-platform/src/main/resources/mapper/managementPlatform/AutoMLDaoMapper.xml @@ -2,23 +2,26 @@ - insert into auto_ml(ml_name, ml_description, task_type, dataset_name, time_left_for_this_task, + insert into auto_ml(ml_name, ml_description, task_type, dataset, time_left_for_this_task, per_run_time_limit, ensemble_size, ensemble_class, ensemble_nbest, max_models_on_disc, seed, memory_limit, include_classifier, include_feature_preprocessor, include_regressor, exclude_classifier, exclude_regressor, exclude_feature_preprocessor, test_size, resampling_strategy, train_size, - shuffle, folds, data_csv, target_columns, metric_name, metrics,greater_is_better,scoring_functions,tmp_folder, - create_by,update_by) - values (#{autoMl.mlName}, #{autoMl.mlDescription}, #{autoMl.taskType}, #{autoMl.datasetName}, + shuffle, folds, target_columns, metric_name, metrics, greater_is_better, scoring_functions, + tmp_folder, + create_by, update_by) + values (#{autoMl.mlName}, #{autoMl.mlDescription}, #{autoMl.taskType}, #{autoMl.dataset}, #{autoMl.timeLeftForThisTask}, #{autoMl.perRunTimeLimit}, #{autoMl.ensembleSize}, #{autoMl.ensembleClass}, #{autoMl.ensembleNbest}, #{autoMl.maxModelsOnDisc}, #{autoMl.seed}, #{autoMl.memoryLimit}, #{autoMl.includeClassifier}, #{autoMl.includeFeaturePreprocessor}, #{autoMl.includeRegressor}, #{autoMl.excludeClassifier}, - #{autoMl.excludeRegressor}, #{autoMl.excludeFeaturePreprocessor}, #{autoMl.testSize}, #{autoMl.resamplingStrategy}, + #{autoMl.excludeRegressor}, #{autoMl.excludeFeaturePreprocessor}, #{autoMl.testSize}, + #{autoMl.resamplingStrategy}, #{autoMl.trainSize}, #{autoMl.shuffle}, - #{autoMl.folds}, #{autoMl.dataCsv}, - #{autoMl.targetColumns}, #{autoMl.metricName}, #{autoMl.metrics},#{autoMl.greaterIsBetter},#{autoMl.scoringFunctions},#{autoMl.tmpFolder}, + #{autoMl.folds}, + #{autoMl.targetColumns}, #{autoMl.metricName}, #{autoMl.metrics}, #{autoMl.greaterIsBetter}, + #{autoMl.scoringFunctions}, #{autoMl.tmpFolder}, #{autoMl.createBy}, #{autoMl.updateBy}) @@ -40,8 +43,8 @@ task_type = #{autoMl.taskType}, - - dataset_name = #{autoMl.datasetName}, + + dataset = #{autoMl.dataset}, time_left_for_this_task = #{autoMl.timeLeftForThisTask}, @@ -67,24 +70,13 @@ memory_limit = #{autoMl.memoryLimit}, - - include_classifier = #{autoMl.includeClassifier}, - - - include_feature_preprocessor = #{autoMl.includeFeaturePreprocessor}, - - - include_regressor = #{autoMl.includeRegressor}, - - - exclude_classifier = #{autoMl.excludeClassifier}, - - - exclude_regressor = #{autoMl.excludeRegressor}, - - - exclude_feature_preprocessor = #{autoMl.excludeFeaturePreprocessor}, - + include_classifier = #{autoMl.includeClassifier}, + include_feature_preprocessor = #{autoMl.includeFeaturePreprocessor}, + include_regressor = #{autoMl.includeRegressor}, + exclude_classifier = #{autoMl.excludeClassifier}, + exclude_regressor = #{autoMl.excludeRegressor}, + exclude_feature_preprocessor = #{autoMl.excludeFeaturePreprocessor}, + scoring_functions = #{autoMl.scoringFunctions}, test_size = #{autoMl.testSize}, @@ -100,9 +92,6 @@ folds = #{autoMl.folds}, - - data_csv = #{autoMl.dataCsv}, - tmp_folder = #{autoMl.tmpFolder}, @@ -112,12 +101,9 @@ metrics = #{autoMl.metrics}, - + greater_is_better = #{autoMl.greaterIsBetter}, - - scoring_functions = #{autoMl.scoringFunctions}, - target_columns = #{autoMl.targetColumns}, From 6a357c5bb4d16498ac8a3a344db475dd445fb891 Mon Sep 17 00:00:00 2001 From: cp3hnu Date: Tue, 26 Nov 2024 09:50:40 +0800 Subject: [PATCH 15/59] =?UTF-8?q?feat:=20=E6=B5=81=E6=B0=B4=E7=BA=BF?= =?UTF-8?q?=E9=9B=86=E6=88=90=E6=9C=8D=E5=8A=A1=E9=83=A8=E7=BD=B2?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../src/components/ParameterSelect/config.tsx | 22 +++++++++++++ .../ModelDeployment/CreateVersion/index.tsx | 33 +++++++++---------- 2 files changed, 37 insertions(+), 18 deletions(-) diff --git a/react-ui/src/components/ParameterSelect/config.tsx b/react-ui/src/components/ParameterSelect/config.tsx index 757e2a11..2548f44c 100644 --- a/react-ui/src/components/ParameterSelect/config.tsx +++ b/react-ui/src/components/ParameterSelect/config.tsx @@ -1,7 +1,10 @@ +import { ServiceData } from '@/pages/ModelDeployment/types'; import { getDatasetList, getModelList } from '@/services/dataset/index.js'; +import { getServiceListReq } from '@/services/modelDeployment'; import { getComputingResourceReq } from '@/services/pipeline'; import { ComputingResource } from '@/types'; import { type SelectProps } from 'antd'; +import { pick } from 'lodash'; // 过滤资源规格 const filterResourceStandard: SelectProps['filterOption'] = ( @@ -62,6 +65,25 @@ export const paramSelectConfig: Record = { }, optionFilterProp: 'name', }, + service: { + getOptions: async () => { + const res = await getServiceListReq({ + page: 0, + size: 1000, + }); + return ( + res?.data?.content?.map((item: ServiceData) => ({ + label: item.service_name, + value: JSON.stringify(pick(item, ['id', 'service_name'])), + })) ?? [] + ); + }, + fieldNames: { + label: 'label', + value: 'value', + }, + optionFilterProp: 'label', + }, resource: { getOptions: async () => { const res = await getComputingResourceReq({ diff --git a/react-ui/src/pages/ModelDeployment/CreateVersion/index.tsx b/react-ui/src/pages/ModelDeployment/CreateVersion/index.tsx index 60ac8fe8..56aeb333 100644 --- a/react-ui/src/pages/ModelDeployment/CreateVersion/index.tsx +++ b/react-ui/src/pages/ModelDeployment/CreateVersion/index.tsx @@ -24,7 +24,7 @@ import SessionStorage from '@/utils/sessionStorage'; import { modalConfirm } from '@/utils/ui'; 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, InputNumber, Row, Select } from 'antd'; import { omit, pick } from 'lodash'; import { useEffect, useState } from 'react'; import { @@ -120,11 +120,11 @@ function CreateServiceVersion() { // 创建版本 const createServiceVersion = async (formData: FormData) => { - const envList = formData['env_variables'] ?? []; + const envList = formData['env_variables']; const image = formData['image']; const model = formData['model']; const codeConfig = formData['code_config']; - const envVariables = envList.reduce((acc, cur) => { + const envVariables = envList?.reduce((acc, cur) => { acc[cur.key] = cur.value; return acc; }, {} as Record); @@ -139,9 +139,11 @@ function CreateServiceVersion() { pick(model, ['id', 'name', 'version', 'path', 'identifier', 'owner', 'showValue']), { showValue: 'show_value' }, ), - code_config: changePropertyName(pick(codeConfig, ['code_path', 'branch', 'showValue']), { - showValue: 'show_value', - }), + code_config: codeConfig + ? changePropertyName(pick(codeConfig, ['code_path', 'branch', 'showValue']), { + showValue: 'show_value', + }) + : undefined, service_id: serviceInfo?.id, }; @@ -334,17 +336,7 @@ function CreateServiceVersion() { - + - + From 865af276f321badf2bebb6c0684957d168acd228 Mon Sep 17 00:00:00 2001 From: cp3hnu Date: Tue, 26 Nov 2024 14:03:29 +0800 Subject: [PATCH 16/59] =?UTF-8?q?feat:=20=E5=AE=8C=E6=88=90=E8=87=AA?= =?UTF-8?q?=E5=8A=A8=E6=9C=BA=E5=99=A8=E5=AD=A6=E4=B9=A0=E5=A4=8D=E5=88=B6?= =?UTF-8?q?=E5=8A=9F=E8=83=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- react-ui/src/pages/AutoML/Create/index.tsx | 27 +- react-ui/src/pages/AutoML/List/index.tsx | 29 +- .../components/CreateForm/ExecuteConfig.tsx | 2 +- .../components/CreateForm/TrialConfig.tsx | 31 +- .../AutoML/components/CreateForm/index.less | 266 +++++++++--------- .../components/ExecuteScheduleCell/index.tsx | 9 +- react-ui/src/pages/AutoML/types.ts | 5 +- react-ui/src/utils/sessionStorage.ts | 2 + 8 files changed, 200 insertions(+), 171 deletions(-) diff --git a/react-ui/src/pages/AutoML/Create/index.tsx b/react-ui/src/pages/AutoML/Create/index.tsx index 743d8377..26e1866c 100644 --- a/react-ui/src/pages/AutoML/Create/index.tsx +++ b/react-ui/src/pages/AutoML/Create/index.tsx @@ -10,6 +10,7 @@ import { addAutoMLReq, getDatasetInfoReq, updateAutoMLReq } from '@/services/aut import { parseJsonText, trimCharacter } from '@/utils'; import { safeInvoke } from '@/utils/functional'; import { to } from '@/utils/promise'; +import SessionStorage from '@/utils/sessionStorage'; import { useNavigate, useParams } from '@umijs/max'; import { App, Button, Form } from 'antd'; import { omit } from 'lodash'; @@ -29,13 +30,25 @@ function CreateAutoML() { const id = safeInvoke(Number)(params.id); useEffect(() => { - if (id) { - getAutoMLInfo(); + // 复制和新建 + const recordId = SessionStorage.getItem(SessionStorage.autoMLRecordIDKey); + if (recordId && !Number.isNaN(Number(recordId))) { + getAutoMLInfo(Number(recordId), true); + } + return () => { + SessionStorage.removeItem(SessionStorage.autoMLRecordIDKey); + }; + }, []); + + useEffect(() => { + // 编辑 + if (id && !Number.isNaN(id)) { + getAutoMLInfo(id, false); } }, [id]); // 获取服务详情 - const getAutoMLInfo = async () => { + const getAutoMLInfo = async (id: number, isCopy = false) => { const [res] = await to(getDatasetInfoReq({ id })); if (res && res.data) { const autoMLInfo: AutoMLData = res.data; @@ -47,6 +60,8 @@ function CreateAutoML() { exclude_feature_preprocessor: exclude_feature_preprocessor_str, exclude_regressor: exclude_regressor_str, metrics: metrics_str, + ml_name: ml_name_str, + ...rest } = autoMLInfo; const include_classifier = include_classifier_str?.split(',').filter(Boolean); const include_feature_preprocessor = include_feature_preprocessor_str @@ -63,9 +78,10 @@ function CreateAutoML() { name: key, value, })); + const ml_name = isCopy ? `${ml_name_str}-copy` : ml_name_str; const formData = { - ...autoMLInfo, + ...rest, include_classifier, include_feature_preprocessor, include_regressor, @@ -73,6 +89,7 @@ function CreateAutoML() { exclude_feature_preprocessor, exclude_regressor, metrics, + ml_name, }; form.setFieldsValue(formData); @@ -135,7 +152,7 @@ function CreateAutoML() { let buttonText = '新建'; let title = '新增实验'; if (id) { - title = '更新实验'; + title = '编辑实验'; buttonText = '更新'; } diff --git a/react-ui/src/pages/AutoML/List/index.tsx b/react-ui/src/pages/AutoML/List/index.tsx index 9ef6eda6..be76e591 100644 --- a/react-ui/src/pages/AutoML/List/index.tsx +++ b/react-ui/src/pages/AutoML/List/index.tsx @@ -9,6 +9,7 @@ import { useCacheState } from '@/hooks/pageCacheState'; import { deleteAutoMLReq, getAutoMLListReq } from '@/services/autoML'; import themes from '@/styles/theme.less'; import { to } from '@/utils/promise'; +import SessionStorage from '@/utils/sessionStorage'; import tableCellRender, { TableCellValueType } from '@/utils/table'; import { modalConfirm } from '@/utils/ui'; import { useNavigate } from '@umijs/max'; @@ -90,7 +91,7 @@ function AutoMLList() { // 处理删除 const handleAutoMLDelete = (record: AutoMLData) => { modalConfirm({ - title: '删除后,该服务将不可恢复', + title: '删除后,该实验将不可恢复', content: '是否确认删除?', onOk: () => { deleteService(record); @@ -99,15 +100,21 @@ function AutoMLList() { }; // 创建、编辑 - const createService = (record?: AutoMLData) => { + const createService = (record?: AutoMLData, isCopy: boolean = false) => { setCacheState({ pagination, searchText, }); if (record) { - navigate(`/pipeline/autoML/edit/${record.id}`); + if (isCopy) { + SessionStorage.setItem(SessionStorage.autoMLRecordIDKey, record.id, false); + navigate(`/pipeline/autoML/create`); + } else { + navigate(`/pipeline/autoML/edit/${record.id}`); + } } else { + SessionStorage.setItem(SessionStorage.autoMLRecordIDKey, '', false); navigate(`/pipeline/autoML/create`); } }; @@ -139,7 +146,7 @@ function AutoMLList() { title: '序号', dataIndex: 'index', key: 'index', - width: '20%', + width: 80, render: tableCellRender(false, TableCellValueType.Index, { page: pagination.current! - 1, pageSize: pagination.pageSize!, @@ -166,7 +173,7 @@ function AutoMLList() { title: '状态', dataIndex: 'run_state', key: 'run_state', - width: '20%', + width: 100, render: RunStatusCell, }, { @@ -207,7 +214,7 @@ function AutoMLList() { size="small" key="edit" icon={} - onClick={() => createService(record)} + onClick={() => createService(record, false)} > 编辑 @@ -216,17 +223,11 @@ function AutoMLList() { size="small" key="copy" icon={} - onClick={() => toDetail(record)} + onClick={() => createService(record, true)} > 复制 - - + form.resetFields(['metrics'])}> 分类 回归 diff --git a/react-ui/src/pages/AutoML/components/CreateForm/TrialConfig.tsx b/react-ui/src/pages/AutoML/components/CreateForm/TrialConfig.tsx index d65b62e8..9201009a 100644 --- a/react-ui/src/pages/AutoML/components/CreateForm/TrialConfig.tsx +++ b/react-ui/src/pages/AutoML/components/CreateForm/TrialConfig.tsx @@ -8,6 +8,14 @@ import styles from './index.less'; function TrialConfig() { const form = Form.useFormInstance(); const task_type = Form.useWatch('task_type', form); + const metrics = Form.useWatch('metrics', form) || []; + const selectedMetrics = metrics + .map((item: { name: string; value: number }) => item?.name) + .filter(Boolean); + const allMetricsOptions = + task_type === AutoMLTaskType.Classification ? classificationMetrics : regressionMetrics; + const metricsOptions = allMetricsOptions.filter((item) => !selectedMetrics.includes(item.label)); + return ( <> - @@ -30,9 +37,9 @@ function TrialConfig() { {(fields, { add, remove }) => ( <> {fields.map(({ key, name, ...restField }, index) => ( - + - : + : -
+ )} -
+
))} {fields.length === 0 && ( diff --git a/react-ui/src/pages/AutoML/components/CreateForm/index.less b/react-ui/src/pages/AutoML/components/CreateForm/index.less index 8e8414a5..5ee9aacc 100644 --- a/react-ui/src/pages/AutoML/components/CreateForm/index.less +++ b/react-ui/src/pages/AutoML/components/CreateForm/index.less @@ -6,136 +6,136 @@ } } -.command { - width: 83.33%; - margin-bottom: 20px; - border: 1px solid rgba(234, 234, 234, 0.8); - border-radius: 4px; - &__header { - height: 50px; - padding-left: 8px; - color: @text-color; - font-size: @font-size; - background: #f8f8f9; - border-radius: 4px 4px 0px 0px; - - &__name { - flex: none; - width: 100px; - } - &__command { - flex: 1; - margin-right: 15px; - } - - &__operation { - flex: none; - width: 100px; - } - } - &__body { - padding: 8px; - border-bottom: 1px solid rgba(234, 234, 234, 0.8); - - &:last-child { - border-bottom: none; - } - - &__name { - flex: none; - width: 100px; - } - - &__command { - flex: 1; - margin-right: 15px; - margin-bottom: 0 !important; - } - - &__operation { - flex: none; - width: 100px; - } - } - - &__add { - display: flex; - align-items: center; - justify-content: center; - padding: 15px 0; - } -} - -.hyper-parameter { - width: 83.33%; - margin-bottom: 20px; - border: 1px solid rgba(234, 234, 234, 0.8); - border-radius: 4px; - &__header { - height: 50px; - padding-left: 8px; - color: @text-color; - font-size: @font-size; - background: #f8f8f9; - border-radius: 4px 4px 0px 0px; - - &__name, - &__type, - &__space { - flex: 1; - margin-right: 15px; - } - - &__operation { - flex: none; - width: 100px; - } - } - &__body { - padding: 8px; - border-bottom: 1px solid rgba(234, 234, 234, 0.8); - - &:last-child { - border-bottom: none; - } - - &__name, - &__type, - &__space { - flex: 1; - margin-right: 15px; - margin-bottom: 0 !important; - } - - &__operation { - flex: none; - width: 100px; - } - } - - &__add { - display: flex; - align-items: center; - justify-content: center; - padding: 15px 0; - } -} - -.trial-metrics { - width: calc(41.67% - 6px); - margin-bottom: 20px; - padding: 0 20px; - border: 1px dashed #e0e0e0; - border-radius: 8px; -} - -.upload-tip { - margin-top: 5px; - color: @text-color-secondary; - font-size: 14px; -} - -.upload-button { - height: 46px; - font-size: 15px; -} +// .command { +// width: 83.33%; +// margin-bottom: 20px; +// border: 1px solid rgba(234, 234, 234, 0.8); +// border-radius: 4px; +// &__header { +// height: 50px; +// padding-left: 8px; +// color: @text-color; +// font-size: @font-size; +// background: #f8f8f9; +// border-radius: 4px 4px 0px 0px; + +// &__name { +// flex: none; +// width: 100px; +// } +// &__command { +// flex: 1; +// margin-right: 15px; +// } + +// &__operation { +// flex: none; +// width: 100px; +// } +// } +// &__body { +// padding: 8px; +// border-bottom: 1px solid rgba(234, 234, 234, 0.8); + +// &:last-child { +// border-bottom: none; +// } + +// &__name { +// flex: none; +// width: 100px; +// } + +// &__command { +// flex: 1; +// margin-right: 15px; +// margin-bottom: 0 !important; +// } + +// &__operation { +// flex: none; +// width: 100px; +// } +// } + +// &__add { +// display: flex; +// align-items: center; +// justify-content: center; +// padding: 15px 0; +// } +// } + +// .hyper-parameter { +// width: 83.33%; +// margin-bottom: 20px; +// border: 1px solid rgba(234, 234, 234, 0.8); +// border-radius: 4px; +// &__header { +// height: 50px; +// padding-left: 8px; +// color: @text-color; +// font-size: @font-size; +// background: #f8f8f9; +// border-radius: 4px 4px 0px 0px; + +// &__name, +// &__type, +// &__space { +// flex: 1; +// margin-right: 15px; +// } + +// &__operation { +// flex: none; +// width: 100px; +// } +// } +// &__body { +// padding: 8px; +// border-bottom: 1px solid rgba(234, 234, 234, 0.8); + +// &:last-child { +// border-bottom: none; +// } + +// &__name, +// &__type, +// &__space { +// flex: 1; +// margin-right: 15px; +// margin-bottom: 0 !important; +// } + +// &__operation { +// flex: none; +// width: 100px; +// } +// } + +// &__add { +// display: flex; +// align-items: center; +// justify-content: center; +// padding: 15px 0; +// } +// } + +// .trial-metrics { +// width: calc(41.67% - 6px); +// margin-bottom: 20px; +// padding: 0 20px; +// border: 1px dashed #e0e0e0; +// border-radius: 8px; +// } + +// .upload-tip { +// margin-top: 5px; +// color: @text-color-secondary; +// font-size: 14px; +// } + +// .upload-button { +// height: 46px; +// font-size: 15px; +// } diff --git a/react-ui/src/pages/AutoML/components/ExecuteScheduleCell/index.tsx b/react-ui/src/pages/AutoML/components/ExecuteScheduleCell/index.tsx index b1b9ae29..c2af2074 100644 --- a/react-ui/src/pages/AutoML/components/ExecuteScheduleCell/index.tsx +++ b/react-ui/src/pages/AutoML/components/ExecuteScheduleCell/index.tsx @@ -6,18 +6,19 @@ import styles from './index.less'; -function ExecuteScheduleCell(status?: any) { +function ExecuteScheduleCell(progress?: number) { + const width = (progress || 0) * 100 + '%'; return (
- + {/* 1/2 - + */}
); } diff --git a/react-ui/src/pages/AutoML/types.ts b/react-ui/src/pages/AutoML/types.ts index 59496c64..bd61f941 100644 --- a/react-ui/src/pages/AutoML/types.ts +++ b/react-ui/src/pages/AutoML/types.ts @@ -51,4 +51,7 @@ export type AutoMLData = { exclude_feature_preprocessor?: string; exclude_regressor?: string; dataset?: string; -}; +} & Omit< + FormData, + 'metrics|dataset|include_classifier|include_feature_preprocessor|include_regressor|exclude_classifier|exclude_feature_preprocessor|exclude_regressor' +>; diff --git a/react-ui/src/utils/sessionStorage.ts b/react-ui/src/utils/sessionStorage.ts index 8ffa8836..b71a35fd 100644 --- a/react-ui/src/utils/sessionStorage.ts +++ b/react-ui/src/utils/sessionStorage.ts @@ -11,6 +11,8 @@ export default class SessionStorage { static readonly editorUrlKey = 'editor-url'; // 客户端信息 static readonly clientInfoKey = 'client-info'; + // 自动机器学习记录ID + static readonly autoMLRecordIDKey = 'auto-ml-record-id'; static getItem(key: string, isObject: boolean = false) { const jsonStr = sessionStorage.getItem(key); From db0c9abc9e7541c6e652093f61a30ec55694778e Mon Sep 17 00:00:00 2001 From: cp3hnu Date: Wed, 27 Nov 2024 08:38:39 +0800 Subject: [PATCH 17/59] =?UTF-8?q?feat:=20=E6=9C=BA=E5=99=A8=E5=AD=A6?= =?UTF-8?q?=E4=B9=A0=E8=AE=AD=E7=BB=83=E9=9B=86=E6=AF=94=E7=8E=87=E6=B7=BB?= =?UTF-8?q?=E5=8A=A0=E8=AF=B4=E6=98=8E?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../pages/AutoML/components/CreateForm/ExecuteConfig.tsx | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/react-ui/src/pages/AutoML/components/CreateForm/ExecuteConfig.tsx b/react-ui/src/pages/AutoML/components/CreateForm/ExecuteConfig.tsx index 99acd712..87d5ad3f 100644 --- a/react-ui/src/pages/AutoML/components/CreateForm/ExecuteConfig.tsx +++ b/react-ui/src/pages/AutoML/components/CreateForm/ExecuteConfig.tsx @@ -397,7 +397,11 @@ function ExecuteConfig() {
- + @@ -408,7 +412,7 @@ function ExecuteConfig() { From a3fa7d4f1b5a12510f108c16fdbd55e96873f801 Mon Sep 17 00:00:00 2001 From: chenzhihang <709011834@qq.com> Date: Wed, 27 Nov 2024 10:17:51 +0800 Subject: [PATCH 18/59] =?UTF-8?q?=E8=87=AA=E5=8A=A8=E6=9C=BA=E5=99=A8?= =?UTF-8?q?=E5=AD=A6=E4=B9=A0=E5=BC=80=E5=8F=91?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- ...LController.java => AutoMlController.java} | 20 +++--- .../autoML/AutoMlInsController.java | 49 ++++++++++++++ .../com/ruoyi/platform/domain/AutoMlIns.java | 35 ++++++++++ .../mapper/{AutoMLDao.java => AutoMlDao.java} | 2 +- .../ruoyi/platform/mapper/AutoMlInsDao.java | 19 ++++++ .../platform/service/AutoMlInsService.java | 18 ++++++ ...{AutoMLService.java => AutoMlService.java} | 2 +- .../service/impl/AutoMlInsServiceImpl.java | 60 +++++++++++++++++ ...erviceImpl.java => AutoMlServiceImpl.java} | 30 ++++----- .../{AutoMLDaoMapper.xml => AutoMlDao.xml} | 2 +- .../managementPlatform/AutoMlInsDao.xml | 64 +++++++++++++++++++ 11 files changed, 272 insertions(+), 29 deletions(-) rename ruoyi-modules/management-platform/src/main/java/com/ruoyi/platform/controller/autoML/{AutoMLController.java => AutoMlController.java} (80%) create mode 100644 ruoyi-modules/management-platform/src/main/java/com/ruoyi/platform/controller/autoML/AutoMlInsController.java create mode 100644 ruoyi-modules/management-platform/src/main/java/com/ruoyi/platform/domain/AutoMlIns.java rename ruoyi-modules/management-platform/src/main/java/com/ruoyi/platform/mapper/{AutoMLDao.java => AutoMlDao.java} (94%) create mode 100644 ruoyi-modules/management-platform/src/main/java/com/ruoyi/platform/mapper/AutoMlInsDao.java create mode 100644 ruoyi-modules/management-platform/src/main/java/com/ruoyi/platform/service/AutoMlInsService.java rename ruoyi-modules/management-platform/src/main/java/com/ruoyi/platform/service/{AutoMLService.java => AutoMlService.java} (95%) create mode 100644 ruoyi-modules/management-platform/src/main/java/com/ruoyi/platform/service/impl/AutoMlInsServiceImpl.java rename ruoyi-modules/management-platform/src/main/java/com/ruoyi/platform/service/impl/{AutoMLServiceImpl.java => AutoMlServiceImpl.java} (92%) rename ruoyi-modules/management-platform/src/main/resources/mapper/managementPlatform/{AutoMLDaoMapper.xml => AutoMlDao.xml} (99%) create mode 100644 ruoyi-modules/management-platform/src/main/resources/mapper/managementPlatform/AutoMlInsDao.xml diff --git a/ruoyi-modules/management-platform/src/main/java/com/ruoyi/platform/controller/autoML/AutoMLController.java b/ruoyi-modules/management-platform/src/main/java/com/ruoyi/platform/controller/autoML/AutoMlController.java similarity index 80% rename from ruoyi-modules/management-platform/src/main/java/com/ruoyi/platform/controller/autoML/AutoMLController.java rename to ruoyi-modules/management-platform/src/main/java/com/ruoyi/platform/controller/autoML/AutoMlController.java index d859e491..981b9fb1 100644 --- a/ruoyi-modules/management-platform/src/main/java/com/ruoyi/platform/controller/autoML/AutoMLController.java +++ b/ruoyi-modules/management-platform/src/main/java/com/ruoyi/platform/controller/autoML/AutoMlController.java @@ -4,7 +4,7 @@ import com.ruoyi.common.core.web.controller.BaseController; import com.ruoyi.common.core.web.domain.AjaxResult; import com.ruoyi.common.core.web.domain.GenericsAjaxResult; import com.ruoyi.platform.domain.AutoMl; -import com.ruoyi.platform.service.AutoMLService; +import com.ruoyi.platform.service.AutoMlService; import com.ruoyi.platform.vo.AutoMlVo; import io.swagger.annotations.Api; import io.swagger.annotations.ApiOperation; @@ -19,10 +19,10 @@ import java.io.IOException; @RestController @RequestMapping("autoML") @Api("自动机器学习") -public class AutoMLController extends BaseController { +public class AutoMlController extends BaseController { @Resource - private AutoMLService autoMLService; + private AutoMlService autoMlService; @GetMapping @ApiOperation("分页查询") @@ -30,42 +30,42 @@ public class AutoMLController extends BaseController { @RequestParam("size") int size, @RequestParam(value = "ml_name", required = false) String mlName) { PageRequest pageRequest = PageRequest.of(page, size); - return genericsSuccess(this.autoMLService.queryByPage(mlName, pageRequest)); + return genericsSuccess(this.autoMlService.queryByPage(mlName, pageRequest)); } @PostMapping @ApiOperation("新增自动机器学习") public GenericsAjaxResult addAutoMl(@RequestBody AutoMlVo autoMlVo) throws Exception { - return genericsSuccess(this.autoMLService.save(autoMlVo)); + return genericsSuccess(this.autoMlService.save(autoMlVo)); } @PutMapping @ApiOperation("编辑自动机器学习") public GenericsAjaxResult editAutoMl(@RequestBody AutoMlVo autoMlVo) throws Exception { - return genericsSuccess(this.autoMLService.edit(autoMlVo)); + return genericsSuccess(this.autoMlService.edit(autoMlVo)); } @GetMapping("/getAutoMlDetail") @ApiOperation("获取自动机器学习详细信息") public GenericsAjaxResult getAutoMlDetail(@RequestParam("id") Long id) throws IOException { - return genericsSuccess(this.autoMLService.getAutoMlDetail(id)); + return genericsSuccess(this.autoMlService.getAutoMlDetail(id)); } @DeleteMapping("{id}") @ApiOperation("删除自动机器学习") public GenericsAjaxResult deleteAutoMl(@PathVariable("id") Long id) { - return genericsSuccess(this.autoMLService.delete(id)); + return genericsSuccess(this.autoMlService.delete(id)); } @CrossOrigin(origins = "*", allowedHeaders = "*") @PostMapping("/upload") @ApiOperation(value = "上传数据文件csv", notes = "上传数据文件csv,并将信息存入数据库。") public AjaxResult upload(@RequestParam("file") MultipartFile file, @RequestParam("uuid") String uuid) throws Exception { - return AjaxResult.success(this.autoMLService.upload(file, uuid)); + return AjaxResult.success(this.autoMlService.upload(file, uuid)); } @PostMapping("{id}") @ApiOperation("运行自动机器学习实验") public GenericsAjaxResult runAutoML(@PathVariable("id") Long id) throws Exception { - return genericsSuccess(this.autoMLService.runAutoMlIns(id)); + return genericsSuccess(this.autoMlService.runAutoMlIns(id)); } } diff --git a/ruoyi-modules/management-platform/src/main/java/com/ruoyi/platform/controller/autoML/AutoMlInsController.java b/ruoyi-modules/management-platform/src/main/java/com/ruoyi/platform/controller/autoML/AutoMlInsController.java new file mode 100644 index 00000000..3aaba610 --- /dev/null +++ b/ruoyi-modules/management-platform/src/main/java/com/ruoyi/platform/controller/autoML/AutoMlInsController.java @@ -0,0 +1,49 @@ +package com.ruoyi.platform.controller.autoML; + +import com.ruoyi.common.core.web.controller.BaseController; +import com.ruoyi.common.core.web.domain.GenericsAjaxResult; +import com.ruoyi.platform.domain.AutoMlIns; +import com.ruoyi.platform.service.AutoMlInsService; +import io.swagger.annotations.Api; +import io.swagger.annotations.ApiOperation; +import org.springframework.data.domain.Page; +import org.springframework.data.domain.PageRequest; +import org.springframework.web.bind.annotation.*; + +import javax.annotation.Resource; +import java.io.IOException; +import java.util.List; + +@RestController +@RequestMapping("autoMLIns") +@Api("自动机器学习实验实例") +public class AutoMlInsController extends BaseController { + + @Resource + private AutoMlInsService autoMLInsService; + + @GetMapping + @ApiOperation("分页查询") + public GenericsAjaxResult> queryByPage(AutoMlIns autoMlIns, int page, int size) throws IOException { + PageRequest pageRequest = PageRequest.of(page, size); + return genericsSuccess(this.autoMLInsService.queryByPage(autoMlIns, pageRequest)); + } + + @PostMapping + @ApiOperation("新增实验实例") + public GenericsAjaxResult add(@RequestBody AutoMlIns autoMlIns) { + return genericsSuccess(this.autoMLInsService.insert(autoMlIns)); + } + + @DeleteMapping("{id}") + @ApiOperation("删除实验实例") + public GenericsAjaxResult deleteById(@PathVariable("id") Long id) { + return genericsSuccess(this.autoMLInsService.removeById(id)); + } + + @DeleteMapping("batchDelete") + @ApiOperation("批量删除实验实例") + public GenericsAjaxResult batchDelete(@RequestBody List ids) throws Exception { + return genericsSuccess(this.autoMLInsService.batchDelete(ids)); + } +} diff --git a/ruoyi-modules/management-platform/src/main/java/com/ruoyi/platform/domain/AutoMlIns.java b/ruoyi-modules/management-platform/src/main/java/com/ruoyi/platform/domain/AutoMlIns.java new file mode 100644 index 00000000..b5d023ae --- /dev/null +++ b/ruoyi-modules/management-platform/src/main/java/com/ruoyi/platform/domain/AutoMlIns.java @@ -0,0 +1,35 @@ +package com.ruoyi.platform.domain; + +import com.fasterxml.jackson.databind.PropertyNamingStrategy; +import com.fasterxml.jackson.databind.annotation.JsonNaming; +import io.swagger.annotations.ApiModel; +import lombok.Data; + +import java.util.Date; + +@Data +@JsonNaming(PropertyNamingStrategy.SnakeCaseStrategy.class) +@ApiModel(description = "自动机器学习实验实例") +public class AutoMlIns { + private Long id; + + private Long autoMlId; + + private String modelPath; + + private String imgPath; + + private Integer state; + + private String status; + + private String nodeStatus; + + private String nodeResult; + + private String param; + + private Date createTime; + + private Date updateTime; +} diff --git a/ruoyi-modules/management-platform/src/main/java/com/ruoyi/platform/mapper/AutoMLDao.java b/ruoyi-modules/management-platform/src/main/java/com/ruoyi/platform/mapper/AutoMlDao.java similarity index 94% rename from ruoyi-modules/management-platform/src/main/java/com/ruoyi/platform/mapper/AutoMLDao.java rename to ruoyi-modules/management-platform/src/main/java/com/ruoyi/platform/mapper/AutoMlDao.java index f99f2156..eeffd683 100644 --- a/ruoyi-modules/management-platform/src/main/java/com/ruoyi/platform/mapper/AutoMLDao.java +++ b/ruoyi-modules/management-platform/src/main/java/com/ruoyi/platform/mapper/AutoMlDao.java @@ -6,7 +6,7 @@ import org.springframework.data.domain.Pageable; import java.util.List; -public interface AutoMLDao { +public interface AutoMlDao { long count(@Param("mlName") String mlName); List queryByPage(@Param("mlName") String mlName, @Param("pageable") Pageable pageable); diff --git a/ruoyi-modules/management-platform/src/main/java/com/ruoyi/platform/mapper/AutoMlInsDao.java b/ruoyi-modules/management-platform/src/main/java/com/ruoyi/platform/mapper/AutoMlInsDao.java new file mode 100644 index 00000000..7468c8f3 --- /dev/null +++ b/ruoyi-modules/management-platform/src/main/java/com/ruoyi/platform/mapper/AutoMlInsDao.java @@ -0,0 +1,19 @@ +package com.ruoyi.platform.mapper; + +import com.ruoyi.platform.domain.AutoMlIns; +import org.apache.ibatis.annotations.Param; +import org.springframework.data.domain.Pageable; + +import java.util.List; + +public interface AutoMlInsDao { + long count(@Param("autoMlIns") AutoMlIns autoMlIns); + + List queryAllByLimit(@Param("autoMlIns") AutoMlIns autoMlIns, @Param("pageable") Pageable pageable); + + int insert(@Param("autoMlIns") AutoMlIns autoMlIns); + + int update(@Param("autoMlIns") AutoMlIns autoMlIns); + + AutoMlIns queryById(@Param("id") Long id); +} diff --git a/ruoyi-modules/management-platform/src/main/java/com/ruoyi/platform/service/AutoMlInsService.java b/ruoyi-modules/management-platform/src/main/java/com/ruoyi/platform/service/AutoMlInsService.java new file mode 100644 index 00000000..4b7a8601 --- /dev/null +++ b/ruoyi-modules/management-platform/src/main/java/com/ruoyi/platform/service/AutoMlInsService.java @@ -0,0 +1,18 @@ +package com.ruoyi.platform.service; +import org.springframework.data.domain.Page; +import org.springframework.data.domain.PageRequest; +import com.ruoyi.platform.domain.AutoMlIns; + +import java.io.IOException; +import java.util.List; + +public interface AutoMlInsService { + + Page queryByPage(AutoMlIns autoMlIns, PageRequest pageRequest) throws IOException; + + AutoMlIns insert(AutoMlIns autoMlIns); + + String removeById(Long id); + + String batchDelete(List ids); +} diff --git a/ruoyi-modules/management-platform/src/main/java/com/ruoyi/platform/service/AutoMLService.java b/ruoyi-modules/management-platform/src/main/java/com/ruoyi/platform/service/AutoMlService.java similarity index 95% rename from ruoyi-modules/management-platform/src/main/java/com/ruoyi/platform/service/AutoMLService.java rename to ruoyi-modules/management-platform/src/main/java/com/ruoyi/platform/service/AutoMlService.java index 78e25480..a2bad796 100644 --- a/ruoyi-modules/management-platform/src/main/java/com/ruoyi/platform/service/AutoMLService.java +++ b/ruoyi-modules/management-platform/src/main/java/com/ruoyi/platform/service/AutoMlService.java @@ -9,7 +9,7 @@ import org.springframework.web.multipart.MultipartFile; import java.io.IOException; import java.util.Map; -public interface AutoMLService { +public interface AutoMlService { Page queryByPage(String mlName, PageRequest pageRequest); AutoMl save(AutoMlVo autoMlVo) throws Exception; diff --git a/ruoyi-modules/management-platform/src/main/java/com/ruoyi/platform/service/impl/AutoMlInsServiceImpl.java b/ruoyi-modules/management-platform/src/main/java/com/ruoyi/platform/service/impl/AutoMlInsServiceImpl.java new file mode 100644 index 00000000..5b190226 --- /dev/null +++ b/ruoyi-modules/management-platform/src/main/java/com/ruoyi/platform/service/impl/AutoMlInsServiceImpl.java @@ -0,0 +1,60 @@ +package com.ruoyi.platform.service.impl; + +import com.ruoyi.platform.constant.Constant; +import com.ruoyi.platform.domain.AutoMlIns; +import com.ruoyi.platform.mapper.AutoMlInsDao; +import com.ruoyi.platform.service.AutoMlInsService; +import org.springframework.data.domain.Page; +import org.springframework.data.domain.PageImpl; +import org.springframework.data.domain.PageRequest; +import org.springframework.stereotype.Service; + +import javax.annotation.Resource; +import java.io.IOException; +import java.util.List; + +@Service +public class AutoMlInsServiceImpl implements AutoMlInsService { + @Resource + private AutoMlInsDao autoMlInsDao; + + + @Override + public Page queryByPage(AutoMlIns autoMlIns, PageRequest pageRequest) throws IOException { + long total = this.autoMlInsDao.count(autoMlIns); + List autoMlInsList = this.autoMlInsDao.queryAllByLimit(autoMlIns, pageRequest); + return new PageImpl<>(autoMlInsList, pageRequest, total); + } + + @Override + public AutoMlIns insert(AutoMlIns autoMlIns) { + this.autoMlInsDao.insert(autoMlIns); + return autoMlIns; + } + + @Override + public String removeById(Long id) { + AutoMlIns autoMlIns = autoMlInsDao.queryById(id); + if (autoMlIns == null) { + return "实验实例不存在"; + } + autoMlIns.setState(Constant.State_invalid); + int update = autoMlInsDao.update(autoMlIns); + if (update > 0) { + return "删除成功"; + } else { + return "删除失败"; + } + } + + @Override + public String batchDelete(List ids) { + for (Long id : ids) { + String result = removeById(id); + if (!"删除成功".equals(result)) { + return result; + } + } + return "删除成功"; + } +} diff --git a/ruoyi-modules/management-platform/src/main/java/com/ruoyi/platform/service/impl/AutoMLServiceImpl.java b/ruoyi-modules/management-platform/src/main/java/com/ruoyi/platform/service/impl/AutoMlServiceImpl.java similarity index 92% rename from ruoyi-modules/management-platform/src/main/java/com/ruoyi/platform/service/impl/AutoMLServiceImpl.java rename to ruoyi-modules/management-platform/src/main/java/com/ruoyi/platform/service/impl/AutoMlServiceImpl.java index 40fd4ce2..7669232a 100644 --- a/ruoyi-modules/management-platform/src/main/java/com/ruoyi/platform/service/impl/AutoMLServiceImpl.java +++ b/ruoyi-modules/management-platform/src/main/java/com/ruoyi/platform/service/impl/AutoMlServiceImpl.java @@ -3,8 +3,8 @@ package com.ruoyi.platform.service.impl; import com.ruoyi.common.security.utils.SecurityUtils; import com.ruoyi.platform.constant.Constant; import com.ruoyi.platform.domain.AutoMl; -import com.ruoyi.platform.mapper.AutoMLDao; -import com.ruoyi.platform.service.AutoMLService; +import com.ruoyi.platform.mapper.AutoMlDao; +import com.ruoyi.platform.service.AutoMlService; import com.ruoyi.platform.utils.*; import com.ruoyi.platform.vo.AutoMlVo; import io.kubernetes.client.openapi.models.V1Pod; @@ -29,7 +29,7 @@ import java.util.Map; import java.util.concurrent.CompletableFuture; @Service("autoMLService") -public class AutoMLServiceImpl implements AutoMLService { +public class AutoMlServiceImpl implements AutoMlService { @Value("${harbor.serviceNS}") private String serviceNS; @Value("${dockerpush.proxyUrl}") @@ -46,22 +46,20 @@ public class AutoMLServiceImpl implements AutoMLService { private static final Logger logger = LoggerFactory.getLogger(ModelsServiceImpl.class); @Resource - private AutoMLDao autoMLDao; + private AutoMlDao autoMlDao; @Resource private K8sClientUtil k8sClientUtil; - @Resource - private DVCUtils dvcUtils; @Override public Page queryByPage(String mlName, PageRequest pageRequest) { - long total = autoMLDao.count(mlName); - List autoMls = autoMLDao.queryByPage(mlName, pageRequest); + long total = autoMlDao.count(mlName); + List autoMls = autoMlDao.queryByPage(mlName, pageRequest); return new PageImpl<>(autoMls, pageRequest, total); } @Override public AutoMl save(AutoMlVo autoMlVo) throws Exception { - AutoMl autoMlByName = autoMLDao.getAutoMlByName(autoMlVo.getMlName()); + AutoMl autoMlByName = autoMlDao.getAutoMlByName(autoMlVo.getMlName()); if (autoMlByName != null) { throw new RuntimeException("实验名称已存在"); } @@ -72,13 +70,13 @@ public class AutoMLServiceImpl implements AutoMLService { autoMl.setUpdateBy(username); String datasetJson = JacksonUtil.toJSONString(autoMlVo.getDataset()); autoMl.setDataset(datasetJson); - autoMLDao.save(autoMl); + autoMlDao.save(autoMl); return autoMl; } @Override public String edit(AutoMlVo autoMlVo) throws Exception { - AutoMl oldAutoMl = autoMLDao.getAutoMlByName(autoMlVo.getMlName()); + AutoMl oldAutoMl = autoMlDao.getAutoMlByName(autoMlVo.getMlName()); if (oldAutoMl != null && !oldAutoMl.getId().equals(autoMlVo.getId())) { throw new RuntimeException("实验名称已存在"); } @@ -90,13 +88,13 @@ public class AutoMLServiceImpl implements AutoMLService { String datasetJson = JacksonUtil.toJSONString(autoMlVo.getDataset()); autoMl.setDataset(datasetJson); - autoMLDao.edit(autoMl); + autoMlDao.edit(autoMl); return "修改成功"; } @Override public String delete(Long id) { - AutoMl autoMl = autoMLDao.getAutoMlById(id); + AutoMl autoMl = autoMlDao.getAutoMlById(id); if (autoMl == null) { throw new RuntimeException("服务不存在"); } @@ -108,12 +106,12 @@ public class AutoMLServiceImpl implements AutoMLService { } autoMl.setState(Constant.State_invalid); - return autoMLDao.edit(autoMl) > 0 ? "删除成功" : "删除失败"; + return autoMlDao.edit(autoMl) > 0 ? "删除成功" : "删除失败"; } @Override public AutoMlVo getAutoMlDetail(Long id) throws IOException { - AutoMl autoMl = autoMLDao.getAutoMlById(id); + AutoMl autoMl = autoMlDao.getAutoMlById(id); AutoMlVo autoMlVo = new AutoMlVo(); BeanUtils.copyProperties(autoMl, autoMlVo); if (StringUtils.isNotEmpty(autoMl.getDataset())) { @@ -146,7 +144,7 @@ public class AutoMLServiceImpl implements AutoMLService { @Override public String runAutoMlIns(Long id) throws Exception { - AutoMl autoMl = autoMLDao.getAutoMlById(id); + AutoMl autoMl = autoMlDao.getAutoMlById(id); if (autoMl == null) { throw new Exception("开发环境配置不存在"); } diff --git a/ruoyi-modules/management-platform/src/main/resources/mapper/managementPlatform/AutoMLDaoMapper.xml b/ruoyi-modules/management-platform/src/main/resources/mapper/managementPlatform/AutoMlDao.xml similarity index 99% rename from ruoyi-modules/management-platform/src/main/resources/mapper/managementPlatform/AutoMLDaoMapper.xml rename to ruoyi-modules/management-platform/src/main/resources/mapper/managementPlatform/AutoMlDao.xml index 73a37ff0..b6a3c4e7 100644 --- a/ruoyi-modules/management-platform/src/main/resources/mapper/managementPlatform/AutoMLDaoMapper.xml +++ b/ruoyi-modules/management-platform/src/main/resources/mapper/managementPlatform/AutoMlDao.xml @@ -1,6 +1,6 @@ - + insert into auto_ml(ml_name, ml_description, task_type, dataset, time_left_for_this_task, per_run_time_limit, ensemble_size, ensemble_class, ensemble_nbest, max_models_on_disc, seed, diff --git a/ruoyi-modules/management-platform/src/main/resources/mapper/managementPlatform/AutoMlInsDao.xml b/ruoyi-modules/management-platform/src/main/resources/mapper/managementPlatform/AutoMlInsDao.xml new file mode 100644 index 00000000..7aa36bdf --- /dev/null +++ b/ruoyi-modules/management-platform/src/main/resources/mapper/managementPlatform/AutoMlInsDao.xml @@ -0,0 +1,64 @@ + + + + + + insert into auto_ml_ins(auto_ml_id, model_path, img_path, node_status, node_result, param) + values (#{autoMlIns.autoMlId}, #{autoMlIns.modelPath}, #{autoMlIns.imgPath}, #{autoMlIns.nodeStatus}, + #{autoMlIns.nodeResult}, #{autoMlIns.param}) + + + + update auto_ml_ins + + + model_path = #{autoMlIns.modelPath}, + + + img_path = #{autoMlIns.imgPath}, + + + status = #{autoMlIns.status}, + + + node_status = #{autoMlIns.nodeStatus}, + + + node_result = #{autoMlIns.nodeResult}, + + + state = #{autoMlIns.state}, + + + + + + + + + + \ No newline at end of file From c75fa47ce0b1ed259b241a20c29e317b602786bc Mon Sep 17 00:00:00 2001 From: chenzhihang <709011834@qq.com> Date: Wed, 27 Nov 2024 11:15:13 +0800 Subject: [PATCH 19/59] =?UTF-8?q?=E8=87=AA=E5=8A=A8=E6=9C=BA=E5=99=A8?= =?UTF-8?q?=E5=AD=A6=E4=B9=A0=E5=BC=80=E5=8F=91?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../ruoyi/platform/controller/autoML/AutoMlInsController.java | 2 +- .../src/main/java/com/ruoyi/platform/domain/AutoMlIns.java | 2 ++ .../main/resources/mapper/managementPlatform/AutoMlInsDao.xml | 4 ++-- 3 files changed, 5 insertions(+), 3 deletions(-) diff --git a/ruoyi-modules/management-platform/src/main/java/com/ruoyi/platform/controller/autoML/AutoMlInsController.java b/ruoyi-modules/management-platform/src/main/java/com/ruoyi/platform/controller/autoML/AutoMlInsController.java index 3aaba610..6d7b68d3 100644 --- a/ruoyi-modules/management-platform/src/main/java/com/ruoyi/platform/controller/autoML/AutoMlInsController.java +++ b/ruoyi-modules/management-platform/src/main/java/com/ruoyi/platform/controller/autoML/AutoMlInsController.java @@ -43,7 +43,7 @@ public class AutoMlInsController extends BaseController { @DeleteMapping("batchDelete") @ApiOperation("批量删除实验实例") - public GenericsAjaxResult batchDelete(@RequestBody List ids) throws Exception { + public GenericsAjaxResult batchDelete(@RequestBody List ids) { return genericsSuccess(this.autoMLInsService.batchDelete(ids)); } } diff --git a/ruoyi-modules/management-platform/src/main/java/com/ruoyi/platform/domain/AutoMlIns.java b/ruoyi-modules/management-platform/src/main/java/com/ruoyi/platform/domain/AutoMlIns.java index b5d023ae..d31711bd 100644 --- a/ruoyi-modules/management-platform/src/main/java/com/ruoyi/platform/domain/AutoMlIns.java +++ b/ruoyi-modules/management-platform/src/main/java/com/ruoyi/platform/domain/AutoMlIns.java @@ -29,6 +29,8 @@ public class AutoMlIns { private String param; + private String source; + private Date createTime; private Date updateTime; diff --git a/ruoyi-modules/management-platform/src/main/resources/mapper/managementPlatform/AutoMlInsDao.xml b/ruoyi-modules/management-platform/src/main/resources/mapper/managementPlatform/AutoMlInsDao.xml index 7aa36bdf..05168691 100644 --- a/ruoyi-modules/management-platform/src/main/resources/mapper/managementPlatform/AutoMlInsDao.xml +++ b/ruoyi-modules/management-platform/src/main/resources/mapper/managementPlatform/AutoMlInsDao.xml @@ -3,9 +3,9 @@ - insert into auto_ml_ins(auto_ml_id, model_path, img_path, node_status, node_result, param) + insert into auto_ml_ins(auto_ml_id, model_path, img_path, node_status, node_result, param, source) values (#{autoMlIns.autoMlId}, #{autoMlIns.modelPath}, #{autoMlIns.imgPath}, #{autoMlIns.nodeStatus}, - #{autoMlIns.nodeResult}, #{autoMlIns.param}) + #{autoMlIns.nodeResult}, #{autoMlIns.param}, #{autoMlIns.source}) From 07d906b8572b98b8371a67159a6efbc5f51a42aa Mon Sep 17 00:00:00 2001 From: cp3hnu Date: Wed, 27 Nov 2024 14:32:24 +0800 Subject: [PATCH 20/59] =?UTF-8?q?fix:=20=E6=B5=81=E6=B0=B4=E7=BA=BF?= =?UTF-8?q?=E7=BB=84=E4=BB=B6=E4=BF=9D=E6=8C=81=E8=BE=93=E5=85=A5=E5=8F=82?= =?UTF-8?q?=E6=95=B0=E7=9A=84=E9=A1=BA=E5=BA=8F?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../assets/images/component-icon-9-Failed.png | Bin 0 -> 3952 bytes .../images/component-icon-9-Omitted.png | Bin 0 -> 3821 bytes .../images/component-icon-9-Pending.png | Bin 0 -> 3962 bytes .../images/component-icon-9-Running.png | Bin 0 -> 3844 bytes .../images/component-icon-9-Skipped.png | Bin 0 -> 3821 bytes .../images/component-icon-9-Succeeded.png | Bin 0 -> 4095 bytes .../public/assets/images/component-icon-9.png | Bin 0 -> 2280 bytes .../ModelDeployment/ServiceInfo/index.tsx | 5 ++++ .../components/BasicInfo/index.tsx | 4 +-- .../components/PipelineNodeDrawer/index.tsx | 28 +++++++++++++----- 10 files changed, 27 insertions(+), 10 deletions(-) create mode 100644 react-ui/public/assets/images/component-icon-9-Failed.png create mode 100644 react-ui/public/assets/images/component-icon-9-Omitted.png create mode 100644 react-ui/public/assets/images/component-icon-9-Pending.png create mode 100644 react-ui/public/assets/images/component-icon-9-Running.png create mode 100644 react-ui/public/assets/images/component-icon-9-Skipped.png create mode 100644 react-ui/public/assets/images/component-icon-9-Succeeded.png create mode 100644 react-ui/public/assets/images/component-icon-9.png diff --git a/react-ui/public/assets/images/component-icon-9-Failed.png b/react-ui/public/assets/images/component-icon-9-Failed.png new file mode 100644 index 0000000000000000000000000000000000000000..64fae87aa375ec26346e2a9af0a19f80e4ef81bd GIT binary patch literal 3952 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 KMNUMnLSTYZk%3|W literal 0 HcmV?d00001 diff --git a/react-ui/public/assets/images/component-icon-9-Omitted.png b/react-ui/public/assets/images/component-icon-9-Omitted.png new file mode 100644 index 0000000000000000000000000000000000000000..2b2911c62f6620b01d7cf8ff119064008e87e891 GIT binary patch literal 3821 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 jh7MUgU^&(PS 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 U)-g#?L;wH)07*qoM6N<$f=Tyv(f|Me literal 0 HcmV?d00001 diff --git a/react-ui/public/assets/images/component-icon-9-Running.png b/react-ui/public/assets/images/component-icon-9-Running.png new file mode 100644 index 0000000000000000000000000000000000000000..b96deabe7bfb346a9778089def259edc5778a927 GIT binary patch literal 3844 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 jh7MUgU^9qP)nzmu6T8RpH~ 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 literal 0 HcmV?d00001 diff --git a/react-ui/public/assets/images/component-icon-9.png b/react-ui/public/assets/images/component-icon-9.png new file mode 100644 index 0000000000000000000000000000000000000000..92dae0647914e416f15d4a0b8212557f4b5ff688 GIT binary patch literal 2280 zcmVP)rXj6{@@Np*T|ry15R>7vvC>7$pQf zeH{>{=S|_eJ4a#vq-9vn* zm+38l8DYv;=}}D6qqSnEsr&M5>>QvlcL*eDHjJD>g4*S(H#2>oNK&|dlQ^8K3^VsR z0tj(t6gpZBuj#3wTmuu;3MLtxhXLY!I#cic1~1LOP$lAK3^v==RzSXS{Gi7}8({3i zYKp-l5MpR6g7K{XgE0$Vkb)jTZ{ogy#=Ylo^K0>A^e%lXn#~$-q`fVGI*sN)`(j!F z1;)T@RQrI({KN?g$2WoHjVhULR~q7HJWLzml>=1a6I})JbxEd0@mg`gY#-U} z^9Iy?91rq(v7aG&Qgd{fc3vf#b}k}*&r9j2xX9n1UWSZC0M%7vFk*l-FW#FXXqn8l z&zu2u4s!QmqBkHQ3`M;f3LJu0g`?Hthg_9Zx=;4s4nZ72zTD3K%>x?2UVoRC5hUuS zVRRfLI6r!tvuVgS*4+%!-t2H@Iy93vK55d(}G z6A1Azv|WX7U#%DjZ(bSo&@!Vj&=4S_s|a?<`w)aD#3JIDQNt{4cWo7&2AOj-S5cxr zF@Uo$fZO!?*9GJY@icgi92xb2VLHYhs*G2QJ$fBFaorc@UI@#}VB8oTz4teIX}H#I zToVx7_C0hkL1Q{yM37t~dzvALHG5sK$(AnR1jpe7eKLe!j@F2dhCB#>J_ewX znT@%aN@5$tMO(CWK_~!i1E5ivUE`ow`cFjEXa1kE2L<^vxZv>2hSBrXfp&-n7i_Vm zOE^I-j31XJyeGWq^ld;Oa2t@0-`z5R63FGm2_8^w&PmIpTTLKV_9EtPa_Vi<2 zJa*lMEWb+A!)iFL0%|!e|Caei)l6*224n{u zR{`NE-V`h&H#}j?1(IEGTm_VxULM+y>2~Go^HiQ0yNtvvBS#B`?L%QbQPd3FG7V25 z6U{C-1?NHO(bIyIJ;HXvDOf;y(=83jZa4)ENDq<|a0(uf8YHLS+!TPMlBH$56luPb z@y|9$0R6z|!>(Lbt%6_0tT4(-8Ljq_t5uNl0)+S0RXB^6XDZXwOnuL77RRkp&}})$ zfBF(SJeR45(i;)w^p_CNz>Tt$;J**v7_7yUL77E=(|=In@DO6(kJlN(REPAJ{@KPU z0_qm#-ffbkl<<8B2~-tG(%gSDQqsz;MtaW*r1#7+1Squ(26+#*|IaIdfH!@5m1$<) zS@RB3MoCsByg_E?cUJ0FEre+0DE5G$U-9*+*l{IQci}J^zgn*{OBXg&U`hYQh-QWXrG?39S1eMmb=1>L z94_USCFYnfRnX~P4Wm!Mb7q=-iD?>8S^=zbPhccAXewoMDYwBAb6L2zBHB#LX|rfF z;x(CVat4$V0=EvFr12QN*Rk_1)pBb~OSxrIcp_D>GF_*{d^;+AXPPa6vJOy-By23Z z6eUTPE>tLG=cj@=Qvx1Wja1ut*{QZ>MioK$MZ9y9Wd(O=49G!5;qaM=IABJ$QrrAI zC`%%gzXvObEFO7QL;0Laxeb<>|Bb%q#%gG@Ra*{A0i~h%%6SONk>t-$s2AU&fW$~C zx1kbqrY3mIT5l6AKD|X;%GYZwpJIv%g}F^2L6fm^EKu9tw@kgglt7A1b}6?kj=P*H zJeiA8R4p-wjNKTFsi$j2ot0qHvTX67Ohr45V}E-OtpKF4ng%ef!B0&rksG&Ec_q_o zz`0xX9;U-MU`|8{(>Hd2aSTwpBN%-@0=4`(oa?J>%v#4(k6rS@*m>HB(ujoF+s(GO z-Gj0flCPN2seIPd+l;c9s?G4Kjm`K$le1OgyqSCRY3n$kl(J*FU7(2MvzM9pcF;2b zaK6JEkMWpJNA%iSecc^EEmp{#FiIMwH@%=_7^dVAtaP*Rqb0koHr%K@R{^D!l;=Bu zdI|Aq5dNhNi_C9R%b{RFqu66|h$S&iokozyJTdRdVLEaAl0000 { + if (item.model && !item.model.show_value) { + item.model.show_value = `${item.model.name}:${item.model.version}`; + } + }); setTableData(content); setTotal(totalElements); } diff --git a/react-ui/src/pages/ModelDeployment/components/BasicInfo/index.tsx b/react-ui/src/pages/ModelDeployment/components/BasicInfo/index.tsx index f741f750..32497e13 100644 --- a/react-ui/src/pages/ModelDeployment/components/BasicInfo/index.tsx +++ b/react-ui/src/pages/ModelDeployment/components/BasicInfo/index.tsx @@ -27,7 +27,7 @@ function BasicInfo({ info }: BasicInfoProps) { }; const formatCodeConfig = () => { - if (info && info.code_config) { + if (info && info.code_config && info.code_config.code_path) { const { code_path, branch } = info.code_config; const url = getGitUrl(code_path, branch); return ( @@ -36,7 +36,7 @@ function BasicInfo({ info }: BasicInfoProps) { ); } - return undefined; + return '--'; }; const formatResource = () => { diff --git a/react-ui/src/pages/Pipeline/components/PipelineNodeDrawer/index.tsx b/react-ui/src/pages/Pipeline/components/PipelineNodeDrawer/index.tsx index 2eae5c64..8cc9f441 100644 --- a/react-ui/src/pages/Pipeline/components/PipelineNodeDrawer/index.tsx +++ b/react-ui/src/pages/Pipeline/components/PipelineNodeDrawer/index.tsx @@ -43,22 +43,34 @@ const PipelineNodeParameter = forwardRef(({ onFormChange }: PipelineNodeParamete if (!open) { // eslint-disable-next-line @typescript-eslint/no-unused-vars const [_values, error] = await to(form.validateFields()); + // 不管是否验证成功,都需要获取表单数据 const fields = form.getFieldsValue(); - const control_strategy = JSON.stringify(fields.control_strategy); - const in_parameters = JSON.stringify(fields.in_parameters); - const out_parameters = JSON.stringify(fields.out_parameters); + + // 保存字段顺序 + const control_strategy = { + ...stagingItem.control_strategy, + ...fields.control_strategy, + }; + const in_parameters = { + ...stagingItem.in_parameters, + ...fields.in_parameters, + }; + const out_parameters = { + ...stagingItem.out_parameters, + ...fields.out_parameters, + }; + // console.log('getFieldsValue', fields); const res = { ...stagingItem, ...fields, - control_strategy: control_strategy, - in_parameters: in_parameters, - out_parameters: out_parameters, + control_strategy: JSON.stringify(control_strategy), + in_parameters: JSON.stringify(in_parameters), + out_parameters: JSON.stringify(out_parameters), formError: !!error, }; - - console.log('res', res); + // console.log('res', res); onFormChange(res); } }; From 2f5c3d447645426a8284b80898a5ccf8aa849092 Mon Sep 17 00:00:00 2001 From: chenzhihang <709011834@qq.com> Date: Thu, 28 Nov 2024 15:01:43 +0800 Subject: [PATCH 21/59] =?UTF-8?q?=E8=87=AA=E5=8A=A8=E6=9C=BA=E5=99=A8?= =?UTF-8?q?=E5=AD=A6=E4=B9=A0=E5=BC=80=E5=8F=91?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../controller/autoML/AutoMlController.java | 2 +- .../autoML/AutoMlInsController.java | 6 + .../com/ruoyi/platform/domain/AutoMl.java | 2 + .../com/ruoyi/platform/domain/AutoMlIns.java | 7 + .../ruoyi/platform/mapper/AutoMlInsDao.java | 4 + .../scheduling/AutoMlInsStatusTask.java | 97 ++++++++++ .../platform/service/AutoMlInsService.java | 6 + .../service/impl/AutoMlInsServiceImpl.java | 168 +++++++++++++++++- .../service/impl/AutoMlServiceImpl.java | 157 +++++++--------- .../com/ruoyi/platform/vo/AutoMlParamVo.java | 155 ++++++++++++++++ .../mapper/managementPlatform/AutoMlDao.xml | 6 +- .../managementPlatform/AutoMlInsDao.xml | 24 ++- 12 files changed, 531 insertions(+), 103 deletions(-) create mode 100644 ruoyi-modules/management-platform/src/main/java/com/ruoyi/platform/scheduling/AutoMlInsStatusTask.java create mode 100644 ruoyi-modules/management-platform/src/main/java/com/ruoyi/platform/vo/AutoMlParamVo.java diff --git a/ruoyi-modules/management-platform/src/main/java/com/ruoyi/platform/controller/autoML/AutoMlController.java b/ruoyi-modules/management-platform/src/main/java/com/ruoyi/platform/controller/autoML/AutoMlController.java index 981b9fb1..9c1028bb 100644 --- a/ruoyi-modules/management-platform/src/main/java/com/ruoyi/platform/controller/autoML/AutoMlController.java +++ b/ruoyi-modules/management-platform/src/main/java/com/ruoyi/platform/controller/autoML/AutoMlController.java @@ -63,7 +63,7 @@ public class AutoMlController extends BaseController { return AjaxResult.success(this.autoMlService.upload(file, uuid)); } - @PostMapping("{id}") + @PostMapping("/run/{id}") @ApiOperation("运行自动机器学习实验") public GenericsAjaxResult runAutoML(@PathVariable("id") Long id) throws Exception { return genericsSuccess(this.autoMlService.runAutoMlIns(id)); diff --git a/ruoyi-modules/management-platform/src/main/java/com/ruoyi/platform/controller/autoML/AutoMlInsController.java b/ruoyi-modules/management-platform/src/main/java/com/ruoyi/platform/controller/autoML/AutoMlInsController.java index 6d7b68d3..125fd668 100644 --- a/ruoyi-modules/management-platform/src/main/java/com/ruoyi/platform/controller/autoML/AutoMlInsController.java +++ b/ruoyi-modules/management-platform/src/main/java/com/ruoyi/platform/controller/autoML/AutoMlInsController.java @@ -46,4 +46,10 @@ public class AutoMlInsController extends BaseController { public GenericsAjaxResult batchDelete(@RequestBody List ids) { return genericsSuccess(this.autoMLInsService.batchDelete(ids)); } + + @PutMapping("{id}") + @ApiOperation("终止实验实例") + public GenericsAjaxResult terminateAutoMlIns(@PathVariable("id") Long id) { + return genericsSuccess(this.autoMLInsService.terminateAutoMlIns(id)); + } } diff --git a/ruoyi-modules/management-platform/src/main/java/com/ruoyi/platform/domain/AutoMl.java b/ruoyi-modules/management-platform/src/main/java/com/ruoyi/platform/domain/AutoMl.java index 811168da..a0f130cc 100644 --- a/ruoyi-modules/management-platform/src/main/java/com/ruoyi/platform/domain/AutoMl.java +++ b/ruoyi-modules/management-platform/src/main/java/com/ruoyi/platform/domain/AutoMl.java @@ -179,4 +179,6 @@ public class AutoMl { private String dataset; + @ApiModelProperty(value = "状态列表") + private String statusList; } diff --git a/ruoyi-modules/management-platform/src/main/java/com/ruoyi/platform/domain/AutoMlIns.java b/ruoyi-modules/management-platform/src/main/java/com/ruoyi/platform/domain/AutoMlIns.java index d31711bd..ba425dcf 100644 --- a/ruoyi-modules/management-platform/src/main/java/com/ruoyi/platform/domain/AutoMlIns.java +++ b/ruoyi-modules/management-platform/src/main/java/com/ruoyi/platform/domain/AutoMlIns.java @@ -3,6 +3,7 @@ package com.ruoyi.platform.domain; import com.fasterxml.jackson.databind.PropertyNamingStrategy; import com.fasterxml.jackson.databind.annotation.JsonNaming; import io.swagger.annotations.ApiModel; +import io.swagger.annotations.ApiModelProperty; import lombok.Data; import java.util.Date; @@ -31,6 +32,12 @@ public class AutoMlIns { private String source; + @ApiModelProperty(value = "Argo实例名称") + private String argoInsName; + + @ApiModelProperty(value = "Argo命名空间") + private String argoInsNs; + private Date createTime; private Date updateTime; diff --git a/ruoyi-modules/management-platform/src/main/java/com/ruoyi/platform/mapper/AutoMlInsDao.java b/ruoyi-modules/management-platform/src/main/java/com/ruoyi/platform/mapper/AutoMlInsDao.java index 7468c8f3..7fcc3ca9 100644 --- a/ruoyi-modules/management-platform/src/main/java/com/ruoyi/platform/mapper/AutoMlInsDao.java +++ b/ruoyi-modules/management-platform/src/main/java/com/ruoyi/platform/mapper/AutoMlInsDao.java @@ -11,9 +11,13 @@ public interface AutoMlInsDao { List queryAllByLimit(@Param("autoMlIns") AutoMlIns autoMlIns, @Param("pageable") Pageable pageable); + List getByAutoMlId(@Param("autoMlId") Long AutoMlId); + int insert(@Param("autoMlIns") AutoMlIns autoMlIns); int update(@Param("autoMlIns") AutoMlIns autoMlIns); AutoMlIns queryById(@Param("id") Long id); + + List queryByAutoMlInsIsNotTerminated(); } diff --git a/ruoyi-modules/management-platform/src/main/java/com/ruoyi/platform/scheduling/AutoMlInsStatusTask.java b/ruoyi-modules/management-platform/src/main/java/com/ruoyi/platform/scheduling/AutoMlInsStatusTask.java new file mode 100644 index 00000000..5c94284f --- /dev/null +++ b/ruoyi-modules/management-platform/src/main/java/com/ruoyi/platform/scheduling/AutoMlInsStatusTask.java @@ -0,0 +1,97 @@ +package com.ruoyi.platform.scheduling; + +import com.ruoyi.platform.domain.AutoMl; +import com.ruoyi.platform.domain.AutoMlIns; +import com.ruoyi.platform.mapper.AutoMlDao; +import com.ruoyi.platform.mapper.AutoMlInsDao; +import com.ruoyi.platform.service.AutoMlInsService; +import org.apache.commons.lang3.StringUtils; +import org.springframework.scheduling.annotation.Scheduled; +import org.springframework.stereotype.Component; + +import javax.annotation.Resource; +import java.util.ArrayList; +import java.util.Iterator; +import java.util.List; + +@Component() +public class AutoMlInsStatusTask { + + @Resource + private AutoMlInsService autoMlInsService; + + @Resource + private AutoMlInsDao autoMlInsDao; + + @Resource + private AutoMlDao autoMlDao; + + private List autoMlIds = new ArrayList<>(); + + @Scheduled(cron = "0/30 * * * * ?") // 每30S执行一次 + public void executeAutoMlInsStatus() throws Exception { + // 首先查到所有非终止态的实验实例 + List autoMlInsList = autoMlInsService.queryByAutoMlInsIsNotTerminated(); + + // 去argo查询状态 + List updateList = new ArrayList<>(); + if (autoMlInsList != null && autoMlInsList.size() > 0) { + for (AutoMlIns autoMlIns : autoMlInsList) { + //当原本状态为null或非终止态时才调用argo接口 + try { + autoMlIns = autoMlInsService.queryStatusFromArgo(autoMlIns); + } catch (Exception e) { + autoMlIns.setStatus("Failed"); + } + // 线程安全的添加操作 + synchronized (autoMlIds) { + autoMlIds.add(autoMlIns.getAutoMlId()); + } + updateList.add(autoMlIns); + } + if (updateList.size() > 0) { + for (AutoMlIns autoMlIns : updateList) { + autoMlInsDao.update(autoMlIns); + } + } + } + } + + @Scheduled(cron = "0/30 * * * * ?") // / 每30S执行一次 + public void executeAutoMlStatus() throws Exception { + if (autoMlIds.size() == 0) { + return; + } + // 存储需要更新的实验对象列表 + List updateAutoMls = new ArrayList<>(); + for (Long autoMlId : autoMlIds) { + // 获取当前实验的所有实例列表 + List insList = autoMlInsDao.getByAutoMlId(autoMlId); + List statusList = new ArrayList<>(); + // 更新实验状态列表 + for (int i = 0; i < insList.size(); i++) { + statusList.add(insList.get(i).getStatus()); + } + String subStatus = statusList.toString().substring(1, statusList.toString().length() - 1); + AutoMl autoMl = autoMlDao.getAutoMlById(autoMlId); + if (!StringUtils.equals(autoMl.getStatusList(), subStatus)) { + autoMl.setStatusList(subStatus); + updateAutoMls.add(autoMl); + autoMlDao.edit(autoMl); + } + } + + if (!updateAutoMls.isEmpty()) { + // 使用Iterator进行安全的删除操作 + Iterator iterator = autoMlIds.iterator(); + while (iterator.hasNext()) { + Long autoMlId = iterator.next(); + for (AutoMl autoMl : updateAutoMls) { + if (autoMl.getId().equals(autoMlId)) { + iterator.remove(); + } + } + } + } + } +} diff --git a/ruoyi-modules/management-platform/src/main/java/com/ruoyi/platform/service/AutoMlInsService.java b/ruoyi-modules/management-platform/src/main/java/com/ruoyi/platform/service/AutoMlInsService.java index 4b7a8601..d4956a22 100644 --- a/ruoyi-modules/management-platform/src/main/java/com/ruoyi/platform/service/AutoMlInsService.java +++ b/ruoyi-modules/management-platform/src/main/java/com/ruoyi/platform/service/AutoMlInsService.java @@ -15,4 +15,10 @@ public interface AutoMlInsService { String removeById(Long id); String batchDelete(List ids); + + List queryByAutoMlInsIsNotTerminated(); + + AutoMlIns queryStatusFromArgo(AutoMlIns autoMlIns); + + boolean terminateAutoMlIns(Long id); } diff --git a/ruoyi-modules/management-platform/src/main/java/com/ruoyi/platform/service/impl/AutoMlInsServiceImpl.java b/ruoyi-modules/management-platform/src/main/java/com/ruoyi/platform/service/impl/AutoMlInsServiceImpl.java index 5b190226..3d11123e 100644 --- a/ruoyi-modules/management-platform/src/main/java/com/ruoyi/platform/service/impl/AutoMlInsServiceImpl.java +++ b/ruoyi-modules/management-platform/src/main/java/com/ruoyi/platform/service/impl/AutoMlInsServiceImpl.java @@ -4,6 +4,11 @@ import com.ruoyi.platform.constant.Constant; import com.ruoyi.platform.domain.AutoMlIns; import com.ruoyi.platform.mapper.AutoMlInsDao; import com.ruoyi.platform.service.AutoMlInsService; +import com.ruoyi.platform.utils.DateUtils; +import com.ruoyi.platform.utils.HttpUtils; +import com.ruoyi.platform.utils.JsonUtils; +import org.apache.commons.lang3.StringUtils; +import org.springframework.beans.factory.annotation.Value; import org.springframework.data.domain.Page; import org.springframework.data.domain.PageImpl; import org.springframework.data.domain.PageRequest; @@ -11,10 +16,17 @@ import org.springframework.stereotype.Service; import javax.annotation.Resource; import java.io.IOException; -import java.util.List; +import java.util.*; @Service public class AutoMlInsServiceImpl implements AutoMlInsService { + @Value("${argo.url}") + private String argoUrl; + @Value("${argo.workflowStatus}") + private String argoWorkflowStatus; + @Value("${argo.workflowTermination}") + private String argoWorkflowTermination; + @Resource private AutoMlInsDao autoMlInsDao; @@ -38,6 +50,14 @@ public class AutoMlInsServiceImpl implements AutoMlInsService { if (autoMlIns == null) { return "实验实例不存在"; } + + if (StringUtils.isEmpty(autoMlIns.getStatus())) { + autoMlIns = queryStatusFromArgo(autoMlIns); + } + if (StringUtils.equals(autoMlIns.getStatus(), "Running")) { + return "实验实例正在运行,不可删除"; + } + autoMlIns.setState(Constant.State_invalid); int update = autoMlInsDao.update(autoMlIns); if (update > 0) { @@ -57,4 +77,150 @@ public class AutoMlInsServiceImpl implements AutoMlInsService { } return "删除成功"; } + + @Override + public List queryByAutoMlInsIsNotTerminated() { + return autoMlInsDao.queryByAutoMlInsIsNotTerminated(); + } + + @Override + public AutoMlIns queryStatusFromArgo(AutoMlIns ins) { + String namespace = ins.getArgoInsNs(); + String name = ins.getArgoInsName(); + Long id = ins.getId(); + + // 创建请求数据map + Map requestData = new HashMap<>(); + requestData.put("namespace", namespace); + requestData.put("name", name); + + // 创建发送数据map,将请求数据作为"data"键的值 + Map res = new HashMap<>(); + res.put("data", requestData); + + try { + // 发送POST请求到Argo工作流状态查询接口,并将请求数据转换为JSON + String req = HttpUtils.sendPost(argoUrl + argoWorkflowStatus, null, JsonUtils.mapToJson(res)); + // 检查响应是否为空或无内容 + if (req == null || StringUtils.isEmpty(req)) { + throw new RuntimeException("工作流状态响应为空。"); + } + // 将响应的JSON字符串转换为Map对象 + Map runResMap = JsonUtils.jsonToMap(req); + // 从响应Map中获取"data"部分 + Map data = (Map) runResMap.get("data"); + if (data == null || data.isEmpty()) { + throw new RuntimeException("工作流数据为空."); + } + // 从"data"中获取"status"部分,并返回"phase"的值 + Map status = (Map) data.get("status"); + if (status == null || status.isEmpty()) { + throw new RuntimeException("工作流状态为空。"); + } + + //解析流水线结束时间 + String finishedAtString = (String) status.get("finishedAt"); + if (finishedAtString != null && !finishedAtString.isEmpty()) { + Date finishTime = DateUtils.convertUTCtoShanghaiDate(finishedAtString); + ins.setUpdateTime(finishTime); + } + + // 解析nodes字段,提取节点状态并转换为JSON字符串 + Map nodes = (Map) status.get("nodes"); + Map modifiedNodes = new LinkedHashMap<>(); + if (nodes != null) { + for (Map.Entry nodeEntry : nodes.entrySet()) { + Map nodeDetails = (Map) nodeEntry.getValue(); + String templateName = (String) nodeDetails.get("displayName"); + modifiedNodes.put(templateName, nodeDetails); + } + } + + String nodeStatusJson = JsonUtils.mapToJson(modifiedNodes); + ins.setNodeStatus(nodeStatusJson); + + //终止态为终止不改 + if (!StringUtils.equals(ins.getStatus(), "Terminated")) { + ins.setStatus(StringUtils.isNotEmpty((String) status.get("phase")) ? (String) status.get("phase") : "Pending"); + } + if (StringUtils.equals(ins.getStatus(), "Error")) { + ins.setStatus("Failed"); + } + return ins; + } catch (Exception e) { + throw new RuntimeException("查询状态失败: " + e.getMessage(), e); + } + } + + @Override + public boolean terminateAutoMlIns(Long id) { + AutoMlIns autoMlIns = autoMlInsDao.queryById(id); + if (autoMlIns == null) { + throw new IllegalStateException("实验实例未查询到,id: " + id); + } + + String currentStatus = autoMlIns.getStatus(); + String name = autoMlIns.getArgoInsName(); + String namespace = autoMlIns.getArgoInsNs(); + + // 获取当前状态,如果为空,则从Argo查询 + if (StringUtils.isEmpty(currentStatus)) { + currentStatus = queryStatusFromArgo(autoMlIns).getStatus(); + } + // 只有状态是"Running"时才能终止实例 + if (!currentStatus.equalsIgnoreCase(Constant.Running)) { + return false; // 如果不是"Running"状态,则不执行终止操作 + } + + // 创建请求数据map + Map requestData = new HashMap<>(); + requestData.put("namespace", namespace); + requestData.put("name", name); + // 创建发送数据map,将请求数据作为"data"键的值 + Map res = new HashMap<>(); + res.put("data", requestData); + + try { + // 发送POST请求到Argo工作流状态查询接口,并将请求数据转换为JSON + String req = HttpUtils.sendPost(argoUrl + argoWorkflowTermination, null, JsonUtils.mapToJson(res)); + // 检查响应是否为空或无内容 + if (StringUtils.isEmpty(req)) { + throw new RuntimeException("终止响应内容为空。"); + + } + // 将响应的JSON字符串转换为Map对象 + Map runResMap = JsonUtils.jsonToMap(req); + // 从响应Map中直接获取"errCode"的值 + Integer errCode = (Integer) runResMap.get("errCode"); + if (errCode != null && errCode == 0) { + //更新autoMlIns,确保状态更新被保存到数据库 + AutoMlIns ins = queryStatusFromArgo(autoMlIns); + String nodeStatus = ins.getNodeStatus(); + Map nodeMap = JsonUtils.jsonToMap(nodeStatus); + + // 遍历 map + for (Map.Entry entry : nodeMap.entrySet()) { + // 获取每个 Map 中的值并强制转换为 Map + Map innerMap = (Map) entry.getValue(); + // 检查 phase 的值 + if (innerMap.containsKey("phase")) { + String phaseValue = (String) innerMap.get("phase"); + // 如果值不等于 Succeeded,则赋值为 Failed + if (!StringUtils.equals("Succeeded", phaseValue)) { + innerMap.put("phase", "Failed"); + } + } + } + ins.setNodeStatus(JsonUtils.mapToJson(nodeMap)); + ins.setStatus(Constant.Terminated); + ins.setUpdateTime(new Date()); + this.autoMlInsDao.update(ins); + return true; + } else { + return false; + } + } catch (Exception e) { + throw new RuntimeException("终止实例错误: " + e.getMessage(), e); + } + } } diff --git a/ruoyi-modules/management-platform/src/main/java/com/ruoyi/platform/service/impl/AutoMlServiceImpl.java b/ruoyi-modules/management-platform/src/main/java/com/ruoyi/platform/service/impl/AutoMlServiceImpl.java index 7669232a..4b7e58e9 100644 --- a/ruoyi-modules/management-platform/src/main/java/com/ruoyi/platform/service/impl/AutoMlServiceImpl.java +++ b/ruoyi-modules/management-platform/src/main/java/com/ruoyi/platform/service/impl/AutoMlServiceImpl.java @@ -3,15 +3,19 @@ package com.ruoyi.platform.service.impl; import com.ruoyi.common.security.utils.SecurityUtils; import com.ruoyi.platform.constant.Constant; import com.ruoyi.platform.domain.AutoMl; +import com.ruoyi.platform.domain.AutoMlIns; import com.ruoyi.platform.mapper.AutoMlDao; +import com.ruoyi.platform.mapper.AutoMlInsDao; import com.ruoyi.platform.service.AutoMlService; -import com.ruoyi.platform.utils.*; +import com.ruoyi.platform.utils.FileUtil; +import com.ruoyi.platform.utils.HttpUtils; +import com.ruoyi.platform.utils.JacksonUtil; +import com.ruoyi.platform.utils.JsonUtils; +import com.ruoyi.platform.vo.AutoMlParamVo; import com.ruoyi.platform.vo.AutoMlVo; -import io.kubernetes.client.openapi.models.V1Pod; +import org.apache.commons.collections4.MapUtils; import org.apache.commons.io.FileUtils; import org.apache.commons.lang3.StringUtils; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; import org.springframework.beans.BeanUtils; import org.springframework.beans.factory.annotation.Value; import org.springframework.data.domain.Page; @@ -26,29 +30,24 @@ import java.io.IOException; import java.util.HashMap; import java.util.List; import java.util.Map; -import java.util.concurrent.CompletableFuture; @Service("autoMLService") public class AutoMlServiceImpl implements AutoMlService { - @Value("${harbor.serviceNS}") - private String serviceNS; - @Value("${dockerpush.proxyUrl}") - private String proxyUrl; - @Value("${dockerpush.mountPath}") - private String mountPath; - @Value("${minio.pvcName}") - private String pvcName; - @Value("${automl.image}") - private String image; @Value("${git.localPath}") String localPath; - private static final Logger logger = LoggerFactory.getLogger(ModelsServiceImpl.class); + @Value("${argo.url}") + private String argoUrl; + @Value("${argo.convertAutoML}") + String convertAutoML; + @Value("${argo.workflowRun}") + private String argoWorkflowRun; @Resource private AutoMlDao autoMlDao; + @Resource - private K8sClientUtil k8sClientUtil; + private AutoMlInsDao autoMlInsDao; @Override public Page queryByPage(String mlName, PageRequest pageRequest) { @@ -146,86 +145,52 @@ public class AutoMlServiceImpl implements AutoMlService { public String runAutoMlIns(Long id) throws Exception { AutoMl autoMl = autoMlDao.getAutoMlById(id); if (autoMl == null) { - throw new Exception("开发环境配置不存在"); - } - - StringBuffer command = new StringBuffer(); - command.append("nohup python /opt/automl.py --task_type " + autoMl.getTaskType()); - if (StringUtils.isNotEmpty(autoMl.getTargetColumns())) { - command.append(" --target_columns " + autoMl.getTargetColumns()); - } else { - throw new Exception("目标列为空"); - } - -// String username = SecurityUtils.getLoginUser().getUsername().toLowerCase(); - String username = "admin"; - //构造pod名称 - String podName = username + "-autoMlIns-pod-" + id; - V1Pod pod = k8sClientUtil.createPodWithEnv(podName, serviceNS, proxyUrl, mountPath, pvcName, image); - - if (autoMl.getTimeLeftForThisTask() != null) { - command.append(" --time_left_for_this_task " + autoMl.getTimeLeftForThisTask()); - } - if (autoMl.getPerRunTimeLimit() != null) { - command.append(" --per_run_time_limit " + autoMl.getPerRunTimeLimit()); - } - if (autoMl.getEnsembleSize() != null) { - command.append(" --ensemble_size " + autoMl.getEnsembleSize()); - } - if (StringUtils.isNotEmpty(autoMl.getEnsembleClass())) { - command.append(" --ensemble_class " + autoMl.getEnsembleClass()); - } - if (autoMl.getEnsembleNbest() != null) { - command.append(" --ensemble_nbest " + autoMl.getEnsembleNbest()); - } - if (autoMl.getMaxModelsOnDisc() != null) { - command.append(" --max_models_on_disc " + autoMl.getMaxModelsOnDisc()); - } - if (autoMl.getSeed() != null) { - command.append(" --seed " + autoMl.getSeed()); - } - if (autoMl.getMemoryLimit() != null) { - command.append(" --memory_limit " + autoMl.getMemoryLimit()); - } - if (StringUtils.isNotEmpty(autoMl.getIncludeClassifier())) { - command.append(" --include_classifier " + autoMl.getIncludeClassifier()); - } - if (StringUtils.isNotEmpty(autoMl.getIncludeRegressor())) { - command.append(" --include_regressor " + autoMl.getIncludeRegressor()); - } - if (StringUtils.isNotEmpty(autoMl.getIncludeFeaturePreprocessor())) { - command.append(" --include_feature_preprocessor " + autoMl.getIncludeFeaturePreprocessor()); - } - if (StringUtils.isNotEmpty(autoMl.getExcludeClassifier())) { - command.append(" --exclude_classifier " + autoMl.getExcludeClassifier()); - } - if (StringUtils.isNotEmpty(autoMl.getExcludeRegressor())) { - command.append(" --exclude_regressor " + autoMl.getExcludeRegressor()); - } - if (StringUtils.isNotEmpty(autoMl.getExcludeFeaturePreprocessor())) { - command.append(" --exclude_feature_preprocessor " + autoMl.getExcludeFeaturePreprocessor()); - } - if (StringUtils.isNotEmpty(autoMl.getResamplingStrategy())) { - command.append(" --resampling_strategy " + autoMl.getResamplingStrategy()); - } - if (autoMl.getTrainSize() != null) { - command.append(" --train_size " + autoMl.getTrainSize()); - } - if (autoMl.getShuffle() != null) { - command.append(" --shuffle " + autoMl.getShuffle()); - } - if (autoMl.getFolds() != null) { - command.append(" --folds " + autoMl.getFolds()); - } - command.append(" &"); - CompletableFuture.supplyAsync(() -> { - try { - String log = k8sClientUtil.executeCommand(pod, String.valueOf(command)); - } catch (Exception e) { - logger.error(e.getMessage(), e); + throw new Exception("自动机器学习配置不存在"); + } + + AutoMlParamVo autoMlParam = new AutoMlParamVo(); + BeanUtils.copyProperties(autoMl, autoMlParam); + autoMlParam.setDataset(JsonUtils.jsonToMap(autoMl.getDataset())); + String param = JsonUtils.objectToJson(autoMlParam); + // 调argo转换接口 + try { + String convertRes = HttpUtils.sendPost(argoUrl + convertAutoML, param); + if (convertRes == null || StringUtils.isEmpty(convertRes)) { + throw new RuntimeException("转换流水线失败"); } - return null; - }); + Map converMap = JsonUtils.jsonToMap(convertRes); + // 组装运行接口json + Map output = (Map) converMap.get("output"); + Map runReqMap = new HashMap<>(); + runReqMap.put("data", converMap.get("data")); + // 调argo运行接口 + String runRes = HttpUtils.sendPost(argoUrl + argoWorkflowRun, JsonUtils.mapToJson(runReqMap)); + + if (runRes == null || StringUtils.isEmpty(runRes)) { + throw new RuntimeException("Failed to run workflow."); + } + Map runResMap = JsonUtils.jsonToMap(runRes); + Map data = (Map) runResMap.get("data"); + //判断data为空 + if (data == null || MapUtils.isEmpty(data)) { + throw new RuntimeException("Failed to run workflow."); + } + Map metadata = (Map) data.get("metadata"); + // 插入记录到实验实例表 + AutoMlIns autoMlIns = new AutoMlIns(); + autoMlIns.setAutoMlId(autoMl.getId()); + autoMlIns.setArgoInsNs((String) metadata.get("namespace")); + autoMlIns.setArgoInsName((String) metadata.get("name")); + autoMlIns.setParam(param); + autoMlIns.setStatus(Constant.Pending); + //替换argoInsName + String outputString = JsonUtils.mapToJson(output); + autoMlIns.setNodeResult(outputString.replace("{{workflow.name}}", (String) metadata.get("name"))); + autoMlInsDao.insert(autoMlIns); + + } catch (Exception e) { + throw new RuntimeException(e); + } return "执行成功"; } } diff --git a/ruoyi-modules/management-platform/src/main/java/com/ruoyi/platform/vo/AutoMlParamVo.java b/ruoyi-modules/management-platform/src/main/java/com/ruoyi/platform/vo/AutoMlParamVo.java new file mode 100644 index 00000000..540631af --- /dev/null +++ b/ruoyi-modules/management-platform/src/main/java/com/ruoyi/platform/vo/AutoMlParamVo.java @@ -0,0 +1,155 @@ +package com.ruoyi.platform.vo; + +import com.fasterxml.jackson.annotation.JsonInclude; +import com.fasterxml.jackson.databind.PropertyNamingStrategy; +import com.fasterxml.jackson.databind.annotation.JsonNaming; +import io.swagger.annotations.ApiModel; +import io.swagger.annotations.ApiModelProperty; +import lombok.Data; + +import java.util.Map; + +@Data +@JsonNaming(PropertyNamingStrategy.SnakeCaseStrategy.class) +@JsonInclude(JsonInclude.Include.NON_NULL) +@ApiModel(description = "自动机器学习参数") +public class AutoMlParamVo { + @ApiModelProperty(value = "任务类型:classification或regression") + private String taskType; + + @ApiModelProperty(value = "搜索合适模型的时间限制(以秒为单位)。通过增加这个值,auto-sklearn有更高的机会找到更好的模型。默认3600,非必传。") + private Integer timeLeftForThisTask; + + @ApiModelProperty(value = "单次调用机器学习模型的时间限制(以秒为单位)。如果机器学习算法运行超过时间限制,将终止模型拟合。将这个值设置得足够高,这样典型的机器学习算法就可以适用于训练数据。默认600,非必传。") + private Integer perRunTimeLimit; + + @ApiModelProperty(value = "集成模型数量,如果设置为0,则没有集成。默认50,非必传。") + private Integer ensembleSize; + + @ApiModelProperty(value = "设置为None将禁用集成构建,设置为SingleBest仅使用单个最佳模型而不是集成,设置为default,它将对单目标问题使用EnsembleSelection,对多目标问题使用MultiObjectiveDummyEnsemble。默认default,非必传。") + private String ensembleClass; + + @ApiModelProperty(value = "在构建集成时只考虑ensemble_nbest模型。这是受到了“最大限度地利用集成选择”中引入的库修剪概念的启发。这是独立于ensemble_class参数的,并且这个修剪步骤是在构造集成之前完成的。默认50,非必传。") + private Integer ensembleNbest; + + @ApiModelProperty(value = "定义在磁盘中保存的模型的最大数量。额外的模型数量将被永久删除。由于这个变量的性质,它设置了一个集成可以使用多少个模型的上限。必须是大于等于1的整数。如果设置为None,则所有模型都保留在磁盘上。默认50,非必传。") + private Integer maxModelsOnDisc; + + @ApiModelProperty(value = "随机种子,将决定输出文件名。默认1,非必传。") + private Integer seed; + + @ApiModelProperty(value = "机器学习算法的内存限制(MB)。如果auto-sklearn试图分配超过memory_limit MB,它将停止拟合机器学习算法。默认3072,非必传。") + private Integer memoryLimit; + + @ApiModelProperty(value = "如果为None,则使用所有可能的分类算法。否则,指定搜索中包含的步骤和组件。有关可用组件,请参见/pipeline/components//*。与参数exclude不兼容。多选,逗号分隔。包含:adaboost\n" + + "bernoulli_nb\n" + + "decision_tree\n" + + "extra_trees\n" + + "gaussian_nb\n" + + "gradient_boosting\n" + + "k_nearest_neighbors\n" + + "lda\n" + + "liblinear_svc\n" + + "libsvm_svc\n" + + "mlp\n" + + "multinomial_nb\n" + + "passive_aggressive\n" + + "qda\n" + + "random_forest\n" + + "sgd") + private String includeClassifier; + + @ApiModelProperty(value = "如果为None,则使用所有可能的特征预处理算法。否则,指定搜索中包含的步骤和组件。有关可用组件,请参见/pipeline/components//*。与参数exclude不兼容。多选,逗号分隔。包含:densifier\n" + + "extra_trees_preproc_for_classification\n" + + "extra_trees_preproc_for_regression\n" + + "fast_ica\n" + + "feature_agglomeration\n" + + "kernel_pca\n" + + "kitchen_sinks\n" + + "liblinear_svc_preprocessor\n" + + "no_preprocessing\n" + + "nystroem_sampler\n" + + "pca\n" + + "polynomial\n" + + "random_trees_embedding\n" + + "select_percentile_classification\n" + + "select_percentile_regression\n" + + "select_rates_classification\n" + + "select_rates_regression\n" + + "truncatedSVD") + private String includeFeaturePreprocessor; + + @ApiModelProperty(value = "如果为None,则使用所有可能的回归算法。否则,指定搜索中包含的步骤和组件。有关可用组件,请参见/pipeline/components//*。与参数exclude不兼容。多选,逗号分隔。包含:adaboost,\n" + + "ard_regression,\n" + + "decision_tree,\n" + + "extra_trees,\n" + + "gaussian_process,\n" + + "gradient_boosting,\n" + + "k_nearest_neighbors,\n" + + "liblinear_svr,\n" + + "libsvm_svr,\n" + + "mlp,\n" + + "random_forest,\n" + + "sgd") + private String includeRegressor; + + private String excludeClassifier; + + private String excludeRegressor; + + private String excludeFeaturePreprocessor; + + @ApiModelProperty(value = "测试集的比率,0到1之间") + private Float testSize; + + @ApiModelProperty(value = "如何处理过拟合,如果使用基于“cv”的方法或Splitter对象,可能需要使用resampling_strategy_arguments。holdout或crossValid") + private String resamplingStrategy; + + @ApiModelProperty(value = "重采样划分训练集和验证集,训练集的比率,0到1之间") + private Float trainSize; + + @ApiModelProperty(value = "拆分数据前是否进行shuffle") + private Boolean shuffle; + + @ApiModelProperty(value = "交叉验证的折数,当resamplingStrategy为crossValid时,此项必填,为整数") + private Integer folds; + + @ApiModelProperty(value = "数据集csv文件中哪几列是预测目标列,逗号分隔") + private String targetColumns; + + @ApiModelProperty(value = "自定义指标名称") + private String metricName; + + @ApiModelProperty(value = "模型优化目标指标及权重,json格式。分类的指标包含:accuracy\n" + + "balanced_accuracy\n" + + "roc_auc\n" + + "average_precision\n" + + "log_loss\n" + + "precision_macro\n" + + "precision_micro\n" + + "precision_samples\n" + + "precision_weighted\n" + + "recall_macro\n" + + "recall_micro\n" + + "recall_samples\n" + + "recall_weighted\n" + + "f1_macro\n" + + "f1_micro\n" + + "f1_samples\n" + + "f1_weighted\n" + + "回归的指标包含:mean_absolute_error\n" + + "mean_squared_error\n" + + "root_mean_squared_error\n" + + "mean_squared_log_error\n" + + "median_absolute_error\n" + + "r2") + private String metrics; + + @ApiModelProperty(value = "指标优化方向,是越大越好还是越小越好") + private Boolean greaterIsBetter; + + @ApiModelProperty(value = "模型计算并打印指标") + private String scoringFunctions; + + private Map dataset; +} diff --git a/ruoyi-modules/management-platform/src/main/resources/mapper/managementPlatform/AutoMlDao.xml b/ruoyi-modules/management-platform/src/main/resources/mapper/managementPlatform/AutoMlDao.xml index b6a3c4e7..610fe143 100644 --- a/ruoyi-modules/management-platform/src/main/resources/mapper/managementPlatform/AutoMlDao.xml +++ b/ruoyi-modules/management-platform/src/main/resources/mapper/managementPlatform/AutoMlDao.xml @@ -34,9 +34,9 @@ ml_description = #{autoMl.mlDescription}, - - - + + status_list = #{autoMl.statusList}, + diff --git a/ruoyi-modules/management-platform/src/main/resources/mapper/managementPlatform/AutoMlInsDao.xml b/ruoyi-modules/management-platform/src/main/resources/mapper/managementPlatform/AutoMlInsDao.xml index 05168691..ca1300d3 100644 --- a/ruoyi-modules/management-platform/src/main/resources/mapper/managementPlatform/AutoMlInsDao.xml +++ b/ruoyi-modules/management-platform/src/main/resources/mapper/managementPlatform/AutoMlInsDao.xml @@ -3,9 +3,9 @@ - insert into auto_ml_ins(auto_ml_id, model_path, img_path, node_status, node_result, param, source) + insert into auto_ml_ins(auto_ml_id, model_path, img_path, node_status, node_result, param, source, argo_ins_name, argo_ins_ns) values (#{autoMlIns.autoMlId}, #{autoMlIns.modelPath}, #{autoMlIns.imgPath}, #{autoMlIns.nodeStatus}, - #{autoMlIns.nodeResult}, #{autoMlIns.param}, #{autoMlIns.source}) + #{autoMlIns.nodeResult}, #{autoMlIns.param}, #{autoMlIns.source}, #{autoMlIns.argoInsName}, #{autoMlIns.argoInsNs}) @@ -29,7 +29,11 @@ state = #{autoMlIns.state}, + + update_time = #{autoMlIns.updateTime}, + + where id = #{autoMlIns.id} + + + + \ No newline at end of file From 9af40754e78dea138c3d90b1408df6a32e00041c Mon Sep 17 00:00:00 2001 From: chenzhihang <709011834@qq.com> Date: Thu, 28 Nov 2024 17:10:19 +0800 Subject: [PATCH 22/59] =?UTF-8?q?=E8=87=AA=E5=8A=A8=E6=9C=BA=E5=99=A8?= =?UTF-8?q?=E5=AD=A6=E4=B9=A0=E5=BC=80=E5=8F=91?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../autoML/AutoMlInsController.java | 6 +++++ .../platform/service/AutoMlInsService.java | 2 ++ .../service/impl/AutoMlInsServiceImpl.java | 12 ++++++---- .../service/impl/AutoMlServiceImpl.java | 22 +++++++++++++++---- 4 files changed, 34 insertions(+), 8 deletions(-) diff --git a/ruoyi-modules/management-platform/src/main/java/com/ruoyi/platform/controller/autoML/AutoMlInsController.java b/ruoyi-modules/management-platform/src/main/java/com/ruoyi/platform/controller/autoML/AutoMlInsController.java index 125fd668..874ec59c 100644 --- a/ruoyi-modules/management-platform/src/main/java/com/ruoyi/platform/controller/autoML/AutoMlInsController.java +++ b/ruoyi-modules/management-platform/src/main/java/com/ruoyi/platform/controller/autoML/AutoMlInsController.java @@ -52,4 +52,10 @@ public class AutoMlInsController extends BaseController { public GenericsAjaxResult terminateAutoMlIns(@PathVariable("id") Long id) { return genericsSuccess(this.autoMLInsService.terminateAutoMlIns(id)); } + + @GetMapping("{id}") + @ApiOperation("查看实验实例详情") + public GenericsAjaxResult getDetailById(@PathVariable("id") Long id) { + return genericsSuccess(this.autoMLInsService.getDetailById(id)); + } } diff --git a/ruoyi-modules/management-platform/src/main/java/com/ruoyi/platform/service/AutoMlInsService.java b/ruoyi-modules/management-platform/src/main/java/com/ruoyi/platform/service/AutoMlInsService.java index d4956a22..bb417ed2 100644 --- a/ruoyi-modules/management-platform/src/main/java/com/ruoyi/platform/service/AutoMlInsService.java +++ b/ruoyi-modules/management-platform/src/main/java/com/ruoyi/platform/service/AutoMlInsService.java @@ -21,4 +21,6 @@ public interface AutoMlInsService { AutoMlIns queryStatusFromArgo(AutoMlIns autoMlIns); boolean terminateAutoMlIns(Long id); + + AutoMlIns getDetailById(Long id); } diff --git a/ruoyi-modules/management-platform/src/main/java/com/ruoyi/platform/service/impl/AutoMlInsServiceImpl.java b/ruoyi-modules/management-platform/src/main/java/com/ruoyi/platform/service/impl/AutoMlInsServiceImpl.java index 3d11123e..cd01b672 100644 --- a/ruoyi-modules/management-platform/src/main/java/com/ruoyi/platform/service/impl/AutoMlInsServiceImpl.java +++ b/ruoyi-modules/management-platform/src/main/java/com/ruoyi/platform/service/impl/AutoMlInsServiceImpl.java @@ -87,7 +87,6 @@ public class AutoMlInsServiceImpl implements AutoMlInsService { public AutoMlIns queryStatusFromArgo(AutoMlIns ins) { String namespace = ins.getArgoInsNs(); String name = ins.getArgoInsName(); - Long id = ins.getId(); // 创建请求数据map Map requestData = new HashMap<>(); @@ -140,11 +139,11 @@ public class AutoMlInsServiceImpl implements AutoMlInsService { ins.setNodeStatus(nodeStatusJson); //终止态为终止不改 - if (!StringUtils.equals(ins.getStatus(), "Terminated")) { - ins.setStatus(StringUtils.isNotEmpty((String) status.get("phase")) ? (String) status.get("phase") : "Pending"); + if (!StringUtils.equals(ins.getStatus(), Constant.Terminated)) { + ins.setStatus(StringUtils.isNotEmpty((String) status.get("phase")) ? (String) status.get("phase") : Constant.Pending); } if (StringUtils.equals(ins.getStatus(), "Error")) { - ins.setStatus("Failed"); + ins.setStatus(Constant.Failed); } return ins; } catch (Exception e) { @@ -223,4 +222,9 @@ public class AutoMlInsServiceImpl implements AutoMlInsService { throw new RuntimeException("终止实例错误: " + e.getMessage(), e); } } + + @Override + public AutoMlIns getDetailById(Long id) { + return null; + } } diff --git a/ruoyi-modules/management-platform/src/main/java/com/ruoyi/platform/service/impl/AutoMlServiceImpl.java b/ruoyi-modules/management-platform/src/main/java/com/ruoyi/platform/service/impl/AutoMlServiceImpl.java index 4b7e58e9..97aea010 100644 --- a/ruoyi-modules/management-platform/src/main/java/com/ruoyi/platform/service/impl/AutoMlServiceImpl.java +++ b/ruoyi-modules/management-platform/src/main/java/com/ruoyi/platform/service/impl/AutoMlServiceImpl.java @@ -27,6 +27,7 @@ import org.springframework.web.multipart.MultipartFile; import javax.annotation.Resource; import java.io.File; import java.io.IOException; +import java.util.ArrayList; import java.util.HashMap; import java.util.List; import java.util.Map; @@ -43,6 +44,9 @@ public class AutoMlServiceImpl implements AutoMlService { @Value("${argo.workflowRun}") private String argoWorkflowRun; + @Value("${minio.endpoint}") + private String minioEndpoint; + @Resource private AutoMlDao autoMlDao; @@ -81,8 +85,7 @@ public class AutoMlServiceImpl implements AutoMlService { } AutoMl autoMl = new AutoMl(); BeanUtils.copyProperties(autoMlVo, autoMl); -// String username = SecurityUtils.getLoginUser().getUsername(); - String username = "admin"; + String username = SecurityUtils.getLoginUser().getUsername(); autoMl.setUpdateBy(username); String datasetJson = JacksonUtil.toJSONString(autoMlVo.getDataset()); autoMl.setDataset(datasetJson); @@ -123,8 +126,7 @@ public class AutoMlServiceImpl implements AutoMlService { public Map upload(MultipartFile file, String uuid) throws Exception { Map result = new HashMap<>(); -// String username = SecurityUtils.getLoginUser().getUsername(); - String username = "admin"; + String username = SecurityUtils.getLoginUser().getUsername(); String fileName = file.getOriginalFilename(); String path = localPath + "temp/" + username + "/automl_data/" + uuid; long sizeInBytes = file.getSize(); @@ -186,6 +188,18 @@ public class AutoMlServiceImpl implements AutoMlService { //替换argoInsName String outputString = JsonUtils.mapToJson(output); autoMlIns.setNodeResult(outputString.replace("{{workflow.name}}", (String) metadata.get("name"))); + + Map param_output = (Map) output.get("param_output"); + List output1 = (ArrayList) param_output.values().toArray()[0]; + Map output2 = (Map) output1.get(0); + String outputPath = minioEndpoint + "/" + output2.get("path").replace("{{workflow.name}}", (String) metadata.get("name")) + "/"; + autoMlIns.setModelPath(outputPath + "save_model.joblib"); + if (Constant.AutoMl_Classification.equals(autoMl.getTaskType())) { + autoMlIns.setImgPath(outputPath + "Auto-sklearn_accuracy_over_time.png" + "," + outputPath + "Train_Confusion_Matrix.png" + "," + outputPath + "Test_Confusion_Matrix.png"); + } else { + autoMlIns.setImgPath(outputPath + "Auto-sklearn_accuracy_over_time.png" + "," + outputPath + "regression.png"); + } + autoMlInsDao.insert(autoMlIns); } catch (Exception e) { From 48bd1f9e89454b6525b35d360485400022057550 Mon Sep 17 00:00:00 2001 From: chenzhihang <709011834@qq.com> Date: Thu, 28 Nov 2024 17:11:28 +0800 Subject: [PATCH 23/59] =?UTF-8?q?=E8=87=AA=E5=8A=A8=E6=9C=BA=E5=99=A8?= =?UTF-8?q?=E5=AD=A6=E4=B9=A0=E5=BC=80=E5=8F=91?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../com/ruoyi/platform/service/impl/AutoMlInsServiceImpl.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ruoyi-modules/management-platform/src/main/java/com/ruoyi/platform/service/impl/AutoMlInsServiceImpl.java b/ruoyi-modules/management-platform/src/main/java/com/ruoyi/platform/service/impl/AutoMlInsServiceImpl.java index cd01b672..a672da1f 100644 --- a/ruoyi-modules/management-platform/src/main/java/com/ruoyi/platform/service/impl/AutoMlInsServiceImpl.java +++ b/ruoyi-modules/management-platform/src/main/java/com/ruoyi/platform/service/impl/AutoMlInsServiceImpl.java @@ -225,6 +225,6 @@ public class AutoMlInsServiceImpl implements AutoMlInsService { @Override public AutoMlIns getDetailById(Long id) { - return null; + return this.autoMlInsDao.queryById(id); } } From 605a0614e9f203e0e6e8c60f7eff560be5ec3e8f Mon Sep 17 00:00:00 2001 From: cp3hnu Date: Fri, 29 Nov 2024 13:55:33 +0800 Subject: [PATCH 24/59] =?UTF-8?q?feat:=20=E5=AE=8C=E6=88=90=E8=87=AA?= =?UTF-8?q?=E5=8A=A8=E6=9C=BA=E5=99=A8=E5=AD=A6=E4=B9=A0=E8=AF=A6=E6=83=85?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../src/assets/img/dataset-config-icon.png | Bin 0 -> 3407 bytes .../src/components/BasicInfo/components.tsx | 113 ++++++ react-ui/src/components/BasicInfo/format.ts | 48 +++ react-ui/src/components/BasicInfo/index.tsx | 122 +------ react-ui/src/components/BasicInfo/types.ts | 14 + .../src/components/BasicTableInfo/index.tsx | 4 +- react-ui/src/enums/index.ts | 12 +- react-ui/src/pages/AutoML/Create/index.less | 2 +- react-ui/src/pages/AutoML/Create/index.tsx | 33 +- react-ui/src/pages/AutoML/List/index.tsx | 49 ++- .../AutoML/components/AutoMLBasic/index.less | 6 + .../AutoML/components/AutoMLBasic/index.tsx | 341 +++++++++++++++--- .../AutoML/components/ConfigInfo/index.tsx | 3 +- .../components/CreateForm/DatasetConfig.tsx | 2 +- .../components/CreateForm/ExecuteConfig.tsx | 27 +- .../components/CreateForm/TrialConfig.tsx | 11 +- .../AutoML/components/CreateForm/index.less | 15 +- react-ui/src/pages/AutoML/types.ts | 50 +-- react-ui/src/services/autoML.js | 2 +- react-ui/src/utils/index.ts | 12 +- 20 files changed, 616 insertions(+), 250 deletions(-) create mode 100644 react-ui/src/assets/img/dataset-config-icon.png create mode 100644 react-ui/src/components/BasicInfo/components.tsx create mode 100644 react-ui/src/components/BasicInfo/format.ts create mode 100644 react-ui/src/components/BasicInfo/types.ts diff --git a/react-ui/src/assets/img/dataset-config-icon.png b/react-ui/src/assets/img/dataset-config-icon.png new file mode 100644 index 0000000000000000000000000000000000000000..1afc4f729b44e9b6c0fc1b6cc4ddc39cd8d58672 GIT binary patch literal 3407 zcmV-V4Y2ZwP)Qv<+P^}77e4-V^7Zs!^AOb>>Kwe}w$!@Z{*~k6w-|yee zk`OlRnxf+{-r-L6-v9jPobR0fKj-}C!ss-**lC1?z60}Z_2#MYjLqS2l<0b%Ls6Im zKy!s)7DW*n=b}henb$OxxDb(;rfDJ0x!*91ww9KbM$nq4(lGr2V{^G&H5NXESCl(#W`)geYMw zp%4K`S^+|c#QVO+S_P|F^Ff%*~iYYyH zPwFe66qy)`K`Fyzik}fn05yf+3RaRZImc66QIrzUs&XlUPeOK2uwCH7rfI%R#1}O* zH2A&>sJ_8W6Kd(F-@i}m+G@NI3 zFssvmzQ^=1Vqw$2d)2|B!}sP`EEY#J8nx?s?quw-OF?uN_?60~hv&qcE>PY`girVR z>NaHys829uRp0xIYBVo`XmQsSz{0o0; zp7;9=%vuhs_w!Py!S^JSQ7rA1pI`2@TIpt8SAPxJiC_XFhC`viNWb4-n@MU~FlFUK zKh{|6`~)*n0@0!O&Za>N`cxv5kYnSFI($#DFEY_PoyFsGb93H-hjh8B4a0n_sp;@9 zGR;c|M)wRHykAw!3T#A*+Q;5U6JB^N6QgX$eNVIhWJBxmoUsl;<=W)R5Stqs4qudM zUOF&ZS$XwAjqx+!ViLI(&%`Fa_gE%I*^XxqreZUe+PEYb+EQO%dtoLSX~B5Phrg$? z$T$fGu`^`aM?4iB^Z4^hPE|Y;!%X9u!8r3ho;>Us<&G3DARGqV0 zUdBw~fm|C7L&V)2?vP93Q^4Pq!&#=A;8*pRWh@^khOgx*?17{t5A$q1EeQS6|f#P!yZ+o+q;Z- zYCIDkcIQ-{HzJ@Pd~`Ak9lAs@+Rs72Mmci4Q@iV|^gc>}N0pPVlQkz;Ay;#TU{UZzHyN0AvRlOF3< z9|cz*HN}Zw8I%_XfN>8BSb&JX(S!@UB9dQOS;Z}<2F$?f{hGq7Bp74hl$-hJM?UFo zZxL%zscpvq7CbaSZQoX@L>fFS9w=i*uqaVIA`s#q#}8ByiAPS>!o#e6&#rh}*{LYe zhDgMQ#C|9sw_|Vqrwoh-c}DHZGd#EY31i}hCE0H9Zn`)Lqg`Q|>$n40=Fptva0@Uoa|XA5aEV zNBaxx8wXR?BWX&dyU!U)Q0VrZ!dALHS~BHr!uf>?M+cd;B!X;#*}sF@wtOV5~jgP&A7k4$C4z&RtyDixW8nzxr!TLV`MYMGvY z)U=f~8UschQIQ~+qM2OiToGZ5Fe9qMqZ$Qv4px019&Ko-T?sZgnV$SAEiJzkSUe34 zbIw~$9U(ln=fVY)}h41Lzsxm&P>8CedU-G=&s}l z&zkR`*1BAR88AzFT4AUVuEg=myl&F;2#$T&1zlA-prdNYHT{a0!B{QC~6vXZR-m5?@WNw zEi(&Z{9a8{21_v9ao&SpDJ+{|IIBYyMnoae$(W^(bJdCHXF23w&ip+Ua?KN~ghCVB z4Vve$&_4mH0|nh~C^bTYivlg5NNb+~nsxvYu_IvR`64^TS{h3D3T=8xW` z?4eBZ(;v<}70&|9v{atKp57a0`M)s-&Dh9l?&-+HG&SyhoSks)an2Xh6pg$_i~^cQ zFJPbfyi1ZN%EUEC%m|HV7AnJUSL<@NKAjF}!3-Q&u?+Ej zb_$G)NXg&qeMF+y3`4ALZayL#9N}z%NjmpUViGpfa}a97EjvGcZ2X$u%kQ|QxIm0W zvHdCxiiRRU;l{GNr)3FyytnKuz?ko2LtotUECX!qh)f?q0NOt$dc@{NfQ+Ik9zj#Q z6T!o@TCKdUu1+4ak0n(10`WP*gFvru87v%biO?$gJ8SO(Y6riEz`GiIr=A=1VB1QT zsMcL`#Rc%n&jG?JI8|eQrMH7hd-vcnf-#K9XjQem9ID@GBbv914LM|yfEBN!NVSbcr{7ez%y0}z_VqrG}P6degBUFY%L`dMv4~7KM2FfF6Q+B`HlnwZ6ARVZbHDw z8Vo};TOp&F1cMDdy{W1G#SCDw8@?T(Hb6(9d@#mnO#2enDkmb!A;{2~JWdPSe6*p^ zLqk+SuL4ZWt74fw$>Q`=QRt4+W?0`EiqS>kRVVhW)AyRo%4eynwo2Ao0Q7kz68)La z*Ys|78uvvdn0p?5fkdc9<1{w7k~OC&6C2w~pW)d7nO@jX)_a{1K{df;%zKREd|`7b zv|mQTh>s?PUuF%N_KX}ySFz$1B+g5^h}*G|{~C*RKI-*)Wz(Tw7eez^ER`X+S3}`2 z?muDQBqI_wxXYmXk^O5BAdUd=11Z3+IbsCqG!tWP02z(HrM@p|7;R$5oum5br4Z%i z6_+?1Icsq+8!u&bmG}+#`wEm;6N|CWP%O%2lJ%(*>wYZg*TDLDP%2T8#^W?5u#)xp z*{@)psMb~5f{!{fKXJ&8o1A8}J;OfiZP7l#NW#Lx!UCJkz8D_54hq;)laqmg_Tk-b ze23hJX5QgQ#CR7=6JL~hED$hap-}VDbnZiOc^ObNLvWs=(pu0op@V`(ey}&Sgw_Z;z;mTI^HJ*lbYampK-p$!WN;Ag)<042l9AUy?SEx z2*-{18nOZ3RPL5e?Q2b<(EJz1hM*ESw + {formatValue.map((item: BasicInfoLink) => ( + + ))} + + ); + } else if (React.isValidElement(formatValue)) { + // 这个判断必须在下面的判断之前 + valueComponent = ( + + ); + } else if (typeof formatValue === 'object' && formatValue) { + valueComponent = ( + + ); + } else { + valueComponent = ( + + ); + } + return ( +

+
+ {label} +
+ {valueComponent} +
+ ); +} + +type BasicInfoItemValueProps = { + ellipsis?: boolean; + classPrefix: string; + value: string | React.ReactNode; + link?: string; + url?: string; +}; + +export function BasicInfoItemValue({ + value, + link, + url, + ellipsis, + classPrefix, +}: BasicInfoItemValueProps) { + const myClassName = `${classPrefix}__item__value`; + let component = undefined; + if (url && value) { + component = ( + + {value} + + ); + } else if (link && value) { + component = ( + + {value} + + ); + } else if (React.isValidElement(value)) { + return value; + } else { + component = {value ?? '--'}; + } + + return ( +
+ + {component} + +
+ ); +} diff --git a/react-ui/src/components/BasicInfo/format.ts b/react-ui/src/components/BasicInfo/format.ts new file mode 100644 index 00000000..0dae2422 --- /dev/null +++ b/react-ui/src/components/BasicInfo/format.ts @@ -0,0 +1,48 @@ +/* + * @Author: 赵伟 + * @Date: 2024-11-29 09:27:19 + * @Description: 用于 BasicInfo 和 BasicTableInfo 组件的常用转化格式 + */ + +// 格式化日期 +export { formatDate } from '@/utils/date'; + +/** + * 格式化字符串数组 + * @param value - 字符串数组 + * @returns 逗号分隔的字符串 + */ +export const formatList = (value: string[] | null | undefined): string => { + if ( + value === undefined || + value === null || + Array.isArray(value) === false || + value.length === 0 + ) { + return '--'; + } + return value.join(','); +}; + +/** + * 格式化布尔值 + * @param value - 布尔值 + * @returns "是" 或 "否" + */ +export const formatBoolean = (value: boolean): string => { + return value ? '是' : '否'; +}; + +type FormatEnum = (value: string | number) => string; + +/** + * 格式化枚举 + * @param options - 枚举选项 + * @returns 格式化枚举函数 + */ +export const formatEnum = (options: { value: string | number; label: string }[]): FormatEnum => { + return (value: string | number) => { + const option = options.find((item) => item.value === value); + return option ? option.label : '--'; + }; +}; diff --git a/react-ui/src/components/BasicInfo/index.tsx b/react-ui/src/components/BasicInfo/index.tsx index 1fa18783..1336d0b6 100644 --- a/react-ui/src/components/BasicInfo/index.tsx +++ b/react-ui/src/components/BasicInfo/index.tsx @@ -1,21 +1,10 @@ -import { Link } from '@umijs/max'; -import { Typography } from 'antd'; import classNames from 'classnames'; import React from 'react'; +import { BasicInfoItem } from './components'; import './index.less'; - -export type BasicInfoLink = { - value: string; - link?: string; - url?: string; -}; - -export type BasicInfoData = { - label: string; - value?: any; - ellipsis?: boolean; - format?: (_value?: any) => string | BasicInfoLink | BasicInfoLink[] | undefined; -}; +import type { BasicInfoData, BasicInfoLink } from './types'; +export * from './format'; +export type { BasicInfoData, BasicInfoLink }; type BasicInfoProps = { datas: BasicInfoData[]; @@ -24,20 +13,6 @@ type BasicInfoProps = { labelWidth: number; }; -type BasicInfoItemProps = { - data: BasicInfoData; - labelWidth: number; - classPrefix: string; -}; - -type BasicInfoItemValueProps = { - ellipsis?: boolean; - classPrefix: string; - value: string | React.ReactNode; - link?: string; - url?: string; -}; - export default function BasicInfo({ datas, className, style, labelWidth }: BasicInfoProps) { return (
@@ -52,92 +27,3 @@ export default function BasicInfo({ datas, className, style, labelWidth }: Basic
); } - -export function BasicInfoItem({ data, labelWidth, classPrefix }: BasicInfoItemProps) { - const { label, value, format, ellipsis } = data; - const formatValue = format ? format(value) : value; - const myClassName = `${classPrefix}__item`; - let valueComponent = undefined; - if (Array.isArray(formatValue)) { - valueComponent = ( -
- {formatValue.map((item: BasicInfoLink) => ( - - ))} -
- ); - } else if (React.isValidElement(formatValue)) { - // 这个判断必须在下面的判断之前 - valueComponent = ( - - ); - } else if (typeof formatValue === 'object' && formatValue) { - valueComponent = ( - - ); - } else { - valueComponent = ( - - ); - } - return ( -
-
- {label} -
- {valueComponent} -
- ); -} - -export function BasicInfoItemValue({ - value, - link, - url, - ellipsis, - classPrefix, -}: BasicInfoItemValueProps) { - const myClassName = `${classPrefix}__item__value`; - let component = undefined; - if (url && value) { - component = ( - - {value} - - ); - } else if (link && value) { - component = ( - - {value} - - ); - } else if (React.isValidElement(value)) { - return value; - } else { - component = {value ?? '--'}; - } - - return ( -
- - {component} - -
- ); -} diff --git a/react-ui/src/components/BasicInfo/types.ts b/react-ui/src/components/BasicInfo/types.ts new file mode 100644 index 00000000..a7c10ba0 --- /dev/null +++ b/react-ui/src/components/BasicInfo/types.ts @@ -0,0 +1,14 @@ +// 基础信息 +export type BasicInfoData = { + label: string; + value?: any; + ellipsis?: boolean; + format?: (_value?: any) => string | BasicInfoLink | BasicInfoLink[] | undefined; +}; + +// 值为链接的类型 +export type BasicInfoLink = { + value: string; + link?: string; + url?: string; +}; diff --git a/react-ui/src/components/BasicTableInfo/index.tsx b/react-ui/src/components/BasicTableInfo/index.tsx index df167ae2..104bc2bb 100644 --- a/react-ui/src/components/BasicTableInfo/index.tsx +++ b/react-ui/src/components/BasicTableInfo/index.tsx @@ -1,6 +1,8 @@ import classNames from 'classnames'; -import { BasicInfoItem, type BasicInfoData, type BasicInfoLink } from '../BasicInfo'; +import { BasicInfoItem } from '../BasicInfo/components'; +import { type BasicInfoData, type BasicInfoLink } from '../BasicInfo/types'; import './index.less'; +export * from '../BasicInfo/format'; export type { BasicInfoData, BasicInfoLink }; type BasicTableInfoProps = { diff --git a/react-ui/src/enums/index.ts b/react-ui/src/enums/index.ts index 05e14b34..26359678 100644 --- a/react-ui/src/enums/index.ts +++ b/react-ui/src/enums/index.ts @@ -92,19 +92,29 @@ export enum AutoMLTaskType { Regression = 'regression', } +export const autoMLTaskTypeOptions = [ + { label: '分类', value: AutoMLTaskType.Classification }, + { label: '回归', value: AutoMLTaskType.Regression }, +]; + // 自动化任务集成策略 export enum AutoMLEnsembleClass { Default = 'default', SingleBest = 'SingleBest', } +export const autoMLEnsembleClassOptions = [ + { label: '集成模型', value: AutoMLEnsembleClass.Default }, + { label: '单一最佳模型', value: AutoMLEnsembleClass.SingleBest }, +]; + // 自动化任务重采样策略 export enum AutoMLResamplingStrategy { Holdout = 'holdout', CrossValid = 'crossValid', } -export const resamplingStrategyOptions = [ +export const autoMLResamplingStrategyOptions = [ { label: 'holdout', value: AutoMLResamplingStrategy.Holdout }, { label: 'crossValid', value: AutoMLResamplingStrategy.CrossValid }, ]; diff --git a/react-ui/src/pages/AutoML/Create/index.less b/react-ui/src/pages/AutoML/Create/index.less index 7c88312e..f8d15d2e 100644 --- a/react-ui/src/pages/AutoML/Create/index.less +++ b/react-ui/src/pages/AutoML/Create/index.less @@ -1,4 +1,4 @@ -.create-service-version { +.create-automl { height: 100%; &__content { diff --git a/react-ui/src/pages/AutoML/Create/index.tsx b/react-ui/src/pages/AutoML/Create/index.tsx index 26e1866c..4d4d70d3 100644 --- a/react-ui/src/pages/AutoML/Create/index.tsx +++ b/react-ui/src/pages/AutoML/Create/index.tsx @@ -5,9 +5,9 @@ */ import PageTitle from '@/components/PageTitle'; -import { AutoMLTaskType } from '@/enums'; -import { addAutoMLReq, getDatasetInfoReq, updateAutoMLReq } from '@/services/autoML'; -import { parseJsonText, trimCharacter } from '@/utils'; +import { AutoMLEnsembleClass, AutoMLTaskType } from '@/enums'; +import { addAutoMLReq, getAutoMLInfoReq, updateAutoMLReq } from '@/services/autoML'; +import { convertEmptyStringToUndefined, parseJsonText, trimCharacter } from '@/utils'; import { safeInvoke } from '@/utils/functional'; import { to } from '@/utils/promise'; import SessionStorage from '@/utils/sessionStorage'; @@ -49,7 +49,7 @@ function CreateAutoML() { // 获取服务详情 const getAutoMLInfo = async (id: number, isCopy = false) => { - const [res] = await to(getDatasetInfoReq({ id })); + const [res] = await to(getAutoMLInfoReq({ id })); if (res && res.data) { const autoMLInfo: AutoMLData = res.data; const { @@ -96,7 +96,7 @@ function CreateAutoML() { } }; - // 创建版本 + // 创建、更新、复制实验 const createExperiment = async (formData: FormData) => { const include_classifier = formData['include_classifier']?.join(','); const include_feature_preprocessor = formData['include_feature_preprocessor']?.join(','); @@ -113,12 +113,12 @@ function CreateAutoML() { // 根据后台要求,修改表单数据 const object = { ...omit(formData), - include_classifier, - include_feature_preprocessor, - include_regressor, - exclude_classifier, - exclude_feature_preprocessor, - exclude_regressor, + include_classifier: convertEmptyStringToUndefined(include_classifier), + include_feature_preprocessor: convertEmptyStringToUndefined(include_feature_preprocessor), + include_regressor: convertEmptyStringToUndefined(include_regressor), + exclude_classifier: convertEmptyStringToUndefined(exclude_classifier), + exclude_feature_preprocessor: convertEmptyStringToUndefined(exclude_feature_preprocessor), + exclude_regressor: convertEmptyStringToUndefined(exclude_regressor), metrics: metrics ? JSON.stringify(metrics) : undefined, target_columns, }; @@ -148,7 +148,6 @@ function CreateAutoML() { navigate(-1); }; - const disabled = id !== null || id !== undefined; let buttonText = '新建'; let title = '新增实验'; if (id) { @@ -157,26 +156,28 @@ function CreateAutoML() { } return ( -
+
-
+
- + diff --git a/react-ui/src/pages/AutoML/List/index.tsx b/react-ui/src/pages/AutoML/List/index.tsx index be76e591..34a5d395 100644 --- a/react-ui/src/pages/AutoML/List/index.tsx +++ b/react-ui/src/pages/AutoML/List/index.tsx @@ -6,7 +6,7 @@ import KFIcon from '@/components/KFIcon'; import PageTitle from '@/components/PageTitle'; import { useCacheState } from '@/hooks/pageCacheState'; -import { deleteAutoMLReq, getAutoMLListReq } from '@/services/autoML'; +import { deleteAutoMLReq, getAutoMLListReq, runAutoMLReq } from '@/services/autoML'; import themes from '@/styles/theme.less'; import { to } from '@/utils/promise'; import SessionStorage from '@/utils/sessionStorage'; @@ -129,6 +129,24 @@ function AutoMLList() { navigate(`/pipeline/autoML/info/${record.id}`); }; + // 启动 + const startAutoML = async (record: AutoMLData) => { + const [res] = await to(runAutoMLReq(record.id)); + if (res) { + message.success('操作成功'); + getServiceList(); + } + }; + + // 停止 + const stopAutoML = async (record: AutoMLData) => { + const [res] = await to(runAutoMLReq(record.id)); + if (res) { + message.success('操作成功'); + getServiceList(); + } + }; + // 分页切换 const handleTableChange: TableProps['onChange'] = ( pagination, @@ -202,13 +220,10 @@ function AutoMLList() { { title: '操作', dataIndex: 'operation', - width: 400, + width: 320, key: 'operation', render: (_: any, record: AutoMLData) => (
- - + {record.run_state === 'Running' ? ( + + ) : ( + + )} { + if (!dataset || !dataset.name || !dataset.version) { + return '--'; + } + return `${dataset.name}:${dataset.version}`; +}; + +// 格式化优化方向 +const formatOptimizeMode = (value: boolean) => { + return value ? '越大越好' : '越小越好'; +}; + +const formatMetricsWeight = (value: string) => { + if (!value) { + return '--'; + } + const json = parseJsonText(value); + if (!json) { + return '--'; + } + return Object.entries(json) + .map(([key, value]) => `${key}:${value}`) + .join('\n'); +}; + function AutoMLBasic() { - useEffect(() => {}, []); + const params = useParams(); + const id = safeInvoke(Number)(params.id); + const [basicDatas, setBasicDatas] = useState([]); + const [configDatas, setConfigDatas] = useState([]); + + useEffect(() => { + if (id) { + getAutoMLInfo(id); + } + }, []); - const datas: BasicInfoData[] = [ - { - label: '项目名称', - value: '测试项目名称', - ellipsis: true, - }, - { - label: '项目名称', - value: '测试项目名称', - ellipsis: true, - }, - { - label: '项目名称', - value: '测试项目名称', - ellipsis: true, - }, - { - label: '项目名称', - value: '测试项目名称', - ellipsis: true, - }, - { - label: '项目名称', - value: '测试项目名称', - ellipsis: true, - }, - { - label: '项目名称', - value: '测试项目名称', - ellipsis: true, - }, - { - label: '项目名称', - value: '测试项目名称', - ellipsis: true, - }, - { - label: '项目名称', - value: , - ellipsis: false, - }, - ]; + // const basicDatas: BasicInfoData[] = [ + // { + // label: '项目名称', + // value: '测试项目名称', + // ellipsis: true, + // }, + // { + // label: '项目名称', + // value: '测试项目名称', + // ellipsis: true, + // }, + // { + // label: '项目名称', + // value: '测试项目名称', + // ellipsis: true, + // }, + // { + // label: '项目名称', + // value: '测试项目名称', + // ellipsis: true, + // }, + // { + // label: '项目名称', + // value: '测试项目名称', + // ellipsis: true, + // }, + // { + // label: '项目名称', + // value: '测试项目名称', + // ellipsis: true, + // }, + // { + // label: '项目名称', + // value: '测试项目名称', + // ellipsis: true, + // }, + // { + // label: '项目名称', + // value: , + // ellipsis: false, + // }, + // ]; + + // 获取服务详情 + const getAutoMLInfo = async (id: number) => { + const [res] = await to(getAutoMLInfoReq({ id })); + if (res && res.data) { + const info: AutoMLData = res.data; + const basicDatas: BasicInfoData[] = [ + { + label: '实验名称', + value: info.ml_name, + ellipsis: true, + }, + { + label: '实验描述', + value: info.ml_description, + ellipsis: true, + }, + { + label: '创建人', + value: info.create_by, + ellipsis: true, + }, + { + label: '创建时间', + value: info.create_time, + ellipsis: true, + format: formatDate, + }, + { + label: '更新时间', + value: info.update_time, + ellipsis: true, + format: formatDate, + }, + { + label: '状态', + value: info.run_state, + ellipsis: true, + }, + ]; + setBasicDatas(basicDatas); + + const configDatas: BasicInfoData[] = [ + { + label: '任务类型', + value: info.task_type, + ellipsis: true, + format: formatEnum(autoMLTaskTypeOptions), + }, + { + label: '特征预处理算法', + value: info.include_feature_preprocessor, + ellipsis: true, + }, + { + label: '排除的特征预处理算法', + value: info.exclude_feature_preprocessor, + ellipsis: true, + }, + { + label: info.task_type === AutoMLTaskType.Regression ? '回归算法' : '分类算法', + value: + info.task_type === AutoMLTaskType.Regression + ? info.include_regressor + : info.include_classifier, + ellipsis: true, + }, + { + label: info.task_type === AutoMLTaskType.Regression ? '排除的回归算法' : '排除的分类算法', + value: + info.task_type === AutoMLTaskType.Regression + ? info.exclude_regressor + : info.exclude_classifier, + ellipsis: true, + }, + { + label: '集成方式', + value: info.ensemble_class, + ellipsis: true, + format: formatEnum(autoMLEnsembleClassOptions), + }, + { + label: '集成模型数量', + value: info.ensemble_size, + ellipsis: true, + }, + { + label: '集成最佳模型数量', + value: info.ensemble_nbest, + ellipsis: true, + }, + { + label: '最大数量', + value: info.max_models_on_disc, + ellipsis: true, + }, + { + label: '内存限制(MB)', + value: info.memory_limit, + ellipsis: true, + }, + { + label: '时间限制(秒)', + value: info.per_run_time_limit, + ellipsis: true, + }, + { + label: '搜索时间限制(秒)', + value: info.time_left_for_this_task, + ellipsis: true, + }, + { + label: '重采样策略', + value: info.resampling_strategy, + ellipsis: true, + }, + { + label: '交叉验证折数', + value: info.folds, + ellipsis: true, + }, + { + label: '是否打乱', + value: info.shuffle, + ellipsis: true, + format: formatBoolean, + }, + { + label: '训练集比率', + value: info.train_size, + ellipsis: true, + }, + { + label: '测试集比率', + value: info.test_size, + ellipsis: true, + }, + { + label: '计算指标', + value: info.scoring_functions, + ellipsis: true, + }, + { + label: '随机种子', + value: info.seed, + ellipsis: true, + }, + + { + label: '数据集', + value: info.dataset, + ellipsis: true, + format: formatDataset, + }, + { + label: '预测目标列', + value: info.target_columns, + ellipsis: true, + }, + { + label: '指标名称', + value: info.metric_name, + ellipsis: true, + }, + { + label: '优化方向', + value: info.greater_is_better, + ellipsis: true, + format: formatOptimizeMode, + }, + { + label: '指标权重', + value: info.metrics, + ellipsis: true, + format: formatMetricsWeight, + }, + ]; + setConfigDatas(configDatas); + } + }; return (
- - + {/* - + /> */}
); diff --git a/react-ui/src/pages/AutoML/components/ConfigInfo/index.tsx b/react-ui/src/pages/AutoML/components/ConfigInfo/index.tsx index e12b291b..2fbb6825 100644 --- a/react-ui/src/pages/AutoML/components/ConfigInfo/index.tsx +++ b/react-ui/src/pages/AutoML/components/ConfigInfo/index.tsx @@ -3,7 +3,8 @@ import classNames from 'classnames'; import { useEffect } from 'react'; import ConfigTitle from '../ConfigTitle'; import styles from './index.less'; -export { type BasicInfoData }; +export * from '@/components/BasicInfo/format'; +export type { BasicInfoData }; type ConfigInfoProps = { title: string; diff --git a/react-ui/src/pages/AutoML/components/CreateForm/DatasetConfig.tsx b/react-ui/src/pages/AutoML/components/CreateForm/DatasetConfig.tsx index 79984a41..b3b3f2dd 100644 --- a/react-ui/src/pages/AutoML/components/CreateForm/DatasetConfig.tsx +++ b/react-ui/src/pages/AutoML/components/CreateForm/DatasetConfig.tsx @@ -10,7 +10,7 @@ function DatasetConfig() { <> diff --git a/react-ui/src/pages/AutoML/components/CreateForm/ExecuteConfig.tsx b/react-ui/src/pages/AutoML/components/CreateForm/ExecuteConfig.tsx index 87d5ad3f..b472ddb3 100644 --- a/react-ui/src/pages/AutoML/components/CreateForm/ExecuteConfig.tsx +++ b/react-ui/src/pages/AutoML/components/CreateForm/ExecuteConfig.tsx @@ -3,9 +3,11 @@ import { AutoMLEnsembleClass, AutoMLResamplingStrategy, AutoMLTaskType, - resamplingStrategyOptions, + autoMLEnsembleClassOptions, + autoMLResamplingStrategyOptions, + autoMLTaskTypeOptions, } from '@/enums'; -import { Col, Form, Input, InputNumber, Radio, Row, Select, Switch } from 'antd'; +import { Col, Form, InputNumber, Radio, Row, Select, Switch } from 'antd'; // 分类算法 const classificationAlgorithms = [ @@ -120,10 +122,10 @@ function ExecuteConfig() { name="task_type" rules={[{ required: true, message: '请选择任务类型' }]} > - form.resetFields(['metrics'])}> - 分类 - 回归 - + form.resetFields(['metrics'])} + > @@ -259,10 +261,7 @@ function ExecuteConfig() { name="ensemble_class" tooltip="仅使用单个最佳模型还是集成模型" > - - 集成模型 - 单一最佳模型 - + @@ -357,7 +356,7 @@ function ExecuteConfig() { @@ -51,9 +55,7 @@ select * from auto_ml_ins state = 1 - - and auto_ml_id = #{autoMlIns.autoMlId} - + and auto_ml_id = #{autoMlIns.autoMlId} order by update_time DESC limit #{pageable.offset}, #{pageable.pageSize} From e624cd117029eb649575066217a8f2250876ece4 Mon Sep 17 00:00:00 2001 From: chenzhihang <709011834@qq.com> Date: Sat, 30 Nov 2024 09:26:40 +0800 Subject: [PATCH 27/59] =?UTF-8?q?=E8=87=AA=E5=8A=A8=E6=9C=BA=E5=99=A8?= =?UTF-8?q?=E5=AD=A6=E4=B9=A0=E5=BC=80=E5=8F=91?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../java/com/ruoyi/platform/service/AutoMlInsService.java | 5 ++++- .../com/ruoyi/platform/service/impl/AutoMlServiceImpl.java | 6 +++++- 2 files changed, 9 insertions(+), 2 deletions(-) diff --git a/ruoyi-modules/management-platform/src/main/java/com/ruoyi/platform/service/AutoMlInsService.java b/ruoyi-modules/management-platform/src/main/java/com/ruoyi/platform/service/AutoMlInsService.java index bb417ed2..2131fc44 100644 --- a/ruoyi-modules/management-platform/src/main/java/com/ruoyi/platform/service/AutoMlInsService.java +++ b/ruoyi-modules/management-platform/src/main/java/com/ruoyi/platform/service/AutoMlInsService.java @@ -1,7 +1,8 @@ package com.ruoyi.platform.service; + +import com.ruoyi.platform.domain.AutoMlIns; import org.springframework.data.domain.Page; import org.springframework.data.domain.PageRequest; -import com.ruoyi.platform.domain.AutoMlIns; import java.io.IOException; import java.util.List; @@ -23,4 +24,6 @@ public interface AutoMlInsService { boolean terminateAutoMlIns(Long id); AutoMlIns getDetailById(Long id); + + void updateAutoMlStatus(Long autoMlId); } diff --git a/ruoyi-modules/management-platform/src/main/java/com/ruoyi/platform/service/impl/AutoMlServiceImpl.java b/ruoyi-modules/management-platform/src/main/java/com/ruoyi/platform/service/impl/AutoMlServiceImpl.java index 7b842299..d5e436e8 100644 --- a/ruoyi-modules/management-platform/src/main/java/com/ruoyi/platform/service/impl/AutoMlServiceImpl.java +++ b/ruoyi-modules/management-platform/src/main/java/com/ruoyi/platform/service/impl/AutoMlServiceImpl.java @@ -6,6 +6,7 @@ import com.ruoyi.platform.domain.AutoMl; import com.ruoyi.platform.domain.AutoMlIns; import com.ruoyi.platform.mapper.AutoMlDao; import com.ruoyi.platform.mapper.AutoMlInsDao; +import com.ruoyi.platform.service.AutoMlInsService; import com.ruoyi.platform.service.AutoMlService; import com.ruoyi.platform.utils.FileUtil; import com.ruoyi.platform.utils.HttpUtils; @@ -53,6 +54,9 @@ public class AutoMlServiceImpl implements AutoMlService { @Resource private AutoMlInsDao autoMlInsDao; + @Resource + private AutoMlInsService autoMlInsService; + @Override public Page queryByPage(String mlName, PageRequest pageRequest) { long total = autoMlDao.count(mlName); @@ -203,7 +207,7 @@ public class AutoMlServiceImpl implements AutoMlService { String seed = autoMl.getSeed() != null ? String.valueOf(autoMl.getSeed()) : "1"; autoMlIns.setRunHistoryPath(outputPath + "smac3-output/run_" + seed + "/runhistory.json"); autoMlInsDao.insert(autoMlIns); - + autoMlInsService.updateAutoMlStatus(id); } catch (Exception e) { throw new RuntimeException(e); } From fb6c8e1a4df4f1eb482c63e4f4d015d27dc7f7b0 Mon Sep 17 00:00:00 2001 From: chenzhihang <709011834@qq.com> Date: Sat, 30 Nov 2024 09:29:13 +0800 Subject: [PATCH 28/59] =?UTF-8?q?=E8=87=AA=E5=8A=A8=E6=9C=BA=E5=99=A8?= =?UTF-8?q?=E5=AD=A6=E4=B9=A0=E5=BC=80=E5=8F=91?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../com/ruoyi/platform/service/impl/AutoMlInsServiceImpl.java | 1 + 1 file changed, 1 insertion(+) diff --git a/ruoyi-modules/management-platform/src/main/java/com/ruoyi/platform/service/impl/AutoMlInsServiceImpl.java b/ruoyi-modules/management-platform/src/main/java/com/ruoyi/platform/service/impl/AutoMlInsServiceImpl.java index 7b945e76..4fee6d4b 100644 --- a/ruoyi-modules/management-platform/src/main/java/com/ruoyi/platform/service/impl/AutoMlInsServiceImpl.java +++ b/ruoyi-modules/management-platform/src/main/java/com/ruoyi/platform/service/impl/AutoMlInsServiceImpl.java @@ -218,6 +218,7 @@ public class AutoMlInsServiceImpl implements AutoMlInsService { ins.setStatus(Constant.Terminated); ins.setUpdateTime(new Date()); this.autoMlInsDao.update(ins); + updateAutoMlStatus(autoMlIns.getAutoMlId()); return true; } else { return false; From cab811d246b0d545cb7fe0557516b73078cee01b Mon Sep 17 00:00:00 2001 From: cp3hnu Date: Sat, 30 Nov 2024 15:43:11 +0800 Subject: [PATCH 29/59] =?UTF-8?q?feat:=20=E5=AE=8C=E6=88=90=E8=87=AA?= =?UTF-8?q?=E5=8A=A8=E6=9C=BA=E5=99=A8=E5=AD=A6=E4=B9=A0=E5=AE=9E=E4=BE=8B?= =?UTF-8?q?=E8=AF=A6=E6=83=85?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- react-ui/config/routes.ts | 5 + react-ui/src/pages/AutoML/Info/index.tsx | 39 +- react-ui/src/pages/AutoML/Instance/index.less | 17 + react-ui/src/pages/AutoML/Instance/index.tsx | 118 +++++ react-ui/src/pages/AutoML/List/index.tsx | 12 +- .../AutoML/components/AutoMLBasic/index.tsx | 449 ++++++++---------- .../AutoML/components/ConfigInfo/index.less | 5 - .../components/ExperimentHistory/index.less | 14 + .../components/ExperimentHistory/index.tsx | 132 +++++ .../components/ExperimentLog/index.less | 0 .../AutoML/components/ExperimentLog/index.tsx | 0 .../components/ExperimentResult/index.less | 18 + .../components/ExperimentResult/index.tsx | 51 ++ react-ui/src/pages/AutoML/types.ts | 20 +- .../Experiment/components/LogGroup/index.tsx | 2 +- react-ui/src/pages/Experiment/index.jsx | 1 + react-ui/src/requestConfig.ts | 3 +- .../services/{autoML.js => autoML/index.js} | 0 react-ui/src/services/file/index.js | 20 + 19 files changed, 628 insertions(+), 278 deletions(-) create mode 100644 react-ui/src/pages/AutoML/Instance/index.less create mode 100644 react-ui/src/pages/AutoML/Instance/index.tsx create mode 100644 react-ui/src/pages/AutoML/components/ExperimentHistory/index.less create mode 100644 react-ui/src/pages/AutoML/components/ExperimentHistory/index.tsx create mode 100644 react-ui/src/pages/AutoML/components/ExperimentLog/index.less create mode 100644 react-ui/src/pages/AutoML/components/ExperimentLog/index.tsx create mode 100644 react-ui/src/pages/AutoML/components/ExperimentResult/index.less create mode 100644 react-ui/src/pages/AutoML/components/ExperimentResult/index.tsx rename react-ui/src/services/{autoML.js => autoML/index.js} (100%) create mode 100644 react-ui/src/services/file/index.js diff --git a/react-ui/config/routes.ts b/react-ui/config/routes.ts index c58b182e..cf4e1aaf 100644 --- a/react-ui/config/routes.ts +++ b/react-ui/config/routes.ts @@ -169,6 +169,11 @@ export default [ path: 'edit/:id', component: './AutoML/Create/index', }, + { + name: '实验实例', + path: 'instance/:autoMLId/:id', + component: './AutoML/Instance/index', + }, ], }, ], diff --git a/react-ui/src/pages/AutoML/Info/index.tsx b/react-ui/src/pages/AutoML/Info/index.tsx index c167af97..caa2cc55 100644 --- a/react-ui/src/pages/AutoML/Info/index.tsx +++ b/react-ui/src/pages/AutoML/Info/index.tsx @@ -5,26 +5,23 @@ */ import KFIcon from '@/components/KFIcon'; import { CommonTabKeys } from '@/enums'; -import { useCacheState } from '@/hooks/pageCacheState'; +import { getAutoMLInfoReq } from '@/services/autoML'; import themes from '@/styles/theme.less'; -import { useNavigate } from '@umijs/max'; +import { safeInvoke } from '@/utils/functional'; +import { to } from '@/utils/promise'; +import { useParams } from '@umijs/max'; import { Tabs } from 'antd'; -import { useState } from 'react'; +import { useEffect, useState } from 'react'; import AutoMLBasic from '../components/AutoMLBasic'; import AutoMLTable from '../components/AutoMLTable'; +import { AutoMLData } from '../types'; import styles from './index.less'; -export type MirrorData = { - id: number; - name: string; - description: string; - create_time: string; -}; - function AutoMLInfo() { - const navigate = useNavigate(); - const [cacheState, setCacheState] = useCacheState(); - const [activeTab, setActiveTab] = useState(cacheState?.activeTab ?? CommonTabKeys.Public); + const [activeTab, setActiveTab] = useState(CommonTabKeys.Public); + const params = useParams(); + const autoMLId = safeInvoke(Number)(params.id); + const [autoMLInfo, setAutoMLInfo] = useState(undefined); const tabItems = [ { @@ -39,13 +36,27 @@ function AutoMLInfo() { }, ]; + useEffect(() => { + if (autoMLId) { + getAutoMLInfo(); + } + }, []); + + // 获取自动机器学习详情 + const getAutoMLInfo = async () => { + const [res] = await to(getAutoMLInfoReq({ id: autoMLId })); + if (res && res.data) { + setAutoMLInfo(res.data); + } + }; + return (
- {activeTab === CommonTabKeys.Public && } + {activeTab === CommonTabKeys.Public && } {activeTab === CommonTabKeys.Private && }
{activeTab === CommonTabKeys.Private && ( diff --git a/react-ui/src/pages/AutoML/Instance/index.less b/react-ui/src/pages/AutoML/Instance/index.less new file mode 100644 index 00000000..0c62da46 --- /dev/null +++ b/react-ui/src/pages/AutoML/Instance/index.less @@ -0,0 +1,17 @@ +.auto-ml-instance { + height: 100%; + + &__tabs { + height: 50px; + padding-left: 25px; + background-image: url(@/assets/img/page-title-bg.png); + background-repeat: no-repeat; + background-position: top center; + background-size: 100% 100%; + } + + &__content { + height: calc(100% - 60px); + margin-top: 10px; + } +} diff --git a/react-ui/src/pages/AutoML/Instance/index.tsx b/react-ui/src/pages/AutoML/Instance/index.tsx new file mode 100644 index 00000000..a8707c34 --- /dev/null +++ b/react-ui/src/pages/AutoML/Instance/index.tsx @@ -0,0 +1,118 @@ +import KFIcon from '@/components/KFIcon'; +import { AutoMLTaskType, ExperimentStatus } from '@/enums'; +import LogList from '@/pages/Experiment/components/LogList'; +import { getExperimentInsReq } from '@/services/autoML'; +import { parseJsonText } from '@/utils'; +import { safeInvoke } from '@/utils/functional'; +import { to } from '@/utils/promise'; +import { useParams } from '@umijs/max'; +import { Tabs } from 'antd'; +import { useEffect, useState } from 'react'; +import AutoMLBasic from '../components/AutoMLBasic'; +import ExperimentHistory from '../components/ExperimentHistory'; +import ExperimentResult from '../components/ExperimentResult'; +import { AutoMLData, AutoMLInstanceData } from '../types'; +import styles from './index.less'; + +enum TabKeys { + Params = 'params', + Log = 'log', + Result = 'result', + History = 'history', +} + +function AutoMLInstance() { + const [activeTab, setActiveTab] = useState(TabKeys.Params); + const [autoMLInfo, setAutoMLInfo] = useState(undefined); + const [instanceInfo, setInstanceInfo] = useState(undefined); + const params = useParams(); + // const autoMLId = safeInvoke(Number)(params.autoMLId); + const instanceId = safeInvoke(Number)(params.id); + + useEffect(() => { + if (instanceId) { + getExperimentInsInfo(); + } + }, []); + + // 获取实验实例详情 + const getExperimentInsInfo = async () => { + const [res] = await to(getExperimentInsReq(instanceId)); + if (res && res.data) { + const info = res.data as AutoMLInstanceData; + const { param, node_status } = info; + // 解析配置参数 + const paramJson = parseJsonText(param); + if (paramJson) { + setAutoMLInfo(paramJson); + } + // 进行节点状态 + const nodeStatusJson = parseJsonText(node_status); + if (nodeStatusJson) { + Object.keys(nodeStatusJson).forEach((key) => { + if (key.startsWith('auto-ml')) { + const value = nodeStatusJson[key]; + value.nodeId = key; + info.nodeStatus = value; + } + }); + } + setInstanceInfo(info); + } + }; + + const tabItems = [ + { + key: TabKeys.Params, + label: '参数信息', + icon: , + }, + { + key: TabKeys.Log, + label: '日志', + icon: , + }, + { + key: TabKeys.Result, + label: '实验结果', + icon: , + }, + { + key: TabKeys.History, + label: '运行历史', + icon: , + }, + ]; + + return ( +
+
+ +
+
+ {activeTab === TabKeys.Params && } + {activeTab === TabKeys.Log && instanceInfo && instanceInfo.nodeStatus && ( + + )} + {activeTab === TabKeys.Result && ( + + )} + {activeTab === TabKeys.History && ( + + )} +
+
+ ); +} + +export default AutoMLInstance; diff --git a/react-ui/src/pages/AutoML/List/index.tsx b/react-ui/src/pages/AutoML/List/index.tsx index 288001ec..aa269e50 100644 --- a/react-ui/src/pages/AutoML/List/index.tsx +++ b/react-ui/src/pages/AutoML/List/index.tsx @@ -143,8 +143,10 @@ function AutoMLList() { const startAutoML = async (record: AutoMLData) => { const [res] = await to(runAutoMLReq(record.id)); if (res) { - message.success('操作成功'); - getAutoMLList(); + message.success('运行成功'); + setExpandedRowKeys([record.id]); + refreshExperimentList(); + refreshExperimentIns(record.id); } }; @@ -183,8 +185,8 @@ function AutoMLList() { }; // 跳转到实验实例详情 - const gotoInstanceInfo = (item, record) => { - navigate({ pathname: `/pipeline/experiment/instance/${record.workflow_id}/${item.id}` }); + const gotoInstanceInfo = (autoML: AutoMLData, record: ExperimentInstanceData) => { + navigate({ pathname: `/pipeline/automl/instance/${autoML.id}/${record.id}` }); }; // 刷新实验实例列表 @@ -386,7 +388,7 @@ function AutoMLList() { gotoInstanceInfo(item, record)} + onClickInstance={(item) => gotoInstanceInfo(record, item)} onRemove={() => { refreshExperimentIns(record.id); refreshExperimentList(); diff --git a/react-ui/src/pages/AutoML/components/AutoMLBasic/index.tsx b/react-ui/src/pages/AutoML/components/AutoMLBasic/index.tsx index 30b646af..02a5e69b 100644 --- a/react-ui/src/pages/AutoML/components/AutoMLBasic/index.tsx +++ b/react-ui/src/pages/AutoML/components/AutoMLBasic/index.tsx @@ -1,19 +1,13 @@ +import { AutoMLTaskType, autoMLEnsembleClassOptions, autoMLTaskTypeOptions } from '@/enums'; import { AutoMLData } from '@/pages/AutoML/types'; -import { getAutoMLInfoReq } from '@/services/autoML'; -import { safeInvoke } from '@/utils/functional'; -import { to } from '@/utils/promise'; -import { useParams } from '@umijs/max'; -import { Flex } from 'antd'; -import { useEffect, useState } from 'react'; +import { parseJsonText } from '@/utils'; +import { useMemo } from 'react'; import ConfigInfo, { formatBoolean, formatDate, formatEnum, type BasicInfoData, } from '../ConfigInfo'; -// import CopyingText from '../CopyingText'; -import { AutoMLTaskType, autoMLEnsembleClassOptions, autoMLTaskTypeOptions } from '@/enums'; -import { parseJsonText } from '@/utils'; import styles from './index.less'; // 格式化数据集 @@ -42,263 +36,216 @@ const formatMetricsWeight = (value: string) => { .join('\n'); }; -function AutoMLBasic() { - const params = useParams(); - const id = safeInvoke(Number)(params.id); - const [basicDatas, setBasicDatas] = useState([]); - const [configDatas, setConfigDatas] = useState([]); +type AutoMLBasicProps = { + info?: AutoMLData; + hasBasicInfo?: boolean; +}; - useEffect(() => { - if (id) { - getAutoMLInfo(id); +function AutoMLBasic({ info, hasBasicInfo = true }: AutoMLBasicProps) { + const basicDatas: BasicInfoData[] = useMemo(() => { + if (!info) { + return []; } - }, []); - // const basicDatas: BasicInfoData[] = [ - // { - // label: '项目名称', - // value: '测试项目名称', - // ellipsis: true, - // }, - // { - // label: '项目名称', - // value: '测试项目名称', - // ellipsis: true, - // }, - // { - // label: '项目名称', - // value: '测试项目名称', - // ellipsis: true, - // }, - // { - // label: '项目名称', - // value: '测试项目名称', - // ellipsis: true, - // }, - // { - // label: '项目名称', - // value: '测试项目名称', - // ellipsis: true, - // }, - // { - // label: '项目名称', - // value: '测试项目名称', - // ellipsis: true, - // }, - // { - // label: '项目名称', - // value: '测试项目名称', - // ellipsis: true, - // }, - // { - // label: '项目名称', - // value: , - // ellipsis: false, - // }, - // ]; + return [ + { + label: '实验名称', + value: info.ml_name, + ellipsis: true, + }, + { + label: '实验描述', + value: info.ml_description, + ellipsis: true, + }, + { + label: '创建人', + value: info.create_by, + ellipsis: true, + }, + { + label: '创建时间', + value: info.create_time, + ellipsis: true, + format: formatDate, + }, + { + label: '更新时间', + value: info.update_time, + ellipsis: true, + format: formatDate, + }, + ]; + }, [info]); - // 获取服务详情 - const getAutoMLInfo = async (id: number) => { - const [res] = await to(getAutoMLInfoReq({ id })); - if (res && res.data) { - const info: AutoMLData = res.data; - const basicDatas: BasicInfoData[] = [ - { - label: '实验名称', - value: info.ml_name, - ellipsis: true, - }, - { - label: '实验描述', - value: info.ml_description, - ellipsis: true, - }, - { - label: '创建人', - value: info.create_by, - ellipsis: true, - }, - { - label: '创建时间', - value: info.create_time, - ellipsis: true, - format: formatDate, - }, - { - label: '更新时间', - value: info.update_time, - ellipsis: true, - format: formatDate, - }, - { - label: '状态', - value: info.run_state, - ellipsis: true, - }, - ]; - setBasicDatas(basicDatas); + const configDatas: BasicInfoData[] = useMemo(() => { + if (!info) { + return []; + } + return [ + { + label: '任务类型', + value: info.task_type, + ellipsis: true, + format: formatEnum(autoMLTaskTypeOptions), + }, + { + label: '特征预处理算法', + value: info.include_feature_preprocessor, + ellipsis: true, + }, + { + label: '排除的特征预处理算法', + value: info.exclude_feature_preprocessor, + ellipsis: true, + }, + { + label: info.task_type === AutoMLTaskType.Regression ? '回归算法' : '分类算法', + value: + info.task_type === AutoMLTaskType.Regression + ? info.include_regressor + : info.include_classifier, + ellipsis: true, + }, + { + label: info.task_type === AutoMLTaskType.Regression ? '排除的回归算法' : '排除的分类算法', + value: + info.task_type === AutoMLTaskType.Regression + ? info.exclude_regressor + : info.exclude_classifier, + ellipsis: true, + }, + { + label: '集成方式', + value: info.ensemble_class, + ellipsis: true, + format: formatEnum(autoMLEnsembleClassOptions), + }, + { + label: '集成模型数量', + value: info.ensemble_size, + ellipsis: true, + }, + { + label: '集成最佳模型数量', + value: info.ensemble_nbest, + ellipsis: true, + }, + { + label: '最大数量', + value: info.max_models_on_disc, + ellipsis: true, + }, + { + label: '内存限制(MB)', + value: info.memory_limit, + ellipsis: true, + }, + { + label: '时间限制(秒)', + value: info.per_run_time_limit, + ellipsis: true, + }, + { + label: '搜索时间限制(秒)', + value: info.time_left_for_this_task, + ellipsis: true, + }, + { + label: '重采样策略', + value: info.resampling_strategy, + ellipsis: true, + }, + { + label: '交叉验证折数', + value: info.folds, + ellipsis: true, + }, + { + label: '是否打乱', + value: info.shuffle, + ellipsis: true, + format: formatBoolean, + }, + { + label: '训练集比率', + value: info.train_size, + ellipsis: true, + }, + { + label: '测试集比率', + value: info.test_size, + ellipsis: true, + }, + { + label: '计算指标', + value: info.scoring_functions, + ellipsis: true, + }, + { + label: '随机种子', + value: info.seed, + ellipsis: true, + }, - const configDatas: BasicInfoData[] = [ - { - label: '任务类型', - value: info.task_type, - ellipsis: true, - format: formatEnum(autoMLTaskTypeOptions), - }, - { - label: '特征预处理算法', - value: info.include_feature_preprocessor, - ellipsis: true, - }, - { - label: '排除的特征预处理算法', - value: info.exclude_feature_preprocessor, - ellipsis: true, - }, - { - label: info.task_type === AutoMLTaskType.Regression ? '回归算法' : '分类算法', - value: - info.task_type === AutoMLTaskType.Regression - ? info.include_regressor - : info.include_classifier, - ellipsis: true, - }, - { - label: info.task_type === AutoMLTaskType.Regression ? '排除的回归算法' : '排除的分类算法', - value: - info.task_type === AutoMLTaskType.Regression - ? info.exclude_regressor - : info.exclude_classifier, - ellipsis: true, - }, - { - label: '集成方式', - value: info.ensemble_class, - ellipsis: true, - format: formatEnum(autoMLEnsembleClassOptions), - }, - { - label: '集成模型数量', - value: info.ensemble_size, - ellipsis: true, - }, - { - label: '集成最佳模型数量', - value: info.ensemble_nbest, - ellipsis: true, - }, - { - label: '最大数量', - value: info.max_models_on_disc, - ellipsis: true, - }, - { - label: '内存限制(MB)', - value: info.memory_limit, - ellipsis: true, - }, - { - label: '时间限制(秒)', - value: info.per_run_time_limit, - ellipsis: true, - }, - { - label: '搜索时间限制(秒)', - value: info.time_left_for_this_task, - ellipsis: true, - }, - { - label: '重采样策略', - value: info.resampling_strategy, - ellipsis: true, - }, - { - label: '交叉验证折数', - value: info.folds, - ellipsis: true, - }, - { - label: '是否打乱', - value: info.shuffle, - ellipsis: true, - format: formatBoolean, - }, - { - label: '训练集比率', - value: info.train_size, - ellipsis: true, - }, - { - label: '测试集比率', - value: info.test_size, - ellipsis: true, - }, - { - label: '计算指标', - value: info.scoring_functions, - ellipsis: true, - }, - { - label: '随机种子', - value: info.seed, - ellipsis: true, - }, + { + label: '数据集', + value: info.dataset, + ellipsis: true, + format: formatDataset, + }, + { + label: '预测目标列', + value: info.target_columns, + ellipsis: true, + }, + ]; + }, [info]); - { - label: '数据集', - value: info.dataset, - ellipsis: true, - format: formatDataset, - }, - { - label: '预测目标列', - value: info.target_columns, - ellipsis: true, - }, - { - label: '指标名称', - value: info.metric_name, - ellipsis: true, - }, - { - label: '优化方向', - value: info.greater_is_better, - ellipsis: true, - format: formatOptimizeMode, - }, - { - label: '指标权重', - value: info.metrics, - ellipsis: true, - format: formatMetricsWeight, - }, - ]; - setConfigDatas(configDatas); + const metricsData = useMemo(() => { + if (!info) { + return []; } - }; + return [ + { + label: '指标名称', + value: info.metric_name, + ellipsis: true, + }, + { + label: '优化方向', + value: info.greater_is_better, + ellipsis: true, + format: formatOptimizeMode, + }, + { + label: '指标权重', + value: info.metrics, + ellipsis: true, + format: formatMetricsWeight, + }, + ]; + }, [info]); return (
- - - {/* */} - + {hasBasicInfo && ( + + )} - +
); } diff --git a/react-ui/src/pages/AutoML/components/ConfigInfo/index.less b/react-ui/src/pages/AutoML/components/ConfigInfo/index.less index babbac65..78bc1a9c 100644 --- a/react-ui/src/pages/AutoML/components/ConfigInfo/index.less +++ b/react-ui/src/pages/AutoML/components/ConfigInfo/index.less @@ -12,18 +12,13 @@ :global { .kf-basic-info { - gap: 15px; width: 100%; &__item { - width: calc((100% - 15px) / 2); &__label { font-size: @font-size; text-align: left; text-align-last: left; - &::after { - display: none; - } } &__value { min-width: 0; diff --git a/react-ui/src/pages/AutoML/components/ExperimentHistory/index.less b/react-ui/src/pages/AutoML/components/ExperimentHistory/index.less new file mode 100644 index 00000000..24d00d35 --- /dev/null +++ b/react-ui/src/pages/AutoML/components/ExperimentHistory/index.less @@ -0,0 +1,14 @@ +.experiment-history { + height: 100%; + &__content { + height: 100%; + margin-top: 10px; + padding: 20px @content-padding 0; + background-color: white; + border-radius: 10px; + + &__table { + height: 100%; + } + } +} diff --git a/react-ui/src/pages/AutoML/components/ExperimentHistory/index.tsx b/react-ui/src/pages/AutoML/components/ExperimentHistory/index.tsx new file mode 100644 index 00000000..e95ccd42 --- /dev/null +++ b/react-ui/src/pages/AutoML/components/ExperimentHistory/index.tsx @@ -0,0 +1,132 @@ +import { getFileReq } from '@/services/file'; +import { to } from '@/utils/promise'; +import tableCellRender from '@/utils/table'; +import { Table, type TableProps } from 'antd'; +import classNames from 'classnames'; +import { useEffect, useState } from 'react'; +import styles from './index.less'; + +type ExperimentHistoryProps = { + fileUrl?: string; + isClassification: boolean; +}; + +type TableData = { + id?: string; + accuracy?: number; + duration?: number; + train_loss?: number; + status?: string; + feature?: string; + althorithm?: string; +}; + +function ExperimentHistory({ fileUrl, isClassification }: ExperimentHistoryProps) { + const [tableData, setTableData] = useState([]); + useEffect(() => { + if (fileUrl) { + getHistoryFile(); + } + }, [fileUrl]); + + // 获取实验运行历史记录 + const getHistoryFile = async () => { + const [res] = await to(getFileReq(fileUrl)); + if (res) { + const data: any[] = res.data; + const list: TableData[] = data.map((item) => { + return { + id: item[0]?.[0], + accuracy: item[1]?.[5]?.accuracy, + duration: item[1]?.[5]?.duration, + train_loss: item[1]?.[5]?.train_loss, + status: item[1]?.[2]?.['__enum__']?.split('.')?.[1], + }; + }); + list.forEach((item) => { + if (!item.id) return; + const config = (res as any).configs?.[item.id]; + item.feature = config?.['feature_preprocessor:__choice__']; + item.althorithm = isClassification + ? config?.['classifier:__choice__'] + : config?.['regressor:__choice__']; + }); + setTableData(list); + } + }; + + const columns: TableProps['columns'] = [ + { + title: 'ID', + dataIndex: 'id', + key: 'id', + width: 80, + render: tableCellRender(false), + }, + { + title: '准确率', + dataIndex: 'accuracy', + key: 'accuracy', + render: tableCellRender(true), + ellipsis: { showTitle: false }, + }, + { + title: '耗时', + dataIndex: 'duration', + key: 'duration', + render: tableCellRender(true), + ellipsis: { showTitle: false }, + }, + { + title: '训练损失', + dataIndex: 'train_loss', + key: 'train_loss', + render: tableCellRender(true), + ellipsis: { showTitle: false }, + }, + { + title: '特征处理', + dataIndex: 'feature', + key: 'feature', + render: tableCellRender(true), + ellipsis: { showTitle: false }, + }, + { + title: '算法', + dataIndex: 'althorithm', + key: 'althorithm', + render: tableCellRender(true), + ellipsis: { showTitle: false }, + }, + { + title: '状态', + dataIndex: 'status', + key: 'status', + width: 120, + render: tableCellRender(false), + }, + ]; + + return ( +
+
+
+
+ + + + ); +} + +export default ExperimentHistory; diff --git a/react-ui/src/pages/AutoML/components/ExperimentLog/index.less b/react-ui/src/pages/AutoML/components/ExperimentLog/index.less new file mode 100644 index 00000000..e69de29b diff --git a/react-ui/src/pages/AutoML/components/ExperimentLog/index.tsx b/react-ui/src/pages/AutoML/components/ExperimentLog/index.tsx new file mode 100644 index 00000000..e69de29b diff --git a/react-ui/src/pages/AutoML/components/ExperimentResult/index.less b/react-ui/src/pages/AutoML/components/ExperimentResult/index.less new file mode 100644 index 00000000..bdf1858c --- /dev/null +++ b/react-ui/src/pages/AutoML/components/ExperimentResult/index.less @@ -0,0 +1,18 @@ +.experiment-result { + height: 100%; + margin-top: 10px; + padding: 20px @content-padding 0; + overflow-y: auto; + background-color: white; + border-radius: 10px; + + &__text { + margin-bottom: 20px; + white-space: pre-wrap; + } + + &__image { + display: block; + height: 200px; + } +} diff --git a/react-ui/src/pages/AutoML/components/ExperimentResult/index.tsx b/react-ui/src/pages/AutoML/components/ExperimentResult/index.tsx new file mode 100644 index 00000000..2e0b71a6 --- /dev/null +++ b/react-ui/src/pages/AutoML/components/ExperimentResult/index.tsx @@ -0,0 +1,51 @@ +import { getFileReq } from '@/services/file'; +import { to } from '@/utils/promise'; +import { useEffect, useMemo, useState } from 'react'; +import styles from './index.less'; + +type ExperimentResultProps = { + fileUrl?: string; + imageUrl?: string; +}; + +function ExperimentResult({ fileUrl, imageUrl }: ExperimentResultProps) { + const [result, setResult] = useState(''); + + const images = useMemo(() => { + if (imageUrl) { + return imageUrl.split(',').map((item) => item.trim()); + } + return []; + }, [imageUrl]); + + useEffect(() => { + if (fileUrl) { + getResultFile(); + } + }, [fileUrl]); + + // 获取实验运行历史记录 + const getResultFile = async () => { + const [res] = await to(getFileReq(fileUrl)); + if (res) { + setResult(res as any as string); + } + }; + + return ( +
+
{result}
+ {images.map((item, index) => ( + + ))} +
+ ); +} + +export default ExperimentResult; diff --git a/react-ui/src/pages/AutoML/types.ts b/react-ui/src/pages/AutoML/types.ts index 3464e599..f068d168 100644 --- a/react-ui/src/pages/AutoML/types.ts +++ b/react-ui/src/pages/AutoML/types.ts @@ -61,6 +61,24 @@ export type AutoMLData = { 'metrics|dataset|include_classifier|include_feature_preprocessor|include_regressor|exclude_classifier|exclude_feature_preprocessor|exclude_regressor' >; -export type ExperimentInstanceData = { +// 自动机器学习实验实例 +export type AutoMLInstanceData = { id: number; + auto_ml_id: number; + result_path: string; + model_path: string; + img_path: string; + run_history_path: string; + state: number; + status: string; + node_status: string; + node_result: string; + param: string; + source: string | null; + argo_ins_name: string; + argo_ins_ns: string; + create_time: string; + update_time: string; + finish_time: string; + nodeStatus?: { [key: string]: string }; }; diff --git a/react-ui/src/pages/Experiment/components/LogGroup/index.tsx b/react-ui/src/pages/Experiment/components/LogGroup/index.tsx index b9a93e8f..27f3354c 100644 --- a/react-ui/src/pages/Experiment/components/LogGroup/index.tsx +++ b/react-ui/src/pages/Experiment/components/LogGroup/index.tsx @@ -135,7 +135,7 @@ function LogGroup({ const setupSockect = () => { let { host } = location; if (process.env.NODE_ENV === 'development') { - host = '172.20.32.185:31213'; + host = '172.20.32.181:31213'; } const socket = new WebSocket( `ws://${host}/newlog/realtimeLog?start=${start_time}&query={pod="${pod_name}"}`, diff --git a/react-ui/src/pages/Experiment/index.jsx b/react-ui/src/pages/Experiment/index.jsx index 95bc2953..998fb436 100644 --- a/react-ui/src/pages/Experiment/index.jsx +++ b/react-ui/src/pages/Experiment/index.jsx @@ -271,6 +271,7 @@ function Experiment() { const [res] = await to(runExperiments(id)); if (res) { message.success('运行成功'); + refreshExperimentList(); refreshExperimentIns(id); } else { message.error('运行失败'); diff --git a/react-ui/src/requestConfig.ts b/react-ui/src/requestConfig.ts index b268bc47..6d298d33 100644 --- a/react-ui/src/requestConfig.ts +++ b/react-ui/src/requestConfig.ts @@ -58,11 +58,12 @@ export const requestConfig: RequestConfig = { const options = config as RequestOptions; const skipErrorHandler = options?.skipErrorHandler; const skipLoading = options?.skipLoading; + const skipValidating = options?.skipValidating; if (!skipLoading) { Loading.hide(); } if (status >= 200 && status < 300) { - if (data && (data instanceof Blob || data.code === 200)) { + if (data && (skipValidating || data instanceof Blob || data.code === 200)) { return response; } else if (data && data.code === 401) { clearSessionToken(); diff --git a/react-ui/src/services/autoML.js b/react-ui/src/services/autoML/index.js similarity index 100% rename from react-ui/src/services/autoML.js rename to react-ui/src/services/autoML/index.js diff --git a/react-ui/src/services/file/index.js b/react-ui/src/services/file/index.js new file mode 100644 index 00000000..a6786007 --- /dev/null +++ b/react-ui/src/services/file/index.js @@ -0,0 +1,20 @@ +/* + * @Author: 赵伟 + * @Date: 2024-11-30 11:43:26 + * @Description: 请求文件,比如 json 文件 + */ + + +import { request } from '@umijs/max'; + +// 获取文件,不需要token,非结构化数据 +export function getFileReq(url, config) { + return request(url, { + method: 'GET', + headers: { + isToken: false, + }, + skipValidating: true, + ...config + }); +} \ No newline at end of file From 8a0b086d721f70989cdfcef2c306d86f19530322 Mon Sep 17 00:00:00 2001 From: chenzhihang <709011834@qq.com> Date: Mon, 2 Dec 2024 14:24:23 +0800 Subject: [PATCH 30/59] =?UTF-8?q?=E8=87=AA=E5=8A=A8=E6=9C=BA=E5=99=A8?= =?UTF-8?q?=E5=AD=A6=E4=B9=A0=E5=BC=80=E5=8F=91?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../main/resources/mapper/managementPlatform/AutoMlDao.xml | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/ruoyi-modules/management-platform/src/main/resources/mapper/managementPlatform/AutoMlDao.xml b/ruoyi-modules/management-platform/src/main/resources/mapper/managementPlatform/AutoMlDao.xml index 610fe143..c4453c5c 100644 --- a/ruoyi-modules/management-platform/src/main/resources/mapper/managementPlatform/AutoMlDao.xml +++ b/ruoyi-modules/management-platform/src/main/resources/mapper/managementPlatform/AutoMlDao.xml @@ -77,6 +77,7 @@ exclude_regressor = #{autoMl.excludeRegressor}, exclude_feature_preprocessor = #{autoMl.excludeFeaturePreprocessor}, scoring_functions = #{autoMl.scoringFunctions}, + metrics = #{autoMl.metrics}, test_size = #{autoMl.testSize}, @@ -98,9 +99,6 @@ metric_name = #{autoMl.metricName}, - - metrics = #{autoMl.metrics}, - greater_is_better = #{autoMl.greaterIsBetter}, From 4c2cdfaf61bdb3b6111d00745b5efcc95691253d Mon Sep 17 00:00:00 2001 From: chenzhihang <709011834@qq.com> Date: Mon, 2 Dec 2024 14:44:15 +0800 Subject: [PATCH 31/59] =?UTF-8?q?=E4=BF=AE=E6=94=B9=E9=85=8D=E7=BD=AE?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../management-platform/src/main/resources/bootstrap.yml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/ruoyi-modules/management-platform/src/main/resources/bootstrap.yml b/ruoyi-modules/management-platform/src/main/resources/bootstrap.yml index 29dd7cd4..a034e81d 100644 --- a/ruoyi-modules/management-platform/src/main/resources/bootstrap.yml +++ b/ruoyi-modules/management-platform/src/main/resources/bootstrap.yml @@ -14,16 +14,16 @@ spring: nacos: discovery: # 服务注册地址 - server-addr: 172.20.32.181:18848 + server-addr: nacos-ci4s.argo.svc:8848 username: nacos - password: nacos + password: h1n2x3j4y5@ retry: enabled: true # namespace: 6caf5d79-c4ce-4e3b-a357-141b74e52a01 config: # namespace: 6caf5d79-c4ce-4e3b-a357-141b74e52a01 # 配置中心地址 - server-addr: 172.20.32.181:18848 + server-addr: 1nacos-ci4s.argo.svc:8848 # 配置文件格式 file-extension: yml # 共享配置 From 557b85b3c3306c0efaf6a0ecd16f2301f0f482cc Mon Sep 17 00:00:00 2001 From: chenzhihang <709011834@qq.com> Date: Mon, 2 Dec 2024 14:52:25 +0800 Subject: [PATCH 32/59] =?UTF-8?q?=E4=BF=AE=E6=94=B9=E9=85=8D=E7=BD=AE?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../management-platform/src/main/resources/bootstrap.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/ruoyi-modules/management-platform/src/main/resources/bootstrap.yml b/ruoyi-modules/management-platform/src/main/resources/bootstrap.yml index a034e81d..65baf66d 100644 --- a/ruoyi-modules/management-platform/src/main/resources/bootstrap.yml +++ b/ruoyi-modules/management-platform/src/main/resources/bootstrap.yml @@ -19,9 +19,9 @@ spring: password: h1n2x3j4y5@ retry: enabled: true -# namespace: 6caf5d79-c4ce-4e3b-a357-141b74e52a01 config: -# namespace: 6caf5d79-c4ce-4e3b-a357-141b74e52a01 + username: nacos + password: h1n2x3j4y5@ # 配置中心地址 server-addr: 1nacos-ci4s.argo.svc:8848 # 配置文件格式 From b0e3f2660b7a2d9b0de2826f450458ad1c341207 Mon Sep 17 00:00:00 2001 From: chenzhihang <709011834@qq.com> Date: Mon, 2 Dec 2024 15:00:07 +0800 Subject: [PATCH 33/59] =?UTF-8?q?=E4=BF=AE=E6=94=B9=E9=85=8D=E7=BD=AE?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../java/com/ruoyi/platform/utils/K8sClientUtil.java | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/ruoyi-modules/management-platform/src/main/java/com/ruoyi/platform/utils/K8sClientUtil.java b/ruoyi-modules/management-platform/src/main/java/com/ruoyi/platform/utils/K8sClientUtil.java index 34975688..0ef17539 100644 --- a/ruoyi-modules/management-platform/src/main/java/com/ruoyi/platform/utils/K8sClientUtil.java +++ b/ruoyi-modules/management-platform/src/main/java/com/ruoyi/platform/utils/K8sClientUtil.java @@ -12,7 +12,9 @@ import io.kubernetes.client.openapi.ApiException; import io.kubernetes.client.openapi.apis.AppsV1Api; import io.kubernetes.client.openapi.apis.CoreV1Api; import io.kubernetes.client.openapi.models.*; +import io.kubernetes.client.util.ClientBuilder; import io.kubernetes.client.util.Config; +import io.kubernetes.client.util.credentials.AccessTokenAuthentication; import lombok.extern.slf4j.Slf4j; import org.apache.commons.lang.StringUtils; import org.json.JSONObject; @@ -61,10 +63,10 @@ public class K8sClientUtil { this.http = http; this.token = token; try { -// this.apiClient = new ClientBuilder(). -// setBasePath(http).setVerifyingSsl(false). -// setAuthentication(new AccessTokenAuthentication(token)).build(); - this.apiClient = Config.fromCluster(); + this.apiClient = new ClientBuilder(). + setBasePath(http).setVerifyingSsl(false). + setAuthentication(new AccessTokenAuthentication(token)).build(); +// this.apiClient = Config.fromCluster(); } catch (Exception e) { log.error("构建K8s-Client异常", e); throw new RuntimeException("构建K8s-Client异常"); From b1556da623257d8e7146e357a34b38d12763040c Mon Sep 17 00:00:00 2001 From: chenzhihang <709011834@qq.com> Date: Mon, 2 Dec 2024 15:07:14 +0800 Subject: [PATCH 34/59] =?UTF-8?q?=E4=BF=AE=E6=94=B9=E9=85=8D=E7=BD=AE?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../main/java/com/ruoyi/platform/utils/K8sClientUtil.java | 8 ++++---- .../management-platform/src/main/resources/bootstrap.yml | 2 +- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/ruoyi-modules/management-platform/src/main/java/com/ruoyi/platform/utils/K8sClientUtil.java b/ruoyi-modules/management-platform/src/main/java/com/ruoyi/platform/utils/K8sClientUtil.java index 0ef17539..3930c112 100644 --- a/ruoyi-modules/management-platform/src/main/java/com/ruoyi/platform/utils/K8sClientUtil.java +++ b/ruoyi-modules/management-platform/src/main/java/com/ruoyi/platform/utils/K8sClientUtil.java @@ -63,10 +63,10 @@ public class K8sClientUtil { this.http = http; this.token = token; try { - this.apiClient = new ClientBuilder(). - setBasePath(http).setVerifyingSsl(false). - setAuthentication(new AccessTokenAuthentication(token)).build(); -// this.apiClient = Config.fromCluster(); +// this.apiClient = new ClientBuilder(). +// setBasePath(http).setVerifyingSsl(false). +// setAuthentication(new AccessTokenAuthentication(token)).build(); + this.apiClient = Config.fromCluster(); } catch (Exception e) { log.error("构建K8s-Client异常", e); throw new RuntimeException("构建K8s-Client异常"); diff --git a/ruoyi-modules/management-platform/src/main/resources/bootstrap.yml b/ruoyi-modules/management-platform/src/main/resources/bootstrap.yml index 65baf66d..22eaf828 100644 --- a/ruoyi-modules/management-platform/src/main/resources/bootstrap.yml +++ b/ruoyi-modules/management-platform/src/main/resources/bootstrap.yml @@ -23,7 +23,7 @@ spring: username: nacos password: h1n2x3j4y5@ # 配置中心地址 - server-addr: 1nacos-ci4s.argo.svc:8848 + server-addr: nacos-ci4s.argo.svc:8848 # 配置文件格式 file-extension: yml # 共享配置 From 1b70a295f4789350edf4bfee664ccd0bb74cf9d4 Mon Sep 17 00:00:00 2001 From: cp3hnu Date: Mon, 2 Dec 2024 16:02:24 +0800 Subject: [PATCH 35/59] =?UTF-8?q?feat:=20=E5=AE=8C=E6=88=90=E8=87=AA?= =?UTF-8?q?=E5=8A=A8=E6=9C=BA=E5=99=A8=E5=AD=A6=E4=B9=A0=E5=AE=9E=E9=AA=8C?= =?UTF-8?q?=E5=AE=9E=E4=BE=8B=E8=AF=A6=E6=83=85?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- react-ui/src/app.tsx | 8 +- react-ui/src/assets/img/resample-icon.png | Bin 0 -> 1281 bytes react-ui/src/enums/index.ts | 2 +- react-ui/src/global.less | 2 +- react-ui/src/iconfont/iconfont.js | 2 +- react-ui/src/pages/AutoML/Create/index.tsx | 23 ++- react-ui/src/pages/AutoML/Info/index.tsx | 17 +- react-ui/src/pages/AutoML/Instance/index.less | 41 ++++- react-ui/src/pages/AutoML/Instance/index.tsx | 148 ++++++++++++++---- .../AutoML/components/AutoMLBasic/index.tsx | 67 +++++++- .../AutoML/components/ConfigInfo/index.less | 4 +- .../AutoML/components/ConfigTitle/index.less | 1 + .../components/CreateForm/ExecuteConfig.tsx | 88 ++++++----- .../AutoML/components/CreateForm/index.less | 136 +--------------- .../components/ExperimentHistory/index.less | 6 +- .../components/ExperimentResult/index.less | 33 +++- .../components/ExperimentResult/index.tsx | 23 +-- react-ui/src/pages/AutoML/types.ts | 3 +- .../components/ExperimentDrawer/index.less | 6 + .../components/ExperimentDrawer/index.tsx | 18 ++- .../Experiment/components/LogGroup/index.tsx | 2 +- .../Experiment/components/LogList/index.less | 6 +- react-ui/src/types.ts | 10 ++ 23 files changed, 368 insertions(+), 278 deletions(-) create mode 100644 react-ui/src/assets/img/resample-icon.png diff --git a/react-ui/src/app.tsx b/react-ui/src/app.tsx index 7af61ab5..2f757e15 100644 --- a/react-ui/src/app.tsx +++ b/react-ui/src/app.tsx @@ -50,7 +50,7 @@ export async function getInitialState(): Promise { // 如果不是登录页面,执行 const { location } = history; - console.log('getInitialState', needAuth(location.pathname)); + // console.log('getInitialState', needAuth(location.pathname)); if (needAuth(location.pathname)) { const currentUser = await fetchUserInfo(); return { @@ -163,7 +163,7 @@ export const layout: RuntimeConfig['layout'] = ({ initialState }) => { export const onRouteChange: RuntimeConfig['onRouteChange'] = async (e) => { const { location } = e; const menus = getRemoteMenu(); - console.log('onRouteChange', menus); + // console.log('onRouteChange', menus); if (menus === null && needAuth(location.pathname)) { history.go(0); } @@ -174,12 +174,12 @@ export const patchRoutes: RuntimeConfig['patchRoutes'] = (e) => { }; export const patchClientRoutes: RuntimeConfig['patchClientRoutes'] = (e) => { - console.log('patchClientRoutes', e); + // console.log('patchClientRoutes', e); patchRouteWithRemoteMenus(e.routes); }; export function render(oldRender: () => void) { - console.log('render'); + // console.log('render'); const token = getAccessToken(); if (!token || token?.length === 0) { oldRender(); diff --git a/react-ui/src/assets/img/resample-icon.png b/react-ui/src/assets/img/resample-icon.png new file mode 100644 index 0000000000000000000000000000000000000000..fa24c1aa9c82738a5a6907422b8fca983cbd723a GIT binary patch literal 1281 zcmV+c1^)VpP)>GQg}T3ov9*Z}=SlJ2Aj8 zJqn-aJ5yjXBA`V8**zm2vQWeO!43;jx=bjl#c*Wm^p%tphg@G;R!I7>zlC>?OJ;;a z?-~FDK$ed4`D6et95-lr>}$T|CR=m@fCFnuP;vtUs9Kg_8zzA$+icLVn^TafLN?VV zJcRWZ(=v`l-$&2$595-VQAPzVhLcACN(h5C8Hlv2V}jP<{vrIuz+ep{G9^U-Q(($4 zGw6+dD8XwWjH~Fe@+~s}`3jW%5gPv~7;rb6R9&_`BL#pk?E!!Yh@sJNXldDkQEcTn zw303ANHSbGmsy&6(K8ECZnw%F41jDUNgDv{Dtf2AY|AV$L^Dl`+6;s0grHs!!na_x zFS9qz;bDpnwDDc92>`F}t$5am^&LGu_S;@^z6MR*2_PZpErX~6Byd0^=!EITW_jLc zls(L64Y3%tN27LhY+#HTe+;hv^9wH}=!MbajHG6i*BnIi2q^hla;*yi;Kap=KBi9k zjvY_^nh*3c!4ADAd>>SGP??{Z^45G9${m{-#5{$=H z-6U(#d5v>0;yp@6R-f=RYGW?_d)ZB2KS8{D%K`b87C~&@Sk4%?!X?x_{qR8?WmX zOlDH5XMZk_VP8=ZkHhePpqDQEUjrkG)#d}p0O)yrxP`A2+T1$wfr3#6k%6NC^cjF; zmct;LJ|=J<<@Jk>V}4`;2cw**YLfL9(`saA2}&k`jQkY2pbjulgc(=rlgauV4gUhB zv3}BcesGR`CKsFeiK1GAhPMIs^#az9{gXx!!HgV;>j28{var a=(h=(h=document.getElementsByTagName("script"))[h.length-1]).getAttribute("data-injectcss"),h=h.getAttribute("data-disable-injectsvg");if(!h){var l,v,z,i,o,m=function(a,h){h.parentNode.insertBefore(a,h)};if(a&&!t.__iconfont__svg__cssinject__){t.__iconfont__svg__cssinject__=!0;try{document.write("")}catch(a){console&&console.log(a)}}l=function(){var a,h=document.createElement("div");h.innerHTML=t._iconfont_svg_string_4511447,(h=h.getElementsByTagName("svg")[0])&&(h.setAttribute("aria-hidden","true"),h.style.position="absolute",h.style.width=0,h.style.height=0,h.style.overflow="hidden",h=h,(a=document.body).firstChild?m(h,a.firstChild):a.appendChild(h))},document.addEventListener?~["complete","loaded","interactive"].indexOf(document.readyState)?setTimeout(l,0):(v=function(){document.removeEventListener("DOMContentLoaded",v,!1),l()},document.addEventListener("DOMContentLoaded",v,!1)):document.attachEvent&&(z=l,i=t.document,o=!1,d(),i.onreadystatechange=function(){"complete"==i.readyState&&(i.onreadystatechange=null,p())})}function p(){o||(o=!0,z())}function d(){try{i.documentElement.doScroll("left")}catch(a){return void setTimeout(d,50)}p()}})(window); \ No newline at end of file +window._iconfont_svg_string_4511447='',(t=>{var a=(h=(h=document.getElementsByTagName("script"))[h.length-1]).getAttribute("data-injectcss"),h=h.getAttribute("data-disable-injectsvg");if(!h){var l,v,z,i,o,m=function(a,h){h.parentNode.insertBefore(a,h)};if(a&&!t.__iconfont__svg__cssinject__){t.__iconfont__svg__cssinject__=!0;try{document.write("")}catch(a){console&&console.log(a)}}l=function(){var a,h=document.createElement("div");h.innerHTML=t._iconfont_svg_string_4511447,(h=h.getElementsByTagName("svg")[0])&&(h.setAttribute("aria-hidden","true"),h.style.position="absolute",h.style.width=0,h.style.height=0,h.style.overflow="hidden",h=h,(a=document.body).firstChild?m(h,a.firstChild):a.appendChild(h))},document.addEventListener?~["complete","loaded","interactive"].indexOf(document.readyState)?setTimeout(l,0):(v=function(){document.removeEventListener("DOMContentLoaded",v,!1),l()},document.addEventListener("DOMContentLoaded",v,!1)):document.attachEvent&&(z=l,i=t.document,o=!1,d(),i.onreadystatechange=function(){"complete"==i.readyState&&(i.onreadystatechange=null,p())})}function p(){o||(o=!0,z())}function d(){try{i.documentElement.doScroll("left")}catch(a){return void setTimeout(d,50)}p()}})(window); \ No newline at end of file diff --git a/react-ui/src/pages/AutoML/Create/index.tsx b/react-ui/src/pages/AutoML/Create/index.tsx index 4d4d70d3..1dc2c3cc 100644 --- a/react-ui/src/pages/AutoML/Create/index.tsx +++ b/react-ui/src/pages/AutoML/Create/index.tsx @@ -104,10 +104,15 @@ function CreateAutoML() { const exclude_classifier = formData['exclude_classifier']?.join(','); const exclude_feature_preprocessor = formData['exclude_feature_preprocessor']?.join(','); const exclude_regressor = formData['exclude_regressor']?.join(','); - const metrics = formData['metrics']?.reduce((acc, cur) => { - acc[cur.name] = cur.value; - return acc; - }, {} as Record); + const formMetrics = formData['metrics']; + const metrics = + formMetrics && Array.isArray(formMetrics) && formMetrics.length > 0 + ? formMetrics.reduce((acc, cur) => { + acc[cur.name] = cur.value; + return acc; + }, {} as Record) + : undefined; + const target_columns = trimCharacter(formData['target_columns'], ','); // 根据后台要求,修改表单数据 @@ -174,6 +179,16 @@ function CreateAutoML() { shuffle: false, ensemble_class: AutoMLEnsembleClass.Default, greater_is_better: true, + ensemble_size: 50, + ensemble_nbest: 50, + max_models_on_disc: 50, + memory_limit: 3072, + per_run_time_limit: 600, + time_left_for_this_task: 3600, + resampling_strategy: 'holdout', + test_size: 0.25, + train_size: 0.67, + seed: 1, }} > diff --git a/react-ui/src/pages/AutoML/Info/index.tsx b/react-ui/src/pages/AutoML/Info/index.tsx index caa2cc55..cc5247e2 100644 --- a/react-ui/src/pages/AutoML/Info/index.tsx +++ b/react-ui/src/pages/AutoML/Info/index.tsx @@ -4,16 +4,14 @@ * @Description: 自主机器学习详情 */ import KFIcon from '@/components/KFIcon'; +import PageTitle from '@/components/PageTitle'; import { CommonTabKeys } from '@/enums'; import { getAutoMLInfoReq } from '@/services/autoML'; -import themes from '@/styles/theme.less'; import { safeInvoke } from '@/utils/functional'; import { to } from '@/utils/promise'; import { useParams } from '@umijs/max'; -import { Tabs } from 'antd'; import { useEffect, useState } from 'react'; import AutoMLBasic from '../components/AutoMLBasic'; -import AutoMLTable from '../components/AutoMLTable'; import { AutoMLData } from '../types'; import styles from './index.less'; @@ -52,19 +50,10 @@ function AutoMLInfo() { return (
-
- -
+
- {activeTab === CommonTabKeys.Public && } - {activeTab === CommonTabKeys.Private && } +
- {activeTab === CommonTabKeys.Private && ( -
- - Trial是一次独立的尝试,他会使用某组超参来运行 -
- )}
); } diff --git a/react-ui/src/pages/AutoML/Instance/index.less b/react-ui/src/pages/AutoML/Instance/index.less index 0c62da46..889faeb5 100644 --- a/react-ui/src/pages/AutoML/Instance/index.less +++ b/react-ui/src/pages/AutoML/Instance/index.less @@ -2,16 +2,41 @@ height: 100%; &__tabs { - height: 50px; - padding-left: 25px; - background-image: url(@/assets/img/page-title-bg.png); - background-repeat: no-repeat; - background-position: top center; - background-size: 100% 100%; + height: 100%; + :global { + .ant-tabs-nav-list { + width: 100%; + height: 50px; + padding-left: 15px; + background-image: url(@/assets/img/page-title-bg.png); + background-repeat: no-repeat; + background-position: top center; + background-size: 100% 100%; + } + + .ant-tabs-content-holder { + height: calc(100% - 50px); + .ant-tabs-content { + height: 100%; + .ant-tabs-tabpane { + height: 100%; + } + } + } + } + } + + &__basic { + height: calc(100% - 10px); + margin-top: 10px; } - &__content { - height: calc(100% - 60px); + &__log { + height: calc(100% - 10px); margin-top: 10px; + padding: 20px calc(@content-padding - 8px); + overflow-y: visible; + background-color: white; + border-radius: 10px; } } diff --git a/react-ui/src/pages/AutoML/Instance/index.tsx b/react-ui/src/pages/AutoML/Instance/index.tsx index a8707c34..376961bb 100644 --- a/react-ui/src/pages/AutoML/Instance/index.tsx +++ b/react-ui/src/pages/AutoML/Instance/index.tsx @@ -2,12 +2,13 @@ import KFIcon from '@/components/KFIcon'; import { AutoMLTaskType, ExperimentStatus } from '@/enums'; import LogList from '@/pages/Experiment/components/LogList'; import { getExperimentInsReq } from '@/services/autoML'; +import { NodeStatus } from '@/types'; import { parseJsonText } from '@/utils'; import { safeInvoke } from '@/utils/functional'; import { to } from '@/utils/promise'; import { useParams } from '@umijs/max'; import { Tabs } from 'antd'; -import { useEffect, useState } from 'react'; +import { useEffect, useRef, useState } from 'react'; import AutoMLBasic from '../components/AutoMLBasic'; import ExperimentHistory from '../components/ExperimentHistory'; import ExperimentResult from '../components/ExperimentResult'; @@ -28,11 +29,15 @@ function AutoMLInstance() { const params = useParams(); // const autoMLId = safeInvoke(Number)(params.autoMLId); const instanceId = safeInvoke(Number)(params.id); + const evtSourceRef = useRef(null); useEffect(() => { if (instanceId) { getExperimentInsInfo(); } + return () => { + closeSSE(); + }; }, []); // 获取实验实例详情 @@ -40,7 +45,7 @@ function AutoMLInstance() { const [res] = await to(getExperimentInsReq(instanceId)); if (res && res.data) { const info = res.data as AutoMLInstanceData; - const { param, node_status } = info; + const { param, node_status, argo_ins_name, argo_ins_ns, status } = info; // 解析配置参数 const paramJson = parseJsonText(param); if (paramJson) { @@ -52,65 +57,142 @@ function AutoMLInstance() { Object.keys(nodeStatusJson).forEach((key) => { if (key.startsWith('auto-ml')) { const value = nodeStatusJson[key]; - value.nodeId = key; info.nodeStatus = value; } }); } setInstanceInfo(info); + // 运行中或者等待中,开启 SSE + if (status === ExperimentStatus.Pending || status === ExperimentStatus.Running) { + setupSSE(argo_ins_name, argo_ins_ns); + } } }; - const tabItems = [ + const setupSSE = (name: string, namespace: string) => { + let { origin } = location; + if (process.env.NODE_ENV === 'development') { + origin = 'http://172.20.32.181:31213'; + } + const params = encodeURIComponent(`metadata.namespace=${namespace},metadata.name=${name}`); + const evtSource = new EventSource( + `${origin}/api/v1/realtimeStatus?listOptions.fieldSelector=${params}`, + { withCredentials: false }, + ); + evtSource.onmessage = (event) => { + const data = event?.data; + if (!data) { + return; + } + const dataJson = parseJsonText(data); + if (dataJson) { + const nodes = dataJson?.result?.object?.status?.nodes; + if (nodes) { + const statusData = Object.values(nodes).find((node: any) => + node.displayName.startsWith('auto-ml'), + ) as NodeStatus; + if (statusData) { + setInstanceInfo((prev) => ({ + ...(prev as AutoMLInstanceData), + nodeStatus: statusData, + })); + + // 实验结束,关闭 SSE + if ( + statusData.phase !== ExperimentStatus.Pending && + statusData.phase !== ExperimentStatus.Running + ) { + closeSSE(); + getExperimentInsInfo(); + } + } + } + } + }; + evtSource.onerror = (error) => { + console.error('SSE error: ', error); + }; + + evtSourceRef.current = evtSource; + }; + + const closeSSE = () => { + if (evtSourceRef.current) { + evtSourceRef.current.close(); + evtSourceRef.current = null; + } + }; + + const basicTabItems = [ { key: TabKeys.Params, - label: '参数信息', + label: '基本信息', icon: , + children: ( + + ), }, { key: TabKeys.Log, label: '日志', - icon: , + icon: , + children: ( +
+ {instanceInfo && instanceInfo.nodeStatus && ( + + )} +
+ ), }, + ]; + + const resultTabItems = [ { key: TabKeys.Result, label: '实验结果', - icon: , + icon: , + children: ( + + ), }, { key: TabKeys.History, - label: '运行历史', + label: 'Trial列表', icon: , + children: ( + + ), }, ]; + const tabItems = + instanceInfo?.status === ExperimentStatus.Succeeded + ? [...basicTabItems, ...resultTabItems] + : basicTabItems; + return (
-
- -
-
- {activeTab === TabKeys.Params && } - {activeTab === TabKeys.Log && instanceInfo && instanceInfo.nodeStatus && ( - - )} - {activeTab === TabKeys.Result && ( - - )} - {activeTab === TabKeys.History && ( - - )} -
+
); } diff --git a/react-ui/src/pages/AutoML/components/AutoMLBasic/index.tsx b/react-ui/src/pages/AutoML/components/AutoMLBasic/index.tsx index 02a5e69b..b6f214ae 100644 --- a/react-ui/src/pages/AutoML/components/AutoMLBasic/index.tsx +++ b/react-ui/src/pages/AutoML/components/AutoMLBasic/index.tsx @@ -1,6 +1,11 @@ import { AutoMLTaskType, autoMLEnsembleClassOptions, autoMLTaskTypeOptions } from '@/enums'; import { AutoMLData } from '@/pages/AutoML/types'; +import { experimentStatusInfo } from '@/pages/Experiment/status'; +import { type NodeStatus } from '@/types'; import { parseJsonText } from '@/utils'; +import { elapsedTime } from '@/utils/date'; +import { Flex } from 'antd'; +import classNames from 'classnames'; import { useMemo } from 'react'; import ConfigInfo, { formatBoolean, @@ -38,10 +43,12 @@ const formatMetricsWeight = (value: string) => { type AutoMLBasicProps = { info?: AutoMLData; - hasBasicInfo?: boolean; + className?: string; + isInstance?: boolean; + runStatus?: NodeStatus; }; -function AutoMLBasic({ info, hasBasicInfo = true }: AutoMLBasicProps) { +function AutoMLBasic({ info, className, runStatus, isInstance = false }: AutoMLBasicProps) { const basicDatas: BasicInfoData[] = useMemo(() => { if (!info) { return []; @@ -142,7 +149,7 @@ function AutoMLBasic({ info, hasBasicInfo = true }: AutoMLBasicProps) { ellipsis: true, }, { - label: '时间限制(秒)', + label: '单次时间限制(秒)', value: info.per_run_time_limit, ellipsis: true, }, @@ -227,9 +234,59 @@ function AutoMLBasic({ info, hasBasicInfo = true }: AutoMLBasicProps) { ]; }, [info]); + const instanceDatas = useMemo(() => { + if (!runStatus) { + return []; + } + + return [ + { + label: '启动时间', + value: formatDate(runStatus.startedAt), + ellipsis: true, + }, + { + label: '执行时长', + value: elapsedTime(runStatus.startedAt, runStatus.finishedAt), + ellipsis: true, + }, + { + label: '状态', + value: ( + + +
+ {experimentStatusInfo[runStatus?.phase]?.label} +
+
+ ), + ellipsis: true, + }, + ]; + }, [runStatus]); + return ( -
- {hasBasicInfo && ( +
+ {isInstance && runStatus ? ( + + ) : ( @@ -325,7 +325,7 @@ function ExecuteConfig() {
@@ -339,13 +339,56 @@ function ExecuteConfig() { + + + + + + + + + + + + - - - - - - - - - - - - {/* +
{result}
- {images.map((item, index) => ( - - ))} + +
+ {images.map((item, index) => ( + + ))} +
); } diff --git a/react-ui/src/pages/AutoML/types.ts b/react-ui/src/pages/AutoML/types.ts index f068d168..339a9e51 100644 --- a/react-ui/src/pages/AutoML/types.ts +++ b/react-ui/src/pages/AutoML/types.ts @@ -1,4 +1,5 @@ import { type ParameterInputObject } from '@/components/ResourceSelect'; +import { type NodeStatus } from '@/types'; // 操作类型 export enum OperationType { @@ -80,5 +81,5 @@ export type AutoMLInstanceData = { create_time: string; update_time: string; finish_time: string; - nodeStatus?: { [key: string]: string }; + nodeStatus?: NodeStatus; }; diff --git a/react-ui/src/pages/Experiment/components/ExperimentDrawer/index.less b/react-ui/src/pages/Experiment/components/ExperimentDrawer/index.less index 1d5bdc34..83a91180 100644 --- a/react-ui/src/pages/Experiment/components/ExperimentDrawer/index.less +++ b/react-ui/src/pages/Experiment/components/ExperimentDrawer/index.less @@ -39,4 +39,10 @@ margin-right: 6px; border-radius: 50%; } + + &__log { + height: 100%; + padding: 8px; + background: white; + } } diff --git a/react-ui/src/pages/Experiment/components/ExperimentDrawer/index.tsx b/react-ui/src/pages/Experiment/components/ExperimentDrawer/index.tsx index 48cd0064..26da1c07 100644 --- a/react-ui/src/pages/Experiment/components/ExperimentDrawer/index.tsx +++ b/react-ui/src/pages/Experiment/components/ExperimentDrawer/index.tsx @@ -48,14 +48,16 @@ const ExperimentDrawer = ({ key: '1', label: '日志详情', children: ( - +
+ +
), icon: , }, diff --git a/react-ui/src/pages/Experiment/components/LogGroup/index.tsx b/react-ui/src/pages/Experiment/components/LogGroup/index.tsx index 27f3354c..75d914f5 100644 --- a/react-ui/src/pages/Experiment/components/LogGroup/index.tsx +++ b/react-ui/src/pages/Experiment/components/LogGroup/index.tsx @@ -90,7 +90,7 @@ function LogGroup({ start_time: startTime, }; const res = await getExperimentPodsLog(params); - const { log_detail } = res.data; + const { log_detail } = res.data || {}; if (log_detail) { setLogList((oldList) => oldList.concat(log_detail)); diff --git a/react-ui/src/pages/Experiment/components/LogList/index.less b/react-ui/src/pages/Experiment/components/LogList/index.less index 3909c8de..18fcb21f 100644 --- a/react-ui/src/pages/Experiment/components/LogList/index.less +++ b/react-ui/src/pages/Experiment/components/LogList/index.less @@ -1,7 +1,7 @@ .log-list { height: 100%; - padding: 8px; overflow-y: auto; + background: #19253b; &__empty { padding: 15px; @@ -12,4 +12,8 @@ word-break: break-all; background: #19253b; } + + &::-webkit-scrollbar-thumb { + background: rgba(255, 255, 255, 0.5); + } } diff --git a/react-ui/src/types.ts b/react-ui/src/types.ts index a7df0561..45884c02 100644 --- a/react-ui/src/types.ts +++ b/react-ui/src/types.ts @@ -114,3 +114,13 @@ export type ComputingResource = { standard: string; create_by: string; }; + +// 实验运行节点状态 +export type NodeStatus = { + id: string; // workflow Id + displayName: string; + name: string; + phase: ExperimentStatus; + startedAt: string; + finishedAt: string; +}; From 65c6baa7d6b1b679d5e5f9be1cc573b3e70235a0 Mon Sep 17 00:00:00 2001 From: cp3hnu Date: Mon, 2 Dec 2024 16:04:29 +0800 Subject: [PATCH 36/59] =?UTF-8?q?chore:=20=E5=88=A0=E9=99=A4=E5=A4=9A?= =?UTF-8?q?=E4=BD=99=E6=96=87=E4=BB=B6?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../CreateForm/ExecuteConfigDLC.tsx | 261 ------------------ .../components/CreateForm/ExecuteConfigMC.tsx | 82 ------ .../components/CreateForm/SearchConfig.tsx | 119 -------- .../components/CreateForm/UploadConfig.tsx | 80 ------ 4 files changed, 542 deletions(-) delete mode 100644 react-ui/src/pages/AutoML/components/CreateForm/ExecuteConfigDLC.tsx delete mode 100644 react-ui/src/pages/AutoML/components/CreateForm/ExecuteConfigMC.tsx delete mode 100644 react-ui/src/pages/AutoML/components/CreateForm/SearchConfig.tsx delete mode 100644 react-ui/src/pages/AutoML/components/CreateForm/UploadConfig.tsx diff --git a/react-ui/src/pages/AutoML/components/CreateForm/ExecuteConfigDLC.tsx b/react-ui/src/pages/AutoML/components/CreateForm/ExecuteConfigDLC.tsx deleted file mode 100644 index 6b2c63e6..00000000 --- a/react-ui/src/pages/AutoML/components/CreateForm/ExecuteConfigDLC.tsx +++ /dev/null @@ -1,261 +0,0 @@ -import CodeSelect from '@/components/CodeSelect'; -import ResourceSelect, { - requiredValidator, - ResourceSelectorType, -} from '@/components/ResourceSelect'; -import { MinusCircleOutlined, PlusCircleOutlined } from '@ant-design/icons'; -import { Button, Col, Flex, Form, Input, InputNumber, Radio, Row, Select } from 'antd'; -import styles from './index.less'; - -type ExecuteConfigDLCProps = { - disabled?: boolean; -}; - -function ExecuteConfigDLC({ disabled = false }: ExecuteConfigDLCProps) { - return ( - <> - -
- - - - - - - - - - - - - - - - - - - - - - - - 官方镜像 - 自定义镜像 - 镜像地址 - - - - - - - - {({ getFieldValue }) => { - const imageType = getFieldValue('image_type'); - if (imageType === 1 || imageType === 2) { - return ( - - - - ); - } - }} - - - - - - - - - - - - - - - {(fields, { add, remove }) => ( - <> - {fields.map(({ key, name, ...restField }, index) => ( - - - - - : - - - -
- - {index === fields.length - 1 && ( - - )} -
-
- ))} - {fields.length === 0 && ( - - - - )} - - )} -
-
- - - - - - - - - - - ); -} - -export default ExecuteConfigDLC; diff --git a/react-ui/src/pages/AutoML/components/CreateForm/ExecuteConfigMC.tsx b/react-ui/src/pages/AutoML/components/CreateForm/ExecuteConfigMC.tsx deleted file mode 100644 index 8f8b7078..00000000 --- a/react-ui/src/pages/AutoML/components/CreateForm/ExecuteConfigMC.tsx +++ /dev/null @@ -1,82 +0,0 @@ -import KFIcon from '@/components/KFIcon'; -import { MinusCircleOutlined, PlusCircleOutlined } from '@ant-design/icons'; -import { Button, Col, Flex, Form, Input, Row } from 'antd'; -import styles from './index.less'; - -type ExecuteConfigMCProps = { - disabled?: boolean; -}; - -function ExecuteConfigMC({ disabled = false }: ExecuteConfigMCProps) { - return ( - <> - - {(fields, { add, remove }) => ( - <> - - - - - -
- -
Key
-
命令
-
操作
-
- - {fields.map(({ key, name, ...restField }, index) => ( - - cmd{index + 1} - - - -
- - {index === fields.length - 1 && ( - - )} -
-
- ))} - {fields.length === 0 && ( -
- -
- )} -
- - )} - - - ); -} - -export default ExecuteConfigMC; diff --git a/react-ui/src/pages/AutoML/components/CreateForm/SearchConfig.tsx b/react-ui/src/pages/AutoML/components/CreateForm/SearchConfig.tsx deleted file mode 100644 index 255a6657..00000000 --- a/react-ui/src/pages/AutoML/components/CreateForm/SearchConfig.tsx +++ /dev/null @@ -1,119 +0,0 @@ -import SubAreaTitle from '@/components/SubAreaTitle'; -import { Col, Form, InputNumber, Row, Select, Switch } from 'antd'; -function SearchConfig() { - return ( - <> - - - - - - - - - - - - - - - - - - -
只允许上传 .csv 格式文件
-
-
- - ); -} - -export default UploadConfig; From ad68c6d558f22da5d292309bdba8e426e016b0c7 Mon Sep 17 00:00:00 2001 From: cp3hnu Date: Mon, 2 Dec 2024 16:13:12 +0800 Subject: [PATCH 37/59] =?UTF-8?q?chore:=20=E5=88=A0=E9=99=A4=E5=A4=9A?= =?UTF-8?q?=E4=BD=99=E6=96=87=E4=BB=B6=E5=92=8C=E6=B3=A8=E9=87=8A=E7=9A=84?= =?UTF-8?q?=E4=BB=A3=E7=A0=81?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../AutoML/components/AutoMLTable/index.less | 15 - .../AutoML/components/AutoMLTable/index.tsx | 305 ------------------ .../AutoML/components/CopyingText/index.tsx | 3 - .../components/CreateForm/BasicConfig.tsx | 23 -- .../components/CreateForm/ExecuteConfig.tsx | 101 ------ .../components/ExecuteScheduleCell/index.less | 30 -- .../components/ExecuteScheduleCell/index.tsx | 26 -- .../components/RunStatusCell/index.less | 19 -- .../AutoML/components/RunStatusCell/index.tsx | 44 --- .../AutoML/components/StatusChart/index.less | 8 - .../AutoML/components/StatusChart/index.tsx | 255 --------------- 11 files changed, 829 deletions(-) delete mode 100644 react-ui/src/pages/AutoML/components/AutoMLTable/index.less delete mode 100644 react-ui/src/pages/AutoML/components/AutoMLTable/index.tsx delete mode 100644 react-ui/src/pages/AutoML/components/ExecuteScheduleCell/index.less delete mode 100644 react-ui/src/pages/AutoML/components/ExecuteScheduleCell/index.tsx delete mode 100644 react-ui/src/pages/AutoML/components/RunStatusCell/index.less delete mode 100644 react-ui/src/pages/AutoML/components/RunStatusCell/index.tsx delete mode 100644 react-ui/src/pages/AutoML/components/StatusChart/index.less delete mode 100644 react-ui/src/pages/AutoML/components/StatusChart/index.tsx diff --git a/react-ui/src/pages/AutoML/components/AutoMLTable/index.less b/react-ui/src/pages/AutoML/components/AutoMLTable/index.less deleted file mode 100644 index 2d7d326f..00000000 --- a/react-ui/src/pages/AutoML/components/AutoMLTable/index.less +++ /dev/null @@ -1,15 +0,0 @@ -.auto-ml-table { - height: 100%; - padding: 20px @content-padding 0; - background-color: white; - border-radius: 10px; - &__filter { - display: flex; - align-items: center; - } - - &__table { - height: calc(100% - 32px - 28px); - margin-top: 28px; - } -} diff --git a/react-ui/src/pages/AutoML/components/AutoMLTable/index.tsx b/react-ui/src/pages/AutoML/components/AutoMLTable/index.tsx deleted file mode 100644 index 19979d0e..00000000 --- a/react-ui/src/pages/AutoML/components/AutoMLTable/index.tsx +++ /dev/null @@ -1,305 +0,0 @@ -/* - * @Author: 赵伟 - * @Date: 2024-04-16 13:58:08 - * @Description: 自主机器学习 - */ -import KFIcon from '@/components/KFIcon'; -import { useCacheState } from '@/hooks/pageCacheState'; -import { deleteServiceReq, getServiceListReq } from '@/services/modelDeployment'; -import themes from '@/styles/theme.less'; -import { to } from '@/utils/promise'; -import SessionStorage from '@/utils/sessionStorage'; -import tableCellRender, { TableCellValueType } from '@/utils/table'; -import { modalConfirm } from '@/utils/ui'; -import { useNavigate } from '@umijs/max'; -import { - App, - Button, - ConfigProvider, - Input, - Table, - type TablePaginationConfig, - type TableProps, -} from 'antd'; -import { type SearchProps } from 'antd/es/input'; -import classNames from 'classnames'; -import { useEffect, useState } from 'react'; -// import ExecuteScheduleCell from '../components/ExecuteScheduleCell'; -import { OperationType, ServiceData } from '@/pages/AutoML/types'; -import RunStatusCell from '../RunStatusCell'; -import styles from './index.less'; - -function AutoMLTable() { - const navigate = useNavigate(); - const { message } = App.useApp(); - const [cacheState, setCacheState] = useCacheState(); - const [searchText, setSearchText] = useState(cacheState?.searchText); - const [inputText, setInputText] = useState(cacheState?.searchText); - const [tableData, setTableData] = useState([]); - const [total, setTotal] = useState(0); - const [pagination, setPagination] = useState( - cacheState?.pagination ?? { - current: 1, - pageSize: 10, - }, - ); - - useEffect(() => { - getServiceList(); - }, [pagination, searchText]); - - // 获取模型部署服务列表 - const getServiceList = async () => { - const params: Record = { - page: pagination.current! - 1, - size: pagination.pageSize, - service_name: searchText, - }; - const [res] = await to(getServiceListReq(params)); - if (res && res.data) { - const { content = [], totalElements = 0 } = res.data; - setTableData(content); - setTotal(totalElements); - } - }; - - // 删除模型部署 - const deleteService = async (record: ServiceData) => { - const [res] = await to(deleteServiceReq(record.id)); - if (res) { - message.success('删除成功'); - // 如果是一页的唯一数据,删除时,请求第一页的数据 - // 否则直接刷新这一页的数据 - // 避免回到第一页 - if (tableData.length > 1) { - setPagination((prev) => ({ - ...prev, - current: 1, - })); - } else { - getServiceList(); - } - } - }; - - // 搜索 - const onSearch: SearchProps['onSearch'] = (value) => { - setSearchText(value); - }; - - // 处理删除 - const handleServiceDelete = (record: ServiceData) => { - modalConfirm({ - title: '删除后,该服务将不可恢复', - content: '是否确认删除?', - onOk: () => { - deleteService(record); - }, - }); - }; - - // 创建、更新服务 - const createService = (type: OperationType, record?: ServiceData) => { - SessionStorage.setItem( - SessionStorage.serviceInfoKey, - { - ...record, - operationType: type, - }, - true, - ); - - setCacheState({ - pagination, - searchText, - }); - - navigate(`/modelDeployment/createService`); - }; - - // 查看详情 - const toDetail = (record: ServiceData) => { - setCacheState({ - pagination, - searchText, - }); - - navigate(`/modelDeployment/serviceInfo/${record.id}`); - }; - - // 分页切换 - const handleTableChange: TableProps['onChange'] = ( - pagination, - _filters, - _sorter, - { action }, - ) => { - if (action === 'paginate') { - setPagination(pagination); - } - }; - - const columns: TableProps['columns'] = [ - { - title: '序号', - dataIndex: 'index', - key: 'index', - width: '20%', - render: tableCellRender(false, TableCellValueType.Index, { - page: pagination.current! - 1, - pageSize: pagination.pageSize!, - }), - }, - { - title: 'Trial ID', - dataIndex: 'service_name', - key: 'service_name', - width: '20%', - render: tableCellRender(false, TableCellValueType.Link, { - onClick: toDetail, - }), - }, - { - title: '状态', - dataIndex: 'run_status', - key: 'run_status', - width: '20%', - render: RunStatusCell, - }, - { - title: '最终指标', - dataIndex: 'service_type_name', - key: 'service_type_name', - width: '20%', - render: tableCellRender(true), - ellipsis: { showTitle: false }, - }, - { - title: '当前指标', - dataIndex: 'description', - key: 'description', - width: '20%', - render: tableCellRender(true), - ellipsis: { showTitle: false }, - }, - { - title: 'lr', - dataIndex: 'description', - key: 'description', - width: '20%', - render: tableCellRender(true), - ellipsis: { showTitle: false }, - }, - { - title: 'batch_size', - dataIndex: 'description', - key: 'description', - width: '20%', - render: tableCellRender(true), - ellipsis: { showTitle: false }, - }, - { - title: '修改时间', - dataIndex: 'update_time', - key: 'update_time', - width: '20%', - render: tableCellRender(true, TableCellValueType.Date), - ellipsis: { showTitle: false }, - }, - { - title: '执行时长', - dataIndex: 'description', - key: 'description', - width: '20%', - render: tableCellRender(true), - ellipsis: { showTitle: false }, - }, - { - title: '操作', - dataIndex: 'operation', - width: 250, - key: 'operation', - render: (_: any, record: ServiceData) => ( -
- - - - - - -
- ), - }, - ]; - - return ( -
-
- setInputText(e.target.value)} - style={{ width: 300 }} - value={inputText} - allowClear - /> - -
-
-
`共${total}条`, - }} - onChange={handleTableChange} - rowKey="id" - /> - - - ); -} - -export default AutoMLTable; diff --git a/react-ui/src/pages/AutoML/components/CopyingText/index.tsx b/react-ui/src/pages/AutoML/components/CopyingText/index.tsx index 1df718dd..b4c56f4e 100644 --- a/react-ui/src/pages/AutoML/components/CopyingText/index.tsx +++ b/react-ui/src/pages/AutoML/components/CopyingText/index.tsx @@ -1,12 +1,9 @@ import KFIcon from '@/components/KFIcon'; import { Typography } from 'antd'; - import styles from './index.less'; export type CopyingTextProps = { text: string; - onCopySuccess?: () => void; - onCopyFailed?: () => void; }; function CopyingText({ text }: CopyingTextProps) { diff --git a/react-ui/src/pages/AutoML/components/CreateForm/BasicConfig.tsx b/react-ui/src/pages/AutoML/components/CreateForm/BasicConfig.tsx index dbf32e32..aec61d3f 100644 --- a/react-ui/src/pages/AutoML/components/CreateForm/BasicConfig.tsx +++ b/react-ui/src/pages/AutoML/components/CreateForm/BasicConfig.tsx @@ -46,29 +46,6 @@ function BasicConfig() { - {/* - - - - - - */} - - {/* - {(fields, { add, remove }) => ( - <> - - - - - -
- -
参数名称
-
约束类型
-
搜索空间
-
操作
-
- - {fields.map(({ key, name, ...restField }, index) => ( - - - - - - - - - - -
- - {index === fields.length - 1 && ( - - )} -
-
- ))} - {fields.length === 0 && ( -
- -
- )} -
- - )} - */} ); } diff --git a/react-ui/src/pages/AutoML/components/ExecuteScheduleCell/index.less b/react-ui/src/pages/AutoML/components/ExecuteScheduleCell/index.less deleted file mode 100644 index 707a9b0d..00000000 --- a/react-ui/src/pages/AutoML/components/ExecuteScheduleCell/index.less +++ /dev/null @@ -1,30 +0,0 @@ -.execute-schedule-cell { - display: flex; - flex-direction: row; - align-items: center; - - &__progress { - width: 112px; - height: 16px; - padding: 4px 5px; - background: rgba(96, 107, 122, 0.15); - border-radius: 8px; - - &__bar { - height: 7px; - background: linear-gradient(270deg, #1664ff 0%, rgba(22, 100, 255, 0.1) 100%); - border-radius: 9px; - } - } - - &__text { - margin-left: 6px; - color: @text-color-tertiary; - font-size: 12px; - letter-spacing: 2px; - - strong { - color: @text-color; - } - } -} diff --git a/react-ui/src/pages/AutoML/components/ExecuteScheduleCell/index.tsx b/react-ui/src/pages/AutoML/components/ExecuteScheduleCell/index.tsx deleted file mode 100644 index c2af2074..00000000 --- a/react-ui/src/pages/AutoML/components/ExecuteScheduleCell/index.tsx +++ /dev/null @@ -1,26 +0,0 @@ -/* - * @Author: 赵伟 - * @Date: 2024-10-29 18:35:41 - * @Description: 实验实例执行进度 - */ - -import styles from './index.less'; - -function ExecuteScheduleCell(progress?: number) { - const width = (progress || 0) * 100 + '%'; - return ( -
-
-
-
- {/* - 1/2 - */} -
- ); -} - -export default ExecuteScheduleCell; diff --git a/react-ui/src/pages/AutoML/components/RunStatusCell/index.less b/react-ui/src/pages/AutoML/components/RunStatusCell/index.less deleted file mode 100644 index b6aba701..00000000 --- a/react-ui/src/pages/AutoML/components/RunStatusCell/index.less +++ /dev/null @@ -1,19 +0,0 @@ -.run-status-cell { - color: @text-color; - - &--running { - color: @primary-color; - } - - &--stopped { - color: @abort-color; - } - - &--error { - color: @error-color; - } - - &--pending { - color: @warning-color; - } -} diff --git a/react-ui/src/pages/AutoML/components/RunStatusCell/index.tsx b/react-ui/src/pages/AutoML/components/RunStatusCell/index.tsx deleted file mode 100644 index a20a2fb6..00000000 --- a/react-ui/src/pages/AutoML/components/RunStatusCell/index.tsx +++ /dev/null @@ -1,44 +0,0 @@ -/* - * @Author: 赵伟 - * @Date: 2024-04-18 18:35:41 - * @Description: 实验运行状态 - */ -import { ServiceRunStatus } from '@/enums'; -import styles from './index.less'; - -export type ServiceRunStatusInfo = { - text: string; - classname: string; -}; - -export const statusInfo: Record = { - [ServiceRunStatus.Init]: { - text: '启动中', - classname: styles['run-status-cell'], - }, - [ServiceRunStatus.Running]: { - classname: styles['run-status-cell--running'], - text: '运行中', - }, - [ServiceRunStatus.Stopped]: { - classname: styles['run-status-cell--stopped'], - text: '已停止', - }, - [ServiceRunStatus.Failed]: { - classname: styles['run-status-cell--error'], - text: '失败', - }, - [ServiceRunStatus.Pending]: { - classname: styles['run-status-cell--pending'], - text: '挂起中', - }, -}; - -function RunStatusCell(status?: ServiceRunStatus | null) { - if (status === null || status === undefined || !statusInfo[status]) { - return --; - } - return {statusInfo[status].text}; -} - -export default RunStatusCell; diff --git a/react-ui/src/pages/AutoML/components/StatusChart/index.less b/react-ui/src/pages/AutoML/components/StatusChart/index.less deleted file mode 100644 index be1c816e..00000000 --- a/react-ui/src/pages/AutoML/components/StatusChart/index.less +++ /dev/null @@ -1,8 +0,0 @@ -.status-chart { - flex: none; - width: 380px; - min-width: 380px; - background: #ffffff; - border: 1px solid @border-color-base; - border-radius: 4px; -} diff --git a/react-ui/src/pages/AutoML/components/StatusChart/index.tsx b/react-ui/src/pages/AutoML/components/StatusChart/index.tsx deleted file mode 100644 index 2c8b8aa4..00000000 --- a/react-ui/src/pages/AutoML/components/StatusChart/index.tsx +++ /dev/null @@ -1,255 +0,0 @@ -import themes from '@/styles/theme.less'; -import * as echarts from 'echarts'; -import React, { useEffect, useRef } from 'react'; -import ConfigTitle from '../ConfigTitle'; -import styles from './index.less'; - -const colors = ['#c73131', '#6ac21d', '#1664ff', '#f0864d', '#8a8a8a']; - -const color1 = new echarts.graphic.LinearGradient( - 0, - 0, - 0, - 1, - [ - { offset: 0, color: '#c73131' }, - { offset: 1, color: '#ff7e96' }, - ], - false, -); - -const color2 = new echarts.graphic.LinearGradient( - 0, - 0, - 0, - 1, - [ - { offset: 0, color: '#6ac21d' }, - { offset: 1, color: '#96e850' }, - ], - false, -); -const color3 = new echarts.graphic.LinearGradient( - 0, - 0, - 0, - 1, - [ - { offset: 0, color: '#8c8c8c' }, - { offset: 1, color: '#c8c6c6' }, - ], - false, -); -const color4 = new echarts.graphic.LinearGradient( - 0, - 0, - 0, - 1, - [ - { offset: 0, color: '#ecb934' }, - { offset: 1, color: '#f0864d' }, - ], - false, -); - -const color5 = new echarts.graphic.LinearGradient( - 0, - 0, - 0, - 1, - [ - { offset: 0, color: '#7ea9fe' }, - { offset: 1, color: '#1664ff' }, - ], - false, -); - -const circleBgColor = new echarts.graphic.LinearGradient( - 0, - 0, - 0, - 1, - [ - { offset: 0, color: 'rgba(255, 255, 255, 0.62)' }, - { offset: 1, color: '#ebf2ff ' }, - ], - false, -); - -export type ExperimentStatistics = { - Failed: number; - Pending: number; - Running: number; - Succeeded: number; - Terminated: number; -}; - -type ExperimentChartProps = { - style?: React.CSSProperties; - chartData: ExperimentStatistics; -}; - -function StatusChart({ chartData, style }: ExperimentChartProps) { - const chartRef = useRef(null); - const total = - chartData.Failed + - chartData.Pending + - chartData.Running + - chartData.Succeeded + - chartData.Terminated; - const options: echarts.EChartsOption = { - title: { - show: true, - left: '29%', - top: 'center', - textAlign: 'center', - text: [`{a|${total}}`, '{b|Trials}'].join('\n'), - textStyle: { - rich: { - a: { - color: themes['textColor'], - fontSize: 20, - fontWeight: 700, - lineHeight: 28, - }, - b: { - color: themes['textColorSecondary'], - fontSize: 10, - fontWeight: 'normal', - }, - }, - }, - }, - tooltip: { - trigger: 'item', - }, - legend: { - top: 'center', - right: '5%', - orient: 'vertical', - icon: 'circle', - itemWidth: 6, - itemGap: 20, - height: 100, - }, - color: colors, //[color1, color2, color3, color4, color5], - series: [ - { - type: 'pie', - radius: '80%', - center: ['30%', '50%'], - avoidLabelOverlap: false, - label: { - show: false, - }, - tooltip: { - show: false, - }, - emphasis: { - label: { - show: false, - }, - disabled: true, - }, - animation: false, - labelLine: { - show: false, - }, - data: [ - { - value: 100, - itemStyle: { - color: circleBgColor, - borderColor: 'rgba(22, 100, 255, 0.08)', - borderWidth: 1, - }, - }, - ], - }, - { - type: 'pie', - radius: ['50%', '70%'], - center: ['30%', '50%'], - avoidLabelOverlap: false, - padAngle: 3, - itemStyle: { - borderRadius: 0, - }, - minAngle: 5, - label: { - show: false, - }, - emphasis: { - label: { - show: false, - }, - }, - labelLine: { - show: false, - }, - data: [ - { value: chartData.Failed > 0 ? chartData.Failed : undefined, name: '失败' }, - { value: chartData.Succeeded > 0 ? chartData.Succeeded : undefined, name: '成功' }, - { value: chartData.Terminated > 0 ? chartData.Terminated : undefined, name: '中止' }, - { value: chartData.Pending > 0 ? chartData.Pending : undefined, name: '等待' }, - { value: chartData.Running > 0 ? chartData.Running : undefined, name: '运行中' }, - ], - }, - { - type: 'pie', - radius: '40%', - center: ['30%', '50%'], - avoidLabelOverlap: false, - label: { - show: false, - }, - tooltip: { - show: false, - }, - emphasis: { - label: { - show: false, - }, - disabled: true, - }, - animation: false, - labelLine: { - show: false, - }, - data: [ - { - value: 100, - itemStyle: { - color: circleBgColor, - borderColor: 'rgba(22, 100, 255, 0.08)', - borderWidth: 1, - }, - }, - ], - }, - ], - }; - - useEffect(() => { - // 创建一个echarts实例,返回echarts实例 - const chart = echarts.init(chartRef.current); - - // 设置图表实例的配置项和数据 - chart.setOption(options); - - // 组件卸载 - return () => { - // myChart.dispose() 销毁实例 - chart.dispose(); - }; - }, []); - - return ( -
- -
-
- ); -} - -export default StatusChart; From f4969b672e0fae91622ba6d836ba42ab5a8927cb Mon Sep 17 00:00:00 2001 From: cp3hnu Date: Mon, 2 Dec 2024 16:15:22 +0800 Subject: [PATCH 38/59] =?UTF-8?q?chore:=20=E4=BF=AE=E6=94=B9=E6=97=A5?= =?UTF-8?q?=E5=BF=97=E5=9B=BE=E6=A0=87?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- react-ui/src/iconfont/iconfont.js | 2 +- react-ui/src/pages/AutoML/Instance/index.tsx | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/react-ui/src/iconfont/iconfont.js b/react-ui/src/iconfont/iconfont.js index 65e19f39..7097beaf 100644 --- a/react-ui/src/iconfont/iconfont.js +++ b/react-ui/src/iconfont/iconfont.js @@ -1 +1 @@ -window._iconfont_svg_string_4511447='',(t=>{var a=(h=(h=document.getElementsByTagName("script"))[h.length-1]).getAttribute("data-injectcss"),h=h.getAttribute("data-disable-injectsvg");if(!h){var l,v,z,i,o,m=function(a,h){h.parentNode.insertBefore(a,h)};if(a&&!t.__iconfont__svg__cssinject__){t.__iconfont__svg__cssinject__=!0;try{document.write("")}catch(a){console&&console.log(a)}}l=function(){var a,h=document.createElement("div");h.innerHTML=t._iconfont_svg_string_4511447,(h=h.getElementsByTagName("svg")[0])&&(h.setAttribute("aria-hidden","true"),h.style.position="absolute",h.style.width=0,h.style.height=0,h.style.overflow="hidden",h=h,(a=document.body).firstChild?m(h,a.firstChild):a.appendChild(h))},document.addEventListener?~["complete","loaded","interactive"].indexOf(document.readyState)?setTimeout(l,0):(v=function(){document.removeEventListener("DOMContentLoaded",v,!1),l()},document.addEventListener("DOMContentLoaded",v,!1)):document.attachEvent&&(z=l,i=t.document,o=!1,d(),i.onreadystatechange=function(){"complete"==i.readyState&&(i.onreadystatechange=null,p())})}function p(){o||(o=!0,z())}function d(){try{i.documentElement.doScroll("left")}catch(a){return void setTimeout(d,50)}p()}})(window); \ No newline at end of file +window._iconfont_svg_string_4511447='',(t=>{var a=(h=(h=document.getElementsByTagName("script"))[h.length-1]).getAttribute("data-injectcss"),h=h.getAttribute("data-disable-injectsvg");if(!h){var l,z,v,i,o,m=function(a,h){h.parentNode.insertBefore(a,h)};if(a&&!t.__iconfont__svg__cssinject__){t.__iconfont__svg__cssinject__=!0;try{document.write("")}catch(a){console&&console.log(a)}}l=function(){var a,h=document.createElement("div");h.innerHTML=t._iconfont_svg_string_4511447,(h=h.getElementsByTagName("svg")[0])&&(h.setAttribute("aria-hidden","true"),h.style.position="absolute",h.style.width=0,h.style.height=0,h.style.overflow="hidden",h=h,(a=document.body).firstChild?m(h,a.firstChild):a.appendChild(h))},document.addEventListener?~["complete","loaded","interactive"].indexOf(document.readyState)?setTimeout(l,0):(z=function(){document.removeEventListener("DOMContentLoaded",z,!1),l()},document.addEventListener("DOMContentLoaded",z,!1)):document.attachEvent&&(v=l,i=t.document,o=!1,d(),i.onreadystatechange=function(){"complete"==i.readyState&&(i.onreadystatechange=null,p())})}function p(){o||(o=!0,v())}function d(){try{i.documentElement.doScroll("left")}catch(a){return void setTimeout(d,50)}p()}})(window); \ No newline at end of file diff --git a/react-ui/src/pages/AutoML/Instance/index.tsx b/react-ui/src/pages/AutoML/Instance/index.tsx index 376961bb..7b416de1 100644 --- a/react-ui/src/pages/AutoML/Instance/index.tsx +++ b/react-ui/src/pages/AutoML/Instance/index.tsx @@ -140,7 +140,7 @@ function AutoMLInstance() { { key: TabKeys.Log, label: '日志', - icon: , + icon: , children: (
{instanceInfo && instanceInfo.nodeStatus && ( @@ -169,7 +169,7 @@ function AutoMLInstance() { }, { key: TabKeys.History, - label: 'Trial列表', + label: 'Trial 列表', icon: , children: ( Date: Mon, 2 Dec 2024 16:39:29 +0800 Subject: [PATCH 39/59] =?UTF-8?q?fix:=20=E5=88=A0=E9=99=A4clipboard?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- package-lock.json | 45 -------------------------------------------- package.json | 5 ----- react-ui/src/app.tsx | 2 +- 3 files changed, 1 insertion(+), 51 deletions(-) delete mode 100644 package-lock.json delete mode 100644 package.json diff --git a/package-lock.json b/package-lock.json deleted file mode 100644 index d579dd5a..00000000 --- a/package-lock.json +++ /dev/null @@ -1,45 +0,0 @@ -{ - "name": "ci4sManagement-cloud", - "lockfileVersion": 3, - "requires": true, - "packages": { - "": { - "dependencies": { - "clipboard": "~2.0.11" - } - }, - "node_modules/clipboard": { - "version": "2.0.11", - "resolved": "https://registry.npmjs.org/clipboard/-/clipboard-2.0.11.tgz", - "integrity": "sha512-C+0bbOqkezLIsmWSvlsXS0Q0bmkugu7jcfMIACB+RDEntIzQIkdr148we28AfSloQLRdZlYL/QYyrq05j/3Faw==", - "dependencies": { - "good-listener": "^1.2.2", - "select": "^1.1.2", - "tiny-emitter": "^2.0.0" - } - }, - "node_modules/delegate": { - "version": "3.2.0", - "resolved": "https://registry.npmjs.org/delegate/-/delegate-3.2.0.tgz", - "integrity": "sha512-IofjkYBZaZivn0V8nnsMJGBr4jVLxHDheKSW88PyxS5QC4Vo9ZbZVvhzlSxY87fVq3STR6r+4cGepyHkcWOQSw==" - }, - "node_modules/good-listener": { - "version": "1.2.2", - "resolved": "https://registry.npmjs.org/good-listener/-/good-listener-1.2.2.tgz", - "integrity": "sha512-goW1b+d9q/HIwbVYZzZ6SsTr4IgE+WA44A0GmPIQstuOrgsFcT7VEJ48nmr9GaRtNu0XTKacFLGnBPAM6Afouw==", - "dependencies": { - "delegate": "^3.1.2" - } - }, - "node_modules/select": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/select/-/select-1.1.2.tgz", - "integrity": "sha512-OwpTSOfy6xSs1+pwcNrv0RBMOzI39Lp3qQKUTPVVPRjCdNa5JH/oPRiqsesIskK8TVgmRiHwO4KXlV2Li9dANA==" - }, - "node_modules/tiny-emitter": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/tiny-emitter/-/tiny-emitter-2.1.0.tgz", - "integrity": "sha512-NB6Dk1A9xgQPMoGqC5CVXn123gWyte215ONT5Pp5a0yt4nlEoO1ZWeCwpncaekPHXO60i47ihFnZPiRPjRMq4Q==" - } - } -} diff --git a/package.json b/package.json deleted file mode 100644 index 32560620..00000000 --- a/package.json +++ /dev/null @@ -1,5 +0,0 @@ -{ - "dependencies": { - "clipboard": "~2.0.11" - } -} diff --git a/react-ui/src/app.tsx b/react-ui/src/app.tsx index 2f757e15..d08ca129 100644 --- a/react-ui/src/app.tsx +++ b/react-ui/src/app.tsx @@ -20,7 +20,7 @@ import './styles/menu.less'; export { requestConfig as request } from './requestConfig'; // const isDev = process.env.NODE_ENV === 'development'; import { type GlobalInitialState } from '@/types'; -import '@/utils/clipboard'; +// import '@/utils/clipboard'; import { menuItemRender } from '@/utils/menuRender'; import ErrorBoundary from './components/ErrorBoundary'; import { needAuth } from './utils'; From cbd6d916531dd3d35f77385e78690b8ce90adffa Mon Sep 17 00:00:00 2001 From: cp3hnu Date: Tue, 3 Dec 2024 09:12:33 +0800 Subject: [PATCH 40/59] =?UTF-8?q?fix:=20=E8=87=AA=E5=8A=A8=E6=9C=BA?= =?UTF-8?q?=E5=99=A8=E5=AD=A6=E4=B9=A0=E5=AE=8C=E6=88=90=E6=97=B6=EF=BC=8C?= =?UTF-8?q?=E8=8E=B7=E5=8F=96=E8=AF=A6=E6=83=85=E5=8F=88=E8=BF=94=E5=9B=9E?= =?UTF-8?q?Running=E7=9A=84=E9=97=AE=E9=A2=98?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- react-ui/src/components/InfoGroup/index.less | 14 ++++++++ react-ui/src/components/InfoGroup/index.tsx | 28 ++++++++++++++++ .../InfoGroupTitle}/index.less | 4 +-- .../src/components/InfoGroupTitle/index.tsx | 25 +++++++++++++++ react-ui/src/pages/AutoML/Instance/index.tsx | 14 ++++++-- .../AutoML/components/AutoMLBasic/index.tsx | 5 +-- .../AutoML/components/ConfigInfo/index.less | 22 +------------ .../AutoML/components/ConfigInfo/index.tsx | 32 +++---------------- .../AutoML/components/ConfigTitle/index.tsx | 22 ------------- .../components/ExperimentResult/index.less | 10 ++---- .../components/ExperimentResult/index.tsx | 32 ++++++++++--------- 11 files changed, 106 insertions(+), 102 deletions(-) create mode 100644 react-ui/src/components/InfoGroup/index.less create mode 100644 react-ui/src/components/InfoGroup/index.tsx rename react-ui/src/{pages/AutoML/components/ConfigTitle => components/InfoGroupTitle}/index.less (95%) create mode 100644 react-ui/src/components/InfoGroupTitle/index.tsx delete mode 100644 react-ui/src/pages/AutoML/components/ConfigTitle/index.tsx diff --git a/react-ui/src/components/InfoGroup/index.less b/react-ui/src/components/InfoGroup/index.less new file mode 100644 index 00000000..cf05c8b2 --- /dev/null +++ b/react-ui/src/components/InfoGroup/index.less @@ -0,0 +1,14 @@ +.kf-info-group { + width: 100%; + + &__content { + padding: 20px @content-padding; + background-color: white; + border: 1px solid @border-color-base; + border-radius: 0 0 4px 4px; + + &&--scroll { + padding: 0; + } + } +} diff --git a/react-ui/src/components/InfoGroup/index.tsx b/react-ui/src/components/InfoGroup/index.tsx new file mode 100644 index 00000000..4e3359c0 --- /dev/null +++ b/react-ui/src/components/InfoGroup/index.tsx @@ -0,0 +1,28 @@ +import classNames from 'classnames'; +import InfoGroupTitle from '../InfoGroupTitle'; +import './index.less'; + +type InfoGroupProps = { + title: string; + contentScroll?: boolean; // 内容是否需要滚动,如果可以滚动,则取消 padding + className?: string; + style?: React.CSSProperties; + children?: React.ReactNode; +}; + +function InfoGroup({ title, contentScroll = false, className, style, children }: InfoGroupProps) { + return ( +
+ +
+ {children} +
+
+ ); +} + +export default InfoGroup; diff --git a/react-ui/src/pages/AutoML/components/ConfigTitle/index.less b/react-ui/src/components/InfoGroupTitle/index.less similarity index 95% rename from react-ui/src/pages/AutoML/components/ConfigTitle/index.less rename to react-ui/src/components/InfoGroupTitle/index.less index 5b894c43..ee3c9011 100644 --- a/react-ui/src/pages/AutoML/components/ConfigTitle/index.less +++ b/react-ui/src/components/InfoGroupTitle/index.less @@ -1,4 +1,4 @@ -.config-title { +.kf-info-group-title { width: 100%; height: 56px; padding-left: @content-padding; @@ -10,7 +10,7 @@ border: 1px solid #e8effb; border-radius: 4px 4px 0 0; - &__img { + &__image { width: 16px; height: 16px; margin-right: 10px; diff --git a/react-ui/src/components/InfoGroupTitle/index.tsx b/react-ui/src/components/InfoGroupTitle/index.tsx new file mode 100644 index 00000000..7eec2ab8 --- /dev/null +++ b/react-ui/src/components/InfoGroupTitle/index.tsx @@ -0,0 +1,25 @@ +import { Flex } from 'antd'; +import classNames from 'classnames'; +import './index.less'; + +type InfoGroupTitleProps = { + title: string; + className?: string; + style?: React.CSSProperties; +}; + +function InfoGroupTitle({ title, style, className }: InfoGroupTitleProps) { + return ( + + + {title} + + ); +} + +export default InfoGroupTitle; diff --git a/react-ui/src/pages/AutoML/Instance/index.tsx b/react-ui/src/pages/AutoML/Instance/index.tsx index 7b416de1..b146b57f 100644 --- a/react-ui/src/pages/AutoML/Instance/index.tsx +++ b/react-ui/src/pages/AutoML/Instance/index.tsx @@ -33,7 +33,7 @@ function AutoMLInstance() { useEffect(() => { if (instanceId) { - getExperimentInsInfo(); + getExperimentInsInfo(false); } return () => { closeSSE(); @@ -41,7 +41,7 @@ function AutoMLInstance() { }, []); // 获取实验实例详情 - const getExperimentInsInfo = async () => { + const getExperimentInsInfo = async (isStatusDetermined: boolean) => { const [res] = await to(getExperimentInsReq(instanceId)); if (res && res.data) { const info = res.data as AutoMLInstanceData; @@ -51,6 +51,14 @@ function AutoMLInstance() { if (paramJson) { setAutoMLInfo(paramJson); } + + // 这个接口返回的状态有延时,SSE 返回的状态是最新的 + // SSE 调用时,不需要解析 node_status, 也不要重新建立 SSE + if (!isStatusDetermined) { + setInstanceInfo(info); + return; + } + // 进行节点状态 const nodeStatusJson = parseJsonText(node_status); if (nodeStatusJson) { @@ -103,7 +111,7 @@ function AutoMLInstance() { statusData.phase !== ExperimentStatus.Running ) { closeSSE(); - getExperimentInsInfo(); + getExperimentInsInfo(true); } } } diff --git a/react-ui/src/pages/AutoML/components/AutoMLBasic/index.tsx b/react-ui/src/pages/AutoML/components/AutoMLBasic/index.tsx index b6f214ae..4b74db21 100644 --- a/react-ui/src/pages/AutoML/components/AutoMLBasic/index.tsx +++ b/react-ui/src/pages/AutoML/components/AutoMLBasic/index.tsx @@ -283,7 +283,6 @@ function AutoMLBasic({ info, className, runStatus, isInstance = false }: AutoMLB title="运行信息" data={instanceDatas} labelWidth={70} - threeColumn style={{ marginBottom: '20px' }} /> ) : ( @@ -291,7 +290,6 @@ function AutoMLBasic({ info, className, runStatus, isInstance = false }: AutoMLB title="基本信息" data={basicDatas} labelWidth={70} - threeColumn style={{ marginBottom: '20px' }} /> )} @@ -299,10 +297,9 @@ function AutoMLBasic({ info, className, runStatus, isInstance = false }: AutoMLB title="配置信息" data={configDatas} labelWidth={150} - threeColumn style={{ marginBottom: '20px' }} /> - +
); } diff --git a/react-ui/src/pages/AutoML/components/ConfigInfo/index.less b/react-ui/src/pages/AutoML/components/ConfigInfo/index.less index 7722651f..33fb3314 100644 --- a/react-ui/src/pages/AutoML/components/ConfigInfo/index.less +++ b/react-ui/src/pages/AutoML/components/ConfigInfo/index.less @@ -1,20 +1,10 @@ .config-info { - flex: 1; - min-width: 0; - - &__content { - padding: 20px; - padding: 20px @content-padding; - background-color: white; - border: 1px solid @border-color-base; - border-radius: 0 0 4px 4px; - } - :global { .kf-basic-info { width: 100%; &__item { + width: calc((100% - 80px) / 3); &__label { font-size: @font-size; text-align: left; @@ -27,14 +17,4 @@ } } } - - &--three-column { - :global { - .kf-basic-info { - &__item { - width: calc((100% - 80px) / 3); - } - } - } - } } diff --git a/react-ui/src/pages/AutoML/components/ConfigInfo/index.tsx b/react-ui/src/pages/AutoML/components/ConfigInfo/index.tsx index 2fbb6825..10e042e4 100644 --- a/react-ui/src/pages/AutoML/components/ConfigInfo/index.tsx +++ b/react-ui/src/pages/AutoML/components/ConfigInfo/index.tsx @@ -1,7 +1,6 @@ import BasicInfo, { type BasicInfoData } from '@/components/BasicInfo'; +import InfoGroup from '@/components/InfoGroup'; import classNames from 'classnames'; -import { useEffect } from 'react'; -import ConfigTitle from '../ConfigTitle'; import styles from './index.less'; export * from '@/components/BasicInfo/format'; export type { BasicInfoData }; @@ -9,39 +8,18 @@ export type { BasicInfoData }; type ConfigInfoProps = { title: string; data: BasicInfoData[]; + labelWidth: number; className?: string; style?: React.CSSProperties; - children?: React.ReactNode; - labelWidth: number; - threeColumn?: boolean; }; -function ConfigInfo({ - title, - data, - className, - style, - children, - labelWidth, - threeColumn = false, -}: ConfigInfoProps) { - useEffect(() => {}, []); - +function ConfigInfo({ title, data, labelWidth, className, style }: ConfigInfoProps) { return ( -
- +
- {children}
-
+ ); } diff --git a/react-ui/src/pages/AutoML/components/ConfigTitle/index.tsx b/react-ui/src/pages/AutoML/components/ConfigTitle/index.tsx deleted file mode 100644 index 546eca88..00000000 --- a/react-ui/src/pages/AutoML/components/ConfigTitle/index.tsx +++ /dev/null @@ -1,22 +0,0 @@ -import { Flex } from 'antd'; -import styles from './index.less'; - -type ConfigTitleProps = { - title: string; -}; - -function ConfigTitle({ title }: ConfigTitleProps) { - return ( - - - {title} - - ); -} - -export default ConfigTitle; diff --git a/react-ui/src/pages/AutoML/components/ExperimentResult/index.less b/react-ui/src/pages/AutoML/components/ExperimentResult/index.less index 6d88dea3..0257451b 100644 --- a/react-ui/src/pages/AutoML/components/ExperimentResult/index.less +++ b/react-ui/src/pages/AutoML/components/ExperimentResult/index.less @@ -9,24 +9,18 @@ &__text { width: 100%; height: 460px; - margin-bottom: 16px; padding: 20px @content-padding; overflow: auto; white-space: pre-wrap; - border: 1px solid @border-color-base; - border-radius: 0 0 4px 4px; } - &__image-container { + &__images { display: flex; align-items: flex-start; width: 100%; - padding: 20px @content-padding; overflow-x: auto; - border: 1px solid @border-color-base; - border-radius: 0 0 4px 4px; - &__image { + &__item { height: 248px; margin-right: 20px; border: 1px solid rgba(96, 107, 122, 0.3); diff --git a/react-ui/src/pages/AutoML/components/ExperimentResult/index.tsx b/react-ui/src/pages/AutoML/components/ExperimentResult/index.tsx index 933f6fbd..fce2e5fb 100644 --- a/react-ui/src/pages/AutoML/components/ExperimentResult/index.tsx +++ b/react-ui/src/pages/AutoML/components/ExperimentResult/index.tsx @@ -1,7 +1,7 @@ +import InfoGroup from '@/components/InfoGroup'; import { getFileReq } from '@/services/file'; import { to } from '@/utils/promise'; import { useEffect, useMemo, useState } from 'react'; -import ConfigTitle from '../ConfigTitle'; import styles from './index.less'; type ExperimentResultProps = { @@ -35,20 +35,22 @@ function ExperimentResult({ fileUrl, imageUrl }: ExperimentResultProps) { return (
- -
{result}
- -
- {images.map((item, index) => ( - - ))} -
+ +
{result}
+
+ +
+ {images.map((item, index) => ( + + ))} +
+
); } From f33aafd786c5306d9bf0879bb194ee4a05293926 Mon Sep 17 00:00:00 2001 From: cp3hnu Date: Tue, 3 Dec 2024 09:21:10 +0800 Subject: [PATCH 41/59] =?UTF-8?q?fix:=20=E8=87=AA=E5=8A=A8=E6=9C=BA?= =?UTF-8?q?=E5=99=A8=E5=AD=A6=E4=B9=A0=E5=AE=8C=E6=88=90=E6=97=B6=EF=BC=8C?= =?UTF-8?q?=E8=8E=B7=E5=8F=96=E8=AF=A6=E6=83=85=E5=8F=88=E8=BF=94=E5=9B=9E?= =?UTF-8?q?Running=E7=9A=84=E9=97=AE=E9=A2=98?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- react-ui/src/pages/AutoML/Instance/index.tsx | 2 +- react-ui/src/pages/AutoML/components/AutoMLBasic/index.tsx | 5 +++-- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/react-ui/src/pages/AutoML/Instance/index.tsx b/react-ui/src/pages/AutoML/Instance/index.tsx index b146b57f..fd64d079 100644 --- a/react-ui/src/pages/AutoML/Instance/index.tsx +++ b/react-ui/src/pages/AutoML/Instance/index.tsx @@ -54,7 +54,7 @@ function AutoMLInstance() { // 这个接口返回的状态有延时,SSE 返回的状态是最新的 // SSE 调用时,不需要解析 node_status, 也不要重新建立 SSE - if (!isStatusDetermined) { + if (isStatusDetermined) { setInstanceInfo(info); return; } diff --git a/react-ui/src/pages/AutoML/components/AutoMLBasic/index.tsx b/react-ui/src/pages/AutoML/components/AutoMLBasic/index.tsx index 4b74db21..854c6035 100644 --- a/react-ui/src/pages/AutoML/components/AutoMLBasic/index.tsx +++ b/react-ui/src/pages/AutoML/components/AutoMLBasic/index.tsx @@ -278,14 +278,15 @@ function AutoMLBasic({ info, className, runStatus, isInstance = false }: AutoMLB return (
- {isInstance && runStatus ? ( + {isInstance && runStatus && ( - ) : ( + )} + {!isInstance && ( Date: Tue, 3 Dec 2024 09:51:22 +0800 Subject: [PATCH 42/59] =?UTF-8?q?=E8=87=AA=E5=8A=A8=E6=9C=BA=E5=99=A8?= =?UTF-8?q?=E5=AD=A6=E4=B9=A0=E5=BC=80=E5=8F=91?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../com/ruoyi/platform/service/impl/AutoMlInsServiceImpl.java | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/ruoyi-modules/management-platform/src/main/java/com/ruoyi/platform/service/impl/AutoMlInsServiceImpl.java b/ruoyi-modules/management-platform/src/main/java/com/ruoyi/platform/service/impl/AutoMlInsServiceImpl.java index 4fee6d4b..f1fb6b41 100644 --- a/ruoyi-modules/management-platform/src/main/java/com/ruoyi/platform/service/impl/AutoMlInsServiceImpl.java +++ b/ruoyi-modules/management-platform/src/main/java/com/ruoyi/platform/service/impl/AutoMlInsServiceImpl.java @@ -230,7 +230,8 @@ public class AutoMlInsServiceImpl implements AutoMlInsService { @Override public AutoMlIns getDetailById(Long id) { - return this.autoMlInsDao.queryById(id); + AutoMlIns autoMlIns = this.autoMlInsDao.queryById(id); + return queryStatusFromArgo(autoMlIns); } public void updateAutoMlStatus(Long autoMlId) { From 359e4a994b42d3fc988ce1226bbca8bc621ed003 Mon Sep 17 00:00:00 2001 From: chenzhihang <709011834@qq.com> Date: Tue, 3 Dec 2024 10:11:06 +0800 Subject: [PATCH 43/59] =?UTF-8?q?=E8=87=AA=E5=8A=A8=E6=9C=BA=E5=99=A8?= =?UTF-8?q?=E5=AD=A6=E4=B9=A0=E5=BC=80=E5=8F=91?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../ruoyi/platform/service/impl/AutoMlInsServiceImpl.java | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/ruoyi-modules/management-platform/src/main/java/com/ruoyi/platform/service/impl/AutoMlInsServiceImpl.java b/ruoyi-modules/management-platform/src/main/java/com/ruoyi/platform/service/impl/AutoMlInsServiceImpl.java index f1fb6b41..dd4c2f07 100644 --- a/ruoyi-modules/management-platform/src/main/java/com/ruoyi/platform/service/impl/AutoMlInsServiceImpl.java +++ b/ruoyi-modules/management-platform/src/main/java/com/ruoyi/platform/service/impl/AutoMlInsServiceImpl.java @@ -231,7 +231,10 @@ public class AutoMlInsServiceImpl implements AutoMlInsService { @Override public AutoMlIns getDetailById(Long id) { AutoMlIns autoMlIns = this.autoMlInsDao.queryById(id); - return queryStatusFromArgo(autoMlIns); + if(Constant.Running.equals(autoMlIns.getStatus()) || Constant.Pending.equals(autoMlIns.getStatus())){ + autoMlIns = queryStatusFromArgo(autoMlIns); + } + return autoMlIns; } public void updateAutoMlStatus(Long autoMlId) { From bde0766e4196d5db2f85fdd0c6d8c77e53247956 Mon Sep 17 00:00:00 2001 From: chenzhihang <709011834@qq.com> Date: Tue, 3 Dec 2024 11:01:38 +0800 Subject: [PATCH 44/59] =?UTF-8?q?=E4=BF=AE=E6=94=B9tensorboard=20bug?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../ruoyi/platform/service/impl/TensorBoardServiceImpl.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/ruoyi-modules/management-platform/src/main/java/com/ruoyi/platform/service/impl/TensorBoardServiceImpl.java b/ruoyi-modules/management-platform/src/main/java/com/ruoyi/platform/service/impl/TensorBoardServiceImpl.java index 83b87d82..2931ebd4 100644 --- a/ruoyi-modules/management-platform/src/main/java/com/ruoyi/platform/service/impl/TensorBoardServiceImpl.java +++ b/ruoyi-modules/management-platform/src/main/java/com/ruoyi/platform/service/impl/TensorBoardServiceImpl.java @@ -38,7 +38,7 @@ public class TensorBoardServiceImpl implements TensorBoardService { return tensorboardStatusVo; } LoginUser loginUser = SecurityUtils.getLoginUser(); - String podName = loginUser.getUsername().toLowerCase() + "-" + frameLogPathVo.getPath().split("/")[2] + "-tensorboard-pod"; + String podName = loginUser.getUsername().toLowerCase() + "-" + frameLogPathVo.getPath().split("/")[1] + "-tensorboard-pod"; try { String podStatus = k8sClientUtil.getPodStatus(podName, StringUtils.isEmpty(frameLogPathVo.getNamespace()) ? "default" : frameLogPathVo.getNamespace()); @@ -66,7 +66,7 @@ public class TensorBoardServiceImpl implements TensorBoardService { throw new Exception("存储路径为空"); } LoginUser loginUser = SecurityUtils.getLoginUser(); - String podName = loginUser.getUsername().toLowerCase()+"-"+frameLogPathVo.getPath().split("/")[2]+ "-tensorboard-pod"; + String podName = loginUser.getUsername().toLowerCase()+"-"+frameLogPathVo.getPath().split("/")[1]+ "-tensorboard-pod"; // Integer podPort = k8sClientUtil.createPodWithSubPath(podName, StringUtils.isEmpty(frameLogPathVo.getNamespace()) ? "default" : frameLogPathVo.getNamespace(), port, mountPath, frameLogPathVo.getPath(), frameLogPathVo.getPvcName(), image); Integer podPort = k8sClientUtil.createPodWithSubPath(podName, StringUtils.isEmpty(frameLogPathVo.getNamespace()) ? "default" : frameLogPathVo.getNamespace(), port, mountPath, frameLogPathVo.getPath(), image); From e7240031ffc83c1702e44da9d717c42573ade815 Mon Sep 17 00:00:00 2001 From: cp3hnu Date: Tue, 3 Dec 2024 14:22:40 +0800 Subject: [PATCH 45/59] =?UTF-8?q?feat:=20=E8=87=AA=E5=8A=A8=E6=9C=BA?= =?UTF-8?q?=E5=99=A8=E5=AD=A6=E4=B9=A0=E5=AE=9E=E9=AA=8C=E7=BB=93=E6=9E=9C?= =?UTF-8?q?=E6=B7=BB=E5=8A=A0=E6=A8=A1=E5=9E=8B=E4=B8=8B=E8=BD=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- react-ui/src/iconfont/iconfont.js | 2 +- react-ui/src/pages/AutoML/Instance/index.tsx | 13 +++-- .../components/ExperimentResult/index.less | 33 ++++++++++--- .../components/ExperimentResult/index.tsx | 49 +++++++++++++++---- .../Experiment/components/LogGroup/index.tsx | 4 +- 5 files changed, 79 insertions(+), 22 deletions(-) diff --git a/react-ui/src/iconfont/iconfont.js b/react-ui/src/iconfont/iconfont.js index 7097beaf..d7576e9c 100644 --- a/react-ui/src/iconfont/iconfont.js +++ b/react-ui/src/iconfont/iconfont.js @@ -1 +1 @@ -window._iconfont_svg_string_4511447='',(t=>{var a=(h=(h=document.getElementsByTagName("script"))[h.length-1]).getAttribute("data-injectcss"),h=h.getAttribute("data-disable-injectsvg");if(!h){var l,z,v,i,o,m=function(a,h){h.parentNode.insertBefore(a,h)};if(a&&!t.__iconfont__svg__cssinject__){t.__iconfont__svg__cssinject__=!0;try{document.write("")}catch(a){console&&console.log(a)}}l=function(){var a,h=document.createElement("div");h.innerHTML=t._iconfont_svg_string_4511447,(h=h.getElementsByTagName("svg")[0])&&(h.setAttribute("aria-hidden","true"),h.style.position="absolute",h.style.width=0,h.style.height=0,h.style.overflow="hidden",h=h,(a=document.body).firstChild?m(h,a.firstChild):a.appendChild(h))},document.addEventListener?~["complete","loaded","interactive"].indexOf(document.readyState)?setTimeout(l,0):(z=function(){document.removeEventListener("DOMContentLoaded",z,!1),l()},document.addEventListener("DOMContentLoaded",z,!1)):document.attachEvent&&(v=l,i=t.document,o=!1,d(),i.onreadystatechange=function(){"complete"==i.readyState&&(i.onreadystatechange=null,p())})}function p(){o||(o=!0,v())}function d(){try{i.documentElement.doScroll("left")}catch(a){return void setTimeout(d,50)}p()}})(window); \ No newline at end of file +window._iconfont_svg_string_4511447='',(t=>{var a=(h=(h=document.getElementsByTagName("script"))[h.length-1]).getAttribute("data-injectcss"),h=h.getAttribute("data-disable-injectsvg");if(!h){var l,z,v,i,o,m=function(a,h){h.parentNode.insertBefore(a,h)};if(a&&!t.__iconfont__svg__cssinject__){t.__iconfont__svg__cssinject__=!0;try{document.write("")}catch(a){console&&console.log(a)}}l=function(){var a,h=document.createElement("div");h.innerHTML=t._iconfont_svg_string_4511447,(h=h.getElementsByTagName("svg")[0])&&(h.setAttribute("aria-hidden","true"),h.style.position="absolute",h.style.width=0,h.style.height=0,h.style.overflow="hidden",h=h,(a=document.body).firstChild?m(h,a.firstChild):a.appendChild(h))},document.addEventListener?~["complete","loaded","interactive"].indexOf(document.readyState)?setTimeout(l,0):(z=function(){document.removeEventListener("DOMContentLoaded",z,!1),l()},document.addEventListener("DOMContentLoaded",z,!1)):document.attachEvent&&(v=l,i=t.document,o=!1,d(),i.onreadystatechange=function(){"complete"==i.readyState&&(i.onreadystatechange=null,p())})}function p(){o||(o=!0,v())}function d(){try{i.documentElement.doScroll("left")}catch(a){return void setTimeout(d,50)}p()}})(window); \ No newline at end of file diff --git a/react-ui/src/pages/AutoML/Instance/index.tsx b/react-ui/src/pages/AutoML/Instance/index.tsx index fd64d079..947ac63d 100644 --- a/react-ui/src/pages/AutoML/Instance/index.tsx +++ b/react-ui/src/pages/AutoML/Instance/index.tsx @@ -55,7 +55,10 @@ function AutoMLInstance() { // 这个接口返回的状态有延时,SSE 返回的状态是最新的 // SSE 调用时,不需要解析 node_status, 也不要重新建立 SSE if (isStatusDetermined) { - setInstanceInfo(info); + setInstanceInfo((prev) => ({ + ...info, + nodeStatus: prev!.nodeStatus, + })); return; } @@ -101,7 +104,7 @@ function AutoMLInstance() { ) as NodeStatus; if (statusData) { setInstanceInfo((prev) => ({ - ...(prev as AutoMLInstanceData), + ...prev!, nodeStatus: statusData, })); @@ -172,7 +175,11 @@ function AutoMLInstance() { label: '实验结果', icon: , children: ( - + ), }, { diff --git a/react-ui/src/pages/AutoML/components/ExperimentResult/index.less b/react-ui/src/pages/AutoML/components/ExperimentResult/index.less index 0257451b..c36da152 100644 --- a/react-ui/src/pages/AutoML/components/ExperimentResult/index.less +++ b/react-ui/src/pages/AutoML/components/ExperimentResult/index.less @@ -6,9 +6,25 @@ background-color: white; border-radius: 10px; + &__download { + display: flex; + align-items: center; + height: 34px; + margin-bottom: 16px; + padding-left: @content-padding; + color: @text-color; + font-size: 13px; + background-color: #f8f8f9; + border-radius: 4px; + + &__btn { + margin-left: 22px; + } + } + &__text { width: 100%; - height: 460px; + height: 420px; padding: 20px @content-padding; overflow: auto; white-space: pre-wrap; @@ -20,14 +36,19 @@ width: 100%; overflow-x: auto; + :global { + .ant-image { + margin-right: 20px; + + &:last-child { + margin-right: 0; + } + } + } + &__item { height: 248px; - margin-right: 20px; border: 1px solid rgba(96, 107, 122, 0.3); - - &:last-child { - margin-right: 0; - } } } } diff --git a/react-ui/src/pages/AutoML/components/ExperimentResult/index.tsx b/react-ui/src/pages/AutoML/components/ExperimentResult/index.tsx index fce2e5fb..c5522834 100644 --- a/react-ui/src/pages/AutoML/components/ExperimentResult/index.tsx +++ b/react-ui/src/pages/AutoML/components/ExperimentResult/index.tsx @@ -1,15 +1,18 @@ import InfoGroup from '@/components/InfoGroup'; +import KFIcon from '@/components/KFIcon'; import { getFileReq } from '@/services/file'; import { to } from '@/utils/promise'; +import { Button, Image } from 'antd'; import { useEffect, useMemo, useState } from 'react'; import styles from './index.less'; type ExperimentResultProps = { fileUrl?: string; imageUrl?: string; + modelPath?: string; }; -function ExperimentResult({ fileUrl, imageUrl }: ExperimentResultProps) { +function ExperimentResult({ fileUrl, imageUrl, modelPath }: ExperimentResultProps) { const [result, setResult] = useState(''); const images = useMemo(() => { @@ -35,20 +38,46 @@ function ExperimentResult({ fileUrl, imageUrl }: ExperimentResultProps) { return (
+ {modelPath && ( +
+ 文件名 + save_model.joblib + +
+ )}
{result}
- {images.map((item, index) => ( - - ))} + + console.log(`current index: ${current}, prev index: ${prev}`), + }} + > + {images.map((item) => ( + + ))} +
diff --git a/react-ui/src/pages/Experiment/components/LogGroup/index.tsx b/react-ui/src/pages/Experiment/components/LogGroup/index.tsx index 75d914f5..1b448930 100644 --- a/react-ui/src/pages/Experiment/components/LogGroup/index.tsx +++ b/react-ui/src/pages/Experiment/components/LogGroup/index.tsx @@ -52,7 +52,7 @@ function LogGroup({ const [_isMouseDown, setIsMouseDown, isMouseDownRef] = useStateRef(false); const preStatusRef = useRef(undefined); const socketRef = useRef(undefined); - const retryRef = useRef(2); // 等待 2 秒,重试 2 次 + const retryRef = useRef(2); // 等待 2 秒,重试 3 次 useEffect(() => { scrollToBottom(false); @@ -147,7 +147,7 @@ function LogGroup({ socket.addEventListener('close', (event) => { console.log('WebSocket is closed:', event); - // 有时候会出现连接失败,重试 2 次 + // 有时候会出现连接失败,重试 3 次 if (event.code !== 1000 && retryRef.current > 0) { retryRef.current -= 1; setTimeout(() => { From 8a8d2f6dfbe430838efcbe64f3d4f771675b8b08 Mon Sep 17 00:00:00 2001 From: cp3hnu Date: Tue, 3 Dec 2024 14:36:59 +0800 Subject: [PATCH 46/59] =?UTF-8?q?chore:=20=E5=88=A0=E9=99=A4=20moment=20?= =?UTF-8?q?=E4=BE=9D=E8=B5=96?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- react-ui/mock/listTableList.ts | 176 --------------------------------- react-ui/package.json | 1 - 2 files changed, 177 deletions(-) delete mode 100644 react-ui/mock/listTableList.ts diff --git a/react-ui/mock/listTableList.ts b/react-ui/mock/listTableList.ts deleted file mode 100644 index 35ec3cec..00000000 --- a/react-ui/mock/listTableList.ts +++ /dev/null @@ -1,176 +0,0 @@ -import { Request, Response } from 'express'; -import moment from 'moment'; -import { parse } from 'url'; - -// mock tableListDataSource -const genList = (current: number, pageSize: number) => { - const tableListDataSource: API.RuleListItem[] = []; - - for (let i = 0; i < pageSize; i += 1) { - const index = (current - 1) * 10 + i; - tableListDataSource.push({ - key: index, - disabled: i % 6 === 0, - href: 'https://ant.design', - avatar: [ - 'https://gw.alipayobjects.com/zos/rmsportal/eeHMaZBwmTvLdIwMfBpg.png', - 'https://gw.alipayobjects.com/zos/rmsportal/udxAbMEhpwthVVcjLXik.png', - ][i % 2], - name: `TradeCode ${index}`, - owner: '曲丽丽', - desc: '这是一段描述', - callNo: Math.floor(Math.random() * 1000), - status: Math.floor(Math.random() * 10) % 4, - updatedAt: moment().format('YYYY-MM-DD'), - createdAt: moment().format('YYYY-MM-DD'), - progress: Math.ceil(Math.random() * 100), - }); - } - tableListDataSource.reverse(); - return tableListDataSource; -}; - -let tableListDataSource = genList(1, 100); - -function getRule(req: Request, res: Response, u: string) { - let realUrl = u; - if (!realUrl || Object.prototype.toString.call(realUrl) !== '[object String]') { - realUrl = req.url; - } - const { current = 1, pageSize = 10 } = req.query; - const params = parse(realUrl, true).query as unknown as API.PageParams & - API.RuleListItem & { - sorter: any; - filter: any; - }; - - let dataSource = [...tableListDataSource].slice( - ((current as number) - 1) * (pageSize as number), - (current as number) * (pageSize as number), - ); - if (params.sorter) { - const sorter = JSON.parse(params.sorter); - dataSource = dataSource.sort((prev, next) => { - let sortNumber = 0; - (Object.keys(sorter) as Array).forEach((key) => { - let nextSort = next?.[key] as number; - let preSort = prev?.[key] as number; - if (sorter[key] === 'descend') { - if (preSort - nextSort > 0) { - sortNumber += -1; - } else { - sortNumber += 1; - } - return; - } - if (preSort - nextSort > 0) { - sortNumber += 1; - } else { - sortNumber += -1; - } - }); - return sortNumber; - }); - } - if (params.filter) { - const filter = JSON.parse(params.filter as any) as { - [key: string]: string[]; - }; - if (Object.keys(filter).length > 0) { - dataSource = dataSource.filter((item) => { - return (Object.keys(filter) as Array).some((key) => { - if (!filter[key]) { - return true; - } - if (filter[key].includes(`${item[key]}`)) { - return true; - } - return false; - }); - }); - } - } - - if (params.name) { - dataSource = dataSource.filter((data) => data?.name?.includes(params.name || '')); - } - const result = { - data: dataSource, - total: tableListDataSource.length, - success: true, - pageSize, - current: parseInt(`${params.current}`, 10) || 1, - }; - - return res.json(result); -} - -function postRule(req: Request, res: Response, u: string, b: Request) { - let realUrl = u; - if (!realUrl || Object.prototype.toString.call(realUrl) !== '[object String]') { - realUrl = req.url; - } - - const body = (b && b.body) || req.body; - const { method, name, desc, key } = body; - - switch (method) { - /* eslint no-case-declarations:0 */ - case 'delete': - tableListDataSource = tableListDataSource.filter((item) => key.indexOf(item.key) === -1); - break; - case 'post': - (() => { - const i = Math.ceil(Math.random() * 10000); - const newRule: API.RuleListItem = { - key: tableListDataSource.length, - href: 'https://ant.design', - avatar: [ - 'https://gw.alipayobjects.com/zos/rmsportal/eeHMaZBwmTvLdIwMfBpg.png', - 'https://gw.alipayobjects.com/zos/rmsportal/udxAbMEhpwthVVcjLXik.png', - ][i % 2], - name, - owner: '曲丽丽', - desc, - callNo: Math.floor(Math.random() * 1000), - status: Math.floor(Math.random() * 10) % 2, - updatedAt: moment().format('YYYY-MM-DD'), - createdAt: moment().format('YYYY-MM-DD'), - progress: Math.ceil(Math.random() * 100), - }; - tableListDataSource.unshift(newRule); - return res.json(newRule); - })(); - return; - - case 'update': - (() => { - let newRule = {}; - tableListDataSource = tableListDataSource.map((item) => { - if (item.key === key) { - newRule = { ...item, desc, name }; - return { ...item, desc, name }; - } - return item; - }); - return res.json(newRule); - })(); - return; - default: - break; - } - - const result = { - list: tableListDataSource, - pagination: { - total: tableListDataSource.length, - }, - }; - - res.json(result); -} - -export default { - 'GET /api/rule': getRule, - 'POST /api/rule': postRule, -}; diff --git a/react-ui/package.json b/react-ui/package.json index aebf21ca..dc5be1c5 100644 --- a/react-ui/package.json +++ b/react-ui/package.json @@ -67,7 +67,6 @@ "fabric": "^5.3.0", "highlight.js": "^11.7.0", "lodash": "^4.17.21", - "moment": "^2.30.1", "omit.js": "^2.0.2", "pnpm": "^8.9.0", "query-string": "^8.1.0", From 76248917c327c10d12a7b73e5da89b5f6ccf9e9f Mon Sep 17 00:00:00 2001 From: cp3hnu Date: Tue, 3 Dec 2024 15:30:31 +0800 Subject: [PATCH 47/59] =?UTF-8?q?style:=20=E4=BF=AE=E6=94=B9=E8=87=AA?= =?UTF-8?q?=E5=8A=A8=E6=9C=BA=E5=99=A8=E5=AD=A6=E4=B9=A0=E5=AE=9E=E9=AA=8C?= =?UTF-8?q?=E7=BB=93=E6=9E=9C=E6=A8=A1=E5=9E=8B=E4=B8=8B=E8=BD=BD=E6=A0=B7?= =?UTF-8?q?=E5=BC=8F?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../components/ExperimentResult/index.less | 12 ++++--- .../components/ExperimentResult/index.tsx | 36 +++++++++---------- .../components/ExperimentDrawer/index.less | 6 ++++ .../components/ExperimentDrawer/index.tsx | 5 +-- .../components/GlobalParamsDrawer/index.tsx | 2 +- .../components/PipelineNodeDrawer/index.tsx | 2 +- 6 files changed, 34 insertions(+), 29 deletions(-) diff --git a/react-ui/src/pages/AutoML/components/ExperimentResult/index.less b/react-ui/src/pages/AutoML/components/ExperimentResult/index.less index c36da152..a263564d 100644 --- a/react-ui/src/pages/AutoML/components/ExperimentResult/index.less +++ b/react-ui/src/pages/AutoML/components/ExperimentResult/index.less @@ -7,10 +7,9 @@ border-radius: 10px; &__download { - display: flex; - align-items: center; - height: 34px; - margin-bottom: 16px; + padding-top: 16px; + padding-bottom: 16px; + padding-left: @content-padding; color: @text-color; font-size: 13px; @@ -18,7 +17,10 @@ border-radius: 4px; &__btn { - margin-left: 22px; + display: block; + height: 36px; + margin-top: 15px; + font-size: 14px; } } diff --git a/react-ui/src/pages/AutoML/components/ExperimentResult/index.tsx b/react-ui/src/pages/AutoML/components/ExperimentResult/index.tsx index c5522834..3d191070 100644 --- a/react-ui/src/pages/AutoML/components/ExperimentResult/index.tsx +++ b/react-ui/src/pages/AutoML/components/ExperimentResult/index.tsx @@ -1,5 +1,4 @@ import InfoGroup from '@/components/InfoGroup'; -import KFIcon from '@/components/KFIcon'; import { getFileReq } from '@/services/file'; import { to } from '@/utils/promise'; import { Button, Image } from 'antd'; @@ -38,28 +37,10 @@ function ExperimentResult({ fileUrl, imageUrl, modelPath }: ExperimentResultProp return (
- {modelPath && ( -
- 文件名 - save_model.joblib - -
- )}
{result}
- +
+ {modelPath && ( +
+ 文件名 + save_model.joblib + +
+ )}
); } diff --git a/react-ui/src/pages/Experiment/components/ExperimentDrawer/index.less b/react-ui/src/pages/Experiment/components/ExperimentDrawer/index.less index 83a91180..c1f1b7ef 100644 --- a/react-ui/src/pages/Experiment/components/ExperimentDrawer/index.less +++ b/react-ui/src/pages/Experiment/components/ExperimentDrawer/index.less @@ -3,6 +3,12 @@ .ant-drawer-body { overflow-y: hidden; } + + .ant-drawer-close { + position: absolute; + top: 16px; + right: 16px; + } } &__tabs { diff --git a/react-ui/src/pages/Experiment/components/ExperimentDrawer/index.tsx b/react-ui/src/pages/Experiment/components/ExperimentDrawer/index.tsx index 26da1c07..cdcaea19 100644 --- a/react-ui/src/pages/Experiment/components/ExperimentDrawer/index.tsx +++ b/react-ui/src/pages/Experiment/components/ExperimentDrawer/index.tsx @@ -2,7 +2,7 @@ import { ExperimentStatus } from '@/enums'; import { experimentStatusInfo } from '@/pages/Experiment/status'; import { PipelineNodeModelSerialize } from '@/types'; import { elapsedTime, formatDate } from '@/utils/date'; -import { DatabaseOutlined, ProfileOutlined } from '@ant-design/icons'; +import { CloseOutlined, DatabaseOutlined, ProfileOutlined } from '@ant-design/icons'; import { Drawer, Tabs } from 'antd'; import { useMemo } from 'react'; import ExperimentParameter from '../ExperimentParameter'; @@ -95,10 +95,11 @@ const ExperimentDrawer = ({ return ( } onClose={onClose} open={open} width={520} diff --git a/react-ui/src/pages/Pipeline/components/GlobalParamsDrawer/index.tsx b/react-ui/src/pages/Pipeline/components/GlobalParamsDrawer/index.tsx index 7fe6440a..e54894ea 100644 --- a/react-ui/src/pages/Pipeline/components/GlobalParamsDrawer/index.tsx +++ b/react-ui/src/pages/Pipeline/components/GlobalParamsDrawer/index.tsx @@ -49,7 +49,7 @@ const GlobalParamsDrawer = forwardRef( return ( Date: Wed, 4 Dec 2024 10:03:20 +0800 Subject: [PATCH 48/59] =?UTF-8?q?feat:=20=E6=B7=BB=E5=8A=A0=20valtio?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- react-ui/config/config.ts | 1 + react-ui/src/components/InfoGroup/index.less | 5 +---- react-ui/src/components/InfoGroup/index.tsx | 20 ++++++++++++------- react-ui/src/hooks/resource.ts | 16 ++++++++++++++- .../components/ExperimentResult/index.less | 4 ---- .../components/ExperimentResult/index.tsx | 2 +- react-ui/src/pages/System/User/index.tsx | 2 +- react-ui/src/state/computingResourceStore.ts | 16 +++++++++++++++ 8 files changed, 48 insertions(+), 18 deletions(-) create mode 100644 react-ui/src/state/computingResourceStore.ts diff --git a/react-ui/config/config.ts b/react-ui/config/config.ts index c10b23b6..04ab330d 100644 --- a/react-ui/config/config.ts +++ b/react-ui/config/config.ts @@ -156,4 +156,5 @@ export default defineConfig({ }, javascriptEnabled: true, }, + valtio: {}, }); diff --git a/react-ui/src/components/InfoGroup/index.less b/react-ui/src/components/InfoGroup/index.less index cf05c8b2..4dccf4c7 100644 --- a/react-ui/src/components/InfoGroup/index.less +++ b/react-ui/src/components/InfoGroup/index.less @@ -5,10 +5,7 @@ padding: 20px @content-padding; background-color: white; border: 1px solid @border-color-base; + border-top: none; border-radius: 0 0 4px 4px; - - &&--scroll { - padding: 0; - } } } diff --git a/react-ui/src/components/InfoGroup/index.tsx b/react-ui/src/components/InfoGroup/index.tsx index 4e3359c0..8cf4b4e8 100644 --- a/react-ui/src/components/InfoGroup/index.tsx +++ b/react-ui/src/components/InfoGroup/index.tsx @@ -4,21 +4,27 @@ import './index.less'; type InfoGroupProps = { title: string; - contentScroll?: boolean; // 内容是否需要滚动,如果可以滚动,则取消 padding + height?: string | number; // 如果要纵向滚动,需要设置高度 + width?: string | number; // 如果要横向滚动,需要设置宽度 className?: string; style?: React.CSSProperties; children?: React.ReactNode; }; -function InfoGroup({ title, contentScroll = false, className, style, children }: InfoGroupProps) { +function InfoGroup({ title, height, width, className, style, children }: InfoGroupProps) { + const contentStyle: React.CSSProperties = {}; + if (height) { + contentStyle.height = height; + contentStyle.overflowY = 'auto'; + } + if (width) { + contentStyle.width = width; + contentStyle.overflowX = 'auto'; + } return (
-
+
{children}
diff --git a/react-ui/src/hooks/resource.ts b/react-ui/src/hooks/resource.ts index d3957bb7..0b491aeb 100644 --- a/react-ui/src/hooks/resource.ts +++ b/react-ui/src/hooks/resource.ts @@ -1,15 +1,28 @@ +/* + * @Author: 赵伟 + * @Date: 2024-10-10 08:51:41 + * @Description: 资源规格 hook + */ + import { getComputingResourceReq } from '@/services/pipeline'; +import computingResourceState, { setComputingResource } from '@/state/computingResourceStore'; import { ComputingResource } from '@/types'; import { to } from '@/utils/promise'; import { type SelectProps } from 'antd'; import { useCallback, useEffect, useState } from 'react'; +import { useSnapshot } from 'umi'; // 获取资源规格 export function useComputingResource() { const [resourceStandardList, setResourceStandardList] = useState([]); + const computingResourceSnap = useSnapshot(computingResourceState); useEffect(() => { - getComputingResource(); + if (computingResourceSnap.computingResource.length > 0) { + setResourceStandardList(computingResourceSnap.computingResource as ComputingResource[]); + } else { + getComputingResource(); + } }, []); // 获取资源规格列表数据 @@ -22,6 +35,7 @@ export function useComputingResource() { const [res] = await to(getComputingResourceReq(params)); if (res && res.data && res.data.content) { setResourceStandardList(res.data.content); + setComputingResource(res.data.content); } }, []); diff --git a/react-ui/src/pages/AutoML/components/ExperimentResult/index.less b/react-ui/src/pages/AutoML/components/ExperimentResult/index.less index a263564d..342817c3 100644 --- a/react-ui/src/pages/AutoML/components/ExperimentResult/index.less +++ b/react-ui/src/pages/AutoML/components/ExperimentResult/index.less @@ -25,10 +25,6 @@ } &__text { - width: 100%; - height: 420px; - padding: 20px @content-padding; - overflow: auto; white-space: pre-wrap; } diff --git a/react-ui/src/pages/AutoML/components/ExperimentResult/index.tsx b/react-ui/src/pages/AutoML/components/ExperimentResult/index.tsx index 3d191070..a826155d 100644 --- a/react-ui/src/pages/AutoML/components/ExperimentResult/index.tsx +++ b/react-ui/src/pages/AutoML/components/ExperimentResult/index.tsx @@ -37,7 +37,7 @@ function ExperimentResult({ fileUrl, imageUrl, modelPath }: ExperimentResultProp return (
- +
{result}
diff --git a/react-ui/src/pages/System/User/index.tsx b/react-ui/src/pages/System/User/index.tsx index e4e65596..fcd4220d 100644 --- a/react-ui/src/pages/System/User/index.tsx +++ b/react-ui/src/pages/System/User/index.tsx @@ -235,7 +235,7 @@ const UserTableList: React.FC = () => { const columns: ProColumns[] = [ { title: , - dataIndex: 'deptId', + dataIndex: 'userId', valueType: 'text', }, { diff --git a/react-ui/src/state/computingResourceStore.ts b/react-ui/src/state/computingResourceStore.ts new file mode 100644 index 00000000..23d1455b --- /dev/null +++ b/react-ui/src/state/computingResourceStore.ts @@ -0,0 +1,16 @@ +import { ComputingResource } from '@/types'; +import { proxy } from 'umi'; + +type ComputingResourceStore = { + computingResource: ComputingResource[]; +}; + +const state = proxy({ + computingResource: [], +}); + +export const setComputingResource = (computingResource: ComputingResource[]) => { + state.computingResource = computingResource; +}; + +export default state; From 03c9f8828fdab92f597f2f0ed4fca437f14181f8 Mon Sep 17 00:00:00 2001 From: chenzhihang <709011834@qq.com> Date: Wed, 4 Dec 2024 11:23:16 +0800 Subject: [PATCH 49/59] =?UTF-8?q?=E4=BC=98=E5=8C=96=E6=9C=8D=E5=8A=A1?= =?UTF-8?q?=E5=8A=9F=E8=83=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../main/java/com/ruoyi/platform/domain/ServiceVersion.java | 2 ++ .../com/ruoyi/platform/vo/serviceVos/ServiceVersionVo.java | 2 ++ .../resources/mapper/managementPlatform/ServiceDaoMapper.xml | 4 ++-- 3 files changed, 6 insertions(+), 2 deletions(-) diff --git a/ruoyi-modules/management-platform/src/main/java/com/ruoyi/platform/domain/ServiceVersion.java b/ruoyi-modules/management-platform/src/main/java/com/ruoyi/platform/domain/ServiceVersion.java index f190a99a..0ca0b8ed 100644 --- a/ruoyi-modules/management-platform/src/main/java/com/ruoyi/platform/domain/ServiceVersion.java +++ b/ruoyi-modules/management-platform/src/main/java/com/ruoyi/platform/domain/ServiceVersion.java @@ -53,4 +53,6 @@ public class ServiceVersion implements Serializable { private String deploymentName; private String svcName; + + private String deployType; } diff --git a/ruoyi-modules/management-platform/src/main/java/com/ruoyi/platform/vo/serviceVos/ServiceVersionVo.java b/ruoyi-modules/management-platform/src/main/java/com/ruoyi/platform/vo/serviceVos/ServiceVersionVo.java index 3baae9b1..2cf03f47 100644 --- a/ruoyi-modules/management-platform/src/main/java/com/ruoyi/platform/vo/serviceVos/ServiceVersionVo.java +++ b/ruoyi-modules/management-platform/src/main/java/com/ruoyi/platform/vo/serviceVos/ServiceVersionVo.java @@ -57,4 +57,6 @@ public class ServiceVersionVo { private String svcName; private Boolean rerun; + + private String deployType; } diff --git a/ruoyi-modules/management-platform/src/main/resources/mapper/managementPlatform/ServiceDaoMapper.xml b/ruoyi-modules/management-platform/src/main/resources/mapper/managementPlatform/ServiceDaoMapper.xml index fc21d3cb..cfa98aa1 100644 --- a/ruoyi-modules/management-platform/src/main/resources/mapper/managementPlatform/ServiceDaoMapper.xml +++ b/ruoyi-modules/management-platform/src/main/resources/mapper/managementPlatform/ServiceDaoMapper.xml @@ -91,10 +91,10 @@ insert into service_version(service_id, version, model, description, image, resource, replicas, mount_path, env_variables, - code_config, command, create_by, update_by) + code_config, command, create_by, update_by, deploy_type) values (#{serviceVersion.serviceId}, #{serviceVersion.version}, #{serviceVersion.model}, #{serviceVersion.description}, #{serviceVersion.image}, #{serviceVersion.resource}, #{serviceVersion.replicas}, #{serviceVersion.mountPath}, #{serviceVersion.envVariables}, - #{serviceVersion.codeConfig}, #{serviceVersion.command}, #{serviceVersion.createBy}, #{serviceVersion.updateBy}) + #{serviceVersion.codeConfig}, #{serviceVersion.command}, #{serviceVersion.createBy}, #{serviceVersion.updateBy}, #{serviceVersion.deployType}) From 69b93bc275ebea7bdfa0213cc721e2c9de08841d Mon Sep 17 00:00:00 2001 From: chenzhihang <709011834@qq.com> Date: Wed, 4 Dec 2024 13:54:45 +0800 Subject: [PATCH 50/59] =?UTF-8?q?=E4=BC=98=E5=8C=96=E6=9C=8D=E5=8A=A1?= =?UTF-8?q?=E5=8A=9F=E8=83=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../java/com/ruoyi/platform/service/impl/ServiceServiceImpl.java | 1 + 1 file changed, 1 insertion(+) diff --git a/ruoyi-modules/management-platform/src/main/java/com/ruoyi/platform/service/impl/ServiceServiceImpl.java b/ruoyi-modules/management-platform/src/main/java/com/ruoyi/platform/service/impl/ServiceServiceImpl.java index 6075f9bc..9ac899d3 100644 --- a/ruoyi-modules/management-platform/src/main/java/com/ruoyi/platform/service/impl/ServiceServiceImpl.java +++ b/ruoyi-modules/management-platform/src/main/java/com/ruoyi/platform/service/impl/ServiceServiceImpl.java @@ -238,6 +238,7 @@ public class ServiceServiceImpl implements ServiceService { paramMap.put("image", serviceVersion.getImage()); paramMap.put("model", JSONObject.parseObject(serviceVersion.getModel())); paramMap.put("service_type", service.getServiceType()); + paramMap.put("deploy_type", serviceVersion.getDeployType()); String req = HttpUtils.sendPost(argoUrl + modelService + "/create", JSON.toJSONString(paramMap)); if (StringUtils.isNotEmpty(req)) { Map reqMap = JacksonUtil.parseJSONStr2Map(req); From 8189e2e059ed7828879f15841a0f8a8d5ae02eb1 Mon Sep 17 00:00:00 2001 From: chenzhihang <709011834@qq.com> Date: Wed, 4 Dec 2024 14:22:16 +0800 Subject: [PATCH 51/59] =?UTF-8?q?=E4=BC=98=E5=8C=96=E5=BC=80=E5=8F=91?= =?UTF-8?q?=E7=8E=AF=E5=A2=83=E5=8A=9F=E8=83=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../com/ruoyi/platform/utils/K8sClientUtil.java | 13 ++++++++++--- 1 file changed, 10 insertions(+), 3 deletions(-) diff --git a/ruoyi-modules/management-platform/src/main/java/com/ruoyi/platform/utils/K8sClientUtil.java b/ruoyi-modules/management-platform/src/main/java/com/ruoyi/platform/utils/K8sClientUtil.java index 3930c112..a6eaed5f 100644 --- a/ruoyi-modules/management-platform/src/main/java/com/ruoyi/platform/utils/K8sClientUtil.java +++ b/ruoyi-modules/management-platform/src/main/java/com/ruoyi/platform/utils/K8sClientUtil.java @@ -459,12 +459,19 @@ public class K8sClientUtil { // 配置卷和卷挂载 List volumeMounts = new ArrayList<>(); volumeMounts.add(new V1VolumeMount().name("workspace").mountPath("/opt/notebooks")); - volumeMounts.add(new V1VolumeMount().name("data").mountPath("/opt/dataset").subPath(datasetPath).readOnly(true)); - volumeMounts.add(new V1VolumeMount().name("data").mountPath("/opt/model").subPath(modelPath).readOnly(true)); List volumes = new ArrayList<>(); volumes.add(new V1Volume().name("workspace").hostPath(new V1HostPathVolumeSource().path(hostPath + "/" + podName + "/notebooks").type("DirectoryOrCreate"))); - volumes.add(new V1Volume().name("data").hostPath(new V1HostPathVolumeSource().path(hostPath).type("DirectoryOrCreate"))); + + if (StringUtils.isNotEmpty(datasetPath)) { + volumeMounts.add(new V1VolumeMount().name("data").mountPath("/opt/dataset").subPath(datasetPath).readOnly(true)); + } + if (StringUtils.isNotEmpty(modelPath)) { + volumeMounts.add(new V1VolumeMount().name("data").mountPath("/opt/model").subPath(modelPath).readOnly(true)); + } + if (StringUtils.isNotEmpty(datasetPath) || StringUtils.isNotEmpty(modelPath)) { + volumes.add(new V1Volume().name("data").hostPath(new V1HostPathVolumeSource().path(hostPath).type("DirectoryOrCreate"))); + } // 配置卷和卷挂载 // List volumeMounts = new ArrayList<>(); From bc8b3ceddced63eeaee0a7cce6a28ed88b88937c Mon Sep 17 00:00:00 2001 From: cp3hnu Date: Wed, 4 Dec 2024 16:03:55 +0800 Subject: [PATCH 52/59] =?UTF-8?q?fix:=20=E6=A8=A1=E5=9E=8B=E9=83=A8?= =?UTF-8?q?=E7=BD=B2=E5=AF=BC=E8=88=AA=E9=9D=A2=E5=8C=85=E5=B1=91=E9=94=99?= =?UTF-8?q?=E8=AF=AF?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- react-ui/config/routes.ts | 51 ++++++++++----- react-ui/src/hooks/sessionStorage.ts | 6 ++ react-ui/src/pages/AutoML/Create/index.tsx | 1 - .../DevelopmentEnvironment/Create/index.tsx | 26 +------- .../ModelDeployment/CreateService/index.tsx | 63 ++++++++++--------- .../ModelDeployment/CreateVersion/index.tsx | 39 ++++++------ .../src/pages/ModelDeployment/List/index.tsx | 25 +++----- .../ModelDeployment/ServiceInfo/index.tsx | 16 +++-- react-ui/src/utils/sessionStorage.ts | 2 - 9 files changed, 121 insertions(+), 108 deletions(-) diff --git a/react-ui/config/routes.ts b/react-ui/config/routes.ts index cf4e1aaf..faa18dba 100644 --- a/react-ui/config/routes.ts +++ b/react-ui/config/routes.ts @@ -155,7 +155,7 @@ export default [ component: './AutoML/List/index', }, { - name: '自动机器学习详情', + name: '实验详情', path: 'info/:id', component: './AutoML/Info/index', }, @@ -170,7 +170,7 @@ export default [ component: './AutoML/Create/index', }, { - name: '实验实例', + name: '实验实例详情', path: 'instance/:autoMLId/:id', component: './AutoML/Instance/index', }, @@ -261,25 +261,46 @@ export default [ path: '', component: './ModelDeployment/List', }, - { - name: '服务详情', - path: 'serviceInfo/:id', - component: './ModelDeployment/ServiceInfo', - }, - { - name: '服务版本详情', - path: 'versionInfo/:id', - component: './ModelDeployment/VersionInfo', - }, { name: '创建推理服务', path: 'createService', component: './ModelDeployment/CreateService', }, { - name: '新增服务版本', - path: 'addVersion/:id', - component: './ModelDeployment/CreateVersion', + name: '编辑推理服务', + path: 'editService/:serviceId', + component: './ModelDeployment/CreateService', + }, + { + name: '服务详情', + path: 'serviceInfo/:serviceId', + routes: [ + { + name: '服务详情', + path: '', + component: './ModelDeployment/ServiceInfo', + }, + { + name: '新增服务版本', + path: 'createVersion', + component: './ModelDeployment/CreateVersion', + }, + { + name: '更新服务版本', + path: 'updateVersion', + component: './ModelDeployment/CreateVersion', + }, + { + name: '重启服务版本', + path: 'restartVersion', + component: './ModelDeployment/CreateVersion', + }, + { + name: '服务版本详情', + path: 'versionInfo/:id', + component: './ModelDeployment/VersionInfo', + }, + ], }, ], }, diff --git a/react-ui/src/hooks/sessionStorage.ts b/react-ui/src/hooks/sessionStorage.ts index 7f528cfe..8cf3c921 100644 --- a/react-ui/src/hooks/sessionStorage.ts +++ b/react-ui/src/hooks/sessionStorage.ts @@ -1,3 +1,9 @@ +/* + * @Author: 赵伟 + * @Date: 2024-11-06 14:53:37 + * @Description: SessionStorage hook + */ + import SessionStorage from '@/utils/sessionStorage'; import { useEffect, useState } from 'react'; diff --git a/react-ui/src/pages/AutoML/Create/index.tsx b/react-ui/src/pages/AutoML/Create/index.tsx index 1dc2c3cc..93e4c2c6 100644 --- a/react-ui/src/pages/AutoML/Create/index.tsx +++ b/react-ui/src/pages/AutoML/Create/index.tsx @@ -4,7 +4,6 @@ * @Description: 创建服务版本 */ import PageTitle from '@/components/PageTitle'; - import { AutoMLEnsembleClass, AutoMLTaskType } from '@/enums'; import { addAutoMLReq, getAutoMLInfoReq, updateAutoMLReq } from '@/services/autoML'; import { convertEmptyStringToUndefined, parseJsonText, trimCharacter } from '@/utils'; diff --git a/react-ui/src/pages/DevelopmentEnvironment/Create/index.tsx b/react-ui/src/pages/DevelopmentEnvironment/Create/index.tsx index 73a2006f..90ad4d15 100644 --- a/react-ui/src/pages/DevelopmentEnvironment/Create/index.tsx +++ b/react-ui/src/pages/DevelopmentEnvironment/Create/index.tsx @@ -167,7 +167,7 @@ function EditorCreate() {
- + - + (undefined); const { message } = App.useApp(); + const params = useParams(); + const serviceId = params.serviceId; useEffect(() => { - const res = SessionStorage.getItem(SessionStorage.serviceInfoKey, true); - if (res) { - setOperationType(res.operationType); - setServiceInfo(res); - form.setFieldsValue(pick(res, ['service_name', 'service_type', 'description'])); + if (serviceId) { + getServiceInfo(); } - return () => { - SessionStorage.removeItem(SessionStorage.serviceInfoKey); - }; }, []); + // 获取服务详情 + const getServiceInfo = async () => { + const [res] = await to(getServiceInfoReq(serviceId)); + if (res && res.data) { + setServiceInfo(res.data); + const { service_type, service_name, description } = res.data; + form.setFieldsValue({ + service_type, + service_name, + description, + }); + } + }; + // 创建、更新服务 const createService = async (formData: FormData) => { - const request = - operationType === ServiceOperationType.Create ? createServiceReq : updateServiceReq; - const params = - operationType === ServiceOperationType.Create - ? formData - : { - id: serviceInfo?.id, - ...formData, - }; + const request = serviceId ? updateServiceReq : createServiceReq; + const params = serviceId + ? { + id: serviceId, + ...formData, + } + : formData; const [res] = await to(request(params)); if (res && res.data) { message.success('操作成功'); navigate(-1); - setTimeout(() => { - window.postMessage({ type: createServiceVersionMessage, payload: res.data.id }); - }, 500); + if (!serviceId) { + setTimeout(() => { + window.postMessage({ type: createServiceVersionMessage, payload: res.data.id }); + }, 500); + } } }; @@ -74,8 +81,8 @@ function CreateService() { navigate(-1); }; - const disabled = operationType !== ServiceOperationType.Create; - const title = operationType === ServiceOperationType.Create ? '创建推理服务' : '更新推理服务'; + const disabled = !!serviceId; + const title = serviceId ? '编辑推理服务' : '创建推理服务'; return (
diff --git a/react-ui/src/pages/ModelDeployment/CreateVersion/index.tsx b/react-ui/src/pages/ModelDeployment/CreateVersion/index.tsx index 56aeb333..06a71073 100644 --- a/react-ui/src/pages/ModelDeployment/CreateVersion/index.tsx +++ b/react-ui/src/pages/ModelDeployment/CreateVersion/index.tsx @@ -27,12 +27,7 @@ import { useNavigate, useParams } from '@umijs/max'; import { App, Button, Col, Flex, Form, Input, InputNumber, Row, Select } from 'antd'; import { omit, pick } from 'lodash'; import { useEffect, useState } from 'react'; -import { - CreateServiceVersionFrom, - ServiceData, - ServiceOperationType, - ServiceVersionData, -} from '../types'; +import { CreateServiceVersionFrom, ServiceOperationType, ServiceVersionData } from '../types'; import styles from './index.less'; // 表单数据 @@ -49,6 +44,11 @@ export type FormData = { env_variables: { key: string; value: string }[]; // 环境变量 }; +type ServiceVersionCache = ServiceVersionData & { + operationType: ServiceOperationType; + lastPage: CreateServiceVersionFrom; +}; + function CreateServiceVersion() { const navigate = useNavigate(); const [form] = Form.useForm(); @@ -56,18 +56,16 @@ function CreateServiceVersion() { const [operationType, setOperationType] = useState(ServiceOperationType.Create); const [lastPage, setLastPage] = useState(CreateServiceVersionFrom.ServiceInfo); const { message } = App.useApp(); - const [serviceInfo, setServiceInfo] = useState(undefined); + // const [serviceInfo, setServiceInfo] = useState(undefined); const [versionInfo, setVersionInfo] = useState(undefined); const params = useParams(); - const id = params.id; + const serviceId = params.serviceId; useEffect(() => { - const res: - | (ServiceVersionData & { - operationType: ServiceOperationType; - lastPage: CreateServiceVersionFrom; - }) - | undefined = SessionStorage.getItem(SessionStorage.serviceVersionInfoKey, true); + const res: ServiceVersionCache | undefined = SessionStorage.getItem( + SessionStorage.serviceVersionInfoKey, + true, + ); if (res) { setOperationType(res.operationType); setLastPage(res.lastPage); @@ -109,9 +107,9 @@ function CreateServiceVersion() { // 获取服务详情 const getServiceInfo = async () => { - const [res] = await to(getServiceInfoReq(id)); + const [res] = await to(getServiceInfoReq(serviceId)); if (res && res.data) { - setServiceInfo(res.data); + // setServiceInfo(res.data); form.setFieldsValue({ service_name: res.data.service_name, }); @@ -144,12 +142,15 @@ function CreateServiceVersion() { showValue: 'show_value', }) : undefined, - service_id: serviceInfo?.id, + service_id: serviceId, }; const params = operationType === ServiceOperationType.Create - ? object + ? { + ...object, + deploy_type: 'web', + } : { id: versionInfo?.id, rerun: operationType === ServiceOperationType.Restart ? true : false, @@ -168,7 +169,7 @@ function CreateServiceVersion() { if (lastPage === CreateServiceVersionFrom.ServiceInfo) { navigate(-1); } else { - navigate(`/modelDeployment/serviceInfo/${serviceInfo?.id}`, { replace: true }); + navigate(`serviceInfo/${serviceId}`, { replace: true }); } } }; diff --git a/react-ui/src/pages/ModelDeployment/List/index.tsx b/react-ui/src/pages/ModelDeployment/List/index.tsx index 21e9b514..6e61c3ab 100644 --- a/react-ui/src/pages/ModelDeployment/List/index.tsx +++ b/react-ui/src/pages/ModelDeployment/List/index.tsx @@ -116,23 +116,18 @@ function ModelDeployment() { }; // 创建、更新服务 - const createService = (type: ServiceOperationType, record?: ServiceData) => { - SessionStorage.setItem( - SessionStorage.serviceInfoKey, - { - ...record, - operationType: type, - }, - true, - ); - + const createService = (record?: ServiceData) => { setCacheState({ pagination, searchText, serviceType: serviceType, }); - navigate(`/modelDeployment/createService`); + if (record) { + navigate(`editService/${record.id}`); + } else { + navigate('createService'); + } }; // 查看详情 @@ -143,7 +138,7 @@ function ModelDeployment() { serviceType: serviceType, }); - navigate(`/modelDeployment/serviceInfo/${record.id}`); + navigate(`serviceInfo/${record.id}`); }; const handleMessage = (e: MessageEvent) => { @@ -172,7 +167,7 @@ function ModelDeployment() { true, ); - navigate(`/modelDeployment/addVersion/${serviceId}`); + navigate(`serviceInfo/${serviceId}/createVersion`); }; // 分页切换 @@ -248,7 +243,7 @@ function ModelDeployment() { size="small" key="edit" icon={} - onClick={() => createService(ServiceOperationType.Update, record)} + onClick={() => createService(record)} > 编辑 @@ -307,7 +302,7 @@ function ModelDeployment() {
- {state.icons.length > 0 && ( - - - - - - - )} - - {state.icons.map((icon) => { - const { type } = icon; - const iconName = `${type - .split('-') - .map((str) => `${str[0].toUpperCase()}${str.slice(1)}`) - .join('')}Outlined`; - return ( - - - - - ); - })} - -
- {formatMessage({ id: 'app.docs.components.icon.pic-searcher.th-icon' })} - - {formatMessage({ id: 'app.docs.components.icon.pic-searcher.th-score' })} -
- - {React.createElement(allIcons[iconName])} - - - -
- {state.error && ( - - )} -
- - -
- ); -}; - -export default PicSearcher; diff --git a/react-ui/src/components/IconSelector/fields.ts b/react-ui/src/components/IconSelector/fields.ts deleted file mode 100644 index 5de572cb..00000000 --- a/react-ui/src/components/IconSelector/fields.ts +++ /dev/null @@ -1,223 +0,0 @@ -import * as AntdIcons from '@ant-design/icons/lib/icons'; - -const all = Object.keys(AntdIcons) - .map((n) => n.replace(/(Outlined|Filled|TwoTone)$/, '')) - .filter((n, i, arr) => arr.indexOf(n) === i); - -const direction = [ - 'StepBackward', - 'StepForward', - 'FastBackward', - 'FastForward', - 'Shrink', - 'ArrowsAlt', - 'Down', - 'Up', - 'Left', - 'Right', - 'CaretUp', - 'CaretDown', - 'CaretLeft', - 'CaretRight', - 'UpCircle', - 'DownCircle', - 'LeftCircle', - 'RightCircle', - 'DoubleRight', - 'DoubleLeft', - 'VerticalLeft', - 'VerticalRight', - 'VerticalAlignTop', - 'VerticalAlignMiddle', - 'VerticalAlignBottom', - 'Forward', - 'Backward', - 'Rollback', - 'Enter', - 'Retweet', - 'Swap', - 'SwapLeft', - 'SwapRight', - 'ArrowUp', - 'ArrowDown', - 'ArrowLeft', - 'ArrowRight', - 'PlayCircle', - 'UpSquare', - 'DownSquare', - 'LeftSquare', - 'RightSquare', - 'Login', - 'Logout', - 'MenuFold', - 'MenuUnfold', - 'BorderBottom', - 'BorderHorizontal', - 'BorderInner', - 'BorderOuter', - 'BorderLeft', - 'BorderRight', - 'BorderTop', - 'BorderVerticle', - 'PicCenter', - 'PicLeft', - 'PicRight', - 'RadiusBottomleft', - 'RadiusBottomright', - 'RadiusUpleft', - 'RadiusUpright', - 'Fullscreen', - 'FullscreenExit', -]; - -const suggestion = [ - 'Question', - 'QuestionCircle', - 'Plus', - 'PlusCircle', - 'Pause', - 'PauseCircle', - 'Minus', - 'MinusCircle', - 'PlusSquare', - 'MinusSquare', - 'Info', - 'InfoCircle', - 'Exclamation', - 'ExclamationCircle', - 'Close', - 'CloseCircle', - 'CloseSquare', - 'Check', - 'CheckCircle', - 'CheckSquare', - 'ClockCircle', - 'Warning', - 'IssuesClose', - 'Stop', -]; - -const editor = [ - 'Edit', - 'Form', - 'Copy', - 'Scissor', - 'Delete', - 'Snippets', - 'Diff', - 'Highlight', - 'AlignCenter', - 'AlignLeft', - 'AlignRight', - 'BgColors', - 'Bold', - 'Italic', - 'Underline', - 'Strikethrough', - 'Redo', - 'Undo', - 'ZoomIn', - 'ZoomOut', - 'FontColors', - 'FontSize', - 'LineHeight', - 'Dash', - 'SmallDash', - 'SortAscending', - 'SortDescending', - 'Drag', - 'OrderedList', - 'UnorderedList', - 'RadiusSetting', - 'ColumnWidth', - 'ColumnHeight', -]; - -const data = [ - 'AreaChart', - 'PieChart', - 'BarChart', - 'DotChart', - 'LineChart', - 'RadarChart', - 'HeatMap', - 'Fall', - 'Rise', - 'Stock', - 'BoxPlot', - 'Fund', - 'Sliders', -]; - -const logo = [ - 'Android', - 'Apple', - 'Windows', - 'Ie', - 'Chrome', - 'Github', - 'Aliwangwang', - 'Dingding', - 'WeiboSquare', - 'WeiboCircle', - 'TaobaoCircle', - 'Html5', - 'Weibo', - 'Twitter', - 'Wechat', - 'Youtube', - 'AlipayCircle', - 'Taobao', - 'Skype', - 'Qq', - 'MediumWorkmark', - 'Gitlab', - 'Medium', - 'Linkedin', - 'GooglePlus', - 'Dropbox', - 'Facebook', - 'Codepen', - 'CodeSandbox', - 'CodeSandboxCircle', - 'Amazon', - 'Google', - 'CodepenCircle', - 'Alipay', - 'AntDesign', - 'AntCloud', - 'Aliyun', - 'Zhihu', - 'Slack', - 'SlackSquare', - 'Behance', - 'BehanceSquare', - 'Dribbble', - 'DribbbleSquare', - 'Instagram', - 'Yuque', - 'Alibaba', - 'Yahoo', - 'Reddit', - 'Sketch', - 'WhatsApp', - 'Dingtalk', -]; - -const datum = [...direction, ...suggestion, ...editor, ...data, ...logo]; - -const other = all.filter((n) => !datum.includes(n)); - -export const categories = { - direction, - suggestion, - editor, - data, - logo, - other, -}; - -export default categories; - -export type Categories = typeof categories; -export type CategoriesKeys = keyof Categories; diff --git a/react-ui/src/components/IconSelector/index.tsx b/react-ui/src/components/IconSelector/index.tsx deleted file mode 100644 index 0d50db68..00000000 --- a/react-ui/src/components/IconSelector/index.tsx +++ /dev/null @@ -1,146 +0,0 @@ -import Icon, * as AntdIcons from '@ant-design/icons'; -import { Empty, Input, Radio } from 'antd'; -import type { RadioChangeEvent } from 'antd/es/radio/interface'; -import debounce from 'lodash/debounce'; -import * as React from 'react'; -import Category from './Category'; -import IconPicSearcher from './IconPicSearcher'; -import type { CategoriesKeys } from './fields'; -import { categories } from './fields'; -import { FilledIcon, OutlinedIcon, TwoToneIcon } from './themeIcons'; -// import { useIntl } from '@umijs/max'; - -export enum ThemeType { - Filled = 'Filled', - Outlined = 'Outlined', - TwoTone = 'TwoTone', -} - -const allIcons: { [key: string]: any } = AntdIcons; - -interface IconSelectorProps { - //intl: any; - onSelect: any; -} - -interface IconSelectorState { - theme: ThemeType; - searchKey: string; -} - -const IconSelector: React.FC = (props) => { - // const intl = useIntl(); - // const { messages } = intl; - const { onSelect } = props; - const [displayState, setDisplayState] = React.useState({ - theme: ThemeType.Outlined, - searchKey: '', - }); - - const newIconNames: string[] = []; - - const handleSearchIcon = React.useCallback( - debounce((searchKey: string) => { - setDisplayState((prevState) => ({ ...prevState, searchKey })); - }), - [], - ); - - const handleChangeTheme = React.useCallback((e: RadioChangeEvent) => { - setDisplayState((prevState) => ({ ...prevState, theme: e.target.value as ThemeType })); - }, []); - - const renderCategories = React.useMemo(() => { - const { searchKey = '', theme } = displayState; - - const categoriesResult = Object.keys(categories) - .map((key: CategoriesKeys) => { - let iconList = categories[key]; - if (searchKey) { - const matchKey = searchKey - // eslint-disable-next-line prefer-regex-literals - .replace(new RegExp(`^<([a-zA-Z]*)\\s/>$`, 'gi'), (_, name) => name) - .replace(/(Filled|Outlined|TwoTone)$/, '') - .toLowerCase(); - iconList = iconList.filter((iconName: string) => - iconName.toLowerCase().includes(matchKey), - ); - } - - // CopyrightCircle is same as Copyright, don't show it - iconList = iconList.filter((icon: string) => icon !== 'CopyrightCircle'); - - return { - category: key, - icons: iconList - .map((iconName: string) => iconName + theme) - .filter((iconName: string) => allIcons[iconName]), - }; - }) - .filter(({ icons }) => !!icons.length) - .map(({ category, icons }) => ( - { - if (onSelect) { - onSelect(name, allIcons[name]); - } - }} - /> - )); - return categoriesResult.length === 0 ? : categoriesResult; - }, [displayState.searchKey, displayState.theme]); - return ( - <> -
- , - value: ThemeType.Outlined, - }, - { - label: , - value: ThemeType.Filled, - }, - { - label: , - value: ThemeType.TwoTone, - }, - ]} - > - {/* - {messages['app.docs.components.icon.outlined']} - - - {messages['app.docs.components.icon.filled']} - - - {messages['app.docs.components.icon.two-tone']} - */} - - handleSearchIcon(e.currentTarget.value)} - size="large" - autoFocus - suffix={} - /> -
- {renderCategories} - - ); -}; - -export default IconSelector; diff --git a/react-ui/src/components/IconSelector/style.less b/react-ui/src/components/IconSelector/style.less deleted file mode 100644 index 0a4353d6..00000000 --- a/react-ui/src/components/IconSelector/style.less +++ /dev/null @@ -1,137 +0,0 @@ -.iconPicSearcher { - display: inline-block; - margin: 0 8px; - - .icon-pic-btn { - color: @text-color-secondary; - cursor: pointer; - transition: all 0.3s; - - &:hover { - color: @input-icon-hover-color; - } - } -} - -.icon-pic-preview { - width: 30px; - height: 30px; - margin-top: 10px; - padding: 8px; - text-align: center; - border: 1px solid @border-color-base; - border-radius: 4px; - - > img { - max-width: 50px; - max-height: 50px; - } -} - -.icon-pic-search-result { - min-height: 50px; - padding: 0 10px; - - > .result-tip { - padding: 10px 0; - color: @text-color-secondary; - } - - > table { - width: 100%; - - .col-icon { - width: 80px; - padding: 10px 0; - - > .anticon { - font-size: 30px; - - :hover { - color: @link-hover-color; - } - } - } - } -} - -ul.anticonsList { - margin: 2px 0; - overflow: hidden; - direction: ltr; - list-style: none; - - li { - position: relative; - float: left; - width: 48px; - height: 48px; - margin: 3px 0; - padding: 2px 0 0; - overflow: hidden; - color: #555; - text-align: center; - list-style: none; - background-color: inherit; - border-radius: 4px; - cursor: pointer; - transition: color 0.3s ease-in-out, background-color 0.3s ease-in-out; - - .rtl & { - margin: 3px 0; - padding: 2px 0 0; - } - - .anticon { - margin: 4px 0 2px; - font-size: 24px; - transition: transform 0.3s ease-in-out; - will-change: transform; - } - - .anticonClass { - display: block; - font-family: 'Lucida Console', Consolas, Monaco, 'Andale Mono', 'Ubuntu Mono', monospace; - white-space: nowrap; - text-align: center; - transform: scale(0.83); - - .ant-badge { - transition: color 0.3s ease-in-out; - } - } - - &:hover { - color: #fff; - background-color: @primary-color; - - .anticon { - transform: scale(1.4); - } - - .ant-badge { - color: #fff; - } - } - - &.TwoTone:hover { - background-color: #8ecafe; - } - - &.copied:hover { - color: rgba(255, 255, 255, 0.2); - } - - &.copied::after { - top: -2px; - opacity: 1; - } - } -} - -.copied-code { - padding: 2px 4px; - font-size: 12px; - background: #f5f5f5; - border-radius: 2px; -} diff --git a/react-ui/src/components/IconSelector/themeIcons.tsx b/react-ui/src/components/IconSelector/themeIcons.tsx deleted file mode 100644 index 1d9762cd..00000000 --- a/react-ui/src/components/IconSelector/themeIcons.tsx +++ /dev/null @@ -1,40 +0,0 @@ -import * as React from 'react'; - -export const FilledIcon: React.FC = (props) => { - const path = - 'M864 64H160C107 64 64 107 64 160v' + - '704c0 53 43 96 96 96h704c53 0 96-43 96-96V16' + - '0c0-53-43-96-96-96z'; - return ( - - - - ); -}; - -export const OutlinedIcon: React.FC = (props) => { - const path = - 'M864 64H160C107 64 64 107 64 160v7' + - '04c0 53 43 96 96 96h704c53 0 96-43 96-96V160c' + - '0-53-43-96-96-96z m-12 800H172c-6.6 0-12-5.4-' + - '12-12V172c0-6.6 5.4-12 12-12h680c6.6 0 12 5.4' + - ' 12 12v680c0 6.6-5.4 12-12 12z'; - return ( - - - - ); -}; - -export const TwoToneIcon: React.FC = (props) => { - const path = - 'M16 512c0 273.932 222.066 496 496 49' + - '6s496-222.068 496-496S785.932 16 512 16 16 238.' + - '066 16 512z m496 368V144c203.41 0 368 164.622 3' + - '68 368 0 203.41-164.622 368-368 368z'; - return ( - - - - ); -}; diff --git a/react-ui/src/components/KFConfirmModal/index.less b/react-ui/src/components/KFConfirmModal/index.less deleted file mode 100644 index 5c82b5ec..00000000 --- a/react-ui/src/components/KFConfirmModal/index.less +++ /dev/null @@ -1,7 +0,0 @@ -.kf-confirm-modal { - &__content { - width: 100%; - font-size: 18px; - text-align: center; - } -} diff --git a/react-ui/src/components/KFConfirmModal/index.tsx b/react-ui/src/components/KFConfirmModal/index.tsx deleted file mode 100644 index d494ad2a..00000000 --- a/react-ui/src/components/KFConfirmModal/index.tsx +++ /dev/null @@ -1,37 +0,0 @@ -/* - * @Author: 赵伟 - * @Date: 2024-10-10 10:54:25 - * @Description: 自定义 Confirm Modal - */ - -import classNames from 'classnames'; -import KFModal, { KFModalProps } from '../KFModal'; -import './index.less'; - -export interface KFConfirmModalProps extends KFModalProps { - content: string; -} -function KFConfirmModal({ - title, - image, - className = '', - centered, - maskClosable, - content, - ...rest -}: KFConfirmModalProps) { - return ( - -
{content}
-
- ); -} - -export default KFConfirmModal; From 3b018f05653d0dbaad7599f1391286f664f4cdb3 Mon Sep 17 00:00:00 2001 From: chenzhihang <709011834@qq.com> Date: Thu, 5 Dec 2024 13:57:08 +0800 Subject: [PATCH 55/59] =?UTF-8?q?=E4=BF=AE=E5=A4=8D=E5=88=9B=E5=BB=BA?= =?UTF-8?q?=E5=85=AC=E5=BC=80=E6=95=B0=E6=8D=AE=E9=9B=86=E6=A8=A1=E5=9E=8B?= =?UTF-8?q?bug?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../com/ruoyi/platform/constant/Constant.java | 2 ++ .../dataset/NewDatasetFromGitController.java | 13 +++++----- .../model/NewModelFromGitController.java | 13 +++++----- .../ruoyi/platform/service/ModelsService.java | 6 ++--- .../platform/service/NewDatasetService.java | 16 ++++++++---- .../service/impl/ModelsServiceImpl.java | 25 ++++++++----------- .../service/impl/NewDatasetServiceImpl.java | 16 ++++++------ 7 files changed, 49 insertions(+), 42 deletions(-) diff --git a/ruoyi-modules/management-platform/src/main/java/com/ruoyi/platform/constant/Constant.java b/ruoyi-modules/management-platform/src/main/java/com/ruoyi/platform/constant/Constant.java index 2f72576e..454007c3 100644 --- a/ruoyi-modules/management-platform/src/main/java/com/ruoyi/platform/constant/Constant.java +++ b/ruoyi-modules/management-platform/src/main/java/com/ruoyi/platform/constant/Constant.java @@ -22,6 +22,8 @@ public class Constant { public final static int Git_Category_Id = 39; + public final static String Item_Public = "public"; + public final static String Source_Auto_Export = "auto_export"; public final static String Source_Hand_Export = "hand_export"; public final static String Source_Add = "add"; diff --git a/ruoyi-modules/management-platform/src/main/java/com/ruoyi/platform/controller/dataset/NewDatasetFromGitController.java b/ruoyi-modules/management-platform/src/main/java/com/ruoyi/platform/controller/dataset/NewDatasetFromGitController.java index 62f5122a..96417687 100644 --- a/ruoyi-modules/management-platform/src/main/java/com/ruoyi/platform/controller/dataset/NewDatasetFromGitController.java +++ b/ruoyi-modules/management-platform/src/main/java/com/ruoyi/platform/controller/dataset/NewDatasetFromGitController.java @@ -85,14 +85,15 @@ public class NewDatasetFromGitController { @RequestParam("id") Integer id, @RequestParam(value = "owner", required = false) String owner, @RequestParam(value = "identifier", required = false) String identifier, - @RequestParam(value = "version", required = false) String version) throws Exception { - return AjaxResult.success(this.newDatasetService.getNewDatasetDesc(id, name, identifier, owner, version)); + @RequestParam(value = "version", required = false) String version, + @RequestParam(value = "is_public") Boolean isPublic) throws Exception { + return AjaxResult.success(this.newDatasetService.getNewDatasetDesc(id, name, identifier, owner, version, isPublic)); } @DeleteMapping("/deleteDataset") @ApiOperation(value = "删除数据集") - public AjaxResult deleteDataset(@RequestParam("id") Integer id, @RequestParam("identifier") String identifier, @RequestParam("owner") String owner) throws Exception { - this.newDatasetService.deleteDatasetNew(id, identifier, owner); + public AjaxResult deleteDataset(@RequestParam("id") Integer id, @RequestParam("identifier") String identifier, @RequestParam("owner") String owner, @RequestParam("is_public") Boolean isPublic) throws Exception { + this.newDatasetService.deleteDatasetNew(id, identifier, owner, isPublic); return AjaxResult.success(); } @@ -132,8 +133,8 @@ public class NewDatasetFromGitController { */ @GetMapping("/downloadAllFiles") @ApiOperation(value = "下载同一版本下所有数据集,并打包") - public ResponseEntity downloadAllDatasetFiles(@RequestParam("name") String name, @RequestParam("identifier") String identifier, @RequestParam("id") Integer id, @RequestParam("version") String version) throws Exception { - return newDatasetService.downloadAllDatasetFilesNew(name, identifier, id, version); + public ResponseEntity downloadAllDatasetFiles(@RequestParam("name") String name, @RequestParam("identifier") String identifier, @RequestParam("id") Integer id, @RequestParam("version") String version, @RequestParam("is_public") Boolean isPublic) throws Exception { + return newDatasetService.downloadAllDatasetFilesNew(name, identifier, id, version, isPublic); } /** diff --git a/ruoyi-modules/management-platform/src/main/java/com/ruoyi/platform/controller/model/NewModelFromGitController.java b/ruoyi-modules/management-platform/src/main/java/com/ruoyi/platform/controller/model/NewModelFromGitController.java index 3ba4a9fa..30338e95 100644 --- a/ruoyi-modules/management-platform/src/main/java/com/ruoyi/platform/controller/model/NewModelFromGitController.java +++ b/ruoyi-modules/management-platform/src/main/java/com/ruoyi/platform/controller/model/NewModelFromGitController.java @@ -1,7 +1,6 @@ package com.ruoyi.platform.controller.model; import com.ruoyi.common.core.web.domain.AjaxResult; -import com.ruoyi.platform.domain.ModelDependency1; import com.ruoyi.platform.service.ModelsService; import com.ruoyi.platform.vo.ModelsVo; import com.ruoyi.platform.vo.QueryModelMetricsVo; @@ -46,8 +45,8 @@ public class NewModelFromGitController { @GetMapping("/downloadAllFiles") @ApiOperation(value = "下载同一版本下所有模型,并打包") - public ResponseEntity downloadAllDatasetFiles(@RequestParam("name") String name, @RequestParam("identifier") String identifier, @RequestParam("id") Integer id, @RequestParam("version") String version) throws Exception { - return modelsService.downloadAllModelFilesNew(name, identifier, id, version); + public ResponseEntity downloadAllDatasetFiles(@RequestParam("name") String name, @RequestParam("identifier") String identifier, @RequestParam("id") Integer id, @RequestParam("version") String version, @RequestParam("is_public") Boolean isPublic) throws Exception { + return modelsService.downloadAllModelFilesNew(name, identifier, id, version, isPublic); } @GetMapping("/downloadSingleFile") @@ -103,8 +102,8 @@ public class NewModelFromGitController { @GetMapping("/getModelDetail") @ApiOperation(value = "获取模型详细信息") - public AjaxResult getModelDetail(@RequestParam("id") Integer id, @RequestParam("identifier") String identifier, @RequestParam("owner") String owner, @RequestParam("version") String version) throws Exception { - return AjaxResult.success(this.modelsService.getModelDetail(id, identifier, owner, version)); + public AjaxResult getModelDetail(@RequestParam("id") Integer id, @RequestParam("identifier") String identifier, @RequestParam("owner") String owner, @RequestParam("version") String version, @RequestParam("is_public") Boolean isPublic) throws Exception { + return AjaxResult.success(this.modelsService.getModelDetail(id, identifier, owner, version, isPublic)); } @GetMapping("/getModelDependencyTree") @@ -116,8 +115,8 @@ public class NewModelFromGitController { @DeleteMapping("/delete") @ApiOperation(value = "删除模型") public AjaxResult deleteModel(@RequestParam("id") Integer id, @RequestParam("identifier") String identifier, - @RequestParam("owner") String owner) throws Exception { - this.modelsService.deleteModel(id, identifier, owner); + @RequestParam("owner") String owner, @RequestParam("is_public") Boolean isPublic) throws Exception { + this.modelsService.deleteModel(id, identifier, owner, isPublic); return AjaxResult.success(); } diff --git a/ruoyi-modules/management-platform/src/main/java/com/ruoyi/platform/service/ModelsService.java b/ruoyi-modules/management-platform/src/main/java/com/ruoyi/platform/service/ModelsService.java index a346d050..d3d08871 100644 --- a/ruoyi-modules/management-platform/src/main/java/com/ruoyi/platform/service/ModelsService.java +++ b/ruoyi-modules/management-platform/src/main/java/com/ruoyi/platform/service/ModelsService.java @@ -95,7 +95,7 @@ public interface ModelsService { List> uploadModelLocal(MultipartFile[] files, String uuid) throws Exception; - ResponseEntity downloadAllModelFilesNew(String name, String identifier, Integer id, String version) throws IOException, Exception; + ResponseEntity downloadAllModelFilesNew(String name, String identifier, Integer id, String version, Boolean isPublic) throws IOException, Exception; Page newPubilcQueryByPage(ModelsVo modelsVo, PageRequest pageRequest) throws Exception; @@ -107,11 +107,11 @@ public interface ModelsService { List> getVersionList(String identifier, String owner) throws Exception; - ModelsVo getModelDetail(Integer id, String identifier, String owner, String version) throws Exception; + ModelsVo getModelDetail(Integer id, String identifier, String owner, String version, Boolean isPublic) throws Exception; ModelDependency1TreeVo getModelDependencyTree(Integer repoId, String identifier, String version) throws Exception; - void deleteModel(Integer repoId, String identifier, String owner) throws Exception; + void deleteModel(Integer repoId, String identifier, String owner, Boolean isPublic) throws Exception; void deleteVersion(Integer repoId, String identifier, String owner, String version, String relativePath) throws Exception; } diff --git a/ruoyi-modules/management-platform/src/main/java/com/ruoyi/platform/service/NewDatasetService.java b/ruoyi-modules/management-platform/src/main/java/com/ruoyi/platform/service/NewDatasetService.java index f43891ee..e1a2a516 100644 --- a/ruoyi-modules/management-platform/src/main/java/com/ruoyi/platform/service/NewDatasetService.java +++ b/ruoyi-modules/management-platform/src/main/java/com/ruoyi/platform/service/NewDatasetService.java @@ -16,18 +16,24 @@ public interface NewDatasetService { String newCreateDataset(NewDatasetVo datasetVo) throws Exception; + String newCreateVersion(NewDatasetVo datasetVo) throws Exception; + List> uploadDatasetlocal(MultipartFile[] files, String uuid) throws Exception; + ResponseEntity downloadDatasetlocal(String filePath) throws Exception; - ResponseEntity downloadAllDatasetFilesNew(String name, String identifier, Integer id, String version) throws IOException, Exception; + + ResponseEntity downloadAllDatasetFilesNew(String name, String identifier, Integer id, String version, Boolean isPublic) throws IOException, Exception; + Page newPersonalQueryByPage(Dataset dataset, PageRequest pageRequest) throws Exception; - Page newPubilcQueryByPage(Dataset dataset, PageRequest pageRequest) throws Exception; - NewDatasetVo getNewDatasetDesc(Integer repoId,String name,String repo, String owner, String version)throws Exception; + Page newPubilcQueryByPage(Dataset dataset, PageRequest pageRequest) throws Exception; + + NewDatasetVo getNewDatasetDesc(Integer repoId, String name, String repo, String owner, String version, Boolean isPublic) throws Exception; - List> getVersionList(String repo, String owner) throws Exception; + List> getVersionList(String repo, String owner) throws Exception; - void deleteDatasetNew(Integer repoId, String repo, String owner) throws Exception; + void deleteDatasetNew(Integer repoId, String repo, String owner, Boolean isPublic) throws Exception; void deleteDatasetVersionNew(String repo, String owner, String version, String relativePath) throws Exception; } diff --git a/ruoyi-modules/management-platform/src/main/java/com/ruoyi/platform/service/impl/ModelsServiceImpl.java b/ruoyi-modules/management-platform/src/main/java/com/ruoyi/platform/service/impl/ModelsServiceImpl.java index a39905a4..e9f56899 100644 --- a/ruoyi-modules/management-platform/src/main/java/com/ruoyi/platform/service/impl/ModelsServiceImpl.java +++ b/ruoyi-modules/management-platform/src/main/java/com/ruoyi/platform/service/impl/ModelsServiceImpl.java @@ -561,10 +561,10 @@ public class ModelsServiceImpl implements ModelsService { String ci4sUsername = loginUser.getUsername(); String gitLinkUsername = loginUser.getSysUser().getGitLinkUsername(); String gitLinkPassword = loginUser.getSysUser().getGitLinkPassword(); - Map userInfo = getUserInfo(ci4sUsername, gitLinkUsername, gitLinkPassword); Integer userId = (Integer) userInfo.get("user_id"); + ci4sUsername = modelsVo.getIsPublic() ? Constant.Item_Public : ci4sUsername; String repositoryName = modelsVo.getIdentifier() == null ? ci4sUsername + "_model_" + DateUtils.dateTimeNow() : modelsVo.getIdentifier(); ModelDependency1 modelDependency = new ModelDependency1(); List oldModelDependencys = modelDependency1Dao.queryModelDependency(modelsVo.getName(), null, gitLinkUsername); @@ -650,11 +650,11 @@ public class ModelsServiceImpl implements ModelsService { modelDependency.setState(Constant.State_valid); modelDependency1Dao.insert(modelDependency); + String s3Path = bucketName + "/mini-model-management-platform-files/" + ci4sUsername + "/model/" + gitlinIid + "/" + repositoryName + "/" + branchName; CompletableFuture.supplyAsync(() -> { try { dvcUtils.dvcInit(rootPath); // 配置远程S3地址 - String s3Path = bucketName + "/mini-model-management-platform-files/" + ci4sUsername + "/model/" + gitlinIid + "/" + repositoryName + "/" + branchName; dvcUtils.dvcRemoteAdd(rootPath, s3Path); dvcUtils.dvcConfigS3Credentials(rootPath, endpoint); dvcUtils.dvcConfigS3Credentials2(rootPath, accessKeyId); @@ -663,7 +663,7 @@ public class ModelsServiceImpl implements ModelsService { // dvc 跟踪 dvcUtils.dvcAdd(rootPath, "model"); // git commit - dvcUtils.gitCommit(rootPath, "commit from ci4s with " + ci4sUsername); + dvcUtils.gitCommit(rootPath, "commit from ci4s with " + loginUser.getUsername()); dvcUtils.gitPush(rootPath, gitLinkUsername, gitLinkPassword); dvcUtils.dvcPush(rootPath); } catch (Exception e) { @@ -686,9 +686,9 @@ public class ModelsServiceImpl implements ModelsService { String ci4sUsername = loginUser.getUsername(); String gitLinkUsername = loginUser.getSysUser().getGitLinkUsername(); String gitLinkPassword = loginUser.getSysUser().getGitLinkPassword(); - Map userInfo = getUserInfo(ci4sUsername, gitLinkUsername, gitLinkPassword); + ci4sUsername = modelsVo.getIsPublic() ? Constant.Item_Public : loginUser.getUsername(); String repositoryName = modelsVo.getIdentifier() == null ? ci4sUsername + "_model_" + DateUtils.dateTimeNow() : modelsVo.getIdentifier(); ModelDependency1 modelDependency = new ModelDependency1(); List oldModelDependencys = modelDependency1Dao.queryModelDependency(modelsVo.getName(), modelsVo.getId(), gitLinkUsername); @@ -879,10 +879,9 @@ public class ModelsServiceImpl implements ModelsService { } @Override - public ResponseEntity downloadAllModelFilesNew(String name, String identifier, Integer id, String version) throws Exception { + public ResponseEntity downloadAllModelFilesNew(String name, String identifier, Integer id, String version, Boolean isPublic) throws Exception { // 命令行操作 git clone 项目地址 - LoginUser loginUser = SecurityUtils.getLoginUser(); - String ci4sUsername = loginUser.getUsername(); + String ci4sUsername = isPublic ? Constant.Item_Public : SecurityUtils.getLoginUser().getUsername(); String localPath1 = localPath + ci4sUsername + "/model/" + id + "/" + identifier + "/" + version; // 打包 data 文件夹 @@ -1090,9 +1089,8 @@ public class ModelsServiceImpl implements ModelsService { } @Override - public ModelsVo getModelDetail(Integer id, String identifier, String owner, String version) throws Exception { - LoginUser loginUser = SecurityUtils.getLoginUser(); - String ci4sUsername = loginUser.getUsername(); + public ModelsVo getModelDetail(Integer id, String identifier, String owner, String version, Boolean isPublic) throws Exception { + String ci4sUsername = isPublic ? Constant.Item_Public : SecurityUtils.getLoginUser().getUsername(); if (StringUtils.isEmpty(version)) { List> versionList = this.getVersionList(identifier, owner); @@ -1126,7 +1124,7 @@ public class ModelsServiceImpl implements ModelsService { } @Override - public ModelDependency1TreeVo getModelDependencyTree(Integer repoId, String identifier, String version) throws Exception { + public ModelDependency1TreeVo getModelDependencyTree(Integer repoId, String identifier, String version) { ModelDependency1 modelDependency1 = modelDependency1Dao.queryByRepoAndVersion(repoId, identifier, version); ModelDependency1TreeVo modelDependency1TreeVo = new ModelDependency1TreeVo(); BeanUtils.copyProperties(modelDependency1, modelDependency1TreeVo); @@ -1143,7 +1141,7 @@ public class ModelsServiceImpl implements ModelsService { } @Override - public void deleteModel(Integer repoId, String identifier, String owner) throws Exception { + public void deleteModel(Integer repoId, String identifier, String owner, Boolean isPublic) throws Exception { String token = gitService.checkoutToken(); gitService.deleteProject(token, owner, identifier); //删除模型依赖 @@ -1155,8 +1153,7 @@ public class ModelsServiceImpl implements ModelsService { String parentModel = JSON.toJSONString(map); modelDependency1Dao.deleteModelDependency(parentModel); - LoginUser loginUser = SecurityUtils.getLoginUser(); - String ci4sUsername = loginUser.getUsername(); + String ci4sUsername = isPublic ? Constant.Item_Public : SecurityUtils.getLoginUser().getUsername(); dvcUtils.deleteDirectory(localPath + "/" + ci4sUsername + "/model/" + repoId + "/" + identifier); } diff --git a/ruoyi-modules/management-platform/src/main/java/com/ruoyi/platform/service/impl/NewDatasetServiceImpl.java b/ruoyi-modules/management-platform/src/main/java/com/ruoyi/platform/service/impl/NewDatasetServiceImpl.java index 6c2b8c6b..177e2ac7 100644 --- a/ruoyi-modules/management-platform/src/main/java/com/ruoyi/platform/service/impl/NewDatasetServiceImpl.java +++ b/ruoyi-modules/management-platform/src/main/java/com/ruoyi/platform/service/impl/NewDatasetServiceImpl.java @@ -90,6 +90,7 @@ public class NewDatasetServiceImpl implements NewDatasetService { Map userInfo = JsonUtils.jsonToMap(userReq); Integer userId = (Integer) userInfo.get("user_id"); // 拼接project + ci4sUsername = datasetVo.getIsPublic() ? Constant.Item_Public : loginUser.getUsername(); String repositoryName = ci4sUsername + "_dataset_" + DateUtils.dateTimeNow(); GitProjectVo gitProjectVo = new GitProjectVo(); gitProjectVo.setRepositoryName(repositoryName); @@ -181,6 +182,7 @@ public class NewDatasetServiceImpl implements NewDatasetService { String gitLinkUsername = loginUser.getSysUser().getGitLinkUsername(); String gitLinkPassword = loginUser.getSysUser().getGitLinkPassword(); String userReq = jedis.get(ci4sUsername + "_gitUserInfo"); + ci4sUsername = datasetVo.getIsPublic() ? Constant.Item_Public : loginUser.getUsername(); Map userInfo = JsonUtils.jsonToMap(userReq); // 创建分支 String branchName = StringUtils.isEmpty(datasetVo.getVersion()) ? "master" : datasetVo.getVersion(); @@ -342,9 +344,9 @@ public class NewDatasetServiceImpl implements NewDatasetService { } @Override - public NewDatasetVo getNewDatasetDesc(Integer id, String name, String repo, String owner, String version) throws Exception { + public NewDatasetVo getNewDatasetDesc(Integer id, String name, String repo, String owner, String version, Boolean isPublic) throws Exception { LoginUser loginUser = SecurityUtils.getLoginUser(); - String ci4sUsername = loginUser.getUsername(); + String ci4sUsername = isPublic ? Constant.Item_Public : loginUser.getUsername(); // cd到 localPathlocal/id/下面还有一个文件夹,然后做git pull操作,然后读取里面的文件列表,列出每个文件的大小和名称,封装成MAP if (StringUtils.isEmpty(version)) { List> versionList = this.getVersionList(repo, owner); @@ -384,12 +386,12 @@ public class NewDatasetServiceImpl implements NewDatasetService { } @Override - public void deleteDatasetNew(Integer repoId, String repo, String owner) throws Exception { + public void deleteDatasetNew(Integer repoId, String repo, String owner, Boolean isPublic) throws Exception { String token = gitService.checkoutToken(); gitService.deleteProject(token, owner, repo); LoginUser loginUser = SecurityUtils.getLoginUser(); - String ci4sUsername = loginUser.getUsername(); + String ci4sUsername = isPublic ? Constant.Item_Public : loginUser.getUsername(); dvcUtils.deleteDirectory(localPathlocal + "/" + ci4sUsername + "/datasets/" + repoId + "/" + repo); } @@ -452,10 +454,10 @@ public class NewDatasetServiceImpl implements NewDatasetService { } @Override - public ResponseEntity downloadAllDatasetFilesNew(String name, String identifier, Integer id, String version) throws Exception { + public ResponseEntity downloadAllDatasetFilesNew(String name, String identifier, Integer id, String version, Boolean isPublic) throws Exception { // 命令行操作 git clone 项目地址 - LoginUser loginUser = SecurityUtils.getLoginUser(); - String localPath = localPathlocal + loginUser.getUsername() + "/datasets/" + id + "/" + identifier + "/" + version; + String ci4sUsername = isPublic ? Constant.Item_Public : SecurityUtils.getLoginUser().getUsername(); + String localPath = localPathlocal + ci4sUsername + "/datasets/" + id + "/" + identifier + "/" + version; // 打包 data 文件夹 String dataFolderPath = localPath + "/dataset"; From e3e85076efb5908e5e9b17a45721358ab33fa10b Mon Sep 17 00:00:00 2001 From: cp3hnu Date: Thu, 5 Dec 2024 14:18:40 +0800 Subject: [PATCH 56/59] =?UTF-8?q?chore:=20=E5=BA=94=E5=90=8E=E5=8F=B0?= =?UTF-8?q?=E8=A6=81=E6=B1=82=E6=B7=BB=E5=8A=A0is=5Fpublic=E5=8F=82?= =?UTF-8?q?=E6=95=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- react-ui/src/components/CodeSelect/index.tsx | 2 +- .../Dataset/components/ResourceInfo/index.tsx | 3 +++ .../Dataset/components/ResourceList/index.tsx | 4 ++-- .../components/ResourceVersion/index.tsx | 1 + react-ui/src/pages/System/User/edit.tsx | 5 +++++ react-ui/src/types.ts | 17 ++++++++++++++--- 6 files changed, 26 insertions(+), 6 deletions(-) diff --git a/react-ui/src/components/CodeSelect/index.tsx b/react-ui/src/components/CodeSelect/index.tsx index 03474c00..79401b25 100644 --- a/react-ui/src/components/CodeSelect/index.tsx +++ b/react-ui/src/components/CodeSelect/index.tsx @@ -1,7 +1,7 @@ /* * @Author: 赵伟 * @Date: 2024-10-08 15:36:08 - * @Description: 代码配置选择表单组件 + * @Description: 流水线选择代码配置表单 */ import KFIcon from '@/components/KFIcon'; diff --git a/react-ui/src/pages/Dataset/components/ResourceInfo/index.tsx b/react-ui/src/pages/Dataset/components/ResourceInfo/index.tsx index 159ba9f1..11e8eb10 100644 --- a/react-ui/src/pages/Dataset/components/ResourceInfo/index.tsx +++ b/react-ui/src/pages/Dataset/components/ResourceInfo/index.tsx @@ -47,6 +47,7 @@ const ResourceInfo = ({ resourceType }: ResourceInfoProps) => { const name = searchParams.get('name') || ''; const owner = searchParams.get('owner') || ''; const identifier = searchParams.get('identifier') || ''; + const is_public = searchParams.get('is_public') || ''; const [versionList, setVersionList] = useState([]); const [version, setVersion] = useState(undefined); const [activeTab, setActiveTab] = useState(defaultTab); @@ -66,6 +67,7 @@ const ResourceInfo = ({ resourceType }: ResourceInfoProps) => { name, identifier, version, + is_public: is_public === 'true', }); } }, [version]); @@ -77,6 +79,7 @@ const ResourceInfo = ({ resourceType }: ResourceInfoProps) => { id: number; identifier: string; version?: string; + is_public: boolean; }) => { const request = config.getInfo; const [res] = await to(request(params)); diff --git a/react-ui/src/pages/Dataset/components/ResourceList/index.tsx b/react-ui/src/pages/Dataset/components/ResourceList/index.tsx index 08546bc7..9e872aca 100644 --- a/react-ui/src/pages/Dataset/components/ResourceList/index.tsx +++ b/react-ui/src/pages/Dataset/components/ResourceList/index.tsx @@ -122,7 +122,7 @@ function ResourceList( modalConfirm({ title: config.deleteModalTitle, onOk: () => { - deleteRecord(pick(record, ['owner', 'identifier', 'id'])); + deleteRecord(pick(record, ['owner', 'identifier', 'id', 'is_public'])); }, }); }; @@ -138,7 +138,7 @@ function ResourceList( }); const prefix = config.prefix; navigate( - `/dataset/${prefix}/info/${record.id}?name=${record.name}&owner=${record.owner}&identifier=${record.identifier}`, + `/dataset/${prefix}/info/${record.id}?name=${record.name}&owner=${record.owner}&identifier=${record.identifier}&is_public=${record.is_public}`, ); }; diff --git a/react-ui/src/pages/Dataset/components/ResourceVersion/index.tsx b/react-ui/src/pages/Dataset/components/ResourceVersion/index.tsx index 44c54320..5e04ee17 100644 --- a/react-ui/src/pages/Dataset/components/ResourceVersion/index.tsx +++ b/react-ui/src/pages/Dataset/components/ResourceVersion/index.tsx @@ -28,6 +28,7 @@ function ResourceVersion({ resourceType, info }: ResourceVersionProps) { id: info.id, version: info.version, identifier: info.identifier, + is_public: info.is_public, }); }; diff --git a/react-ui/src/pages/System/User/edit.tsx b/react-ui/src/pages/System/User/edit.tsx index 4fde814d..8d24b539 100644 --- a/react-ui/src/pages/System/User/edit.tsx +++ b/react-ui/src/pages/System/User/edit.tsx @@ -199,6 +199,11 @@ const UserForm: React.FC = (props) => { { required: true, }, + { + pattern: /^[a-zA-Z0-9](?:[a-zA-Z0-9_.-]*[a-zA-Z0-9])?$/, + message: + '只能包含数字,字母,下划线(_),中横线(-),英文句号(.),且必须以数字或字母开头与结尾', + }, ]} /> = Omit & { [P in K]: NewType }; -// export type PascalCaseType = { -// [K in keyof T as `${Capitalize}`]: T[K]; -// } +// 将属性名称首字母大写 +export type PascalCaseType = { + [K in keyof T as `${Capitalize}`]: T[K]; +}; + +// 将属性名称下划线转换为驼峰 +type ToCamelCase = S extends `${infer P}_${infer R}` + ? `${P}${Capitalize>}` + : S; + +export type KeysToCamelCase = { + [K in keyof T as ToCamelCase]: T[K]; +}; // 序列化后的流水线节点 export type PipelineNodeModelSerialize = Omit< From 7e295dd2632850265a897780c9a10a5ee88b1adc Mon Sep 17 00:00:00 2001 From: cp3hnu Date: Thu, 5 Dec 2024 14:37:43 +0800 Subject: [PATCH 57/59] =?UTF-8?q?chore:=20=E5=BA=94=E5=90=8E=E5=8F=B0?= =?UTF-8?q?=E8=A6=81=E6=B1=82=E6=B7=BB=E5=8A=A0is=5Fpublic=E5=8F=82?= =?UTF-8?q?=E6=95=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../src/pages/Dataset/components/AddVersionModal/index.tsx | 3 +++ react-ui/src/pages/Dataset/components/ResourceInfo/index.tsx | 1 + 2 files changed, 4 insertions(+) diff --git a/react-ui/src/pages/Dataset/components/AddVersionModal/index.tsx b/react-ui/src/pages/Dataset/components/AddVersionModal/index.tsx index a91159fc..3218e306 100644 --- a/react-ui/src/pages/Dataset/components/AddVersionModal/index.tsx +++ b/react-ui/src/pages/Dataset/components/AddVersionModal/index.tsx @@ -23,6 +23,7 @@ interface AddVersionModalProps extends Omit { resourceId: number; identifier: string; resoureName: string; + is_public: boolean; onOk: () => void; } @@ -31,6 +32,7 @@ function AddVersionModal({ resourceId, resoureName, identifier, + is_public, onOk, ...rest }: AddVersionModalProps) { @@ -71,6 +73,7 @@ function AddVersionModal({ const params = { id: resourceId, identifier, + is_public, [config.filePropKey]: version_vos, ...omit(formData, 'fileList'), [config.sourceParamKey]: DataSource.Create, diff --git a/react-ui/src/pages/Dataset/components/ResourceInfo/index.tsx b/react-ui/src/pages/Dataset/components/ResourceInfo/index.tsx index 11e8eb10..106df228 100644 --- a/react-ui/src/pages/Dataset/components/ResourceInfo/index.tsx +++ b/react-ui/src/pages/Dataset/components/ResourceInfo/index.tsx @@ -120,6 +120,7 @@ const ResourceInfo = ({ resourceType }: ResourceInfoProps) => { resourceId: resourceId, resoureName: info.name, identifier: info.identifier, + is_public: info.is_public, onOk: () => { getVersionList(); close(); From 4baa29828395b0ce9b102ca2f604e81dd9fc9ec9 Mon Sep 17 00:00:00 2001 From: chenzhihang <709011834@qq.com> Date: Thu, 5 Dec 2024 15:40:29 +0800 Subject: [PATCH 58/59] =?UTF-8?q?=E4=BF=AE=E5=A4=8D=E5=88=9B=E5=BB=BA?= =?UTF-8?q?=E6=95=B0=E6=8D=AE=E9=9B=86=E6=A8=A1=E5=9E=8Bbug?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../src/main/java/com/ruoyi/platform/vo/ModelMetaVo.java | 2 ++ .../src/main/java/com/ruoyi/platform/vo/NewDatasetVo.java | 2 ++ 2 files changed, 4 insertions(+) diff --git a/ruoyi-modules/management-platform/src/main/java/com/ruoyi/platform/vo/ModelMetaVo.java b/ruoyi-modules/management-platform/src/main/java/com/ruoyi/platform/vo/ModelMetaVo.java index 2a5cc2f5..9e0b7591 100644 --- a/ruoyi-modules/management-platform/src/main/java/com/ruoyi/platform/vo/ModelMetaVo.java +++ b/ruoyi-modules/management-platform/src/main/java/com/ruoyi/platform/vo/ModelMetaVo.java @@ -1,5 +1,6 @@ package com.ruoyi.platform.vo; +import com.fasterxml.jackson.annotation.JsonInclude; import com.fasterxml.jackson.databind.PropertyNamingStrategy; import com.fasterxml.jackson.databind.annotation.JsonNaming; import com.ruoyi.platform.domain.dependencydomain.ProjectDepency; @@ -14,6 +15,7 @@ import java.util.List; @Data @JsonNaming(PropertyNamingStrategy.SnakeCaseStrategy.class) +@JsonInclude(JsonInclude.Include.NON_NULL) public class ModelMetaVo implements Serializable { private Integer id; diff --git a/ruoyi-modules/management-platform/src/main/java/com/ruoyi/platform/vo/NewDatasetVo.java b/ruoyi-modules/management-platform/src/main/java/com/ruoyi/platform/vo/NewDatasetVo.java index fb089000..1f84900f 100644 --- a/ruoyi-modules/management-platform/src/main/java/com/ruoyi/platform/vo/NewDatasetVo.java +++ b/ruoyi-modules/management-platform/src/main/java/com/ruoyi/platform/vo/NewDatasetVo.java @@ -1,5 +1,6 @@ package com.ruoyi.platform.vo; +import com.fasterxml.jackson.annotation.JsonInclude; import com.fasterxml.jackson.databind.PropertyNamingStrategy; import com.fasterxml.jackson.databind.annotation.JsonNaming; import io.swagger.annotations.ApiModelProperty; @@ -13,6 +14,7 @@ import java.util.Map; @Data @JsonNaming(PropertyNamingStrategy.SnakeCaseStrategy.class) +@JsonInclude(JsonInclude.Include.NON_NULL) public class NewDatasetVo implements Serializable { From eb871b72ebe8874d8fdeef429592efed2e94224e Mon Sep 17 00:00:00 2001 From: chenzhihang <709011834@qq.com> Date: Thu, 5 Dec 2024 15:58:43 +0800 Subject: [PATCH 59/59] =?UTF-8?q?=E4=BF=AE=E5=A4=8D=E5=88=9B=E5=BB=BA?= =?UTF-8?q?=E6=95=B0=E6=8D=AE=E9=9B=86=E6=A8=A1=E5=9E=8Bbug?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../ruoyi/platform/service/impl/ModelsServiceImpl.java | 8 ++++++-- .../platform/service/impl/NewDatasetServiceImpl.java | 8 ++++++-- 2 files changed, 12 insertions(+), 4 deletions(-) diff --git a/ruoyi-modules/management-platform/src/main/java/com/ruoyi/platform/service/impl/ModelsServiceImpl.java b/ruoyi-modules/management-platform/src/main/java/com/ruoyi/platform/service/impl/ModelsServiceImpl.java index e9f56899..e388cc04 100644 --- a/ruoyi-modules/management-platform/src/main/java/com/ruoyi/platform/service/impl/ModelsServiceImpl.java +++ b/ruoyi-modules/management-platform/src/main/java/com/ruoyi/platform/service/impl/ModelsServiceImpl.java @@ -605,8 +605,12 @@ public class ModelsServiceImpl implements ModelsService { gitService.createBranch(token, (String) userInfo.get("login"), repositoryName, branchName, "master"); // 定义标签 标签1:ci4s-model 标签2:ModelTag 标签3:ModelType gitService.createTopic(token, gitlinIid, "ci4s-model"); - gitService.createTopic(token, gitlinIid, "modeltag-" + modelsVo.getModelTag()); - gitService.createTopic(token, gitlinIid, "modeltype-" + modelsVo.getModelType()); + if (StringUtils.isNotEmpty(modelsVo.getModelTag())) { + gitService.createTopic(token, gitlinIid, "modeltag-" + modelsVo.getModelTag()); + } + if (StringUtils.isNotEmpty(modelsVo.getModelType())) { + gitService.createTopic(token, gitlinIid, "modeltype-" + modelsVo.getModelType()); + } dvcUtils.gitClone(rootPath, projectUrl, branchName, gitLinkUsername, gitLinkPassword); diff --git a/ruoyi-modules/management-platform/src/main/java/com/ruoyi/platform/service/impl/NewDatasetServiceImpl.java b/ruoyi-modules/management-platform/src/main/java/com/ruoyi/platform/service/impl/NewDatasetServiceImpl.java index 177e2ac7..44cd03f1 100644 --- a/ruoyi-modules/management-platform/src/main/java/com/ruoyi/platform/service/impl/NewDatasetServiceImpl.java +++ b/ruoyi-modules/management-platform/src/main/java/com/ruoyi/platform/service/impl/NewDatasetServiceImpl.java @@ -112,8 +112,12 @@ public class NewDatasetServiceImpl implements NewDatasetService { gitService.createBranch(token, (String) userInfo.get("login"), repositoryName, branchName, "master"); // 定义标签 标签1:ci4s-dataset 标签2:DataTag 标签3:DataType gitService.createTopic(token, gitlinIid, "ci4s-dataset"); - gitService.createTopic(token, gitlinIid, "datatag-" + datasetVo.getDataTag()); - gitService.createTopic(token, gitlinIid, "datatype-" + datasetVo.getDataType()); + if (StringUtils.isNotEmpty(datasetVo.getDataTag())) { + gitService.createTopic(token, gitlinIid, "datatag-" + datasetVo.getDataTag()); + } + if (StringUtils.isNotEmpty(datasetVo.getDataType())) { + gitService.createTopic(token, gitlinIid, "datatype-" + datasetVo.getDataType()); + } // 得到项目地址 String projectUrl = gitendpoint + "/" + owner + "/" + repositoryName + ".git";