diff --git a/react-ui/config/routes.ts b/react-ui/config/routes.ts
index bf012a65..a1bb3d0c 100644
--- a/react-ui/config/routes.ts
+++ b/react-ui/config/routes.ts
@@ -181,7 +181,7 @@ export default [
},
{
name: '实验实例详情',
- path: 'instance/:autoMLId/:id',
+ path: 'instance/:experimentId/:id',
component: './AutoML/Instance/index',
},
],
@@ -217,11 +217,47 @@ export default [
},
{
name: '实验实例详情',
- path: 'instance/:autoMLId/:id',
+ path: 'instance/:experimentId/:id',
component: './HyperParameter/Instance/index',
},
],
},
+ {
+ name: '主动学习',
+ path: 'active-learn',
+ routes: [
+ {
+ name: '超参数寻优',
+ path: '',
+ component: './ActiveLearn/List/index',
+ },
+ {
+ name: '实验详情',
+ path: 'info/:id',
+ component: './ActiveLearn/Info/index',
+ },
+ {
+ name: '创建实验',
+ path: 'create',
+ component: './ActiveLearn/Create/index',
+ },
+ {
+ name: '编辑实验',
+ path: 'edit/:id',
+ component: './ActiveLearn/Create/index',
+ },
+ {
+ name: '复制实验',
+ path: 'copy/:id',
+ component: './ActiveLearn/Create/index',
+ },
+ {
+ name: '实验实例详情',
+ path: 'instance/:experimentId/:id',
+ component: './ActiveLearn/Instance/index',
+ },
+ ],
+ },
],
},
{
diff --git a/react-ui/src/components/PageTitle/index.less b/react-ui/src/components/PageTitle/index.less
index 47907246..40913e00 100644
--- a/react-ui/src/components/PageTitle/index.less
+++ b/react-ui/src/components/PageTitle/index.less
@@ -7,4 +7,25 @@
background-repeat: no-repeat;
background-position: top center;
background-size: 100% 100%;
+
+ &__tips {
+ position: relative;
+ margin-left: 18px;
+ padding: 3px 15px;
+ color: @primary-color;
+ background: .addAlpha(@primary-color, 0.1) [];
+ 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.1) [];
+ border-bottom: 4px solid transparent;
+ content: '';
+ }
+ }
}
diff --git a/react-ui/src/components/PageTitle/index.tsx b/react-ui/src/components/PageTitle/index.tsx
index 2703e032..113f9b7b 100644
--- a/react-ui/src/components/PageTitle/index.tsx
+++ b/react-ui/src/components/PageTitle/index.tsx
@@ -3,6 +3,7 @@
* @Date: 2024-04-17 14:01:46
* @Description: 页面标题
*/
+import KFIcon from '@/components/KFIcon';
import classNames from 'classnames';
import React from 'react';
import './index.less';
@@ -10,19 +11,29 @@ import './index.less';
type PageTitleProps = {
/** 标题 */
title: React.ReactNode;
+ /** 图标 */
+ tooltip?: string;
/** 自定义类名 */
className?: string;
/** 自定义样式 */
style?: React.CSSProperties;
+ /** 自定义标题 */
+ titleRender?: () => React.ReactNode;
};
/**
* 页面标题
*/
-function PageTitle({ title, style, className = '' }: PageTitleProps) {
+function PageTitle({ title, style, className, tooltip }: PageTitleProps) {
return (
- {title}
+
{title}
+ {tooltip && (
+
+
+ {tooltip}
+
+ )}
);
}
diff --git a/react-ui/src/pages/ActiveLearn/Create/index.less b/react-ui/src/pages/ActiveLearn/Create/index.less
new file mode 100644
index 00000000..145be0d1
--- /dev/null
+++ b/react-ui/src/pages/ActiveLearn/Create/index.less
@@ -0,0 +1,55 @@
+.create-hyperparameter {
+ 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-variant-text:disabled {
+ color: @text-disabled-color;
+ }
+
+ .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/ActiveLearn/Create/index.tsx b/react-ui/src/pages/ActiveLearn/Create/index.tsx
new file mode 100644
index 00000000..8ad3f3b1
--- /dev/null
+++ b/react-ui/src/pages/ActiveLearn/Create/index.tsx
@@ -0,0 +1,142 @@
+/*
+ * @Author: 赵伟
+ * @Date: 2024-04-16 13:58:08
+ * @Description: 创建实验
+ */
+import PageTitle from '@/components/PageTitle';
+import { AutoMLTaskType } from '@/enums';
+import {
+ addActiveLearnReq,
+ getActiveLearnInfoReq,
+ updateActiveLearnReq,
+} from '@/services/activeLearn';
+import { safeInvoke } from '@/utils/functional';
+import { to } from '@/utils/promise';
+import { useLocation, useNavigate, useParams } from '@umijs/max';
+import { App, Button, Form } from 'antd';
+import { useEffect } from 'react';
+import BasicConfig from '../components/CreateForm/BasicConfig';
+import ExecuteConfig from '../components/CreateForm/ExecuteConfig';
+import { ActiveLearnData, FormData } from '../types';
+import styles from './index.less';
+
+function CreateActiveLearn() {
+ const navigate = useNavigate();
+ const [form] = Form.useForm();
+ const { message } = App.useApp();
+ const params = useParams();
+ const id = safeInvoke(Number)(params.id);
+ const { pathname } = useLocation();
+ const isCopy = pathname.includes('copy');
+
+ useEffect(() => {
+ // 编辑,复制
+ if (id && !Number.isNaN(id)) {
+ getActiveLearnInfo(id);
+ }
+ }, [id]);
+
+ // 获取服务详情
+ const getActiveLearnInfo = async (id: number) => {
+ const [res] = await to(getActiveLearnInfoReq({ id }));
+ if (res && res.data) {
+ const info: ActiveLearnData = res.data;
+ const { name: name_str, ...rest } = info;
+ const name = isCopy ? `${name_str}-copy` : name_str;
+ const formData = {
+ ...rest,
+ name,
+ };
+
+ form.setFieldsValue(formData);
+ }
+ };
+
+ // 创建、更新、复制实验
+ const createExperiment = async (formData: FormData) => {
+ // 根据后台要求,修改表单数据
+ const object = {
+ ...formData,
+ };
+
+ const params =
+ id && !isCopy
+ ? {
+ id: id,
+ ...object,
+ }
+ : object;
+
+ const request = id && !isCopy ? updateActiveLearnReq : addActiveLearnReq;
+ const [res] = await to(request(params));
+ if (res) {
+ message.success('操作成功');
+ navigate(-1);
+ }
+ };
+
+ // 提交
+ const handleSubmit = (values: FormData) => {
+ createExperiment(values);
+ };
+
+ // 取消
+ const cancel = () => {
+ navigate(-1);
+ };
+
+ let buttonText = '新建';
+ let title = '新建实验';
+ if (id) {
+ if (isCopy) {
+ title = '复制实验';
+ buttonText = '确定';
+ } else {
+ title = '编辑实验';
+ buttonText = '更新';
+ }
+ }
+
+ return (
+
+
+
+
+
+
+
+
+
+
+
+
+ );
+}
+
+export default CreateActiveLearn;
diff --git a/react-ui/src/pages/ActiveLearn/Info/index.less b/react-ui/src/pages/ActiveLearn/Info/index.less
new file mode 100644
index 00000000..e27756ef
--- /dev/null
+++ b/react-ui/src/pages/ActiveLearn/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/ActiveLearn/Info/index.tsx b/react-ui/src/pages/ActiveLearn/Info/index.tsx
new file mode 100644
index 00000000..0091de1f
--- /dev/null
+++ b/react-ui/src/pages/ActiveLearn/Info/index.tsx
@@ -0,0 +1,45 @@
+/*
+ * @Author: 赵伟
+ * @Date: 2024-04-16 13:58:08
+ * @Description: 主动学习实验详情
+ */
+import PageTitle from '@/components/PageTitle';
+import { getActiveLearnInfoReq } from '@/services/activeLearn';
+import { safeInvoke } from '@/utils/functional';
+import { to } from '@/utils/promise';
+import { useParams } from '@umijs/max';
+import { useEffect, useState } from 'react';
+import BasicInfo from '../components/BasicInfo';
+import { ActiveLearnData } from '../types';
+import styles from './index.less';
+
+function ActiveLearnInfo() {
+ const params = useParams();
+ const id = safeInvoke(Number)(params.id);
+ const [info, setInfo] = useState(undefined);
+
+ useEffect(() => {
+ if (id) {
+ getActiveLearnInfo();
+ }
+ }, []);
+
+ // 获取主动学习详情
+ const getActiveLearnInfo = async () => {
+ const [res] = await to(getActiveLearnInfoReq({ id: id }));
+ if (res && res.data) {
+ setInfo(res.data);
+ }
+ };
+
+ return (
+
+ );
+}
+
+export default ActiveLearnInfo;
diff --git a/react-ui/src/pages/ActiveLearn/Instance/index.less b/react-ui/src/pages/ActiveLearn/Instance/index.less
new file mode 100644
index 00000000..889faeb5
--- /dev/null
+++ b/react-ui/src/pages/ActiveLearn/Instance/index.less
@@ -0,0 +1,42 @@
+.auto-ml-instance {
+ height: 100%;
+
+ &__tabs {
+ 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;
+ }
+
+ &__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/ActiveLearn/Instance/index.tsx b/react-ui/src/pages/ActiveLearn/Instance/index.tsx
new file mode 100644
index 00000000..5079fc66
--- /dev/null
+++ b/react-ui/src/pages/ActiveLearn/Instance/index.tsx
@@ -0,0 +1,215 @@
+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, useRef, useState } from 'react';
+import BasicInfo from '../components/BasicInfo';
+import ExperimentHistory from '../components/ExperimentHistory';
+import ExperimentResult from '../components/ExperimentResult';
+import { AutoMLInstanceData, HyperparameterData } 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);
+ const evtSourceRef = useRef(null);
+
+ useEffect(() => {
+ if (instanceId) {
+ getExperimentInsInfo(false);
+ }
+ return () => {
+ closeSSE();
+ };
+ }, []);
+
+ // 获取实验实例详情
+ const getExperimentInsInfo = async (isStatusDetermined: boolean) => {
+ const [res] = await to(getExperimentInsReq(instanceId));
+ if (res && res.data) {
+ const info = res.data as AutoMLInstanceData;
+ const { param, node_status, argo_ins_name, argo_ins_ns, status } = info;
+ // 解析配置参数
+ const paramJson = parseJsonText(param);
+ if (paramJson) {
+ setAutoMLInfo(paramJson);
+ }
+
+ // 这个接口返回的状态有延时,SSE 返回的状态是最新的
+ // SSE 调用时,不需要解析 node_status, 也不要重新建立 SSE
+ if (isStatusDetermined) {
+ setInstanceInfo((prev) => ({
+ ...info,
+ nodeStatus: prev!.nodeStatus,
+ }));
+ return;
+ }
+
+ // 进行节点状态
+ const nodeStatusJson = parseJsonText(node_status);
+ if (nodeStatusJson) {
+ Object.keys(nodeStatusJson).forEach((key) => {
+ if (key.startsWith('auto-ml')) {
+ const value = nodeStatusJson[key];
+ info.nodeStatus = value;
+ }
+ });
+ }
+ setInstanceInfo(info);
+ // 运行中或者等待中,开启 SSE
+ if (status === ExperimentStatus.Pending || status === ExperimentStatus.Running) {
+ setupSSE(argo_ins_name, argo_ins_ns);
+ }
+ }
+ };
+
+ 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!,
+ nodeStatus: statusData,
+ }));
+
+ // 实验结束,关闭 SSE
+ if (
+ statusData.phase !== ExperimentStatus.Pending &&
+ statusData.phase !== ExperimentStatus.Running
+ ) {
+ closeSSE();
+ getExperimentInsInfo(true);
+ }
+ }
+ }
+ }
+ };
+ 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: '基本信息',
+ icon: ,
+ children: (
+
+ ),
+ },
+ {
+ key: TabKeys.Log,
+ label: '日志',
+ icon: ,
+ children: (
+
+ {instanceInfo && instanceInfo.nodeStatus && (
+
+ )}
+
+ ),
+ },
+ ];
+
+ const resultTabItems = [
+ {
+ key: TabKeys.Result,
+ label: '实验结果',
+ icon: ,
+ children: (
+
+ ),
+ },
+ {
+ key: TabKeys.History,
+ label: 'Trial 列表',
+ icon: ,
+ children: (
+
+ ),
+ },
+ ];
+
+ const tabItems =
+ instanceInfo?.status === ExperimentStatus.Succeeded
+ ? [...basicTabItems, ...resultTabItems]
+ : basicTabItems;
+
+ return (
+
+
+
+ );
+}
+
+export default AutoMLInstance;
diff --git a/react-ui/src/pages/ActiveLearn/List/index.tsx b/react-ui/src/pages/ActiveLearn/List/index.tsx
new file mode 100644
index 00000000..01e316bd
--- /dev/null
+++ b/react-ui/src/pages/ActiveLearn/List/index.tsx
@@ -0,0 +1,13 @@
+/*
+ * @Author: 赵伟
+ * @Date: 2024-04-16 13:58:08
+ * @Description: 超参数自动寻优
+ */
+
+import ExperimentList, { ExperimentListType } from '@/pages/AutoML/components/ExperimentList';
+
+function ActiveLearn() {
+ return ;
+}
+
+export default ActiveLearn;
diff --git a/react-ui/src/pages/ActiveLearn/components/BasicInfo/index.less b/react-ui/src/pages/ActiveLearn/components/BasicInfo/index.less
new file mode 100644
index 00000000..f365aa66
--- /dev/null
+++ b/react-ui/src/pages/ActiveLearn/components/BasicInfo/index.less
@@ -0,0 +1,13 @@
+.hyper-parameter-basic {
+ height: 100%;
+ padding: 20px @content-padding;
+ overflow-y: auto;
+ background-color: white;
+ border-radius: 10px;
+
+ :global {
+ .kf-basic-info__item__value__text {
+ white-space: pre;
+ }
+ }
+}
diff --git a/react-ui/src/pages/ActiveLearn/components/BasicInfo/index.tsx b/react-ui/src/pages/ActiveLearn/components/BasicInfo/index.tsx
new file mode 100644
index 00000000..6536b772
--- /dev/null
+++ b/react-ui/src/pages/ActiveLearn/components/BasicInfo/index.tsx
@@ -0,0 +1,278 @@
+import ConfigInfo, { type BasicInfoData } from '@/components/ConfigInfo';
+import { AutoMLTaskType, autoMLTaskTypeOptions } from '@/enums';
+import { useComputingResource } from '@/hooks/useComputingResource';
+import {
+ classifierAlgorithms,
+ FrameworkType,
+ frameworkTypeOptions,
+ queryStrategies,
+ regressorAlgorithms,
+} from '@/pages/ActiveLearn/components/CreateForm/utils';
+import { ActiveLearnData } from '@/pages/ActiveLearn/types';
+import { experimentStatusInfo } from '@/pages/Experiment/status';
+import { type NodeStatus } from '@/types';
+import { elapsedTime } from '@/utils/date';
+import {
+ formatBoolean,
+ formatCodeConfig,
+ formatDataset,
+ formatDate,
+ formatEnum,
+ formatMirror,
+ formatModel,
+} from '@/utils/format';
+import { Flex } from 'antd';
+import classNames from 'classnames';
+import { useMemo } from 'react';
+import styles from './index.less';
+
+type BasicInfoProps = {
+ info?: ActiveLearnData;
+ className?: string;
+ isInstance?: boolean;
+ runStatus?: NodeStatus;
+};
+
+function BasicInfo({ info, className, runStatus, isInstance = false }: BasicInfoProps) {
+ const getResourceDescription = useComputingResource()[1];
+ const basicDatas: BasicInfoData[] = useMemo(() => {
+ if (!info) {
+ return [];
+ }
+
+ return [
+ {
+ label: '实验名称',
+ value: info.name,
+ },
+ {
+ label: '实验描述',
+ value: info.description,
+ },
+ {
+ label: '创建人',
+ value: info.create_by,
+ },
+ {
+ label: '创建时间',
+ value: info.create_time,
+ format: formatDate,
+ },
+ {
+ label: '更新时间',
+ value: info.update_time,
+ format: formatDate,
+ },
+ ];
+ }, [info]);
+
+ const configDatas: BasicInfoData[] = useMemo(() => {
+ if (!info) {
+ return [];
+ }
+
+ const modelInfo = [
+ {
+ label: '模型',
+ value: info.model,
+ format: formatModel,
+ },
+ {
+ label: '模型文件路径',
+ value: info.model_py,
+ },
+ {
+ label: '模型类名称',
+ value: info.model_class_name,
+ },
+ ];
+
+ const lossInfo = [
+ {
+ label: 'loss文件路径',
+ value: info.loss_py,
+ },
+ {
+ label: 'loss类名',
+ value: info.loss_class_name,
+ },
+ ];
+
+ const algorithmInfo = [
+ {
+ label: info.task_type === AutoMLTaskType.Regression ? '回归算法' : '分类算法',
+ value:
+ info.task_type === AutoMLTaskType.Regression ? info.regressor_alg : info.classifier_alg,
+ format: formatEnum(
+ info.task_type === AutoMLTaskType.Regression ? regressorAlgorithms : classifierAlgorithms,
+ ),
+ },
+ ];
+
+ const diffInfo =
+ info.framework_type === FrameworkType.Pytorch
+ ? [...modelInfo, ...lossInfo]
+ : info.framework_type === FrameworkType.Keras
+ ? modelInfo
+ : algorithmInfo;
+
+ return [
+ {
+ label: '任务类型',
+ value: info.task_type,
+ format: formatEnum(autoMLTaskTypeOptions),
+ },
+ {
+ label: '框架类型',
+ value: info.framework_type,
+ format: formatEnum(frameworkTypeOptions),
+ },
+ ...diffInfo,
+ {
+ label: '代码配置',
+ value: info.code_config,
+ format: formatCodeConfig,
+ },
+ {
+ label: '数据集',
+ value: info.dataset,
+ format: formatDataset,
+ },
+ {
+ label: '数据集处理文件路径',
+ value: info.dataset_py,
+ },
+ {
+ label: '数据集类名',
+ value: info.dataset_class_name,
+ },
+ {
+ label: '镜像',
+ value: info.image,
+ format: formatMirror,
+ },
+ {
+ label: '资源规格',
+ value: info.computing_resource_id,
+ format: getResourceDescription,
+ },
+ {
+ label: '是否打乱',
+ value: info.shuffle,
+ format: formatBoolean,
+ },
+ {
+ label: '数据量',
+ value: info.data_size,
+ },
+ {
+ label: '训练集数据量',
+ value: info.train_size,
+ },
+ {
+ label: '初始训练数据量',
+ value: info.ninitial,
+ },
+ {
+ label: '查询次数',
+ value: info.nqueries,
+ },
+ {
+ label: '每次查询数据量',
+ value: info.ninstances,
+ },
+ {
+ label: '查询策略',
+ value: info.query_strategy,
+ format: formatEnum(queryStrategies),
+ },
+ {
+ label: '轮数',
+ value: info.ncheckpoint,
+ },
+ {
+ label: 'batch_size',
+ value: info.batch_size,
+ },
+ {
+ label: 'epochs',
+ value: info.epochs,
+ },
+ {
+ label: '学习率',
+ value: info.lr,
+ },
+ ];
+ }, [info, getResourceDescription]);
+
+ 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 (
+
+ {isInstance && runStatus && (
+
+ )}
+ {!isInstance && (
+
+ )}
+
+
+ );
+}
+
+export default BasicInfo;
diff --git a/react-ui/src/pages/ActiveLearn/components/CreateForm/BasicConfig.tsx b/react-ui/src/pages/ActiveLearn/components/CreateForm/BasicConfig.tsx
new file mode 100644
index 00000000..8829f12a
--- /dev/null
+++ b/react-ui/src/pages/ActiveLearn/components/CreateForm/BasicConfig.tsx
@@ -0,0 +1,54 @@
+import SubAreaTitle from '@/components/SubAreaTitle';
+import { Col, Form, Input, Row } from 'antd';
+
+function BasicConfig() {
+ return (
+ <>
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ >
+ );
+}
+
+export default BasicConfig;
diff --git a/react-ui/src/pages/ActiveLearn/components/CreateForm/ExecuteConfig.tsx b/react-ui/src/pages/ActiveLearn/components/CreateForm/ExecuteConfig.tsx
new file mode 100644
index 00000000..37d04ee9
--- /dev/null
+++ b/react-ui/src/pages/ActiveLearn/components/CreateForm/ExecuteConfig.tsx
@@ -0,0 +1,530 @@
+import CodeSelect from '@/components/CodeSelect';
+import ParameterSelect from '@/components/ParameterSelect';
+import ResourceSelect, {
+ requiredValidator,
+ ResourceSelectorType,
+} from '@/components/ResourceSelect';
+import SubAreaTitle from '@/components/SubAreaTitle';
+import { AutoMLTaskType, autoMLTaskTypeOptions } from '@/enums';
+import { Col, Form, Input, InputNumber, Radio, Row, Select, Switch } from 'antd';
+import {
+ classifierAlgorithms,
+ FrameworkType,
+ frameworkTypeOptions,
+ queryStrategies,
+ regressorAlgorithms,
+} from './utils';
+
+function ExecuteConfig() {
+ const form = Form.useFormInstance();
+ return (
+ <>
+
+
+
+
+
+ form.resetFields(['metrics'])}
+ >
+
+
+
+
+
+
+
+
+
+
+
+
+
+ {({ getFieldValue }) => {
+ const taskType = getFieldValue('task_type');
+ const frameworkType = getFieldValue('framework_type');
+ if (frameworkType === FrameworkType.Keras || frameworkType === FrameworkType.Pytorch) {
+ return (
+ <>
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ {frameworkType === FrameworkType.Pytorch ? (
+ <>
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ >
+ ) : null}
+ >
+ );
+ } else if (frameworkType === FrameworkType.Sklearn) {
+ if (taskType === AutoMLTaskType.Classification) {
+ return (
+ <>
+
+
+
+
+
+
+
+ >
+ );
+ } else {
+ return (
+ <>
+
+
+
+
+
+
+
+ >
+ );
+ }
+ } else {
+ return null;
+ }
+ }}
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ >
+ );
+}
+
+export default ExecuteConfig;
diff --git a/react-ui/src/pages/ActiveLearn/components/CreateForm/index.less b/react-ui/src/pages/ActiveLearn/components/CreateForm/index.less
new file mode 100644
index 00000000..06bbd5b7
--- /dev/null
+++ b/react-ui/src/pages/ActiveLearn/components/CreateForm/index.less
@@ -0,0 +1,145 @@
+.metrics-weight {
+ margin-bottom: 20px;
+
+ &:last-child {
+ margin-bottom: 0;
+ }
+}
+
+.add-weight {
+ margin-bottom: 0 !important;
+
+ // 增加样式权重
+ & &__button {
+ border-color: .addAlpha(@primary-color, 0.5) [];
+ box-shadow: none !important;
+ &:hover {
+ border-style: solid;
+ }
+ }
+}
+
+.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;
+ min-width: 0;
+ margin-right: 15px;
+
+ &::before {
+ display: inline-block;
+ color: @error-color;
+ font-size: 14px;
+ font-family: SimSun, sans-serif;
+ line-height: 1;
+ content: '*';
+ margin-inline-end: 4px;
+ }
+
+ :global {
+ .anticon-question-circle {
+ vertical-align: middle;
+ cursor: help;
+ }
+ }
+ }
+
+ &__tooltip {
+ max-width: 600px;
+ :global {
+ .ant-tooltip-inner {
+ max-height: 400px;
+ overflow-y: auto;
+ white-space: pre-line;
+
+ &::-webkit-scrollbar-thumb {
+ background: rgba(255, 255, 255, 0.5);
+ }
+ }
+ }
+ }
+
+ &__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;
+ min-width: 0;
+ margin-right: 15px;
+ margin-bottom: 0 !important;
+ }
+
+ &__operation {
+ display: flex;
+ flex: none;
+ align-items: center;
+ width: 100px;
+ height: 46px;
+ }
+ }
+
+ &__add {
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ padding: 15px 0;
+ }
+}
+
+.run-parameter {
+ width: calc(41.66% + 126px);
+ margin-bottom: 20px;
+
+ &__body {
+ flex: 1;
+ margin-right: 10px;
+ padding: 20px 20px 0;
+ border: 1px dashed #e0e0e0;
+ border-radius: 8px;
+
+ :global {
+ .ant-form-item-label {
+ label {
+ width: calc(100% - 10px);
+ }
+ }
+ }
+ }
+
+ &__operation {
+ display: flex;
+ flex: none;
+ align-items: center;
+ width: 100px;
+ }
+
+ &__error {
+ margin-top: -20px;
+ color: @error-color;
+ }
+}
diff --git a/react-ui/src/pages/ActiveLearn/components/CreateForm/utils.ts b/react-ui/src/pages/ActiveLearn/components/CreateForm/utils.ts
new file mode 100644
index 00000000..0090f5df
--- /dev/null
+++ b/react-ui/src/pages/ActiveLearn/components/CreateForm/utils.ts
@@ -0,0 +1,92 @@
+// 分类算法
+export const classifierAlgorithms = [
+ {
+ label: 'logistic_regression(逻辑回归)',
+ value: 'logistic_regression',
+ },
+ {
+ label: 'decision_tree(决策树)',
+ value: 'decision_tree',
+ },
+ {
+ label: 'random_forest(随机森林)',
+ value: 'random_forest',
+ },
+ {
+ label: 'SVM(支持向量机)',
+ value: 'SVM',
+ },
+ {
+ label: 'naive_bayes(朴素贝叶斯)',
+ value: 'naive_bayes',
+ },
+ {
+ label: 'GBM(梯度提升树)',
+ value: 'GBM',
+ },
+];
+
+// 回归算法
+export const regressorAlgorithms = [
+ {
+ label: 'bayesian_ridge(岭回归)',
+ value: 'bayesian_ridge',
+ },
+ {
+ label: 'ARD_regression(自动相关性确定回归)',
+ value: 'ARD_regression',
+ },
+ {
+ label: 'gaussian_process(高斯回归)',
+ value: 'gaussian_process',
+ }
+];
+
+// 框架类型
+export enum FrameworkType {
+ Sklearn = 'sklearn',
+ Keras = 'keras',
+ Pytorch = 'pytorch',
+}
+
+// 框架类型选项
+export const frameworkTypeOptions = [
+ {
+ label: FrameworkType.Sklearn,
+ value: FrameworkType.Sklearn,
+ },
+ {
+ label: FrameworkType.Keras,
+ value: FrameworkType.Keras,
+ },
+ {
+ label: FrameworkType.Pytorch,
+ value: FrameworkType.Pytorch,
+ },
+];
+
+
+// 查询策略
+export const queryStrategies = [
+ {
+ label: 'uncertainty_sampling',
+ value: 'uncertainty_sampling',
+ },
+ {
+ label: 'uncertainty_batch_sampling',
+ value: 'uncertainty_batch_sampling',
+ },
+ {
+ label: 'max_std_sampling',
+ value: 'max_std_sampling',
+ },
+ {
+ label: 'expected_improvement',
+ value: 'expected_improvement',
+ },
+ {
+ label: 'upper_confidence_bound',
+ value: 'upper_confidence_bound',
+ }
+];
+
diff --git a/react-ui/src/pages/ActiveLearn/components/ExperimentHistory/index.less b/react-ui/src/pages/ActiveLearn/components/ExperimentHistory/index.less
new file mode 100644
index 00000000..beac2a8a
--- /dev/null
+++ b/react-ui/src/pages/ActiveLearn/components/ExperimentHistory/index.less
@@ -0,0 +1,14 @@
+.experiment-history {
+ height: calc(100% - 10px);
+ margin-top: 10px;
+ &__content {
+ height: 100%;
+ padding: 20px @content-padding;
+ background-color: white;
+ border-radius: 10px;
+
+ &__table {
+ height: 100%;
+ }
+ }
+}
diff --git a/react-ui/src/pages/ActiveLearn/components/ExperimentHistory/index.tsx b/react-ui/src/pages/ActiveLearn/components/ExperimentHistory/index.tsx
new file mode 100644
index 00000000..e95ccd42
--- /dev/null
+++ b/react-ui/src/pages/ActiveLearn/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/ActiveLearn/components/ExperimentLog/index.less b/react-ui/src/pages/ActiveLearn/components/ExperimentLog/index.less
new file mode 100644
index 00000000..e69de29b
diff --git a/react-ui/src/pages/ActiveLearn/components/ExperimentLog/index.tsx b/react-ui/src/pages/ActiveLearn/components/ExperimentLog/index.tsx
new file mode 100644
index 00000000..e69de29b
diff --git a/react-ui/src/pages/ActiveLearn/components/ExperimentResult/index.less b/react-ui/src/pages/ActiveLearn/components/ExperimentResult/index.less
new file mode 100644
index 00000000..342817c3
--- /dev/null
+++ b/react-ui/src/pages/ActiveLearn/components/ExperimentResult/index.less
@@ -0,0 +1,52 @@
+.experiment-result {
+ height: calc(100% - 10px);
+ margin-top: 10px;
+ padding: 20px @content-padding;
+ overflow-y: auto;
+ background-color: white;
+ border-radius: 10px;
+
+ &__download {
+ padding-top: 16px;
+ padding-bottom: 16px;
+
+ padding-left: @content-padding;
+ color: @text-color;
+ font-size: 13px;
+ background-color: #f8f8f9;
+ border-radius: 4px;
+
+ &__btn {
+ display: block;
+ height: 36px;
+ margin-top: 15px;
+ font-size: 14px;
+ }
+ }
+
+ &__text {
+ white-space: pre-wrap;
+ }
+
+ &__images {
+ display: flex;
+ align-items: flex-start;
+ width: 100%;
+ overflow-x: auto;
+
+ :global {
+ .ant-image {
+ margin-right: 20px;
+
+ &:last-child {
+ margin-right: 0;
+ }
+ }
+ }
+
+ &__item {
+ height: 248px;
+ border: 1px solid rgba(96, 107, 122, 0.3);
+ }
+ }
+}
diff --git a/react-ui/src/pages/ActiveLearn/components/ExperimentResult/index.tsx b/react-ui/src/pages/ActiveLearn/components/ExperimentResult/index.tsx
new file mode 100644
index 00000000..a826155d
--- /dev/null
+++ b/react-ui/src/pages/ActiveLearn/components/ExperimentResult/index.tsx
@@ -0,0 +1,83 @@
+import InfoGroup from '@/components/InfoGroup';
+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, modelPath }: 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}
+
+
+
+
+ console.log(`current index: ${current}, prev index: ${prev}`),
+ }}
+ >
+ {images.map((item) => (
+
+ ))}
+
+
+
+ {modelPath && (
+
+ 文件名
+ save_model.joblib
+
+
+ )}
+
+ );
+}
+
+export default ExperimentResult;
diff --git a/react-ui/src/pages/ActiveLearn/types.ts b/react-ui/src/pages/ActiveLearn/types.ts
new file mode 100644
index 00000000..acccdd94
--- /dev/null
+++ b/react-ui/src/pages/ActiveLearn/types.ts
@@ -0,0 +1,85 @@
+/*
+ * @Author: error: error: git config user.name & please set dead value or install git && error: git config user.email & please set dead value or install git & please set dead value or install git
+ * @Date: 2025-04-18 08:40:03
+ * @LastEditors: error: error: git config user.name & please set dead value or install git && error: git config user.email & please set dead value or install git & please set dead value or install git
+ * @LastEditTime: 2025-04-18 11:30:21
+ * @FilePath: \ci4s\react-ui\src\pages\ActiveLearn\types.ts
+ * @Description: 这是默认设置,请设置`customMade`, 打开koroFileHeader查看配置 进行设置: https://github.com/OBKoro1/koro1FileHeader/wiki/%E9%85%8D%E7%BD%AE
+ */
+import { type ParameterInputObject } from '@/components/ResourceSelect';
+import { type NodeStatus } from '@/types';
+import { AutoMLTaskType } from '@/enums';
+
+// 操作类型
+export enum OperationType {
+ Create = 'Create', // 创建
+ Update = 'Update', // 更新
+}
+
+// 表单数据
+export type FormData = {
+ name: string; // 实验名称
+ description: string; // 实验描述
+ task_type: AutoMLTaskType; // 任务类型
+ framework_type: string; // 框架类型
+ code_config: ParameterInputObject; // 代码配置
+ model?: ParameterInputObject; // 模型
+ model_py?: string; // 模型文件路径
+ model_class_name?: string; // 模型类名称
+ loss_py?: string; // loss文件路径
+ loss_class_name?: string; // loss类名
+ classifier_alg?: string; // 分类算法
+ regressor_alg?: string; // 回归算法
+ dataset: ParameterInputObject; // 数据集
+ dataset_py: string; // dataset处理文件路径
+ dataset_class_name: string; // dataset类名
+ data_size: number; // 数据量
+ train_size: number; // 训练集数据量
+ ninitial: number; // 初始训练数据量
+ nqueries: number; // 查询次数
+ ninstances: number; // 每次查询数据量
+ computing_resource_id: number; // 资源规格
+ image: ParameterInputObject; // 镜像
+ shuffle: boolean; // 是否随机打乱
+ query_strategy: string; // 查询策略
+ ncheckpoint: number; // 多少轮查询保存一次模型参数
+ batch_size: number; // batch_size
+ epochs: number; // epochs
+ lr: number; // 学习率
+
+};
+
+// 主动学习
+export type ActiveLearnData = {
+ id: number;
+ progress: number;
+ run_state: string;
+ state: number;
+ create_by?: string;
+ create_time?: string;
+ update_by?: string;
+ update_time?: string;
+ status_list: string; // 最近五次运行状态
+} & FormData;
+
+// 主动学习实验实例
+export type ActiveLearnInstanceData = {
+ 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?: NodeStatus;
+};
diff --git a/react-ui/src/pages/AutoML/components/ExperimentList/config.ts b/react-ui/src/pages/AutoML/components/ExperimentList/config.ts
index 0a748520..adbd2789 100644
--- a/react-ui/src/pages/AutoML/components/ExperimentList/config.ts
+++ b/react-ui/src/pages/AutoML/components/ExperimentList/config.ts
@@ -4,6 +4,15 @@
* @Description: 实验列表组件配置
*/
+import {
+ batchDeleteActiveLearnInsReq,
+ deleteActiveLearnInsReq,
+ deleteActiveLearnReq,
+ getActiveLearnInsListReq,
+ getActiveLearnListReq,
+ runActiveLearnReq,
+ stopActiveLearnInsReq,
+} from '@/services/activeLearn';
import {
batchDeleteExperimentInsReq,
deleteAutoMLReq,
@@ -26,6 +35,7 @@ import {
export enum ExperimentListType {
AutoML = 'AutoML',
HyperParameter = 'HyperParameter',
+ ActiveLearn = 'ActiveLearn',
}
type ExperimentListInfo = {
@@ -72,4 +82,18 @@ export const experimentListConfig: Record = {
page: pagination.current! - 1,
size: pagination.pageSize,
- ml_name: searchText || undefined,
+ [config.nameProperty]: searchText || undefined,
};
const request = config.getListReq;
const [res] = await to(request(params));
@@ -248,7 +248,7 @@ function ExperimentList({ type }: ExperimentListProps) {
{
title: '实验名称',
dataIndex: config.nameProperty,
- key: 'ml_name',
+ key: 'name',
width: '16%',
render: tableCellRender(false, TableCellValueType.Link, {
onClick: gotoDetail,
@@ -257,7 +257,7 @@ function ExperimentList({ type }: ExperimentListProps) {
{
title: '实验描述',
dataIndex: config.descProperty,
- key: 'ml_description',
+ key: 'description',
render: tableCellRender(true),
},
{
diff --git a/react-ui/src/services/activeLearn/index.js b/react-ui/src/services/activeLearn/index.js
new file mode 100644
index 00000000..25aeb5cf
--- /dev/null
+++ b/react-ui/src/services/activeLearn/index.js
@@ -0,0 +1,93 @@
+/*
+ * @Author: 赵伟
+ * @Date: 2024-11-18 10:18:27
+ * @Description: 主动学习
+ */
+
+import { request } from '@umijs/max';
+
+
+// 分页查询超参数自动寻优
+export function getActiveLearnListReq(params) {
+ return request(`/api/mmp/activeLearn`, {
+ method: 'GET',
+ params,
+ });
+}
+
+// 查询超参数自动寻优详情
+export function getActiveLearnInfoReq(params) {
+ return request(`/api/mmp/activeLearn/getActiveLearnDetail`, {
+ method: 'GET',
+ params,
+ });
+}
+
+// 新增超参数自动寻优
+export function addActiveLearnReq(data) {
+ return request(`/api/mmp/activeLearn`, {
+ method: 'POST',
+ data,
+ });
+}
+
+// 编辑超参数自动寻优
+export function updateActiveLearnReq(data) {
+ return request(`/api/mmp/activeLearn`, {
+ method: 'PUT',
+ data,
+ });
+}
+
+// 删除超参数自动寻优
+export function deleteActiveLearnReq(id) {
+ return request(`/api/mmp/activeLearn/${id}`, {
+ method: 'DELETE',
+ });
+}
+
+// 运行超参数自动寻优
+export function runActiveLearnReq(id) {
+ return request(`/api/mmp/activeLearn/run/${id}`, {
+ method: 'POST',
+ });
+}
+
+// ----------------------- 实验实例 -----------------------
+// 获取实验实例列表
+export function getActiveLearnInsListReq(params) {
+ return request(`/api/mmp/activeLearnIns`, {
+ method: 'GET',
+ params,
+ });
+}
+
+// 查询实验实例详情
+export function getActiveLearnInsReq(id) {
+ return request(`/api/mmp/activeLearnIns/${id}`, {
+ method: 'GET',
+ });
+}
+
+// 停止实验实例
+export function stopActiveLearnInsReq(id) {
+ return request(`/api/mmp/activeLearnIns/${id}`, {
+ method: 'PUT',
+ });
+}
+
+// 删除实验实例
+export function deleteActiveLearnInsReq(id) {
+ return request(`/api/mmp/activeLearnIns/${id}`, {
+ method: 'DELETE',
+ });
+}
+
+// 批量删除实验实例
+export function batchDeleteActiveLearnInsReq(data) {
+ return request(`/api/mmp/activeLearnIns/batchDelete`, {
+ method: 'DELETE',
+ data
+ });
+}
+
diff --git a/react-ui/src/stories/PageTitle.stories.tsx b/react-ui/src/stories/PageTitle.stories.tsx
index b8eb31c7..b3e0ae94 100644
--- a/react-ui/src/stories/PageTitle.stories.tsx
+++ b/react-ui/src/stories/PageTitle.stories.tsx
@@ -1,5 +1,6 @@
import PageTitle from '@/components/PageTitle';
import type { Meta, StoryObj } from '@storybook/react';
+import { Tabs } from 'antd';
// More on how to set up stories at: https://storybook.js.org/docs/writing-stories#default-export
const meta = {
@@ -28,3 +29,25 @@ export const Primary: Story = {
title: '数据集列表',
},
};
+
+/* 带有提示信息 */
+export const WithTooltip: Story = {
+ args: {
+ title: '数据集列表',
+ tooltip: '其它提示',
+ },
+};
+
+/* Title 可以是 ReactNode */
+export const Custom: Story = {
+ args: {
+ title: (
+
+ ),
+ },
+};