video-flow-b/components/task-monitor/task-statistics-cards.tsx
qikongjian 9258f50f8e feat: 添加任务监控功能
- 新增任务统计数据接口和消息队列状态接口
- 添加任务监控页面 (app/task-monitor/page.tsx)
- 实现任务监控相关组件:
  - 项目选择器 (project-selector.tsx)
  - 任务统计卡片 (task-statistics-cards.tsx)
  - 任务状态图表 (task-status-chart.tsx)
  - 任务时间线图表 (task-timeline-chart.tsx)
  - 任务详情表格 (task-detail-table.tsx)
  - 消息队列面板 (message-queue-panel.tsx)
- 扩展 video_flow.ts API 接口,支持项目任务统计和消息队列管理
2025-08-26 22:03:57 +08:00

213 lines
6.9 KiB
TypeScript

'use client';
import React from 'react';
import { TaskStatistics } from '@/api/video_flow';
import { cn } from '@/public/lib/utils';
interface TaskStatisticsCardsProps {
statistics: TaskStatistics[];
}
export default function TaskStatisticsCards({ statistics }: TaskStatisticsCardsProps) {
// 聚合多个项目的统计数据
const aggregatedStats = React.useMemo(() => {
if (statistics.length === 0) {
return {
total_tasks: 0,
status_stats: {
completed: 0,
in_progress: 0,
pending: 0,
failed: 0,
blocked: 0,
},
success_rate: 0,
avg_execution_time: 0,
project_count: 0,
};
}
const totals = statistics.reduce(
(acc, stat) => ({
total_tasks: acc.total_tasks + stat.total_tasks,
completed: acc.completed + stat.status_stats.completed,
in_progress: acc.in_progress + stat.status_stats.in_progress,
pending: acc.pending + stat.status_stats.pending,
failed: acc.failed + stat.status_stats.failed,
blocked: acc.blocked + stat.status_stats.blocked,
total_execution_time: acc.total_execution_time + (stat.avg_execution_time * stat.total_tasks),
}),
{
total_tasks: 0,
completed: 0,
in_progress: 0,
pending: 0,
failed: 0,
blocked: 0,
total_execution_time: 0,
}
);
const success_rate = totals.total_tasks > 0
? (totals.completed / totals.total_tasks) * 100
: 0;
const avg_execution_time = totals.total_tasks > 0
? totals.total_execution_time / totals.total_tasks
: 0;
return {
total_tasks: totals.total_tasks,
status_stats: {
completed: totals.completed,
in_progress: totals.in_progress,
pending: totals.pending,
failed: totals.failed,
blocked: totals.blocked,
},
success_rate,
avg_execution_time,
project_count: statistics.length,
};
}, [statistics]);
const cards = [
{
title: '总任务数',
value: aggregatedStats.total_tasks.toLocaleString(),
icon: '📊',
color: 'blue',
subtitle: `${aggregatedStats.project_count} 个项目`,
},
{
title: '成功率',
value: `${aggregatedStats.success_rate.toFixed(1)}%`,
icon: '✅',
color: aggregatedStats.success_rate >= 90 ? 'green' : aggregatedStats.success_rate >= 70 ? 'yellow' : 'red',
subtitle: `${aggregatedStats.status_stats.completed} 个成功`,
},
{
title: '进行中',
value: aggregatedStats.status_stats.in_progress.toLocaleString(),
icon: '⚡',
color: 'blue',
subtitle: '正在执行',
},
{
title: '失败任务',
value: aggregatedStats.status_stats.failed.toLocaleString(),
icon: '❌',
color: 'red',
subtitle: '需要处理',
},
{
title: '平均耗时',
value: `${(aggregatedStats.avg_execution_time / 60).toFixed(1)}`,
icon: '⏱️',
color: 'purple',
subtitle: '执行时间',
},
{
title: '阻塞任务',
value: aggregatedStats.status_stats.blocked.toLocaleString(),
icon: '🚫',
color: 'orange',
subtitle: '等待处理',
},
];
const getColorClasses = (color: string) => {
switch (color) {
case 'green':
return 'bg-emerald-500/10 border-emerald-500/20 text-emerald-400';
case 'red':
return 'bg-red-500/10 border-red-500/20 text-red-400';
case 'yellow':
return 'bg-yellow-500/10 border-yellow-500/20 text-yellow-400';
case 'blue':
return 'bg-blue-500/10 border-blue-500/20 text-blue-400';
case 'purple':
return 'bg-purple-500/10 border-purple-500/20 text-purple-400';
case 'orange':
return 'bg-orange-500/10 border-orange-500/20 text-orange-400';
default:
return 'bg-gray-500/10 border-gray-500/20 text-gray-400';
}
};
return (
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 xl:grid-cols-6 gap-4">
{cards.map((card, index) => (
<div
key={index}
className={cn(
'p-4 rounded-lg border backdrop-blur-sm transition-all duration-200 hover:scale-105',
getColorClasses(card.color)
)}
>
<div className="flex items-center justify-between mb-2">
<div className="text-2xl">{card.icon}</div>
<div className="text-xs opacity-60">{card.title}</div>
</div>
<div className="space-y-1">
<div className="text-2xl font-bold">{card.value}</div>
<div className="text-xs opacity-80">{card.subtitle}</div>
</div>
</div>
))}
</div>
);
}
// 单个项目的详细统计卡片
export function ProjectStatisticsCard({ statistics }: { statistics: TaskStatistics }) {
const statusItems = [
{ label: '已完成', value: statistics.status_stats.completed, color: 'text-emerald-400' },
{ label: '进行中', value: statistics.status_stats.in_progress, color: 'text-blue-400' },
{ label: '等待中', value: statistics.status_stats.pending, color: 'text-yellow-400' },
{ label: '失败', value: statistics.status_stats.failed, color: 'text-red-400' },
{ label: '阻塞', value: statistics.status_stats.blocked, color: 'text-orange-400' },
];
return (
<div className="bg-gray-800/50 border border-gray-700 rounded-lg p-6 backdrop-blur-sm">
<div className="flex items-center justify-between mb-4">
<div>
<h3 className="text-lg font-semibold text-white">
{statistics.project_name || statistics.project_id}
</h3>
<p className="text-sm text-gray-400">ID: {statistics.project_id}</p>
</div>
<div className="text-right">
<div className="text-2xl font-bold text-white">{statistics.total_tasks}</div>
<div className="text-sm text-gray-400"></div>
</div>
</div>
<div className="grid grid-cols-5 gap-3 mb-4">
{statusItems.map((item, index) => (
<div key={index} className="text-center">
<div className={cn('text-lg font-semibold', item.color)}>{item.value}</div>
<div className="text-xs text-gray-500">{item.label}</div>
</div>
))}
</div>
<div className="flex items-center justify-between text-sm">
<div className="flex items-center gap-4">
<span className="text-gray-400">
: <span className="text-emerald-400 font-semibold">{statistics.success_rate.toFixed(1)}%</span>
</span>
<span className="text-gray-400">
: <span className="text-blue-400 font-semibold">{(statistics.avg_execution_time / 60).toFixed(1)}</span>
</span>
</div>
<div className="text-xs text-gray-500">
{new Date(statistics.last_updated).toLocaleTimeString()}
</div>
</div>
</div>
);
}