diff --git a/components/dashboard/network-timeline.tsx b/components/dashboard/network-timeline.tsx index 3a2b768..52da35d 100644 --- a/components/dashboard/network-timeline.tsx +++ b/components/dashboard/network-timeline.tsx @@ -852,6 +852,10 @@ export function NetworkTimeline({ // 智能时间格式化 - 提升用户体验 function formatTime(ms: number): string { + // 统一兜底:避免负值/NaN 导致显示 "-Xs" + if (!Number.isFinite(ms) || ms <= 0) { + return '0s'; + } if (ms < 1000) { return `${Math.round(ms)}ms`; } @@ -983,8 +987,51 @@ export function NetworkTimeline({ }); }, []); - // 计算时间线位置 + // 计算时间线位置(用于绘制可视化相对宽度) const maxTime = Math.max(...(filteredTaskExecutions.length > 0 ? filteredTaskExecutions.map((task: TaskExecution) => task.endTime) : [0])); + + // 计算“项目总耗时”应独立于过滤条件,直接基于原始 tasks + // 规则: + // 1) 全局开始时间 = 所有任务 start_time/created_at 的最小值 + // 2) 全局结束时间 = + // - 若存在已结束任务:取已结束任务 end_time 的最大值 + // - 否则:取所有任务 updated_at 的最大值(表示仍在进行中的最近更新时间) + const projectDurationMs = React.useMemo(() => { + if (!tasks || tasks.length === 0) return 0; + + const toMs = (v: any) => { + const n = new Date(v as string).getTime(); + return Number.isNaN(n) ? NaN : n; + }; + + // 1) 全局开始 + const allStarts = tasks + .map((t: any) => toMs(t.start_time || t.created_at)) + .filter((n: number) => !Number.isNaN(n)); + const globalStart = allStarts.length > 0 ? Math.min(...allStarts) : Date.now(); + + // 2) 终止态集合 + const terminal = new Set(['COMPLETED','SUCCESS','FINISHED','FAILED','FAILURE','ERROR','CANCELLED','TIMEOUT']); + + // 3) 已结束任务的结束时间 + const endedEnds = tasks + .map((t: any) => terminal.has(t.task_status) ? toMs(t.end_time) : NaN) + .filter((n: number) => !Number.isNaN(n)); + + let globalEnd: number | null = null; + if (endedEnds.length > 0) { + globalEnd = Math.max(...endedEnds); + } else { + // 4) 无已结束任务,则取最近一次 updated_at 代表“当前进度” + const latestUpdated = tasks + .map((t: any) => toMs(t.updated_at || t.created_at)) + .filter((n: number) => !Number.isNaN(n)); + if (latestUpdated.length > 0) globalEnd = Math.max(...latestUpdated); + } + + if (!globalEnd || Number.isNaN(globalEnd)) return 0; + return Math.max(0, globalEnd - globalStart); + }, [tasks]); return (