forked from 77media/video-flow
时间线优化
This commit is contained in:
parent
20762e806c
commit
f8a88bcadf
@ -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>
|
||||
);
|
||||
})()}
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user