124 lines
3.6 KiB
TypeScript
Raw Permalink 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.

// Game-spezifischer Kontext für den Prompt
const GAME_VIBES: Record<string, string> = {
snake: "Snake präzise, fließend, ein Moment der Kontrolle",
tetris: "Tetris logisch, fokussiert, Gedanken ordnen statt Chaos",
memory: "Memory Geduld, Konzentration, im Hier und Jetzt bleiben",
tictactoe: "Tic-Tac-Toe ruhig, überlegt, kleine Strategie",
};
function getGameVibe(gameName: string): string {
const key = gameName.toLowerCase();
for (const [k, v] of Object.entries(GAME_VIBES)) {
if (key.includes(k)) return v;
}
return `${gameName} ein Moment bewusster Ablenkung`;
}
function buildFallback(
isNewRecord: boolean,
myRank: number | null,
mode: string,
): string {
const parts: string[] = [];
if (isNewRecord) parts.push("Neuer persönlicher Rekord! 🏆");
else if (myRank) parts.push(`Rang #${myRank} in der Rangliste`);
const challenge =
mode === "impulse"
? "Impuls überwunden kannst du länger standhalten? 💪"
: "Kannst du das schlagen? 👊";
parts.push(challenge);
return parts.join("\n");
}
const SYSTEM_PROMPT = `Du generierst kurze Share-Texte (max 23 Zeilen) für die ReBreak-App.
ReBreak hilft Menschen bei Glücksspielsucht, indem sie Mini-Games spielen statt zu gamble.
Der Text wird im Community-Feed unter dem Spielnamen gepostet er soll den Vibe des jeweiligen Spiels widerspiegeln.
Regeln:
- Zeile 1 (optional): Nur wenn neuer Rekord ODER guter Rang, eine kurze Meldung dazu
- Letzte Zeile: Kurzer, kreativer Challenge-Satz passend zum Spiel auf Deutsch
- 12 Emojis, passend zum Spiel und Ton
- KEIN Spielname, KEINE Score-Zahl im Text
- Keine Hashtags, keine URLs
- Antworte NUR mit dem Text, kein Prefix, keine Anführungszeichen`;
/** POST /api/games/share-text */
export default defineEventHandler(async (event) => {
await requireUser(event);
const body = await readBody(event);
const {
gameName,
score,
scoreLabel = "Punkte",
bestScore,
myRank,
isNewRecord,
mode = "game",
} = body as {
gameName: string;
score: number;
scoreLabel?: string;
bestScore?: number;
myRank?: number | null;
isNewRecord?: boolean;
mode?: string;
};
if (!gameName || score == null) {
throw createError({
statusCode: 400,
message: "gameName und score erforderlich",
});
}
const config = useRuntimeConfig();
if (!config.groqApiKey) {
return {
text: buildFallback(!!isNewRecord, myRank ?? null, mode),
};
}
const gameVibe = getGameVibe(gameName);
const userPrompt = [
`Spiel-Vibe: ${gameVibe}`,
isNewRecord ? "NEUER PERSÖNLICHER REKORD!" : null,
myRank ? `Rang #${myRank} in der Rangliste` : null,
mode === "impulse"
? "Der Spieler hat einen Spielimpuls damit überwunden."
: null,
]
.filter(Boolean)
.join("\n");
try {
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.1-8b-instant",
max_tokens: 120,
messages: [
{ role: "system", content: SYSTEM_PROMPT },
{ role: "user", content: userPrompt },
],
},
});
const text = response.choices?.[0]?.message?.content?.trim();
if (!text) throw new Error("empty response");
return { text };
} catch {
return {
text: buildFallback(!!isNewRecord, myRank ?? null, mode),
};
}
});