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>
112 lines
4.4 KiB
TypeScript
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>
|
|
)
|
|
}
|