| @@ -169,6 +169,11 @@ export default [ | |||
| path: 'edit/:id', | |||
| component: './AutoML/Create/index', | |||
| }, | |||
| { | |||
| name: '实验实例', | |||
| path: 'instance/:autoMLId/:id', | |||
| component: './AutoML/Instance/index', | |||
| }, | |||
| ], | |||
| }, | |||
| ], | |||
| @@ -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<string>(cacheState?.activeTab ?? CommonTabKeys.Public); | |||
| const [activeTab, setActiveTab] = useState<string>(CommonTabKeys.Public); | |||
| const params = useParams(); | |||
| const autoMLId = safeInvoke(Number)(params.id); | |||
| const [autoMLInfo, setAutoMLInfo] = useState<AutoMLData | undefined>(undefined); | |||
| const tabItems = [ | |||
| { | |||
| @@ -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 ( | |||
| <div className={styles['auto-ml-info']}> | |||
| <div className={styles['auto-ml-info__tabs']}> | |||
| <Tabs items={tabItems} activeKey={activeTab} onChange={setActiveTab} /> | |||
| </div> | |||
| <div className={styles['auto-ml-info__content']}> | |||
| {activeTab === CommonTabKeys.Public && <AutoMLBasic />} | |||
| {activeTab === CommonTabKeys.Public && <AutoMLBasic info={autoMLInfo} />} | |||
| {activeTab === CommonTabKeys.Private && <AutoMLTable />} | |||
| </div> | |||
| {activeTab === CommonTabKeys.Private && ( | |||
| @@ -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; | |||
| } | |||
| } | |||
| @@ -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<string>(TabKeys.Params); | |||
| const [autoMLInfo, setAutoMLInfo] = useState<AutoMLData | undefined>(undefined); | |||
| const [instanceInfo, setInstanceInfo] = useState<AutoMLInstanceData | undefined>(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: <KFIcon type="icon-jibenxinxi" />, | |||
| }, | |||
| { | |||
| key: TabKeys.Log, | |||
| label: '日志', | |||
| icon: <KFIcon type="icon-Trialliebiao" />, | |||
| }, | |||
| { | |||
| key: TabKeys.Result, | |||
| label: '实验结果', | |||
| icon: <KFIcon type="icon-Trialliebiao" />, | |||
| }, | |||
| { | |||
| key: TabKeys.History, | |||
| label: '运行历史', | |||
| icon: <KFIcon type="icon-Trialliebiao" />, | |||
| }, | |||
| ]; | |||
| return ( | |||
| <div className={styles['auto-ml-instance']}> | |||
| <div className={styles['auto-ml-instance__tabs']}> | |||
| <Tabs items={tabItems} activeKey={activeTab} onChange={setActiveTab} /> | |||
| </div> | |||
| <div className={styles['auto-ml-instance__content']}> | |||
| {activeTab === TabKeys.Params && <AutoMLBasic info={autoMLInfo} hasBasicInfo={false} />} | |||
| {activeTab === TabKeys.Log && instanceInfo && instanceInfo.nodeStatus && ( | |||
| <LogList | |||
| instanceName={instanceInfo.argo_ins_name} | |||
| instanceNamespace={instanceInfo.argo_ins_ns} | |||
| pipelineNodeId={instanceInfo.nodeStatus.nodeId} | |||
| workflowId={instanceInfo.nodeStatus.id} | |||
| instanceNodeStartTime={instanceInfo.nodeStatus.startedAt} | |||
| instanceNodeStatus={instanceInfo.nodeStatus.phase as ExperimentStatus} | |||
| ></LogList> | |||
| )} | |||
| {activeTab === TabKeys.Result && ( | |||
| <ExperimentResult fileUrl={instanceInfo?.result_path} imageUrl={instanceInfo?.img_path} /> | |||
| )} | |||
| {activeTab === TabKeys.History && ( | |||
| <ExperimentHistory | |||
| fileUrl={instanceInfo?.run_history_path} | |||
| isClassification={autoMLInfo?.task_type === AutoMLTaskType.Classification} | |||
| /> | |||
| )} | |||
| </div> | |||
| </div> | |||
| ); | |||
| } | |||
| export default AutoMLInstance; | |||
| @@ -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() { | |||
| <ExperimentInstance | |||
| experimentInsList={experimentInsList} | |||
| experimentInsTotal={experimentInsTotal} | |||
| onClickInstance={(item) => gotoInstanceInfo(item, record)} | |||
| onClickInstance={(item) => gotoInstanceInfo(record, item)} | |||
| onRemove={() => { | |||
| refreshExperimentIns(record.id); | |||
| refreshExperimentList(); | |||
| @@ -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<BasicInfoData[]>([]); | |||
| const [configDatas, setConfigDatas] = useState<BasicInfoData[]>([]); | |||
| 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: <CopyingText text="测试项目名称测试项目名称测试项目名称"></CopyingText>, | |||
| // 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 ( | |||
| <div className={styles['auto-ml-basic']}> | |||
| <Flex gap={15} align="stretch"> | |||
| <ConfigInfo title="基本信息" data={basicDatas} labelWidth={70} threeColumn /> | |||
| {/* <StatusChart | |||
| chartData={{ Failed: 10, Pending: 20, Running: 30, Succeeded: 40, Terminated: 50 }} | |||
| /> */} | |||
| </Flex> | |||
| {hasBasicInfo && ( | |||
| <ConfigInfo | |||
| title="基本信息" | |||
| data={basicDatas} | |||
| labelWidth={70} | |||
| threeColumn | |||
| style={{ marginBottom: '20px' }} | |||
| /> | |||
| )} | |||
| <ConfigInfo | |||
| title="配置信息" | |||
| data={configDatas.slice(0, -3)} | |||
| data={configDatas} | |||
| labelWidth={150} | |||
| threeColumn | |||
| style={{ marginTop: '20px' }} | |||
| /> | |||
| <ConfigInfo | |||
| title="优化指标" | |||
| data={configDatas.slice(-3)} | |||
| labelWidth={70} | |||
| threeColumn | |||
| style={{ marginTop: '20px' }} | |||
| style={{ marginBottom: '20px' }} | |||
| /> | |||
| <ConfigInfo title="优化指标" data={metricsData} labelWidth={70} threeColumn /> | |||
| </div> | |||
| ); | |||
| } | |||
| @@ -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; | |||
| @@ -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%; | |||
| } | |||
| } | |||
| } | |||
| @@ -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<TableData[]>([]); | |||
| 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<TableData>['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 ( | |||
| <div className={styles['experiment-history']}> | |||
| <div className={styles['experiment-history__content']}> | |||
| <div | |||
| className={classNames( | |||
| 'vertical-scroll-table-no-page', | |||
| styles['experiment-history__content__table'], | |||
| )} | |||
| > | |||
| <Table | |||
| dataSource={tableData} | |||
| columns={columns} | |||
| pagination={false} | |||
| scroll={{ y: 'calc(100% - 55px)' }} | |||
| rowKey="id" | |||
| /> | |||
| </div> | |||
| </div> | |||
| </div> | |||
| ); | |||
| } | |||
| export default ExperimentHistory; | |||
| @@ -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; | |||
| } | |||
| } | |||
| @@ -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<string | undefined>(''); | |||
| 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 ( | |||
| <div className={styles['experiment-result']}> | |||
| <div className={styles['experiment-result__text']}>{result}</div> | |||
| {images.map((item, index) => ( | |||
| <img | |||
| key={index} | |||
| className={styles['experiment-result__image']} | |||
| src={item} | |||
| draggable={false} | |||
| alt="" | |||
| /> | |||
| ))} | |||
| </div> | |||
| ); | |||
| } | |||
| export default ExperimentResult; | |||
| @@ -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 }; | |||
| }; | |||
| @@ -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}"}`, | |||
| @@ -271,6 +271,7 @@ function Experiment() { | |||
| const [res] = await to(runExperiments(id)); | |||
| if (res) { | |||
| message.success('运行成功'); | |||
| refreshExperimentList(); | |||
| refreshExperimentIns(id); | |||
| } else { | |||
| message.error('运行失败'); | |||
| @@ -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(); | |||
| @@ -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 | |||
| }); | |||
| } | |||