From bef5bf649ef7e61cb460145bdc870d545fbb4a52 Mon Sep 17 00:00:00 2001 From: zhaowei Date: Fri, 9 May 2025 11:43:25 +0800 Subject: [PATCH] =?UTF-8?q?fix:=20=E5=AE=9E=E9=AA=8C=E8=BF=90=E8=A1=8C?= =?UTF-8?q?=E6=97=B6=E9=95=BF=E5=AE=9A=E6=97=B6=E5=88=B7=E6=96=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- react-ui/src/app.tsx | 2 + react-ui/src/hooks/useSSE.ts | 39 +++--- react-ui/src/hooks/useServerTime.ts | 54 ++++++++ .../src/pages/ActiveLearn/Instance/index.tsx | 14 +- react-ui/src/pages/AutoML/Instance/index.tsx | 14 +- .../index.less | 0 .../index.tsx | 43 ++----- .../ExperimentInstanceList/instance.tsx | 83 ++++++++++++ .../components/ExperimentList/index.tsx | 121 ++++++++++++------ react-ui/src/pages/Experiment/Info/index.jsx | 8 +- .../components/ExperimentDrawer/index.tsx | 10 +- .../index.less | 0 .../index.tsx | 45 +++---- .../ExperimentInstanceList/instance.tsx | 85 ++++++++++++ react-ui/src/pages/Experiment/index.jsx | 117 +++++++++++------ .../pages/HyperParameter/Instance/index.tsx | 14 +- .../Points/components/Statistics/index.less | 1 + .../Points/components/Statistics/index.tsx | 13 +- .../Workspace/components/UserPoints/index.tsx | 7 +- react-ui/src/services/experiment/index.js | 8 ++ react-ui/src/utils/constant.ts | 3 + react-ui/src/utils/format.ts | 16 +++ react-ui/src/utils/index.ts | 9 -- 23 files changed, 503 insertions(+), 203 deletions(-) create mode 100644 react-ui/src/hooks/useServerTime.ts rename react-ui/src/pages/AutoML/components/{ExperimentInstance => ExperimentInstanceList}/index.less (100%) rename react-ui/src/pages/AutoML/components/{ExperimentInstance => ExperimentInstanceList}/index.tsx (83%) create mode 100644 react-ui/src/pages/AutoML/components/ExperimentInstanceList/instance.tsx rename react-ui/src/pages/Experiment/components/{ExperimentInstance => ExperimentInstanceList}/index.less (100%) rename react-ui/src/pages/Experiment/components/{ExperimentInstance => ExperimentInstanceList}/index.tsx (84%) create mode 100644 react-ui/src/pages/Experiment/components/ExperimentInstanceList/instance.tsx diff --git a/react-ui/src/app.tsx b/react-ui/src/app.tsx index ac8b8652..686c495d 100644 --- a/react-ui/src/app.tsx +++ b/react-ui/src/app.tsx @@ -11,6 +11,7 @@ import { getAccessToken } from './access'; import ErrorBoundary from './components/ErrorBoundary'; import './dayjsConfig'; import { removeAllPageCacheState } from './hooks/useCacheState'; +import { globalGetSeverTime } from './hooks/useServerTime'; import { getRemoteMenu, getRoutersInfo, @@ -29,6 +30,7 @@ export { requestConfig as request } from './requestConfig'; export async function getInitialState(): Promise { const fetchUserInfo = async () => { try { + globalGetSeverTime(); const response = await getUserInfo(); return { ...response.user, diff --git a/react-ui/src/hooks/useSSE.ts b/react-ui/src/hooks/useSSE.ts index 5e278675..9f364f68 100644 --- a/react-ui/src/hooks/useSSE.ts +++ b/react-ui/src/hooks/useSSE.ts @@ -1,11 +1,12 @@ import { parseJsonText } from '@/utils'; -import { useCallback, useRef } from 'react'; +import { useEffect } from 'react'; +import { ExperimentStatus } from '@/enums'; +import { NodeStatus } from '@/types'; -export const useSSE = (onMessage: (data: any) => void) => { - const evtSourceRef = useRef(null); - - const setupSSE = useCallback( - (name: string, namespace: string) => { +export type MessageHandler = (experimentInsId: number, status: string, finishedAt: string, nodes: Record) => void +export const useSSE = (experimentInsId: number, status: ExperimentStatus, name: string, namespace: string, onMessage: MessageHandler) => { + useEffect(() => { + if (status === ExperimentStatus.Pending || status === ExperimentStatus.Running) { const { origin } = location; const params = encodeURIComponent(`metadata.namespace=${namespace},metadata.name=${name}`); const evtSource = new EventSource( @@ -18,11 +19,10 @@ export const useSSE = (onMessage: (data: any) => void) => { return; } const dataJson = parseJsonText(data); - if (dataJson) { - const nodes = dataJson?.result?.object?.status?.nodes; - if (nodes) { - onMessage(nodes); - } + const statusData = dataJson?.result?.object?.status; + if (statusData) { + const { finishedAt, phase, nodes } = statusData; + onMessage(experimentInsId, phase, finishedAt, nodes); } }; @@ -30,17 +30,10 @@ export const useSSE = (onMessage: (data: any) => void) => { console.error('SSE error: ', error); }; - evtSourceRef.current = evtSource; - }, - [onMessage], - ); - - const closeSSE = useCallback(() => { - if (evtSourceRef.current) { - evtSourceRef.current.close(); - evtSourceRef.current = null; + return () => { + evtSource.close(); + } } - }, []); - - return [setupSSE, closeSSE]; + + }, [experimentInsId, status, name, namespace, onMessage]); }; diff --git a/react-ui/src/hooks/useServerTime.ts b/react-ui/src/hooks/useServerTime.ts new file mode 100644 index 00000000..89b39775 --- /dev/null +++ b/react-ui/src/hooks/useServerTime.ts @@ -0,0 +1,54 @@ +/* + * @Author: 赵伟 + * @Date: 2024-10-10 08:51:41 + * @Description: 服务器时间 hook + */ + +import { getSeverTimeReq } from '@/services/experiment'; +import { to } from '@/utils/promise'; +import { useCallback, useEffect, useState } from 'react'; + +let globalTimeOffset: number | undefined = undefined; + +export const globalGetSeverTime = async () => { + const requestStartTime = Date.now() + const [res] = await to(getSeverTimeReq()); + const requestEndTime = Date.now() + const requestDuration = (requestEndTime - requestStartTime) / 2; + if (res && res.data) { + const serverDate = new Date(res.data); + const timeOffset = serverDate.getTime() + requestDuration - requestEndTime ; + globalTimeOffset = timeOffset; + return timeOffset + } +}; + +export const now = () => { + return new Date(Date.now() + (globalTimeOffset ?? 0)) +} + +/** 获取服务器时间 */ +export function useServerTime() { + const [timeOffset, setTimeOffset] = useState(globalTimeOffset ?? 0); + + useEffect(() => { + // 获取服务器时间 + const getSeverTime = async () => { + const [res] = await to(globalGetSeverTime()); + if (res) { + setTimeOffset(res) + } + }; + + if (!globalTimeOffset) { + getSeverTime(); + } + }, []); + + const now = useCallback(() => { + return new Date(Date.now() + timeOffset) + }, [timeOffset]) + + + return [now, timeOffset] as const; +} diff --git a/react-ui/src/pages/ActiveLearn/Instance/index.tsx b/react-ui/src/pages/ActiveLearn/Instance/index.tsx index 756acfd4..ba62fae7 100644 --- a/react-ui/src/pages/ActiveLearn/Instance/index.tsx +++ b/react-ui/src/pages/ActiveLearn/Instance/index.tsx @@ -1,5 +1,6 @@ import KFIcon from '@/components/KFIcon'; import { AutoMLTaskType, ExperimentStatus } from '@/enums'; +import { useServerTime } from '@/hooks/useServerTime'; import { getActiveLearnInsReq } from '@/services/activeLearn'; import { NodeStatus } from '@/types'; import { parseJsonText } from '@/utils'; @@ -35,7 +36,8 @@ function ActiveLearnInstance() { const params = useParams(); const instanceId = safeInvoke(Number)(params.id); const evtSourceRef = useRef(null); - const [currentTime, setCurrentTime] = useState(); + const [now] = useServerTime(); + const [currentTime, setCurrentTime] = useState(now()); const finish_time = workflowStatus?.finishedAt; useEffect(() => { @@ -54,13 +56,13 @@ function ActiveLearnInstance() { setCurrentTime(new Date(finish_time)); } else { const timer = setInterval(() => { - setCurrentTime(new Date()); + setCurrentTime(now()); }, 1000); return () => { clearInterval(timer); }; } - }, [finish_time]); + }, [finish_time, now]); // 获取实验实例详情 const getExperimentInsInfo = async (isStatusDetermined: boolean) => { @@ -122,13 +124,13 @@ function ActiveLearnInstance() { if (dataJson) { const nodes = dataJson?.result?.object?.status?.nodes; if (nodes) { + // 节点 + setNodes(nodes); + const workflowStatus = Object.values(nodes).find((node: any) => node.displayName.startsWith(NodePrefix), ) as NodeStatus; - // 节点 - setNodes(nodes); - // 设置工作流状态 if (workflowStatus) { setWorkflowStatus(workflowStatus); diff --git a/react-ui/src/pages/AutoML/Instance/index.tsx b/react-ui/src/pages/AutoML/Instance/index.tsx index 4ffb085a..675b02d4 100644 --- a/react-ui/src/pages/AutoML/Instance/index.tsx +++ b/react-ui/src/pages/AutoML/Instance/index.tsx @@ -1,5 +1,6 @@ import KFIcon from '@/components/KFIcon'; import { AutoMLTaskType, AutoMLType, ExperimentStatus } from '@/enums'; +import { useServerTime } from '@/hooks/useServerTime'; import { getExperimentInsReq } from '@/services/autoML'; import { NodeStatus } from '@/types'; import { parseJsonText } from '@/utils'; @@ -35,7 +36,8 @@ function AutoMLInstance() { const params = useParams(); const instanceId = safeInvoke(Number)(params.id); const evtSourceRef = useRef(null); - const [currentTime, setCurrentTime] = useState(); + const [now] = useServerTime(); + const [currentTime, setCurrentTime] = useState(now()); const finish_time = workflowStatus?.finishedAt; useEffect(() => { @@ -54,13 +56,13 @@ function AutoMLInstance() { setCurrentTime(new Date(finish_time)); } else { const timer = setInterval(() => { - setCurrentTime(new Date()); + setCurrentTime(now()); }, 1000); return () => { clearInterval(timer); }; } - }, [finish_time]); + }, [finish_time, now]); // 获取实验实例详情 const getExperimentInsInfo = async (isStatusDetermined: boolean) => { @@ -126,13 +128,13 @@ function AutoMLInstance() { if (dataJson) { const nodes = dataJson?.result?.object?.status?.nodes; if (nodes) { + // 节点 + setNodes(nodes); + const workflowStatus = Object.values(nodes).find((node: any) => node.displayName.startsWith(NodePrefix), ) as NodeStatus; - // 节点 - setNodes(nodes); - if (workflowStatus) { setWorkflowStatus(workflowStatus); diff --git a/react-ui/src/pages/AutoML/components/ExperimentInstance/index.less b/react-ui/src/pages/AutoML/components/ExperimentInstanceList/index.less similarity index 100% rename from react-ui/src/pages/AutoML/components/ExperimentInstance/index.less rename to react-ui/src/pages/AutoML/components/ExperimentInstanceList/index.less diff --git a/react-ui/src/pages/AutoML/components/ExperimentInstance/index.tsx b/react-ui/src/pages/AutoML/components/ExperimentInstanceList/index.tsx similarity index 83% rename from react-ui/src/pages/AutoML/components/ExperimentInstance/index.tsx rename to react-ui/src/pages/AutoML/components/ExperimentInstanceList/index.tsx index 74cf1115..05d6d786 100644 --- a/react-ui/src/pages/AutoML/components/ExperimentInstance/index.tsx +++ b/react-ui/src/pages/AutoML/components/ExperimentInstanceList/index.tsx @@ -1,20 +1,19 @@ import KFIcon from '@/components/KFIcon'; import { ExperimentStatus } from '@/enums'; import { useCheck } from '@/hooks/useCheck'; -import { experimentStatusInfo } from '@/pages/Experiment/status'; import themes from '@/styles/theme.less'; import { type ExperimentInstance } from '@/types'; -import { elapsedTime, formatDate } from '@/utils/date'; import { to } from '@/utils/promise'; import { modalConfirm } from '@/utils/ui'; import { DoubleRightOutlined } from '@ant-design/icons'; -import { App, Button, Checkbox, ConfigProvider, Typography } from 'antd'; +import { App, Button, Checkbox, ConfigProvider } from 'antd'; import classNames from 'classnames'; import { useEffect, useMemo } from 'react'; import { ExperimentListType, experimentListConfig } from '../ExperimentList/config'; import styles from './index.less'; +import ExperimentInstanceComponent from './instance'; -type ExperimentInstanceProps = { +type ExperimentInstanceListProps = { type: ExperimentListType; experimentInsList?: ExperimentInstance[]; experimentInsTotal: number; @@ -24,7 +23,7 @@ type ExperimentInstanceProps = { onLoadMore?: () => void; }; -function ExperimentInstanceComponent({ +function ExperimentInstanceList({ type, experimentInsList, experimentInsTotal, @@ -32,7 +31,7 @@ function ExperimentInstanceComponent({ onRemove, onTerminate, onLoadMore, -}: ExperimentInstanceProps) { +}: ExperimentInstanceListProps) { const { message } = App.useApp(); const allIntanceIds = useMemo(() => { return experimentInsList?.map((item) => item.id) || []; @@ -171,28 +170,14 @@ function ExperimentInstanceComponent({ > {index + 1} -
- {elapsedTime(item.create_time, item.finish_time)} -
-
- - {formatDate(item.create_time)} - -
-
- - - {experimentStatusInfo[item.status as ExperimentStatus]?.label} - -
+
-
-
{elapsedTime(item.create_time, item.finish_time)}
-
- - {formatDate(item.create_time)} - -
-
-
- - - {experimentStatusInfo[item.status as ExperimentStatus]?.label} - -
+ + +
))} diff --git a/react-ui/src/pages/Workspace/components/UserPoints/index.tsx b/react-ui/src/pages/Workspace/components/UserPoints/index.tsx index 98e63cbf..a6eb3ece 100644 --- a/react-ui/src/pages/Workspace/components/UserPoints/index.tsx +++ b/react-ui/src/pages/Workspace/components/UserPoints/index.tsx @@ -1,5 +1,6 @@ import { PointsStatistics } from '@/pages/Points/index'; import { getPointsStatisticsReq } from '@/services/points'; +import { formatNumber } from '@/utils/format'; import { to } from '@/utils/promise'; import { useNavigate } from '@umijs/max'; import { Typography } from 'antd'; @@ -22,14 +23,16 @@ function UserPoints() { getPointsStatistics(); }, []); + const userCredit = formatNumber(statistics?.userCredit); + return (
当前可用算力积分
- {statistics?.userCredit ?? '--'} + {userCredit}
{ return option && option.label ? option.label : '--'; }; }; + + +/** + * 格式化数字 + * + * @param value - 值、 + * @param toFixed - 保留几位小数 + * @return 格式化的数字,如果不是数字,返回 '--' + */ +export const formatNumber = (value?: number | null, toFixed?: number) : number | string => { + if (typeof value !== "number") { + return '--' + } + + return toFixed ? Number(value).toFixed(toFixed) : value +} diff --git a/react-ui/src/utils/index.ts b/react-ui/src/utils/index.ts index 995f3501..a6fba453 100644 --- a/react-ui/src/utils/index.ts +++ b/react-ui/src/utils/index.ts @@ -348,12 +348,3 @@ export const convertEmptyStringToUndefined = (value?: string): string | undefine return value === '' ? undefined : value; }; - -export const formatNumber = (value?: number | null, toFixed?: number) : number | string => { - if (typeof value !== "number") { - return '--' - } - - return toFixed ? Number(value).toFixed(toFixed) : value -} -