184 lines
6.9 KiB
Markdown
184 lines
6.9 KiB
Markdown
|
|
# 0014 — Tour-System mit ElevenLabs-Voice + Web-Speech-Fallback
|
|||
|
|
|
|||
|
|
| | |
|
|||
|
|
|---|---|
|
|||
|
|
| **Status** | accepted |
|
|||
|
|
| **Datum** | 2026-05-09 |
|
|||
|
|
| **Refs** | #185, Commits 1c74cb8 → bf6201e |
|
|||
|
|
|
|||
|
|
## Kontext
|
|||
|
|
|
|||
|
|
Aus Issue #185 ("Bürger:innen-Perspektive: Was überfordert? Profi-Infos
|
|||
|
|
hinter Klick verstecken") kam die User-Anforderung, den Profi-Modus
|
|||
|
|
(v3-Antrag-Detail mit GWÖ-Score, Matrix 5×5, Marker-Pillen) für
|
|||
|
|
Erst-Besucher:innen erklärbar zu machen — **ohne** den Profi-Modus
|
|||
|
|
selbst zu vereinfachen.
|
|||
|
|
|
|||
|
|
Anforderung wurde im Verlauf konkretisiert:
|
|||
|
|
|
|||
|
|
1. Schaltfläche „Tour" auf jeder Seite außer Administration.
|
|||
|
|
2. „Du bist neu hier?"-Banner auf der Startseite, einmalig per
|
|||
|
|
localStorage dismissable; Topbar-Tour-Link bleibt permanent.
|
|||
|
|
3. Pro Menüpunkt **eigene Stationen**, die die Funktionalität dieser
|
|||
|
|
Page erklären (nicht eine generische Erklärung des Layouts).
|
|||
|
|
4. Sprachausgabe: weibliche, freundliche, warme, seriöse Stimme.
|
|||
|
|
Phase 1: Web Speech API als MVP. Phase 2: ElevenLabs (Domi).
|
|||
|
|
5. Ermächtigend formulieren, nicht vereinfachend — Bürger:innen sollen
|
|||
|
|
das Profi-Tool **lesen können**, nicht eine reduzierte Variante
|
|||
|
|
bekommen.
|
|||
|
|
|
|||
|
|
## Optionen
|
|||
|
|
|
|||
|
|
### Option A — Externe Tour-Library (intro.js, shepherd.js, driver.js)
|
|||
|
|
|
|||
|
|
Etabliertes Library-Ökosystem mit Spotlight-Effekt und Tour-Choreografie.
|
|||
|
|
|
|||
|
|
**Vorteile:** weniger eigener Code, vertraute UX-Patterns.
|
|||
|
|
**Nachteile:** zusätzliche JS-Dependency (~30–80 KB gezippt), keine
|
|||
|
|
Audio-Integration, kein bürgernahes Wording out of the box, Styling
|
|||
|
|
anders als das übrige v2/v3-Design.
|
|||
|
|
|
|||
|
|
### Option B — Eigene Spotlight-Engine, page-spezifische Stations
|
|||
|
|
|
|||
|
|
Eine schlanke Tour-Engine in `app/templates/v3/components/tour.html`,
|
|||
|
|
~250 Zeilen JS + CSS. Stationen pro Page über `window.GWOE_TOUR_STEPS`
|
|||
|
|
gesetzt. Engine löst Selektoren zur Laufzeit auf und überspringt
|
|||
|
|
Stations, deren Element nicht im DOM ist (z.B. keine Plenum-Votes auf
|
|||
|
|
einer bestimmten Drucksache).
|
|||
|
|
|
|||
|
|
**Vorteile:** keine externe Dependency, volle Kontrolle über Wording,
|
|||
|
|
eigenes Design (pill-shaped Buttons, teal-Farbgebung), enge
|
|||
|
|
Integration in CSP/Auth-Logik.
|
|||
|
|
**Nachteile:** Engine-Code muss selbst gepflegt werden.
|
|||
|
|
|
|||
|
|
### Option C — Server-seitig generierte Tour-Texte pro User-Profil
|
|||
|
|
|
|||
|
|
Backend bestimmt anhand des User-Profils (Erst-Visit, Eingeloggt,
|
|||
|
|
Bundesland-Filter), welche Stationen geladen werden. Mehr Personalisierung.
|
|||
|
|
|
|||
|
|
**Vorteile:** maximale Anpassung.
|
|||
|
|
**Nachteile:** über-engineered für den Erst-Aufschlag; macht jede
|
|||
|
|
Tour-Iteration zum Server-Deploy.
|
|||
|
|
|
|||
|
|
## Entscheidung
|
|||
|
|
|
|||
|
|
**Option B** mit folgender Architektur:
|
|||
|
|
|
|||
|
|
### Engine
|
|||
|
|
|
|||
|
|
`app/templates/v3/components/tour.html` enthält UI (Overlay, Spotlight,
|
|||
|
|
Bubble, Mute-Toggle), Engine-Logik und CSS. Wird global in
|
|||
|
|
`v2/base.html` per Jinja-`{% include %}` eingebunden, nur ausgeschlossen
|
|||
|
|
für Administration:
|
|||
|
|
|
|||
|
|
```jinja
|
|||
|
|
{% if v2_active_nav not in ['admin_stand', 'admin_queue', 'admin_abos',
|
|||
|
|
'freischaltungen', 'queue', 'abos'] %}
|
|||
|
|
{% include "v3/components/tour.html" ignore missing %}
|
|||
|
|
{% endif %}
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
### Stations pro Page
|
|||
|
|
|
|||
|
|
Jedes Page-Template definiert seine Stationen am Anfang des
|
|||
|
|
`body_scripts`-Blocks:
|
|||
|
|
|
|||
|
|
```html
|
|||
|
|
<script>
|
|||
|
|
window.GWOE_TOUR_STEPS = [
|
|||
|
|
{ selector: '.v3-bewertung',
|
|||
|
|
title: 'Die Gemeinwohl-Note',
|
|||
|
|
text: '…' },
|
|||
|
|
{ selector: '.v3-fraktionen',
|
|||
|
|
title: 'Programm-Treue pro Fraktion',
|
|||
|
|
text: '…' },
|
|||
|
|
];
|
|||
|
|
</script>
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
Selektoren mit Komma-separierter Fallback-Liste; Engine nimmt das erste
|
|||
|
|
Match und überspringt Stations ohne Match. Damit funktioniert die Tour
|
|||
|
|
auch, wenn ein Element auf manchen Drucksachen fehlt.
|
|||
|
|
|
|||
|
|
Auth-spezifische Stations via Jinja: z.B. die Startseite hat
|
|||
|
|
unterschiedliche „Navigation links"-Texte je nach `is_authenticated`,
|
|||
|
|
weil die Sidebar unterschiedliche Items zeigt.
|
|||
|
|
|
|||
|
|
### Fallback
|
|||
|
|
|
|||
|
|
Pages ohne `window.GWOE_TOUR_STEPS` bekommen drei generische Stations
|
|||
|
|
(Logo+Konzept, Topbar, Sidebar) — damit der Topbar-Tour-Link überall
|
|||
|
|
einen Inhalt zeigt.
|
|||
|
|
|
|||
|
|
### Audio: zwei Pfade
|
|||
|
|
|
|||
|
|
**Phase 1** (Web Speech API): Browser-eingebaute TTS via
|
|||
|
|
`speechSynthesis`. Funktioniert ohne API-Key, klingt mechanisch.
|
|||
|
|
|
|||
|
|
**Phase 2** (ElevenLabs/Domi):
|
|||
|
|
|
|||
|
|
- Backend-Endpoint `GET /api/tour/voice?text=…`, ENV-konfiguriert
|
|||
|
|
via `ELEVENLABS_API_KEY` und `ELEVENLABS_VOICE_ID`
|
|||
|
|
(Default: Domi `AZnzlk1XvdvUeBnXmlld`).
|
|||
|
|
- Server-seitiges Caching: `data/tour_audio/<sha256(text|voice|model)>.mp3`.
|
|||
|
|
Folgeabrufe gehen aus dem Datei-Cache, kein API-Quota-Verbrauch.
|
|||
|
|
- Frontend: `speak()` versucht erst den Endpoint. Bei 503 (kein API-Key)
|
|||
|
|
oder Fehler Fallback auf Web Speech.
|
|||
|
|
- **Auto-Play-Block-Workaround**: persistentes `<audio>`-Element wird
|
|||
|
|
einmal beim Tour-Start im Click-Handler (synchroner User-Gesture-Frame)
|
|||
|
|
mit einer 1×1-silent-WAV als data-URL entsperrt. Spätere src-Updates
|
|||
|
|
spielen dann ohne `NotAllowedError`.
|
|||
|
|
|
|||
|
|
### Content-Security-Policy
|
|||
|
|
|
|||
|
|
`media-src 'self' data: blob:;` ergänzt. `data:` für den Unlock-WAV,
|
|||
|
|
`blob:` für die ElevenLabs-MP3-Blob-URLs. Ohne diesen Eintrag blockt
|
|||
|
|
der Browser jeden `<audio>.play()`-Versuch.
|
|||
|
|
|
|||
|
|
## Konsequenzen
|
|||
|
|
|
|||
|
|
### Positiv
|
|||
|
|
|
|||
|
|
- Tour-Engine ist auf jeder Page verfügbar (ausgenommen Administration),
|
|||
|
|
ohne dass jede Page selbst die Engine einbindet.
|
|||
|
|
- Stationen pro Page sind deklarativ und page-lokal — pflegbar ohne
|
|||
|
|
Backend-Deploy bei reinen Text-Änderungen.
|
|||
|
|
- Audio-Pipeline ist Cache-basiert und API-Quota-schonend (1 ElevenLabs-
|
|||
|
|
Call pro einzigartigem Text-String über alle Sessions).
|
|||
|
|
- Web-Speech-Fallback macht den Code robust, wenn ElevenLabs ausfällt
|
|||
|
|
oder der Key entfernt wird.
|
|||
|
|
- Mute-Toggle, ESC-Schließen und Pfeiltasten-Navigation sind Standard
|
|||
|
|
ohne externe Library.
|
|||
|
|
|
|||
|
|
### Negativ
|
|||
|
|
|
|||
|
|
- Engine-Code wächst mit Features (~250 LOC heute). Bei zukünftigen
|
|||
|
|
Tour-Erweiterungen muss eigene Pflege geleistet werden, statt sich
|
|||
|
|
auf eine Library zu stützen.
|
|||
|
|
- ElevenLabs-Voices haben ein Free-Tier-Limit (10 k Zeichen/Monat).
|
|||
|
|
Bei breiter Nutzung wäre ein Server-seitiges Rate-Limit zusätzlich
|
|||
|
|
zum 30/min-Limit am Endpoint sinnvoll — heute reicht der Cache.
|
|||
|
|
- CSP-Erweiterung um `data:` und `blob:` für `media-src` erweitert die
|
|||
|
|
Angriffsfläche minimal (Audio-Embed via XSS könnte missbraucht
|
|||
|
|
werden). Risiko-Bewertung: niedrig, weil unsere Templates keine
|
|||
|
|
user-generated Audio-Sources einbetten.
|
|||
|
|
|
|||
|
|
### Folgen für andere ADRs
|
|||
|
|
|
|||
|
|
- ADR 0008 (DDD-Lightweight): Tour-System ist UI-only, kein
|
|||
|
|
Domain-Konzept; berührt die Schichten-Architektur nicht.
|
|||
|
|
- ADR 0012 (Debug-Auth-Token-Bypass): Tour-Tests in Playwright nutzen
|
|||
|
|
den Bypass für headless E2E.
|
|||
|
|
- Methodik-Page (`/methodik`): Tour ist die *bürgernahe* Variante der
|
|||
|
|
Methodik-Erklärung, beide bestehen nebeneinander — Methodik
|
|||
|
|
ausführlich, Tour als 3-5-Stationen-Quickstart.
|
|||
|
|
|
|||
|
|
## Folge-Iterationen (out of scope dieser ADR)
|
|||
|
|
|
|||
|
|
- Verfeinerung der Tour-Texte pro Page (User-Feedback bei konkreten
|
|||
|
|
Stations).
|
|||
|
|
- Weitere Voice-Settings (stability, similarity, speed) ggf. als
|
|||
|
|
Cookie/UI-Toggle.
|
|||
|
|
- Auf Antrag-Detail eigene Stations je Konfidenz oder vorhandenen
|
|||
|
|
Daten (z.B. zusätzliche Station bei vorhandenen Plenum-Votes).
|