| <!DOCTYPE html> |
| <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; |
| } |
| |
| |
| .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; |
| } |
| |
| |
| .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 { |
| 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; |
| |
| |
| |
| 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(); |
| } |
| } |
| |
| |
| |
| 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 + "%"; |
| } |
| |
| |
| |
| 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")) { location.reload(); return; } |
| |
| 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"); |
| } |
| |
| |
| document.getElementById("bewertung").value = row.bewertung || ""; |
| |
| |
| 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); |
| } |
| |
| |
| document.getElementById("anmerkungen").value = row.anmerkungen || ""; |
| |
| |
| 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"; |
| } |
| |
| |
| |
| 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) { |
| |
| for (const key of ["bewertung", "korrekt", "relevant", "vollstaendig", "kohaerenz", "anmerkungen"]) { |
| row[key] = data[key]; |
| } |
| row.evaluated = true; |
| |
| 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"); |
| } |
| } |
| |
| |
| |
| 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); |
| }); |
| |
| |
| |
| 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"); |
| } |
| |
| |
| |
| 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); |
| } |
| |
| |
| checkAuth(); |
| </script> |
| </body> |
| </html> |
|
|