'use client'; import React, { useState, useMemo, useEffect } from 'react'; import { motion, AnimatePresence } from 'framer-motion'; import { Network, Clock, CheckCircle, Search, ChevronRight, ChevronDown, RefreshCw, XCircle, Info, Activity, Pause } from 'lucide-react'; import { cn } from '@/public/lib/utils'; interface TaskExecution { id: string; name: string; displayName: string; status: string; statusCode: number; type: string; dataSize: number; executionTime: number; startTime: number; endTime: number; progress: number; level: number; // 层级:0=主任务,1=子任务 parentId?: string; // 父任务ID isExpanded?: boolean; // 是否展开子任务 subTasks?: TaskExecution[]; // 子任务列表 phases: { initialization?: number; dataLoading?: number; aiProcessing?: number; resultGeneration?: number; dataTransfer?: number; completion?: number; }; taskResult?: any; } interface TaskTimelineProps { tasks: any[]; className?: string; onRefresh?: () => void; isRefreshing?: boolean; onRetryTask?: (taskId: string) => Promise; // 实时监控相关 isPolling?: boolean; lastUpdate?: Date; onTogglePolling?: () => void; } export function NetworkTimeline({ tasks, className, onRefresh, isRefreshing = false, onRetryTask, isPolling = false, lastUpdate, onTogglePolling }: TaskTimelineProps) { const [selectedTask, setSelectedTask] = useState(null); const [filterType, setFilterType] = useState('all'); const [searchTerm, setSearchTerm] = useState(''); const [expandedTasks, setExpandedTasks] = useState>(new Set()); const [timeAgo, setTimeAgo] = useState(''); const [retryingTasks, setRetryingTasks] = useState>(new Set()); // 自动清理重试状态:当任务状态不再是RETRYING时,移除重试状态 useEffect(() => { if (retryingTasks.size === 0) return; const currentRetryingTaskIds = Array.from(retryingTasks); const tasksToRemove: string[] = []; currentRetryingTaskIds.forEach(taskId => { // 检查任务是否还在重试状态 const isStillRetrying = tasks.some(task => { // 检查主任务 if (task.task_id === taskId && task.task_status === 'RETRYING') { return true; } // 检查子任务 if (task.sub_tasks && Array.isArray(task.sub_tasks)) { return task.sub_tasks.some(subTask => subTask.task_id === taskId && subTask.task_status === 'RETRYING' ); } return false; }); if (!isStillRetrying) { tasksToRemove.push(taskId); } }); // 移除不再重试的任务 if (tasksToRemove.length > 0) { setRetryingTasks(prev => { const newSet = new Set(prev); tasksToRemove.forEach(taskId => newSet.delete(taskId)); return newSet; }); } }, [tasks, retryingTasks]); // 更新时间显示 useEffect(() => { if (!lastUpdate) return; const updateTimeAgo = () => { const now = new Date(); const diff = Math.floor((now.getTime() - lastUpdate.getTime()) / 1000); if (diff < 60) { setTimeAgo(`${diff}秒前`); } else if (diff < 3600) { setTimeAgo(`${Math.floor(diff / 60)}分钟前`); } else { setTimeAgo(`${Math.floor(diff / 3600)}小时前`); } }; updateTimeAgo(); const interval = setInterval(updateTimeAgo, 1000); return () => clearInterval(interval); }, [lastUpdate]); // 计算任务统计信息 const taskStats = useMemo(() => { if (!tasks || tasks.length === 0) { return { total: 0, completed: 0, inProgress: 0, failed: 0, pending: 0 }; } const stats = { total: tasks.length, completed: 0, inProgress: 0, failed: 0, pending: 0 }; tasks.forEach((task: any) => { const status = task.task_status; // 成功状态 if (['COMPLETED', 'SUCCESS', 'FINISHED'].includes(status)) { stats.completed++; } // 进行中状态 else if (['IN_PROGRESS', 'RUNNING', 'PROCESSING', 'EXECUTING', 'PAUSED', 'SUSPENDED'].includes(status)) { stats.inProgress++; } // 失败状态 else if (['FAILED', 'FAILURE', 'ERROR', 'CANCELLED', 'TIMEOUT'].includes(status)) { stats.failed++; } // 等待状态 else if (['PENDING', 'QUEUED', 'WAITING', 'SCHEDULED', 'INIT'].includes(status)) { stats.pending++; } // 未知状态默认为待处理 else { stats.pending++; } }); return stats; }, [tasks]); // 主任务列表 - 不依赖展开状态,避免重复计算 const mainTaskExecutions = useMemo((): TaskExecution[] => { if (!tasks || tasks.length === 0) return []; // 添加性能监控 const startTime = Date.now(); console.log(`[性能] 开始计算mainTaskExecutions,任务数量: ${tasks.length}`); // 获取所有任务的真实开始时间,用于计算时间线的基准点 const allStartTimes = tasks.map(task => { const startTime = task.start_time || task.created_at; return new Date(startTime).getTime(); }).filter(time => !isNaN(time)); const globalStartTime = allStartTimes.length > 0 ? Math.min(...allStartTimes) : Date.now(); const result: TaskExecution[] = []; tasks.forEach((task: any) => { // 使用真实的开始时间和结束时间 const realStartTime = task.start_time || task.created_at; const realEndTime = task.end_time || task.updated_at; const taskStartTime = new Date(realStartTime).getTime(); const taskEndTime = new Date(realEndTime).getTime(); const duration = taskEndTime - taskStartTime; // 任务执行阶段分解 const phases = { initialization: Math.min(duration * 0.05, 1000), dataLoading: Math.min(duration * 0.1, 2000), aiProcessing: duration * 0.7, resultGeneration: Math.min(duration * 0.1, 1000), dataTransfer: Math.min(duration * 0.05, 500), completion: Math.min(duration * 0.05, 500) }; // 主任务 - 直接使用新API数据 const mainTask: TaskExecution = { id: task.task_id, name: task.task_name, displayName: getTaskDisplayName(task.task_name), status: task.task_status, // 保持原始状态,不转换 statusCode: getTaskStatusCode(task.task_status), type: getTaskType(task.task_name), dataSize: getTaskDataSize(task), executionTime: duration, startTime: taskStartTime - globalStartTime, endTime: taskEndTime - globalStartTime, progress: getTaskProgress(task), level: 0, isExpanded: false, // 初始状态,后续动态更新 subTasks: [], phases, taskResult: task.task_result // 直接使用原始数据,避免不必要的解析 }; // 预处理子任务数据但不添加到结果中(性能优化:限制处理数量) if (task.task_result?.data && Array.isArray(task.task_result.data)) { // 性能优化:如果子任务过多,只处理前20个,但保留总数信息 const maxSubTasks = 20; const subTaskData = task.task_result.data.length > maxSubTasks ? task.task_result.data.slice(0, maxSubTasks) : task.task_result.data; if (task.task_result.data.length > maxSubTasks) { console.log(`[性能] 任务 ${task.task_id} 有 ${task.task_result.data.length} 个子任务,只处理前 ${maxSubTasks} 个`); } const subTasks = subTaskData.map((subItem: any, subIndex: number) => { const totalCount = task.task_result.total_count || task.task_result.data.length; const subDuration = duration / totalCount; const subStartTime = taskStartTime - globalStartTime + (subIndex * subDuration); const subEndTime = subStartTime + subDuration; const subPhases = { initialization: subDuration * 0.05, dataLoading: subDuration * 0.1, aiProcessing: subDuration * 0.7, resultGeneration: subDuration * 0.1, dataTransfer: subDuration * 0.03, completion: subDuration * 0.02 }; return { id: subItem.video_id || `${task.task_id}-sub-${subIndex}`, name: `${task.task_name}_item_${subIndex + 1}`, displayName: `${getTaskDisplayName(task.task_name)} - 项目 ${subIndex + 1}`, status: subItem.video_status === 1 ? 'COMPLETED' : subIndex < (task.task_result.completed_count || 0) ? 'COMPLETED' : subIndex === (task.task_result.completed_count || 0) ? 'IN_PROGRESS' : 'PENDING', statusCode: subItem.video_status === 1 ? 200 : subIndex < (task.task_result.completed_count || 0) ? 200 : subIndex === (task.task_result.completed_count || 0) ? 202 : 100, type: getTaskType(task.task_name), dataSize: getSubTaskDataSize(subItem), executionTime: subDuration, startTime: subStartTime, endTime: subEndTime, progress: subItem.video_status === 1 ? 100 : subIndex < (task.task_result.completed_count || 0) ? 100 : subIndex === (task.task_result.completed_count || 0) ? (task.task_result.progress_percentage || 0) : 0, level: 1, parentId: task.task_id, phases: subPhases, taskResult: subItem }; }); mainTask.subTasks = subTasks; } // 处理真实的子任务(新API的sub_tasks字段)- 优先使用真实子任务 if (task.sub_tasks && Array.isArray(task.sub_tasks) && task.sub_tasks.length > 0) { const realSubTasks = task.sub_tasks.map((subTask: any, subIndex: number) => { const subRealStartTime = subTask.start_time || subTask.created_at; const subRealEndTime = subTask.end_time || subTask.updated_at; const subTaskStartTime = new Date(subRealStartTime).getTime(); const subTaskEndTime = new Date(subRealEndTime).getTime(); const subTaskDuration = subTaskEndTime - subTaskStartTime; const subPhases = { initialization: subTaskDuration * 0.05, dataLoading: subTaskDuration * 0.1, aiProcessing: subTaskDuration * 0.7, resultGeneration: subTaskDuration * 0.1, dataTransfer: subTaskDuration * 0.03, completion: subTaskDuration * 0.02 }; return { id: subTask.task_id, name: subTask.task_name, displayName: getTaskDisplayName(subTask.task_name), status: subTask.task_status, // 保持原始状态 statusCode: getTaskStatusCode(subTask.task_status), type: getTaskType(subTask.task_name), dataSize: getTaskDataSize(subTask), executionTime: subTaskDuration, startTime: subTaskStartTime - globalStartTime, endTime: subTaskEndTime - globalStartTime, progress: getTaskProgress(subTask), level: 1, parentId: task.task_id, isExpanded: false, subTasks: [], phases: subPhases, taskResult: subTask.task_result // 直接使用原始数据,避免不必要的解析 }; }); // 真实子任务优先:覆盖之前的subTasks mainTask.subTasks = realSubTasks; } result.push(mainTask); }); const endTime = Date.now(); const duration = endTime - startTime; console.log(`[性能] mainTaskExecutions计算完成,耗时: ${duration}ms`); // 如果计算时间超过1秒,记录警告 if (duration > 1000) { console.warn(`[性能警告] mainTaskExecutions计算耗时过长: ${duration}ms,任务数量: ${tasks.length}`); } return result; }, [tasks]); // 完整的任务执行列表 - 包含展开的子任务 const taskExecutions = useMemo((): TaskExecution[] => { const result: TaskExecution[] = []; mainTaskExecutions.forEach(mainTask => { // 更新展开状态 mainTask.isExpanded = expandedTasks.has(mainTask.id); result.push(mainTask); // 如果展开,添加子任务 if (mainTask.isExpanded && mainTask.subTasks && mainTask.subTasks.length > 0) { result.push(...mainTask.subTasks); } }); return result; }, [mainTaskExecutions, expandedTasks]); // 动态获取所有任务类型 - 基于接口数据 const availableTaskTypes = useMemo(() => { const types = new Set(); taskExecutions.forEach(task => { if (task.type) { types.add(task.type); } }); return Array.from(types).sort(); }, [taskExecutions]); // 过滤后的任务执行列表 const filteredTaskExecutions = useMemo(() => { let filtered = taskExecutions; // 按类型过滤 if (filterType !== 'all') { filtered = filtered.filter(task => task.type === filterType); } // 按搜索词过滤 if (searchTerm.trim()) { const searchLower = searchTerm.toLowerCase(); filtered = filtered.filter(task => task.displayName.toLowerCase().includes(searchLower) || task.name.toLowerCase().includes(searchLower) || task.status.toLowerCase().includes(searchLower) || task.type.toLowerCase().includes(searchLower) ); } return filtered; }, [taskExecutions, filterType, searchTerm]); // 格式化时间显示 function formatDateTime(dateString: string | null | undefined): string { if (!dateString) return '未知'; try { const date = new Date(dateString); if (isNaN(date.getTime())) return '无效时间'; // 格式化为本地时间:YYYY-MM-DD HH:mm:ss return date.toLocaleString('zh-CN', { year: 'numeric', month: '2-digit', day: '2-digit', hour: '2-digit', minute: '2-digit', second: '2-digit', hour12: false }); } catch (error) { console.warn('时间格式化失败:', error, dateString); return '格式错误'; } } // 解析任务结果 - 处理JSON字符串(性能优化版本) function parseTaskResult(taskResult: any) { if (!taskResult) return null; if (typeof taskResult === 'string') { // 性能优化:快速检查是否为JSON格式 const trimmed = taskResult.trim(); if (!trimmed.startsWith('{') && !trimmed.startsWith('[')) { // 不是JSON格式,直接返回原始字符串,避免JSON.parse异常 return { raw_text: trimmed }; } try { return JSON.parse(taskResult); } catch (error) { // 静默处理解析错误,避免大量日志输出影响性能 return { raw_text: trimmed, parse_error: true }; } } return taskResult; } // 获取子任务完成状况文本 (如: "10/12") function getSubTaskStatus(task: any): string | null { if (!task.sub_tasks || !Array.isArray(task.sub_tasks) || task.sub_tasks.length === 0) { return null; } const completedSubTasks = task.sub_tasks.filter((sub: any) => ['SUCCESS', 'COMPLETED', 'FINISHED'].includes(sub.task_status) ).length; return `${completedSubTasks}/${task.sub_tasks.length}`; } // 获取任务进度 function getTaskProgress(task: any): number { // 如果有子任务,优先基于子任务完成情况计算进度 if (task.sub_tasks && Array.isArray(task.sub_tasks) && task.sub_tasks.length > 0) { const completedSubTasks = task.sub_tasks.filter((sub: any) => ['SUCCESS', 'COMPLETED', 'FINISHED'].includes(sub.task_status) ).length; return Math.round((completedSubTasks / task.sub_tasks.length) * 100); } // 性能优化:直接检查task.progress字段,避免JSON解析 if (typeof task.progress === 'number' && task.progress >= 0 && task.progress <= 100) { return task.progress; } // 检查task_result是否已经是对象格式 if (task.task_result && typeof task.task_result === 'object' && task.task_result.progress_percentage) { return task.task_result.progress_percentage; } // 最后根据状态推断进度 switch (task.task_status) { case 'SUCCESS': case 'COMPLETED': case 'FINISHED': return 100; case 'FAILED': case 'FAILURE': case 'ERROR': case 'CANCELLED': return 0; case 'IN_PROGRESS': case 'RUNNING': case 'PROCESSING': case 'RETRYING': return 50; // 默认50% default: return 0; } } // 获取任务的开始和结束时间 function getTaskTimes(task: TaskExecution) { // 首先尝试从主任务列表中查找 const originalTask = tasks.find((t: any) => t.task_id === task.id); if (originalTask) { return { startTime: originalTask.start_time || originalTask.created_at, endTime: originalTask.end_time || originalTask.updated_at, createdAt: originalTask.created_at, updatedAt: originalTask.updated_at }; } // 如果是子任务,从父任务的sub_tasks中查找 if (task.level === 1 && task.parentId) { const parentTask = tasks.find((t: any) => t.task_id === task.parentId); if (parentTask && parentTask.sub_tasks) { const subTask = parentTask.sub_tasks.find((st: any) => st.task_id === task.id); if (subTask) { return { startTime: subTask.start_time || subTask.created_at, endTime: subTask.end_time || subTask.updated_at, createdAt: subTask.created_at, updatedAt: subTask.updated_at }; } } } // 如果都找不到,返回空值 return { startTime: null, endTime: null, createdAt: null, updatedAt: null }; } // 辅助函数 function getTaskDisplayName(taskName: string): string { const nameMap: Record = { 'generate_character': '角色生成', 'generate_character_image': '角色图片生成', 'generate_sketch': '草图生成', 'generate_shot_sketch': '分镜生成', 'generate_video': '视频生成', 'generate_videos': '视频生成', 'generate_music': '音乐生成', 'final_composition': '最终合成', 'refine_orginal_script': '脚本优化', 'generate_production_bible': '制作手册生成', 'generate_production_bible_json': '制作手册JSON生成' }; return nameMap[taskName] || taskName; } function getTaskMethod(taskName: string): string { return taskName.includes('generate') ? 'POST' : 'GET'; } function getTaskStatusCode(status: string): number { const statusMap: Record = { // 成功状态 'COMPLETED': 200, 'SUCCESS': 200, 'FINISHED': 200, // 进行中状态 'IN_PROGRESS': 202, 'RUNNING': 202, 'PROCESSING': 202, 'EXECUTING': 202, 'RETRYING': 202, // 新增:重试状态 // 等待状态 'PENDING': 100, 'QUEUED': 100, 'WAITING': 100, 'SCHEDULED': 100, 'INIT': 100, // 新增:初始化状态 // 失败状态 'FAILED': 500, 'FAILURE': 500, 'ERROR': 500, 'CANCELLED': 499, 'TIMEOUT': 408, // 暂停状态 'PAUSED': 202, 'SUSPENDED': 202 }; return statusMap[status] || 200; } function getTaskType(taskName: string): string { const typeMap: Record = { 'generate_character': 'AI', 'generate_character_image': 'AI', 'generate_sketch': 'AI', 'generate_shot_sketch': 'AI', 'generate_video': 'Video', 'generate_videos': 'Video', 'generate_music': 'Audio', 'final_composition': 'Comp', 'refine_orginal_script': 'Script', 'generate_production_bible': 'Doc', 'generate_production_bible_json': 'Data' }; return typeMap[taskName] || 'Task'; } function getTaskDataSize(task: any): number { const baseSize = 1024; // 1KB base const dataCount = task.task_result?.total_count || 1; const completedCount = task.task_result?.completed_count || 0; return baseSize * completedCount * (Math.random() * 5 + 1); } function getSubTaskDataSize(subItem: any): number { const baseSize = 512; // 0.5KB base for sub-items if (subItem.urls && subItem.urls.length > 0) { // 如果有URL,估算为较大的文件 return baseSize * 10 * (Math.random() * 3 + 1); } return baseSize * (Math.random() * 2 + 1); } // 获取任务进度数量显示 - 基于sub_tasks的实际完成状态 function getTaskProgressCount(task: any): string { // 如果有子任务,基于子任务的完成状态计算进度 if (task.sub_tasks && Array.isArray(task.sub_tasks) && task.sub_tasks.length > 0) { const totalSubTasks = task.sub_tasks.length; const completedSubTasks = task.sub_tasks.filter((subTask: any) => ['SUCCESS', 'COMPLETED', 'FINISHED'].includes(subTask.task_status) ).length; return `${completedSubTasks}/${totalSubTasks}`; } // 如果没有子任务,基于主任务状态 if (['SUCCESS', 'COMPLETED', 'FINISHED'].includes(task.task_status)) { return '1/1'; // 单任务已完成 } if (['IN_PROGRESS', 'RUNNING', 'PROCESSING'].includes(task.task_status)) { return '0/1'; // 单任务进行中 } if (['FAILED', 'FAILURE', 'ERROR'].includes(task.task_status)) { return '0/1'; // 单任务失败 } // 其他情况(等待、初始化等) return '0/1'; } // 获取任务进度百分比显示 - 与任务执行状态面板完全一致 function getTaskProgressPercentage(task: any): string { const completed = task.task_result?.completed_count || 0; const total = task.task_result?.total_count || 0; const progress = task.task_result?.progress_percentage || 0; // 如果任务已完成,显示100% if (task.task_status === 'COMPLETED') { return '100%'; } // 如果任务进行中,优先使用API返回的progress_percentage if (task.task_status === 'IN_PROGRESS') { if (progress > 0) { return `${progress}%`; } // 如果没有progress_percentage,根据completed/total计算 if (total > 0) { return `${Math.round((completed / total) * 100)}%`; } } // 其他情况显示0% return '0%'; } // 获取任务状态显示文本 - 与任务执行状态面板完全一致 function getTaskStatusText(status: string): string { const statusTextMap: Record = { // 成功状态 'SUCCESS': '已完成', 'COMPLETED': '已完成', 'FINISHED': '已完成', // 进行中状态 'IN_PROGRESS': '进行中', 'RUNNING': '运行中', 'PROCESSING': '处理中', 'EXECUTING': '执行中', 'RETRYING': '重试中', // 新增:重试状态 // 等待状态 'PENDING': '待处理', 'QUEUED': '队列中', 'WAITING': '等待中', 'SCHEDULED': '已调度', 'INIT': '初始化', // 新增:接口实际返回的状态 // 失败状态 'FAILED': '失败', 'FAILURE': '失败', 'ERROR': '错误', 'CANCELLED': '已取消', 'TIMEOUT': '超时', // 暂停状态 'PAUSED': '已暂停', 'SUSPENDED': '已挂起' }; return statusTextMap[status] || status; } // 获取任务状态颜色 - 更明显的颜色区分 function getTaskStatusColor(status: string): string { // 成功状态 - 绿色系 if (['SUCCESS', 'COMPLETED', 'FINISHED'].includes(status)) { return 'text-emerald-400'; } // 进行中状态 - 蓝色系 else if (['IN_PROGRESS', 'RUNNING', 'PROCESSING', 'EXECUTING'].includes(status)) { return 'text-cyan-400'; } // 等待状态 - 黄色系 else if (['PENDING', 'QUEUED', 'WAITING', 'SCHEDULED', 'INIT'].includes(status)) { return 'text-amber-400'; } // 失败状态 - 红色系 else if (['FAILED', 'FAILURE', 'ERROR'].includes(status)) { return 'text-rose-400'; } // 取消状态 - 橙色系 else if (['CANCELLED', 'TIMEOUT'].includes(status)) { return 'text-orange-400'; } // 暂停状态 - 紫色系 else if (['PAUSED', 'SUSPENDED'].includes(status)) { return 'text-purple-400'; } // 默认状态 else { return 'text-gray-400'; } } // 获取进度条背景颜色类名 function getProgressBarColor(status: string): string { switch (status) { case 'COMPLETED': return 'bg-emerald-500'; // 绿色 case 'IN_PROGRESS': return 'bg-cyan-500'; // 蓝色 case 'FAILED': return 'bg-rose-500'; // 红色 case 'PENDING': default: return 'bg-amber-500'; // 黄色 } } // 获取任务错误信息 function getTaskErrorInfo(task: any): { hasError: boolean; errorMessage: string; errorCode?: string } { if (task.task_status !== 'FAILED') { return { hasError: false, errorMessage: '' }; } // 从task_result中提取错误信息 const errorMessage = task.task_result?.error_message || task.task_result?.message || task.error_message || '任务执行失败,请重试'; const errorCode = task.task_result?.error_code || task.error_code || 'UNKNOWN_ERROR'; return { hasError: true, errorMessage, errorCode }; } // 处理重试任务 const handleRetryTask = async (taskId: string) => { if (onRetryTask) { try { // 添加到重试中的任务集合 setRetryingTasks(prev => new Set(prev).add(taskId)); // 调用父组件的重试逻辑(包含乐观更新) await onRetryTask(taskId); console.log(`任务 ${taskId} 重试请求已发送`); } catch (error) { console.error('重试任务失败:', error); // 重试失败时立即移除重试状态 setRetryingTasks(prev => { const newSet = new Set(prev); newSet.delete(taskId); return newSet; }); } // 注意:成功情况下不立即移除重试状态,让乐观更新的RETRYING状态显示一段时间 // 重试状态会在数据刷新后自然消失(当任务状态从RETRYING变为其他状态时) } }; // 格式化文件大小 function formatSize(bytes: number): string { if (bytes < 1024) return `${bytes} B`; if (bytes < 1024 * 1024) return `${(bytes / 1024).toFixed(1)} KB`; return `${(bytes / (1024 * 1024)).toFixed(1)} MB`; } // 智能时间格式化 - 提升用户体验 function formatTime(ms: number): string { if (ms < 1000) { return `${Math.round(ms)}ms`; } const seconds = Math.floor(ms / 1000); // 小于1分钟:显示秒 if (seconds < 60) { return `${seconds}s`; } // 1分钟到59分钟:显示分钟和秒 if (seconds < 3600) { const minutes = Math.floor(seconds / 60); const remainingSeconds = seconds % 60; if (remainingSeconds === 0) { return `${minutes}m`; } return `${minutes}m ${remainingSeconds}s`; } // 1小时到23小时:显示小时和分钟 if (seconds < 86400) { const hours = Math.floor(seconds / 3600); const remainingMinutes = Math.floor((seconds % 3600) / 60); if (remainingMinutes === 0) { return `${hours}h`; } return `${hours}h ${remainingMinutes}m`; } // 24小时以上:显示天和小时 const days = Math.floor(seconds / 86400); const remainingHours = Math.floor((seconds % 86400) / 3600); if (remainingHours === 0) { return `${days}d`; } return `${days}d ${remainingHours}h`; } // 获取时间显示的颜色类名(基于时长) function getTimeDisplayColor(ms: number): string { const seconds = Math.floor(ms / 1000); if (ms < 1000) return 'text-gray-400'; // 毫秒 - 灰色 if (seconds < 60) return 'text-gray-200'; // 秒 - 浅灰 if (seconds < 3600) return 'text-white'; // 分钟 - 白色 if (seconds < 86400) return 'text-amber-300'; // 小时 - 琥珀色 return 'text-rose-300'; // 天 - 玫瑰色(警示长时间) } // 估算剩余时间(基于当前进度和已用时间) function estimateRemainingTime(task: any): string | null { if (task.task_status !== 'IN_PROGRESS') return null; const progress = task.task_result?.progress_percentage || 0; if (progress <= 0 || progress >= 100) return null; const now = new Date().getTime(); const startTime = new Date(task.created_at).getTime(); const elapsedTime = now - startTime; // 基于当前进度估算总时间 const estimatedTotalTime = (elapsedTime / progress) * 100; const remainingTime = estimatedTotalTime - elapsedTime; if (remainingTime <= 0) return null; return formatTime(remainingTime); } // 获取状态颜色 function getStatusColor(status: number): string { if (status >= 200 && status < 300) return 'text-green-400'; if (status >= 300 && status < 400) return 'text-yellow-400'; if (status >= 400) return 'text-red-400'; return 'text-blue-400'; } // 展开/折叠任务 - 优化版本 const toggleTaskExpansion = React.useCallback((taskId: string) => { setExpandedTasks(prev => { const newSet = new Set(prev); if (newSet.has(taskId)) { newSet.delete(taskId); } else { newSet.add(taskId); } return newSet; }); }, []); // 计算时间线位置 const maxTime = Math.max(...(filteredTaskExecutions.length > 0 ? filteredTaskExecutions.map((task: TaskExecution) => task.endTime) : [0])); return (
{/* 工具栏 */}

任务执行时间线

{/* 任务统计 */}
总任务: {taskStats.total}
{taskStats.completed}
{taskStats.inProgress}
{taskStats.failed > 0 && (
{taskStats.failed}
)}
{taskStats.pending}
{/* 实时监控控制 */} {onTogglePolling && (
{lastUpdate && ( 最后更新: {timeAgo} )}
)} {/* 搜索框 */}
setSearchTerm(e.target.value)} className="pl-10 pr-10 py-2 bg-gray-800 border border-gray-700 rounded-lg text-sm text-white placeholder-gray-400 focus:outline-none focus:border-cyan-500 focus:ring-2 focus:ring-cyan-500/20 transition-all duration-200 hover:border-gray-600" /> {searchTerm && ( )}
{/* 类型过滤 - 基于接口数据动态生成 */}
{/* 重置过滤器按钮 */} {(searchTerm || filterType !== 'all') && ( )}
{/* 主内容区域 */}
{/* 左侧任务列表 */}
{/* 列表头部 */}
状态
{/* 展开/折叠按钮列 */}
任务名称
状态
类型
进度
时间信息
{/* 任务列表 */}
{filteredTaskExecutions.length === 0 ? (

没有找到匹配的任务

{(searchTerm || filterType !== 'all') && (

尝试调整搜索条件或过滤器

)}
) : ( filteredTaskExecutions.map((task, index) => (
setSelectedTask(selectedTask === task.id ? null : task.id)} > {/* 状态图标 */}
{task.statusCode === 200 && } {task.statusCode === 202 && } {task.statusCode === 100 && } {task.statusCode >= 400 && }
{/* 展开/折叠按钮 */}
{task.level === 0 && task.subTasks && task.subTasks.length > 0 && ( { e.stopPropagation(); toggleTaskExpansion(task.id); }} className="p-0.5 hover:bg-gray-700 rounded text-gray-400 hover:text-white transition-colors duration-150" whileHover={{ scale: 1.1 }} whileTap={{ scale: 0.95 }} > )}
{/* 任务名称 */}
{task.level === 1 && "└ "} {task.displayName} {/* 错误信息和重试按钮 */} {(task.statusCode >= 400 || task.status === 'RETRYING') && (
{/* 错误信息悬停提示 */}
{/* 悬停时显示的错误信息卡片 */}
{/* 错误标题 */}

任务执行失败

{/* 错误详情 */} {(() => { const originalTask = tasks.find((t: any) => t.task_id === task.id); if (!originalTask) return null; const errorInfo = getTaskErrorInfo(originalTask); return (
{/* 错误消息 */}
错误信息:
{errorInfo.errorMessage || '未知错误'}
{/* 错误代码 */} {errorInfo.errorCode && (
错误代码:
{errorInfo.errorCode}
)} {/* 任务信息 */}
任务详情:
任务ID: {task.id}
任务类型: {task.type}
失败时间: {(() => { const taskTimes = getTaskTimes(task); return taskTimes.endTime ? formatDateTime(taskTimes.endTime) : '未知'; })()}
); })()} {/* 小三角箭头 */}
{/* 重试按钮 */} {onRetryTask && ( )}
)}
{/* 状态 */}
{getTaskStatusText(task.status)}
{/* 类型 */}
{task.type}
{/* 进度 */}
{task.level === 0 ? ( // 主任务显示 completed/total 格式 (如: 14/16 或 16/16) getTaskProgressCount(tasks.find((t: any) => t.task_id === task.id) || {}) ) : ( // 子任务显示百分比格式 task.status === 'SUCCESS' || task.status === 'COMPLETED' ? '100%' : task.status === 'IN_PROGRESS' ? `${task.progress}%` : task.status === 'FAILED' ? '0%' : '0%' )}
{/* 时间信息 */}
{formatTime(task.executionTime)} {/* 显示开始时间 */} {(() => { const taskTimes = getTaskTimes(task); return taskTimes.startTime ? ( {new Date(taskTimes.startTime).toLocaleTimeString('zh-CN', { hour: '2-digit', minute: '2-digit' })} ) : null; })()} {/* 显示结束时间 */} {(() => { const taskTimes = getTaskTimes(task); return taskTimes.endTime ? ( {new Date(taskTimes.endTime).toLocaleTimeString('zh-CN', { hour: '2-digit', minute: '2-digit' })} ) : null; })()} {/* 剩余时间估算 */} {task.level === 0 && (() => { const originalTask = tasks.find((t: any) => t.task_id === task.id); const remainingTime = originalTask ? estimateRemainingTime(originalTask) : null; return remainingTime ? ( ~{remainingTime} ) : null; })()}
)) )}
{/* 右侧时间线 */}
时间线视图 (总耗时: {formatTime(maxTime)})
{/* 时间刻度 */}
{Array.from({ length: 6 }, (_, i) => { const timeValue = (maxTime / 5) * i; return ( {formatTime(timeValue)} ); })}
{/* 时间刻度线 */}
{Array.from({ length: 6 }, (_, i) => (
))}
{/* 瀑布图 */}
{filteredTaskExecutions.length === 0 ? (

没有任务数据

) : ( filteredTaskExecutions.map((task) => (
{/* 任务条 */}
= 400 ? "bg-rose-500" : "bg-amber-500", selectedTask === task.id ? "opacity-100" : "opacity-80 hover:opacity-95", task.level === 1 && "opacity-70" )} style={{ left: `${(task.startTime / maxTime) * 100}%`, width: `${Math.max((task.executionTime / maxTime) * 100, 0.5)}%` }} onClick={() => setSelectedTask(task.id)} title={(() => { const taskTimes = getTaskTimes(task); return `${task.displayName} 执行时间: ${formatTime(task.executionTime)} 状态: ${getTaskStatusText(task.status)} ${task.status === 'IN_PROGRESS' ? `进度: ${task.progress}%` : ''} 开始时间: ${formatDateTime(taskTimes.startTime)} 结束时间: ${formatDateTime(taskTimes.endTime)}`; })()} > {/* 基于状态的单色进度条 */}
{/* 背景条 */}
{/* 进度条 - 根据任务状态和进度显示 */} {task.status === 'IN_PROGRESS' && (
)} {/* 完成状态显示满进度 */} {task.status === 'COMPLETED' && (
)} {/* 失败状态显示部分进度 */} {task.status === 'FAILED' && (
)} {/* 等待状态显示微弱指示 */} {task.status === 'PENDING' && (
)} {/* 智能百分比文字显示 - 只在主任务且有足够空间时显示 */} {(() => { const barWidth = (task.executionTime / maxTime) * 100; const showPercentage = task.level === 0 && barWidth > 6; // 主任务且宽度足够 if (!showPercentage) return null; const getPercentageText = () => { switch (task.status) { case 'IN_PROGRESS': return `${task.progress}%`; case 'COMPLETED': return '100%'; case 'FAILED': return `${task.progress}%`; case 'PENDING': return '0%'; default: return ''; } }; const percentageText = getPercentageText(); if (!percentageText) return null; // 根据进度条宽度和进度值决定文字位置 const progressWidth = task.status === 'IN_PROGRESS' ? task.progress : task.status === 'COMPLETED' ? 100 : task.status === 'FAILED' ? Math.max(task.progress, 15) : 8; const shouldCenterInProgress = task.status === 'IN_PROGRESS' && progressWidth > 30; const shouldCenterInBar = (task.status === 'COMPLETED') || (task.status === 'FAILED' && progressWidth > 25); return (
12 ? '11px' : '10px', fontWeight: '600' }} > {percentageText}
); })()}
{/* 子任务进度指示器和百分比 */} {task.level === 1 && task.status === 'IN_PROGRESS' && ( <>
{/* 子任务百分比 - 只在有足够空间时显示 */} {(() => { const barWidth = (task.executionTime / maxTime) * 100; if (barWidth < 4) return null; // 子任务条太窄不显示 return (
{task.progress}%
); })()} )}
)) )}
{/* 详细信息面板 */} {selectedTask && ( {(() => { const task = taskExecutions.find((t: TaskExecution) => t.id === selectedTask); if (!task) return null; return (

{task.level === 0 ? '主任务详情' : '子任务详情'}

任务名称: {task.displayName}
任务ID: {task.name}
状态: {getTaskStatusText(task.status)}
类型: {task.type}
层级: {task.level === 0 ? '主任务' : '子任务'}
{/* 子任务完成状况 */} {task.level === 0 && (() => { const originalTask = tasks.find((t: any) => t.task_id === task.id); const subTaskStatus = originalTask ? getSubTaskStatus(originalTask) : null; return subTaskStatus ? (
子任务进度: {subTaskStatus}
) : null; })()} {/* 时间信息显示 */} {(() => { const taskTimes = getTaskTimes(task); return ( <>
⏰ 时间信息
开始时间: {formatDateTime(taskTimes.startTime)}
结束时间: {formatDateTime(taskTimes.endTime)}
{taskTimes.createdAt && taskTimes.createdAt !== taskTimes.startTime && (
创建时间: {formatDateTime(taskTimes.createdAt)}
)} {taskTimes.updatedAt && taskTimes.updatedAt !== taskTimes.endTime && (
更新时间: {formatDateTime(taskTimes.updatedAt)}
)} {taskTimes.startTime && taskTimes.endTime && (
执行时长: {formatTime(task.executionTime)}
)}
); })()} {task.status === 'IN_PROGRESS' && ( <>
进度: {task.progress}%
{(() => { const originalTask = tasks.find((t: any) => t.task_id === task.id); const remainingTime = originalTask ? estimateRemainingTime(originalTask) : null; return remainingTime ? (
预计剩余: {remainingTime}
) : null; })()} )} {task.parentId && (
父任务: {task.parentId}
)} {/* 错误信息显示 */} {task.statusCode >= 400 && (() => { const originalTask = tasks.find((t: any) => t.task_id === task.id); if (originalTask) { const errorInfo = getTaskErrorInfo(originalTask); return (
错误信息
{errorInfo.errorMessage}
{errorInfo.errorCode && (
代码: {errorInfo.errorCode}
)} {onRetryTask && ( )}
); } return null; })()}

执行分析

总时间: {formatTime(task.executionTime)}
AI处理: {formatTime(task.phases.aiProcessing || 0)}
{/*
数据大小: {formatSize(task.dataSize)}
*/} {task.level === 0 && task.taskResult?.total_count && (
子任务数: {task.taskResult.total_count}
)} {task.level === 0 && task.taskResult?.completed_count !== undefined && (
已完成: {task.taskResult.completed_count}/{task.taskResult.total_count}
)} {task.level === 1 && task.taskResult?.urls && (
输出文件: {task.taskResult.urls.length} 个
)}
); })()}
)}
); } export default NetworkTimeline;