diff --git a/src/companion/companion.ts b/src/companion/companion.ts new file mode 100644 index 0000000..c41324a --- /dev/null +++ b/src/companion/companion.ts @@ -0,0 +1,145 @@ +/** + * 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; +}