126 lines
4.7 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

import { createPost } from "../../db/community";
export const LYRA_TOPICS = [
"motivation",
"tipp",
"zitat",
"witzig",
"news",
"feature",
] as const;
export type LyraTopic = (typeof LYRA_TOPICS)[number];
export const TOPIC_HINTS: Record<LyraTopic, string> = {
motivation:
"Schreibe einen kurzen, stillen Gedanken für Menschen die heute kämpfen. Nicht übertrieben eher ruhig stark.",
tipp: "Teile einen kleinen, konkreten Trick aus der Verhaltensforschung oder CBT gegen Spieldrang. Praktisch und direkt.",
zitat:
"Teile ein tiefgründiges Zitat aus Psychologie, Stoizismus oder Verhaltensforschung ohne das Wort 'Sucht' zu verwenden. Kurz kommentiert.",
witzig:
"Schreibe einen witzigen, selbstironischen Post über das Thema Ablenkung, Impulskontrolle oder Gewohnheiten leicht und menschlich, nicht flach.",
news: "Beschreibe kurz eine typische Taktik der Glücksspielindustrie (z.B. Push-Notifications, Bonusangebote) sachlich und als Warnung formuliert.",
feature:
"Weise freundlich auf ein ReBreak-Feature hin (Blocker, Streak, Mail-Agent, Lyra-Chat, SOS-Atemübung) wähle eines zufällig oder nutze den gegebenen Kontext.",
};
const LYRA_SYSTEM_PROMPT = `Du bist Lyra Recovery-Coach und Begleiterin der ReBreak-Community. Du hast tiefes Wissen in CBT, Verhaltenspsychologie und dem Alltag von Menschen mit Spielsucht.
Du schreibst kurze Community-Beiträge. Deine Stimme:
- Direkt und persönlich du sprichst die Person an ("du")
- Warmherzig, geerdet, wie jemand der wirklich zuhört
- Niemals klischeehafte Motivationsfloskeln ("Du schaffst das!!!")
- Kein KI-Sprech, keine Listen, keine Aufzählungen
- Keine Casino-Werbung, keine Links, keine medizinischen Diagnosen
- Auf Deutsch, max. 34 Sätze
Antworte NUR mit dem Post-Text. Kein "Lyra:" Prefix, keine Anführungszeichen.`;
const REBREAK_SYSTEM_PROMPT = `Du bist der offizielle ReBreak Account. ReBreak ist eine App zur Überwindung von Glücksspielsucht.
Du postest Neuigkeiten, Updates und Community-Ankündigungen. Deine Tonalität:
- Offiziell aber nahbar wie ein Team-Update, nicht wie Werbung
- Kurz (max. 34 Sätze)
- Sachlich und informativ, gelegentlich motivierend
- Keine medizinischen Ratschläge, keine Links
- Auf Deutsch
Antworte NUR mit dem Post-Text. Kein "ReBreak:" Prefix, keine Anführungszeichen.`;
/** POST /api/admin/lyra-post — manueller Bot-Post vom Admin-Dashboard */
export default defineEventHandler(async (event) => {
const config = useRuntimeConfig();
const adminSecret = getHeader(event, "x-admin-secret");
if (!config.adminSecret || adminSecret !== config.adminSecret) {
throw createError({ statusCode: 401, message: "Unauthorized" });
}
const body = await readBody(event);
const author: "lyra" | "rebreak" =
body?.author === "rebreak" ? "rebreak" : "lyra";
const botUserId =
author === "rebreak" ? config.rebreakBotUserId : config.lyraBotUserId;
if (!botUserId) {
throw createError({
statusCode: 500,
message: `${author === "rebreak" ? "REBREAK_BOT_USER_ID" : "LYRA_BOT_USER_ID"} nicht konfiguriert`,
});
}
let content: string;
if (body?.customContent?.trim()) {
// Admin hat Text direkt eingegeben kein LLM-Call nötig
content = body.customContent.trim();
} else {
if (!config.groqApiKey) {
throw createError({
statusCode: 500,
message: "Groq API Key fehlt",
});
}
const topic: LyraTopic = LYRA_TOPICS.includes(body?.topic)
? body.topic
: LYRA_TOPICS[Math.floor(Math.random() * LYRA_TOPICS.length)];
const context: string | undefined = body?.context?.trim() || undefined;
const userPrompt = context
? `${TOPIC_HINTS[topic]}\n\nZusätzlicher Kontext für diesen Post: ${context}`
: TOPIC_HINTS[topic];
const systemPrompt =
author === "rebreak" ? REBREAK_SYSTEM_PROMPT : LYRA_SYSTEM_PROMPT;
const response = await $fetch<{
choices: { message: { content: string } }[];
}>("https://api.groq.com/openai/v1/chat/completions", {
method: "POST",
headers: {
Authorization: `Bearer ${config.groqApiKey}`,
"Content-Type": "application/json",
},
body: {
model: "llama-3.3-70b-versatile",
max_tokens: 200,
messages: [
{ role: "system", content: systemPrompt },
{ role: "user", content: userPrompt },
],
},
});
content = response.choices?.[0]?.message?.content?.trim() ?? "";
if (!content) {
throw createError({ statusCode: 500, message: "Keine Antwort von LLM" });
}
}
const post = await createPost(botUserId, "community", content);
return { success: true, postId: post.id, author, content };
});