Files
breakpilot-lehrer/admin-lehrer/app/(admin)/ai/agents/statistics/page.tsx
Benjamin Boenisch 5a31f52310 Initial commit: breakpilot-lehrer - Lehrer KI Platform
Services: Admin-Lehrer, Backend-Lehrer, Studio v2, Website,
Klausur-Service, School-Service, Voice-Service, Geo-Service,
BreakPilot Drive, Agent-Core

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-11 23:47:26 +01:00

492 lines
19 KiB
TypeScript

'use client'
import { useState, useEffect } from 'react'
import Link from 'next/link'
import { ArrowLeft, BarChart3, TrendingUp, TrendingDown, Clock, Activity, Bot, Brain, MessageSquare, AlertTriangle, Settings, RefreshCw, Calendar, Filter, Download } from 'lucide-react'
// Types
interface AgentMetric {
agentType: string
name: string
color: string
sessions: number
messagesProcessed: number
avgResponseTime: number
errorRate: number
successRate: number
trend: 'up' | 'down' | 'stable'
trendValue: number
}
interface TimeSeriesData {
timestamp: string
value: number
}
interface DailyStats {
date: string
sessions: number
messages: number
errors: number
avgLatency: number
}
// Mock data
const mockAgentMetrics: AgentMetric[] = [
{
agentType: 'tutor-agent',
name: 'TutorAgent',
color: '#3b82f6',
sessions: 156,
messagesProcessed: 4521,
avgResponseTime: 234,
errorRate: 0.3,
successRate: 99.7,
trend: 'up',
trendValue: 12
},
{
agentType: 'grader-agent',
name: 'GraderAgent',
color: '#10b981',
sessions: 45,
messagesProcessed: 1205,
avgResponseTime: 1102,
errorRate: 0.5,
successRate: 99.5,
trend: 'stable',
trendValue: 2
},
{
agentType: 'quality-judge',
name: 'QualityJudge',
color: '#f59e0b',
sessions: 89,
messagesProcessed: 8934,
avgResponseTime: 89,
errorRate: 0.1,
successRate: 99.9,
trend: 'up',
trendValue: 8
},
{
agentType: 'alert-agent',
name: 'AlertAgent',
color: '#ef4444',
sessions: 12,
messagesProcessed: 892,
avgResponseTime: 45,
errorRate: 0.0,
successRate: 100,
trend: 'stable',
trendValue: 0
},
{
agentType: 'orchestrator',
name: 'Orchestrator',
color: '#8b5cf6',
sessions: 234,
messagesProcessed: 15420,
avgResponseTime: 12,
errorRate: 0.2,
successRate: 99.8,
trend: 'up',
trendValue: 15
}
]
const mockDailyStats: DailyStats[] = [
{ date: '2026-01-28', sessions: 420, messages: 12500, errors: 15, avgLatency: 156 },
{ date: '2026-01-29', sessions: 445, messages: 13200, errors: 12, avgLatency: 148 },
{ date: '2026-01-30', sessions: 398, messages: 11800, errors: 18, avgLatency: 162 },
{ date: '2026-01-31', sessions: 512, messages: 15600, errors: 10, avgLatency: 145 },
{ date: '2026-02-01', sessions: 489, messages: 14200, errors: 8, avgLatency: 139 },
{ date: '2026-02-02', sessions: 534, messages: 16100, errors: 11, avgLatency: 142 },
{ date: '2026-02-03', sessions: 478, messages: 14800, errors: 9, avgLatency: 151 }
]
const mockHourlyLatency: TimeSeriesData[] = Array.from({ length: 24 }, (_, i) => ({
timestamp: `${i.toString().padStart(2, '0')}:00`,
value: Math.floor(100 + Math.random() * 100)
}))
function getAgentIcon(agentType: string) {
switch (agentType) {
case 'tutor-agent': return <Brain className="w-4 h-4" />
case 'grader-agent': return <Bot className="w-4 h-4" />
case 'quality-judge': return <Settings className="w-4 h-4" />
case 'alert-agent': return <AlertTriangle className="w-4 h-4" />
case 'orchestrator': return <MessageSquare className="w-4 h-4" />
default: return <Bot className="w-4 h-4" />
}
}
// Simple bar chart component
function BarChart({ data, color, maxValue }: { data: number[], color: string, maxValue: number }) {
return (
<div className="flex items-end gap-1 h-20">
{data.map((value, i) => (
<div
key={i}
className="flex-1 rounded-t transition-all hover:opacity-80"
style={{
height: `${(value / maxValue) * 100}%`,
backgroundColor: color,
minHeight: '4px'
}}
/>
))}
</div>
)
}
// Simple line chart visualization
function SparkLine({ data, color }: { data: number[], color: string }) {
const max = Math.max(...data)
const min = Math.min(...data)
const range = max - min || 1
const points = data.map((value, i) => {
const x = (i / (data.length - 1)) * 100
const y = 100 - ((value - min) / range) * 100
return `${x},${y}`
}).join(' ')
return (
<svg viewBox="0 0 100 100" className="w-full h-12" preserveAspectRatio="none">
<polyline
fill="none"
stroke={color}
strokeWidth="2"
points={points}
/>
</svg>
)
}
export default function StatisticsPage() {
const [loading, setLoading] = useState(false)
const [timeRange, setTimeRange] = useState<'24h' | '7d' | '30d'>('7d')
const [lastRefresh, setLastRefresh] = useState(new Date())
const refreshData = async () => {
setLoading(true)
await new Promise(resolve => setTimeout(resolve, 500))
setLastRefresh(new Date())
setLoading(false)
}
// Calculate totals
const totals = {
sessions: mockAgentMetrics.reduce((sum, m) => sum + m.sessions, 0),
messages: mockAgentMetrics.reduce((sum, m) => sum + m.messagesProcessed, 0),
avgLatency: Math.round(mockAgentMetrics.reduce((sum, m) => sum + m.avgResponseTime, 0) / mockAgentMetrics.length),
avgErrorRate: (mockAgentMetrics.reduce((sum, m) => sum + m.errorRate, 0) / mockAgentMetrics.length).toFixed(2)
}
// Calculate week stats
const weekTotals = {
sessions: mockDailyStats.reduce((sum, d) => sum + d.sessions, 0),
messages: mockDailyStats.reduce((sum, d) => sum + d.messages, 0),
errors: mockDailyStats.reduce((sum, d) => sum + d.errors, 0),
avgLatency: Math.round(mockDailyStats.reduce((sum, d) => sum + d.avgLatency, 0) / mockDailyStats.length)
}
return (
<div className="p-6 max-w-7xl mx-auto">
{/* Header */}
<div className="mb-8">
<Link
href="/ai/agents"
className="flex items-center gap-2 text-gray-500 hover:text-gray-700 mb-4"
>
<ArrowLeft className="w-4 h-4" />
Zurueck zur Agent-Verwaltung
</Link>
<div className="flex items-center justify-between">
<div>
<h1 className="text-2xl font-bold text-gray-900 flex items-center gap-3">
<div className="p-2 bg-green-100 rounded-lg">
<BarChart3 className="w-6 h-6 text-green-600" />
</div>
Agent Statistiken
</h1>
<p className="text-gray-500 mt-1">
Performance-Metriken und Trends des Multi-Agent-Systems
</p>
</div>
<div className="flex items-center gap-3">
<select
value={timeRange}
onChange={(e) => setTimeRange(e.target.value as '24h' | '7d' | '30d')}
className="px-4 py-2 border border-gray-200 rounded-lg focus:outline-none focus:ring-2 focus:ring-green-500"
>
<option value="24h">Letzte 24 Stunden</option>
<option value="7d">Letzte 7 Tage</option>
<option value="30d">Letzte 30 Tage</option>
</select>
<button
onClick={refreshData}
disabled={loading}
className="flex items-center gap-2 px-4 py-2 bg-green-600 text-white rounded-lg hover:bg-green-700 transition-colors disabled:opacity-50"
>
<RefreshCw className={`w-4 h-4 ${loading ? 'animate-spin' : ''}`} />
Aktualisieren
</button>
</div>
</div>
</div>
{/* Overview Stats */}
<div className="grid grid-cols-2 md:grid-cols-4 gap-4 mb-8">
<div className="bg-white border border-gray-200 rounded-xl p-5">
<div className="flex items-center justify-between mb-2">
<span className="text-sm text-gray-500">Sessions (7d)</span>
<Activity className="w-4 h-4 text-gray-400" />
</div>
<div className="text-3xl font-bold text-gray-900">{weekTotals.sessions.toLocaleString()}</div>
<div className="flex items-center gap-1 mt-1 text-sm text-green-600">
<TrendingUp className="w-3.5 h-3.5" />
<span>+12% vs. Vorwoche</span>
</div>
</div>
<div className="bg-white border border-gray-200 rounded-xl p-5">
<div className="flex items-center justify-between mb-2">
<span className="text-sm text-gray-500">Messages (7d)</span>
<MessageSquare className="w-4 h-4 text-gray-400" />
</div>
<div className="text-3xl font-bold text-gray-900">{weekTotals.messages.toLocaleString()}</div>
<div className="flex items-center gap-1 mt-1 text-sm text-green-600">
<TrendingUp className="w-3.5 h-3.5" />
<span>+8% vs. Vorwoche</span>
</div>
</div>
<div className="bg-white border border-gray-200 rounded-xl p-5">
<div className="flex items-center justify-between mb-2">
<span className="text-sm text-gray-500">Avg. Latenz</span>
<Clock className="w-4 h-4 text-gray-400" />
</div>
<div className="text-3xl font-bold text-gray-900">{weekTotals.avgLatency}ms</div>
<div className="flex items-center gap-1 mt-1 text-sm text-green-600">
<TrendingDown className="w-3.5 h-3.5" />
<span>-5% (verbessert)</span>
</div>
</div>
<div className="bg-white border border-gray-200 rounded-xl p-5">
<div className="flex items-center justify-between mb-2">
<span className="text-sm text-gray-500">Fehler (7d)</span>
<AlertTriangle className="w-4 h-4 text-gray-400" />
</div>
<div className="text-3xl font-bold text-gray-900">{weekTotals.errors}</div>
<div className="flex items-center gap-1 mt-1 text-sm text-amber-600">
<TrendingUp className="w-3.5 h-3.5" />
<span>+3 vs. Vorwoche</span>
</div>
</div>
</div>
{/* Charts Row */}
<div className="grid grid-cols-1 lg:grid-cols-2 gap-6 mb-8">
{/* Sessions per Day */}
<div className="bg-white border border-gray-200 rounded-xl p-5">
<h3 className="font-semibold text-gray-900 mb-4">Sessions pro Tag</h3>
<div className="space-y-3">
<BarChart
data={mockDailyStats.map(d => d.sessions)}
color="#3b82f6"
maxValue={Math.max(...mockDailyStats.map(d => d.sessions)) * 1.1}
/>
<div className="flex justify-between text-xs text-gray-500">
{mockDailyStats.map(d => (
<span key={d.date}>{new Date(d.date).toLocaleDateString('de-DE', { weekday: 'short' })}</span>
))}
</div>
</div>
</div>
{/* Messages per Day */}
<div className="bg-white border border-gray-200 rounded-xl p-5">
<h3 className="font-semibold text-gray-900 mb-4">Messages pro Tag</h3>
<div className="space-y-3">
<BarChart
data={mockDailyStats.map(d => d.messages)}
color="#10b981"
maxValue={Math.max(...mockDailyStats.map(d => d.messages)) * 1.1}
/>
<div className="flex justify-between text-xs text-gray-500">
{mockDailyStats.map(d => (
<span key={d.date}>{new Date(d.date).toLocaleDateString('de-DE', { weekday: 'short' })}</span>
))}
</div>
</div>
</div>
</div>
{/* Latency Chart */}
<div className="bg-white border border-gray-200 rounded-xl p-5 mb-8">
<div className="flex items-center justify-between mb-4">
<h3 className="font-semibold text-gray-900">Latenz (24h)</h3>
<div className="flex items-center gap-2 text-sm text-gray-500">
<Clock className="w-4 h-4" />
Durchschnitt: {totals.avgLatency}ms
</div>
</div>
<SparkLine
data={mockHourlyLatency.map(d => d.value)}
color="#8b5cf6"
/>
<div className="flex justify-between text-xs text-gray-400 mt-2">
<span>00:00</span>
<span>06:00</span>
<span>12:00</span>
<span>18:00</span>
<span>24:00</span>
</div>
</div>
{/* Agent Performance Table */}
<div className="bg-white border border-gray-200 rounded-xl overflow-hidden mb-8">
<div className="px-5 py-4 border-b border-gray-200">
<h3 className="font-semibold text-gray-900">Agent Performance</h3>
</div>
<div className="overflow-x-auto">
<table className="min-w-full divide-y divide-gray-200">
<thead className="bg-gray-50">
<tr>
<th className="px-5 py-3 text-left text-xs font-medium text-gray-500 uppercase">Agent</th>
<th className="px-5 py-3 text-right text-xs font-medium text-gray-500 uppercase">Sessions</th>
<th className="px-5 py-3 text-right text-xs font-medium text-gray-500 uppercase">Messages</th>
<th className="px-5 py-3 text-right text-xs font-medium text-gray-500 uppercase">Avg. Response</th>
<th className="px-5 py-3 text-right text-xs font-medium text-gray-500 uppercase">Success Rate</th>
<th className="px-5 py-3 text-right text-xs font-medium text-gray-500 uppercase">Error Rate</th>
<th className="px-5 py-3 text-center text-xs font-medium text-gray-500 uppercase">Trend</th>
</tr>
</thead>
<tbody className="divide-y divide-gray-200">
{mockAgentMetrics.map(metric => (
<tr key={metric.agentType} className="hover:bg-gray-50">
<td className="px-5 py-4">
<div className="flex items-center gap-3">
<div
className="p-2 rounded-lg"
style={{ backgroundColor: `${metric.color}20` }}
>
<span style={{ color: metric.color }}>{getAgentIcon(metric.agentType)}</span>
</div>
<div>
<div className="font-medium text-gray-900">{metric.name}</div>
<div className="text-xs text-gray-500">{metric.agentType}</div>
</div>
</div>
</td>
<td className="px-5 py-4 text-right">
<span className="font-medium text-gray-900">{metric.sessions}</span>
</td>
<td className="px-5 py-4 text-right">
<span className="font-medium text-gray-900">{metric.messagesProcessed.toLocaleString()}</span>
</td>
<td className="px-5 py-4 text-right">
<span className="text-gray-900">{metric.avgResponseTime}ms</span>
</td>
<td className="px-5 py-4 text-right">
<span className="inline-flex items-center px-2 py-0.5 rounded text-xs font-medium bg-green-100 text-green-700">
{metric.successRate}%
</span>
</td>
<td className="px-5 py-4 text-right">
<span className={`inline-flex items-center px-2 py-0.5 rounded text-xs font-medium ${
metric.errorRate > 0.5 ? 'bg-red-100 text-red-700' :
metric.errorRate > 0 ? 'bg-amber-100 text-amber-700' :
'bg-green-100 text-green-700'
}`}>
{metric.errorRate}%
</span>
</td>
<td className="px-5 py-4 text-center">
{metric.trend === 'up' && (
<span className="inline-flex items-center gap-1 text-green-600 text-sm">
<TrendingUp className="w-4 h-4" />
+{metric.trendValue}%
</span>
)}
{metric.trend === 'down' && (
<span className="inline-flex items-center gap-1 text-red-600 text-sm">
<TrendingDown className="w-4 h-4" />
-{metric.trendValue}%
</span>
)}
{metric.trend === 'stable' && (
<span className="inline-flex items-center gap-1 text-gray-500 text-sm">
<span className="w-4 h-0.5 bg-gray-400 rounded" />
{metric.trendValue}%
</span>
)}
</td>
</tr>
))}
</tbody>
</table>
</div>
</div>
{/* Error Distribution */}
<div className="grid grid-cols-1 lg:grid-cols-2 gap-6">
{/* Error by Agent */}
<div className="bg-white border border-gray-200 rounded-xl p-5">
<h3 className="font-semibold text-gray-900 mb-4">Fehlerverteilung nach Agent</h3>
<div className="space-y-3">
{mockAgentMetrics.filter(m => m.errorRate > 0).map(metric => (
<div key={metric.agentType} className="flex items-center gap-3">
<div className="w-24 text-sm text-gray-600 truncate">{metric.name}</div>
<div className="flex-1 h-4 bg-gray-100 rounded-full overflow-hidden">
<div
className="h-full rounded-full"
style={{
width: `${metric.errorRate * 20}%`,
backgroundColor: metric.color
}}
/>
</div>
<div className="w-12 text-right text-sm text-gray-600">{metric.errorRate}%</div>
</div>
))}
</div>
</div>
{/* Message Distribution */}
<div className="bg-white border border-gray-200 rounded-xl p-5">
<h3 className="font-semibold text-gray-900 mb-4">Message-Verteilung nach Agent</h3>
<div className="space-y-3">
{mockAgentMetrics.map(metric => {
const percentage = (metric.messagesProcessed / totals.messages) * 100
return (
<div key={metric.agentType} className="flex items-center gap-3">
<div className="w-24 text-sm text-gray-600 truncate">{metric.name}</div>
<div className="flex-1 h-4 bg-gray-100 rounded-full overflow-hidden">
<div
className="h-full rounded-full"
style={{
width: `${percentage}%`,
backgroundColor: metric.color
}}
/>
</div>
<div className="w-12 text-right text-sm text-gray-600">{percentage.toFixed(1)}%</div>
</div>
)
})}
</div>
</div>
</div>
{/* Export Button */}
<div className="mt-8 flex justify-end">
<button className="flex items-center gap-2 px-4 py-2 border border-gray-200 rounded-lg hover:bg-gray-50 text-gray-600 hover:text-gray-900 transition-colors">
<Download className="w-4 h-4" />
Statistiken exportieren (CSV)
</button>
</div>
</div>
)
}