1102 lines
59 KiB
HTML
1102 lines
59 KiB
HTML
<!DOCTYPE html>
|
||
<html lang="de">
|
||
<head>
|
||
<meta charset="UTF-8">
|
||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||
<title>Admin — OCR: Übersicht & Modell-Detail</title>
|
||
<link href="https://fonts.googleapis.com/css2?family=Tinos:wght@400;700&family=Montserrat:wght@400;500;600;700&display=swap" rel="stylesheet">
|
||
<style>
|
||
*,*::before,*::after{box-sizing:border-box;margin:0;padding:0}
|
||
body{font-family:'Montserrat',system-ui,sans-serif;background:#E8E6DF;color:#1A1A24;line-height:1.5;font-size:13px}
|
||
.page{max-width:1400px;margin:0 auto;padding:48px 32px 120px}
|
||
|
||
/* ── Masthead ── */
|
||
.mh{padding-bottom:24px;border-bottom:3px solid #012851;margin-bottom:56px}
|
||
.mh h1{font-size:24px;font-weight:900;color:#012851;letter-spacing:-.4px}
|
||
.mh p{font-size:12.5px;color:#555;max-width:720px;line-height:1.75;margin-top:8px}
|
||
.mh .byline{font-size:9px;color:#999;font-weight:700;letter-spacing:1.5px;text-transform:uppercase;margin-top:10px}
|
||
.tag-row{display:flex;gap:6px;margin-top:10px;flex-wrap:wrap}
|
||
.tag{background:#012851;color:#a1dcd8;padding:2px 8px;border-radius:2px;font-size:8px;font-weight:700;letter-spacing:.8px;text-transform:uppercase}
|
||
.tag.amber{background:#92400e;color:#fef3c7}
|
||
.tag.green{background:#3a6e42;color:#d1fae5}
|
||
.tag.gray{background:#4b5563;color:#e5e7eb}
|
||
|
||
/* ── Section headers ── */
|
||
.sh{margin:64px 0 28px;padding-bottom:14px;border-bottom:2px solid #D8D5CE}
|
||
.sh h2{font-size:16px;font-weight:900;color:#012851}
|
||
.sh p{font-size:12px;color:#666;margin-top:4px;max-width:760px;line-height:1.65}
|
||
|
||
/* ── Grid layouts ── */
|
||
.grid{display:flex;gap:24px;flex-wrap:wrap;margin-bottom:32px;align-items:flex-start}
|
||
.col{display:flex;flex-direction:column;gap:8px}
|
||
.lbl{font-size:8px;font-weight:800;text-transform:uppercase;letter-spacing:1.2px;color:#888;display:flex;align-items:center;gap:5px}
|
||
.lbl .badge{background:#E0DDD6;color:#666;padding:1px 5px;border-radius:2px;font-size:7px;font-weight:700}
|
||
.lbl .badge.new{background:#012851;color:#a1dcd8}
|
||
.cap{font-size:10px;color:#888;font-style:italic;line-height:1.6;max-width:480px;margin-top:4px}
|
||
|
||
/* ── Browser chrome ── */
|
||
.chrome{background:#F5F4EE;border:1.5px solid #C4C0BA;border-radius:8px;overflow:hidden;box-shadow:0 4px 16px rgba(0,0,0,.1)}
|
||
.bar{height:18px;background:#E8E6E0;border-bottom:1px solid #C4C0BA;display:flex;align-items:center;padding:0 7px;gap:3px;flex-shrink:0}
|
||
.dot{width:5px;height:5px;border-radius:50%;background:#BDB8B1}
|
||
.url{flex:1;height:7px;background:#CCC8C2;border-radius:5px;margin-left:4px}
|
||
|
||
/* ── App header (50px real → 28px spec) ── */
|
||
.app-header{height:28px;background:#012851;display:flex;align-items:center;padding:0 10px;gap:8px;flex-shrink:0}
|
||
.app-logo{font-size:6.5px;font-weight:900;color:#fff;letter-spacing:.8px;border-bottom:2px solid #a1dcd8;padding-bottom:1px;font-family:'Montserrat',sans-serif;white-space:nowrap}
|
||
.app-link{font-size:5px;color:rgba(255,255,255,.4);font-weight:700;text-transform:uppercase;letter-spacing:.4px;white-space:nowrap}
|
||
.app-link.act{color:rgba(255,255,255,.85)}
|
||
.app-nav-r{margin-left:auto;display:flex;gap:4px;align-items:center}
|
||
.app-av{width:14px;height:14px;background:rgba(255,255,255,.12);border-radius:50%;display:flex;align-items:center;justify-content:center;font-size:5px;font-weight:800;color:rgba(255,255,255,.5)}
|
||
|
||
/* ── Admin shell ── */
|
||
.admin-shell{display:flex;overflow:hidden}
|
||
|
||
/* ── EntityNav ── */
|
||
.entity-nav{width:66px;flex-shrink:0;background:#012851;display:flex;flex-direction:column}
|
||
.en-heading{font-size:4.5px;font-weight:800;text-transform:uppercase;letter-spacing:1px;color:rgba(255,255,255,.35);padding:5px 6px 2px}
|
||
.en-item{display:flex;flex-direction:column;align-items:flex-start;gap:1px;padding:5px 6px 5px 5px;border-left:2.5px solid transparent;cursor:pointer}
|
||
.en-item:hover:not(.en-active){background:rgba(255,255,255,.05)}
|
||
.en-item.en-active{border-left-color:#a1dcd8;background:rgba(255,255,255,.08)}
|
||
.en-icon{width:9px;height:9px;border-radius:1px;background:rgba(255,255,255,.25);flex-shrink:0}
|
||
.en-icon.mint{background:#a1dcd8}
|
||
.en-count{font-size:5.5px;font-weight:900;color:rgba(255,255,255,.4);margin-top:1px}
|
||
.en-count.mint{color:rgba(255,255,255,.6)}
|
||
.en-label{font-size:5px;font-weight:800;text-transform:uppercase;letter-spacing:.5px;color:rgba(255,255,255,.45)}
|
||
.en-label.mint{color:rgba(255,255,255,.95)}
|
||
.en-spacer{flex:1}
|
||
.en-sep{border-top:1px solid rgba(255,255,255,.1)}
|
||
.v-line{width:1px;background:#e4e2d7;flex-shrink:0}
|
||
|
||
/* ── OCR content area ── */
|
||
.ocr-content{flex:1;display:flex;flex-direction:column;overflow:hidden;min-width:0;background:#F5F4EE}
|
||
.ocr-page-header{display:flex;align-items:center;gap:5px;padding:5px 10px;border-bottom:1px solid #e4e2d7;background:white;flex-shrink:0}
|
||
.oph-back{font-size:5px;color:#9ca3af;cursor:pointer;flex-shrink:0}
|
||
.oph-title{font-size:7.5px;font-weight:800;color:#012851;font-family:'Montserrat',sans-serif}
|
||
.oph-sub{font-size:5px;color:#6b7280}
|
||
.ocr-scroll{flex:1;overflow-y:auto;padding:7px 9px;display:flex;flex-direction:column;gap:5px}
|
||
|
||
/* ── Status pills ── */
|
||
.pill{display:inline-flex;align-items:center;gap:2px;padding:1px 5px;border-radius:2px;font-size:5px;font-weight:700;text-transform:uppercase;letter-spacing:.4px;flex-shrink:0}
|
||
.pill.running{background:#fff8e1;color:#f57c00;border:1px solid #ffe082}
|
||
.pill.done{background:#e8f5e9;color:#2e7d32;border:1px solid #c8e6c9}
|
||
.pill.failed{background:#ffcdd2;color:#c62828;border:1px solid #ef9a9a}
|
||
.pill.queued{background:#fef3c7;color:#92400e;border:1px solid #fde68a}
|
||
.pill.online{background:#e8f5e9;color:#2e7d32;border:1px solid #c8e6c9}
|
||
|
||
/* ── Health bar ── */
|
||
.health-bar{display:flex;align-items:center;gap:5px;padding:5px 9px;background:white;border:1px solid #e4e2d7;border-radius:2px}
|
||
.hb-spacer{flex:1}
|
||
.hb-sep{width:1px;height:10px;background:#e4e2d7;flex-shrink:0}
|
||
|
||
/* ── Stat cards ── */
|
||
.stat-row{display:grid;grid-template-columns:repeat(3,1fr);gap:5px}
|
||
.stat-card{background:white;border:1px solid #e4e2d7;border-radius:2px;padding:8px 10px}
|
||
.sc-num{font-family:'Tinos',serif;font-size:20px;font-weight:700;color:#012851;line-height:1}
|
||
.sc-lbl{font-size:5px;font-weight:700;text-transform:uppercase;letter-spacing:.8px;color:#888;margin-top:3px}
|
||
.sc-sub{font-size:4.5px;color:#aaa;margin-top:1px}
|
||
|
||
/* ── Models table ── */
|
||
.models-table{background:white;border:1px solid #e4e2d7;border-radius:2px;overflow:hidden}
|
||
.mt-header-row{display:flex;align-items:center;padding:4px 9px;border-bottom:1px solid #e4e2d7}
|
||
.mt-section-lbl{font-size:5px;font-weight:800;text-transform:uppercase;letter-spacing:.9px;color:#6b7280}
|
||
.mt-thead{background:#012851;padding:3px 9px;display:grid;grid-template-columns:2fr 52px 52px 55px 60px 68px;gap:4px}
|
||
.mt-th{font-size:4.5px;font-weight:700;text-transform:uppercase;letter-spacing:.5px;color:#a1dcd8}
|
||
.mt-row{padding:5px 9px;display:grid;grid-template-columns:2fr 52px 52px 55px 60px 68px;gap:4px;border-bottom:1px solid #f0efe9;cursor:pointer;align-items:center}
|
||
.mt-row:last-child{border-bottom:0}
|
||
.mt-row.global-row{background:#F8F8FC}
|
||
.mt-name{font-size:6px;font-weight:700;color:#012851}
|
||
.mt-name.sender{font-weight:500;color:#374151}
|
||
.mt-person{font-size:4.5px;color:#9ca3af;margin-top:1px}
|
||
.mt-val{font-size:6px;color:#374151}
|
||
.mt-val.good{color:#2e7d32;font-weight:600}
|
||
.mt-val.bad{color:#c62828;font-weight:600}
|
||
.mt-val.muted{color:#9ca3af}
|
||
|
||
/* ── Actions section ── */
|
||
.actions-section{background:white;border:1px solid #e4e2d7;border-radius:2px;padding:7px 9px}
|
||
.as-lbl{font-size:5px;font-weight:800;text-transform:uppercase;letter-spacing:.9px;color:#6b7280;margin-bottom:5px}
|
||
.as-row{display:flex;gap:4px;flex-wrap:wrap}
|
||
.btn-primary{display:inline-flex;align-items:center;height:16px;padding:0 8px;background:#012851;color:white;border-radius:2px;font-size:5px;font-weight:700;text-transform:uppercase;letter-spacing:.3px;cursor:pointer;white-space:nowrap}
|
||
.btn-outline{display:inline-flex;align-items:center;height:16px;padding:0 8px;border:1.5px solid #012851;color:#012851;border-radius:2px;font-size:5px;font-weight:700;text-transform:uppercase;letter-spacing:.3px;cursor:pointer;white-space:nowrap}
|
||
.btn-link{font-size:5px;color:#012851;text-decoration:underline;cursor:pointer;align-self:center}
|
||
|
||
/* ── Section action bar ── */
|
||
.section-bar{display:flex;align-items:center;gap:4px}
|
||
.section-bar-lbl{font-size:5px;font-weight:800;text-transform:uppercase;letter-spacing:.9px;color:#6b7280;flex:1}
|
||
|
||
/* ── Metric cards ── */
|
||
.metric-row{display:grid;grid-template-columns:repeat(4,1fr);gap:5px}
|
||
.metric-card{background:white;border:1px solid #e4e2d7;border-radius:2px;padding:8px 10px;text-align:center}
|
||
.mc-num{font-family:'Tinos',serif;font-size:18px;font-weight:700;line-height:1}
|
||
.mc-num.good{color:#2e7d32}
|
||
.mc-num.neutral{color:#012851}
|
||
.mc-lbl{font-size:5px;font-weight:700;text-transform:uppercase;letter-spacing:.8px;color:#888;margin-top:3px}
|
||
.mc-delta{font-size:4.5px;margin-top:2px}
|
||
.mc-delta.up{color:#2e7d32}
|
||
.mc-delta.down{color:#c62828}
|
||
.mc-delta.dim{color:#9ca3af}
|
||
|
||
/* ── History table ── */
|
||
.hist-table{background:white;border:1px solid #e4e2d7;border-radius:2px;overflow:hidden}
|
||
.ht-thead{background:#012851;padding:3px 9px;display:grid;grid-template-columns:68px 1fr 55px 55px 55px 55px;gap:4px}
|
||
.ht-th{font-size:4.5px;font-weight:700;text-transform:uppercase;letter-spacing:.5px;color:#a1dcd8}
|
||
.ht-row{padding:5px 9px;display:grid;grid-template-columns:68px 1fr 55px 55px 55px 55px;gap:4px;border-bottom:1px solid #f0efe9;align-items:center}
|
||
.ht-row:last-child{border-bottom:0}
|
||
.ht-row.running{background:#fff8e1}
|
||
.ht-row.failed-row{background:#fff8f8}
|
||
.ht-val{font-size:6px;color:#374151}
|
||
.ht-val.good{color:#2e7d32;font-weight:600}
|
||
.ht-val.muted{color:#9ca3af}
|
||
.ht-err{font-size:4.5px;color:#c62828}
|
||
|
||
/* ── Phone frame ── */
|
||
.phone{width:212px;background:#F5F4EE;border-radius:22px;overflow:hidden;box-shadow:0 8px 24px rgba(0,0,0,.18);border:4px solid #1C1C24;display:flex;flex-direction:column}
|
||
.phone-status{height:13px;background:#F5F4EE;display:flex;align-items:center;justify-content:space-between;padding:0 12px;font-size:5.5px;font-weight:600;color:#6b7280;flex-shrink:0}
|
||
.phone-body{display:flex;flex-direction:column;overflow:hidden;flex:1}
|
||
.phone-scroll{flex:1;overflow-y:auto;background:#F5F4EE;padding:5px;display:flex;flex-direction:column;gap:4px}
|
||
|
||
/* ── Annotations ── */
|
||
.annotation{display:flex;gap:8px;margin-top:8px;align-items:flex-start}
|
||
.ann-bullet{width:16px;height:16px;border-radius:50%;background:#012851;color:#a1dcd8;font-size:7px;font-weight:800;display:flex;align-items:center;justify-content:center;flex-shrink:0;margin-top:1px}
|
||
.ann-text{font-size:11px;color:#555;line-height:1.65;flex:1}
|
||
.ann-text strong{color:#1A1A24;font-weight:700}
|
||
.ann-text code{font-family:monospace;background:#E0DDD6;padding:1px 4px;border-radius:2px;font-size:10px;color:#012851}
|
||
|
||
/* ── Impl-ref table ── */
|
||
.impl-ref table{width:100%;border-collapse:collapse;font-size:11px;margin-top:12px}
|
||
.impl-ref th{background:#012851;color:#a1dcd8;font-weight:700;text-transform:uppercase;letter-spacing:.5px;font-size:9px;padding:7px 10px;text-align:left}
|
||
.impl-ref td{padding:7px 10px;border-bottom:1px solid #E0DDD6;vertical-align:top;line-height:1.6}
|
||
.impl-ref tr:hover td{background:#F0EFE9}
|
||
.impl-ref code{font-family:monospace;background:#F0EFE9;padding:1px 4px;border-radius:2px;font-size:10px;color:#012851}
|
||
.impl-ref .new{color:#3a6e42;font-weight:600}
|
||
.impl-ref .warn{color:#92400e;font-weight:600}
|
||
.impl-ref .sub-header td{background:#F0EFE9;font-weight:700;font-size:10px;color:#012851;padding:6px 10px}
|
||
</style>
|
||
</head>
|
||
<body>
|
||
<div class="page">
|
||
|
||
<!-- ══════════════════════════════════════════ MASTHEAD -->
|
||
<div class="mh">
|
||
<h1>Admin — OCR: Übersicht & Modell-Detail</h1>
|
||
<p>Neue Admin-Sektion für das OCR-Pipeline-Monitoring. <strong>Zwei Seiten:</strong> Die Übersicht zeigt Systemstatus, globale Block-Statistiken und alle Modelle (global + sendergebunden) in einer Tabelle auf einen Blick. Die Modell-Detailseite zeigt die aktuellen Trainingsmetriken und die vollständige Trainingshistorie für ein einzelnes Modell, mit Aktions-Buttons direkt über der Tabelle. Tägliche Nutzung — Statistiken und aktive Läufe sind daher immer sofort sichtbar.</p>
|
||
<div class="tag-row">
|
||
<span class="tag">Route: /admin/ocr</span>
|
||
<span class="tag">Route: /admin/ocr/global</span>
|
||
<span class="tag">Route: /admin/ocr/[personId]</span>
|
||
<span class="tag amber">Neue EntityNav-Sektion: System → OCR</span>
|
||
<span class="tag green">Breakpoints: 320 / 640 / 1024 / 1440</span>
|
||
<span class="tag gray">API: GET /api/ocr/training-info · POST /api/ocr/train · POST /api/ocr/segtrain</span>
|
||
</div>
|
||
<p class="byline">Leonie Voss · UI/UX Design Lead · Familienarchiv · 2026-04-17</p>
|
||
</div>
|
||
|
||
<!-- ══════════════════════════════════════════ S1: OVERVIEW DESKTOP -->
|
||
<div class="sh">
|
||
<h2>1 — Übersicht: Desktop (≥1024px)</h2>
|
||
<p>Die Übersichtsseite öffnet sich beim Klick auf „OCR" in der EntityNav. Kein Split-Panel — OCR ist kein Entity-Editor. Der volle Bereich rechts der EntityNav gehört dem Dashboard. Vertikaler Fluss: Health-Bar → Statistiken → Modelltabelle → Globale Aktionen. Beim Klick auf eine Tabellenzeile wird die Modell-Detailseite geöffnet.</p>
|
||
</div>
|
||
|
||
<div class="grid">
|
||
<div class="col">
|
||
<div class="lbl">Desktop lg (1280px) — kein aktiver Lauf</div>
|
||
<div class="chrome" style="width:940px">
|
||
<div class="bar"><span class="dot"></span><span class="dot"></span><span class="dot"></span><span class="url"></span></div>
|
||
<div class="app-header">
|
||
<span class="app-logo">FAMILIENARCHIV</span>
|
||
<span class="app-link">Dokumente</span>
|
||
<span class="app-link">Personen</span>
|
||
<span class="app-link act">Admin</span>
|
||
<div class="app-nav-r"><div class="app-av">M</div></div>
|
||
</div>
|
||
<div class="admin-shell" style="height:530px">
|
||
<!-- EntityNav -->
|
||
<div class="entity-nav">
|
||
<div class="en-heading">Admin</div>
|
||
<div class="en-item"><div class="en-icon"></div><div class="en-count">5</div><div class="en-label">Benutzer</div></div>
|
||
<div class="en-item"><div class="en-icon"></div><div class="en-count">3</div><div class="en-label">Gruppen</div></div>
|
||
<div class="en-item"><div class="en-icon"></div><div class="en-count">142</div><div class="en-label">Schlagwörter</div></div>
|
||
<div class="en-spacer"></div>
|
||
<div class="en-sep"></div>
|
||
<div class="en-heading" style="padding-top:5px">System</div>
|
||
<div class="en-item en-active"><div class="en-icon mint"></div><div class="en-count mint">7</div><div class="en-label mint">OCR</div></div>
|
||
</div>
|
||
<div class="v-line"></div>
|
||
<!-- OCR Content: idle state -->
|
||
<div class="ocr-content">
|
||
<div class="ocr-page-header">
|
||
<span class="oph-title">OCR-Administration</span>
|
||
</div>
|
||
<div class="ocr-scroll">
|
||
<!-- Health bar: idle -->
|
||
<div class="health-bar">
|
||
<span class="pill online">● Service online</span>
|
||
<div class="hb-sep"></div>
|
||
<span style="font-size:4.5px;color:#6b7280;">Kein aktiver Trainingsauftrag</span>
|
||
<div class="hb-spacer"></div>
|
||
<span style="font-size:4.5px;color:#9ca3af;">Letzter Lauf: sender_anna_raddatz · vor 2 Std.</span>
|
||
</div>
|
||
<!-- Stat row -->
|
||
<div class="stat-row">
|
||
<div class="stat-card">
|
||
<div class="sc-num">1.842</div>
|
||
<div class="sc-lbl">Eligible Blocks</div>
|
||
<div class="sc-sub">Kurrent-Erkennung</div>
|
||
</div>
|
||
<div class="stat-card">
|
||
<div class="sc-num">23</div>
|
||
<div class="sc-lbl">Dokumente</div>
|
||
<div class="sc-sub">mit annotierten Blöcken</div>
|
||
</div>
|
||
<div class="stat-card">
|
||
<div class="sc-num">310</div>
|
||
<div class="sc-lbl">Seg. Blocks</div>
|
||
<div class="sc-sub">Segmentierungstraining</div>
|
||
</div>
|
||
</div>
|
||
<!-- Models table -->
|
||
<div class="models-table">
|
||
<div class="mt-header-row">
|
||
<span class="mt-section-lbl">Alle Modelle</span>
|
||
</div>
|
||
<div class="mt-thead">
|
||
<span class="mt-th">Modell / Absender</span>
|
||
<span class="mt-th">Genauigkeit</span>
|
||
<span class="mt-th">ZFR ①</span>
|
||
<span class="mt-th">Blöcke</span>
|
||
<span class="mt-th">Letzter Lauf</span>
|
||
<span class="mt-th">Status</span>
|
||
</div>
|
||
<!-- global row -->
|
||
<div class="mt-row global-row">
|
||
<div><div class="mt-name">global_kurrent</div><div class="mt-person">Globales Erkennungsmodell</div></div>
|
||
<span class="mt-val">95,1 %</span>
|
||
<span class="mt-val">2,3 %</span>
|
||
<span class="mt-val">1.842</span>
|
||
<span class="mt-val" style="color:#6b7280;">vor 1 Tag</span>
|
||
<span><span class="pill done">✓ Fertig</span></span>
|
||
</div>
|
||
<!-- sender rows -->
|
||
<div class="mt-row">
|
||
<div><div class="mt-name sender">Anna Raddatz</div><div class="mt-person">sender_anna_raddatz</div></div>
|
||
<span class="mt-val good">98,3 %</span>
|
||
<span class="mt-val">1,7 %</span>
|
||
<span class="mt-val">128</span>
|
||
<span class="mt-val" style="color:#6b7280;">vor 2 Std.</span>
|
||
<span><span class="pill done">✓ Fertig</span></span>
|
||
</div>
|
||
<div class="mt-row">
|
||
<div><div class="mt-name sender">Franz Kaufmann</div><div class="mt-person">sender_franz_kaufmann</div></div>
|
||
<span class="mt-val bad">—</span>
|
||
<span class="mt-val muted">—</span>
|
||
<span class="mt-val">45</span>
|
||
<span class="mt-val" style="color:#6b7280;">vor 3 Tagen</span>
|
||
<span><span class="pill failed">✕ Fehler</span></span>
|
||
</div>
|
||
<div class="mt-row">
|
||
<div><div class="mt-name sender">Marie Schulz</div><div class="mt-person">sender_marie_schulz</div></div>
|
||
<span class="mt-val">96,7 %</span>
|
||
<span class="mt-val">1,9 %</span>
|
||
<span class="mt-val">203</span>
|
||
<span class="mt-val" style="color:#6b7280;">vor 3 Tagen</span>
|
||
<span><span class="pill done">✓ Fertig</span></span>
|
||
</div>
|
||
<div class="mt-row">
|
||
<div><div class="mt-name sender">Johann Raddatz</div><div class="mt-person">sender_johann_raddatz</div></div>
|
||
<span class="mt-val">94,2 %</span>
|
||
<span class="mt-val">2,9 %</span>
|
||
<span class="mt-val">87</span>
|
||
<span class="mt-val" style="color:#6b7280;">vor 1 Woche</span>
|
||
<span><span class="pill done">✓ Fertig</span></span>
|
||
</div>
|
||
<div class="mt-row" style="opacity:.5">
|
||
<div><div class="mt-name sender" style="color:#9ca3af;">+ 3 weitere Modelle</div></div>
|
||
<span class="mt-val muted">—</span><span class="mt-val muted">—</span><span class="mt-val muted">—</span><span class="mt-val muted">—</span><span></span>
|
||
</div>
|
||
</div>
|
||
<!-- Global actions -->
|
||
<div class="actions-section">
|
||
<div class="as-lbl">Globale Aktionen</div>
|
||
<div class="as-row">
|
||
<span class="btn-primary">Training Erkennung</span>
|
||
<span class="btn-primary">Training Segmentierung</span>
|
||
<span class="btn-outline">Erkennungsdaten exportieren</span>
|
||
<span class="btn-outline">Segmentierungsdaten exportieren</span>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- Second state: active run -->
|
||
<div class="lbl" style="margin-top:18px">Desktop lg — mit aktivem Lauf (Health-Bar + Tabellenzeile)</div>
|
||
<div class="chrome" style="width:940px">
|
||
<div class="bar"><span class="dot"></span><span class="dot"></span><span class="dot"></span><span class="url"></span></div>
|
||
<div class="app-header">
|
||
<span class="app-logo">FAMILIENARCHIV</span>
|
||
<span class="app-link">Dokumente</span>
|
||
<span class="app-link">Personen</span>
|
||
<span class="app-link act">Admin</span>
|
||
<div class="app-nav-r"><div class="app-av">M</div></div>
|
||
</div>
|
||
<div class="admin-shell" style="height:220px">
|
||
<div class="entity-nav">
|
||
<div class="en-heading">Admin</div>
|
||
<div class="en-item"><div class="en-icon"></div><div class="en-count">5</div><div class="en-label">Benutzer</div></div>
|
||
<div class="en-item"><div class="en-icon"></div><div class="en-count">3</div><div class="en-label">Gruppen</div></div>
|
||
<div class="en-item"><div class="en-icon"></div><div class="en-count">142</div><div class="en-label">Schlagwörter</div></div>
|
||
<div class="en-spacer"></div><div class="en-sep"></div>
|
||
<div class="en-heading" style="padding-top:5px">System</div>
|
||
<div class="en-item en-active"><div class="en-icon mint"></div><div class="en-count mint">7</div><div class="en-label mint">OCR</div></div>
|
||
</div>
|
||
<div class="v-line"></div>
|
||
<div class="ocr-content">
|
||
<div class="ocr-page-header"><span class="oph-title">OCR-Administration</span></div>
|
||
<div class="ocr-scroll">
|
||
<!-- Health bar: running + queued -->
|
||
<div class="health-bar">
|
||
<span class="pill online">● Service online</span>
|
||
<div class="hb-sep"></div>
|
||
<span class="pill running">⟳ sender_anna_raddatz · läuft seit 4 Min.</span>
|
||
<div class="hb-sep"></div>
|
||
<span style="font-size:4.5px;color:#92400e;background:#fef3c7;border:1px solid #fde68a;padding:1px 5px;border-radius:2px;font-weight:700;">⏳ 1 in Warteschlange</span>
|
||
<div class="hb-spacer"></div>
|
||
</div>
|
||
<!-- Table snippet showing running + queued rows -->
|
||
<div class="models-table">
|
||
<div class="mt-header-row"><span class="mt-section-lbl">Alle Modelle</span></div>
|
||
<div class="mt-thead">
|
||
<span class="mt-th">Modell / Absender</span><span class="mt-th">Genauigkeit</span><span class="mt-th">ZFR</span><span class="mt-th">Blöcke</span><span class="mt-th">Letzter Lauf</span><span class="mt-th">Status</span>
|
||
</div>
|
||
<div class="mt-row global-row">
|
||
<div><div class="mt-name">global_kurrent</div><div class="mt-person">Globales Erkennungsmodell</div></div>
|
||
<span class="mt-val">95,1 %</span><span class="mt-val">2,3 %</span><span class="mt-val">1.842</span>
|
||
<span class="mt-val" style="color:#6b7280;">vor 1 Tag</span><span><span class="pill done">✓ Fertig</span></span>
|
||
</div>
|
||
<div class="mt-row" style="background:#fff8e1;">
|
||
<div><div class="mt-name sender">Anna Raddatz</div><div class="mt-person">sender_anna_raddatz</div></div>
|
||
<span class="mt-val muted">98,3 %</span><span class="mt-val muted">1,7 %</span><span class="mt-val">128</span>
|
||
<span class="mt-val" style="color:#f57c00;font-weight:600;">gerade jetzt</span><span><span class="pill running">⟳ Läuft</span></span>
|
||
</div>
|
||
<div class="mt-row" style="opacity:.65;">
|
||
<div><div class="mt-name sender">Marie Schulz</div><div class="mt-person">sender_marie_schulz</div></div>
|
||
<span class="mt-val muted">—</span><span class="mt-val muted">—</span><span class="mt-val muted">203</span>
|
||
<span class="mt-val muted">—</span><span><span class="pill queued">⏳ Warteschlange</span></span>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div><!-- /col -->
|
||
</div><!-- /grid -->
|
||
|
||
<div class="annotation">
|
||
<div class="ann-bullet">1</div>
|
||
<div class="ann-text"><strong>Globales Modell immer an Position 0.</strong> Die <code>global_kurrent</code>-Zeile hat einen leicht bläulichen Hintergrund (<code>bg-[#F8F8FC]</code>) und ist unabhängig von Sortierung immer die erste Zeile. Sie wird anhand <code>personId === null</code> identifiziert.</div>
|
||
</div>
|
||
<div class="annotation">
|
||
<div class="ann-bullet">2</div>
|
||
<div class="ann-text"><strong>Health-Bar als Echtzeit-Primärindikator.</strong> Der Status (RUNNING, QUEUED, idle) wird via SSE aktualisiert — derselbe <code>/api/ocr/jobs/{jobId}/progress</code>-Kanal wie in der Dokumentenansicht. RUNNING-Tabellenzeilen erhalten zusätzlich <code>bg-amber-50</code>. Die Buttons „Training Erkennung" und „Training Segmentierung" werden <code>disabled + opacity-50</code> wenn ein Lauf aktiv ist.</div>
|
||
</div>
|
||
<div class="annotation">
|
||
<div class="ann-bullet">3</div>
|
||
<div class="ann-text"><strong>① ZFR = Zeichenfehlerrate (CER).</strong> Abkürzung im <code><th></code> mit <code>title="Zeichenfehlerrate (Character Error Rate)"</code> und <code>aria-label</code> für Screenreader. Kleinere Werte sind besser — ein Pfeil in der Implementierung ist optional.</div>
|
||
</div>
|
||
|
||
|
||
<!-- ══════════════════════════════════════════ S2: OVERVIEW MOBILE -->
|
||
<div class="sh">
|
||
<h2>2 — Übersicht: Mobile (320px)</h2>
|
||
<p>Auf kleinen Bildschirmen stapeln sich die drei Stat-Karten einspaltig. Die Modelltabelle zeigt nur Name und Status-Pill (keine Metriken-Spalten — zu schmal). Aktions-Buttons werden vollbreit und vertikal gestapelt.</p>
|
||
</div>
|
||
|
||
<div class="grid">
|
||
<div class="col">
|
||
<div class="lbl">320px</div>
|
||
<div class="phone">
|
||
<div class="phone-status"><span>09:41</span><span>●●●●</span></div>
|
||
<div class="phone-body">
|
||
<!-- App header mobile -->
|
||
<div class="app-header" style="height:22px;padding:0 8px;gap:6px;">
|
||
<span class="app-logo" style="font-size:5.5px;">FAMILIENARCHIV</span>
|
||
<div style="margin-left:auto;display:flex;flex-direction:column;justify-content:center;gap:2px;width:12px;">
|
||
<div style="height:1.5px;background:rgba(255,255,255,.5);border-radius:1px;"></div>
|
||
<div style="height:1.5px;background:rgba(255,255,255,.5);border-radius:1px;"></div>
|
||
<div style="height:1.5px;background:rgba(255,255,255,.5);border-radius:1px;"></div>
|
||
</div>
|
||
</div>
|
||
<!-- Page header -->
|
||
<div style="background:white;border-bottom:1px solid #e4e2d7;padding:4px 7px;">
|
||
<span style="font-size:6.5px;font-weight:800;color:#012851;">OCR-Administration</span>
|
||
</div>
|
||
<!-- Health bar compact -->
|
||
<div style="display:flex;align-items:center;gap:4px;padding:3px 7px;background:white;border-bottom:1px solid #e4e2d7;">
|
||
<span class="pill online" style="font-size:4px;padding:1px 4px;">● Online</span>
|
||
<span style="font-size:4px;color:#6b7280;">Kein aktiver Lauf</span>
|
||
</div>
|
||
<div class="phone-scroll">
|
||
<!-- Stat cards stacked -->
|
||
<div class="stat-card" style="padding:6px 8px;">
|
||
<div class="sc-num" style="font-size:16px;">1.842</div>
|
||
<div class="sc-lbl">Eligible Blocks</div>
|
||
</div>
|
||
<div class="stat-card" style="padding:6px 8px;">
|
||
<div class="sc-num" style="font-size:16px;">23</div>
|
||
<div class="sc-lbl">Dokumente</div>
|
||
</div>
|
||
<div class="stat-card" style="padding:6px 8px;">
|
||
<div class="sc-num" style="font-size:16px;">310</div>
|
||
<div class="sc-lbl">Seg. Blocks</div>
|
||
</div>
|
||
<!-- Models list simplified -->
|
||
<div class="models-table">
|
||
<div class="mt-header-row"><span class="mt-section-lbl">Alle Modelle</span></div>
|
||
<div style="display:grid;grid-template-columns:1fr auto;gap:4px;padding:4px 7px;border-bottom:1px solid #f0efe9;background:#F8F8FC;align-items:center;">
|
||
<div><div style="font-size:6px;font-weight:700;color:#012851;">global_kurrent</div><div style="font-size:4px;color:#9ca3af;">95,1 % · 1.842 Bl.</div></div>
|
||
<span class="pill done" style="font-size:4px;padding:1px 4px;">✓</span>
|
||
</div>
|
||
<div style="display:grid;grid-template-columns:1fr auto;gap:4px;padding:4px 7px;border-bottom:1px solid #f0efe9;align-items:center;">
|
||
<div><div style="font-size:6px;color:#374151;">Anna Raddatz</div><div style="font-size:4px;color:#9ca3af;">98,3 % · 128 Bl.</div></div>
|
||
<span class="pill done" style="font-size:4px;padding:1px 4px;">✓</span>
|
||
</div>
|
||
<div style="display:grid;grid-template-columns:1fr auto;gap:4px;padding:4px 7px;border-bottom:1px solid #f0efe9;align-items:center;">
|
||
<div><div style="font-size:6px;color:#374151;">Franz Kaufmann</div><div style="font-size:4px;color:#c62828;">Fehler · 45 Bl.</div></div>
|
||
<span class="pill failed" style="font-size:4px;padding:1px 4px;">✕</span>
|
||
</div>
|
||
<div style="display:grid;grid-template-columns:1fr auto;gap:4px;padding:4px 7px;border-bottom:1px solid #f0efe9;align-items:center;">
|
||
<div><div style="font-size:6px;color:#374151;">Marie Schulz</div><div style="font-size:4px;color:#9ca3af;">96,7 % · 203 Bl.</div></div>
|
||
<span class="pill done" style="font-size:4px;padding:1px 4px;">✓</span>
|
||
</div>
|
||
<div style="padding:4px 7px;font-size:4.5px;color:#9ca3af;text-align:center;">+ 4 weitere…</div>
|
||
</div>
|
||
<!-- Action buttons full-width -->
|
||
<span class="btn-primary" style="justify-content:center;height:22px;font-size:5.5px;">Training Erkennung</span>
|
||
<span class="btn-primary" style="justify-content:center;height:22px;font-size:5.5px;">Training Segmentierung</span>
|
||
<span class="btn-outline" style="justify-content:center;height:22px;font-size:5.5px;">Erkennungsdaten exportieren</span>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
|
||
<!-- ══════════════════════════════════════════ S3: DETAIL DESKTOP -->
|
||
<div class="sh">
|
||
<h2>3 — Modell-Detail: Desktop (≥1024px)</h2>
|
||
<p>Die Detailseite öffnet sich beim Klick auf eine Zeile der Übersichtstabelle. Sie folgt demselben vertikalen Muster: Metric-Cards oben, darunter die vollständige Trainingshistorie nur für dieses Modell. Für Sender-Modelle gibt es einen Link zur Personen-Detailseite. Zwei Varianten: Sender-Modell (mit Personen-Link) und globales Modell (ohne).</p>
|
||
</div>
|
||
|
||
<div class="grid">
|
||
<div class="col">
|
||
<div class="lbl">Desktop lg — sender_anna_raddatz (Lauf aktiv)</div>
|
||
<div class="chrome" style="width:940px">
|
||
<div class="bar"><span class="dot"></span><span class="dot"></span><span class="dot"></span><span class="url"></span></div>
|
||
<div class="app-header">
|
||
<span class="app-logo">FAMILIENARCHIV</span>
|
||
<span class="app-link">Dokumente</span>
|
||
<span class="app-link">Personen</span>
|
||
<span class="app-link act">Admin</span>
|
||
<div class="app-nav-r"><div class="app-av">M</div></div>
|
||
</div>
|
||
<div class="admin-shell" style="height:490px">
|
||
<div class="entity-nav">
|
||
<div class="en-heading">Admin</div>
|
||
<div class="en-item"><div class="en-icon"></div><div class="en-count">5</div><div class="en-label">Benutzer</div></div>
|
||
<div class="en-item"><div class="en-icon"></div><div class="en-count">3</div><div class="en-label">Gruppen</div></div>
|
||
<div class="en-item"><div class="en-icon"></div><div class="en-count">142</div><div class="en-label">Schlagwörter</div></div>
|
||
<div class="en-spacer"></div><div class="en-sep"></div>
|
||
<div class="en-heading" style="padding-top:5px">System</div>
|
||
<div class="en-item en-active"><div class="en-icon mint"></div><div class="en-count mint">7</div><div class="en-label mint">OCR</div></div>
|
||
</div>
|
||
<div class="v-line"></div>
|
||
<div class="ocr-content">
|
||
<!-- Page header: sender model with active run -->
|
||
<div class="ocr-page-header">
|
||
<span class="oph-back">← OCR</span>
|
||
<span class="oph-title">sender_anna_raddatz</span>
|
||
<span class="oph-sub">· Anna Raddatz</span>
|
||
<span class="pill running" style="margin-left:auto;">⟳ Läuft</span>
|
||
</div>
|
||
<div class="ocr-scroll">
|
||
<!-- Metric cards -->
|
||
<div class="metric-row">
|
||
<div class="metric-card">
|
||
<div class="mc-num good">98,3 %</div>
|
||
<div class="mc-lbl">Genauigkeit</div>
|
||
<div class="mc-delta up">↑ von 97,1 % (vorher)</div>
|
||
</div>
|
||
<div class="metric-card">
|
||
<div class="mc-num neutral">1,7 %</div>
|
||
<div class="mc-lbl">Zeichenfehlerrate</div>
|
||
<div class="mc-delta up">↓ von 2,2 % (vorher)</div>
|
||
</div>
|
||
<div class="metric-card">
|
||
<div class="mc-num neutral">128</div>
|
||
<div class="mc-lbl">Training-Blöcke</div>
|
||
<div class="mc-delta dim">beim letzten Lauf</div>
|
||
</div>
|
||
<div class="metric-card">
|
||
<div class="mc-num neutral">50</div>
|
||
<div class="mc-lbl">Epochs</div>
|
||
<div class="mc-delta dim">letztes Training</div>
|
||
</div>
|
||
</div>
|
||
<!-- Section bar above history table -->
|
||
<div class="section-bar">
|
||
<span class="section-bar-lbl">Trainingshistorie</span>
|
||
<span class="btn-primary">Jetzt neu trainieren</span>
|
||
<span class="btn-outline">Training-Daten exportieren</span>
|
||
<a class="btn-link">→ Anna Raddatz</a>
|
||
</div>
|
||
<!-- History table -->
|
||
<div class="hist-table">
|
||
<div class="ht-thead">
|
||
<span class="ht-th">Status</span>
|
||
<span class="ht-th">Zeitpunkt</span>
|
||
<span class="ht-th">Genauigkeit</span>
|
||
<span class="ht-th">ZFR</span>
|
||
<span class="ht-th">Blöcke</span>
|
||
<span class="ht-th">Epochs</span>
|
||
</div>
|
||
<div class="ht-row running">
|
||
<span><span class="pill running">⟳ Läuft</span></span>
|
||
<span class="ht-val" style="color:#6b7280;">gerade jetzt</span>
|
||
<span class="ht-val muted">—</span>
|
||
<span class="ht-val muted">—</span>
|
||
<span class="ht-val">128</span>
|
||
<span class="ht-val muted">—</span>
|
||
</div>
|
||
<div class="ht-row">
|
||
<span><span class="pill done">✓ Fertig</span></span>
|
||
<span class="ht-val" style="color:#6b7280;">vor 2 Std.</span>
|
||
<span class="ht-val good">98,3 %</span>
|
||
<span class="ht-val">1,7 %</span>
|
||
<span class="ht-val">128</span>
|
||
<span class="ht-val">50</span>
|
||
</div>
|
||
<div class="ht-row">
|
||
<span><span class="pill done">✓ Fertig</span></span>
|
||
<span class="ht-val" style="color:#6b7280;">vor 3 Tagen</span>
|
||
<span class="ht-val">97,1 %</span>
|
||
<span class="ht-val">2,2 %</span>
|
||
<span class="ht-val">92</span>
|
||
<span class="ht-val">50</span>
|
||
</div>
|
||
<div class="ht-row">
|
||
<span><span class="pill done">✓ Fertig</span></span>
|
||
<span class="ht-val" style="color:#6b7280;">vor 1 Woche</span>
|
||
<span class="ht-val">96,4 %</span>
|
||
<span class="ht-val">2,8 %</span>
|
||
<span class="ht-val">74</span>
|
||
<span class="ht-val">40</span>
|
||
</div>
|
||
<div class="ht-row failed-row">
|
||
<span><span class="pill failed">✕ Fehler</span></span>
|
||
<span class="ht-val" style="color:#6b7280;">vor 3 Wochen</span>
|
||
<span class="ht-val muted">—</span>
|
||
<span class="ht-val muted">—</span>
|
||
<span class="ht-val">60</span>
|
||
<span class="ht-val muted">—</span>
|
||
</div>
|
||
<!-- Error detail inline row -->
|
||
<div style="padding:2px 9px 5px;background:#fff8f8;border-bottom:1px solid #f0efe9;">
|
||
<span class="ht-err">Fehler: Connection timeout nach 120 s — OCR-Service nicht erreichbar</span>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- Global model variant -->
|
||
<div class="lbl" style="margin-top:18px">Desktop lg — global_kurrent (kein Personen-Link)</div>
|
||
<div class="chrome" style="width:940px">
|
||
<div class="bar"><span class="dot"></span><span class="dot"></span><span class="dot"></span><span class="url"></span></div>
|
||
<div class="app-header">
|
||
<span class="app-logo">FAMILIENARCHIV</span>
|
||
<span class="app-link">Dokumente</span>
|
||
<span class="app-link">Personen</span>
|
||
<span class="app-link act">Admin</span>
|
||
<div class="app-nav-r"><div class="app-av">M</div></div>
|
||
</div>
|
||
<div class="admin-shell" style="height:320px">
|
||
<div class="entity-nav">
|
||
<div class="en-heading">Admin</div>
|
||
<div class="en-item"><div class="en-icon"></div><div class="en-count">5</div><div class="en-label">Benutzer</div></div>
|
||
<div class="en-item"><div class="en-icon"></div><div class="en-count">3</div><div class="en-label">Gruppen</div></div>
|
||
<div class="en-item"><div class="en-icon"></div><div class="en-count">142</div><div class="en-label">Schlagwörter</div></div>
|
||
<div class="en-spacer"></div><div class="en-sep"></div>
|
||
<div class="en-heading" style="padding-top:5px">System</div>
|
||
<div class="en-item en-active"><div class="en-icon mint"></div><div class="en-count mint">7</div><div class="en-label mint">OCR</div></div>
|
||
</div>
|
||
<div class="v-line"></div>
|
||
<div class="ocr-content">
|
||
<div class="ocr-page-header">
|
||
<span class="oph-back">← OCR</span>
|
||
<span class="oph-title">global_kurrent</span>
|
||
<span class="oph-sub">· Globales Erkennungsmodell</span>
|
||
<span class="pill done" style="margin-left:auto;">✓ Fertig</span>
|
||
</div>
|
||
<div class="ocr-scroll">
|
||
<div class="metric-row">
|
||
<div class="metric-card">
|
||
<div class="mc-num neutral">95,1 %</div>
|
||
<div class="mc-lbl">Genauigkeit</div>
|
||
<div class="mc-delta up">↑ von 94,8 % (vorher)</div>
|
||
</div>
|
||
<div class="metric-card">
|
||
<div class="mc-num neutral">2,3 %</div>
|
||
<div class="mc-lbl">Zeichenfehlerrate</div>
|
||
<div class="mc-delta up">↓ von 2,7 % (vorher)</div>
|
||
</div>
|
||
<div class="metric-card">
|
||
<div class="mc-num neutral">1.842</div>
|
||
<div class="mc-lbl">Training-Blöcke</div>
|
||
<div class="mc-delta dim">beim letzten Lauf</div>
|
||
</div>
|
||
<div class="metric-card">
|
||
<div class="mc-num neutral">100</div>
|
||
<div class="mc-lbl">Epochs</div>
|
||
<div class="mc-delta dim">letztes Training</div>
|
||
</div>
|
||
</div>
|
||
<div class="section-bar">
|
||
<span class="section-bar-lbl">Trainingshistorie</span>
|
||
<span class="btn-primary">Jetzt neu trainieren</span>
|
||
<span class="btn-outline">Training-Daten exportieren</span>
|
||
<!-- kein Personen-Link für globales Modell -->
|
||
</div>
|
||
<div class="hist-table">
|
||
<div class="ht-thead">
|
||
<span class="ht-th">Status</span><span class="ht-th">Zeitpunkt</span><span class="ht-th">Genauigkeit</span><span class="ht-th">ZFR</span><span class="ht-th">Blöcke</span><span class="ht-th">Epochs</span>
|
||
</div>
|
||
<div class="ht-row">
|
||
<span><span class="pill done">✓ Fertig</span></span>
|
||
<span class="ht-val" style="color:#6b7280;">vor 1 Tag</span>
|
||
<span class="ht-val good">95,1 %</span><span class="ht-val">2,3 %</span><span class="ht-val">1.842</span><span class="ht-val">100</span>
|
||
</div>
|
||
<div class="ht-row">
|
||
<span><span class="pill done">✓ Fertig</span></span>
|
||
<span class="ht-val" style="color:#6b7280;">vor 5 Tagen</span>
|
||
<span class="ht-val">94,8 %</span><span class="ht-val">2,7 %</span><span class="ht-val">1.650</span><span class="ht-val">100</span>
|
||
</div>
|
||
<div class="ht-row">
|
||
<span><span class="pill done">✓ Fertig</span></span>
|
||
<span class="ht-val" style="color:#6b7280;">vor 2 Wochen</span>
|
||
<span class="ht-val">93,2 %</span><span class="ht-val">3,1 %</span><span class="ht-val">1.420</span><span class="ht-val">100</span>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div><!-- /col -->
|
||
</div><!-- /grid -->
|
||
|
||
<div class="annotation">
|
||
<div class="ann-bullet">4</div>
|
||
<div class="ann-text"><strong>Delta-Zeile in Metric-Cards.</strong> Unter jedem Metrikwert erscheint ein Vergleich zum vorherigen erfolgreichen Lauf (<code>↑ / ↓ von X % (vorher)</code>). Grün für Verbesserung, rot für Verschlechterung. Berechnung: letzter DONE-Lauf vs. vorletzter DONE-Lauf im nach <code>completedAt</code> sortierten Array. Entfällt wenn weniger als zwei DONE-Läufe vorhanden.</div>
|
||
</div>
|
||
<div class="annotation">
|
||
<div class="ann-bullet">5</div>
|
||
<div class="ann-text"><strong>FAILED-Fehlertext inline.</strong> Bei einer FAILED-Zeile erscheint direkt darunter eine zweite Zeile mit dem <code>errorMessage</code>-Feld — leicht rosa hinterlegt, kein Modal nötig. Ist <code>errorMessage</code> null oder leer, entfällt die zweite Zeile.</div>
|
||
</div>
|
||
<div class="annotation">
|
||
<div class="ann-bullet">6</div>
|
||
<div class="ann-text"><strong>Keine Danger-Zone.</strong> Sender-Modelle werden programmatisch durch <code>SenderModelService</code> angelegt und gelöscht. Die Detailseite bietet bewusst keinen manuellen Lösch-Button. Beim globalen Modell fehlt der „→ Person"-Link, da <code>personId === null</code>.</div>
|
||
</div>
|
||
|
||
|
||
<!-- ══════════════════════════════════════════ S4: DETAIL MOBILE -->
|
||
<div class="sh">
|
||
<h2>4 — Modell-Detail: Mobile (320px)</h2>
|
||
<p>Die vier Metric-Cards arrangieren sich auf Mobile in einem 2×2-Raster. Aktions-Buttons nehmen die volle Breite ein. Die Trainingshistorie zeigt auf kleinen Bildschirmen nur Status, Datum und Genauigkeit — ZFR, Blöcke und Epochs entfallen.</p>
|
||
</div>
|
||
|
||
<div class="grid">
|
||
<div class="col">
|
||
<div class="lbl">320px — sender_anna_raddatz</div>
|
||
<div class="phone">
|
||
<div class="phone-status"><span>09:41</span><span>●●●●</span></div>
|
||
<div class="phone-body">
|
||
<div class="app-header" style="height:22px;padding:0 8px;gap:6px;">
|
||
<span class="app-logo" style="font-size:5.5px;">FAMILIENARCHIV</span>
|
||
<div style="margin-left:auto;display:flex;flex-direction:column;justify-content:center;gap:2px;width:12px;">
|
||
<div style="height:1.5px;background:rgba(255,255,255,.5);border-radius:1px;"></div>
|
||
<div style="height:1.5px;background:rgba(255,255,255,.5);border-radius:1px;"></div>
|
||
<div style="height:1.5px;background:rgba(255,255,255,.5);border-radius:1px;"></div>
|
||
</div>
|
||
</div>
|
||
<!-- Page header mobile -->
|
||
<div style="background:white;border-bottom:1px solid #e4e2d7;padding:4px 7px;display:flex;align-items:center;gap:4px;">
|
||
<span style="font-size:5px;color:#9ca3af;flex-shrink:0;">← OCR</span>
|
||
<div style="flex:1;min-width:0;">
|
||
<div style="font-size:6px;font-weight:800;color:#012851;white-space:nowrap;overflow:hidden;text-overflow:ellipsis;">sender_anna_raddatz</div>
|
||
<div style="font-size:4px;color:#6b7280;">Anna Raddatz</div>
|
||
</div>
|
||
<span class="pill running" style="font-size:4px;padding:1px 4px;flex-shrink:0;">⟳</span>
|
||
</div>
|
||
<div class="phone-scroll">
|
||
<!-- Metric cards 2x2 -->
|
||
<div style="display:grid;grid-template-columns:1fr 1fr;gap:4px;">
|
||
<div class="metric-card" style="padding:6px 8px;">
|
||
<div class="mc-num good" style="font-size:14px;">98,3 %</div>
|
||
<div class="mc-lbl" style="font-size:4px;">Genauigkeit</div>
|
||
<div class="mc-delta up" style="font-size:4px;">↑ von 97,1 %</div>
|
||
</div>
|
||
<div class="metric-card" style="padding:6px 8px;">
|
||
<div class="mc-num neutral" style="font-size:14px;">1,7 %</div>
|
||
<div class="mc-lbl" style="font-size:4px;">ZFR</div>
|
||
<div class="mc-delta up" style="font-size:4px;">↓ von 2,2 %</div>
|
||
</div>
|
||
<div class="metric-card" style="padding:6px 8px;">
|
||
<div class="mc-num neutral" style="font-size:14px;">128</div>
|
||
<div class="mc-lbl" style="font-size:4px;">Blöcke</div>
|
||
<div class="mc-delta dim" style="font-size:4px;">letzter Lauf</div>
|
||
</div>
|
||
<div class="metric-card" style="padding:6px 8px;">
|
||
<div class="mc-num neutral" style="font-size:14px;">50</div>
|
||
<div class="mc-lbl" style="font-size:4px;">Epochs</div>
|
||
<div class="mc-delta dim" style="font-size:4px;">letzter Lauf</div>
|
||
</div>
|
||
</div>
|
||
<!-- Action buttons full-width -->
|
||
<span class="btn-primary" style="justify-content:center;height:22px;font-size:5.5px;">Jetzt neu trainieren</span>
|
||
<span class="btn-outline" style="justify-content:center;height:22px;font-size:5.5px;">Training-Daten exportieren</span>
|
||
<a style="font-size:5px;color:#012851;text-decoration:underline;text-align:center;padding:2px 0;">→ Anna Raddatz</a>
|
||
<!-- Training history simplified -->
|
||
<div class="hist-table">
|
||
<div style="padding:4px 7px;border-bottom:1px solid #e4e2d7;background:#f5f4ef;">
|
||
<span style="font-size:5px;font-weight:800;text-transform:uppercase;letter-spacing:.8px;color:#6b7280;">Trainingshistorie</span>
|
||
</div>
|
||
<div style="background:#fff8e1;padding:4px 7px;border-bottom:1px solid #f0efe9;display:flex;align-items:center;gap:4px;">
|
||
<span class="pill running" style="font-size:4px;padding:1px 4px;">⟳</span>
|
||
<span style="font-size:5px;color:#6b7280;flex:1;">gerade jetzt · 128 Bl.</span>
|
||
</div>
|
||
<div style="padding:4px 7px;border-bottom:1px solid #f0efe9;display:flex;align-items:center;gap:4px;">
|
||
<span class="pill done" style="font-size:4px;padding:1px 4px;">✓</span>
|
||
<span style="font-size:5px;color:#6b7280;flex:1;">vor 2 Std.</span>
|
||
<span style="font-size:5.5px;color:#2e7d32;font-weight:700;">98,3 %</span>
|
||
</div>
|
||
<div style="padding:4px 7px;border-bottom:1px solid #f0efe9;display:flex;align-items:center;gap:4px;">
|
||
<span class="pill done" style="font-size:4px;padding:1px 4px;">✓</span>
|
||
<span style="font-size:5px;color:#6b7280;flex:1;">vor 3 Tagen</span>
|
||
<span style="font-size:5.5px;color:#374151;">97,1 %</span>
|
||
</div>
|
||
<div style="padding:4px 7px;border-bottom:1px solid #f0efe9;display:flex;align-items:center;gap:4px;">
|
||
<span class="pill done" style="font-size:4px;padding:1px 4px;">✓</span>
|
||
<span style="font-size:5px;color:#6b7280;flex:1;">vor 1 Woche</span>
|
||
<span style="font-size:5.5px;color:#374151;">96,4 %</span>
|
||
</div>
|
||
<div style="padding:4px 7px;background:#fff8f8;display:flex;align-items:center;gap:4px;">
|
||
<span class="pill failed" style="font-size:4px;padding:1px 4px;">✕</span>
|
||
<span style="font-size:5px;color:#c62828;flex:1;">vor 3 Wochen · Timeout</span>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
|
||
<!-- ══════════════════════════════════════════ S5: IMPLEMENTATION REFERENCE -->
|
||
<div class="sh">
|
||
<h2>5 — Implementation Reference</h2>
|
||
<p>Exakte Tailwind-Klassen und Pixelwerte für beide Seiten. Neue Dateien sind mit <span class="impl-ref" style="display:inline"><span class="new">Neu</span></span> markiert.</p>
|
||
</div>
|
||
|
||
<div class="impl-ref">
|
||
<table>
|
||
<thead>
|
||
<tr><th>Element</th><th>Tailwind-Klassen</th><th>Real px / Wert</th><th>Hinweis</th></tr>
|
||
</thead>
|
||
<tbody>
|
||
|
||
<tr class="sub-header"><td colspan="4">Routen & Dateien</td></tr>
|
||
<tr>
|
||
<td>Übersicht</td>
|
||
<td><code>src/routes/admin/ocr/+page.svelte</code><br><code>src/routes/admin/ocr/+page.server.ts</code></td>
|
||
<td><code>GET /api/ocr/training-info</code></td>
|
||
<td class="new">Neu</td>
|
||
</tr>
|
||
<tr>
|
||
<td>Global-Detail</td>
|
||
<td><code>src/routes/admin/ocr/global/+page.svelte</code></td>
|
||
<td><code>GET /api/ocr/training-info</code> (gefiltert: <code>personId === null</code>)</td>
|
||
<td class="new">Neu</td>
|
||
</tr>
|
||
<tr>
|
||
<td>Sender-Detail</td>
|
||
<td><code>src/routes/admin/ocr/[personId]/+page.svelte</code></td>
|
||
<td><code>GET /api/ocr/training-info</code> (gefiltert nach personId)</td>
|
||
<td class="new">Neu</td>
|
||
</tr>
|
||
<tr>
|
||
<td>EntityNav-Eintrag</td>
|
||
<td>Vorhandene <code>EntityNav</code>-Komponente um OCR-Eintrag ergänzen</td>
|
||
<td>Neue Sektion „System" mit Separator davor</td>
|
||
<td class="warn">Bestehende Datei ändern</td>
|
||
</tr>
|
||
|
||
<tr class="sub-header"><td colspan="4">Seitenstruktur (beide Seiten)</td></tr>
|
||
<tr>
|
||
<td>OCR Content Area</td>
|
||
<td><code>flex-1 flex flex-col overflow-hidden min-w-0 bg-surface</code></td>
|
||
<td>Kein Tree-Panel, kein Edit-Panel — volle Breite nach EntityNav</td>
|
||
<td>OCR ist kein Entity-Editor; enthält kein Split-Layout</td>
|
||
</tr>
|
||
<tr>
|
||
<td>Page Header Bar</td>
|
||
<td><code>flex items-center gap-2 px-4 py-2.5 border-b border-line bg-white shrink-0 h-10</code></td>
|
||
<td>h: 40px</td>
|
||
<td>Enthält Back-Link (nur Detail), Titel, optionale Status-Pill</td>
|
||
</tr>
|
||
<tr>
|
||
<td>Scrollbarer Seiteninhalt</td>
|
||
<td><code>flex-1 overflow-y-auto p-4 flex flex-col gap-3</code></td>
|
||
<td>gap: 12px zwischen Sektionen</td>
|
||
<td>—</td>
|
||
</tr>
|
||
<tr>
|
||
<td>Back-Link (Detail)</td>
|
||
<td><code>text-[11px] text-gray-400 hover:text-ink transition-colors shrink-0</code></td>
|
||
<td>—</td>
|
||
<td><code>goto('/admin/ocr')</code> oder <code><a href="/admin/ocr"></code></td>
|
||
</tr>
|
||
|
||
<tr class="sub-header"><td colspan="4">Health Bar (Übersicht)</td></tr>
|
||
<tr>
|
||
<td>Health Bar Container</td>
|
||
<td><code>flex items-center gap-3 px-4 py-2 bg-white border border-line rounded-sm</code></td>
|
||
<td>h: 36px</td>
|
||
<td class="new">Neu</td>
|
||
</tr>
|
||
<tr>
|
||
<td>Service-Pill (online)</td>
|
||
<td><code>inline-flex items-center gap-1 px-2 py-0.5 rounded-sm text-[10px] font-bold uppercase tracking-wide bg-green-50 text-green-700 border border-green-200</code></td>
|
||
<td>—</td>
|
||
<td>Offline: <code>bg-red-50 text-red-700 border-red-200</code></td>
|
||
</tr>
|
||
<tr>
|
||
<td>Running-Pill</td>
|
||
<td><code>bg-amber-50 text-amber-700 border border-amber-200</code> (gleiche Basis)</td>
|
||
<td>—</td>
|
||
<td>Nur sichtbar wenn <code>status === 'RUNNING'</code>. Icon ⟳ via <code>animate-spin</code></td>
|
||
</tr>
|
||
<tr>
|
||
<td>Queued-Badge</td>
|
||
<td><code>text-[10px] text-amber-800 bg-amber-100 border border-amber-200 px-2 py-0.5 rounded-sm font-bold</code></td>
|
||
<td>—</td>
|
||
<td>Nur sichtbar wenn mind. 1 Lauf QUEUED. Text: „⏳ N in Warteschlange"</td>
|
||
</tr>
|
||
|
||
<tr class="sub-header"><td colspan="4">Stat Cards (Übersicht)</td></tr>
|
||
<tr>
|
||
<td>Grid (Desktop)</td>
|
||
<td><code>grid grid-cols-1 sm:grid-cols-3 gap-3</code></td>
|
||
<td>Mobile: 1 Spalte; ab 640px: 3 Spalten</td>
|
||
<td class="new">Neu</td>
|
||
</tr>
|
||
<tr>
|
||
<td>Stat Card</td>
|
||
<td><code>bg-white border border-line rounded-sm px-4 py-3</code></td>
|
||
<td>padding: 12px 16px · kein Hover (nicht klickbar)</td>
|
||
<td>—</td>
|
||
</tr>
|
||
<tr>
|
||
<td>Stat-Zahl</td>
|
||
<td><code>font-serif text-3xl font-bold text-ink leading-none</code></td>
|
||
<td>30px, Merriweather · deutsche Tausend-Trennung via <code>n.toLocaleString('de-DE')</code></td>
|
||
<td>—</td>
|
||
</tr>
|
||
<tr>
|
||
<td>Stat-Label</td>
|
||
<td><code>text-[10px] font-bold uppercase tracking-widest text-gray-400 mt-1</code></td>
|
||
<td>10px</td>
|
||
<td>—</td>
|
||
</tr>
|
||
<tr>
|
||
<td>Stat-Subtext</td>
|
||
<td><code>text-[9px] text-gray-300 mt-0.5</code></td>
|
||
<td>9px · optional</td>
|
||
<td>—</td>
|
||
</tr>
|
||
|
||
<tr class="sub-header"><td colspan="4">Modell-Tabelle (Übersicht)</td></tr>
|
||
<tr>
|
||
<td>Tabellen-Container</td>
|
||
<td><code>bg-white border border-line rounded-sm overflow-hidden</code></td>
|
||
<td>—</td>
|
||
<td class="new">Neu — kein <code><table></code>, sondern CSS-Grid-Rows</td>
|
||
</tr>
|
||
<tr>
|
||
<td>Tabellenkopf</td>
|
||
<td><code>bg-ink grid px-4 py-2 grid-cols-[2fr_80px_80px_80px_80px_100px]</code></td>
|
||
<td>—</td>
|
||
<td>Kopfzellen: <code>text-[9px] font-bold uppercase tracking-wide text-brand-mint</code></td>
|
||
</tr>
|
||
<tr>
|
||
<td>Tabellenzeile</td>
|
||
<td><code>grid px-4 py-2.5 border-b border-line items-center cursor-pointer hover:bg-surface transition-colors grid-cols-[2fr_80px_80px_80px_80px_100px]</code></td>
|
||
<td>Klick → <code>goto('/admin/ocr/{id}')</code></td>
|
||
<td>Mobile: Tabelle horizontal scrollbar (<code>overflow-x-auto</code>) oder vereinfachte 2-Spalten-Liste</td>
|
||
</tr>
|
||
<tr>
|
||
<td>Global-Zeile</td>
|
||
<td>Zusätzlich <code>bg-[#F8F8FC]</code></td>
|
||
<td>Immer erste Zeile (<code>personId === null</code>)</td>
|
||
<td>—</td>
|
||
</tr>
|
||
<tr>
|
||
<td>Sender-Name</td>
|
||
<td><code>text-sm font-medium text-gray-700</code></td>
|
||
<td>14px · darunter modelName: <code>text-[10px] text-gray-400 mt-0.5</code></td>
|
||
<td>Name aus <code>personNames[run.personId]</code> des training-info-Endpoints</td>
|
||
</tr>
|
||
<tr>
|
||
<td>Metrik-Wert (gut)</td>
|
||
<td><code>text-green-700 font-semibold</code></td>
|
||
<td>Accuracy ≥ 95 % oder niedrigste CER</td>
|
||
<td>—</td>
|
||
</tr>
|
||
<tr>
|
||
<td>Metrik-Wert (Fehler)</td>
|
||
<td><code>text-red-600 font-semibold</code></td>
|
||
<td>—</td>
|
||
<td>Zeigt „—" bei fehlenden Werten (FAILED/RUNNING)</td>
|
||
</tr>
|
||
|
||
<tr class="sub-header"><td colspan="4">Status Pills (alle Seiten)</td></tr>
|
||
<tr>
|
||
<td>Basis-Klassen</td>
|
||
<td><code>inline-flex items-center gap-1 px-2 py-0.5 rounded-sm text-[10px] font-bold uppercase tracking-wide border</code></td>
|
||
<td>—</td>
|
||
<td>Icon als Textzeichen vor Label (✓ ⟳ ✕ ⏳)</td>
|
||
</tr>
|
||
<tr>
|
||
<td>DONE</td>
|
||
<td><code>bg-green-50 text-green-700 border-green-200</code></td>
|
||
<td>—</td><td>—</td>
|
||
</tr>
|
||
<tr>
|
||
<td>RUNNING</td>
|
||
<td><code>bg-amber-50 text-amber-700 border-amber-200</code></td>
|
||
<td>—</td>
|
||
<td>Icon ⟳ in eigenem <code><span class="animate-spin"></code></td>
|
||
</tr>
|
||
<tr>
|
||
<td>FAILED</td>
|
||
<td><code>bg-red-50 text-red-700 border-red-200</code></td>
|
||
<td>—</td><td>—</td>
|
||
</tr>
|
||
<tr>
|
||
<td>QUEUED</td>
|
||
<td><code>bg-yellow-50 text-yellow-800 border-yellow-200</code></td>
|
||
<td>—</td><td>—</td>
|
||
</tr>
|
||
|
||
<tr class="sub-header"><td colspan="4">Metric Cards (Detail)</td></tr>
|
||
<tr>
|
||
<td>Grid</td>
|
||
<td><code>grid grid-cols-2 sm:grid-cols-4 gap-3</code></td>
|
||
<td>Mobile: 2×2 · Desktop: 4×1</td>
|
||
<td class="new">Neu</td>
|
||
</tr>
|
||
<tr>
|
||
<td>Metric Card</td>
|
||
<td><code>bg-white border border-line rounded-sm px-4 py-3 text-center</code></td>
|
||
<td>—</td><td>—</td>
|
||
</tr>
|
||
<tr>
|
||
<td>Zahl (positiv)</td>
|
||
<td><code>font-serif text-3xl font-bold leading-none text-green-700</code></td>
|
||
<td>Accuracy ≥ 95 %</td><td>—</td>
|
||
</tr>
|
||
<tr>
|
||
<td>Zahl (neutral)</td>
|
||
<td><code>font-serif text-3xl font-bold leading-none text-ink</code></td>
|
||
<td>Standard</td><td>—</td>
|
||
</tr>
|
||
<tr>
|
||
<td>Delta-Zeile</td>
|
||
<td><code>text-[10px] mt-1</code> + <code>text-green-600</code> / <code>text-red-600</code> / <code>text-gray-400</code></td>
|
||
<td>10px</td>
|
||
<td>Format: „↑ von 94,8 % (vorher)". Entfällt bei < 2 DONE-Läufen.</td>
|
||
</tr>
|
||
|
||
<tr class="sub-header"><td colspan="4">Trainingshistorie (Detail)</td></tr>
|
||
<tr>
|
||
<td>Tabellen-Container</td>
|
||
<td><code>bg-white border border-line rounded-sm overflow-hidden</code></td>
|
||
<td>—</td><td>—</td>
|
||
</tr>
|
||
<tr>
|
||
<td>Tabellenkopf</td>
|
||
<td><code>bg-ink grid px-4 py-2 grid-cols-[100px_1fr_80px_80px_80px_80px]</code></td>
|
||
<td>—</td>
|
||
<td>Mobile: <code>grid-cols-[80px_1fr_80px]</code> (Status · Datum · Genauigkeit)</td>
|
||
</tr>
|
||
<tr>
|
||
<td>RUNNING-Zeile</td>
|
||
<td><code>bg-amber-50</code> zusätzlich</td>
|
||
<td>—</td>
|
||
<td>Leere Metrik-Zellen zeigen „—"</td>
|
||
</tr>
|
||
<tr>
|
||
<td>FAILED-Zeile</td>
|
||
<td><code>bg-red-50</code> (sehr hell, ~#fff8f8)</td>
|
||
<td>—</td>
|
||
<td>Fehlertext folgt als zweite Sub-Zeile: <code>text-[11px] text-red-600 px-4 pb-2 col-span-full</code></td>
|
||
</tr>
|
||
|
||
<tr class="sub-header"><td colspan="4">Buttons & Aktionen</td></tr>
|
||
<tr>
|
||
<td>Primär-Button</td>
|
||
<td><code>inline-flex items-center h-9 px-4 bg-ink text-white rounded-sm text-xs font-bold uppercase tracking-wide hover:opacity-90 transition-opacity</code></td>
|
||
<td>h: 36px (44px auf Mobile)</td>
|
||
<td>Disabled wenn Lauf aktiv: <code>opacity-50 cursor-not-allowed pointer-events-none</code></td>
|
||
</tr>
|
||
<tr>
|
||
<td>Outline-Button</td>
|
||
<td><code>inline-flex items-center h-9 px-4 border border-ink text-ink rounded-sm text-xs font-bold uppercase tracking-wide hover:bg-surface transition-colors</code></td>
|
||
<td>—</td>
|
||
<td>Export: <code>window.location.href = '/api/ocr/training-data/export'</code></td>
|
||
</tr>
|
||
<tr>
|
||
<td>Personen-Link</td>
|
||
<td><code>text-sm text-ink underline underline-offset-2 hover:text-brand-mint transition-colors</code></td>
|
||
<td>—</td>
|
||
<td>Nur auf Sender-Detailseite. Ziel: <code>/persons/{personId}</code>. Entfällt bei <code>personId === null</code>.</td>
|
||
</tr>
|
||
|
||
<tr class="sub-header"><td colspan="4">API-Aufrufe</td></tr>
|
||
<tr>
|
||
<td>Übersicht laden</td>
|
||
<td><code>GET /api/ocr/training-info</code></td>
|
||
<td>Felder: <code>availableBlocks · totalOcrBlocks · availableDocuments · availableSegBlocks · ocrServiceAvailable · runs · personNames</code></td>
|
||
<td>Im <code>+page.server.ts</code> laden; <code>SenderModel</code>-Liste zusätzlich via eigenem Endpunkt oder aus <code>runs</code> aggregieren</td>
|
||
</tr>
|
||
<tr>
|
||
<td>Training starten</td>
|
||
<td><code>POST /api/ocr/train</code> / <code>POST /api/ocr/segtrain</code></td>
|
||
<td>Response: <code>OcrTrainingRun</code></td>
|
||
<td>Sofort in Health-Bar und Tabelle anzeigen; SSE-Kanal öffnen</td>
|
||
</tr>
|
||
<tr>
|
||
<td>Live-Updates</td>
|
||
<td><code>EventSource('/api/ocr/jobs/{jobId}/progress')</code></td>
|
||
<td>Server-Sent Events</td>
|
||
<td>Health-Bar + Status-Pills live aktualisieren; <code>onDestroy</code> schließen</td>
|
||
</tr>
|
||
|
||
</tbody>
|
||
</table>
|
||
</div>
|
||
|
||
</div><!-- /page -->
|
||
</body>
|
||
</html>
|