| @@ -16,6 +16,7 @@ export enum IframePageType { | |||||
| GitLink = 'GitLink', // git link | GitLink = 'GitLink', // git link | ||||
| Aim = 'Aim', // 实验对比 | Aim = 'Aim', // 实验对比 | ||||
| Knowledge = 'Knowledge', // 知识图谱 | Knowledge = 'Knowledge', // 知识图谱 | ||||
| TensorBoard = 'TensorBoard', // 可视化结果 | |||||
| } | } | ||||
| const getRequestAPI = (type: IframePageType): (() => Promise<any>) => { | const getRequestAPI = (type: IframePageType): (() => Promise<any>) => { | ||||
| @@ -36,12 +37,18 @@ const getRequestAPI = (type: IframePageType): (() => Promise<any>) => { | |||||
| return () => | return () => | ||||
| Promise.resolve({ | Promise.resolve({ | ||||
| code: 200, | code: 200, | ||||
| data: SessionStorage.getItem(SessionStorage.aimUrlKey) || '', | |||||
| data: SessionStorage.getItem(SessionStorage.aimUrlKey), | |||||
| }); | }); | ||||
| case IframePageType.Knowledge: { | |||||
| case IframePageType.Knowledge: | |||||
| // 知识图谱 | // 知识图谱 | ||||
| return () => Promise.resolve({ code: 200, data: `http://172.168.15.197:32701` }); | return () => Promise.resolve({ code: 200, data: `http://172.168.15.197:32701` }); | ||||
| } | |||||
| case IframePageType.TensorBoard: | |||||
| // 知识图谱 | |||||
| return () => | |||||
| Promise.resolve({ | |||||
| code: 200, | |||||
| data: SessionStorage.getItem(SessionStorage.editorUrlKey), | |||||
| }); | |||||
| } | } | ||||
| }; | }; | ||||
| @@ -54,7 +54,7 @@ function ExperimentHistory({ trialList, isClassification }: ExperimentHistoryPro | |||||
| icon={<KFIcon type="icon-xiazai" />} | icon={<KFIcon type="icon-xiazai" />} | ||||
| onClick={() => { | onClick={() => { | ||||
| if (query_idx) { | if (query_idx) { | ||||
| const fileName = query_idx.split('/').slice(-1)[0]; | |||||
| const fileName = query_idx.split('/').pop(); | |||||
| let url = query_idx; | let url = query_idx; | ||||
| if (process.env.NODE_ENV === 'development') { | if (process.env.NODE_ENV === 'development') { | ||||
| url = query_idx.replace('172.168.15.197:31213', 'localhost:8000'); | url = query_idx.replace('172.168.15.197:31213', 'localhost:8000'); | ||||
| @@ -1,5 +1,5 @@ | |||||
| import KFIcon from '@/components/KFIcon'; | import KFIcon from '@/components/KFIcon'; | ||||
| import { AutoMLTaskType, ExperimentStatus } from '@/enums'; | |||||
| import { AutoMLTaskType, AutoMLType, ExperimentStatus } from '@/enums'; | |||||
| import { getExperimentInsReq } from '@/services/autoML'; | import { getExperimentInsReq } from '@/services/autoML'; | ||||
| import { NodeStatus } from '@/types'; | import { NodeStatus } from '@/types'; | ||||
| import { parseJsonText } from '@/utils'; | import { parseJsonText } from '@/utils'; | ||||
| @@ -12,6 +12,7 @@ import AutoMLBasic from '../components/AutoMLBasic'; | |||||
| import ExperimentHistory from '../components/ExperimentHistory'; | import ExperimentHistory from '../components/ExperimentHistory'; | ||||
| import ExperimentLog from '../components/ExperimentLog'; | import ExperimentLog from '../components/ExperimentLog'; | ||||
| import ExperimentResult from '../components/ExperimentResult'; | import ExperimentResult from '../components/ExperimentResult'; | ||||
| import TensorBoard from '../components/TensorBoard'; | |||||
| import { AutoMLData, AutoMLInstanceData } from '../types'; | import { AutoMLData, AutoMLInstanceData } from '../types'; | ||||
| import styles from './index.less'; | import styles from './index.less'; | ||||
| @@ -20,6 +21,7 @@ enum TabKeys { | |||||
| Log = 'log', | Log = 'log', | ||||
| Result = 'result', | Result = 'result', | ||||
| History = 'history', | History = 'history', | ||||
| Visual = 'Visual', | |||||
| } | } | ||||
| const NodePrefix = 'workflow'; | const NodePrefix = 'workflow'; | ||||
| @@ -29,6 +31,7 @@ function AutoMLInstance() { | |||||
| const [instanceInfo, setInstanceInfo] = useState<AutoMLInstanceData | undefined>(undefined); | const [instanceInfo, setInstanceInfo] = useState<AutoMLInstanceData | undefined>(undefined); | ||||
| const [workflowStatus, setWorkflowStatus] = useState<NodeStatus | undefined>(undefined); | const [workflowStatus, setWorkflowStatus] = useState<NodeStatus | undefined>(undefined); | ||||
| const [nodes, setNodes] = useState<Record<string, NodeStatus> | undefined>(undefined); | const [nodes, setNodes] = useState<Record<string, NodeStatus> | undefined>(undefined); | ||||
| const [type, setType] = useState<string | undefined>(undefined); | |||||
| const params = useParams(); | const params = useParams(); | ||||
| const instanceId = safeInvoke(Number)(params.id); | const instanceId = safeInvoke(Number)(params.id); | ||||
| const evtSourceRef = useRef<EventSource | null>(null); | const evtSourceRef = useRef<EventSource | null>(null); | ||||
| @@ -49,6 +52,8 @@ function AutoMLInstance() { | |||||
| if (res && res.data) { | if (res && res.data) { | ||||
| const info = res.data as AutoMLInstanceData; | const info = res.data as AutoMLInstanceData; | ||||
| const { param, node_status, argo_ins_name, argo_ins_ns, status, create_time, type } = info; | const { param, node_status, argo_ins_name, argo_ins_ns, status, create_time, type } = info; | ||||
| setType(type); | |||||
| // 解析配置参数 | // 解析配置参数 | ||||
| const paramJson = parseJsonText(param); | const paramJson = parseJsonText(param); | ||||
| if (paramJson) { | if (paramJson) { | ||||
| @@ -174,24 +179,37 @@ function AutoMLInstance() { | |||||
| icon: <KFIcon type="icon-shiyanjieguo1" />, | icon: <KFIcon type="icon-shiyanjieguo1" />, | ||||
| children: ( | children: ( | ||||
| <ExperimentResult | <ExperimentResult | ||||
| type={type} | |||||
| fileUrl={instanceInfo?.result_path} | fileUrl={instanceInfo?.result_path} | ||||
| imageUrl={instanceInfo?.img_path} | imageUrl={instanceInfo?.img_path} | ||||
| modelPath={instanceInfo?.model_path} | modelPath={instanceInfo?.model_path} | ||||
| /> | /> | ||||
| ), | ), | ||||
| }, | }, | ||||
| { | |||||
| key: TabKeys.History, | |||||
| label: '试验列表', | |||||
| icon: <KFIcon type="icon-Trialliebiao" />, | |||||
| children: ( | |||||
| <ExperimentHistory | |||||
| calcMetrics={autoMLInfo?.scoring_functions} | |||||
| fileUrl={instanceInfo?.run_history_path} | |||||
| isClassification={autoMLInfo?.task_type === AutoMLTaskType.Classification} | |||||
| /> | |||||
| ), | |||||
| }, | |||||
| type === AutoMLType.Text | |||||
| ? { | |||||
| key: TabKeys.Visual, | |||||
| label: '可视化结果', | |||||
| icon: <KFIcon type="icon-Trialliebiao" />, | |||||
| children: ( | |||||
| <TensorBoard | |||||
| path={instanceInfo?.run_history_path} | |||||
| namespace={instanceInfo?.argo_ins_ns} | |||||
| /> | |||||
| ), | |||||
| } | |||||
| : { | |||||
| key: TabKeys.History, | |||||
| label: '试验列表', | |||||
| icon: <KFIcon type="icon-Trialliebiao" />, | |||||
| children: ( | |||||
| <ExperimentHistory | |||||
| calcMetrics={autoMLInfo?.scoring_functions} | |||||
| fileUrl={instanceInfo?.run_history_path} | |||||
| isClassification={autoMLInfo?.task_type === AutoMLTaskType.Classification} | |||||
| /> | |||||
| ), | |||||
| }, | |||||
| ]; | ]; | ||||
| const tabItems = | const tabItems = | ||||
| @@ -9,7 +9,7 @@ | |||||
| &__download { | &__download { | ||||
| padding-top: 16px; | padding-top: 16px; | ||||
| padding-bottom: 16px; | padding-bottom: 16px; | ||||
| margin-top: 16px; | |||||
| padding-left: @content-padding; | padding-left: @content-padding; | ||||
| color: @text-color; | color: @text-color; | ||||
| font-size: 13px; | font-size: 13px; | ||||
| @@ -1,4 +1,5 @@ | |||||
| import InfoGroup from '@/components/InfoGroup'; | import InfoGroup from '@/components/InfoGroup'; | ||||
| import { AutoMLType } from '@/enums'; | |||||
| import { getFileReq } from '@/services/file'; | import { getFileReq } from '@/services/file'; | ||||
| import { to } from '@/utils/promise'; | import { to } from '@/utils/promise'; | ||||
| import { Button, Image } from 'antd'; | import { Button, Image } from 'antd'; | ||||
| @@ -9,9 +10,10 @@ type ExperimentResultProps = { | |||||
| fileUrl?: string; | fileUrl?: string; | ||||
| imageUrl?: string; | imageUrl?: string; | ||||
| modelPath?: string; | modelPath?: string; | ||||
| type?: string; | |||||
| }; | }; | ||||
| function ExperimentResult({ fileUrl, imageUrl, modelPath }: ExperimentResultProps) { | |||||
| function ExperimentResult({ fileUrl, imageUrl, modelPath, type }: ExperimentResultProps) { | |||||
| const [result, setResult] = useState<string | undefined>(''); | const [result, setResult] = useState<string | undefined>(''); | ||||
| const images = useMemo(() => { | const images = useMemo(() => { | ||||
| @@ -40,31 +42,33 @@ function ExperimentResult({ fileUrl, imageUrl, modelPath }: ExperimentResultProp | |||||
| <InfoGroup title="实验结果" height={420} width="100%"> | <InfoGroup title="实验结果" height={420} width="100%"> | ||||
| <div className={styles['experiment-result__text']}>{result}</div> | <div className={styles['experiment-result__text']}>{result}</div> | ||||
| </InfoGroup> | </InfoGroup> | ||||
| <InfoGroup title="可视化结果" style={{ margin: '16px 0' }}> | |||||
| <div className={styles['experiment-result__images']}> | |||||
| <Image.PreviewGroup | |||||
| preview={{ | |||||
| onChange: (current, prev) => | |||||
| console.log(`current index: ${current}, prev index: ${prev}`), | |||||
| }} | |||||
| > | |||||
| {images.map((item) => ( | |||||
| <Image | |||||
| key={item} | |||||
| className={styles['experiment-result__images__item']} | |||||
| src={item} | |||||
| height={248} | |||||
| draggable={false} | |||||
| alt="" | |||||
| /> | |||||
| ))} | |||||
| </Image.PreviewGroup> | |||||
| </div> | |||||
| </InfoGroup> | |||||
| {type === AutoMLType.Table && ( | |||||
| <InfoGroup title="可视化结果" style={{ margin: '16px 0' }}> | |||||
| <div className={styles['experiment-result__images']}> | |||||
| <Image.PreviewGroup | |||||
| preview={{ | |||||
| onChange: (current, prev) => | |||||
| console.log(`current index: ${current}, prev index: ${prev}`), | |||||
| }} | |||||
| > | |||||
| {images.map((item) => ( | |||||
| <Image | |||||
| key={item} | |||||
| className={styles['experiment-result__images__item']} | |||||
| src={item} | |||||
| height={248} | |||||
| draggable={false} | |||||
| alt="" | |||||
| /> | |||||
| ))} | |||||
| </Image.PreviewGroup> | |||||
| </div> | |||||
| </InfoGroup> | |||||
| )} | |||||
| {modelPath && ( | {modelPath && ( | ||||
| <div className={styles['experiment-result__download']}> | <div className={styles['experiment-result__download']}> | ||||
| <span style={{ marginRight: '12px', color: '#606b7a' }}>文件名</span> | <span style={{ marginRight: '12px', color: '#606b7a' }}>文件名</span> | ||||
| <span>save_model.joblib</span> | |||||
| <span>{modelPath.split('/').pop()} </span> | |||||
| <Button | <Button | ||||
| type="primary" | type="primary" | ||||
| className={styles['experiment-result__download__btn']} | className={styles['experiment-result__download__btn']} | ||||
| @@ -0,0 +1,42 @@ | |||||
| /* | |||||
| * @Author: 赵伟 | |||||
| * @Date: 2024-09-02 08:42:57 | |||||
| * @Description: 可视化 | |||||
| */ | |||||
| import IframePage, { IframePageType } from '@/components/IFramePage'; | |||||
| import { runTensorBoardReq } from '@/services/experiment/index.js'; | |||||
| import { to } from '@/utils/promise'; | |||||
| import SessionStorage from '@/utils/sessionStorage'; | |||||
| import { useEffect, useState } from 'react'; | |||||
| type TensorBoardProps = { | |||||
| namespace?: string; | |||||
| path?: string; | |||||
| }; | |||||
| function TensorBoard({ namespace, path }: TensorBoardProps) { | |||||
| const [tensorboardUrl, setTensorboardUrl] = useState(''); | |||||
| useEffect(() => { | |||||
| // 运行 TensorBoard | |||||
| const runTensorBoard = async () => { | |||||
| const params = { | |||||
| namespace: namespace, | |||||
| path: path, | |||||
| }; | |||||
| const [res] = await to(runTensorBoardReq(params)); | |||||
| if (res && res.data) { | |||||
| SessionStorage.setItem(SessionStorage.tensorBoardUrlKey, res.data); | |||||
| setTensorboardUrl(res.data); | |||||
| } | |||||
| }; | |||||
| if (namespace && path) { | |||||
| runTensorBoard(); | |||||
| } | |||||
| }, [namespace, path]); | |||||
| return <>{tensorboardUrl && <IframePage type={IframePageType.TensorBoard}></IframePage>}</>; | |||||
| } | |||||
| export default TensorBoard; | |||||
| @@ -17,7 +17,6 @@ import { createEditorReq } from '@/services/developmentEnvironment'; | |||||
| import { to } from '@/utils/promise'; | import { to } from '@/utils/promise'; | ||||
| import { useNavigate } from '@umijs/max'; | import { useNavigate } from '@umijs/max'; | ||||
| import { App, Button, Col, Form, Input, Row } from 'antd'; | import { App, Button, Col, Form, Input, Row } from 'antd'; | ||||
| import { omit, pick } from 'lodash'; | |||||
| import styles from './index.less'; | import styles from './index.less'; | ||||
| type FormData = { | type FormData = { | ||||
| @@ -54,15 +53,7 @@ function EditorCreate() { | |||||
| // 创建编辑器 | // 创建编辑器 | ||||
| const createEditor = async (formData: FormData) => { | const createEditor = async (formData: FormData) => { | ||||
| // 根据后台要求,修改表单数据 | |||||
| const model = formData['model']; | |||||
| const dataset = formData['dataset']; | |||||
| const params = { | |||||
| ...omit(formData, ['model', 'dataset']), | |||||
| model: model && pick(model, ['id', 'version', 'path', 'showValue']), | |||||
| dataset: dataset && pick(dataset, ['id', 'version', 'path', 'showValue']), | |||||
| }; | |||||
| const [res] = await to(createEditorReq(params)); | |||||
| const [res] = await to(createEditorReq(formData)); | |||||
| if (res) { | if (res) { | ||||
| message.success('创建成功'); | message.success('创建成功'); | ||||
| navigate(-1); | navigate(-1); | ||||
| @@ -11,6 +11,8 @@ export default class SessionStorage { | |||||
| static readonly clientInfoKey = 'client-info'; | static readonly clientInfoKey = 'client-info'; | ||||
| /** aim url */ | /** aim url */ | ||||
| static readonly aimUrlKey = 'aim-url'; | static readonly aimUrlKey = 'aim-url'; | ||||
| /** tensorBoard url */ | |||||
| static readonly tensorBoardUrlKey = 'tensor-board-url'; | |||||
| /** | /** | ||||
| * 获取 SessionStorage 值 | * 获取 SessionStorage 值 | ||||