import { createPost } from "../../db/community"; export const LYRA_TOPICS = [ "motivation", "tipp", "zitat", "witzig", "news", "feature", "erinnerung", ] as const; export type LyraTopic = (typeof LYRA_TOPICS)[number]; export const TOPIC_HINTS: Record = { 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.", erinnerung: "Erinnere die Community freundlich und ganz ohne Druck daran, dass sie im Profil ein paar freiwillige Angaben zu sich machen können (z.B. Geburtsjahr). Erkläre in einem Satz, dass das hilft, ReBreak weiterzuentwickeln und als ernsthafte Unterstützung für mehr Menschen zu etablieren. Betone deutlich: völlig freiwillig, bleibt anonym, jederzeit änderbar. Verwende NICHT die Wörter 'DiGA', 'Daten sammeln', 'Studie' oder 'Statistik'.", }; type Lang = "de" | "en" | "fr" | "ar"; const LANGS: Lang[] = ["de", "en", "fr", "ar"]; const LANG_NAME: Record = { de: "Deutsch", en: "English", fr: "français", ar: "العربية (Arabic)", }; function lyraSystemPrompt(lang: Lang): string { return `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" / "tu" / "you" / "أنت" je nach Sprache) - 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 - WICHTIG: Antwort AUSSCHLIESSLICH in ${LANG_NAME[lang]}, max. 3–4 Sätze Antworte NUR mit dem Post-Text. Kein "Lyra:" Prefix, keine Anführungszeichen.`; } function rebreakSystemPrompt(lang: Lang): string { return `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. 3–4 Sätze) - Sachlich und informativ, gelegentlich motivierend - Keine medizinischen Ratschläge, keine Links - WICHTIG: Antwort AUSSCHLIESSLICH in ${LANG_NAME[lang]} 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. // Admin schreibt in genau einer Sprache (was er will) → wir speichern's // plain. PostCard.tsx zeigt das dann allen Usern in der Schreibsprache. 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]; // 4 parallele Groq-Calls für de/en/fr/ar. Promise.allSettled damit ein // fehlgeschlagener Locale-Call die anderen 3 nicht killt. const results = await Promise.allSettled( LANGS.map(async (lang) => { const systemPrompt = author === "rebreak" ? rebreakSystemPrompt(lang) : lyraSystemPrompt(lang); 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 }, ], }, }); const text = response.choices?.[0]?.message?.content?.trim() ?? ""; if (!text) throw new Error("empty content"); return [lang, text] as const; }), ); const localized: Partial> = {}; for (const r of results) { if (r.status === "fulfilled") { const [lang, text] = r.value; localized[lang] = text; } } if (!localized.de) { throw createError({ statusCode: 500, message: "LLM-Generation für DE fehlgeschlagen (alle Locales).", }); } // Content als JSON-encoded {de,en,fr,ar} — PostCard.tsx parsed + picked // current-locale; fällt auf DE zurück wenn locale fehlt. content = JSON.stringify(localized); } const post = await createPost(botUserId, "community", content); return { success: true, postId: post.id, author, content }; });