gwoe-antragspruefer/app/templates/v2/screens/neu.html
Dotty Dotter bf6201eb00 feat(tour): seitenspezifische Touren für jede Public/Auth-Page
Bisher hatten nur Startseite und Antrag-Detail eigene Touren — die
Fallback-Tour (Logo+Topbar+Sidebar) war für jede andere Page identisch.
User-Wunsch: pro Menüpunkt eigene Tour, die diese Page funktional
erklärt.

Pro Page 3-5 Stationen mit konkreten Selektoren auf die Hauptbereiche:
- Quellen: Volltextsuche, Treffer-Liste, Programm-Karten
- Methodik: TOC, GWÖ-Matrix, Pipeline
- Auswertungen: Tabs (4 Sichten), Filter, Visualisierung
- Stimmverhalten: Stimm-Index, Heuchelei-Quote, Cross-BL — eigene
  Variante via {% if v2_active_nav == 'stimmverhalten' %}, da
  Stimmverhalten dasselbe Template wie Auswertungen nutzt
- Tags: Tag-Wolke, gefilterte Treffer
- Merkliste: Liste, Empty-State
- Neuer Antrag: BL-Wahl, Drucksachen-Eingabe, Modell, Submit
- Abos: Filter, aktive Abos
- Atom-Feed: Filter, Feed-URL
- Landtag-Suche: BL, Suchbegriff, Treffer + Bewertung anstoßen
- Aktuelle Themen: Top-News, Cluster+Zeitreihe, PM-Generator

Texte ermächtigend formuliert (was-wofür-wie), nicht oberflächlich.
Selektoren mit Fallback-Liste (komma-separiert), damit Templates ohne
exakte Klassennamen die Station trotzdem zeigen können.
2026-05-09 09:03:59 +02:00

249 lines
8.5 KiB
HTML
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

{% extends "v2/base.html" %}
{% block title %}Neuer Antrag — GWÖ-Antragsprüfer{% endblock %}
{% set v2_active_nav = "neu" %}
{% block head_extra %}
<style>
.neu-form {
max-width: 560px;
}
.neu-form label {
display: block;
font-family: var(--font-mono);
font-size: 11px;
text-transform: uppercase;
letter-spacing: 0.06em;
opacity: 0.7;
margin-bottom: 4px;
}
.neu-form input[type="text"],
.neu-form select {
width: 100%;
font-family: var(--font-mono);
font-size: 13px;
padding: 8px 10px;
border: 1px solid var(--ecg-border);
border-radius: 4px;
background: var(--ecg-card-bg);
color: var(--ecg-dark);
margin-bottom: 14px;
}
.neu-form input[type="text"]:focus,
.neu-form select:focus {
outline: none;
border-color: var(--ecg-teal);
}
.neu-submit {
font-family: var(--font-display);
font-size: 13px;
font-weight: 700;
padding: 10px 24px;
background: var(--ecg-teal);
color: #fff;
border: none;
border-radius: 4px;
cursor: pointer;
letter-spacing: 0.04em;
transition: opacity 0.1s;
}
.neu-submit:hover { opacity: 0.85; }
.neu-submit:disabled { opacity: 0.45; cursor: not-allowed; }
#neu-status {
margin-top: 1rem;
font-family: var(--font-mono);
font-size: 12px;
display: none;
}
.neu-progress {
display: flex;
align-items: center;
gap: 8px;
color: var(--ecg-teal);
}
.neu-spinner {
width: 14px; height: 14px;
border: 2px solid var(--ecg-teal);
border-top-color: transparent;
border-radius: 50%;
animation: spin 0.7s linear infinite;
flex-shrink: 0;
}
@keyframes spin { to { transform: rotate(360deg); } }
.neu-error { color: #c00; }
</style>
{% endblock %}
{% block main %}
<div style="padding:0 0 1.5rem;">
<h1 style="font-family:var(--font-display);font-size:22px;color:var(--ecg-teal);margin:0 0 4px;">Neuer Antrag prüfen</h1>
<p style="font-size:12px;font-family:var(--font-mono);color:var(--ecg-dark);opacity:0.6;">
Drucksachen-Nr. eingeben · Analyse startet sofort
</p>
</div>
<div class="v2-kasten outline-blue" style="max-width:560px;margin-bottom:1.5rem;">
<p style="font-size:12px;">
Der Prüfer holt den Antragstext aus dem Landtags-Portal, bewertet ihn nach der GWÖ-Matrix 2.0
und zeigt Wahlprogramm-Treue sowie Verbesserungsvorschläge. Die Analyse dauert 3090 Sekunden.
</p>
</div>
<form class="neu-form" onsubmit="startAnalyse(event)">
<div id="neu-bl-hint" style="display:none;margin-bottom:14px;padding:8px 12px;background:color-mix(in srgb,var(--ecg-teal) 10%,transparent);border:1px solid var(--ecg-teal);border-radius:4px;font-family:var(--font-mono);font-size:11px;color:var(--ecg-teal);">
Bitte zuerst ein Bundesland im Header wählen.
</div>
<label for="neu-drucksache">Drucksachen-Nummer</label>
<input type="text" id="neu-drucksache" name="drucksache"
placeholder="z. B. 18/12345 oder NRW-18/12345"
required autocomplete="off">
<label for="neu-model">Modell</label>
<select id="neu-model" name="model">
<option value="">Standard ({{ default_model }})</option>
<option value="qwen-plus">qwen-plus</option>
<option value="qwen-max">qwen-max</option>
</select>
<button type="submit" class="neu-submit" id="neu-btn">Analyse starten</button>
</form>
<div id="neu-status">
<div id="neu-progress" class="neu-progress" style="display:none;">
<div class="neu-spinner"></div>
<span id="neu-progress-text">Analyse läuft …</span>
</div>
<div id="neu-error" class="neu-error" style="display:none;"></div>
</div>
{% endblock %}
{% block body_scripts %}
<script>
window.GWOE_TOUR_STEPS = [
{ selector: 'h1',
title: 'Neuer Antrag prüfen',
text: 'Hier kannst du einen Antrag aus einem deutschen Parlament neu durch das LLM laufen lassen — falls er noch nicht in der Datenbank ist oder du eine aktualisierte Bewertung willst.' },
{ selector: 'select[name="bundesland"], #bundesland',
title: 'Bundesland wählen',
text: 'Aus welchem Parlament kommt der Antrag? Bundestag oder einer der 16 Landtage. Jeder hat sein eigenes Drucksachen-Format und seine eigenen Wahlprogramme.' },
{ selector: 'input[name="drucksache"], #drucksache',
title: 'Drucksachen-Nummer',
text: 'Format z.B. 18/12345 — die offizielle Aktennummer. Das Tool sucht den Antrag bei der jeweiligen Landtags-/Bundestags-Quelle und lädt das PDF.' },
{ selector: 'select[name="model"], #model, .model-select',
title: 'LLM-Modell',
text: 'Welches Modell soll bewerten? Standard ist qwen-plus (gutes Verhältnis Qualität/Kosten). Du kannst auf qwen-max wechseln für sensiblere Bewertung — kostet aber mehr.' },
{ selector: 'button[type="submit"], .v2-chip.primary',
title: 'Analyse starten',
text: 'Klick startet die Pipeline: Antrag laden → LLM-Call → Citation-Verifikation → DB-Speicherung. Dauert je nach Antragslänge 3090 Sekunden.' },
];
</script>
<script>
async function startAnalyse(e) {
e.preventDefault();
const btn = document.getElementById('neu-btn');
const statusEl = document.getElementById('neu-status');
const progEl = document.getElementById('neu-progress');
const progText = document.getElementById('neu-progress-text');
const errEl = document.getElementById('neu-error');
const drucksache = document.getElementById('neu-drucksache').value.trim();
const bundesland = (window.v2GetGlobalBl && window.v2GetGlobalBl()) || 'ALL';
const model = document.getElementById('neu-model').value;
if (!drucksache) return;
if (bundesland === 'ALL') {
errEl.style.display = '';
errEl.textContent = 'Bitte zuerst ein Bundesland im Header wählen.';
return;
}
btn.disabled = true;
statusEl.style.display = '';
progEl.style.display = '';
errEl.style.display = 'none';
progText.textContent = 'Analyse läuft … (3090 s)';
try {
const fd = new FormData();
fd.append('drucksache', drucksache);
fd.append('bundesland', bundesland);
if (model) fd.append('model', model);
const resp = await fetch('/api/analyze-drucksache', { method: 'POST', body: fd });
if (resp.status === 401 || resp.status === 403) {
progEl.style.display = 'none';
errEl.style.display = '';
errEl.textContent = 'Sitzung abgelaufen — bitte erneut anmelden.';
btn.disabled = false;
if (typeof window.v2AuthModalOpen === 'function') window.v2AuthModalOpen();
return;
}
if (!resp.ok) {
const err = await resp.json().catch(() => ({ detail: resp.statusText }));
throw new Error(err.detail || ('HTTP ' + resp.status));
}
const data = await resp.json();
const ds = data.drucksache || drucksache;
if (data.status === 'already_checked') {
progText.textContent = 'Bereits bewertet. Weiterleitung …';
setTimeout(() => { window.location.href = '/antrag/' + encodeURIComponent(ds); }, 400);
return;
}
if (data.status === 'skipped') {
progEl.style.display = 'none';
errEl.style.display = '';
errEl.textContent = 'Antrag-Typ "' + (data.typ || 'unbekannt') + '" ist nicht abstimmbar — keine GWÖ-Bewertung sinnvoll.';
btn.disabled = false;
return;
}
// Job-Polling bis Abschluss, dann redirect
const jobId = data.job_id;
if (!jobId) { window.location.href = '/antrag/' + encodeURIComponent(ds); return; }
let attempts = 0;
const maxAttempts = 90;
while (attempts < maxAttempts) {
await new Promise(r => setTimeout(r, 2000));
attempts++;
const st = await fetch('/status/' + jobId).then(r => r.json()).catch(() => null);
if (!st) continue;
progText.textContent = 'Analyse läuft … (' + (st.status || '?') + ', ~' + (attempts * 2) + 's)';
if (st.status === 'completed') {
progText.textContent = 'Fertig. Weiterleitung …';
setTimeout(() => { window.location.href = '/antrag/' + encodeURIComponent(ds); }, 400);
return;
}
if (st.status === 'failed' || st.status === 'rejected') {
throw new Error('Analyse fehlgeschlagen: ' + (st.error || 'unbekannt'));
}
}
throw new Error('Analyse-Timeout (>3 min)');
} catch (err) {
progEl.style.display = 'none';
errEl.style.display = '';
errEl.textContent = 'Fehler: ' + err.message;
btn.disabled = false;
}
}
document.addEventListener('DOMContentLoaded', function () {
var hint = document.getElementById('neu-bl-hint');
function updateHint() {
var bl = (window.v2GetGlobalBl && window.v2GetGlobalBl()) || 'ALL';
if (hint) hint.style.display = (bl === 'ALL') ? '' : 'none';
}
updateHint();
window.addEventListener('v2-bl-changed', updateHint);
});
</script>
{% endblock %}