diff --git a/react-ui/config/proxy.ts b/react-ui/config/proxy.ts index 93f990c1..1a92ab83 100644 --- a/react-ui/config/proxy.ts +++ b/react-ui/config/proxy.ts @@ -20,13 +20,13 @@ export default { // localhost:8000/api/** -> https://preview.pro.ant.design/api/** '/api/': { // 要代理的地址 - target: 'http://172.20.32.181:31213', // 开发环境 - // target: 'http://172.20.32.98:8082', + // target: 'http://172.20.32.181:31213', // 开发环境 + target: 'http://172.168.15.80:8082', // target: 'http://172.20.32.150:8082', // 配置了这个可以从 http 代理到 https // 依赖 origin 的功能可能需要这个,比如 cookie changeOrigin: true, - // pathRewrite: { '^/api': '' }, + pathRewrite: { '^/api': '' }, }, '/profile/avatar/': { target: 'http://172.20.32.185:31213', diff --git a/react-ui/config/routes.ts b/react-ui/config/routes.ts index bf012a65..e64318e0 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', + }, + ], + }, ], }, { @@ -525,6 +561,18 @@ export default [ }, ], }, + { + name: '知识图谱', + path: '/knowledge', + routes: [ + { + name: '知识图谱', + path: '', + key: 'knowledge', + component: './Knowledge/index', + }, + ], + }, { path: '*', layout: false, diff --git a/react-ui/package.json b/react-ui/package.json index c614d920..21710bca 100644 --- a/react-ui/package.json +++ b/react-ui/package.json @@ -140,7 +140,7 @@ "umi-presets-pro": "^2.0.0" }, "engines": { - "node": ">=16.14.0" + "node": ">=18.18.0" }, "create-umi": { "ignoreScript": [ diff --git a/react-ui/src/app.tsx b/react-ui/src/app.tsx index 6ef5c5b8..ac8b8652 100644 --- a/react-ui/src/app.tsx +++ b/react-ui/src/app.tsx @@ -25,7 +25,7 @@ export { requestConfig as request } from './requestConfig'; /** * @see https://umijs.org/zh-CN/plugins/plugin-initial-state - * */ + */ export async function getInitialState(): Promise { const fetchUserInfo = async () => { try { diff --git a/react-ui/src/components/IFramePage/index.tsx b/react-ui/src/components/IFramePage/index.tsx index cd08c7b7..72af2712 100644 --- a/react-ui/src/components/IFramePage/index.tsx +++ b/react-ui/src/components/IFramePage/index.tsx @@ -15,6 +15,7 @@ export enum IframePageType { DevEnv = 'DevEnv', // 开发环境 GitLink = 'GitLink', // git link Aim = 'Aim', // 实验对比 + Knowledge = 'Knowledge', // 知识图谱 } const getRequestAPI = (type: IframePageType): (() => Promise) => { @@ -37,6 +38,10 @@ const getRequestAPI = (type: IframePageType): (() => Promise) => { code: 200, data: SessionStorage.getItem(SessionStorage.aimUrlKey) || '', }); + case IframePageType.Knowledge: { // 知识图谱 + const { origin } = location; + return () => Promise.resolve({ code: 200, data: `http://${origin}:32701` }); + } } }; @@ -58,9 +63,12 @@ function IframePage({ type, openInTab = false, className, style }: IframePagePro useEffect(() => { const requestIframeUrl = async () => { + setLoading(true); const [res] = await to(getRequestAPI(type)()); if (res && res.data) { setIframeUrl(res.data); + } else { + setLoading(false); } }; diff --git a/react-ui/src/components/KFSpin/index.less b/react-ui/src/components/KFSpin/index.less index 753154d7..7d532d2d 100644 --- a/react-ui/src/components/KFSpin/index.less +++ b/react-ui/src/components/KFSpin/index.less @@ -4,7 +4,7 @@ right: 0; bottom: 0; left: 0; - z-index: 1001; + z-index: 1001; // 设置大于 Modal 的 z-index display: flex; flex-direction: column; align-items: center; 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/components/ResourceSelectorModal/index.less b/react-ui/src/components/ResourceSelectorModal/index.less index 1581e510..ac24b43f 100644 --- a/react-ui/src/components/ResourceSelectorModal/index.less +++ b/react-ui/src/components/ResourceSelectorModal/index.less @@ -26,6 +26,12 @@ border: 1px solid rgba(22, 100, 255, 0.3); border-radius: 8px; + :global { + .ant-tree-list-holder { + overflow-x: hidden; + } + } + &__search { margin-bottom: 14px; padding-left: 0; diff --git a/react-ui/src/components/RightContent/AvatarDropdown.tsx b/react-ui/src/components/RightContent/AvatarDropdown.tsx index fc1fceeb..ce90efb5 100644 --- a/react-ui/src/components/RightContent/AvatarDropdown.tsx +++ b/react-ui/src/components/RightContent/AvatarDropdown.tsx @@ -2,6 +2,7 @@ import { clearSessionToken } from '@/access'; import { setRemoteMenu } from '@/services/session'; import { logout } from '@/services/system/auth'; import { ClientInfo } from '@/types'; +import { sleep } from '@/utils/promise'; import SessionStorage from '@/utils/sessionStorage'; import { gotoLoginPage, oauthLogout } from '@/utils/ui'; import { LogoutOutlined, UserOutlined } from '@ant-design/icons'; @@ -63,7 +64,8 @@ const AvatarDropdown: React.FC = ({ menu }) => { */ const loginOut = async () => { oauthLogout('http://172.20.32.197:31209/oauth/logout'); - await logout(); + // 至少 1 秒后跳转,希望子系统能完成注销 + await Promise.all([logout(), sleep(1000)]); clearSessionToken(); setRemoteMenu(null); gotoLoginPage(); 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..a5cc1bec --- /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 ActiveLearnBasic from '../components/ActiveLearnBasic'; +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..168e8de5 --- /dev/null +++ b/react-ui/src/pages/ActiveLearn/Instance/index.less @@ -0,0 +1,42 @@ +.active-learn-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..eff6815f --- /dev/null +++ b/react-ui/src/pages/ActiveLearn/Instance/index.tsx @@ -0,0 +1,194 @@ +import KFIcon from '@/components/KFIcon'; +import { ExperimentStatus } from '@/enums'; +import { getActiveLearnInsReq } from '@/services/activeLearn'; +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 ActiveLearnBasic from '../components/ActiveLearnBasic'; +import ExperimentHistory from '../components/ExperimentHistory'; +import ExperimentLog from '../components/ExperimentLog'; +import ExperimentResult from '../components/ExperimentResult'; +import { ActiveLearnData, ActiveLearnInstanceData } from '../types'; +import styles from './index.less'; + +enum TabKeys { + Params = 'params', + Log = 'log', + Result = 'result', + History = 'history', +} + +const NodePrefix = 'workflow'; + +function ActiveLearnInstance() { + const [experimentInfo, setExperimentInfo] = useState(undefined); + const [instanceInfo, setInstanceInfo] = useState(undefined); + // 超参数寻优运行有3个节点,运行状态取工作流状态,而不是 auto-hpo 节点状态 + const [workflowStatus, setWorkflowStatus] = useState(undefined); + const [nodes, setNodes] = useState | undefined>(undefined); + const params = useParams(); + const instanceId = safeInvoke(Number)(params.id); + const evtSourceRef = useRef(null); + + useEffect(() => { + if (instanceId) { + getExperimentInsInfo(false); + } + return () => { + closeSSE(); + }; + // eslint-disable-next-line react-hooks/exhaustive-deps + }, [instanceId]); + + // 获取实验实例详情 + const getExperimentInsInfo = async (isStatusDetermined: boolean) => { + const [res] = await to(getActiveLearnInsReq(instanceId)); + if (res && res.data) { + const info = res.data as ActiveLearnInstanceData; + const { param, node_status, argo_ins_name, argo_ins_ns, status } = info; + // 解析配置参数 + const paramJson = parseJsonText(param); + if (paramJson) { + setExperimentInfo(paramJson.data); + } + + setInstanceInfo(info); + + // 这个接口返回的状态有延时,SSE 返回的状态是最新的 + // SSE 调用时,不需要解析 node_status,也不要重新建立 SSE + if (isStatusDetermined) { + return; + } + + // 进行节点状态 + const nodeStatusJson = parseJsonText(node_status); + if (nodeStatusJson) { + setNodes(nodeStatusJson); + Object.keys(nodeStatusJson).some((key) => { + if (key.startsWith(NodePrefix)) { + const workflowStatus = nodeStatusJson[key]; + setWorkflowStatus(workflowStatus); + return true; + } + return false; + }); + } + + // 运行中或者等待中,开启 SSE + if (status === ExperimentStatus.Pending || status === ExperimentStatus.Running) { + setupSSE(argo_ins_name, argo_ins_ns); + } + } + }; + + const setupSSE = (name: string, namespace: string) => { + const { origin } = location; + const params = encodeURIComponent(`metadata.namespace=${namespace},metadata.name=${name}`); + const evtSource = new EventSource( + `${origin}/api/v1/realtimeStatus?listOptions.fieldSelector=${params}`, + { withCredentials: false }, + ); + evtSource.onmessage = (event) => { + const data = event?.data; + if (!data) { + return; + } + const dataJson = parseJsonText(data); + if (dataJson) { + const nodes = dataJson?.result?.object?.status?.nodes; + if (nodes) { + const workflowStatus = Object.values(nodes).find((node: any) => + node.displayName.startsWith(NodePrefix), + ) as NodeStatus; + + // 节点 + setNodes(nodes); + + // 设置工作流状态 + if (workflowStatus) { + setWorkflowStatus(workflowStatus); + + // 实验结束,关闭 SSE + if ( + workflowStatus.phase !== ExperimentStatus.Pending && + workflowStatus.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 && nodes && } +
+ ), + }, + ]; + + const resultTabItems = [ + { + key: TabKeys.Result, + label: '实验结果', + icon: , + children: , + }, + { + key: TabKeys.History, + label: '寻优列表', + icon: , + children: , + }, + ]; + + const tabItems = + instanceInfo?.status === ExperimentStatus.Succeeded + ? [...basicTabItems, ...resultTabItems] + : basicTabItems; + + return ( +
+ +
+ ); +} + +export default ActiveLearnInstance; 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/ActiveLearnBasic/index.less b/react-ui/src/pages/ActiveLearn/components/ActiveLearnBasic/index.less new file mode 100644 index 00000000..a357f2ee --- /dev/null +++ b/react-ui/src/pages/ActiveLearn/components/ActiveLearnBasic/index.less @@ -0,0 +1,13 @@ +.active-learn-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/ActiveLearnBasic/index.tsx b/react-ui/src/pages/ActiveLearn/components/ActiveLearnBasic/index.tsx new file mode 100644 index 00000000..46508069 --- /dev/null +++ b/react-ui/src/pages/ActiveLearn/components/ActiveLearnBasic/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, + }, + { + label: 'epochs', + value: info.epochs, + }, + ]; + + const lossInfo = [ + { + label: 'loss文件路径', + value: info.loss_py, + }, + { + label: 'loss类名', + value: info.loss_class_name, + }, + { + label: '学习率', + value: info.lr, + }, + ]; + + 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.initial_num, + }, + { + label: '查询次数', + value: info.queries_num, + }, + { + label: '每次查询数据量', + value: info.instances_num, + }, + { + label: '查询策略', + value: info.query_strategy, + format: formatEnum(queryStrategies), + }, + { + label: '检查点轮数', + value: info.checkpoint_num, + }, + { + label: 'batch_size', + value: info.batch_size, + }, + ]; + }, [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..b3dd6285 --- /dev/null +++ b/react-ui/src/pages/ActiveLearn/components/CreateForm/ExecuteConfig.tsx @@ -0,0 +1,518 @@ +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'])} + > + + + + + + + + + + + + + + + + + + + + + + + + + + {frameworkType === FrameworkType.Pytorch ? ( + <> + + + + + + + + + + + + + + + + + + + + + + + ) : null} + + ); + } else if (frameworkType === FrameworkType.Sklearn) { + if (taskType === AutoMLTaskType.Classification) { + return ( + <> + + + + + + + + + ); + } + } else { + return null; + } + }} + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + select count(1) from active_learn + + + + + + + + + insert into active_learn(name, description, task_type, framework_type, + code_config, + model, model_py, model_class_name, + classifier_alg, regressor_alg, dataset_py, dataset_class_name, + dataset, data_size, image, computing_resource_id, shuffle, + train_size, initial_num, queries_num, instances_num, query_strategy, + loss_py, loss_class_name, checkpoint_num, batch_size, epochs, + lr, create_by, update_by) + values (#{activeLearn.name}, #{activeLearn.description}, #{activeLearn.taskType}, #{activeLearn.frameworkType}, #{activeLearn.codeConfig}, + #{activeLearn.model}, + #{activeLearn.modelPy}, #{activeLearn.modelClassName}, #{activeLearn.classifierAlg}, + #{activeLearn.regressorAlg}, + #{activeLearn.datasetPy}, #{activeLearn.datasetClassName}, + #{activeLearn.dataset}, #{activeLearn.dataSize}, + #{activeLearn.image}, + #{activeLearn.computingResourceId}, #{activeLearn.shuffle}, + #{activeLearn.trainSize}, #{activeLearn.initialNum}, #{activeLearn.queriesNum}, #{activeLearn.instancesNum}, + #{activeLearn.queryStrategy}, #{activeLearn.lossPy}, + #{activeLearn.lossClassName}, #{activeLearn.checkpointNum}, #{activeLearn.batchSize}, + #{activeLearn.epochs}, #{activeLearn.lr}, #{activeLearn.createBy}, #{activeLearn.updateBy}) + + + + update active_learn + + + name = #{activeLearn.name}, + + + description = #{activeLearn.description}, + + + task_type = #{activeLearn.taskType}, + + + framework_type = #{activeLearn.frameworkType}, + + + code_config = #{activeLearn.codeConfig}, + + + model = #{activeLearn.model}, + + + model_py = #{activeLearn.modelPy}, + + + model_class_name = #{activeLearn.modelClassName}, + + + classifier_alg = #{activeLearn.classifierAlg}, + + + regressor_alg = #{activeLearn.regressorAlg}, + + + dataset = #{activeLearn.dataset}, + + + dataset_py = #{activeLearn.datasetPy}, + + + dataset_class_name = #{activeLearn.datasetClassName}, + + + data_size = #{activeLearn.dataSize}, + + + image = #{activeLearn.image}, + + + computing_resource_id = #{activeLearn.computingResourceId}, + + + shuffle = #{activeLearn.shuffle}, + + + train_size = #{activeLearn.trainSize}, + + + initial_num = #{activeLearn.initialNum}, + + + queries_num = #{activeLearn.queriesNum}, + + + instances_num = #{activeLearn.instancesNum}, + + + query_strategy = #{activeLearn.queryStrategy}, + + + loss_py = #{activeLearn.lossPy}, + + + loss_class_name = #{activeLearn.lossClassName}, + + + checkpoint_num = #{activeLearn.checkpointNum}, + + + batch_size = #{activeLearn.batchSize}, + + + epochs = #{activeLearn.epochs}, + + + lr = #{activeLearn.lr}, + + + update_by = #{activeLearn.updateBy}, + + + status_list = #{activeLearn.statusList}, + + + state = #{activeLearn.state}, + + + where id = #{activeLearn.id} + + + + + + + + + + + + + + + state = 1 + + and name like concat('%', #{name}, '%') + + + + \ No newline at end of file diff --git a/ruoyi-modules/management-platform/src/main/resources/mapper/managementPlatform/ActiveLearnInsDaoMapper.xml b/ruoyi-modules/management-platform/src/main/resources/mapper/managementPlatform/ActiveLearnInsDaoMapper.xml new file mode 100644 index 00000000..955d71d8 --- /dev/null +++ b/ruoyi-modules/management-platform/src/main/resources/mapper/managementPlatform/ActiveLearnInsDaoMapper.xml @@ -0,0 +1,79 @@ + + + + + + + + + insert into active_learn_ins(active_learn_id, param, argo_ins_name, argo_ins_ns, node_status, node_result, + result_path, status) + values (#{activeLearnIns.activeLearnId}, #{activeLearnIns.param}, #{activeLearnIns.argoInsName}, + #{activeLearnIns.argoInsNs}, + #{activeLearnIns.nodeStatus}, #{activeLearnIns.nodeResult}, #{activeLearnIns.resultPath}, + #{activeLearnIns.status}) + + + + update active_learn_ins + + + result_path = #{activeLearnIns.resultPath}, + + + status = #{activeLearnIns.status}, + + + node_status = #{activeLearnIns.nodeStatus}, + + + node_result = #{activeLearnIns.nodeResult}, + + + state = #{activeLearnIns.state}, + + + finish_time = #{activeLearnIns.finishTime}, + + + where id = #{activeLearnIns.id} + + + + + + + + \ No newline at end of file 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 1e4d228c..8bf8250f 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 @@ -120,6 +120,7 @@ + select * + from dev_environment + where name = #{name} + and state = 1 + delete from dev_environment where id = #{id} + + + + + + diff --git a/ruoyi-modules/management-platform/src/main/resources/mapper/managementPlatform/RayDaoMapper.xml b/ruoyi-modules/management-platform/src/main/resources/mapper/managementPlatform/RayDaoMapper.xml index 7355d286..bd0e2c77 100644 --- a/ruoyi-modules/management-platform/src/main/resources/mapper/managementPlatform/RayDaoMapper.xml +++ b/ruoyi-modules/management-platform/src/main/resources/mapper/managementPlatform/RayDaoMapper.xml @@ -1,7 +1,7 @@ - + insert into ray(name, description, dataset, model, code_config, main_py, num_samples, parameters, points_to_evaluate, storage_path, search_alg, scheduler, metric, mode, max_t, @@ -94,11 +94,12 @@ diff --git a/ruoyi-modules/management-platform/src/main/resources/mapper/managementPlatform/ResourceOccupy.xml b/ruoyi-modules/management-platform/src/main/resources/mapper/managementPlatform/ResourceOccupy.xml index 22e662a4..3225be5c 100644 --- a/ruoyi-modules/management-platform/src/main/resources/mapper/managementPlatform/ResourceOccupy.xml +++ b/ruoyi-modules/management-platform/src/main/resources/mapper/managementPlatform/ResourceOccupy.xml @@ -60,6 +60,17 @@ where id = #{id} + + update resource_occupy set task_state = 0 + where task_type = #{taskType} + + and task_id = #{taskId} + + + and task_ins_id = #{taskInsId} + + + + select concat(b.service_name, ':', a.version) + from service_version a, + service b + where JSON_CONTAINS(a.model, #{modelId}) + and a.state = 1 + and b.state = 1 + and a.service_id = b.id + + + + + \ No newline at end of file