99 lines
3.8 KiB
TypeScript
99 lines
3.8 KiB
TypeScript
/**
|
|
* GET /api/lyra/welcome-back
|
|
*
|
|
* Generiert einen kurzen, freundlichen Motivations-Text wenn der User nach
|
|
* einer Schutz-Deaktivierung den Schutz wieder aktiviert hat.
|
|
*
|
|
* Pattern: erst LLM-Call (OpenRouter falls Key da, sonst OpenAI), bei Fehler
|
|
* fällt's auf einen statischen Pool von vorgeschriebenen Sätzen zurück —
|
|
* UX-Fallback statt User-facing-Error.
|
|
*
|
|
* Response: { message: string, source: "ai" | "fallback" }
|
|
*/
|
|
|
|
const WELCOME_BACK_PROMPT = `Du bist Lyra, der KI-Coach der Rebreak-App.
|
|
Der Nutzer hat seinen Glücksspiel-Schutz kurz deaktiviert und gerade wieder aktiviert.
|
|
Schreibe ihm GENAU EINE freundliche, warme Nachricht (max 3 kurze Sätze, Deutsch, du-Form).
|
|
|
|
WICHTIG:
|
|
- Kein Urteil, keine Belehrung
|
|
- Kein Schuldgefühl, kein "endlich"
|
|
- Anerkennung dass Rückfälle/Ausrutscher zur Recovery gehören
|
|
- Stärke betonen dass er WIEDER aktiviert hat
|
|
- Kein Wort "Sucht", "süchtig", "Rückfall" — stattdessen: "Phase", "Moment", "Stärke"
|
|
|
|
Antwort: nur die Nachricht, keine Anführungszeichen, kein Kontext.`;
|
|
|
|
const FALLBACK_MESSAGES = [
|
|
"Schön dass du wieder da bist. Diese eine Geste — den Schutz wieder einzuschalten — zeigt mehr Stärke als die meisten je sehen werden. Ich bin stolz auf dich.",
|
|
"Hey. Ausrutscher gehören zum Weg. Was zählt ist dass du jetzt hier bist, mit dem Schutz an. Das ist die wichtige Entscheidung.",
|
|
"Willkommen zurück. Du hast eine Pause genommen — und bist jetzt wieder hier. Genau so läuft Recovery: nicht linear, sondern echt. Lass uns weitermachen.",
|
|
"Gut dich wieder zu sehen. Manchmal braucht es einen kurzen Umweg um den eigenen Weg klarer zu sehen. Du gehst ihn weiter — das ist das Wichtige.",
|
|
"Hey, alles gut. Du hast den Schutz wieder aktiv — das war eine bewusste Entscheidung gegen den Impuls. Genau das ist der Muskel den wir hier trainieren.",
|
|
];
|
|
|
|
export default defineEventHandler(async (event) => {
|
|
await requireUser(event);
|
|
|
|
const config = useRuntimeConfig(event);
|
|
const openrouterKey = config.openrouterApiKey as string | undefined;
|
|
const openaiKey = config.openaiApiKey as string | undefined;
|
|
const key = openrouterKey ?? openaiKey;
|
|
|
|
// Fallback wenn keine Keys konfiguriert
|
|
if (!key) {
|
|
return {
|
|
message: pickRandom(FALLBACK_MESSAGES),
|
|
source: "fallback" as const,
|
|
};
|
|
}
|
|
|
|
const isOpenRouter = !!openrouterKey;
|
|
const url = isOpenRouter
|
|
? "https://openrouter.ai/api/v1/chat/completions"
|
|
: "https://api.openai.com/v1/chat/completions";
|
|
const model = isOpenRouter
|
|
? "meta-llama/llama-3.1-8b-instruct"
|
|
: "gpt-4o-mini";
|
|
|
|
try {
|
|
const res = await $fetch<{ choices: { message: { content: string } }[] }>(
|
|
url,
|
|
{
|
|
method: "POST",
|
|
headers: {
|
|
Authorization: `Bearer ${key}`,
|
|
"Content-Type": "application/json",
|
|
...(isOpenRouter && {
|
|
"HTTP-Referer": "https://rebreak.org",
|
|
"X-Title": "ReBreak Welcome-Back",
|
|
}),
|
|
},
|
|
body: {
|
|
model,
|
|
max_tokens: 200,
|
|
temperature: 0.85,
|
|
messages: [
|
|
{ role: "system", content: WELCOME_BACK_PROMPT },
|
|
{ role: "user", content: "Schreibe mir die Welcome-Back-Nachricht." },
|
|
],
|
|
},
|
|
timeout: 6000,
|
|
},
|
|
);
|
|
|
|
const text = res.choices?.[0]?.message?.content?.trim();
|
|
if (text && text.length > 10 && text.length < 600) {
|
|
return { message: text, source: "ai" as const };
|
|
}
|
|
return { message: pickRandom(FALLBACK_MESSAGES), source: "fallback" as const };
|
|
} catch (e: any) {
|
|
console.warn("[lyra.welcome-back] LLM-call failed:", e?.message ?? e);
|
|
return { message: pickRandom(FALLBACK_MESSAGES), source: "fallback" as const };
|
|
}
|
|
});
|
|
|
|
function pickRandom<T>(arr: readonly T[]): T {
|
|
return arr[Math.floor(Math.random() * arr.length)];
|
|
}
|