Files
breakpilot-lehrer/website/app/admin/rag/_components/MetricsTab.tsx
Benjamin Admin 0b37c5e692 [split-required] Split website + studio-v2 monoliths (Phase 3 continued)
Website (14 monoliths split):
- compliance/page.tsx (1,519 → 9), docs/audit (1,262 → 20)
- quality (1,231 → 16), alerts (1,203 → 10), docs (1,202 → 11)
- i18n.ts (1,173 → 8 language files)
- unity-bridge (1,094 → 12), backlog (1,087 → 6)
- training (1,066 → 8), rag (1,063 → 8)
- Deleted index_original.ts (4,899 LOC dead backup)

Studio-v2 (5 monoliths split):
- meet/page.tsx (1,481 → 9), messages (1,166 → 9)
- AlertsB2BContext.tsx (1,165 → 5 modules)
- alerts-b2b/page.tsx (1,019 → 6), korrektur/archiv (1,001 → 6)

All existing imports preserved. Zero new TypeScript errors.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-24 17:52:36 +02:00

112 lines
4.4 KiB
TypeScript

'use client'
import { useState, useEffect } from 'react'
import type { Collection } from './types'
import { API_BASE } from './types'
function MetricCard({ title, value, change, positive }: {
title: string
value: string
change?: string
positive?: boolean
}) {
return (
<div className="bg-white rounded-lg border border-slate-200 p-4">
<p className="text-xs text-slate-500 uppercase tracking-wider mb-1">{title}</p>
<div className="flex items-end gap-2">
<p className="text-2xl font-bold text-slate-900">{value}</p>
{change && (
<span className={`text-sm font-medium ${positive ? 'text-green-600' : 'text-red-600'}`}>{change}</span>
)}
</div>
</div>
)
}
function ScoreBar({ label, percent, color }: { label: string; percent: number; color: string }) {
return (
<div className="flex items-center gap-4">
<span className="text-sm text-slate-600 w-16">{label}</span>
<div className="flex-1 h-4 bg-slate-100 rounded-full overflow-hidden">
<div className={`h-full ${color} transition-all`} style={{ width: `${percent}%` }} />
</div>
<span className="text-sm text-slate-500 w-12 text-right">{percent}%</span>
</div>
)
}
export function MetricsTab({ collections }: { collections: Collection[] }) {
const [metrics, setMetrics] = useState({
precision: 0,
recall: 0,
mrr: 0,
avgLatency: 0,
totalRatings: 0,
errorRate: 0,
scoreDistribution: { '0.9+': 0, '0.7-0.9': 0, '0.5-0.7': 0, '<0.5': 0 },
})
const [loading, setLoading] = useState(true)
useEffect(() => {
const fetchMetrics = async () => {
try {
const res = await fetch(`${API_BASE}/api/v1/admin/rag/metrics`)
if (res.ok) {
const data = await res.json()
setMetrics({
precision: data.precision_at_5 || 0,
recall: data.recall_at_10 || 0,
mrr: data.mrr || 0,
avgLatency: data.avg_latency_ms || 0,
totalRatings: data.total_ratings || 0,
errorRate: data.error_rate || 0,
scoreDistribution: data.score_distribution || {},
})
}
} catch (err) {
console.error('Failed to fetch metrics:', err)
} finally {
setLoading(false)
}
}
fetchMetrics()
}, [])
return (
<div className="space-y-6">
<div>
<h2 className="text-lg font-semibold text-slate-900">RAG Qualitätsmetriken</h2>
<p className="text-sm text-slate-500">Übersicht über Retrieval-Performance und Nutzerbewertungen</p>
</div>
<div className="grid grid-cols-2 md:grid-cols-3 gap-4">
<MetricCard title="Precision@5" value={`${(metrics.precision * 100).toFixed(0)}%`} change="+5%" positive />
<MetricCard title="Recall@10" value={`${(metrics.recall * 100).toFixed(0)}%`} change="+3%" positive />
<MetricCard title="MRR" value={metrics.mrr.toFixed(2)} change="-2%" positive={false} />
<MetricCard title="Avg. Latenz" value={`${metrics.avgLatency}ms`} />
<MetricCard title="Bewertungen" value={metrics.totalRatings.toString()} />
<MetricCard title="Fehlerrate" value={`${metrics.errorRate}%`} />
</div>
<div className="bg-white rounded-lg border border-slate-200 p-6">
<h3 className="text-sm font-medium text-slate-700 mb-4">Score-Verteilung</h3>
{loading ? (
<div className="flex justify-center py-4"><div className="animate-spin rounded-full h-6 w-6 border-b-2 border-primary-600"></div></div>
) : (
<div className="space-y-3">
<ScoreBar label="0.9+" percent={metrics.scoreDistribution['0.9+'] || 0} color="bg-green-500" />
<ScoreBar label="0.7-0.9" percent={metrics.scoreDistribution['0.7-0.9'] || 0} color="bg-green-400" />
<ScoreBar label="0.5-0.7" percent={metrics.scoreDistribution['0.5-0.7'] || 0} color="bg-yellow-400" />
<ScoreBar label="<0.5" percent={metrics.scoreDistribution['<0.5'] || 0} color="bg-red-400" />
</div>
)}
</div>
<div className="flex gap-4">
<button className="px-4 py-2 text-sm font-medium text-slate-700 bg-white border border-slate-300 rounded-lg hover:bg-slate-50">Export CSV</button>
<button className="px-4 py-2 text-sm font-medium text-slate-700 bg-white border border-slate-300 rounded-lg hover:bg-slate-50">Detailbericht</button>
</div>
</div>
)
}