diff --git a/app/api/chat.py b/app/api/chat.py index a83fd61..8098f60 100644 --- a/app/api/chat.py +++ b/app/api/chat.py @@ -18,11 +18,7 @@ from app.dependencies import ( from app.core.memory_extractor import maybe_schedule_extraction from app.quota import enforce_quota, record_usage, QuotaExceededError from app.runtime_config import runtime_settings -from app.safety.emergency import ( - record_manual_emergency, - emergency_cooldown_status, - notify_readiness, -) +from app.safety.emergency import record_manual_emergency, emergency_cooldown_status from app.schemas import ChatRequest, EmergencyRequest router = APIRouter() @@ -165,8 +161,5 @@ async def trigger_emergency(payload: EmergencyRequest, user: User = Depends(requ @router.get("/emergency/status") async def emergency_status(user: User = Depends(require_user)): """Aktueller Notruf-Sperr-Status des Nutzers (für die grüne Button-Anzeige, - auch nach einem Seiten-Neuladen) plus notify_ready für die Vorab-Warnung, - falls (noch) kein Benachrichtigungskanal eingerichtet ist.""" - st = emergency_cooldown_status(user, get_store(), runtime_settings) - st["notify_ready"] = notify_readiness(user, runtime_settings) - return st + auch nach einem Seiten-Neuladen).""" + return emergency_cooldown_status(user, get_store(), runtime_settings) diff --git a/app/dependencies.py b/app/dependencies.py index 35479f4..f9ac40c 100644 --- a/app/dependencies.py +++ b/app/dependencies.py @@ -165,7 +165,6 @@ LANG_TO_PIPER_VOICE: dict[str, str] = { "fr": "fr_FR-siwis-medium", "es": "es_ES-sharvard-medium", "it": "it_IT-paola-medium", - "nl": "nl_NL-mls-medium", "pt": "pt_BR-faber-medium", "pl": "pl_PL-darkman-medium", "ar": "ar_JO-kareem-medium", diff --git a/app/safety/emergency.py b/app/safety/emergency.py index 0dfa255..1a0d7fb 100644 --- a/app/safety/emergency.py +++ b/app/safety/emergency.py @@ -35,7 +35,6 @@ _NO_NOTIFY_NOTICE: dict[str, str] = { "fr": "ATTENTION : aucune notification n'est encore configurée.", "es": "ATENCIÓN: todavía no se ha configurado ninguna notificación.", "it": "ATTENZIONE: non è ancora stata configurata alcuna notifica.", - "nl": "LET OP: er is nog geen melding ingesteld.", "pt": "ATENÇÃO: ainda não há nenhuma notificação configurada.", "pl": "UWAGA: Nie skonfigurowano jeszcze żadnego powiadomienia.", "ar": "تنبيه: لم يتم إعداد أي إشعار بعد.", @@ -50,7 +49,6 @@ _SENT_NOTICE: dict[str, str] = { "fr": "Les secours ont été alertés. Un proche a été prévenu.", "es": "Se ha avisado a ayuda. Se notificó a un contacto.", "it": "L'aiuto è stato allertato. Una persona di contatto è stata avvisata.", - "nl": "Er is hulp gewaarschuwd. Een contactpersoon is op de hoogte gesteld.", "pt": "A ajuda foi avisada. Uma pessoa de contato foi notificada.", "pl": "Pomoc została powiadomiona. Osoba kontaktowa została powiadomiona.", "ar": "تم تنبيه المساعدة. تم إخطار شخص الاتصال.", @@ -58,51 +56,6 @@ _SENT_NOTICE: dict[str, str] = { "zh": "已通知求助。已通知联系人。", } -# Hinweis je Sprache, wenn GAR KEIN Kanal nutzbar ist (keine Kontakte/kein Webhook -# konfiguriert) — niemand wurde erreicht. Handlungsleitend: selbst den Notruf rufen. -_NO_RECIPIENTS_NOTICE: dict[str, str] = { - "de": "Es konnte niemand benachrichtigt werden – es sind keine Notfall-Kontakte eingerichtet. Bitte rufen Sie selbst den Notruf 112 an.", - "en": "No one could be notified – no emergency contacts are set up. Please call your emergency number (112) yourself.", - "fr": "Personne n'a pu être prévenu – aucun contact d'urgence n'est configuré. Veuillez appeler vous-même le 112.", - "es": "No se pudo avisar a nadie: no hay contactos de emergencia configurados. Por favor, llame usted mismo al 112.", - "it": "Non è stato possibile avvisare nessuno: nessun contatto di emergenza è configurato. Per favore chiami lei stesso il 112.", - "nl": "Er kon niemand worden gewaarschuwd – er zijn geen noodcontacten ingesteld. Bel zelf het alarmnummer 112.", - "pt": "Não foi possível avisar ninguém – não há contactos de emergência configurados. Por favor, ligue você mesmo para o 112.", - "pl": "Nie udało się nikogo powiadomić – nie skonfigurowano kontaktów alarmowych. Proszę samodzielnie zadzwonić pod numer 112.", - "ar": "تعذّر إخطار أي شخص – لا توجد جهات اتصال للطوارئ. يرجى الاتصال بنفسك برقم الطوارئ 112.", - "ru": "Никого не удалось уведомить — экстренные контакты не настроены. Пожалуйста, позвоните в службу спасения 112 сами.", - "zh": "无法通知任何人——尚未设置紧急联系人。请您自行拨打急救电话 112。", -} - -# Hinweis je Sprache, wenn Kanäle KONFIGURIERT waren, der Versand aber scheiterte -# (SMTP-/Webhook-Fehler). Unterscheidet den echten Zustellfehler vom Fehlen von Kontakten. -_DELIVERY_FAILED_NOTICE: dict[str, str] = { - "de": "Die Benachrichtigung konnte nicht zugestellt werden. Bitte versuchen Sie es erneut oder rufen Sie selbst den Notruf 112 an.", - "en": "The notification could not be delivered. Please try again or call your emergency number (112) yourself.", - "fr": "La notification n'a pas pu être transmise. Veuillez réessayer ou appeler vous-même le 112.", - "es": "No se pudo entregar la notificación. Por favor, inténtelo de nuevo o llame usted mismo al 112.", - "it": "Non è stato possibile recapitare la notifica. Per favore riprovi o chiami lei stesso il 112.", - "nl": "De melding kon niet worden afgeleverd. Probeer het opnieuw of bel zelf het alarmnummer 112.", - "pt": "Não foi possível entregar a notificação. Por favor, tente novamente ou ligue você mesmo para o 112.", - "pl": "Nie udało się dostarczyć powiadomienia. Proszę spróbować ponownie lub samodzielnie zadzwonić pod numer 112.", - "ar": "تعذّر تسليم الإشعار. يرجى المحاولة مرة أخرى أو الاتصال بنفسك برقم الطوارئ 112.", - "ru": "Не удалось доставить уведомление. Пожалуйста, повторите попытку или позвоните в службу спасения 112 сами.", - "zh": "通知发送失败。请重试或您自行拨打急救电话 112。", -} - -# Status -> Hinweis-Tabelle. "cooldown" nutzt bewusst den "gesendet"-Text (beruhigen). -_NOTICE_TABLES: dict[str, dict[str, str]] = { - "alerted": _SENT_NOTICE, - "no_recipients": _NO_RECIPIENTS_NOTICE, - "delivery_failed": _DELIVERY_FAILED_NOTICE, -} - - -def emergency_notice(language: str | None, status: str) -> str: - """Hinweis in Nutzersprache (Fallback Deutsch) passend zum Notruf-Status.""" - table = _NOTICE_TABLES.get(status, _NO_NOTIFY_NOTICE) - return table.get((language or "de").lower(), table["de"]) - def notify_notice(language: str | None, sent: bool) -> str: """Hinweis in Nutzersprache (Fallback Deutsch), je nachdem ob etwas rausging.""" @@ -261,19 +214,6 @@ def _phones(user, cfg) -> list[str]: return _csv(raw) or _csv(cfg.emergency_contact_phone) -def notify_readiness(user, cfg=settings) -> dict: - """Ob für diesen Nutzer ein Benachrichtigungskanal *funktionsfähig* wäre. - - Wichtig: nicht „Kontakt hinterlegt", sondern „Kanal würde tatsächlich senden". - E-Mail braucht einen SMTP-Host UND mindestens einen Empfänger; SMS/Anruf - braucht eine Webhook-URL. Dient dem Frontend für eine Vorab-Warnung und der - sauberen Status-Unterscheidung no_recipients vs. delivery_failed. - """ - email = bool(cfg.smtp_host) and bool(_contacts(user, cfg)) - webhook = bool(cfg.emergency_webhook_url) - return {"email": email, "webhook": webhook, "any": email or webhook} - - def _send_email_sync(to: list[str], subject: str, body: str, cfg) -> bool: """Versendet eine E-Mail per SMTP. Liefert True bei Erfolg, sonst False (best effort).""" if not cfg.smtp_host or not to: @@ -424,25 +364,11 @@ async def record_manual_emergency(user, store, language: str | None = None, metrics.inc("emergency_notify_total", {"channel": "webhook"}) notified = email_sent or webhook_sent - ready = notify_readiness(user, cfg) - # Drei ehrliche Endzustände: erreicht / kein Kanal eingerichtet / Zustellung gescheitert. - if notified: - status = "alerted" - elif not ready["any"]: - status = "no_recipients" - else: - status = "delivery_failed" - if status != "alerted": - logger.warning("Notruf von %s: status=%s, keine Benachrichtigung zugestellt " - "(email_ready=%s webhook_ready=%s)", - profile["name"], status, ready["email"], ready["webhook"]) return { "category": "manual", - "status": status, + "status": "alerted", "cooldown_seconds": cd["cooldown_seconds"], - "notice": emergency_notice(language, status), - "notified": notified, - "notify_ready": ready, + "notice": notify_notice(language, notified), "email_sent": email_sent, "webhook_sent": webhook_sent, "channels": channels, diff --git a/app/web/app.js b/app/web/app.js index ecb1b3a..fbdcc96 100644 --- a/app/web/app.js +++ b/app/web/app.js @@ -68,7 +68,7 @@ async function savePrefs(obj) { const TTS_SUPPORTED = typeof window !== "undefined" && "speechSynthesis" in window; const LANG_BCP47 = { de: "de-DE", en: "en-US", fr: "fr-FR", es: "es-ES", - it: "it-IT", nl: "nl-NL", pt: "pt-BR", pl: "pl-PL", ar: "ar-SA", ru: "ru-RU", zh: "zh-CN", + it: "it-IT", pt: "pt-BR", pl: "pl-PL", ar: "ar-SA", ru: "ru-RU", zh: "zh-CN", }; function isMobile() { @@ -359,7 +359,7 @@ async function copyText(text, btn) { } const ALL_LANGS = [["de","DE"],["en","EN"],["fr","FR"],["es","ES"],["it","IT"], - ["nl","NL"],["pt","PT"],["pl","PL"],["ar","AR"],["ru","RU"],["zh","ZH"]]; + ["pt","PT"],["pl","PL"],["ar","AR"],["ru","RU"],["zh","ZH"]]; // --- Format-Validierung der Notfall-Profil-Felder ------------------------ const _isEmail = (v) => /^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(v); @@ -1955,12 +1955,12 @@ const FIELD_META = { default_stt_provider: { ui: "combo", opts: ["openrouter","faster-whisper"], test: "stt" }, default_llm_provider: { ui: "combo", opts: ["openrouter","local-openai-compatible"], test: "llm" }, default_tts_provider: { ui: "combo", opts: ["openrouter","piper","chatterbox"], test: "tts" }, - default_language: { ui: "combo", opts: ["de","en","fr","es","it","nl","pt","pl","ar"], - labels: { de:"Deutsch",en:"Englisch",fr:"Französisch",es:"Spanisch",it:"Italienisch",nl:"Niederländisch",pt:"Portugiesisch",pl:"Polnisch",ar:"Arabisch" } }, + default_language: { ui: "combo", opts: ["de","en","fr","es","it","pt","pl","ar"], + labels: { de:"Deutsch",en:"Englisch",fr:"Französisch",es:"Spanisch",it:"Italienisch",pt:"Portugiesisch",pl:"Polnisch",ar:"Arabisch" } }, openrouter_llm_model: { ui: "datalist", test: "llm" }, openrouter_tts_model: { ui: "datalist", test: "tts", testProvider: "openrouter" }, openrouter_tts_voice: { ui: "combo", opts: ["Zephyr","Puck","Charon","Kore","Fenrir","Leda","Orus","Aoede","Callirrhoe","Enceladus","Algieba","Despina","Achernar"], test: "tts", testProvider: "openrouter" }, - piper_voice: { ui: "combo", opts: ["de_DE-thorsten-high","de_DE-kerstin-low","en_US-lessac-high","en_US-amy-medium","en_US-ryan-high","en_GB-cori-high","fr_FR-siwis-medium","fr_FR-tom-medium","es_ES-sharvard-medium","it_IT-paola-medium","it_IT-riccardo-x_low","nl_NL-mls-medium","pt_BR-faber-medium","pl_PL-gosia-medium","pl_PL-darkman-medium","ar_JO-kareem-medium","ru_RU-irina-medium","ru_RU-ruslan-medium","zh_CN-huayan-medium"], test: "tts", testProvider: "piper" }, + piper_voice: { ui: "combo", opts: ["de_DE-thorsten-high","de_DE-kerstin-low","en_US-lessac-high","en_US-amy-medium","en_US-ryan-high","en_GB-cori-high","fr_FR-siwis-medium","fr_FR-tom-medium","es_ES-sharvard-medium","it_IT-paola-medium","it_IT-riccardo-x_low","pt_BR-faber-medium","pl_PL-gosia-medium","pl_PL-darkman-medium","ar_JO-kareem-medium","ru_RU-irina-medium","ru_RU-ruslan-medium","zh_CN-huayan-medium"], test: "tts", testProvider: "piper" }, local_llm_system_prompt: { ui: "textarea", test: "llm" }, local_llm_temperature: { ui: "range", min: 0, max: 2, step: 0.1, test: "llm" }, local_llm_top_p: { ui: "range", min: 0, max: 1, step: 0.05, test: "llm" }, @@ -2380,7 +2380,6 @@ async function runEmergency() { body: JSON.stringify(body), }); const data = await res.json().catch(() => ({})); - if (data && data.notify_ready) sosNotifyReady = !!data.notify_ready.any; if (data && data.status === "cooldown") { // Server hat den (Doppel-)Alarm unterdrückt -> nur beruhigen, kein neuer Alarm. enterCooldown(data.remaining_seconds || 0, data.minutes_ago || 0); @@ -2390,19 +2389,12 @@ async function runEmergency() { const notice = (data && data.notice) || "ACHTUNG: Es ist noch keine Benachrichtigung eingebaut."; addMessage("emergency", "🆘 " + notice); - if (data && data.status === "alerted") { - const cd = data.cooldown_seconds || 0; - if (cd > 0) { - enterCooldown(cd, 0); // grün/pulsierend für die Sperre - } else { - setSosState("done"); // Sperre aus -> nur kurzes Erfolgs-Feedback - setTimeout(() => { if (!sosCooldownActive) setSosState("idle"); }, 6000); - } + const cd = (data && data.cooldown_seconds) || 0; + if (cd > 0) { + enterCooldown(cd, 0); // grün/pulsierend für die Sperre } else { - // no_recipients / delivery_failed: es ging NICHTS raus -> KEIN grünes Erfolgssignal, - // sondern roter Fehlerzustand; der Hinweis nennt die Selbsthilfe (112). - setSosState("error"); - setTimeout(() => { if (!sosCooldownActive) setSosState("idle"); }, 8000); + setSosState("done"); // Sperre aus -> nur kurzes Erfolgs-Feedback + setTimeout(() => { if (!sosCooldownActive) setSosState("idle"); }, 6000); } } catch (e) { addMessage("emergency", "🆘 Fehler beim Auslösen des Notrufs."); @@ -2411,17 +2403,9 @@ async function runEmergency() { } } -// Ist (serverseitig) überhaupt ein Benachrichtigungskanal nutzbar? null = noch -// unbekannt; erst bei sicherem `false` zeigen wir die Vorab-Warnung im Dialog. -let sosNotifyReady = null; - // Großer eigener Bestätigungsdialog statt native confirm() — seniorentauglich. const sosConfirm = $("#sos-confirm"); -function openSosConfirm() { - const warn = $("#sos-confirm-warn"); - if (warn) warn.classList.toggle("hidden", sosNotifyReady !== false); - if (sosConfirm) sosConfirm.classList.remove("hidden"); -} +function openSosConfirm() { if (sosConfirm) sosConfirm.classList.remove("hidden"); } function closeSosConfirm() { if (sosConfirm) sosConfirm.classList.add("hidden"); } // Beruhigender grüner Hinweis, wenn während der Sperre erneut gedrückt wird. @@ -2444,7 +2428,6 @@ async function initSosCooldown() { const res = await fetch("/api/emergency/status", { headers: { Accept: "application/json" } }); if (!res.ok) return; const s = await res.json(); - if (s && s.notify_ready) sosNotifyReady = !!s.notify_ready.any; if (s && s.active) enterCooldown(s.remaining_seconds || 0, s.minutes_ago || 0); } catch (e) { /* ignore */ } } diff --git a/app/web/index.html b/app/web/index.html index a0fd6d0..0e9bbfb 100644 --- a/app/web/index.html +++ b/app/web/index.html @@ -386,11 +386,6 @@
Möchten Sie wirklich Hilfe rufen?
Es werden sofort Helfer alarmiert.
-- ⚠️ Achtung: Es sind noch keine Notfall-Kontakte eingerichtet. Es kann niemand - automatisch benachrichtigt werden – im Notfall bitte selbst 112 anrufen. -