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 = { 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.", }; 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 }; });