LLM-unabhaengiges Sicherheitsnetz fuer Lyras SOS-Pfad, schliesst das Top-Risiko der Risiko-Akte (verpasste Krise, ISO 14971 R-LYRA-01). Backend: - crisis-filter.ts: deterministische Krisen-/Suizid-Erkennung (DE primaer, EN/FR/AR Grundabdeckung) auf den letzten User-Nachrichten, synchron, kein LLM - sos-session.post: liefert crisisLevel sofort an die App (vor Stream-Start) - sos-stream: sendet bei Krise zuerst 'crisis_chips' (BZgA/112/Telefonseelsorge); Fallback an 3 Stellen (LLM-Fehler/Abbruch/keine Chips) -> nie leerer Screen - 43/43 Unit-Tests (crisis.json positiv, harmless.json False-Positive-Guard) Frontend (urge.tsx): - permanente rote Krisen-Bar oben, durch LLM-Chips nicht ueberschreibbar (eigener State-Slot), Hotline-Chips als tel:-Links - neue Locale-Strings DE/EN Risiko-Akte: R-LYRA-01 Restrisiko HOCH -> MITTEL. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
60 lines
1.6 KiB
TypeScript
60 lines
1.6 KiB
TypeScript
/**
|
|
* In-Memory Session Store für SOS-Streaming
|
|
*
|
|
* POST /api/coach/sos-session speichert messages/locale hier,
|
|
* GET /api/coach/sos-stream lädt sie per sessionId.
|
|
*
|
|
* TTL: 5 Minuten (Auto-Cleanup)
|
|
*/
|
|
import type { CrisisLevel } from "./crisis-filter";
|
|
|
|
type SosSessionData = {
|
|
userId: string;
|
|
messages: Array<{ role: "user" | "assistant"; content: string }>;
|
|
locale: string;
|
|
/** A/B-Test: client wählt LLM via Toggle. Default openrouter-sonnet. */
|
|
llmProvider?: string;
|
|
createdAt: number;
|
|
/**
|
|
* Deterministischer Krisen-Flag: gesetzt von /api/coach/sos-session (POST)
|
|
* via crisis-filter.ts, BEVOR das LLM aufgerufen wird.
|
|
* "crisis" | "elevated" | "none" — "none" oder undefined = kein Match.
|
|
*/
|
|
crisisLevel?: CrisisLevel;
|
|
};
|
|
|
|
const sessions = new Map<string, SosSessionData>();
|
|
const SESSION_TTL = 5 * 60 * 1000; // 5min
|
|
|
|
// Cleanup-Intervall: alle 2min alte Sessions löschen
|
|
setInterval(
|
|
() => {
|
|
const now = Date.now();
|
|
for (const [id, data] of sessions.entries()) {
|
|
if (now - data.createdAt > SESSION_TTL) {
|
|
sessions.delete(id);
|
|
}
|
|
}
|
|
},
|
|
2 * 60 * 1000,
|
|
);
|
|
|
|
export function setSosSession(sessionId: string, data: SosSessionData) {
|
|
sessions.set(sessionId, data);
|
|
}
|
|
|
|
export function getSosSession(sessionId: string): SosSessionData | undefined {
|
|
const data = sessions.get(sessionId);
|
|
if (!data) return undefined;
|
|
// TTL-Check
|
|
if (Date.now() - data.createdAt > SESSION_TTL) {
|
|
sessions.delete(sessionId);
|
|
return undefined;
|
|
}
|
|
return data;
|
|
}
|
|
|
|
export function deleteSosSession(sessionId: string) {
|
|
sessions.delete(sessionId);
|
|
}
|