Download index.html from constantinSch/evaluation_summarization: direct link, hf CLI and curl.
- Browser
- Download file 29.2 kB
-
https://huggingface.co/spaces/constantinSch/evaluation_summarization/resolve/main/index.html
- Command line
-
hf download hf://spaces/constantinSch/evaluation_summarization/index.html
-
curl -L -o index.html https://huggingface.co/spaces/constantinSch/evaluation_summarization/resolve/main/index.html
29.2 kB
| <html lang="de"> | |
| <head> | |
| <meta charset="UTF-8"> | |
| <meta name="viewport" content="width=device-width, initial-scale=1.0"> | |
| <title>Zusammenfassungs-Evaluation</title> | |
| <style> | |
| * { box-sizing: border-box; margin: 0; padding: 0; } | |
| body { font-family: system-ui, -apple-system, sans-serif; background: #f5f5f5; color: #222; } | |
| header { | |
| background: #1a1a2e; color: #fff; padding: 12px 24px; | |
| display: flex; align-items: center; gap: 24px; flex-wrap: wrap; | |
| position: sticky; top: 0; z-index: 10; | |
| } | |
| header h1 { font-size: 17px; font-weight: 600; white-space: nowrap; } | |
| .btn-export { | |
| padding: 4px 12px; border: 1px solid rgba(255,255,255,.3); border-radius: 4px; | |
| background: transparent; color: #fff; font-size: 12px; cursor: pointer; | |
| } | |
| .btn-export:hover { background: rgba(255,255,255,.1); } | |
| .progress-box { margin-left: auto; font-size: 13px; opacity: .85; display: flex; align-items: center; gap: 10px; } | |
| .progress-bar { width: 120px; height: 6px; background: #333; border-radius: 3px; overflow: hidden; } | |
| .progress-fill { height: 100%; background: #22c55e; transition: width .3s; } | |
| .nav { | |
| display: flex; align-items: center; gap: 12px; | |
| padding: 10px 24px; background: #fff; border-bottom: 1px solid #ddd; | |
| position: sticky; top: 52px; z-index: 9; flex-wrap: wrap; | |
| } | |
| .nav button { | |
| padding: 6px 16px; border: 1px solid #ccc; border-radius: 4px; | |
| background: #fff; cursor: pointer; font-size: 14px; | |
| } | |
| .nav button:hover { background: #eee; } | |
| .nav button:disabled { opacity: .4; cursor: default; } | |
| .nav .counter { font-size: 14px; font-weight: 600; min-width: 80px; text-align: center; } | |
| .filters { display: flex; align-items: center; gap: 8px; margin-left: auto; } | |
| .filters label { font-size: 12px; font-weight: 600; color: #666; } | |
| .filters select { padding: 4px 8px; border: 1px solid #ccc; border-radius: 4px; font-size: 13px; } | |
| .card { | |
| max-width: 960px; margin: 20px auto; background: #fff; | |
| border-radius: 8px; box-shadow: 0 1px 3px rgba(0,0,0,.1); overflow: hidden; | |
| } | |
| .card-header { | |
| padding: 14px 20px; border-bottom: 1px solid #eee; | |
| display: flex; justify-content: space-between; align-items: center; gap: 12px; | |
| } | |
| .card-header h2 { font-size: 15px; font-weight: 600; } | |
| .card-header .meta { display: flex; align-items: center; gap: 8px; flex-shrink: 0; } | |
| .card-header .id { font-size: 12px; color: #888; font-family: monospace; } | |
| .badge { | |
| padding: 3px 10px; border-radius: 12px; font-size: 12px; font-weight: 600; | |
| } | |
| .badge-DRA { background: #ecfdf5; color: #065f46; } | |
| .badge-DW { background: #eff6ff; color: #1e40af; } | |
| .badge-NDR { background: #fef3c7; color: #92400e; } | |
| .section { padding: 14px 20px; border-bottom: 1px solid #f0f0f0; } | |
| .section-label { | |
| font-size: 11px; font-weight: 700; color: #666; | |
| text-transform: uppercase; letter-spacing: .5px; margin-bottom: 6px; | |
| } | |
| .section-content { font-size: 14px; line-height: 1.65; white-space: pre-wrap; } | |
| .summary-section { | |
| background: #eff6ff; border-left: 4px solid #2563eb; | |
| } | |
| .summary-section .section-content { font-size: 15px; font-weight: 500; } | |
| .transcript-section .section-content { | |
| max-height: 260px; overflow-y: auto; font-size: 13px; color: #444; | |
| } | |
| .original-toggle { | |
| padding: 8px 20px; background: #f8f8f8; border-bottom: 1px solid #f0f0f0; | |
| cursor: pointer; display: flex; align-items: center; gap: 8px; | |
| font-size: 12px; font-weight: 600; color: #666; user-select: none; | |
| } | |
| .original-toggle:hover { background: #f0f0f0; } | |
| .original-toggle .arrow { transition: transform .2s; display: inline-block; } | |
| .original-toggle .arrow.open { transform: rotate(90deg); } | |
| .original-transcript { | |
| display: none; padding: 14px 20px; border-bottom: 1px solid #f0f0f0; | |
| } | |
| .original-transcript.open { display: block; } | |
| .original-transcript .section-content { | |
| max-height: 260px; overflow-y: auto; font-size: 13px; color: #444; | |
| white-space: pre-wrap; line-height: 1.65; | |
| } | |
| /* --- Evaluation controls --- */ | |
| .eval-section { | |
| padding: 20px; border-bottom: 1px solid #f0f0f0; | |
| border: 2px solid #2563eb; border-radius: 0 0 0 0; | |
| margin: 0; background: #fafbff; | |
| } | |
| .eval-section .section-label { font-size: 13px; margin-bottom: 12px; color: #1e40af; } | |
| .rating-row { margin-bottom: 16px; } | |
| .rating-row label { font-size: 13px; font-weight: 600; color: #444; display: block; margin-bottom: 4px; } | |
| .rating-row select { padding: 6px 10px; border: 1px solid #ccc; border-radius: 4px; font-size: 14px; } | |
| .criteria { display: flex; gap: 20px; flex-wrap: wrap; margin-bottom: 16px; } | |
| .criterion { display: flex; flex-direction: column; gap: 4px; min-width: 120px; } | |
| .criterion label { font-size: 12px; font-weight: 700; color: #555; text-transform: uppercase; } | |
| .criterion select { | |
| padding: 5px 8px; border: 1px solid #ccc; border-radius: 4px; font-size: 14px; | |
| } | |
| .criterion select.val-ja { border-color: #22c55e; background: #f0fdf4; } | |
| .criterion select.val-aus { border-color: #f59e0b; background: #fffbeb; } | |
| .criterion select.val-nein { border-color: #ef4444; background: #fef2f2; } | |
| .criterion select.val-none { border-color: #ccc; background: #f9fafb; } | |
| .criterion-desc { | |
| font-size: 11px; color: #777; font-weight: 400; line-height: 1.4; margin-bottom: 2px; | |
| } | |
| /* Collapsible guidelines */ | |
| .guidelines-toggle { | |
| padding: 10px 20px; background: #f0f4ff; border-bottom: 1px solid #d0d8f0; | |
| cursor: pointer; display: flex; align-items: center; gap: 8px; | |
| font-size: 13px; font-weight: 600; color: #1e40af; user-select: none; | |
| } | |
| .guidelines-toggle:hover { background: #e0e8ff; } | |
| .guidelines-toggle .arrow { transition: transform .2s; display: inline-block; } | |
| .guidelines-toggle .arrow.open { transform: rotate(90deg); } | |
| .guidelines-body { | |
| display: none; padding: 14px 20px; background: #f8f9ff; border-bottom: 1px solid #d0d8f0; | |
| font-size: 13px; line-height: 1.7; color: #444; | |
| } | |
| .guidelines-body.open { display: block; } | |
| .guidelines-body h3 { font-size: 13px; font-weight: 700; color: #1e40af; margin: 12px 0 4px 0; } | |
| .guidelines-body h3:first-child { margin-top: 0; } | |
| .guidelines-body ul { margin: 4px 0 8px 18px; } | |
| .guidelines-body li { margin-bottom: 2px; } | |
| .guidelines-body .scale-label { font-weight: 600; } | |
| .comments-row { margin-top: 4px; } | |
| .comments-row label { font-size: 12px; font-weight: 700; color: #555; text-transform: uppercase; display: block; margin-bottom: 4px; } | |
| .comments-row textarea { | |
| width: 100%; padding: 8px 10px; border: 1px solid #ccc; border-radius: 4px; | |
| font-family: inherit; font-size: 14px; line-height: 1.5; resize: vertical; | |
| } | |
| .comments-row textarea:focus { outline: 2px solid #2563eb; border-color: #2563eb; } | |
| .actions { padding: 14px 20px; display: flex; justify-content: flex-end; gap: 12px; align-items: center; } | |
| .save-hint { font-size: 12px; color: #888; margin-right: auto; } | |
| .btn-save { | |
| padding: 8px 24px; border: none; border-radius: 4px; | |
| background: #2563eb; color: #fff; font-size: 14px; cursor: pointer; font-weight: 500; | |
| } | |
| .btn-save:hover { background: #1d4ed8; } | |
| .btn-save:disabled { opacity: .4; cursor: default; } | |
| .toast { | |
| position: fixed; bottom: 24px; right: 24px; padding: 12px 20px; | |
| border-radius: 6px; color: #fff; font-size: 14px; | |
| opacity: 0; transition: opacity .3s; pointer-events: none; | |
| } | |
| .toast.show { opacity: 1; } | |
| .toast.success { background: #22c55e; } | |
| .toast.error { background: #ef4444; } | |
| .empty-state { | |
| text-align: center; padding: 60px 20px; color: #666; font-size: 15px; | |
| } | |
| /* Login overlay */ | |
| .login-overlay { | |
| position: fixed; inset: 0; background: #f5f5f5; z-index: 100; | |
| display: flex; align-items: center; justify-content: center; | |
| } | |
| .login-overlay.hidden { display: none; } | |
| .login-box { | |
| max-width: 320px; width: 100%; background: #fff; | |
| padding: 32px; border-radius: 8px; box-shadow: 0 1px 3px rgba(0,0,0,.1); | |
| text-align: center; | |
| } | |
| .login-box h2 { margin-bottom: 16px; font-size: 20px; } | |
| .login-box input { | |
| padding: 10px; width: 100%; border: 1px solid #ccc; border-radius: 4px; | |
| font-size: 15px; margin-bottom: 12px; box-sizing: border-box; | |
| } | |
| .login-box button { | |
| padding: 10px 24px; border: none; border-radius: 4px; | |
| background: #2563eb; color: #fff; font-size: 15px; cursor: pointer; | |
| } | |
| .login-box button:hover { background: #1d4ed8; } | |
| .login-error { color: #dc2626; font-size: 13px; margin-bottom: 8px; } | |
| .badge-evaluated { | |
| background: #dcfce7; color: #166534; padding: 3px 10px; | |
| border-radius: 12px; font-size: 12px; font-weight: 600; | |
| } | |
| .badge-promptd { | |
| background: #fef3c7; color: #92400e; padding: 3px 10px; | |
| border-radius: 12px; font-size: 12px; font-weight: 600; | |
| } | |
| .reference-banner { | |
| padding: 10px 20px; background: #fef9c3; border-bottom: 1px solid #fde68a; | |
| font-size: 13px; color: #92400e; font-weight: 500; | |
| } | |
| .reference-banner strong { font-weight: 700; } | |
| .prior-judgement { padding: 14px 20px; border-bottom: 1px solid #f0f0f0; background: #fffbeb; } | |
| .prior-judgement .section-label { color: #92400e; } | |
| .prior-judgement .pj-grid { | |
| display: flex; gap: 16px; flex-wrap: wrap; font-size: 13px; margin-top: 6px; | |
| } | |
| .prior-judgement .pj-item { display: flex; flex-direction: column; gap: 2px; } | |
| .prior-judgement .pj-label { font-size: 11px; font-weight: 700; color: #92400e; text-transform: uppercase; } | |
| .prior-judgement .pj-value { font-weight: 600; } | |
| </style> | |
| </head> | |
| <body> | |
| <div class="login-overlay" id="login-overlay"> | |
| <div class="login-box"> | |
| <h2>Zusammenfassungs-Evaluation</h2> | |
| <p class="login-error" id="login-error" style="display:none">Falsches Passwort.</p> | |
| <form onsubmit="handleLogin(event)"> | |
| <input type="password" id="login-password" placeholder="Passwort" autofocus> | |
| <button type="submit">Weiter</button> | |
| </form> | |
| </div> | |
| </div> | |
| <header> | |
| <h1>Zusammenfassungs-Evaluation</h1> | |
| <button class="btn-export" onclick="exportAnnotations()">Export</button> | |
| <div class="progress-box"> | |
| <span id="progress-text">-</span> | |
| <div class="progress-bar"><div class="progress-fill" id="progress-fill" style="width:0"></div></div> | |
| </div> | |
| </header> | |
| <div class="nav"> | |
| <button id="btn-prev" onclick="navigate(-1)">Zurueck</button> | |
| <span class="counter" id="counter">-</span> | |
| <button id="btn-next" onclick="navigate(1)">Weiter</button> | |
| <div class="filters"> | |
| <label>Bearbeitungsstatus:</label> | |
| <select id="filter-status" onchange="applyFilters()"> | |
| <option value="">alle</option> | |
| <option value="pending" selected>offen</option> | |
| <option value="done">bewertet</option> | |
| </select> | |
| <label>LRA:</label> | |
| <select id="filter-rfa" onchange="applyFilters()"> | |
| <option value="">alle</option> | |
| </select> | |
| <label>Bewertungsrunde:</label> | |
| <select id="filter-type" onchange="applyFilters()"> | |
| <option value="">alle</option> | |
| <option value="new" selected>Neu (zu bewerten)</option> | |
| <option value="promptd">Alter Prompt (bereits evaluiert)</option> | |
| </select> | |
| </div> | |
| </div> | |
| <div class="card" id="card"> | |
| <div class="card-header"> | |
| <h2 id="title">-</h2> | |
| <div class="meta"> | |
| <span class="badge" id="badge-rfa"></span> | |
| <span class="badge-evaluated" id="badge-evaluated" style="display:none">Bewertet</span> | |
| <span class="badge-promptd" id="badge-promptd" style="display:none">Alter Prompt</span> | |
| <span class="id" id="eval-id">-</span> | |
| </div> | |
| </div> | |
| <div class="reference-banner" id="reference-banner" style="display:none"> | |
| <strong>Alter Prompt:</strong> Dieser Eintrag wurde bereits in einer frueheren Evaluation bewertet. Die bestehende Bewertung wird unten angezeigt. | |
| </div> | |
| <div class="prior-judgement" id="prior-judgement" style="display:none"> | |
| <div class="section-label">Bestehende Bewertung (Alter Prompt)</div> | |
| <div class="pj-grid" id="pj-grid"></div> | |
| <div id="pj-anmerkungen" style="margin-top:8px; font-size:13px; color:#444;"></div> | |
| </div> | |
| <div class="section summary-section"> | |
| <div class="section-label">Zusammenfassung</div> | |
| <div class="section-content" id="summary">-</div> | |
| </div> | |
| <div class="section"> | |
| <div class="section-label">Referenz</div> | |
| <div class="section-content" id="referenz">-</div> | |
| </div> | |
| <div class="section transcript-section"> | |
| <div class="section-label" id="transcript-label">Transkript</div> | |
| <div class="section-content" id="transcript">-</div> | |
| </div> | |
| <div class="original-toggle" id="original-toggle" style="display:none" onclick="toggleOriginal()"> | |
| <span class="arrow" id="original-arrow">▶</span> | |
| Original-Transkript (Fremdsprache) anzeigen | |
| </div> | |
| <div class="original-transcript" id="original-transcript"> | |
| <div class="section-label">Original-Transkript</div> | |
| <div class="section-content" id="original-text">-</div> | |
| </div> | |
| <div class="guidelines-toggle" onclick="toggleGuidelines()"> | |
| <span class="arrow" id="guidelines-arrow">▶</span> | |
| Bewertungsrichtlinien anzeigen | |
| </div> | |
| <div class="guidelines-body" id="guidelines-body"> | |
| <h3>Vorgehensweise</h3> | |
| <p>Lies das Transkript vollstaendig (oder ueberfliege es gruendlich). Lies dann die Zusammenfassung und waehle fuer jedes Kriterium eine Stufe (ja / ausreichend / nein). Vergib abschliessend eine Gesamtbewertung als Schulnote.</p> | |
| <h3>Stufen</h3> | |
| <ul> | |
| <li><span class="scale-label">Ja</span> -- Perfekt oder nahezu perfekt, keine nennenswerten Schwaechen</li> | |
| <li><span class="scale-label">Ausreichend</span> -- Mit Schwaechen, aber noch brauchbar</li> | |
| <li><span class="scale-label">Nein</span> -- Inakzeptabel, voellig unbrauchbar</li> | |
| </ul> | |
| <h3>1. Korrekt</h3> | |
| <p>Faktische Korrektheit: Werden Fakten, Namen und Orte korrekt wiedergegeben? Gibt es Halluzinationen oder Informationen, die nicht im Transkript vorkommen?</p> | |
| <ul> | |
| <li><span class="scale-label">Ja:</span> Alle Aussagen faktisch korrekt und durch das Transkript gestuetzt. Keine Halluzinationen.</li> | |
| <li><span class="scale-label">Ausreichend:</span> Einige Ungenauigkeiten, Kernaussagen stimmen groesstenteils.</li> | |
| <li><span class="scale-label">Nein:</span> Faktische Fehler und Halluzinationen. Ueberwiegend falsche Informationen.</li> | |
| </ul> | |
| <h3>2. Relevant</h3> | |
| <p>Auswahl der bedeutsamen Inhalte: Enthaelt die Zusammenfassung nur wichtige Informationen? Gibt es unnoetige Wiederholungen?</p> | |
| <ul> | |
| <li><span class="scale-label">Ja:</span> Konzentriert sich ausschliesslich auf die wichtigsten Informationen. Keine Redundanzen.</li> | |
| <li><span class="scale-label">Ausreichend:</span> Enthaelt auch weniger relevante Informationen. Einige unwichtige Details.</li> | |
| <li><span class="scale-label">Nein:</span> Konzentriert sich auf nebensaechliche Aspekte statt Kernthemen.</li> | |
| </ul> | |
| <h3>3. Vollstaendig</h3> | |
| <p>Sind alle wesentlichen Fakten, Kernaussagen und relevanten Entitaeten enthalten?</p> | |
| <ul> | |
| <li><span class="scale-label">Ja:</span> Alle wesentlichen Informationen und Kernaussagen enthalten. Wichtige Entitaeten vollstaendig erfasst.</li> | |
| <li><span class="scale-label">Ausreichend:</span> Grundlegender Ueberblick, aber einzelne bedeutsame Aspekte fehlen.</li> | |
| <li><span class="scale-label">Nein:</span> Wichtige Kernaussagen oder zentrale Entitaeten nicht erfasst. Zu lueckenhaft.</li> | |
| </ul> | |
| <h3>4. Kohaerent</h3> | |
| <p>Ist die Zusammenfassung gut strukturiert und logisch nachvollziehbar? Bilden die Saetze einen zusammenhaengenden Text?</p> | |
| <ul> | |
| <li><span class="scale-label">Ja:</span> Strukturiert und organisiert. Saetze bauen logisch aufeinander auf.</li> | |
| <li><span class="scale-label">Ausreichend:</span> Grundsaetzlich verstaendlich, aber stellenweise sprunghaft.</li> | |
| <li><span class="scale-label">Nein:</span> Unstrukturiert, Zusammenhang nur schwer nachvollziehbar.</li> | |
| </ul> | |
| <h3>Gesamtbewertung</h3> | |
| <p>Schulnote 1 (sehr gut) bis 6 (ungenuegend). Bezieht sich auf den generellen Eindruck, wie gut das Transkript zusammengefasst wurde. Muss kein Durchschnitt der Kriterien sein -- z.B. kann fehlende Korrektheit staerker wiegen als fehlende Kohaerenz.</p> | |
| <h3>Anmerkungen</h3> | |
| <p>Optional: Besondere Fehler, Staerken, Halluzinationen, Begruendungen oder konkrete Verbesserungsvorschlaege fuer den Prompt.</p> | |
| </div> | |
| <div class="eval-section"> | |
| <div class="section-label">Bewertung</div> | |
| <div class="rating-row"> | |
| <label>Gesamtbewertung (1 = sehr gut, 6 = ungenuegend)</label> | |
| <select id="bewertung" onchange="markDirty()"> | |
| <option value="">(bitte waehlen)</option> | |
| <option value="1">1 - sehr gut</option> | |
| <option value="2">2 - gut</option> | |
| <option value="3">3 - befriedigend</option> | |
| <option value="4">4 - ausreichend</option> | |
| <option value="5">5 - mangelhaft</option> | |
| <option value="6">6 - ungenuegend</option> | |
| </select> | |
| </div> | |
| <div class="criteria" id="criteria"></div> | |
| <div class="comments-row"> | |
| <label for="anmerkungen">Anmerkungen</label> | |
| <textarea id="anmerkungen" rows="3" placeholder="Optionale Anmerkungen..." oninput="markDirty()"></textarea> | |
| </div> | |
| </div> | |
| <div class="actions"> | |
| <span class="save-hint" id="save-hint"></span> | |
| <button class="btn-save" id="btn-save" onclick="saveAnnotation()" disabled>Speichern</button> | |
| </div> | |
| </div> | |
| <div class="toast" id="toast"></div> | |
| <script> | |
| const CRITERIA = ["korrekt", "relevant", "vollstaendig", "kohaerenz"]; | |
| const CRITERIA_LABEL = { | |
| korrekt: "Korrekt", | |
| relevant: "Relevant", | |
| vollstaendig: "Vollstaendig", | |
| kohaerenz: "Kohaerent", | |
| }; | |
| const CRITERIA_DESC = { | |
| korrekt: "Faktisch korrekt? Keine Halluzinationen?", | |
| relevant: "Nur wichtige Informationen? Keine Redundanzen?", | |
| vollstaendig: "Alle wesentlichen Fakten und Entitaeten enthalten?", | |
| kohaerenz: "Gut strukturiert und logisch nachvollziehbar?", | |
| }; | |
| let allRows = []; | |
| let filteredRows = []; | |
| let currentIdx = 0; | |
| let dirty = false; | |
| const initialCardMarkup = document.getElementById("card").innerHTML; | |
| /* ---------- Auth ---------- */ | |
| function getAuthToken() { | |
| return localStorage.getItem("auth_token") || ""; | |
| } | |
| function authHeaders() { | |
| const token = getAuthToken(); | |
| const h = {}; | |
| if (token) h["Authorization"] = "Bearer " + token; | |
| return h; | |
| } | |
| async function handleLogin(e) { | |
| e.preventDefault(); | |
| const pw = document.getElementById("login-password").value; | |
| const res = await fetch("/api/login", { | |
| method: "POST", | |
| headers: { "Content-Type": "application/json" }, | |
| body: JSON.stringify({ password: pw }), | |
| }); | |
| if (res.ok) { | |
| const data = await res.json(); | |
| localStorage.setItem("auth_token", data.token); | |
| document.getElementById("login-overlay").classList.add("hidden"); | |
| loadEntries(); | |
| } else { | |
| document.getElementById("login-error").style.display = ""; | |
| } | |
| } | |
| async function checkAuth() { | |
| const res = await fetch("/api/progress", { headers: authHeaders() }); | |
| if (res.status === 401) { | |
| document.getElementById("login-overlay").classList.remove("hidden"); | |
| } else { | |
| document.getElementById("login-overlay").classList.add("hidden"); | |
| loadEntries(); | |
| } | |
| } | |
| /* ---------- Data loading ---------- */ | |
| async function loadEntries() { | |
| const res = await fetch("/api/entries", { headers: authHeaders() }); | |
| if (!res.ok) { allRows = []; applyFilters(); return; } | |
| allRows = await res.json(); | |
| populateRfaFilter(); | |
| applyFilters(); | |
| refreshProgress(); | |
| } | |
| function populateRfaFilter() { | |
| const values = [...new Set(allRows.map(r => r.rfa).filter(Boolean))].sort(); | |
| const select = document.getElementById("filter-rfa"); | |
| select.innerHTML = '<option value="">alle</option>'; | |
| values.forEach(val => { | |
| const opt = document.createElement("option"); | |
| opt.value = val; | |
| opt.textContent = val; | |
| select.appendChild(opt); | |
| }); | |
| } | |
| function applyFilters() { | |
| if (dirty && !confirm("Ungespeicherte Aenderungen verwerfen?")) return; | |
| dirty = false; | |
| const status = document.getElementById("filter-status").value; | |
| const rfa = document.getElementById("filter-rfa").value; | |
| filteredRows = [...allRows]; | |
| if (status === "pending") { | |
| filteredRows = filteredRows.filter(r => !r.evaluated); | |
| } else if (status === "done") { | |
| filteredRows = filteredRows.filter(r => r.evaluated); | |
| } | |
| if (rfa) { | |
| filteredRows = filteredRows.filter(r => r.rfa === rfa); | |
| } | |
| const typeFilter = document.getElementById("filter-type").value; | |
| if (typeFilter === "promptd") { | |
| filteredRows = filteredRows.filter(r => r.has_prior_judgement); | |
| } else if (typeFilter === "new") { | |
| filteredRows = filteredRows.filter(r => !r.has_prior_judgement); | |
| } | |
| currentIdx = 0; | |
| render(); | |
| } | |
| async function refreshProgress() { | |
| const res = await fetch("/api/progress", { headers: authHeaders() }); | |
| if (!res.ok) return; | |
| const p = await res.json(); | |
| document.getElementById("progress-text").textContent = p.annotated + " / " + p.total; | |
| const pct = p.total ? Math.round(100 * p.annotated / p.total) : 0; | |
| document.getElementById("progress-fill").style.width = pct + "%"; | |
| } | |
| /* ---------- Rendering ---------- */ | |
| function render() { | |
| const card = document.getElementById("card"); | |
| if (filteredRows.length === 0) { | |
| card.innerHTML = '<div class="empty-state">Keine Eintraege gefunden.</div>'; | |
| document.getElementById("counter").textContent = "0 / 0"; | |
| return; | |
| } | |
| if (!document.getElementById("title")) { | |
| card.innerHTML = initialCardMarkup; | |
| } | |
| const row = filteredRows[currentIdx]; | |
| document.getElementById("counter").textContent = (currentIdx + 1) + " / " + filteredRows.length; | |
| document.getElementById("btn-prev").disabled = currentIdx === 0; | |
| document.getElementById("btn-next").disabled = currentIdx === filteredRows.length - 1; | |
| document.getElementById("eval-id").textContent = row.eval_id; | |
| document.getElementById("title").textContent = | |
| (row.sende_haupttitel || "") + " \u2013 " + (row.beitragstitel || ""); | |
| const badge = document.getElementById("badge-rfa"); | |
| badge.textContent = row.rfa; | |
| badge.className = "badge badge-" + row.rfa; | |
| document.getElementById("badge-evaluated").style.display = row.evaluated ? "" : "none"; | |
| const isPromptD = row.has_prior_judgement; | |
| document.getElementById("badge-promptd").style.display = isPromptD ? "" : "none"; | |
| document.getElementById("reference-banner").style.display = isPromptD ? "" : "none"; | |
| const pjEl = document.getElementById("prior-judgement"); | |
| if (isPromptD) { | |
| pjEl.style.display = ""; | |
| const grid = document.getElementById("pj-grid"); | |
| grid.innerHTML = | |
| '<div class="pj-item"><span class="pj-label">Gesamt</span><span class="pj-value">' + (row.prior_bewertung || "-") + '</span></div>' + | |
| CRITERIA.map(c => | |
| '<div class="pj-item"><span class="pj-label">' + CRITERIA_LABEL[c] + | |
| '</span><span class="pj-value">' + (row["prior_" + c] || "-") + '</span></div>' | |
| ).join(""); | |
| const pjAnm = document.getElementById("pj-anmerkungen"); | |
| pjAnm.textContent = row.prior_anmerkungen ? "Anmerkungen: " + row.prior_anmerkungen : ""; | |
| } else { | |
| pjEl.style.display = "none"; | |
| } | |
| document.getElementById("summary").textContent = row.summary || "-"; | |
| document.getElementById("referenz").textContent = row.referenz || "-"; | |
| const hasTranslation = row.rfa === "DW" && row.translation; | |
| if (hasTranslation) { | |
| document.getElementById("transcript-label").textContent = "Transkript (Uebersetzung)"; | |
| document.getElementById("transcript").textContent = row.translation; | |
| document.getElementById("original-toggle").style.display = ""; | |
| document.getElementById("original-text").textContent = row.transkript || "-"; | |
| document.getElementById("original-transcript").classList.remove("open"); | |
| document.getElementById("original-arrow").classList.remove("open"); | |
| } else { | |
| document.getElementById("transcript-label").textContent = "Transkript"; | |
| document.getElementById("transcript").textContent = row.transkript || "-"; | |
| document.getElementById("original-toggle").style.display = "none"; | |
| document.getElementById("original-transcript").classList.remove("open"); | |
| } | |
| // Bewertung dropdown | |
| document.getElementById("bewertung").value = row.bewertung || ""; | |
| // Criteria | |
| const criteriaEl = document.getElementById("criteria"); | |
| criteriaEl.innerHTML = ""; | |
| for (const c of CRITERIA) { | |
| const val = (row[c] || "").toLowerCase(); | |
| const div = document.createElement("div"); | |
| div.className = "criterion"; | |
| div.innerHTML = | |
| '<label>' + CRITERIA_LABEL[c] + '</label>' + | |
| '<span class="criterion-desc">' + CRITERIA_DESC[c] + '</span>' + | |
| '<select data-criterion="' + c + '" onchange="onCriterionChange(this)">' + | |
| '<option value=""' + (!val ? ' selected' : '') + '>(bitte waehlen)</option>' + | |
| '<option value="ja"' + (val === 'ja' ? ' selected' : '') + '>ja</option>' + | |
| '<option value="ausreichend"' + (val === 'ausreichend' ? ' selected' : '') + '>ausreichend</option>' + | |
| '<option value="nein"' + (val === 'nein' ? ' selected' : '') + '>nein</option>' + | |
| '</select>'; | |
| const sel = div.querySelector("select"); | |
| applyCriterionStyle(sel); | |
| criteriaEl.appendChild(div); | |
| } | |
| // Anmerkungen | |
| document.getElementById("anmerkungen").value = row.anmerkungen || ""; | |
| // Save hint | |
| document.getElementById("save-hint").textContent = row.bewertung ? "Bereits bewertet" : ""; | |
| dirty = false; | |
| document.getElementById("btn-save").disabled = true; | |
| } | |
| function applyCriterionStyle(sel) { | |
| const v = sel.value; | |
| sel.className = v === "ja" ? "val-ja" : v === "ausreichend" ? "val-aus" : v === "nein" ? "val-nein" : "val-none"; | |
| } | |
| /* ---------- Interaction ---------- */ | |
| function markDirty() { | |
| dirty = true; | |
| document.getElementById("btn-save").disabled = false; | |
| document.getElementById("save-hint").textContent = ""; | |
| } | |
| function onCriterionChange(sel) { | |
| applyCriterionStyle(sel); | |
| markDirty(); | |
| } | |
| function navigate(dir) { | |
| if (dirty && !confirm("Ungespeicherte Aenderungen verwerfen?")) return; | |
| dirty = false; | |
| currentIdx = Math.max(0, Math.min(filteredRows.length - 1, currentIdx + dir)); | |
| render(); | |
| } | |
| async function saveAnnotation() { | |
| const row = filteredRows[currentIdx]; | |
| const data = { | |
| eval_id: row.eval_id, | |
| bewertung: document.getElementById("bewertung").value || null, | |
| anmerkungen: document.getElementById("anmerkungen").value || null, | |
| }; | |
| document.querySelectorAll("#criteria select").forEach(sel => { | |
| data[sel.dataset.criterion] = sel.value || null; | |
| }); | |
| const res = await fetch("/api/annotate", { | |
| method: "POST", | |
| headers: { "Content-Type": "application/json", ...authHeaders() }, | |
| body: JSON.stringify(data), | |
| }); | |
| if (res.ok) { | |
| // Update local row data | |
| for (const key of ["bewertung", "korrekt", "relevant", "vollstaendig", "kohaerenz", "anmerkungen"]) { | |
| row[key] = data[key]; | |
| } | |
| row.evaluated = true; | |
| // Also update in allRows | |
| const src = allRows.find(r => r.eval_id === row.eval_id); | |
| if (src) { Object.assign(src, data); src.evaluated = true; } | |
| dirty = false; | |
| document.getElementById("btn-save").disabled = true; | |
| document.getElementById("save-hint").textContent = "Gespeichert"; | |
| showToast("Gespeichert", "success"); | |
| refreshProgress(); | |
| } else { | |
| showToast("Fehler beim Speichern", "error"); | |
| } | |
| } | |
| /* ---------- Utilities ---------- */ | |
| function showToast(msg, type) { | |
| const el = document.getElementById("toast"); | |
| el.textContent = msg; | |
| el.className = "toast " + type + " show"; | |
| setTimeout(() => el.classList.remove("show"), 2000); | |
| } | |
| document.addEventListener("keydown", e => { | |
| if ((e.ctrlKey || e.metaKey) && e.key.toLowerCase() === "s") { | |
| e.preventDefault(); | |
| if (!document.getElementById("btn-save").disabled) saveAnnotation(); | |
| return; | |
| } | |
| if (e.target.tagName === "SELECT" || e.target.tagName === "TEXTAREA") return; | |
| if (e.key === "ArrowLeft") navigate(-1); | |
| if (e.key === "ArrowRight") navigate(1); | |
| }); | |
| /* ---------- Guidelines toggle ---------- */ | |
| function toggleGuidelines() { | |
| const body = document.getElementById("guidelines-body"); | |
| const arrow = document.getElementById("guidelines-arrow"); | |
| body.classList.toggle("open"); | |
| arrow.classList.toggle("open"); | |
| } | |
| function toggleOriginal() { | |
| const body = document.getElementById("original-transcript"); | |
| const arrow = document.getElementById("original-arrow"); | |
| body.classList.toggle("open"); | |
| arrow.classList.toggle("open"); | |
| } | |
| /* ---------- Export ---------- */ | |
| async function exportAnnotations() { | |
| const res = await fetch("/api/export", { headers: authHeaders() }); | |
| if (!res.ok) { showToast("Export fehlgeschlagen", "error"); return; } | |
| const blob = await res.blob(); | |
| const url = URL.createObjectURL(blob); | |
| const a = document.createElement("a"); | |
| a.href = url; | |
| a.download = "annotations.jsonl"; | |
| a.click(); | |
| URL.revokeObjectURL(url); | |
| } | |
| /* ---------- Init ---------- */ | |
| checkAuth(); | |
| </script> | |
| </body> | |
| </html> | |