'use client' /** * Model Management Page * * Manage ML model backends (PyTorch vs ONNX), view status, * run benchmarks, and configure inference settings. */ import { useState, useEffect, useCallback } from 'react' import { PagePurpose } from '@/components/common/PagePurpose' const KLAUSUR_API = '/klausur-api' // --------------------------------------------------------------------------- // Types // --------------------------------------------------------------------------- type BackendMode = 'auto' | 'pytorch' | 'onnx' type ModelStatus = 'available' | 'not_found' | 'loading' | 'error' type Tab = 'overview' | 'benchmarks' | 'configuration' interface ModelInfo { name: string key: string pytorch: { status: ModelStatus; size_mb: number; ram_mb: number } onnx: { status: ModelStatus; size_mb: number; ram_mb: number; quantized: boolean } } interface BenchmarkRow { model: string backend: string quantization: string size_mb: number ram_mb: number inference_ms: number load_time_s: number } interface StatusInfo { active_backend: BackendMode loaded_models: string[] cache_hits: number cache_misses: number uptime_s: number } // --------------------------------------------------------------------------- // Mock data (used when backend is not available) // --------------------------------------------------------------------------- const MOCK_MODELS: ModelInfo[] = [ { name: 'TrOCR Printed', key: 'trocr_printed', pytorch: { status: 'available', size_mb: 892, ram_mb: 1800 }, onnx: { status: 'available', size_mb: 234, ram_mb: 620, quantized: true }, }, { name: 'TrOCR Handwritten', key: 'trocr_handwritten', pytorch: { status: 'available', size_mb: 892, ram_mb: 1800 }, onnx: { status: 'not_found', size_mb: 0, ram_mb: 0, quantized: false }, }, { name: 'PP-DocLayout', key: 'pp_doclayout', pytorch: { status: 'not_found', size_mb: 0, ram_mb: 0 }, onnx: { status: 'available', size_mb: 48, ram_mb: 180, quantized: false }, }, ] const MOCK_BENCHMARKS: BenchmarkRow[] = [ { model: 'TrOCR Printed', backend: 'PyTorch', quantization: 'FP32', size_mb: 892, ram_mb: 1800, inference_ms: 142, load_time_s: 3.2 }, { model: 'TrOCR Printed', backend: 'ONNX', quantization: 'INT8', size_mb: 234, ram_mb: 620, inference_ms: 38, load_time_s: 0.8 }, { model: 'TrOCR Handwritten', backend: 'PyTorch', quantization: 'FP32', size_mb: 892, ram_mb: 1800, inference_ms: 156, load_time_s: 3.4 }, { model: 'PP-DocLayout', backend: 'ONNX', quantization: 'FP32', size_mb: 48, ram_mb: 180, inference_ms: 22, load_time_s: 0.3 }, ] const MOCK_STATUS: StatusInfo = { active_backend: 'auto', loaded_models: ['trocr_printed (ONNX)', 'pp_doclayout (ONNX)'], cache_hits: 1247, cache_misses: 83, uptime_s: 86400, } // --------------------------------------------------------------------------- // Helpers // --------------------------------------------------------------------------- function StatusBadge({ status }: { status: ModelStatus }) { const cls = status === 'available' ? 'bg-emerald-100 text-emerald-800 border-emerald-200' : status === 'loading' ? 'bg-blue-100 text-blue-800 border-blue-200' : status === 'not_found' ? 'bg-slate-100 text-slate-500 border-slate-200' : 'bg-red-100 text-red-800 border-red-200' const label = status === 'available' ? 'Verfuegbar' : status === 'loading' ? 'Laden...' : status === 'not_found' ? 'Nicht vorhanden' : 'Fehler' return ( {label} ) } function formatBytes(mb: number) { if (mb === 0) return '--' if (mb >= 1000) return `${(mb / 1000).toFixed(1)} GB` return `${mb} MB` } function formatUptime(seconds: number) { const h = Math.floor(seconds / 3600) const m = Math.floor((seconds % 3600) / 60) if (h > 0) return `${h}h ${m}m` return `${m}m` } // --------------------------------------------------------------------------- // Component // --------------------------------------------------------------------------- export default function ModelManagementPage() { const [tab, setTab] = useState('overview') const [models, setModels] = useState(MOCK_MODELS) const [benchmarks, setBenchmarks] = useState(MOCK_BENCHMARKS) const [status, setStatus] = useState(MOCK_STATUS) const [backend, setBackend] = useState('auto') const [saving, setSaving] = useState(false) const [benchmarkRunning, setBenchmarkRunning] = useState(false) const [usingMock, setUsingMock] = useState(false) // Load status const loadStatus = useCallback(async () => { try { const res = await fetch(`${KLAUSUR_API}/api/v1/models/status`) if (res.ok) { const data = await res.json() setStatus(data) setBackend(data.active_backend || 'auto') setUsingMock(false) } else { setUsingMock(true) } } catch { setUsingMock(true) } }, []) // Load models const loadModels = useCallback(async () => { try { const res = await fetch(`${KLAUSUR_API}/api/v1/models`) if (res.ok) { const data = await res.json() if (data.models?.length) setModels(data.models) } } catch { // Keep mock data } }, []) // Load benchmarks const loadBenchmarks = useCallback(async () => { try { const res = await fetch(`${KLAUSUR_API}/api/v1/models/benchmarks`) if (res.ok) { const data = await res.json() if (data.benchmarks?.length) setBenchmarks(data.benchmarks) } } catch { // Keep mock data } }, []) useEffect(() => { loadStatus() loadModels() loadBenchmarks() }, [loadStatus, loadModels, loadBenchmarks]) // Save backend preference const saveBackend = async (mode: BackendMode) => { setBackend(mode) setSaving(true) try { await fetch(`${KLAUSUR_API}/api/v1/models/backend`, { method: 'PUT', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ backend: mode }), }) await loadStatus() } catch { // Silently handle — mock mode } finally { setSaving(false) } } // Run benchmark const runBenchmark = async () => { setBenchmarkRunning(true) try { const res = await fetch(`${KLAUSUR_API}/api/v1/models/benchmark`, { method: 'POST', }) if (res.ok) { const data = await res.json() if (data.benchmarks?.length) setBenchmarks(data.benchmarks) } await loadBenchmarks() } catch { // Keep existing data } finally { setBenchmarkRunning(false) } } const tabs: { key: Tab; label: string }[] = [ { key: 'overview', label: 'Uebersicht' }, { key: 'benchmarks', label: 'Benchmarks' }, { key: 'configuration', label: 'Konfiguration' }, ] return (
{/* Header */}

Model Management

{models.length} Modelle konfiguriert {usingMock && ( Mock-Daten (Backend nicht erreichbar) )}

{/* Status Cards */}

Aktives Backend

{status.active_backend.toUpperCase()}

Geladene Modelle

{status.loaded_models.length}

Cache Hit-Rate

{status.cache_hits + status.cache_misses > 0 ? `${((status.cache_hits / (status.cache_hits + status.cache_misses)) * 100).toFixed(1)}%` : '--'}

Uptime

{formatUptime(status.uptime_s)}

{/* Tabs */}
{/* Overview Tab */} {tab === 'overview' && (

Verfuegbare Modelle

{models.map(m => (

{m.name}

{m.key}

{/* PyTorch */}
PyTorch
{m.pytorch.status === 'available' && ( {formatBytes(m.pytorch.size_mb)} / {formatBytes(m.pytorch.ram_mb)} RAM )}
{/* ONNX */}
ONNX
{m.onnx.status === 'available' && ( {formatBytes(m.onnx.size_mb)} / {formatBytes(m.onnx.ram_mb)} RAM {m.onnx.quantized && ( INT8 )} )}
))}
{/* Loaded Models List */} {status.loaded_models.length > 0 && (

Aktuell geladen

{status.loaded_models.map((m, i) => ( {m} ))}
)}
)} {/* Benchmarks Tab */} {tab === 'benchmarks' && (

PyTorch vs ONNX Vergleich

{benchmarks.map((b, i) => ( ))}
Modell Backend Quantisierung Groesse RAM Inferenz Ladezeit
{b.model} {b.backend} {b.quantization} {formatBytes(b.size_mb)} {formatBytes(b.ram_mb)} {b.inference_ms} ms {b.load_time_s.toFixed(1)}s
{benchmarks.length === 0 && (

Keine Benchmark-Daten

Klicken Sie "Benchmark starten" um einen Vergleich durchzufuehren.

)}
)} {/* Configuration Tab */} {tab === 'configuration' && (
{/* Backend Selector */}

Inference Backend

Waehlen Sie welches Backend fuer die Modell-Inferenz verwendet werden soll.

{([ { mode: 'auto' as const, label: 'Auto', desc: 'ONNX wenn verfuegbar, Fallback auf PyTorch.', }, { mode: 'pytorch' as const, label: 'PyTorch', desc: 'Immer PyTorch verwenden. Hoeherer RAM-Verbrauch, volle Flexibilitaet.', }, { mode: 'onnx' as const, label: 'ONNX', desc: 'Immer ONNX verwenden. Schneller und weniger RAM, Fehler wenn nicht vorhanden.', }, ] as const).map(opt => ( ))}
{saving && (

Speichere...

)}
{/* Model Details Table */}

Modell-Details

{models.map(m => { const ptAvail = m.pytorch.status === 'available' const oxAvail = m.onnx.status === 'available' const savings = ptAvail && oxAvail && m.pytorch.size_mb > 0 ? Math.round((1 - m.onnx.size_mb / m.pytorch.size_mb) * 100) : null return ( ) })}
Modell PyTorch Groesse (PT) ONNX Groesse (ONNX) Einsparung
{m.name} {ptAvail ? formatBytes(m.pytorch.size_mb) : '--'} {oxAvail ? formatBytes(m.onnx.size_mb) : '--'} {savings !== null ? ( -{savings}% ) : ( -- )}
)}
) }