feat: freundliche Hinweise bei abgeschalteter/aufgebrauchter Web-Suche

Ist die Web-Suche bei einer offensichtlich tagesaktuellen Frage
(should_force_search) nicht verfuegbar, sendet ws.py einen {type:"notice"}-
Event mit einer kleinen, lokalisierten Senioren-Systemnachricht ("Web-Suche
aus" bzw. "Tageskontingent aufgebraucht"); Frontend zeigt sie als System-Bubble.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
Dieter Schlüter 2026-06-30 09:25:10 +02:00
commit 4f1e15176b
2 changed files with 34 additions and 0 deletions

View file

@ -34,6 +34,31 @@ from app.audio.vad import EnergyVAD
from app.core.memory_extractor import maybe_schedule_extraction
from app.quota import (enforce_quota, record_usage, QuotaExceededError,
web_search_within_budget, record_web_search)
from app.pipeline.search_backstop import should_force_search
# Freundliche Hinweise (Senioren), wenn die Web-Suche bei einer offensichtlich
# tagesaktuellen Frage nicht verfuegbar ist. Fallback: Deutsch.
_WS_OFF_NOTICE = {
"de": "Hinweis: Die Web-Suche ist gerade nicht eingeschaltet — diese Antwort stammt aus meinem Wissen und ist vielleicht nicht ganz aktuell.",
"en": "Note: Web search is currently turned off — this answer comes from my own knowledge and may not be fully up to date.",
"nl": "Let op: webzoeken staat nu uit — dit antwoord komt uit mijn eigen kennis en is misschien niet helemaal actueel.",
"fr": "Remarque : la recherche web est désactivée — cette réponse vient de mes connaissances et n'est peut-être pas à jour.",
"es": "Nota: la búsqueda web está desactivada — esta respuesta procede de mis conocimientos y puede no estar actualizada.",
"it": "Nota: la ricerca web è disattivata — questa risposta viene dalle mie conoscenze e potrebbe non essere aggiornata.",
}
_WS_BUDGET_NOTICE = {
"de": "Hinweis: Das Web-Such-Kontingent für heute ist aufgebraucht — diese Antwort stammt aus meinem Wissen und ist vielleicht nicht ganz aktuell.",
"en": "Note: Today's web search allowance is used up — this answer comes from my own knowledge and may not be fully up to date.",
"nl": "Let op: het webzoek-tegoed voor vandaag is op — dit antwoord komt uit mijn eigen kennis en is misschien niet helemaal actueel.",
"fr": "Remarque : votre quota de recherche web pour aujourd'hui est épuisé — cette réponse vient de mes connaissances et n'est peut-être pas à jour.",
"es": "Nota: su cupo de búsqueda web de hoy se ha agotado — esta respuesta procede de mis conocimientos y puede no estar actualizada.",
"it": "Nota: il credito di ricerca web per oggi è esaurito — questa risposta viene dalle mie conoscenze e potrebbe non essere aggiornata.",
}
def _ws_notice(language: str | None, kind: str) -> str:
table = _WS_BUDGET_NOTICE if kind == "budget" else _WS_OFF_NOTICE
return table.get((language or "de").lower(), table["de"])
from app.auth import authenticate, _cookie_value, CAPABILITY_COOKIE
router = APIRouter()
@ -184,6 +209,11 @@ async def _run_turn(
{"type": "semantic", "text": trace.semantic_response,
"spoken": trace.spoken_response, "citations": trace.citations}
)
# Freundlicher Hinweis, wenn die Web-Suche bei einer offensichtlich aktuellen
# Frage nicht verfuegbar war (abgeschaltet oder Tagesbudget aufgebraucht).
if not route.web_search_enabled and should_force_search(text):
kind = "budget" if not web_search_within_budget(user, store) else "off"
await websocket.send_json({"type": "notice", "text": _ws_notice(route.language, kind)})
# Im text_only-Modus kommt kein Audio (Gerät spricht selbst).
if not audio_stream and not text_only:
await websocket.send_bytes(audio)