You can not select more than 25 topics Topics must start with a chinese character,a letter or number, can include dashes ('-') and can be up to 35 characters long.

index.tsx 6.7 kB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211
  1. import KFIcon from '@/components/KFIcon';
  2. import { AutoMLTaskType, ExperimentStatus } from '@/enums';
  3. import { getActiveLearnInsReq } from '@/services/activeLearn';
  4. import { NodeStatus } from '@/types';
  5. import { parseJsonText } from '@/utils';
  6. import { safeInvoke } from '@/utils/functional';
  7. import { to } from '@/utils/promise';
  8. import { useParams } from '@umijs/max';
  9. import { Tabs } from 'antd';
  10. import { useEffect, useRef, useState } from 'react';
  11. import ActiveLearnBasic from '../components/ActiveLearnBasic';
  12. import ExperimentHistory from '../components/ExperimentHistory';
  13. import ExperimentLog from '../components/ExperimentLog';
  14. import ExperimentResult from '../components/ExperimentResult';
  15. import ExperimentVisualResult from '../components/ExperimentVisualResult';
  16. import { ActiveLearnData, ActiveLearnInstanceData } from '../types';
  17. import styles from './index.less';
  18. enum TabKeys {
  19. Params = 'params',
  20. Log = 'log',
  21. Result = 'result',
  22. History = 'history',
  23. Visual = 'Visual',
  24. }
  25. const NodePrefix = 'workflow';
  26. function ActiveLearnInstance() {
  27. const [experimentInfo, setExperimentInfo] = useState<ActiveLearnData | undefined>(undefined);
  28. const [instanceInfo, setInstanceInfo] = useState<ActiveLearnInstanceData | undefined>(undefined);
  29. // 主动学习运行有3个节点,运行状态取工作流状态,而不是 active-learn 节点状态
  30. const [workflowStatus, setWorkflowStatus] = useState<NodeStatus | undefined>(undefined);
  31. const [nodes, setNodes] = useState<Record<string, NodeStatus> | undefined>(undefined);
  32. const params = useParams();
  33. const instanceId = safeInvoke(Number)(params.id);
  34. const evtSourceRef = useRef<EventSource | null>(null);
  35. useEffect(() => {
  36. if (instanceId) {
  37. getExperimentInsInfo(false);
  38. }
  39. return () => {
  40. closeSSE();
  41. };
  42. // eslint-disable-next-line react-hooks/exhaustive-deps
  43. }, [instanceId]);
  44. // 获取实验实例详情
  45. const getExperimentInsInfo = async (isStatusDetermined: boolean) => {
  46. const [res] = await to(getActiveLearnInsReq(instanceId));
  47. if (res && res.data) {
  48. const info = res.data as ActiveLearnInstanceData;
  49. const { param, node_status, argo_ins_name, argo_ins_ns, status } = info;
  50. // 解析配置参数
  51. const paramJson = parseJsonText(param);
  52. if (paramJson) {
  53. setExperimentInfo({
  54. ...paramJson.data,
  55. });
  56. }
  57. setInstanceInfo(info);
  58. // 这个接口返回的状态有延时,SSE 返回的状态是最新的
  59. // SSE 调用时,不需要解析 node_status,也不要重新建立 SSE
  60. if (isStatusDetermined) {
  61. return;
  62. }
  63. // 设置总 workflow 状态
  64. const nodeStatusJson = parseJsonText(node_status);
  65. if (nodeStatusJson) {
  66. setNodes(nodeStatusJson);
  67. Object.keys(nodeStatusJson).some((key) => {
  68. if (key.startsWith(NodePrefix)) {
  69. const workflowStatus = nodeStatusJson[key];
  70. setWorkflowStatus(workflowStatus);
  71. return true;
  72. }
  73. return false;
  74. });
  75. }
  76. // 运行中或者等待中,开启 SSE
  77. if (status === ExperimentStatus.Pending || status === ExperimentStatus.Running) {
  78. setupSSE(argo_ins_name, argo_ins_ns);
  79. }
  80. }
  81. };
  82. const setupSSE = (name: string, namespace: string) => {
  83. const { origin } = location;
  84. const params = encodeURIComponent(`metadata.namespace=${namespace},metadata.name=${name}`);
  85. const evtSource = new EventSource(
  86. `${origin}/api/v1/realtimeStatus?listOptions.fieldSelector=${params}`,
  87. { withCredentials: false },
  88. );
  89. evtSource.onmessage = (event) => {
  90. const data = event?.data;
  91. if (!data) {
  92. return;
  93. }
  94. const dataJson = parseJsonText(data);
  95. if (dataJson) {
  96. const nodes = dataJson?.result?.object?.status?.nodes;
  97. if (nodes) {
  98. // 设置节点
  99. setNodes(nodes);
  100. // 设置总 workflow 状态
  101. const workflowStatus = Object.values(nodes).find((node: any) =>
  102. node.displayName.startsWith(NodePrefix),
  103. ) as NodeStatus;
  104. if (workflowStatus) {
  105. setWorkflowStatus(workflowStatus);
  106. // 实验结束,关闭 SSE,获取实验实例结果
  107. if (
  108. workflowStatus.phase !== ExperimentStatus.Pending &&
  109. workflowStatus.phase !== ExperimentStatus.Running
  110. ) {
  111. closeSSE();
  112. getExperimentInsInfo(true);
  113. }
  114. }
  115. }
  116. }
  117. };
  118. evtSource.onerror = (error) => {
  119. console.error('SSE error: ', error);
  120. };
  121. evtSourceRef.current = evtSource;
  122. };
  123. const closeSSE = () => {
  124. if (evtSourceRef.current) {
  125. evtSourceRef.current.close();
  126. evtSourceRef.current = null;
  127. }
  128. };
  129. const basicTabItems = [
  130. {
  131. key: TabKeys.Params,
  132. label: '基本信息',
  133. icon: <KFIcon type="icon-jibenxinxi" />,
  134. children: (
  135. <ActiveLearnBasic
  136. className={styles['active-learn-instance__basic']}
  137. info={experimentInfo}
  138. workflowStatus={workflowStatus}
  139. instanceStatus={instanceInfo?.status as ExperimentStatus}
  140. isInstance
  141. />
  142. ),
  143. },
  144. {
  145. key: TabKeys.Log,
  146. label: '日志',
  147. icon: <KFIcon type="icon-rizhi1" />,
  148. children: (
  149. <div className={styles['active-learn-instance__log']}>
  150. {instanceInfo && nodes && <ExperimentLog instanceInfo={instanceInfo} nodes={nodes} />}
  151. </div>
  152. ),
  153. },
  154. ];
  155. const resultTabItems = [
  156. {
  157. key: TabKeys.Result,
  158. label: '实验结果',
  159. icon: <KFIcon type="icon-shiyanjieguo1" />,
  160. children: (
  161. <ExperimentResult fileUrl={instanceInfo?.data} modelPath={instanceInfo?.result_path} />
  162. ),
  163. },
  164. {
  165. key: TabKeys.History,
  166. label: '运行列表',
  167. icon: <KFIcon type="icon-Trialliebiao" />,
  168. children: (
  169. <ExperimentHistory
  170. trialList={instanceInfo?.trial_list ?? []}
  171. isClassification={experimentInfo?.task_type === AutoMLTaskType.Classification}
  172. />
  173. ),
  174. },
  175. {
  176. key: TabKeys.Visual,
  177. label: '可视化结果',
  178. icon: <KFIcon type="icon-Trialliebiao" />,
  179. children: <ExperimentVisualResult metricsId={experimentInfo?.experiment_ins_id} />,
  180. },
  181. ];
  182. const tabItems =
  183. instanceInfo?.status === ExperimentStatus.Succeeded
  184. ? [...basicTabItems, ...resultTabItems]
  185. : basicTabItems;
  186. return (
  187. <div className={styles['active-learn-instance']}>
  188. <Tabs className={styles['active-learn-instance__tabs']} items={tabItems} />
  189. </div>
  190. );
  191. }
  192. export default ActiveLearnInstance;