fix(admin-v2): Restore complete admin-v2 application

The admin-v2 application was incomplete in the repository. This commit
restores all missing components:

- Admin pages (76 pages): dashboard, ai, compliance, dsgvo, education,
  infrastructure, communication, development, onboarding, rbac
- SDK pages (45 pages): tom, dsfa, vvt, loeschfristen, einwilligungen,
  vendor-compliance, tom-generator, dsr, and more
- Developer portal (25 pages): API docs, SDK guides, frameworks
- All components, lib files, hooks, and types
- Updated package.json with all dependencies

The issue was caused by incomplete initial repository state - the full
admin-v2 codebase existed in backend/admin-v2 and docs-src/admin-v2
but was never fully synced to the main admin-v2 directory.

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
BreakPilot Dev
2026-02-08 23:40:15 -08:00
parent f28244753f
commit 660295e218
385 changed files with 138126 additions and 3079 deletions

View File

@@ -0,0 +1,636 @@
'use client'
import { useState, useEffect } from 'react'
import { useParams, useRouter } from 'next/navigation'
import Link from 'next/link'
import { Bot, Brain, ArrowLeft, Save, RotateCcw, Play, Pause, AlertTriangle, FileText, Settings, Activity, Clock, CheckCircle, XCircle, History, Eye, Edit3 } from 'lucide-react'
// Types
interface AgentDetail {
id: string
name: string
description: string
soulFile: string
soulContent: string
color: string
status: 'running' | 'paused' | 'stopped' | 'error'
activeSessions: number
totalProcessed: number
avgResponseTime: number
errorRate: number
lastRestart: string
version: string
createdAt: string
updatedAt: string
}
interface ChangeLog {
id: string
timestamp: string
user: string
action: string
description: string
}
// Mock data
const mockAgentDetails: Record<string, AgentDetail> = {
'tutor-agent': {
id: 'tutor-agent',
name: 'TutorAgent',
description: 'Geduldiger, ermutigender Lernbegleiter fuer Schueler',
soulFile: 'tutor-agent.soul.md',
soulContent: `# TutorAgent SOUL
## Identitaet
Du bist ein geduldiger, ermutigender Lernbegleiter fuer Schueler.
Dein Ziel ist es, Verstaendnis zu foerdern, nicht Antworten vorzugeben.
## Kernprinzipien
- **Sokratische Methode**: Stelle Fragen, die zum Nachdenken anregen
- **Positives Reinforcement**: Erkenne und feiere Lernfortschritte
- **Adaptive Kommunikation**: Passe Sprache und Komplexitaet an das Niveau an
- **Geduld**: Wiederhole Erklaerungen ohne Frustration zu zeigen
## Kommunikationsstil
- Verwende einfache, klare Sprache
- Stelle Rueckfragen, um Verstaendnis zu pruefen
- Gib Hinweise statt direkter Loesungen
- Feiere kleine Erfolge
- Nutze Analogien und Beispiele aus dem Alltag
- Strukturiere komplexe Themen in verdauliche Schritte
## Fachgebiete
- Mathematik (Grundschule bis Abitur)
- Naturwissenschaften (Physik, Chemie, Biologie)
- Sprachen (Deutsch, Englisch)
- Gesellschaftswissenschaften (Geschichte, Politik)
## Lernstrategien
1. **Konzeptbasiertes Lernen**: Erklaere das "Warum" hinter Regeln
2. **Visualisierung**: Nutze Diagramme und Skizzen wenn moeglich
3. **Verbindungen herstellen**: Verknuepfe neues Wissen mit Bekanntem
4. **Wiederholung**: Baue systematische Wiederholung ein
5. **Selbsttest**: Ermutige zur Selbstueberpruefung
## Einschraenkungen
- Gib NIEMALS vollstaendige Loesungen fuer Hausaufgaben
- Verweise bei komplexen Themen auf Lehrkraefte
- Erkenne Frustration und biete Pausen an
- Keine Unterstuetzung bei Pruefungsbetrug
- Keine medizinischen oder rechtlichen Ratschlaege
## Eskalation
- Bei wiederholtem Unverstaendnis: Schlage alternatives Erklaerformat vor
- Bei emotionaler Belastung: Empfehle Gespraech mit Vertrauensperson
- Bei technischen Problemen: Eskaliere an Support
- Bei Verdacht auf Lernschwierigkeiten: Empfehle professionelle Diagnostik
## Metrik-Ziele
- Verstaendnis-Score > 80% bei Nachfragen
- Engagement-Zeit > 5 Minuten pro Session
- Wiederbesuchs-Rate > 60%
- Frustrations-Indikatoren < 10%`,
color: '#3b82f6',
status: 'running',
activeSessions: 12,
totalProcessed: 1847,
avgResponseTime: 234,
errorRate: 0.5,
lastRestart: '2025-01-14T08:30:00Z',
version: '1.2.0',
createdAt: '2024-11-01T00:00:00Z',
updatedAt: '2025-01-14T10:15:00Z'
},
'grader-agent': {
id: 'grader-agent',
name: 'GraderAgent',
description: 'Objektiver, fairer Pruefer von Schuelerarbeiten',
soulFile: 'grader-agent.soul.md',
soulContent: `# GraderAgent SOUL
## Identitaet
Du bist ein objektiver, fairer Pruefer von Schuelerarbeiten.
Dein Ziel ist konstruktives Feedback, das zum Lernen motiviert.
## Kernprinzipien
- **Objektivitaet**: Bewerte nach festgelegten Kriterien, nicht nach Sympathie
- **Fairness**: Gleiche Massstaebe fuer alle Schueler
- **Konstruktivitaet**: Feedback soll zum Lernen anregen
- **Transparenz**: Begruende jede Bewertung nachvollziehbar
## Bewertungsprinzipien
- Bewerte nach festgelegten Kriterien (Erwartungshorizont)
- Beruecksichtige Teilleistungen
- Unterscheide zwischen Fluechtigkeitsfehlern und Verstaendnisluecken
- Formuliere Feedback lernfoerdernd
- Nutze das 15-Punkte-System korrekt (0-15 Punkte, 5 = ausreichend)
## Workflow
1. Lies die Aufgabenstellung und den Erwartungshorizont
2. Analysiere die Schuelerantwort systematisch
3. Identifiziere korrekte Elemente
4. Identifiziere Fehler mit Kategorisierung
5. Vergebe Punkte nach Kriterienkatalog
6. Formuliere konstruktives Feedback
## Fehlerkategorien
- **Rechtschreibung (R)**: Orthografische Fehler
- **Grammatik (Gr)**: Grammatikalische Fehler
- **Ausdruck (A)**: Stilistische Schwaechen
- **Inhalt (I)**: Fachliche Fehler oder Luecken
- **Struktur (St)**: Aufbau- und Gliederungsprobleme
- **Logik (L)**: Argumentationsfehler
## Qualitaetssicherung
- Bei Unsicherheit: Markiere zur manuellen Ueberpruefung
- Bei Grenzfaellen: Dokumentiere Entscheidungsgrundlage
- Konsistenz: Vergleiche mit aehnlichen Bewertungen
- Kalibrierung: Orientiere an Vergleichsarbeiten
## Eskalation
- Unleserliche Antworten: Markiere fuer manuelles Review
- Verdacht auf Plagiat: Eskaliere an Lehrkraft
- Technische Fehler: Pausiere und melde
- Unklare Aufgabenstellung: Frage nach Klarstellung`,
color: '#10b981',
status: 'running',
activeSessions: 3,
totalProcessed: 456,
avgResponseTime: 1205,
errorRate: 1.2,
lastRestart: '2025-01-13T14:00:00Z',
version: '1.1.0',
createdAt: '2024-11-01T00:00:00Z',
updatedAt: '2025-01-13T16:30:00Z'
},
'quality-judge': {
id: 'quality-judge',
name: 'QualityJudge',
description: 'Kritischer Qualitaetspruefer fuer KI-generierte Inhalte',
soulFile: 'quality-judge.soul.md',
soulContent: `# QualityJudge SOUL
## Identitaet
Du bist ein kritischer Qualitaetspruefer fuer KI-generierte Inhalte.
Dein Ziel ist die Sicherstellung hoher Qualitaetsstandards.
## Bewertungsdimensionen
### 1. Intent Accuracy (0-100)
- Wurde die Benutzerabsicht korrekt erkannt?
- Stimmt die Kategorie der Antwort?
### 2. Faithfulness (1-5)
- **5**: Vollstaendig faktisch korrekt
- **4**: Minor Ungenauigkeiten ohne Auswirkung
- **3**: Einige Ungenauigkeiten, Kernaussage korrekt
- **2**: Signifikante Fehler
- **1**: Grundlegend falsch
### 3. Relevance (1-5)
- **5**: Direkt und vollstaendig relevant
- **4**: Weitgehend relevant
- **3**: Teilweise relevant
- **2**: Geringe Relevanz
- **1**: Voellig irrelevant
### 4. Coherence (1-5)
- **5**: Perfekt strukturiert und logisch
- **4**: Gut strukturiert, kleine Luecken
- **3**: Verstaendlich, aber verbesserungsfaehig
- **2**: Schwer zu folgen
- **1**: Unverstaendlich/chaotisch
### 5. Safety ("pass"/"fail")
- Keine DSGVO-Verstoesse (keine PII)
- Keine schaedlichen Inhalte
- Keine Desinformation
- Keine Diskriminierung
- Altersgerechte Sprache
## Schwellenwerte
- **Production Ready**: composite >= 80
- **Needs Review**: 60 <= composite < 80
- **Failed**: composite < 60`,
color: '#f59e0b',
status: 'running',
activeSessions: 8,
totalProcessed: 3291,
avgResponseTime: 89,
errorRate: 0.3,
lastRestart: '2025-01-14T06:00:00Z',
version: '2.0.0',
createdAt: '2024-10-15T00:00:00Z',
updatedAt: '2025-01-14T08:00:00Z'
},
'alert-agent': {
id: 'alert-agent',
name: 'AlertAgent',
description: 'Aufmerksamer Waechter fuer das Breakpilot-System',
soulFile: 'alert-agent.soul.md',
soulContent: `# AlertAgent SOUL
## Identitaet
Du bist ein aufmerksamer Waechter fuer das Breakpilot-System.
Dein Ziel ist die rechtzeitige Erkennung und Kommunikation relevanter Ereignisse.
## Importance Levels
### KRITISCH (5)
- Systemausfaelle
- Sicherheitsvorfaelle
- DSGVO-Verstoesse
**Aktion**: Sofortige Benachrichtigung aller Admins
### DRINGEND (4)
- Performance-Probleme
- API-Ausfaelle
- Hohe Fehlerraten
**Aktion**: Benachrichtigung innerhalb 5 Minuten
### WICHTIG (3)
- Neue kritische Nachrichten
- Relevante Bildungspolitik
- Technische Warnungen
**Aktion**: Taeglicher Digest
### PRUEFEN (2)
- Interessante Entwicklungen
- Konkurrenznachrichten
**Aktion**: Woechentlicher Digest
### INFO (1)
- Allgemeine Updates
**Aktion**: Archivieren`,
color: '#ef4444',
status: 'running',
activeSessions: 1,
totalProcessed: 892,
avgResponseTime: 45,
errorRate: 0.1,
lastRestart: '2025-01-12T00:00:00Z',
version: '1.0.0',
createdAt: '2024-12-01T00:00:00Z',
updatedAt: '2025-01-12T02:00:00Z'
},
'orchestrator': {
id: 'orchestrator',
name: 'Orchestrator',
description: 'Zentraler Koordinator des Multi-Agent-Systems',
soulFile: 'orchestrator.soul.md',
soulContent: `# OrchestratorAgent SOUL
## Identitaet
Du bist der zentrale Koordinator des Breakpilot Multi-Agent-Systems.
Dein Ziel ist die effiziente Verteilung und Ueberwachung von Aufgaben.
## Kernprinzipien
- **Effizienz**: Minimale Latenz bei maximaler Qualitaet
- **Resilienz**: Graceful Degradation bei Agent-Ausfaellen
- **Fairness**: Ausgewogene Lastverteilung
- **Transparenz**: Volle Nachvollziehbarkeit aller Entscheidungen
## Verantwortlichkeiten
1. Task-Routing zu spezialisierten Agents
2. Session-Management und Recovery
3. Agent-Gesundheitsueberwachung
4. Lastverteilung
5. Fehlerbehandlung und Retry-Logik
## Task-Routing-Logik
| Intent-Kategorie | Primaerer Agent | Fallback |
|------------------|-----------------|----------|
| learning_support | TutorAgent | Manuell |
| exam_grading | GraderAgent | QualityJudge |
| quality_check | QualityJudge | Manual Review |
| system_alert | AlertAgent | E-Mail Fallback |
## Fehlerbehandlung
### Retry-Policy
- **Max Retries**: 3
- **Backoff**: Exponential (1s, 2s, 4s)
- **Keine Retries**: Validation Errors, Auth Failures
### Circuit Breaker
- **Threshold**: 5 Fehler in 60 Sekunden
- **Cooldown**: 30 Sekunden
## Metriken
- **Task Completion Rate**: > 99%
- **Average Latency**: < 2s
- **Error Rate**: < 1%`,
color: '#8b5cf6',
status: 'running',
activeSessions: 24,
totalProcessed: 8934,
avgResponseTime: 12,
errorRate: 0.2,
lastRestart: '2025-01-14T00:00:00Z',
version: '1.5.0',
createdAt: '2024-10-01T00:00:00Z',
updatedAt: '2025-01-14T00:30:00Z'
}
}
const mockChangeLogs: ChangeLog[] = [
{ id: '1', timestamp: '2025-01-14T10:15:00Z', user: 'admin@breakpilot.de', action: 'SOUL Updated', description: 'Kommunikationsstil angepasst' },
{ id: '2', timestamp: '2025-01-13T14:30:00Z', user: 'lehrer1@schule.de', action: 'Einschraenkung hinzugefuegt', description: 'Keine Hausaufgaben-Loesungen' },
{ id: '3', timestamp: '2025-01-10T09:00:00Z', user: 'admin@breakpilot.de', action: 'Version 1.2.0', description: 'Neue Fachgebiete hinzugefuegt' },
]
export default function AgentDetailPage() {
const params = useParams()
const router = useRouter()
const agentId = params.agentId as string
const [agent, setAgent] = useState<AgentDetail | null>(null)
const [editedContent, setEditedContent] = useState('')
const [isEditing, setIsEditing] = useState(false)
const [hasChanges, setHasChanges] = useState(false)
const [saving, setSaving] = useState(false)
const [activeTab, setActiveTab] = useState<'soul' | 'stats' | 'history'>('soul')
useEffect(() => {
// Load agent data
const agentData = mockAgentDetails[agentId]
if (agentData) {
setAgent(agentData)
setEditedContent(agentData.soulContent)
}
}, [agentId])
const handleSave = async () => {
setSaving(true)
// In production, save to API
// await fetch(`/api/admin/agents/${agentId}/soul`, { method: 'PUT', body: editedContent })
await new Promise(resolve => setTimeout(resolve, 1000))
if (agent) {
setAgent({ ...agent, soulContent: editedContent, updatedAt: new Date().toISOString() })
}
setHasChanges(false)
setIsEditing(false)
setSaving(false)
}
const handleReset = () => {
if (agent) {
setEditedContent(agent.soulContent)
setHasChanges(false)
}
}
const handleContentChange = (content: string) => {
setEditedContent(content)
setHasChanges(content !== agent?.soulContent)
}
if (!agent) {
return (
<div className="p-6 max-w-7xl mx-auto">
<div className="text-center py-12">
<AlertTriangle className="w-12 h-12 text-amber-500 mx-auto mb-4" />
<h2 className="text-xl font-semibold text-gray-900 mb-2">Agent nicht gefunden</h2>
<p className="text-gray-500 mb-4">Der Agent "{agentId}" existiert nicht.</p>
<Link href="/ai/agents" className="text-teal-600 hover:text-teal-700">
&larr; Zurueck zur Uebersicht
</Link>
</div>
</div>
)
}
return (
<div className="p-6 max-w-7xl mx-auto">
{/* Header */}
<div className="flex items-center justify-between mb-6">
<div className="flex items-center gap-4">
<Link
href="/ai/agents"
className="p-2 hover:bg-gray-100 rounded-lg transition-colors"
>
<ArrowLeft className="w-5 h-5 text-gray-600" />
</Link>
<div
className="p-3 rounded-xl"
style={{ backgroundColor: `${agent.color}20` }}
>
<Brain className="w-6 h-6" style={{ color: agent.color }} />
</div>
<div>
<h1 className="text-2xl font-bold text-gray-900">{agent.name}</h1>
<p className="text-gray-500">{agent.description}</p>
</div>
</div>
<div className="flex items-center gap-3">
<div className={`flex items-center gap-2 px-3 py-1.5 rounded-full text-sm font-medium ${
agent.status === 'running' ? 'bg-green-100 text-green-700' :
agent.status === 'paused' ? 'bg-yellow-100 text-yellow-700' :
'bg-red-100 text-red-700'
}`}>
{agent.status === 'running' ? <CheckCircle className="w-4 h-4" /> :
agent.status === 'paused' ? <Pause className="w-4 h-4" /> :
<XCircle className="w-4 h-4" />}
{agent.status}
</div>
<button className="flex items-center gap-2 px-4 py-2 border border-gray-300 rounded-lg hover:bg-gray-50 transition-colors">
{agent.status === 'running' ? (
<>
<Pause className="w-4 h-4" />
Pausieren
</>
) : (
<>
<Play className="w-4 h-4" />
Starten
</>
)}
</button>
</div>
</div>
{/* Stats Bar */}
<div className="grid grid-cols-5 gap-4 mb-6">
<div className="bg-white border border-gray-200 rounded-lg p-4">
<div className="text-sm text-gray-500">Aktive Sessions</div>
<div className="text-2xl font-bold text-gray-900">{agent.activeSessions}</div>
</div>
<div className="bg-white border border-gray-200 rounded-lg p-4">
<div className="text-sm text-gray-500">Verarbeitet (24h)</div>
<div className="text-2xl font-bold text-gray-900">{agent.totalProcessed.toLocaleString()}</div>
</div>
<div className="bg-white border border-gray-200 rounded-lg p-4">
<div className="text-sm text-gray-500">Avg. Antwortzeit</div>
<div className="text-2xl font-bold text-gray-900">{agent.avgResponseTime}ms</div>
</div>
<div className="bg-white border border-gray-200 rounded-lg p-4">
<div className="text-sm text-gray-500">Fehlerrate</div>
<div className="text-2xl font-bold text-amber-600">{agent.errorRate}%</div>
</div>
<div className="bg-white border border-gray-200 rounded-lg p-4">
<div className="text-sm text-gray-500">Version</div>
<div className="text-2xl font-bold text-gray-900">{agent.version}</div>
</div>
</div>
{/* Tabs */}
<div className="bg-white border border-gray-200 rounded-xl overflow-hidden">
<div className="border-b border-gray-200">
<div className="flex">
<button
onClick={() => setActiveTab('soul')}
className={`flex items-center gap-2 px-6 py-4 text-sm font-medium border-b-2 transition-colors ${
activeTab === 'soul'
? 'border-teal-500 text-teal-600'
: 'border-transparent text-gray-500 hover:text-gray-700'
}`}
>
<FileText className="w-4 h-4" />
SOUL-File
</button>
<button
onClick={() => setActiveTab('stats')}
className={`flex items-center gap-2 px-6 py-4 text-sm font-medium border-b-2 transition-colors ${
activeTab === 'stats'
? 'border-teal-500 text-teal-600'
: 'border-transparent text-gray-500 hover:text-gray-700'
}`}
>
<Activity className="w-4 h-4" />
Live-Statistiken
</button>
<button
onClick={() => setActiveTab('history')}
className={`flex items-center gap-2 px-6 py-4 text-sm font-medium border-b-2 transition-colors ${
activeTab === 'history'
? 'border-teal-500 text-teal-600'
: 'border-transparent text-gray-500 hover:text-gray-700'
}`}
>
<History className="w-4 h-4" />
Aenderungshistorie
</button>
</div>
</div>
{/* Tab Content */}
<div className="p-6">
{activeTab === 'soul' && (
<div>
<div className="flex items-center justify-between mb-4">
<div className="flex items-center gap-2 text-sm text-gray-500">
<FileText className="w-4 h-4" />
{agent.soulFile}
<span className="text-gray-300">|</span>
<Clock className="w-4 h-4" />
Zuletzt geaendert: {new Date(agent.updatedAt).toLocaleString('de-DE')}
</div>
<div className="flex items-center gap-2">
{isEditing ? (
<>
<button
onClick={handleReset}
disabled={!hasChanges}
className="flex items-center gap-2 px-4 py-2 border border-gray-300 rounded-lg hover:bg-gray-50 transition-colors disabled:opacity-50"
>
<RotateCcw className="w-4 h-4" />
Zuruecksetzen
</button>
<button
onClick={handleSave}
disabled={!hasChanges || saving}
className="flex items-center gap-2 px-4 py-2 bg-teal-600 text-white rounded-lg hover:bg-teal-700 transition-colors disabled:opacity-50"
>
<Save className="w-4 h-4" />
{saving ? 'Speichert...' : 'Speichern'}
</button>
</>
) : (
<button
onClick={() => setIsEditing(true)}
className="flex items-center gap-2 px-4 py-2 bg-teal-600 text-white rounded-lg hover:bg-teal-700 transition-colors"
>
<Edit3 className="w-4 h-4" />
Bearbeiten
</button>
)}
</div>
</div>
{hasChanges && (
<div className="mb-4 p-3 bg-amber-50 border border-amber-200 rounded-lg flex items-center gap-2 text-amber-700">
<AlertTriangle className="w-4 h-4" />
<span className="text-sm">Ungespeicherte Aenderungen vorhanden</span>
</div>
)}
<div className="relative">
{isEditing ? (
<textarea
value={editedContent}
onChange={(e) => handleContentChange(e.target.value)}
className="w-full h-[600px] p-4 font-mono text-sm bg-gray-50 border border-gray-200 rounded-lg focus:outline-none focus:ring-2 focus:ring-teal-500 focus:border-transparent resize-none"
spellCheck={false}
/>
) : (
<div className="w-full h-[600px] p-4 font-mono text-sm bg-gray-50 border border-gray-200 rounded-lg overflow-auto whitespace-pre-wrap">
{agent.soulContent}
</div>
)}
</div>
<div className="mt-4 p-4 bg-blue-50 border border-blue-200 rounded-lg">
<h4 className="font-medium text-blue-900 mb-2">Hinweise zur SOUL-Datei</h4>
<ul className="text-sm text-blue-700 space-y-1">
<li> Die SOUL-Datei definiert die Persoenlichkeit und das Verhalten des Agents</li>
<li> Aenderungen werden nach dem Speichern sofort wirksam</li>
<li> Testen Sie Aenderungen zuerst im Staging-Modus</li>
<li> Alle Aenderungen werden in der Historie protokolliert</li>
</ul>
</div>
</div>
)}
{activeTab === 'stats' && (
<div className="space-y-6">
<div className="text-center py-12 text-gray-500">
<Activity className="w-12 h-12 mx-auto mb-4 text-gray-400" />
<p>Live-Statistiken werden in einer zukuenftigen Version verfuegbar sein.</p>
<p className="text-sm mt-2">
Besuchen Sie die <Link href="/ai/agents/statistics" className="text-teal-600 hover:underline">Statistik-Seite</Link> fuer aggregierte Daten.
</p>
</div>
</div>
)}
{activeTab === 'history' && (
<div>
<div className="space-y-4">
{mockChangeLogs.map((log) => (
<div key={log.id} className="flex items-start gap-4 p-4 bg-gray-50 rounded-lg">
<div className="p-2 bg-white rounded-full border border-gray-200">
<History className="w-4 h-4 text-gray-500" />
</div>
<div className="flex-1">
<div className="flex items-center justify-between">
<span className="font-medium text-gray-900">{log.action}</span>
<span className="text-sm text-gray-500">
{new Date(log.timestamp).toLocaleString('de-DE')}
</span>
</div>
<p className="text-sm text-gray-600 mt-1">{log.description}</p>
<p className="text-xs text-gray-400 mt-1">von {log.user}</p>
</div>
</div>
))}
</div>
</div>
)}
</div>
</div>
</div>
)
}

View File

@@ -0,0 +1,779 @@
'use client'
import { useState } from 'react'
import Link from 'next/link'
import { ArrowLeft, Cpu, Brain, MessageSquare, Database, Activity, Shield, ChevronDown, ChevronRight, GitBranch, Layers, Server, FileText, AlertTriangle, CheckCircle, Zap, RefreshCw } from 'lucide-react'
interface Section {
id: string
title: string
icon: React.ReactNode
content: React.ReactNode
}
export default function ArchitecturePage() {
const [expandedSections, setExpandedSections] = useState<string[]>(['overview', 'agents', 'soul-files'])
const toggleSection = (id: string) => {
setExpandedSections(prev =>
prev.includes(id)
? prev.filter(s => s !== id)
: [...prev, id]
)
}
const sections: Section[] = [
{
id: 'overview',
title: 'System-Uebersicht',
icon: <Layers className="w-5 h-5" />,
content: (
<div className="space-y-6">
<p className="text-gray-600">
Das Breakpilot Multi-Agent-System basiert auf dem Mission Control Konzept. Es ermoeglicht
die Koordination mehrerer spezialisierter KI-Agents, die gemeinsam komplexe Aufgaben loesen.
</p>
{/* Architecture Diagram */}
<div className="bg-gray-50 rounded-xl p-6 font-mono text-sm overflow-x-auto">
<pre className="text-gray-700">{`
┌─────────────────────────────────────────────────────────────────┐
│ Breakpilot Services │
│ ┌─────────────┐ ┌─────────────┐ ┌─────────────────────────┐ │
│ │Voice Service│ │Klausur Svc │ │ Admin-v2 / AlertAgent │ │
│ └──────┬──────┘ └──────┬──────┘ └───────────┬─────────────┘ │
│ │ │ │ │
│ └────────────────┼──────────────────────┘ │
│ │ │
│ ┌───────────────────────▼───────────────────────────────────┐ │
│ │ Agent Core │ │
│ │ ┌─────────────┐ ┌─────────────┐ ┌───────────────────┐ │ │
│ │ │ Sessions │ │Shared Brain │ │ Orchestrator │ │ │
│ │ │ - Manager │ │ - Memory │ │ - Message Bus │ │ │
│ │ │ - Heartbeat │ │ - Context │ │ - Supervisor │ │ │
│ │ │ - Checkpoint│ │ - Knowledge │ │ - Task Router │ │ │
│ │ └─────────────┘ └─────────────┘ └───────────────────┘ │ │
│ └───────────────────────────────────────────────────────────┘ │
│ │ │
│ ┌───────────────────────▼───────────────────────────────────┐ │
│ │ Infrastructure │ │
│ │ Valkey (Redis) PostgreSQL Qdrant │ │
│ └───────────────────────────────────────────────────────────┘ │
└─────────────────────────────────────────────────────────────────┘
`}</pre>
</div>
<div className="grid grid-cols-1 md:grid-cols-3 gap-4">
<div className="bg-blue-50 border border-blue-200 rounded-xl p-4">
<div className="flex items-center gap-2 mb-2">
<Server className="w-5 h-5 text-blue-600" />
<span className="font-semibold text-blue-900">Session Management</span>
</div>
<p className="text-sm text-blue-700">
Verwaltet Agent-Lifecycles mit State Machine, Checkpoints und automatischer Recovery.
</p>
</div>
<div className="bg-purple-50 border border-purple-200 rounded-xl p-4">
<div className="flex items-center gap-2 mb-2">
<Brain className="w-5 h-5 text-purple-600" />
<span className="font-semibold text-purple-900">Shared Brain</span>
</div>
<p className="text-sm text-purple-700">
Gemeinsames Gedaechtnis fuer alle Agents mit TTL, Context-Verwaltung und Knowledge Graph.
</p>
</div>
<div className="bg-green-50 border border-green-200 rounded-xl p-4">
<div className="flex items-center gap-2 mb-2">
<GitBranch className="w-5 h-5 text-green-600" />
<span className="font-semibold text-green-900">Orchestrator</span>
</div>
<p className="text-sm text-green-700">
Message Bus, Supervisor und Task Router fuer die Agent-Koordination.
</p>
</div>
</div>
</div>
)
},
{
id: 'agents',
title: 'Agent-Typen',
icon: <Cpu className="w-5 h-5" />,
content: (
<div className="space-y-4">
<p className="text-gray-600 mb-4">
Jeder Agent hat eine spezialisierte Rolle im System. Die Agents kommunizieren ueber den Message Bus
und nutzen das Shared Brain fuer konsistente Entscheidungen.
</p>
<div className="grid gap-4">
{/* TutorAgent */}
<div className="border border-gray-200 rounded-xl p-4 hover:border-blue-300 transition-colors">
<div className="flex items-start gap-4">
<div className="p-3 bg-blue-100 rounded-lg">
<Brain className="w-6 h-6 text-blue-600" />
</div>
<div className="flex-1">
<h4 className="font-semibold text-gray-900">TutorAgent</h4>
<p className="text-sm text-gray-600 mb-2">Lernbegleitung und Fragen beantworten</p>
<div className="flex flex-wrap gap-2">
<span className="px-2 py-1 bg-blue-50 text-blue-700 text-xs rounded-full">Geduldig</span>
<span className="px-2 py-1 bg-blue-50 text-blue-700 text-xs rounded-full">Ermutigend</span>
<span className="px-2 py-1 bg-blue-50 text-blue-700 text-xs rounded-full">Sokratisch</span>
</div>
<div className="mt-2 text-xs text-gray-500">
SOUL: tutor-agent.soul.md | Routing: learning_*, help_*, question_*
</div>
</div>
</div>
</div>
{/* GraderAgent */}
<div className="border border-gray-200 rounded-xl p-4 hover:border-green-300 transition-colors">
<div className="flex items-start gap-4">
<div className="p-3 bg-green-100 rounded-lg">
<CheckCircle className="w-6 h-6 text-green-600" />
</div>
<div className="flex-1">
<h4 className="font-semibold text-gray-900">GraderAgent</h4>
<p className="text-sm text-gray-600 mb-2">Klausur-Korrektur und Bewertung</p>
<div className="flex flex-wrap gap-2">
<span className="px-2 py-1 bg-green-50 text-green-700 text-xs rounded-full">Objektiv</span>
<span className="px-2 py-1 bg-green-50 text-green-700 text-xs rounded-full">Fair</span>
<span className="px-2 py-1 bg-green-50 text-green-700 text-xs rounded-full">Konstruktiv</span>
</div>
<div className="mt-2 text-xs text-gray-500">
SOUL: grader-agent.soul.md | Routing: grade_*, evaluate_*, correct_*
</div>
</div>
</div>
</div>
{/* QualityJudge */}
<div className="border border-gray-200 rounded-xl p-4 hover:border-amber-300 transition-colors">
<div className="flex items-start gap-4">
<div className="p-3 bg-amber-100 rounded-lg">
<Shield className="w-6 h-6 text-amber-600" />
</div>
<div className="flex-1">
<h4 className="font-semibold text-gray-900">QualityJudge</h4>
<p className="text-sm text-gray-600 mb-2">BQAS Qualitaetspruefung</p>
<div className="flex flex-wrap gap-2">
<span className="px-2 py-1 bg-amber-50 text-amber-700 text-xs rounded-full">Kritisch</span>
<span className="px-2 py-1 bg-amber-50 text-amber-700 text-xs rounded-full">Praezise</span>
<span className="px-2 py-1 bg-amber-50 text-amber-700 text-xs rounded-full">Schnell</span>
</div>
<div className="mt-2 text-xs text-gray-500">
SOUL: quality-judge.soul.md | Routing: quality_*, review_*, validate_*
</div>
</div>
</div>
</div>
{/* AlertAgent */}
<div className="border border-gray-200 rounded-xl p-4 hover:border-red-300 transition-colors">
<div className="flex items-start gap-4">
<div className="p-3 bg-red-100 rounded-lg">
<AlertTriangle className="w-6 h-6 text-red-600" />
</div>
<div className="flex-1">
<h4 className="font-semibold text-gray-900">AlertAgent</h4>
<p className="text-sm text-gray-600 mb-2">Monitoring und Benachrichtigungen</p>
<div className="flex flex-wrap gap-2">
<span className="px-2 py-1 bg-red-50 text-red-700 text-xs rounded-full">Wachsam</span>
<span className="px-2 py-1 bg-red-50 text-red-700 text-xs rounded-full">Proaktiv</span>
<span className="px-2 py-1 bg-red-50 text-red-700 text-xs rounded-full">Priorisierend</span>
</div>
<div className="mt-2 text-xs text-gray-500">
SOUL: alert-agent.soul.md | Routing: alert_*, monitor_*, notify_*
</div>
</div>
</div>
</div>
{/* Orchestrator */}
<div className="border border-gray-200 rounded-xl p-4 hover:border-purple-300 transition-colors">
<div className="flex items-start gap-4">
<div className="p-3 bg-purple-100 rounded-lg">
<MessageSquare className="w-6 h-6 text-purple-600" />
</div>
<div className="flex-1">
<h4 className="font-semibold text-gray-900">Orchestrator</h4>
<p className="text-sm text-gray-600 mb-2">Task-Koordination und Routing</p>
<div className="flex flex-wrap gap-2">
<span className="px-2 py-1 bg-purple-50 text-purple-700 text-xs rounded-full">Koordinierend</span>
<span className="px-2 py-1 bg-purple-50 text-purple-700 text-xs rounded-full">Effizient</span>
<span className="px-2 py-1 bg-purple-50 text-purple-700 text-xs rounded-full">Zuverlaessig</span>
</div>
<div className="mt-2 text-xs text-gray-500">
SOUL: orchestrator.soul.md | Routing: Fallback fuer alle unbekannten Intents
</div>
</div>
</div>
</div>
</div>
</div>
)
},
{
id: 'soul-files',
title: 'SOUL-Files (Persoenlichkeiten)',
icon: <FileText className="w-5 h-5" />,
content: (
<div className="space-y-4">
<p className="text-gray-600 mb-4">
SOUL-Dateien (Semantic Outline for Unified Learning) definieren die Persoenlichkeit und
Verhaltensregeln jedes Agents. Sie bestimmen, wie ein Agent kommuniziert, entscheidet und eskaliert.
</p>
<div className="bg-gray-900 rounded-xl p-6 text-gray-100 font-mono text-sm overflow-x-auto">
<div className="text-gray-400 mb-4"># Beispiel: tutor-agent.soul.md</div>
<pre className="text-green-400">{`
# TutorAgent SOUL
## Identitaet
Du bist ein geduldiger, ermutigender Lernbegleiter fuer Schueler.
Dein Ziel ist es, Verstaendnis zu foerdern, nicht Antworten vorzugeben.
## Kommunikationsstil
- Verwende einfache, klare Sprache
- Stelle Rueckfragen, um Verstaendnis zu pruefen
- Gib Hinweise statt direkter Loesungen
- Feiere kleine Erfolge
## Fachgebiete
- Mathematik (Grundschule bis Abitur)
- Naturwissenschaften (Physik, Chemie, Biologie)
- Sprachen (Deutsch, Englisch)
## Einschraenkungen
- Gib NIEMALS vollstaendige Loesungen fuer Hausaufgaben
- Verweise bei komplexen Themen auf Lehrkraefte
- Erkenne Frustration und biete Pausen an
## Eskalation
- Bei wiederholtem Unverstaendnis: Schlage alternatives Erklaerformat vor
- Bei emotionaler Belastung: Empfehle Gespraech mit Vertrauensperson
- Bei technischen Problemen: Eskaliere an Support
`}</pre>
</div>
<div className="mt-6">
<h4 className="font-semibold text-gray-900 mb-3">SOUL-Struktur</h4>
<div className="grid grid-cols-1 md:grid-cols-2 gap-4">
<div className="bg-white border border-gray-200 rounded-lg p-4">
<h5 className="font-medium text-gray-900 mb-2">Identitaet</h5>
<p className="text-sm text-gray-600">Wer ist der Agent? Welche Rolle nimmt er ein?</p>
</div>
<div className="bg-white border border-gray-200 rounded-lg p-4">
<h5 className="font-medium text-gray-900 mb-2">Kommunikationsstil</h5>
<p className="text-sm text-gray-600">Wie kommuniziert der Agent mit Benutzern?</p>
</div>
<div className="bg-white border border-gray-200 rounded-lg p-4">
<h5 className="font-medium text-gray-900 mb-2">Fachgebiete</h5>
<p className="text-sm text-gray-600">In welchen Bereichen ist der Agent kompetent?</p>
</div>
<div className="bg-white border border-gray-200 rounded-lg p-4">
<h5 className="font-medium text-gray-900 mb-2">Einschraenkungen</h5>
<p className="text-sm text-gray-600">Was darf der Agent NICHT tun?</p>
</div>
<div className="bg-white border border-gray-200 rounded-lg p-4 md:col-span-2">
<h5 className="font-medium text-gray-900 mb-2">Eskalation</h5>
<p className="text-sm text-gray-600">Wann und wie eskaliert der Agent an andere Agents oder Menschen?</p>
</div>
</div>
</div>
</div>
)
},
{
id: 'message-bus',
title: 'Message Bus & Kommunikation',
icon: <MessageSquare className="w-5 h-5" />,
content: (
<div className="space-y-4">
<p className="text-gray-600 mb-4">
Der Message Bus ermoeglicht die asynchrone Kommunikation zwischen Agents via Redis Pub/Sub.
Er unterstuetzt Prioritaeten, Request-Response-Pattern und Broadcast-Nachrichten.
</p>
<div className="bg-gray-50 rounded-xl p-6 font-mono text-sm">
<div className="text-gray-500 mb-2"># Nachrichtenfluss</div>
<pre className="text-gray-700">{`
┌──────────────┐ ┌──────────────┐
│ Sender │ │ Receiver │
│ (Agent) │ │ (Agent) │
└──────┬───────┘ └──────▲───────┘
│ │
│ publish(AgentMessage) │ handle(message)
│ │
▼ │
┌────────────────────────────────────────────────────────┐
│ Message Bus │
│ ┌─────────────┐ ┌─────────────┐ ┌─────────────┐ │
│ │ Priority Q │ │ Routing │ │ Logging │ │
│ │ HIGH/NORMAL │ │ Rules │ │ Audit │ │
│ └─────────────┘ └─────────────┘ └─────────────┘ │
│ │
│ Redis Pub/Sub │
└────────────────────────────────────────────────────────┘
`}</pre>
</div>
<div className="mt-6">
<h4 className="font-semibold text-gray-900 mb-3">Nachrichtentypen</h4>
<div className="overflow-x-auto">
<table className="min-w-full border border-gray-200 rounded-lg">
<thead className="bg-gray-50">
<tr>
<th className="px-4 py-2 text-left text-sm font-medium text-gray-900">Typ</th>
<th className="px-4 py-2 text-left text-sm font-medium text-gray-900">Prioritaet</th>
<th className="px-4 py-2 text-left text-sm font-medium text-gray-900">Beschreibung</th>
</tr>
</thead>
<tbody className="divide-y divide-gray-200">
<tr>
<td className="px-4 py-2 text-sm font-mono text-gray-700">task_request</td>
<td className="px-4 py-2"><span className="px-2 py-1 bg-yellow-100 text-yellow-700 text-xs rounded">NORMAL</span></td>
<td className="px-4 py-2 text-sm text-gray-600">Neue Aufgabe an Agent senden</td>
</tr>
<tr>
<td className="px-4 py-2 text-sm font-mono text-gray-700">task_response</td>
<td className="px-4 py-2"><span className="px-2 py-1 bg-yellow-100 text-yellow-700 text-xs rounded">NORMAL</span></td>
<td className="px-4 py-2 text-sm text-gray-600">Antwort auf task_request</td>
</tr>
<tr>
<td className="px-4 py-2 text-sm font-mono text-gray-700">escalation</td>
<td className="px-4 py-2"><span className="px-2 py-1 bg-orange-100 text-orange-700 text-xs rounded">HIGH</span></td>
<td className="px-4 py-2 text-sm text-gray-600">Eskalation an anderen Agent</td>
</tr>
<tr>
<td className="px-4 py-2 text-sm font-mono text-gray-700">alert</td>
<td className="px-4 py-2"><span className="px-2 py-1 bg-red-100 text-red-700 text-xs rounded">CRITICAL</span></td>
<td className="px-4 py-2 text-sm text-gray-600">Kritische Benachrichtigung</td>
</tr>
<tr>
<td className="px-4 py-2 text-sm font-mono text-gray-700">heartbeat</td>
<td className="px-4 py-2"><span className="px-2 py-1 bg-gray-100 text-gray-700 text-xs rounded">LOW</span></td>
<td className="px-4 py-2 text-sm text-gray-600">Liveness-Signal</td>
</tr>
</tbody>
</table>
</div>
</div>
</div>
)
},
{
id: 'shared-brain',
title: 'Shared Brain (Gedaechtnis)',
icon: <Brain className="w-5 h-5" />,
content: (
<div className="space-y-4">
<p className="text-gray-600 mb-4">
Das Shared Brain speichert Wissen und Kontext, auf den alle Agents zugreifen koennen.
Es besteht aus drei Komponenten: Memory Store, Context Manager und Knowledge Graph.
</p>
<div className="grid grid-cols-1 md:grid-cols-3 gap-4">
<div className="bg-white border border-gray-200 rounded-xl p-5">
<div className="flex items-center gap-2 mb-3">
<Database className="w-5 h-5 text-blue-600" />
<h4 className="font-semibold text-gray-900">Memory Store</h4>
</div>
<p className="text-sm text-gray-600 mb-3">
Langzeit-Gedaechtnis fuer Fakten, Entscheidungen und Lernfortschritte.
</p>
<ul className="text-xs text-gray-500 space-y-1">
<li>- TTL-basierte Expiration (30 Tage default)</li>
<li>- Access-Tracking (Haeufigkeit)</li>
<li>- Pattern-basierte Suche</li>
<li>- Hybrid: Redis + PostgreSQL</li>
</ul>
</div>
<div className="bg-white border border-gray-200 rounded-xl p-5">
<div className="flex items-center gap-2 mb-3">
<Activity className="w-5 h-5 text-purple-600" />
<h4 className="font-semibold text-gray-900">Context Manager</h4>
</div>
<p className="text-sm text-gray-600 mb-3">
Verwaltet Konversationskontext mit automatischer Komprimierung.
</p>
<ul className="text-xs text-gray-500 space-y-1">
<li>- Max 50 Messages pro Context</li>
<li>- Automatische Zusammenfassung</li>
<li>- System-Messages bleiben erhalten</li>
<li>- Entity-Extraktion</li>
</ul>
</div>
<div className="bg-white border border-gray-200 rounded-xl p-5">
<div className="flex items-center gap-2 mb-3">
<GitBranch className="w-5 h-5 text-green-600" />
<h4 className="font-semibold text-gray-900">Knowledge Graph</h4>
</div>
<p className="text-sm text-gray-600 mb-3">
Graph-basierte Darstellung von Entitaeten und ihren Beziehungen.
</p>
<ul className="text-xs text-gray-500 space-y-1">
<li>- Entitaeten: Student, Lehrer, Fach</li>
<li>- Beziehungen: lernt, unterrichtet</li>
<li>- BFS-basierte Pfadsuche</li>
<li>- Verwandte Entitaeten finden</li>
</ul>
</div>
</div>
<div className="bg-gray-50 rounded-xl p-6 font-mono text-sm mt-6">
<div className="text-gray-500 mb-2"># Memory Store Beispiel</div>
<pre className="text-gray-700">{`
# Speichern
await store.remember(
key="student:123:progress",
value={"level": 5, "score": 85, "topic": "algebra"},
agent_id="tutor-agent",
ttl_days=30
)
# Abrufen
progress = await store.recall("student:123:progress")
# → {"level": 5, "score": 85, "topic": "algebra"}
# Suchen
all_progress = await store.search("student:123:*")
# → [Memory(...), Memory(...), ...]
`}</pre>
</div>
</div>
)
},
{
id: 'task-routing',
title: 'Task Routing',
icon: <Zap className="w-5 h-5" />,
content: (
<div className="space-y-4">
<p className="text-gray-600 mb-4">
Der Task Router entscheidet, welcher Agent eine Anfrage bearbeitet. Er verwendet
Intent-basierte Regeln mit Prioritaeten und Fallback-Ketten.
</p>
<div className="overflow-x-auto">
<table className="min-w-full border border-gray-200 rounded-lg">
<thead className="bg-gray-50">
<tr>
<th className="px-4 py-2 text-left text-sm font-medium text-gray-900">Intent-Pattern</th>
<th className="px-4 py-2 text-left text-sm font-medium text-gray-900">Ziel-Agent</th>
<th className="px-4 py-2 text-left text-sm font-medium text-gray-900">Prioritaet</th>
<th className="px-4 py-2 text-left text-sm font-medium text-gray-900">Fallback</th>
</tr>
</thead>
<tbody className="divide-y divide-gray-200">
<tr>
<td className="px-4 py-2 text-sm font-mono text-blue-700">learning_*</td>
<td className="px-4 py-2 text-sm text-gray-700">TutorAgent</td>
<td className="px-4 py-2 text-sm text-gray-700">10</td>
<td className="px-4 py-2 text-sm text-gray-500">Orchestrator</td>
</tr>
<tr>
<td className="px-4 py-2 text-sm font-mono text-blue-700">help_*, question_*</td>
<td className="px-4 py-2 text-sm text-gray-700">TutorAgent</td>
<td className="px-4 py-2 text-sm text-gray-700">8</td>
<td className="px-4 py-2 text-sm text-gray-500">Orchestrator</td>
</tr>
<tr>
<td className="px-4 py-2 text-sm font-mono text-green-700">grade_*, evaluate_*</td>
<td className="px-4 py-2 text-sm text-gray-700">GraderAgent</td>
<td className="px-4 py-2 text-sm text-gray-700">10</td>
<td className="px-4 py-2 text-sm text-gray-500">Orchestrator</td>
</tr>
<tr>
<td className="px-4 py-2 text-sm font-mono text-amber-700">quality_*, review_*</td>
<td className="px-4 py-2 text-sm text-gray-700">QualityJudge</td>
<td className="px-4 py-2 text-sm text-gray-700">10</td>
<td className="px-4 py-2 text-sm text-gray-500">GraderAgent</td>
</tr>
<tr>
<td className="px-4 py-2 text-sm font-mono text-red-700">alert_*, monitor_*</td>
<td className="px-4 py-2 text-sm text-gray-700">AlertAgent</td>
<td className="px-4 py-2 text-sm text-gray-700">10</td>
<td className="px-4 py-2 text-sm text-gray-500">Orchestrator</td>
</tr>
<tr className="bg-gray-50">
<td className="px-4 py-2 text-sm font-mono text-gray-500">* (alle anderen)</td>
<td className="px-4 py-2 text-sm text-gray-700">Orchestrator</td>
<td className="px-4 py-2 text-sm text-gray-700">0</td>
<td className="px-4 py-2 text-sm text-gray-500">-</td>
</tr>
</tbody>
</table>
</div>
<div className="mt-6 grid grid-cols-1 md:grid-cols-2 gap-4">
<div className="bg-white border border-gray-200 rounded-xl p-4">
<h4 className="font-semibold text-gray-900 mb-2">Routing-Strategien</h4>
<ul className="text-sm text-gray-600 space-y-2">
<li><span className="font-mono text-blue-600">ROUND_ROBIN</span> - Gleichmaessige Verteilung</li>
<li><span className="font-mono text-blue-600">LEAST_LOADED</span> - Agent mit wenigsten Tasks</li>
<li><span className="font-mono text-blue-600">PRIORITY</span> - Hoechste Prioritaet zuerst</li>
<li><span className="font-mono text-blue-600">RANDOM</span> - Zufaellige Auswahl</li>
</ul>
</div>
<div className="bg-white border border-gray-200 rounded-xl p-4">
<h4 className="font-semibold text-gray-900 mb-2">Fallback-Verhalten</h4>
<ul className="text-sm text-gray-600 space-y-2">
<li>1. Versuche Ziel-Agent zu erreichen</li>
<li>2. Bei Timeout: Fallback-Agent nutzen</li>
<li>3. Bei Fehler: Orchestrator uebernimmt</li>
<li>4. Bei kritischen Fehlern: Alert an Admin</li>
</ul>
</div>
</div>
</div>
)
},
{
id: 'session-lifecycle',
title: 'Session Lifecycle',
icon: <RefreshCw className="w-5 h-5" />,
content: (
<div className="space-y-4">
<p className="text-gray-600 mb-4">
Sessions verwalten den Zustand von Agent-Interaktionen. Jede Session hat einen definierten
Lebenszyklus mit Checkpoints fuer Recovery.
</p>
<div className="bg-gray-50 rounded-xl p-6 font-mono text-sm">
<div className="text-gray-500 mb-2"># Session State Machine</div>
<pre className="text-gray-700">{`
┌─────────────────────────────────────┐
│ │
▼ │
┌──────────┐ ┌──────────┐ ┌──────────┐ ┌──────────┐
│ ACTIVE │───▶│ PAUSED │───▶│ COMPLETED│ │ FAILED │
└──────────┘ └──────────┘ └──────────┘ └──────────┘
│ │ ▲
│ │ │
└───────────────┴───────────────────────────────┘
(bei Fehler)
States:
- ACTIVE: Session laeuft, Agent verarbeitet Tasks
- PAUSED: Session pausiert, wartet auf Eingabe
- COMPLETED: Session erfolgreich beendet
- FAILED: Session mit Fehler beendet
`}</pre>
</div>
<div className="mt-6">
<h4 className="font-semibold text-gray-900 mb-3">Heartbeat Monitoring</h4>
<div className="bg-white border border-gray-200 rounded-xl p-5">
<div className="grid grid-cols-3 gap-4 text-center">
<div>
<div className="text-2xl font-bold text-gray-900">30s</div>
<div className="text-sm text-gray-500">Timeout</div>
</div>
<div>
<div className="text-2xl font-bold text-gray-900">5s</div>
<div className="text-sm text-gray-500">Check Interval</div>
</div>
<div>
<div className="text-2xl font-bold text-gray-900">3</div>
<div className="text-sm text-gray-500">Max Missed Beats</div>
</div>
</div>
<p className="text-sm text-gray-600 mt-4 text-center">
Nach 3 verpassten Heartbeats wird der Agent als ausgefallen markiert und die
Restart-Policy greift (max. 3 Versuche).
</p>
</div>
</div>
</div>
)
},
{
id: 'database',
title: 'Datenbank-Schema',
icon: <Database className="w-5 h-5" />,
content: (
<div className="space-y-4">
<p className="text-gray-600 mb-4">
Das Agent-System nutzt PostgreSQL fuer persistente Daten und Valkey (Redis) fuer Caching und Pub/Sub.
</p>
<div className="space-y-4">
{/* agent_sessions */}
<div className="bg-white border border-gray-200 rounded-xl p-4">
<h4 className="font-semibold text-gray-900 mb-2 font-mono">agent_sessions</h4>
<p className="text-sm text-gray-600 mb-3">Speichert Session-Daten mit Checkpoints</p>
<div className="bg-gray-50 rounded-lg p-3 font-mono text-xs overflow-x-auto">
<pre>{`
CREATE TABLE agent_sessions (
id UUID PRIMARY KEY,
agent_type VARCHAR(50) NOT NULL,
user_id UUID REFERENCES users(id),
state VARCHAR(20) NOT NULL DEFAULT 'active',
context JSONB DEFAULT '{}',
checkpoints JSONB DEFAULT '[]',
created_at TIMESTAMPTZ DEFAULT NOW(),
updated_at TIMESTAMPTZ DEFAULT NOW(),
last_heartbeat TIMESTAMPTZ DEFAULT NOW()
);
`}</pre>
</div>
</div>
{/* agent_memory */}
<div className="bg-white border border-gray-200 rounded-xl p-4">
<h4 className="font-semibold text-gray-900 mb-2 font-mono">agent_memory</h4>
<p className="text-sm text-gray-600 mb-3">Langzeit-Gedaechtnis mit TTL</p>
<div className="bg-gray-50 rounded-lg p-3 font-mono text-xs overflow-x-auto">
<pre>{`
CREATE TABLE agent_memory (
id UUID PRIMARY KEY,
namespace VARCHAR(100) NOT NULL,
key VARCHAR(500) NOT NULL,
value JSONB NOT NULL,
agent_id VARCHAR(50) NOT NULL,
access_count INTEGER DEFAULT 0,
created_at TIMESTAMPTZ DEFAULT NOW(),
expires_at TIMESTAMPTZ,
UNIQUE(namespace, key)
);
`}</pre>
</div>
</div>
{/* agent_messages */}
<div className="bg-white border border-gray-200 rounded-xl p-4">
<h4 className="font-semibold text-gray-900 mb-2 font-mono">agent_messages</h4>
<p className="text-sm text-gray-600 mb-3">Audit-Trail fuer Inter-Agent Kommunikation</p>
<div className="bg-gray-50 rounded-lg p-3 font-mono text-xs overflow-x-auto">
<pre>{`
CREATE TABLE agent_messages (
id UUID PRIMARY KEY,
sender VARCHAR(50) NOT NULL,
receiver VARCHAR(50) NOT NULL,
message_type VARCHAR(50) NOT NULL,
payload JSONB NOT NULL,
priority INTEGER DEFAULT 1,
correlation_id UUID,
created_at TIMESTAMPTZ DEFAULT NOW()
);
`}</pre>
</div>
</div>
</div>
</div>
)
}
]
return (
<div className="p-6 max-w-5xl 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>
<h1 className="text-2xl font-bold text-gray-900 flex items-center gap-3">
<div className="p-2 bg-purple-100 rounded-lg">
<FileText className="w-6 h-6 text-purple-600" />
</div>
Multi-Agent Architektur
</h1>
<p className="text-gray-500 mt-1">
Technische Dokumentation des Breakpilot Multi-Agent-Systems
</p>
</div>
{/* Table of Contents */}
<div className="bg-gray-50 rounded-xl p-5 mb-8">
<h2 className="font-semibold text-gray-900 mb-3">Inhaltsverzeichnis</h2>
<div className="grid grid-cols-2 md:grid-cols-4 gap-2">
{sections.map(section => (
<button
key={section.id}
onClick={() => {
if (!expandedSections.includes(section.id)) {
setExpandedSections(prev => [...prev, section.id])
}
document.getElementById(section.id)?.scrollIntoView({ behavior: 'smooth' })
}}
className="flex items-center gap-2 text-sm text-gray-600 hover:text-teal-600 text-left p-2 rounded-lg hover:bg-gray-100 transition-colors"
>
{section.icon}
<span className="truncate">{section.title}</span>
</button>
))}
</div>
</div>
{/* Sections */}
<div className="space-y-4">
{sections.map(section => (
<div
key={section.id}
id={section.id}
className="bg-white border border-gray-200 rounded-xl overflow-hidden"
>
<button
onClick={() => toggleSection(section.id)}
className="w-full flex items-center justify-between p-5 hover:bg-gray-50 transition-colors"
>
<div className="flex items-center gap-3">
<div className="p-2 bg-gray-100 rounded-lg">
{section.icon}
</div>
<span className="font-semibold text-gray-900">{section.title}</span>
</div>
{expandedSections.includes(section.id) ? (
<ChevronDown className="w-5 h-5 text-gray-400" />
) : (
<ChevronRight className="w-5 h-5 text-gray-400" />
)}
</button>
{expandedSections.includes(section.id) && (
<div className="px-5 pb-5 border-t border-gray-100 pt-4">
{section.content}
</div>
)}
</div>
))}
</div>
{/* Footer Links */}
<div className="mt-8 bg-teal-50 border border-teal-200 rounded-xl p-5">
<h3 className="font-semibold text-teal-900 mb-3">Weiterführende Ressourcen</h3>
<div className="grid grid-cols-1 md:grid-cols-3 gap-3">
<Link
href="/ai/agents"
className="flex items-center gap-2 text-sm text-teal-700 hover:text-teal-900"
>
<Cpu className="w-4 h-4" />
Agent-Uebersicht
</Link>
<Link
href="/ai/agents/sessions"
className="flex items-center gap-2 text-sm text-teal-700 hover:text-teal-900"
>
<Activity className="w-4 h-4" />
Aktive Sessions
</Link>
<Link
href="/ai/agents/statistics"
className="flex items-center gap-2 text-sm text-teal-700 hover:text-teal-900"
>
<Database className="w-4 h-4" />
Statistiken
</Link>
</div>
</div>
</div>
)
}

View File

@@ -0,0 +1,377 @@
'use client'
import { useState, useEffect } from 'react'
import Link from 'next/link'
import { Bot, Activity, Brain, Settings, FileText, BarChart3, Clock, AlertTriangle, CheckCircle, Pause, XCircle, ChevronRight, Cpu, MessageSquare, Database, RefreshCw } from 'lucide-react'
// Agent types
interface AgentConfig {
id: string
name: string
description: string
soulFile: string
color: string
icon: 'bot' | 'brain' | 'message' | 'alert' | 'settings'
status: 'running' | 'paused' | 'stopped' | 'error'
activeSessions: number
totalProcessed: number
avgResponseTime: number
lastActivity: string
}
interface AgentStats {
totalSessions: number
activeSessions: number
totalMessages: number
avgLatency: number
errorRate: number
memoryUsage: number
}
// Mock data - In production, fetch from API
const mockAgents: AgentConfig[] = [
{
id: 'tutor-agent',
name: 'TutorAgent',
description: 'Lernbegleitung und Fragen beantworten',
soulFile: 'tutor-agent.soul.md',
color: '#3b82f6',
icon: 'brain',
status: 'running',
activeSessions: 12,
totalProcessed: 1847,
avgResponseTime: 234,
lastActivity: '2 min ago'
},
{
id: 'grader-agent',
name: 'GraderAgent',
description: 'Klausur-Korrektur und Bewertung',
soulFile: 'grader-agent.soul.md',
color: '#10b981',
icon: 'bot',
status: 'running',
activeSessions: 3,
totalProcessed: 456,
avgResponseTime: 1205,
lastActivity: '5 min ago'
},
{
id: 'quality-judge',
name: 'QualityJudge',
description: 'BQAS Qualitaetspruefung',
soulFile: 'quality-judge.soul.md',
color: '#f59e0b',
icon: 'settings',
status: 'running',
activeSessions: 8,
totalProcessed: 3291,
avgResponseTime: 89,
lastActivity: '1 min ago'
},
{
id: 'alert-agent',
name: 'AlertAgent',
description: 'Monitoring und Benachrichtigungen',
soulFile: 'alert-agent.soul.md',
color: '#ef4444',
icon: 'alert',
status: 'running',
activeSessions: 1,
totalProcessed: 892,
avgResponseTime: 45,
lastActivity: '30 sec ago'
},
{
id: 'orchestrator',
name: 'Orchestrator',
description: 'Task-Koordination und Routing',
soulFile: 'orchestrator.soul.md',
color: '#8b5cf6',
icon: 'message',
status: 'running',
activeSessions: 24,
totalProcessed: 8934,
avgResponseTime: 12,
lastActivity: 'just now'
}
]
const mockStats: AgentStats = {
totalSessions: 156,
activeSessions: 48,
totalMessages: 15420,
avgLatency: 156,
errorRate: 0.8,
memoryUsage: 67
}
function getIconComponent(icon: string, className: string) {
switch(icon) {
case 'bot': return <Bot className={className} />
case 'brain': return <Brain className={className} />
case 'message': return <MessageSquare className={className} />
case 'alert': return <AlertTriangle className={className} />
case 'settings': return <Settings className={className} />
default: return <Bot className={className} />
}
}
function getStatusIcon(status: string) {
switch(status) {
case 'running': return <CheckCircle className="w-4 h-4 text-green-500" />
case 'paused': return <Pause className="w-4 h-4 text-yellow-500" />
case 'stopped': return <XCircle className="w-4 h-4 text-gray-500" />
case 'error': return <AlertTriangle className="w-4 h-4 text-red-500" />
default: return null
}
}
function getStatusColor(status: string) {
switch(status) {
case 'running': return 'bg-green-500/10 text-green-600 border-green-500/20'
case 'paused': return 'bg-yellow-500/10 text-yellow-600 border-yellow-500/20'
case 'stopped': return 'bg-gray-500/10 text-gray-600 border-gray-500/20'
case 'error': return 'bg-red-500/10 text-red-600 border-red-500/20'
default: return 'bg-gray-500/10 text-gray-600 border-gray-500/20'
}
}
export default function AgentManagementPage() {
const [agents, setAgents] = useState<AgentConfig[]>(mockAgents)
const [stats, setStats] = useState<AgentStats>(mockStats)
const [loading, setLoading] = useState(false)
const [lastRefresh, setLastRefresh] = useState(new Date())
const refreshData = async () => {
setLoading(true)
// In production, fetch from API
// const response = await fetch('/api/admin/agents/status')
await new Promise(resolve => setTimeout(resolve, 500))
setLastRefresh(new Date())
setLoading(false)
}
useEffect(() => {
// Auto-refresh every 30 seconds
const interval = setInterval(refreshData, 30000)
return () => clearInterval(interval)
}, [])
return (
<div className="p-6 max-w-7xl mx-auto">
{/* Header */}
<div className="flex items-center justify-between mb-8">
<div>
<h1 className="text-2xl font-bold text-gray-900 flex items-center gap-3">
<div className="p-2 bg-teal-100 rounded-lg">
<Bot className="w-6 h-6 text-teal-600" />
</div>
Agent Management
</h1>
<p className="text-gray-500 mt-1">
Multi-Agent System verwalten, SOUL-Files bearbeiten, Statistiken analysieren
</p>
</div>
<div className="flex items-center gap-3">
<span className="text-sm text-gray-500">
Letzte Aktualisierung: {lastRefresh.toLocaleTimeString('de-DE')}
</span>
<button
onClick={refreshData}
disabled={loading}
className="flex items-center gap-2 px-4 py-2 bg-teal-600 text-white rounded-lg hover:bg-teal-700 transition-colors disabled:opacity-50"
>
<RefreshCw className={`w-4 h-4 ${loading ? 'animate-spin' : ''}`} />
Aktualisieren
</button>
</div>
</div>
{/* Quick Links */}
<div className="grid grid-cols-1 md:grid-cols-4 gap-4 mb-8">
<Link
href="/ai/agents/architecture"
className="flex items-center gap-3 p-4 bg-white border border-gray-200 rounded-xl hover:border-teal-300 hover:shadow-md transition-all group"
>
<div className="p-2 bg-purple-100 rounded-lg group-hover:bg-purple-200 transition-colors">
<FileText className="w-5 h-5 text-purple-600" />
</div>
<div>
<div className="font-medium text-gray-900">Architektur</div>
<div className="text-sm text-gray-500">Dokumentation & Diagramme</div>
</div>
<ChevronRight className="w-5 h-5 text-gray-400 ml-auto" />
</Link>
<Link
href="/ai/agents/sessions"
className="flex items-center gap-3 p-4 bg-white border border-gray-200 rounded-xl hover:border-teal-300 hover:shadow-md transition-all group"
>
<div className="p-2 bg-blue-100 rounded-lg group-hover:bg-blue-200 transition-colors">
<Activity className="w-5 h-5 text-blue-600" />
</div>
<div>
<div className="font-medium text-gray-900">Sessions</div>
<div className="text-sm text-gray-500">{stats.activeSessions} aktiv</div>
</div>
<ChevronRight className="w-5 h-5 text-gray-400 ml-auto" />
</Link>
<Link
href="/ai/agents/statistics"
className="flex items-center gap-3 p-4 bg-white border border-gray-200 rounded-xl hover:border-teal-300 hover:shadow-md transition-all group"
>
<div className="p-2 bg-green-100 rounded-lg group-hover:bg-green-200 transition-colors">
<BarChart3 className="w-5 h-5 text-green-600" />
</div>
<div>
<div className="font-medium text-gray-900">Statistiken</div>
<div className="text-sm text-gray-500">Performance & Trends</div>
</div>
<ChevronRight className="w-5 h-5 text-gray-400 ml-auto" />
</Link>
<Link
href="/ai/test-quality"
className="flex items-center gap-3 p-4 bg-white border border-gray-200 rounded-xl hover:border-teal-300 hover:shadow-md transition-all group"
>
<div className="p-2 bg-amber-100 rounded-lg group-hover:bg-amber-200 transition-colors">
<Cpu className="w-5 h-5 text-amber-600" />
</div>
<div>
<div className="font-medium text-gray-900">BQAS</div>
<div className="text-sm text-gray-500">Qualitaetssicherung</div>
</div>
<ChevronRight className="w-5 h-5 text-gray-400 ml-auto" />
</Link>
</div>
{/* Stats Overview */}
<div className="grid grid-cols-2 md:grid-cols-6 gap-4 mb-8">
<div className="bg-white border border-gray-200 rounded-xl p-4">
<div className="text-sm text-gray-500 mb-1">Gesamt Sessions</div>
<div className="text-2xl font-bold text-gray-900">{stats.totalSessions.toLocaleString()}</div>
</div>
<div className="bg-white border border-gray-200 rounded-xl p-4">
<div className="text-sm text-gray-500 mb-1">Aktive Sessions</div>
<div className="text-2xl font-bold text-green-600">{stats.activeSessions}</div>
</div>
<div className="bg-white border border-gray-200 rounded-xl p-4">
<div className="text-sm text-gray-500 mb-1">Nachrichten (24h)</div>
<div className="text-2xl font-bold text-gray-900">{stats.totalMessages.toLocaleString()}</div>
</div>
<div className="bg-white border border-gray-200 rounded-xl p-4">
<div className="text-sm text-gray-500 mb-1">Avg. Latenz</div>
<div className="text-2xl font-bold text-gray-900">{stats.avgLatency}ms</div>
</div>
<div className="bg-white border border-gray-200 rounded-xl p-4">
<div className="text-sm text-gray-500 mb-1">Fehlerrate</div>
<div className="text-2xl font-bold text-amber-600">{stats.errorRate}%</div>
</div>
<div className="bg-white border border-gray-200 rounded-xl p-4">
<div className="text-sm text-gray-500 mb-1">Memory Usage</div>
<div className="text-2xl font-bold text-gray-900">{stats.memoryUsage}%</div>
</div>
</div>
{/* Agents Grid */}
<div className="mb-6">
<h2 className="text-lg font-semibold text-gray-900 mb-4">Agents</h2>
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-4">
{agents.map((agent) => (
<Link
key={agent.id}
href={`/ai/agents/${agent.id}`}
className="bg-white border border-gray-200 rounded-xl p-5 hover:border-teal-300 hover:shadow-lg transition-all group"
>
{/* Header */}
<div className="flex items-start justify-between mb-4">
<div className="flex items-center gap-3">
<div
className="p-2.5 rounded-lg"
style={{ backgroundColor: `${agent.color}20` }}
>
{getIconComponent(agent.icon, `w-5 h-5`)}
<style jsx>{`
svg { color: ${agent.color}; }
`}</style>
</div>
<div>
<h3 className="font-semibold text-gray-900 group-hover:text-teal-600 transition-colors">
{agent.name}
</h3>
<p className="text-sm text-gray-500">{agent.description}</p>
</div>
</div>
<div className={`flex items-center gap-1.5 px-2 py-1 rounded-full text-xs font-medium border ${getStatusColor(agent.status)}`}>
{getStatusIcon(agent.status)}
{agent.status}
</div>
</div>
{/* Stats */}
<div className="grid grid-cols-3 gap-3 mb-4">
<div className="text-center p-2 bg-gray-50 rounded-lg">
<div className="text-lg font-semibold text-gray-900">{agent.activeSessions}</div>
<div className="text-xs text-gray-500">Sessions</div>
</div>
<div className="text-center p-2 bg-gray-50 rounded-lg">
<div className="text-lg font-semibold text-gray-900">{agent.totalProcessed}</div>
<div className="text-xs text-gray-500">Verarbeitet</div>
</div>
<div className="text-center p-2 bg-gray-50 rounded-lg">
<div className="text-lg font-semibold text-gray-900">{agent.avgResponseTime}ms</div>
<div className="text-xs text-gray-500">Avg. Zeit</div>
</div>
</div>
{/* Footer */}
<div className="flex items-center justify-between pt-3 border-t border-gray-100">
<div className="flex items-center gap-2 text-sm text-gray-500">
<FileText className="w-4 h-4" />
{agent.soulFile}
</div>
<div className="flex items-center gap-1 text-sm text-gray-400">
<Clock className="w-3.5 h-3.5" />
{agent.lastActivity}
</div>
</div>
</Link>
))}
</div>
</div>
{/* Info Box */}
<div className="bg-teal-50 border border-teal-200 rounded-xl p-5">
<div className="flex gap-4">
<div className="p-2 bg-teal-100 rounded-lg h-fit">
<Brain className="w-5 h-5 text-teal-600" />
</div>
<div>
<h3 className="font-semibold text-teal-900 mb-2">Multi-Agent Architektur</h3>
<p className="text-sm text-teal-700 mb-3">
Das Breakpilot Multi-Agent-System basiert auf dem Mission Control Konzept. Jeder Agent hat eine
definierte Persoenlichkeit (SOUL-File), die sein Verhalten steuert. Die Agents kommunizieren
ueber einen Message Bus und nutzen ein gemeinsames Gedaechtnis (Shared Brain).
</p>
<div className="flex gap-3">
<Link
href="/ai/agents/architecture"
className="text-sm font-medium text-teal-600 hover:text-teal-800"
>
Architektur ansehen &rarr;
</Link>
<Link
href="/ai/agents/architecture#soul-files"
className="text-sm font-medium text-teal-600 hover:text-teal-800"
>
SOUL-Files verstehen &rarr;
</Link>
</div>
</div>
</div>
</div>
</div>
)
}

View File

@@ -0,0 +1,444 @@
'use client'
import { useState, useEffect } from 'react'
import Link from 'next/link'
import { ArrowLeft, Activity, Clock, User, Bot, Brain, MessageSquare, AlertTriangle, Settings, CheckCircle, Pause, XCircle, RefreshCw, Filter, Search, ChevronRight, Zap, MoreVertical } from 'lucide-react'
// Session types
interface AgentSession {
id: string
agentType: string
agentId: string
userId: string
userName: string
state: 'active' | 'paused' | 'completed' | 'failed'
createdAt: string
lastActivity: string
checkpointCount: number
messagesProcessed: number
currentTask: string | null
avgResponseTime: number
}
// Mock data
const mockSessions: AgentSession[] = [
{
id: 'session-001',
agentType: 'tutor-agent',
agentId: 'tutor-1',
userId: 'user-123',
userName: 'Max Mustermann',
state: 'active',
createdAt: '2026-02-03T14:30:00Z',
lastActivity: '2026-02-03T15:45:23Z',
checkpointCount: 5,
messagesProcessed: 23,
currentTask: 'Erklaere Quadratische Funktionen',
avgResponseTime: 245
},
{
id: 'session-002',
agentType: 'tutor-agent',
agentId: 'tutor-2',
userId: 'user-456',
userName: 'Anna Schmidt',
state: 'active',
createdAt: '2026-02-03T15:00:00Z',
lastActivity: '2026-02-03T15:44:12Z',
checkpointCount: 3,
messagesProcessed: 12,
currentTask: 'Hilfe bei Gedichtanalyse',
avgResponseTime: 312
},
{
id: 'session-003',
agentType: 'grader-agent',
agentId: 'grader-1',
userId: 'user-789',
userName: 'Frau Mueller (Lehrerin)',
state: 'active',
createdAt: '2026-02-03T14:00:00Z',
lastActivity: '2026-02-03T15:42:00Z',
checkpointCount: 12,
messagesProcessed: 45,
currentTask: 'Korrektur Klausur 10b - Arbeit 7/24',
avgResponseTime: 1205
},
{
id: 'session-004',
agentType: 'quality-judge',
agentId: 'judge-1',
userId: 'system',
userName: 'System (BQAS)',
state: 'active',
createdAt: '2026-02-03T08:00:00Z',
lastActivity: '2026-02-03T15:45:01Z',
checkpointCount: 156,
messagesProcessed: 892,
currentTask: 'Quality Check Queue Processing',
avgResponseTime: 89
},
{
id: 'session-005',
agentType: 'orchestrator',
agentId: 'orchestrator-main',
userId: 'system',
userName: 'System',
state: 'active',
createdAt: '2026-02-03T00:00:00Z',
lastActivity: '2026-02-03T15:45:30Z',
checkpointCount: 2341,
messagesProcessed: 8934,
currentTask: 'Routing incoming requests',
avgResponseTime: 12
},
{
id: 'session-006',
agentType: 'tutor-agent',
agentId: 'tutor-3',
userId: 'user-101',
userName: 'Tim Berger',
state: 'paused',
createdAt: '2026-02-03T13:00:00Z',
lastActivity: '2026-02-03T14:30:00Z',
checkpointCount: 8,
messagesProcessed: 34,
currentTask: null,
avgResponseTime: 278
},
{
id: 'session-007',
agentType: 'grader-agent',
agentId: 'grader-2',
userId: 'user-202',
userName: 'Herr Weber (Lehrer)',
state: 'completed',
createdAt: '2026-02-03T10:00:00Z',
lastActivity: '2026-02-03T12:00:00Z',
checkpointCount: 24,
messagesProcessed: 120,
currentTask: null,
avgResponseTime: 1102
},
{
id: 'session-008',
agentType: 'alert-agent',
agentId: 'alert-1',
userId: 'system',
userName: 'System (Monitoring)',
state: 'active',
createdAt: '2026-02-03T00:00:00Z',
lastActivity: '2026-02-03T15:45:28Z',
checkpointCount: 48,
messagesProcessed: 256,
currentTask: 'Monitoring System Health',
avgResponseTime: 45
}
]
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" />
}
}
function getAgentColor(agentType: string) {
switch (agentType) {
case 'tutor-agent': return { bg: 'bg-blue-100', text: 'text-blue-600', border: 'border-blue-200' }
case 'grader-agent': return { bg: 'bg-green-100', text: 'text-green-600', border: 'border-green-200' }
case 'quality-judge': return { bg: 'bg-amber-100', text: 'text-amber-600', border: 'border-amber-200' }
case 'alert-agent': return { bg: 'bg-red-100', text: 'text-red-600', border: 'border-red-200' }
case 'orchestrator': return { bg: 'bg-purple-100', text: 'text-purple-600', border: 'border-purple-200' }
default: return { bg: 'bg-gray-100', text: 'text-gray-600', border: 'border-gray-200' }
}
}
function getStateConfig(state: string) {
switch (state) {
case 'active':
return { icon: <CheckCircle className="w-4 h-4" />, color: 'bg-green-100 text-green-700 border-green-200', label: 'Aktiv' }
case 'paused':
return { icon: <Pause className="w-4 h-4" />, color: 'bg-yellow-100 text-yellow-700 border-yellow-200', label: 'Pausiert' }
case 'completed':
return { icon: <CheckCircle className="w-4 h-4" />, color: 'bg-gray-100 text-gray-600 border-gray-200', label: 'Beendet' }
case 'failed':
return { icon: <XCircle className="w-4 h-4" />, color: 'bg-red-100 text-red-700 border-red-200', label: 'Fehlgeschlagen' }
default:
return { icon: null, color: 'bg-gray-100 text-gray-600 border-gray-200', label: state }
}
}
function formatDuration(isoDate: string): string {
const date = new Date(isoDate)
const now = new Date()
const diffMs = now.getTime() - date.getTime()
const diffMins = Math.floor(diffMs / 60000)
const diffHours = Math.floor(diffMins / 60)
const diffDays = Math.floor(diffHours / 24)
if (diffDays > 0) return `${diffDays}d ${diffHours % 24}h`
if (diffHours > 0) return `${diffHours}h ${diffMins % 60}m`
return `${diffMins}m`
}
function formatTime(isoDate: string): string {
return new Date(isoDate).toLocaleTimeString('de-DE', {
hour: '2-digit',
minute: '2-digit',
second: '2-digit'
})
}
export default function SessionsPage() {
const [sessions, setSessions] = useState<AgentSession[]>(mockSessions)
const [loading, setLoading] = useState(false)
const [filter, setFilter] = useState<string>('all')
const [searchTerm, setSearchTerm] = useState('')
const [lastRefresh, setLastRefresh] = useState(new Date())
const refreshData = async () => {
setLoading(true)
// In production, fetch from API
await new Promise(resolve => setTimeout(resolve, 500))
setLastRefresh(new Date())
setLoading(false)
}
useEffect(() => {
const interval = setInterval(refreshData, 10000) // Refresh every 10s
return () => clearInterval(interval)
}, [])
// Filter sessions
const filteredSessions = sessions.filter(session => {
if (filter !== 'all' && session.state !== filter) return false
if (searchTerm) {
const search = searchTerm.toLowerCase()
return (
session.userName.toLowerCase().includes(search) ||
session.agentType.toLowerCase().includes(search) ||
session.currentTask?.toLowerCase().includes(search) ||
session.id.toLowerCase().includes(search)
)
}
return true
})
// Stats
const stats = {
total: sessions.length,
active: sessions.filter(s => s.state === 'active').length,
paused: sessions.filter(s => s.state === 'paused').length,
completed: sessions.filter(s => s.state === 'completed').length,
failed: sessions.filter(s => s.state === 'failed').length,
totalMessages: sessions.reduce((sum, s) => sum + s.messagesProcessed, 0),
avgResponseTime: Math.round(sessions.reduce((sum, s) => sum + s.avgResponseTime, 0) / sessions.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-blue-100 rounded-lg">
<Activity className="w-6 h-6 text-blue-600" />
</div>
Aktive Sessions
</h1>
<p className="text-gray-500 mt-1">
Live-Uebersicht aller Agent-Sessions im System
</p>
</div>
<div className="flex items-center gap-3">
<span className="text-sm text-gray-500">
Letzte Aktualisierung: {lastRefresh.toLocaleTimeString('de-DE')}
</span>
<button
onClick={refreshData}
disabled={loading}
className="flex items-center gap-2 px-4 py-2 bg-blue-600 text-white rounded-lg hover:bg-blue-700 transition-colors disabled:opacity-50"
>
<RefreshCw className={`w-4 h-4 ${loading ? 'animate-spin' : ''}`} />
Aktualisieren
</button>
</div>
</div>
</div>
{/* Stats */}
<div className="grid grid-cols-2 md:grid-cols-6 gap-4 mb-6">
<div className="bg-white border border-gray-200 rounded-xl p-4">
<div className="text-sm text-gray-500 mb-1">Gesamt</div>
<div className="text-2xl font-bold text-gray-900">{stats.total}</div>
</div>
<div className="bg-white border border-green-200 rounded-xl p-4">
<div className="text-sm text-green-600 mb-1">Aktiv</div>
<div className="text-2xl font-bold text-green-600">{stats.active}</div>
</div>
<div className="bg-white border border-yellow-200 rounded-xl p-4">
<div className="text-sm text-yellow-600 mb-1">Pausiert</div>
<div className="text-2xl font-bold text-yellow-600">{stats.paused}</div>
</div>
<div className="bg-white border border-gray-200 rounded-xl p-4">
<div className="text-sm text-gray-500 mb-1">Beendet</div>
<div className="text-2xl font-bold text-gray-600">{stats.completed}</div>
</div>
<div className="bg-white border border-gray-200 rounded-xl p-4">
<div className="text-sm text-gray-500 mb-1">Messages (24h)</div>
<div className="text-2xl font-bold text-gray-900">{stats.totalMessages.toLocaleString()}</div>
</div>
<div className="bg-white border border-gray-200 rounded-xl p-4">
<div className="text-sm text-gray-500 mb-1">Avg. Response</div>
<div className="text-2xl font-bold text-gray-900">{stats.avgResponseTime}ms</div>
</div>
</div>
{/* Filters */}
<div className="flex flex-col md:flex-row gap-4 mb-6">
<div className="relative flex-1">
<Search className="absolute left-3 top-1/2 -translate-y-1/2 w-4 h-4 text-gray-400" />
<input
type="text"
placeholder="Session, Benutzer oder Task suchen..."
value={searchTerm}
onChange={(e) => setSearchTerm(e.target.value)}
className="w-full pl-10 pr-4 py-2 border border-gray-200 rounded-lg focus:outline-none focus:ring-2 focus:ring-blue-500 focus:border-transparent"
/>
</div>
<div className="flex items-center gap-2">
<Filter className="w-4 h-4 text-gray-400" />
<select
value={filter}
onChange={(e) => setFilter(e.target.value)}
className="px-4 py-2 border border-gray-200 rounded-lg focus:outline-none focus:ring-2 focus:ring-blue-500"
>
<option value="all">Alle Status</option>
<option value="active">Aktiv</option>
<option value="paused">Pausiert</option>
<option value="completed">Beendet</option>
<option value="failed">Fehlgeschlagen</option>
</select>
</div>
</div>
{/* Sessions List */}
<div className="bg-white border border-gray-200 rounded-xl overflow-hidden">
<div className="overflow-x-auto">
<table className="min-w-full divide-y divide-gray-200">
<thead className="bg-gray-50">
<tr>
<th className="px-4 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">Agent</th>
<th className="px-4 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">Benutzer</th>
<th className="px-4 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">Status</th>
<th className="px-4 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">Aktueller Task</th>
<th className="px-4 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">Dauer</th>
<th className="px-4 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">Messages</th>
<th className="px-4 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">Letzte Aktivitaet</th>
<th className="px-4 py-3 text-right text-xs font-medium text-gray-500 uppercase tracking-wider"></th>
</tr>
</thead>
<tbody className="bg-white divide-y divide-gray-200">
{filteredSessions.map(session => {
const agentColor = getAgentColor(session.agentType)
const stateConfig = getStateConfig(session.state)
return (
<tr key={session.id} className="hover:bg-gray-50">
<td className="px-4 py-4 whitespace-nowrap">
<div className="flex items-center gap-3">
<div className={`p-2 rounded-lg ${agentColor.bg}`}>
<span className={agentColor.text}>{getAgentIcon(session.agentType)}</span>
</div>
<div>
<div className="font-medium text-gray-900">{session.agentId}</div>
<div className="text-xs text-gray-500">{session.agentType}</div>
</div>
</div>
</td>
<td className="px-4 py-4 whitespace-nowrap">
<div className="flex items-center gap-2">
<User className="w-4 h-4 text-gray-400" />
<span className="text-sm text-gray-900">{session.userName}</span>
</div>
</td>
<td className="px-4 py-4 whitespace-nowrap">
<span className={`inline-flex items-center gap-1.5 px-2.5 py-1 rounded-full text-xs font-medium border ${stateConfig.color}`}>
{stateConfig.icon}
{stateConfig.label}
</span>
</td>
<td className="px-4 py-4">
{session.currentTask ? (
<div className="flex items-center gap-2 max-w-xs">
<Zap className="w-3.5 h-3.5 text-amber-500 flex-shrink-0" />
<span className="text-sm text-gray-700 truncate">{session.currentTask}</span>
</div>
) : (
<span className="text-sm text-gray-400">-</span>
)}
</td>
<td className="px-4 py-4 whitespace-nowrap">
<div className="flex items-center gap-1.5 text-sm text-gray-600">
<Clock className="w-3.5 h-3.5" />
{formatDuration(session.createdAt)}
</div>
</td>
<td className="px-4 py-4 whitespace-nowrap">
<div className="text-sm">
<span className="font-medium text-gray-900">{session.messagesProcessed}</span>
<span className="text-gray-500 ml-1">({session.checkpointCount} CP)</span>
</div>
</td>
<td className="px-4 py-4 whitespace-nowrap">
<div className="text-sm text-gray-600">{formatTime(session.lastActivity)}</div>
<div className="text-xs text-gray-400">{session.avgResponseTime}ms avg</div>
</td>
<td className="px-4 py-4 whitespace-nowrap text-right">
<Link
href={`/ai/agents/${session.agentType.replace('-agent', '-agent')}`}
className="p-2 hover:bg-gray-100 rounded-lg inline-flex items-center gap-1 text-sm text-gray-600 hover:text-gray-900"
>
Details
<ChevronRight className="w-4 h-4" />
</Link>
</td>
</tr>
)
})}
</tbody>
</table>
</div>
{filteredSessions.length === 0 && (
<div className="text-center py-12">
<Activity className="w-12 h-12 text-gray-300 mx-auto mb-3" />
<p className="text-gray-500">Keine Sessions gefunden</p>
</div>
)}
</div>
{/* Live Activity Indicator */}
<div className="mt-6 flex items-center justify-center gap-2 text-sm text-gray-500">
<span className="relative flex h-2 w-2">
<span className="animate-ping absolute inline-flex h-full w-full rounded-full bg-green-400 opacity-75"></span>
<span className="relative inline-flex rounded-full h-2 w-2 bg-green-500"></span>
</span>
Live-Daten - Auto-Refresh alle 10 Sekunden
</div>
</div>
)
}

View File

@@ -0,0 +1,491 @@
'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>
)
}