/** * Companion text module: AI-generated or fallback text for greetings, * letter introductions, and forest comments. * * Strategy: Try Gemini API first (with rate-limit check), fall back * to curated static texts when API is unavailable. */ import { companions } from "./characters"; import type { CompanionType } from "../types"; import { loadConfig } from "../api/config"; import { generateText, checkRateLimit, incrementApiCall } from "../api/gemini"; import { type TimeOfDay, getRandomFallbackGreeting, getFallbackLetterIntro, getRandomFallbackForestComment, } from "./fallbacks"; function getTimeOfDay(): TimeOfDay { const hour = new Date().getHours(); if (hour < 12) return "morgen"; if (hour < 17) return "mittag"; return "abend"; } function getCompanion(type: CompanionType) { return companions.find((c) => c.type === type)!; } export async function getGreeting( characterType: CompanionType, db: IDBDatabase, ): Promise { const timeOfDay = getTimeOfDay(); const config = await loadConfig(); if (!config) return getRandomFallbackGreeting(timeOfDay); const canCall = await checkRateLimit(db, "text"); if (!canCall) return getRandomFallbackGreeting(timeOfDay); const companion = getCompanion(characterType); const systemPrompt = `Du bist ${companion.name}, ein(e) ${companion.personality} im Zauberwald. Du sprichst mit einem 7-jaehrigen Kind, das Tippen lernt. Regeln: - Max. 2 kurze Saetze (insgesamt max. 25 Woerter) - Einfache Sprache, kurze Woerter - Freundlich und warm, nie belehrend - Keine Bewertung von Leistung - Kein Englisch - Beziehe dich auf die Tageszeit: ${timeOfDay}`; const result = await generateText( "Schreib eine Begruessung.", systemPrompt, config, ); if (!result) return getRandomFallbackGreeting(timeOfDay); await incrementApiCall(db, "text"); return result; } export async function getLetterIntro( characterType: CompanionType, letter: string, fingerDescription: string, db: IDBDatabase, ): Promise { const config = await loadConfig(); if (!config) { return ( getFallbackLetterIntro(letter)?.text ?? "Druecke die leuchtende Taste!" ); } const canCall = await checkRateLimit(db, "text"); if (!canCall) { return ( getFallbackLetterIntro(letter)?.text ?? "Druecke die leuchtende Taste!" ); } const companion = getCompanion(characterType); const systemPrompt = `Du bist ${companion.name}, ein(e) ${companion.personality} im Zauberwald. Du stellst einem 7-jaehrigen Kind den Buchstaben ${letter} vor. Regeln: - Max. 2 Saetze (max. 25 Woerter) - Erklaere, welcher Finger den Buchstaben drueckt: ${fingerDescription} - Verwende eine bildhafte Eselsbruecke, die zum Wald/Natur/Tiere-Thema passt - Einfache Sprache - Kein Englisch`; const result = await generateText( "Stelle den Buchstaben vor.", systemPrompt, config, ); if (!result) { return ( getFallbackLetterIntro(letter)?.text ?? "Druecke die leuchtende Taste!" ); } await incrementApiCall(db, "text"); return result; } export async function getForestComment( characterType: CompanionType, elementDescription: string, db: IDBDatabase, ): Promise { const config = await loadConfig(); if (!config) return getRandomFallbackForestComment(); const canCall = await checkRateLimit(db, "text"); if (!canCall) return getRandomFallbackForestComment(); const companion = getCompanion(characterType); const systemPrompt = `Du bist ${companion.name}, ein(e) ${companion.personality} im Zauberwald. Ein Kind hat gerade eine Tippuebung abgeschlossen. Im Wald ist ein neues Wesen erschienen: ${elementDescription}. Regeln: - Max. 2 kurze Saetze (insgesamt max. 25 Woerter) - Druecke Staunen und Freude aus - Einfache Sprache fuer 7-Jaehrige - Kein Lob fuer Leistung, sondern Begeisterung ueber das neue Waldelement - Kein Englisch`; const result = await generateText( "Kommentiere das neue Element.", systemPrompt, config, ); if (!result) return getRandomFallbackForestComment(); await incrementApiCall(db, "text"); return result; }