diff --git a/app/api/ws.py b/app/api/ws.py index 917d98f..20a8720 100644 --- a/app/api/ws.py +++ b/app/api/ws.py @@ -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) diff --git a/app/web/app.js b/app/web/app.js index da863da..eff7acd 100644 --- a/app/web/app.js +++ b/app/web/app.js @@ -757,6 +757,10 @@ function runTurn(path, onopen) { scrollToBottom(); ws.close(); break; + case "notice": + // Kleine System-Notiz (z. B. Web-Suche aus / Kontingent aufgebraucht). + if (msg.text) addMessage("system", msg.text, { lang: turnLang() }); + break; case "error": addMessage("system", "Fehler: " + (msg.detail || msg.status || "unbekannt")); ws.close();