- getGreeting: AI greeting with time-of-day context, falls back to static texts - getLetterIntro: AI letter introduction with finger description, falls back to curated intros - getForestComment: AI forest element comment, falls back to static comments - All functions check config availability and rate limits before API calls - System prompts enforce max 25 words, simple German, no performance praise
146 lines
4.0 KiB
TypeScript
146 lines
4.0 KiB
TypeScript
/**
|
|
* 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<string> {
|
|
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<string> {
|
|
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<string> {
|
|
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;
|
|
}
|