diff --git a/app/main.py b/app/main.py index 947cc3f..3ceeac3 100644 --- a/app/main.py +++ b/app/main.py @@ -2129,6 +2129,36 @@ async def quellen_search( }) +@app.get("/api/tour/voice") +@limiter.limit("30/minute") +async def tour_voice(request: Request, text: str = Query(..., min_length=2, max_length=2000)): + """Generiert (oder liefert aus Cache) eine MP3 für Tour-Erklär-Texte (#185). + + Nutzt ElevenLabs-TTS, wenn ENV ``ELEVENLABS_API_KEY`` gesetzt ist — + sonst 503, damit das Frontend auf ``speechSynthesis`` (browser- + eingebaute Stimme) zurückfällt. + + Caching: pro (text, voice_id, model_id) wird einmal generiert und in + ``data/tour_audio/.mp3`` gespeichert. Folgeabrufe gehen aus + dem Cache und kosten kein API-Quota. + """ + from .tour_audio import get_or_generate, is_available + if not is_available(): + raise HTTPException( + status_code=503, + detail="ElevenLabs nicht konfiguriert (ELEVENLABS_API_KEY fehlt)", + ) + audio = await get_or_generate(text) + if audio is None: + raise HTTPException(status_code=502, detail="TTS-Generierung fehlgeschlagen") + from fastapi.responses import Response + return Response( + content=audio, + media_type="audio/mpeg", + headers={"Cache-Control": "public, max-age=2592000"}, # 30 Tage Browser-Cache + ) + + @app.get("/api/wahlprogramm-cite") async def wahlprogramm_cite( request: Request, diff --git a/app/templates/v3/components/tour.html b/app/templates/v3/components/tour.html index 6bd67de..f53b67a 100644 --- a/app/templates/v3/components/tour.html +++ b/app/templates/v3/components/tour.html @@ -152,11 +152,14 @@ let _resolvedSteps = []; // STEPS gefiltert auf vorhandene Elemente let _tourMuted = false; let _tourUtter = null; + let _tourAudio = null; //