时间线优化

This commit is contained in:
qikongjian 2025-09-07 06:21:49 +08:00
parent 20762e806c
commit f8a88bcadf

View File

@ -13,7 +13,9 @@ import {
XCircle,
Info,
Activity,
Pause
Pause,
Copy,
Check
} from 'lucide-react';
import { cn } from '@/public/lib/utils';
@ -72,6 +74,7 @@ export function NetworkTimeline({
const [expandedTasks, setExpandedTasks] = useState<Set<string>>(new Set());
const [timeAgo, setTimeAgo] = useState<string>('');
const [retryingTasks, setRetryingTasks] = useState<Set<string>>(new Set());
const [copiedTaskId, setCopiedTaskId] = useState<string | null>(null);
// 自动清理重试状态当任务状态不再是RETRYING时移除重试状态
useEffect(() => {
@ -843,6 +846,34 @@ export function NetworkTimeline({
}
};
// 复制错误信息到剪贴板
const copyErrorInfo = async (taskId: string, errorMessage: string, errorCode?: string) => {
try {
const errorText = `任务ID: ${taskId}\n错误信息: ${errorMessage}${errorCode ? `\n错误代码: ${errorCode}` : ''}`;
await navigator.clipboard.writeText(errorText);
// 显示复制成功反馈
setCopiedTaskId(taskId);
setTimeout(() => {
setCopiedTaskId(null);
}, 2000);
} catch (error) {
console.error('复制失败:', error);
// 降级方案:使用传统的复制方法
const textArea = document.createElement('textarea');
textArea.value = `任务ID: ${taskId}\n错误信息: ${errorMessage}${errorCode ? `\n错误代码: ${errorCode}` : ''}`;
document.body.appendChild(textArea);
textArea.select();
document.execCommand('copy');
document.body.removeChild(textArea);
setCopiedTaskId(taskId);
setTimeout(() => {
setCopiedTaskId(null);
}, 2000);
}
};
// 格式化文件大小
function formatSize(bytes: number): string {
if (bytes < 1024) return `${bytes} B`;
@ -1177,7 +1208,14 @@ export function NetworkTimeline({
<div className="w-16 text-center"></div>
<div className="w-20 text-center"></div>
<div className="w-48 text-center"></div>
<div className="flex-1 text-center">线</div>
<div className="flex-1 text-center">
<div className="flex items-center justify-center gap-2">
<span>线</span>
<span className="text-cyan-400 font-bold">
(: {formatTime(projectDurationMs)})
</span>
</div>
</div>
</div>
{/* 任务列表 */}
@ -1197,11 +1235,9 @@ export function NetworkTimeline({
<div
key={task.id}
className={cn(
"flex items-center px-4 py-1.5 text-sm border-b border-gray-800/30 cursor-pointer hover:bg-gray-800/50 transition-all duration-200 ease-out",
selectedTask === task.id && "bg-blue-600/20",
"flex items-center px-4 py-1.5 text-sm border-b border-gray-800/30 transition-all duration-200 ease-out",
task.level === 1 && "ml-4 bg-gray-900/30" // 子任务缩进和背景区分
)}
onClick={() => setSelectedTask(selectedTask === task.id ? null : task.id)}
>
{/* 状态图标 */}
<div className="w-8 flex justify-center">
@ -1214,14 +1250,9 @@ export function NetworkTimeline({
{/* 展开/折叠按钮 */}
<div className="w-6 flex justify-center">
{task.level === 0 && task.subTasks && task.subTasks.length > 0 && (
<motion.button
onClick={(e) => {
e.stopPropagation();
toggleTaskExpansion(task.id);
}}
className="p-0.5 hover:bg-gray-700 rounded text-gray-400 hover:text-white transition-colors duration-150"
<motion.div
className="p-0.5 text-gray-400 transition-colors duration-150"
whileHover={{ scale: 1.1 }}
whileTap={{ scale: 0.95 }}
>
<motion.div
animate={{
@ -1234,15 +1265,26 @@ export function NetworkTimeline({
>
<ChevronRight className="w-3 h-3" />
</motion.div>
</motion.button>
</motion.div>
)}
</div>
{/* 任务名称 */}
<div className={cn(
"w-56 flex items-center gap-2",
task.level === 1 && "pl-4"
)}>
<div
className={cn(
"w-56 flex items-center gap-2 cursor-pointer hover:bg-gray-800/50 rounded px-2 py-1 transition-all duration-200",
task.level === 1 && "pl-4",
selectedTask === task.id && "bg-blue-600/20"
)}
onClick={() => {
// 如果是主任务且有子任务,切换展开状态
if (task.level === 0 && task.subTasks && task.subTasks.length > 0) {
toggleTaskExpansion(task.id);
}
// 切换选中状态
setSelectedTask(selectedTask === task.id ? null : task.id);
}}
>
<span className={cn(
"truncate font-medium",
task.level === 0 ? "text-white text-base" : "text-sm text-gray-300"
@ -1264,9 +1306,52 @@ export function NetworkTimeline({
<div className="absolute left-0 top-8 z-50 invisible group-hover:visible opacity-0 group-hover:opacity-100 transition-all duration-200">
<div className="bg-gray-900 border border-red-500/30 rounded-lg shadow-xl p-4 min-w-80 max-w-96">
{/* 错误标题 */}
<div className="flex items-center gap-2 mb-3">
<XCircle className="w-5 h-5 text-red-400 flex-shrink-0" />
<h4 className="text-red-400 font-semibold text-base"></h4>
<div className="flex items-center justify-between mb-3">
<div className="flex items-center gap-2">
<XCircle className="w-5 h-5 text-red-400 flex-shrink-0" />
<h4 className="text-red-400 font-semibold text-base"></h4>
</div>
{/* 复制按钮 */}
{(() => {
let errorInfo;
if (task.level === 0) {
const originalTask = tasks.find((t: any) => t.task_id === task.id);
if (!originalTask) return null;
errorInfo = getTaskErrorInfo(originalTask);
} else {
errorInfo = getSubTaskErrorInfo(task.id);
}
if (!errorInfo.hasError) return null;
return (
<button
onClick={(e) => {
e.stopPropagation();
copyErrorInfo(task.id, errorInfo.errorMessage, errorInfo.errorCode);
}}
className={cn(
"flex items-center gap-1 px-2 py-1 text-xs rounded transition-colors",
copiedTaskId === task.id
? "bg-green-600/20 text-green-400"
: "bg-gray-700/50 text-gray-300 hover:bg-gray-600/50 hover:text-white"
)}
title="复制错误信息"
>
{copiedTaskId === task.id ? (
<>
<Check className="w-3 h-3" />
<span></span>
</>
) : (
<>
<Copy className="w-3 h-3" />
<span></span>
</>
)}
</button>
);
})()}
</div>
{/* 错误详情 */}
@ -1609,24 +1694,50 @@ export function NetworkTimeline({
{errorInfo.errorCode && (
<span className="text-red-400 text-[10px] font-mono">: {errorInfo.errorCode}</span>
)}
{onRetryTask && (
<div className="flex items-center gap-1 ml-auto">
{/* 复制错误信息按钮 */}
<button
onClick={() => handleRetryTask(task.id)}
disabled={retryingTasks.has(task.id) || task.status === 'RETRYING'}
onClick={() => copyErrorInfo(task.id, errorInfo.errorMessage, errorInfo.errorCode)}
className={cn(
"ml-auto flex items-center gap-1 px-2 py-1 text-white text-[10px] rounded transition-colors",
(retryingTasks.has(task.id) || task.status === 'RETRYING')
? "bg-yellow-600 cursor-not-allowed"
: "bg-blue-600 hover:bg-blue-700"
"flex items-center gap-1 px-2 py-1 text-[10px] rounded transition-colors",
copiedTaskId === task.id
? "bg-green-600/20 text-green-400"
: "bg-gray-700/50 text-gray-300 hover:bg-gray-600/50 hover:text-white"
)}
title="复制错误信息"
>
<RefreshCw className={cn(
"w-3 h-3",
(retryingTasks.has(task.id) || task.status === 'RETRYING') && "animate-spin"
)} />
{(retryingTasks.has(task.id) || task.status === 'RETRYING') ? "重试中..." : "重试任务"}
{copiedTaskId === task.id ? (
<>
<Check className="w-3 h-3" />
<span></span>
</>
) : (
<>
<Copy className="w-3 h-3" />
<span></span>
</>
)}
</button>
)}
{/* 重试按钮 */}
{onRetryTask && (
<button
onClick={() => handleRetryTask(task.id)}
disabled={retryingTasks.has(task.id) || task.status === 'RETRYING'}
className={cn(
"flex items-center gap-1 px-2 py-1 text-white text-[10px] rounded transition-colors",
(retryingTasks.has(task.id) || task.status === 'RETRYING')
? "bg-yellow-600 cursor-not-allowed"
: "bg-blue-600 hover:bg-blue-700"
)}
>
<RefreshCw className={cn(
"w-3 h-3",
(retryingTasks.has(task.id) || task.status === 'RETRYING') && "animate-spin"
)} />
{(retryingTasks.has(task.id) || task.status === 'RETRYING') ? "重试中..." : "重试任务"}
</button>
)}
</div>
</div>
);
})()}