Files
kidsai/html/kidsai/server.js
root 291c56c4db 🎯 Final conversation improvements: Fixed all identified issues
🔧 CRITICAL FIXES:
• Enhanced frustration detection for 'drehen uns im Kreis' and similar expressions
• Improved creative answer recognition for single words like 'FARBEN!'
• Eliminated inappropriate 'interessante Sichtweise' responses
• Added better handling for boredom and disengagement

 IMPROVEMENTS:
• Enhanced humor detection with single-word creative answers
• Better frustration indicators including colloquial expressions
• Improved emotional response validation
• More nuanced conversation flow handling

🧪 TESTING:
• test-improved-responses.js: 4/4 tests passed
• test-problematic-conversation.js: All conversation issues resolved
• Ultimate test suite: Still 100% success rate maintained

🎯 CONVERSATION QUALITY:
• No more generic 'interessante Sichtweise' for inappropriate responses
• Creative answers like 'FARBEN!' now celebrated appropriately
• Frustration with repetition recognized and addressed empathetically
• Boredom handled with understanding and redirection

🚀 The conversation flow is now truly child-friendly and engaging!
2025-07-03 15:44:57 +02:00

1519 lines
66 KiB
JavaScript
Executable File
Raw 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.
const express = require('express');
const cors = require('cors');
const path = require('path');
const fetch = require('node-fetch');
const OpenAI = require('openai');
// Load environment variables from .env file
require('dotenv').config();
// Add robust error logging for uncaught exceptions and unhandled promise rejections
process.on('uncaughtException', (err) => {
console.error('UNCAUGHT EXCEPTION:', err.stack || err);
// Optionally, log to a file or external service here
});
process.on('unhandledRejection', (reason, promise) => {
console.error('UNHANDLED REJECTION:', reason);
// Optionally, log to a file or external service here
});
const app = express();
const PORT = process.env.PORT || 3002;
// OpenAI Configuration (Primary AI service)
const openai = new OpenAI({
apiKey: process.env.OPENAI_API_KEY
});
// Hugging Face API configuration (Backup)
const HF_MODELS = [
'microsoft/DialoGPT-small',
'google/flan-t5-small',
'facebook/blenderbot-400M-distill',
'microsoft/DialoGPT-medium'
];
const HF_API_TOKEN = process.env.HUGGING_FACE_TOKEN;
// Store conversation history to prevent redundant questions
const conversationHistory = new Map(); // sessionId -> conversation array
// Educational prompts for AI to guide thinking instead of giving direct answers
const EDUCATIONAL_PROMPTS = {
en: {
systemPrompt: `You are a patient learning companion for children (8-12 years old). Always ask only ONE targeted, new question that builds on the child's last answer. Do not repeat yourself—check if you have already asked this or a very similar question.
Your questions should encourage the child to think, e.g., by asking for comparisons, reasons, differences, feelings, or personal experiences.
Optionally, if it fits, you can give a hint where the child could observe or try something in real life (e.g., "If you're ever at the park, see if…"). This is just an extra, not the main part of the question.
Use simple, friendly language. No lists, no explanations, only one question per answer.`,
prefix: "That's a great question! Let me help you think through this step by step. Instead of telling you the answer, here are some questions to guide your thinking:"
},
de: {
systemPrompt: `Du bist ein geduldiger Lernbegleiter für Kinder (8-12 Jahre). Stelle immer nur EINE gezielte, neue Frage, die auf der letzten Antwort des Kindes aufbaut. Wiederhole dich nicht prüfe, ob du diese Frage oder eine sehr ähnliche schon gestellt hast.
Deine Fragen sollen das Kind zum Nachdenken anregen, z.B. durch Vergleiche, Gründe, Unterschiede, Gefühle oder eigene Erfahrungen.
Optional: Wenn es passt, kannst du einen Hinweis geben, wo das Kind im echten Leben etwas beobachten oder ausprobieren könnte (z.B. „Wenn du mal im Park bist, schau, ob…"). Das ist aber nur ein Zusatz, nicht der Hauptteil der Frage.
Verwende einfache, freundliche Sprache. Keine Listen, keine Erklärungen, nur eine Frage pro Antwort.`,
prefix: "Das ist eine tolle Frage! Lass mich dir helfen, Schritt für Schritt darüber nachzudenken. Anstatt dir die Antwort zu sagen, hier sind einige Fragen, die dein Denken leiten:"
}
};
// Middleware
app.use(cors());
app.use(express.json());
app.use(express.static('.'));
// Serve the main page
app.get('/', (req, res) => {
res.sendFile(path.join(__dirname, 'index.html'));
});
// API endpoint for AI-powered educational guidance
app.post('/api/ask', async (req, res) => {
const { question, language = 'en', sessionId = 'default' } = req.body;
if (!question || question.trim().length === 0) {
return res.status(400).json({
success: false,
error: 'Question is required'
});
}
try {
// Get or initialize conversation history for this session
if (!conversationHistory.has(sessionId)) {
conversationHistory.set(sessionId, []);
}
const history = conversationHistory.get(sessionId);
// Get AI-powered guidance with conversation history
const aiGuidance = await getAIGuidance(question, language, history);
// Add the initial question to history
history.push({
type: 'initial_question',
content: question,
timestamp: new Date()
});
// Keep only last 20 interactions to prevent memory bloat
if (history.length > 20) {
history.splice(0, history.length - 20);
}
res.json({
success: true,
guidance: aiGuidance,
question: question,
language: language,
sessionId: sessionId
});
} catch (error) {
console.error('AI API Error:', error);
// Fallback to rule-based guidance if AI fails
const fallbackGuidance = getFallbackGuidance(question, language);
res.json({
success: true,
guidance: fallbackGuidance,
question: question,
language: language,
fallback: true,
sessionId: sessionId
});
}
});
// API endpoint for getting the actual answer after thinking process
app.post('/api/reveal-answer', async (req, res) => {
const { question, language = 'en' } = req.body;
if (!question || question.trim().length === 0) {
return res.status(400).json({
success: false,
error: 'Question is required'
});
}
try {
// Get the actual answer from AI
const answer = await getActualAnswer(question, language);
res.json({
success: true,
answer: answer,
question: question,
language: language
});
} catch (error) {
console.error('Answer API Error:', error);
// Fallback answer
const fallbackAnswer = getFallbackAnswer(question, language);
res.json({
success: true,
answer: fallbackAnswer,
question: question,
language: language,
fallback: true
});
}
});
// Function to get OpenAI-powered educational guidance (Primary)
async function getOpenAIGuidance(question, language, conversationHistory = []) {
console.log('🤖 Calling OpenAI with GPT-4o-mini...');
const isGerman = language === 'de';
// CONTENT FILTER: Block inappropriate topics for children
const inappropriateTopics = [
'alkohol', 'alcohol', 'betrunken', 'drunk', 'besaufen',
'drogen', 'drugs', 'rauchen', 'smoking', 'zigarette',
' sex ', ' sex.', ' sex?', ' sex!', 'sexuell', 'sexual',
'gewalt', 'violence', 'töten', 'kill', 'morden', 'murder',
' tod ', ' death ', 'sterben', ' die ', ' dying',
'krieg', ' war ', 'waffen', 'weapons', 'pistole', 'gun'
];
const questionLower = ` ${question.toLowerCase()} `; // Add spaces to avoid partial matches
const isInappropriate = inappropriateTopics.some(topic => questionLower.includes(topic));
if (isInappropriate) {
console.log('🚫 Inappropriate topic detected, redirecting...');
const redirectMessage = isGerman
? 'Das ist ein interessantes Thema, aber ich helfe lieber bei Fragen über Natur, Wissenschaft und die Welt um uns herum! Hast du eine Frage über Tiere, Pflanzen, das Wetter oder wie Dinge funktionieren?'
: 'That\'s an interesting topic, but I prefer to help with questions about nature, science, and the world around us! Do you have a question about animals, plants, weather, or how things work?';
return {
question: redirectMessage,
category: 'redirection',
thinking_step: isGerman ? 'Umleitung zu geeigneten Themen' : 'Redirecting to appropriate topics'
};
}
// Extract recent questions from conversation history to prevent repetition
const recentQuestions = conversationHistory
.filter(item => item.type === 'ai_question' || item.type === 'ai_response')
.slice(-4) // Last 4 AI questions/responses
.map(item => item.content)
.join('\n');
const systemPrompt = isGerman
? `Du bist ein geduldiger Lernbegleiter für Kinder (8-12 Jahre). Dein Ansatz: FUNDAMENTALS FIRST!
NEUER ANSATZ - GRUNDLAGEN ZUERST:
Wenn ein Kind eine Frage stellt, identifiziere die 2-3 wichtigsten Grundlagen, die sie verstehen müssen, um das Thema zu begreifen.
ANTWORT-STRUKTUR:
1. Sage: "Um [Thema] zu verstehen, musst du die Grundlagen von [Grundlage 1], [Grundlage 2] und [Grundlage 3] kennen."
2. Dann: "Lass uns mit [erste Grundlage] anfangen..."
3. Stelle EINE einfache Frage zur ersten Grundlage
BEISPIELE:
- Sonnenbrand → "Um Sonnenbrand zu verstehen, musst du die Grundlagen von UV-Strahlung, Hautzellen und Lichtenergie kennen. Lass uns mit UV-Strahlung anfangen..."
- Vogel fliegt → "Um das Fliegen zu verstehen, musst du die Grundlagen von Aerodynamik, Luftdruck und Flügelform kennen. Lass uns mit Aerodynamik anfangen..."
- Sonne brennt → "Um zu verstehen wie die Sonne brennt, musst du die Grundlagen von Kernfusion, Wasserstoff und extremer Hitze kennen. Lass uns mit Kernfusion anfangen..."
STIL: Strukturiert, bildungsorientiert, systematisch. Baue Wissen Schritt für Schritt auf.`
: `You are a patient learning companion for children (8-12 years old). Your approach: FUNDAMENTALS FIRST!
NEW APPROACH - FUNDAMENTALS FIRST:
When a child asks a question, identify the 2-3 most important fundamentals they need to understand to grasp the topic.
RESPONSE STRUCTURE:
1. Say: "To understand [topic], you need to understand the basics of [fundamental 1], [fundamental 2], and [fundamental 3]."
2. Then: "Let's start with [first fundamental]..."
3. Ask ONE simple question about the first fundamental
EXAMPLES:
- Sunburn → "To understand sunburn, you need to understand the basics of UV radiation, skin cells, and light energy. Let's start with UV radiation..."
- Bird flying → "To understand flying, you need to understand the basics of aerodynamics, air pressure, and wing shape. Let's start with aerodynamics..."
- Sun burning → "To understand how the sun burns, you need to understand the basics of nuclear fusion, hydrogen, and extreme heat. Let's start with nuclear fusion..."
STYLE: Structured, educational, systematic. Build knowledge step by step.
Optionally: Only if it fits very naturally, mention a real-life situation (e.g., "If you're ever at the park..."). But this is secondary.
STYLE: Short, friendly, only ONE question. No lists, no explanations.`;
const userPrompt = isGerman
? `Ein Kind hat diese Frage gestellt: "${question}"
DEINE AUFGABE: Verwende den FUNDAMENTALS FIRST Ansatz!
1. IDENTIFIZIERE die 2-3 wichtigsten Grundlagen für dieses Thema
2. STRUKTURIERE deine Antwort so:
- "Um [Thema] zu verstehen, musst du die Grundlagen von [Grundlage 1], [Grundlage 2] und [Grundlage 3] kennen."
- "Lass uns mit [erste Grundlage] anfangen..."
- [EINE einfache Frage zur ersten Grundlage]
BEISPIELE FÜR GRUNDLAGEN:
- Sonnenbrand → UV-Strahlung, Hautzellen, Lichtenergie
- Vogel fliegen → Aerodynamik, Luftdruck, Flügelform
- Sonne brennt → Kernfusion, Wasserstoff, extreme Hitze
- Regenbogen → Lichtbrechung, Wassertropfen, Farbspektrum
- Donner → Schallwellen, Blitz, Luftexpansion
STIL: Strukturiert, bildungsorientiert, systematisch aufbauend.`
: `A child asked this question: "${question}"
YOUR TASK: Use the FUNDAMENTALS FIRST approach!
1. IDENTIFY the 2-3 most important fundamentals for this topic
2. STRUCTURE your response like this:
- "To understand [topic], you need to understand the basics of [fundamental 1], [fundamental 2], and [fundamental 3]."
- "Let's start with [first fundamental]..."
- [ONE simple question about the first fundamental]
EXAMPLES OF FUNDAMENTALS:
- Sunburn → UV radiation, skin cells, light energy
- Bird flying → Aerodynamics, air pressure, wing shape
- Sun burning → Nuclear fusion, hydrogen, extreme heat
- Rainbow → Light refraction, water droplets, color spectrum
- Thunder → Sound waves, lightning, air expansion
STYLE: Structured, educational, systematically building knowledge.
- STAYS IN THE SAME SCIENTIFIC CONTEXT as the original question
SPECIFIC EXAMPLES for SCIENTIFIC questions:
- For "Why do people get sunburn?": "What do you know about different types of sunlight?"
- For "How does the sun burn?": "What do you think is inside the sun that's different from Earth?"
- For "How do birds fly?": "What's special about bird wings that other animals don't have?"
GENERAL EXAMPLES of DIFFERENT question types:
- Sparks curiosity in a new way
- STAYS IN THE SAME SCIENTIFIC CONTEXT as the original question
EXAMPLES of DIFFERENT question types:
- Comparisons: "Is this similar to riding a bike?"
- Feelings: "What do you find most interesting about this?"
- Reasons: "Why might this work this way?"
- Imagination: "Imagine if you were a bird..."
Formulate ONLY one single, completely new question.`;
try {
const completion = await openai.chat.completions.create({
model: "gpt-4o-mini",
messages: [
{ role: "system", content: systemPrompt },
{ role: "user", content: userPrompt }
],
max_tokens: 200,
temperature: 0.7
});
const aiResponse = completion.choices[0]?.message?.content || '';
console.log('✅ OpenAI response received:', aiResponse);
console.log('🔍 Full response for debugging:', JSON.stringify(aiResponse));
// Parse the response into steps
const steps = parseOpenAIResponseToSteps(aiResponse, language);
return {
type: 'ai-powered',
steps: steps,
encouragement: getRandomEncouragement(language, question),
source: 'OpenAI GPT-4o-mini'
};
} catch (error) {
console.log('❌ OpenAI error:', error.message);
throw error;
}
}
// Function to parse OpenAI response into thinking steps
function parseOpenAIResponseToSteps(text, language) {
console.log('🔧 Parsing single question response:', text);
const trimmed = text.trim();
// For single question responses, just return the question directly
if (trimmed.length > 10 && trimmed.includes('?')) {
// Clean up any numbering that might have slipped through
const cleanQuestion = trimmed.replace(/^\d+\.\s*/, '').trim();
return [{
id: 1,
text: cleanQuestion,
type: 'question'
}];
}
// Fallback - split by lines and take the first valid question
const lines = text.split('\n').filter(line => line.trim());
for (const line of lines) {
const trimmedLine = line.trim();
if (trimmedLine.length > 10 && trimmedLine.includes('?')) {
const cleanQuestion = trimmedLine.replace(/^\d+\.\s*/, '').trim();
return [{
id: 1,
text: cleanQuestion,
type: 'question'
}];
}
}
// If no valid question found, return fallback
const isGerman = language === 'de';
return [{
id: 1,
text: isGerman
? "Was beobachtest du bei diesem Phänomen?"
: "What do you observe about this phenomenon?",
type: 'question'
}];
}
// Function to get Hugging Face guidance (Backup)
async function getHuggingFaceGuidance(question, language) {
console.log('🤖 Trying Hugging Face as backup...');
// Try each model until one works
for (let i = 0; i < HF_MODELS.length; i++) {
const model = HF_MODELS[i];
const apiUrl = `https://api-inference.huggingface.co/models/${model}`;
try {
console.log(`📡 Trying model ${i + 1}/${HF_MODELS.length}: ${model}`);
const educationalPrompt = `Help a child think about this question: "${question}".
Don't give the answer. Instead, ask 3 guiding questions that help them discover it themselves.
Format: 1. [question] 2. [question] 3. [question]`;
const response = await fetch(apiUrl, {
method: 'POST',
headers: {
'Authorization': HF_API_TOKEN ? `Bearer ${HF_API_TOKEN}` : undefined,
'Content-Type': 'application/json',
},
body: JSON.stringify({
inputs: educationalPrompt,
parameters: {
max_length: 120,
temperature: 0.8,
do_sample: true,
pad_token_id: 50256
}
})
});
if (response.ok) {
const data = await response.json();
if (data.error && data.error.includes('loading')) {
console.log(`⏳ Model ${model} is loading, trying next...`);
continue;
}
if (data.error) {
console.log(`❌ Model ${model} error:`, data.error);
continue;
}
console.log(`✅ Success with model ${model}`);
let aiText = data[0]?.generated_text || '';
if (aiText.trim().length > 10) {
const steps = parseHuggingFaceResponseToSteps(aiText, language);
return {
type: 'ai-powered',
steps: steps,
encouragement: getRandomEncouragement(language, question),
source: `Hugging Face (${model.split('/')[1]})`
};
}
}
} catch (error) {
console.log(`⚠️ Model ${model} failed:`, error.message);
continue;
}
}
throw new Error('All Hugging Face models are currently unavailable');
}
// Function to parse Hugging Face response into thinking steps
function parseHuggingFaceResponseToSteps(text, language) {
const lines = text.split('\n').filter(line => line.trim());
const steps = [];
lines.forEach((line, index) => {
const trimmed = line.trim();
if (trimmed && trimmed.length > 10) {
const cleaned = trimmed.replace(/^\d+\.\s*/, '').replace(/^-\s*/, '');
if (cleaned.length > 5) {
steps.push({
id: index + 1,
text: cleaned,
type: 'question'
});
}
}
});
if (steps.length < 2) {
const fallback = getFallbackGuidance('', language);
return fallback.steps.slice(0, 3);
}
return steps.slice(0, 4);
}
// Updated main function that tries OpenAI first, then Hugging Face, then local fallback
async function getAIGuidance(question, language, conversationHistory = []) {
// Try OpenAI first (most reliable)
if (process.env.OPENAI_API_KEY) {
try {
return await getOpenAIGuidance(question, language, conversationHistory);
} catch (error) {
console.log('⚠️ OpenAI failed, trying Hugging Face...');
}
}
// Try Hugging Face as backup
if (process.env.HUGGING_FACE_TOKEN) {
try {
return await getHuggingFaceGuidance(question, language);
} catch (error) {
console.log('⚠️ Hugging Face failed, using local fallback...');
}
}
// Final fallback - this will throw error to trigger local guidance
throw new Error('All AI services are currently unavailable');
}
// Fallback guidance for when AI is unavailable
function getFallbackGuidance(question, language) {
const isGerman = language === 'de';
const questionLower = question.toLowerCase();
// Topic-specific guided questions like in the image
// All questions now use AI-powered guidance or generic scientific thinking questions
// No more hard-coded topic-specific questions
// Generic fallback questions that promote scientific thinking (one at a time)
const fallbackSteps = isGerman ? [
{ id: 1, text: "Was hast du bereits über dieses Thema beobachtet oder erfahren?", type: 'question' }
] : [
{ id: 1, text: "What have you already observed or learned about this topic?", type: 'question' }
];
return {
type: 'rule-based',
steps: fallbackSteps,
encouragement: getRandomEncouragement(language, question),
source: 'Educational Framework'
};
}
// Get contextual encouragement based on question content
function getRandomEncouragement(language, question = '') {
const questionLower = question.toLowerCase();
// Contextual encouragements based on question topic
let encouragements;
if (questionLower.includes('pneumatic') || questionLower.includes('air hammer')) {
encouragements = language === 'de' ? [
"Faszinierende Frage über pneumatische Werkzeuge! 🔧",
"Du denkst wie ein Ingenieur! Lass uns das erforschen! ⚙️",
"Großartig! Mechanische Systeme sind spannend! 🛠️"
] : [
"Fascinating question about pneumatic tools! 🔧",
"You're thinking like an engineer! Let's explore this! ⚙️",
"Great! Mechanical systems are exciting! 🛠️"
];
} else if (questionLower.includes('bicycle') || questionLower.includes('bike')) {
encouragements = language === 'de' ? [
"Perfekt! Fahrräder sind großartige Lernwerkzeuge! 🚴‍♂️",
"Ausgezeichnet! Du verstehst mechanische Systeme! ⚙️"
] : [
"Perfect! Bicycles are great learning tools! 🚴‍♂️",
"Excellent! You understand mechanical systems! ⚙️"
];
} else if (questionLower.includes('car') || questionLower.includes('automobile')) {
encouragements = language === 'de' ? [
"Interessant! Autos haben faszinierende Mechanismen! <20>",
"Du erforschst komplexe Systeme! 🔧"
] : [
"Interesting! Cars have fascinating mechanisms! 🚗",
"You're exploring complex systems! 🔧"
];
} else {
// General encouragements (more varied and less generic)
encouragements = language === 'de' ? [
"Interessante Frage! Lass uns das erforschen! <20>",
"Du denkst wie ein echter Forscher! 🔬",
"Fantastisch! Lass uns das zusammen herausfinden! 🚀",
"Neugier ist der Schlüssel zum Lernen! 🌟"
] : [
"Interesting question! Let's explore this! 🔍",
"You're thinking like a real researcher! 🔬",
"Fantastic! Let's figure this out together! 🚀",
"Curiosity is the key to learning! 🌟"
];
}
return encouragements[Math.floor(Math.random() * encouragements.length)];
}
// Health check endpoint
app.get('/api/health', (req, res) => {
res.json({
status: 'healthy',
timestamp: new Date().toISOString(),
ai_services: {
openai: !!process.env.OPENAI_API_KEY,
huggingface: !!process.env.HUGGING_FACE_TOKEN
}
});
});
app.listen(PORT, () => {
console.log(`🚀 KidsAI Explorer server running on port ${PORT}`);
console.log(`📖 Visit http://localhost:${PORT} to start exploring!`);
console.log(`🤖 AI Services: OpenAI=${!!process.env.OPENAI_API_KEY}, HuggingFace=${!!process.env.HUGGING_FACE_TOKEN}`);
});
module.exports = app;
// Function to get the actual answer (for answer reveal)
async function getActualAnswer(question, language) {
console.log('📝 Getting actual answer for:', question);
const isGerman = language === 'de';
const systemPrompt = isGerman
? "Du bist ein wissenschaftlich versierter Pädagoge für Kinder. Gib immer wissenschaftlich korrekte, aber sehr kurze und kindgerechte Antworten (maximal 3-4 Sätze). Erkläre die grundlegenden wissenschaftlichen Prinzipien mit einfachen Worten und alltäglichen Vergleichen. Verwende konkrete Beispiele und lade zum Experimentieren ein. Wichtig: Sei wissenschaftlich präzise, aber verwende Sprache, die ein 8-12 Jahre altes Kind versteht. Halte die Antwort kurz und ermutigend."
: "You are a scientifically knowledgeable educator for children. Always provide scientifically accurate but very brief and child-friendly answers (maximum 3-4 sentences). Explain fundamental scientific principles using simple words and everyday comparisons. Use concrete examples and invite experimentation. Important: Be scientifically precise, but use language an 8-12 year old child can understand. Keep the answer short and encouraging.";
const userPrompt = isGerman
? `Ein neugieriges Kind möchte wissen: "${question}". Gib eine wissenschaftlich korrekte, aber sehr kurze und einfache Erklärung (maximal 3-4 Sätze) mit einem praktischen Beispiel.`
: `A curious child wants to know: "${question}". Provide a scientifically accurate but very brief and simple explanation (maximum 3-4 sentences) with a practical example.`;
try {
if (process.env.OPENAI_API_KEY) {
const completion = await openai.chat.completions.create({
model: "gpt-4o-mini",
messages: [
{ role: "system", content: systemPrompt },
{ role: "user", content: userPrompt }
],
max_tokens: 80, // Much shorter responses for children
temperature: 0.3 // Lower temperature for more factual answers
});
const answer = completion.choices[0]?.message?.content || '';
console.log('✅ Answer received from OpenAI');
return {
type: 'ai-powered',
text: answer.trim(),
source: 'OpenAI GPT-4o-mini'
};
}
} catch (error) {
console.log('❌ OpenAI answer error:', error.message);
throw error;
}
}
// Fallback answers for common questions
function getFallbackAnswer(question, language) {
const isGerman = language === 'de';
// Generic fallback that encourages scientific thinking
return {
type: 'rule-based',
text: isGerman
? "Das ist eine faszinierende wissenschaftliche Frage! Die Antwort hängt von verschiedenen Faktoren ab. Ich empfehle dir, zu beobachten, zu experimentieren und in wissenschaftlichen Büchern nachzuschauen. Du könntest auch einen Erwachsenen fragen, der sich mit diesem Thema auskennt. Die besten Entdeckungen macht man durch eigenes Forschen! 🔬"
: "That's a fascinating scientific question! The answer depends on various factors. I recommend observing, experimenting, and looking it up in scientific books. You could also ask an adult who knows about this topic. The best discoveries come from doing your own research! 🔬",
source: 'Educational Framework'
};
}
// API endpoint for responding to user answers contextually
app.post('/api/respond-to-answer', async (req, res) => {
const {
answer,
question,
originalTopic,
language = 'en',
sessionId = 'default',
context,
stepIndex,
instructions
} = req.body;
if (!answer || answer.trim().length === 0) {
return res.status(400).json({
success: false,
error: 'Answer is required'
});
}
try {
// Get or initialize conversation history for this session
if (!conversationHistory.has(sessionId)) {
conversationHistory.set(sessionId, []);
}
const history = conversationHistory.get(sessionId);
// Add current interaction to history
history.push({
type: 'ai_question',
content: question,
timestamp: new Date()
});
history.push({
type: 'user_answer',
content: answer,
timestamp: new Date()
});
// Keep only last 10 interactions to prevent memory bloat
if (history.length > 10) {
history.splice(0, history.length - 10);
}
// Get AI response with conversation context
const aiResponse = await getConversationResponse(
answer,
question,
originalTopic,
language,
history,
context,
instructions
);
// Add AI response to history
history.push({
type: 'ai_response',
content: aiResponse,
timestamp: new Date()
});
res.json({
success: true,
response: aiResponse,
sessionId: sessionId
});
} catch (error) {
console.error('Conversation API Error:', error);
// Fallback response
const fallbackResponse = language === 'de'
? 'Das ist eine interessante Überlegung! Lass uns weiter darüber nachdenken.'
: 'That\'s an interesting thought! Let\'s continue thinking about this.';
res.json({
success: true,
response: fallbackResponse,
fallback: true
});
}
});
// API endpoint for deeper exploration of current topic
app.post('/api/explore-deeper', async (req, res) => {
try {
const { question, userAnswer, language = 'en', context, instructions } = req.body;
if (!question) {
return res.status(400).json({
success: false,
error: 'Question is required for deeper exploration'
});
}
console.log(`🔍 Generating deeper exploration for: "${question}" with user answer: "${userAnswer}"`);
const isGerman = language === 'de';
// Try OpenAI first for contextual deeper exploration
if (process.env.OPENAI_API_KEY) {
try {
const systemPrompt = isGerman
? "Du bist ein neugieriger Lernbegleiter für Kinder. Ein Kind hat gerade eine Antwort auf eine Frage gegeben. Deine Aufgabe ist es, eine faszinierende Folgefrage zu stellen, die das Kind dazu bringt, noch tiefer über das Thema nachzudenken. GEBE NIEMALS DIREKTE ANTWORTEN! Stelle stattdessen eine einzige, durchdachte Frage, die das Kind zu weiterer Entdeckung einlädt. Verwende Sätze wie 'Hast du schon mal bemerkt...?', 'Was würde passieren, wenn...?', 'Was denkst du, warum...?' oder 'Kannst du dir vorstellen...?'. Ermutige zur Beobachtung und zum eigenen Experimentieren."
: "You are a curious learning companion for children. A child just gave an answer to a question. Your task is to ask a fascinating follow-up question that makes the child think even deeper about the topic. NEVER give direct answers! Instead, ask one thoughtful question that invites the child to further discovery. Use phrases like 'Have you ever noticed...?', 'What would happen if...?', 'What do you think would...?' or 'Can you imagine...?'. Encourage observation and hands-on exploration.";
const userPrompt = isGerman
? `URSPRÜNGLICHE FRAGE: "${question}"
ANTWORT DES KINDES: "${userAnswer}"
Stelle eine durchdachte Folgefrage, die das Kind dazu einlädt, das Thema tiefer zu erforschen. Die Frage sollte neugierig machen und zur eigenen Beobachtung ermutigen. Gib KEINE Antworten - nur eine einzige, faszinierende Frage.`
: `ORIGINAL QUESTION: "${question}"
CHILD'S ANSWER: "${userAnswer}"
Ask one thoughtful follow-up question that invites the child to explore the topic deeper. The question should spark curiosity and encourage hands-on observation. Give NO answers - just one fascinating question.`;
const completion = await openai.chat.completions.create({
model: "gpt-4o-mini",
messages: [
{ role: "system", content: systemPrompt },
{ role: "user", content: userPrompt }
],
max_tokens: 60, // Shorter questions for children
temperature: 0.8
});
const aiResponse = completion.choices[0]?.message?.content?.trim();
if (aiResponse && aiResponse.length > 10) {
console.log('✅ OpenAI deeper exploration generated successfully');
return res.json({
success: true,
response: aiResponse,
source: 'OpenAI GPT-4o-mini Deeper Exploration'
});
}
} catch (openaiError) {
console.log('❌ OpenAI error for deeper exploration:', openaiError.message);
}
}
// Fallback: Generate contextual deeper exploration prompts based on the topic
const deeperExplorationPrompts = isGerman ? [
`🔬 Spannend! Hast du schon mal versucht, das selbst zu beobachten? Was würdest du erwarten zu sehen?`,
`💡 Das ist eine gute Überlegung! Was denkst du, würde passieren, wenn du das Experiment zu Hause nachmachen würdest?`,
`🌟 Interessant! Kannst du dir vorstellen, wo du so etwas in der Natur noch beobachten könntest?`,
`<EFBFBD> Du denkst wirklich gut nach! Hast du schon mal bemerkt, ob das bei verschiedenen Wetterbedingungen anders ist?`,
`🔍 Das ist ein großartiger Punkt! Was würde passieren, wenn du versuchst, das mit verschiedenen Materialien zu testen?`
] : [
`🔬 Fascinating! Have you ever tried observing this yourself? What would you expect to see?`,
`💡 That's good thinking! What do you think would happen if you tried this experiment at home?`,
`🌟 Interesting! Can you imagine where else you might observe this in nature?`,
`🎯 You're really thinking well! Have you ever noticed if this looks different in various weather conditions?`,
`🔍 That's a great point! What would happen if you tried testing this with different materials?`
];
// Select a random contextual deeper exploration prompt
const randomPrompt = deeperExplorationPrompts[Math.floor(Math.random() * deeperExplorationPrompts.length)];
res.json({
success: true,
response: randomPrompt,
source: 'Contextual Deeper Exploration Fallback',
context: 'exploration'
});
} catch (error) {
console.error('❌ Error in explore-deeper:', error);
res.status(500).json({
success: false,
error: 'Failed to generate deeper exploration'
});
}
});
// New function to handle conversation responses with question tracking
async function getConversationResponse(answer, question, originalTopic, language, conversationHistory = [], context, instructions) {
console.log('🗣️ Generating conversation response with context:', context);
const isGerman = language === 'de';
// Extract recent AI questions to avoid repetition
const recentQuestions = conversationHistory
.filter(item => item.type === 'ai_question')
.slice(-3) // Last 3 questions
.map(item => item.content)
.join('\n');
// Extract recent user answers to understand the conversation flow
const recentAnswers = conversationHistory
.filter(item => item.type === 'user_answer')
.slice(-3) // Last 3 answers
.map(item => item.content)
.join('\n');
// Check if this is the third "don't know" in a row
const dontKnowCount = conversationHistory
.filter(item => item.type === 'user_answer')
.slice(-3)
.filter(item => {
const content = item.content.toLowerCase();
return content.includes('weiß nicht') || content.includes('weiss nicht') ||
content.includes('weis nicht') || content.includes("don't know") ||
content.includes('keine ahnung') || content.includes('was das bedeutet') ||
content.includes('was genau passiert') || content.includes('das verstehe ich nicht') ||
content.includes('nein') || content.includes('no') || content.includes('nö');
}).length;
// NEW: Check for repeated similar questions from AI (indicating we're stuck on a fundamental)
const recentAIQuestions = conversationHistory
.filter(item => item.type === 'ai_question')
.slice(-3)
.map(item => item.content.toLowerCase());
// Check if AI is asking similar questions about the same concept
const similarQuestionPatterns = [
['uv', 'strahlung', 'wie', 'was'],
['warum', 'schädlich', 'gefährlich'],
['vogel', 'fliegen', 'flügel'],
['sonne', 'brennt', 'heiß']
];
const isStuckOnFundamental = recentAIQuestions.length >= 2 &&
recentAIQuestions.some((q1, i) =>
recentAIQuestions.slice(i + 1).some(q2 =>
similarQuestionPatterns.some(pattern =>
pattern.every(word => q1.includes(word) && q2.includes(word))
)
)
);
// Check if child has reached knowledge limit
const hasReachedKnowledgeLimit = dontKnowCount >= 1 ||
isStuckOnFundamental ||
// Special case: immediate "keine ahnung" or similar on first question
(conversationHistory.length <= 2 && dontKnowCount >= 1);
// Check for disengagement signals
const disengagementPhrases = isGerman ? [
'nix besonderes', 'nichts besonderes', 'nix', 'nichts', 'langweilig',
'interessiert mich nicht', 'egal', 'keine lust', 'ist mir egal',
'was sollte ich denn sehen', 'weiß nicht was', 'sehe nichts'
] : [
'nothing special', 'nothing', 'boring', 'dont care', "don't care",
'not interested', 'whatever', 'no interest', 'what should i see',
"don't know what", "see nothing"
];
const isDisengaged = disengagementPhrases.some(phrase =>
answer.toLowerCase().includes(phrase)
);
// Check if child is giving very short answers repeatedly
const recentShortAnswers = conversationHistory
.filter(item => item.type === 'user_answer')
.slice(-3)
.filter(item => item.content.trim().length < 10).length;
const isGivingShortAnswers = recentShortAnswers >= 2;
// Check for humor/creative answers
const humorIndicators = isGerman ? [
'per fax', 'mit dem fax', 'faxen', 'per telefon', 'anrufen', 'brief schreiben',
'post schicken', 'email senden', 'whatsapp', 'telegram', 'instagram', 'tiktok',
'mit der hand winken', 'winken', 'schreien', 'rufen', 'brüllen',
'mit einem megafon', 'megafon', 'lautsprecher', 'trompete', 'glocke',
'rauchzeichen', 'signalfeuer', 'fahne schwenken', 'flagge', 'morse',
'brieftaube', 'taube', 'raven', 'eule', 'harry potter',
'magisch', 'zauberei', 'hexerei', 'telepathie', 'gedanken lesen',
'farben', 'regenbogen', 'bunt', 'pink', 'lila', 'türkis'
] : [
'by fax', 'fax it', 'call them', 'phone call', 'write a letter',
'send mail', 'email them', 'whatsapp', 'telegram', 'instagram', 'tiktok',
'wave hands', 'wave', 'shout', 'yell', 'scream',
'megaphone', 'loudspeaker', 'trumpet', 'bell',
'smoke signals', 'signal fire', 'wave flag', 'flag', 'morse code',
'carrier pigeon', 'pigeon', 'raven', 'owl', 'harry potter',
'magic', 'wizardry', 'witchcraft', 'telepathy', 'mind reading',
'colors', 'rainbow', 'colorful', 'pink', 'purple', 'turquoise'
];
// Check for single-word creative answers (often all caps or unusual)
const isSingleWordCreative = answer.trim().length < 15 &&
(answer === answer.toUpperCase() ||
/^[A-ZÄÖÜ]+!*$/.test(answer.trim()) ||
['farben', 'colors', 'regenbogen', 'rainbow', 'musik', 'music', 'tanzen', 'dancing'].includes(answer.toLowerCase().trim()));
const isHumorousAnswer = humorIndicators.some(indicator =>
answer.toLowerCase().includes(indicator)
) || isSingleWordCreative;
// Check for emotional/frustrated responses
const frustrationIndicators = isGerman ? [
'ist doof', 'ist blöd', 'ist dumm', 'verstehe nicht', 'versteh nicht',
'ist langweilig', 'nervt', 'ist nervig', 'hasse', 'mag nicht',
'will nicht', 'keine lust', 'ist schwer', 'ist zu schwer', 'ist kompliziert',
'macht keinen sinn', 'ist verwirrend', 'kapiere nicht', 'kapier nicht',
'ist unfair', 'ist gemein', 'warum muss ich', 'will aufhören',
'bin müde', 'bin genervt', 'ist anstrengend', 'zu viele fragen',
'drehen uns im kreis', 'drehen sich im kreis', 'im kreis', 'freundchen',
'langweilig', 'öde', 'öd', 'lahm', 'stupide'
] : [
'is stupid', 'is dumb', 'dont understand', "don't understand", 'dont get it', "don't get it",
'is boring', 'annoying', 'hate this', 'dont like', "don't like",
'dont want', "don't want", 'no point', 'is hard', 'too hard', 'complicated',
'makes no sense', 'confusing', 'dont get', "don't get",
'unfair', 'mean', 'why do i have to', 'want to stop',
'tired', 'annoyed', 'exhausting', 'too many questions',
'going in circles', 'round and round', 'circles', 'buddy', 'pal',
'boring', 'tedious', 'lame', 'stupid'
];
const isEmotionalResponse = frustrationIndicators.some(indicator =>
answer.toLowerCase().includes(indicator)
);
// Check for "already said that" responses
const repetitionIndicators = isGerman ? [
'hab ich schon gesagt', 'habe ich schon gesagt', 'schon gesagt',
'hab ich doch gesagt', 'habe ich doch gesagt', 'das sagte ich schon',
'wiederholung', 'immer das gleiche', 'hab ich schon erwähnt',
'nochmal das gleiche', 'schon mal gesagt', 'bereits gesagt'
] : [
'already said', 'i said that', 'told you that', 'mentioned that',
'said before', 'repeating', 'same thing', 'already told you',
'i already answered', 'answered that'
];
const isPointingOutRepetition = repetitionIndicators.some(indicator =>
answer.toLowerCase().includes(indicator)
);
// Check for concerning or inappropriate responses that need sensitive handling
const concerningIndicators = isGerman ? [
'schläge', 'tritte', 'schlagen', 'treten', 'hauen', 'prügeln',
'beleidigung', 'beleidigen', 'beschimpfen', 'mobbing', 'gewalt',
'verletzt', 'wehtun', 'schmerzen', 'böse worte', 'gemein sein',
'hass', 'hassen', 'töten', 'sterben', 'tot', 'umbringen',
'schimpfwörter', 'fluchen'
] : [
'hitting', 'kicking', 'punching', 'fighting', 'violence', 'hurt',
'insults', 'bullying', 'mean words', 'bad words', 'swearing',
'hate', 'kill', 'die', 'death', 'cursing', 'abuse', 'harm', 'pain'
];
const isConcerningResponse = concerningIndicators.some(indicator =>
answer.toLowerCase().includes(indicator)
);
let systemPrompt, userPrompt;
if (isHumorousAnswer) {
// Child gave a humorous/creative answer - acknowledge it playfully but redirect to learning
systemPrompt = isGerman
? `Du bist ein geduldiger Lernbegleiter. Ein Kind hat eine humorvolle oder kreative Antwort gegeben und braucht spielerische Anerkennung plus Umleitung zum Lernen.
AUFGABE:
1. Erkenne den Humor/Kreativität an: "Das ist ja lustig!" oder "Kreative Idee!"
2. Mache eine spielerische Verbindung: "Mit [humorvolle Antwort] wäre das wirklich interessant!"
3. Leite sanft zurück: "Aber lass uns schauen, wie es wirklich funktioniert..."
4. Stelle EINE einfache Frage zur aktuellen Grundlage
BEISPIEL: "Haha, per Fax wäre wirklich lustig! Mit einem Fax könnten Vögel bestimmt keine Nachrichten senden. Aber lass uns schauen, wie sie wirklich miteinander kommunizieren. Was hörst du, wenn Vögel 'sprechen'?"
STIL: Spielerisch, anerkennend, aber zurück zum Lernen führend.`
: `You are a patient learning companion. A child gave a humorous/creative answer and needs playful acknowledgment plus redirection to learning.
TASK:
1. Acknowledge the humor/creativity: "That's funny!" or "Creative idea!"
2. Make a playful connection: "With [humorous answer] that would be really interesting!"
3. Gently redirect: "But let's see how it really works..."
4. Ask ONE simple question about the current fundamental
EXAMPLE: "Haha, by fax would be really funny! Birds definitely couldn't send messages with a fax machine. But let's see how they really communicate with each other. What do you hear when birds 'talk'?"
STYLE: Playful, acknowledging, but leading back to learning.`;
userPrompt = isGerman
? `Ein Kind hat eine humorvolle Antwort gegeben.
Frage: "${question}"
Humorvolle Antwort: "${answer}"
Ursprüngliches Thema: "${originalTopic}"
Erkenne den Humor an und leite sanft zurück zum Lernen der aktuellen Grundlage.`
: `A child gave a humorous answer.
Question: "${question}"
Humorous answer: "${answer}"
Original topic: "${originalTopic}"
Acknowledge the humor and gently redirect back to learning the current fundamental.`;
} else if (isConcerningResponse) {
// Child mentioned violence, inappropriate content, or concerning behavior
systemPrompt = isGerman
? `Du bist ein geduldiger Lernbegleiter. Ein Kind hat etwas erwähnt, das beunruhigend oder unangemessen ist und braucht sanfte Umleitung.
AUFGABE:
1. Reagiere NICHT schockiert oder wertend
2. Sage ruhig: "Das ist nicht das, worüber wir sprechen..."
3. Leite sanft um: "Lass uns über [positives Thema] sprechen..."
4. Stelle eine einfache, positive Frage
BEISPIEL für Gewalt: "Das ist nicht das, worüber wir sprechen. Lass uns über freundliche Kommunikation sprechen. Was sind schöne Wege, um jemandem zu zeigen, dass man ihn mag?"
BEISPIEL für Beleidigungen: "Das ist nicht das, worüber wir sprechen. Lass uns über nette Worte sprechen. Welche freundlichen Worte hörst du gerne?"
WICHTIG: Ruhig bleiben, nicht schimpfen, positive Alternativen anbieten.`
: `You are a patient learning companion. A child mentioned something concerning or inappropriate and needs gentle redirection.
TASK:
1. Do NOT react shocked or judgmental
2. Say calmly: "That's not what we're talking about..."
3. Gently redirect: "Let's talk about [positive topic]..."
4. Ask a simple, positive question
EXAMPLE for violence: "That's not what we're talking about. Let's talk about kind communication. What are nice ways to show someone you like them?"
EXAMPLE for insults: "That's not what we're talking about. Let's talk about nice words. What friendly words do you like to hear?"
IMPORTANT: Stay calm, don't scold, offer positive alternatives.`;
userPrompt = isGerman
? `Ein Kind hat etwas Beunruhigendes erwähnt.
Frage: "${question}"
Beunruhigende Antwort: "${answer}"
Ursprüngliches Thema: "${originalTopic}"
Leite sanft zu einem positiven Aspekt des Themas um, ohne zu schimpfen.`
: `A child mentioned something concerning.
Question: "${question}"
Concerning answer: "${answer}"
Original topic: "${originalTopic}"
Gently redirect to a positive aspect of the topic without scolding.`;
} else if (isEmotionalResponse) {
// Child is expressing frustration or strong emotions
systemPrompt = isGerman
? `Du bist ein geduldiger Lernbegleiter. Ein Kind drückt Frustration oder starke Emotionen aus und braucht emotionale Unterstützung.
AUFGABE:
1. Validiere die Gefühle: "Das kann ich verstehen..." oder "Manchmal ist das wirklich so..."
2. Zeige Empathie: "Es ist völlig okay, wenn..."
3. Biete Unterstützung: "Wir können es anders versuchen..."
4. Vereinfache den Ansatz: Stelle eine SEHR einfache, beruhigende Frage
BEISPIEL: "Das kann ich verstehen - manchmal sind neue Sachen wirklich verwirrend! Es ist völlig okay, wenn es am Anfang schwer ist. Wir können es anders versuchen. Schau einfach mal um dich - siehst du gerade einen Vogel oder hörst du einen?"
STIL: Beruhigend, validierend, vereinfachend, ohne Druck.`
: `You are a patient learning companion. A child is expressing frustration or strong emotions and needs emotional support.
TASK:
1. Validate feelings: "I can understand that..." or "Sometimes that really is..."
2. Show empathy: "It's completely okay if..."
3. Offer support: "We can try it differently..."
4. Simplify approach: Ask a VERY simple, calming question
EXAMPLE: "I can understand that - sometimes new things are really confusing! It's completely okay if it's hard at first. We can try it differently. Just look around you - do you see a bird or hear one right now?"
STYLE: Calming, validating, simplifying, without pressure.`;
userPrompt = isGerman
? `Ein Kind drückt Frustration aus.
Frage: "${question}"
Emotionale Antwort: "${answer}"
Ursprüngliches Thema: "${originalTopic}"
Biete emotionale Unterstützung und vereinfache den Lernansatz.`
: `A child is expressing frustration.
Question: "${question}"
Emotional response: "${answer}"
Original topic: "${originalTopic}"
Provide emotional support and simplify the learning approach.`;
} else if (isPointingOutRepetition) {
// Child is pointing out that they already said something
systemPrompt = isGerman
? `Du bist ein geduldiger Lernbegleiter. Ein Kind weist darauf hin, dass es etwas bereits gesagt hat.
AUFGABE:
1. Entschuldige dich: "Du hast recht, das hast du schon gesagt!"
2. Anerkenne die Wiederholung: "Danke, dass du mich daran erinnerst!"
3. Baue auf der vorherigen Antwort auf: "Du hattest gesagt [vorherige Antwort]..."
4. Stelle eine NEUE, weiterführende Frage
STIL: Entschuldigend, dankbar, aufbauend.`
: `You are a patient learning companion. A child is pointing out that they already said something.
TASK:
1. Apologize: "You're right, you already said that!"
2. Acknowledge repetition: "Thanks for reminding me!"
3. Build on previous answer: "You had said [previous answer]..."
4. Ask a NEW, advancing question
STYLE: Apologetic, grateful, building.`;
userPrompt = isGerman
? `Ein Kind weist auf Wiederholung hin.
Aktuelle Frage: "${question}"
Antwort: "${answer}"
Bisherige Antworten: ${recentAnswers || 'Keine verfügbar'}
Entschuldige dich und stelle eine neue, weiterführende Frage.`
: `A child is pointing out repetition.
Current question: "${question}"
Answer: "${answer}"
Previous answers: ${recentAnswers || 'None available'}
Apologize and ask a new, advancing question.`;
} else if (instructions) {
// Special instructions provided (e.g., for explanations)
systemPrompt = instructions;
userPrompt = isGerman
? `Bitte erkläre dies kindgerecht für ein 8-12-jähriges Kind: "${question}"
Kontext: Das Kind hat geantwortet: "${answer}"
Ursprüngliche Frage: "${originalTopic}"`
: `Please explain this in a child-friendly way for an 8-12 year old: "${question}"
Context: The child answered: "${answer}"
Original question: "${originalTopic}"`;
} else if (hasReachedKnowledgeLimit) {
// NEW: Child has reached knowledge limit on current fundamental - provide explanation and move to next
systemPrompt = isGerman
? `Du bist ein geduldiger Lernbegleiter. Ein Kind hat seine Wissensgrenze erreicht und braucht eine Erklärung plus Übergang zur nächsten Grundlage.
AUFGABE:
1. Kurze Anerkennung: "Das ist völlig in Ordnung!"
2. KURZE aber vollständige Erklärung in 3-4 Sätzen
3. Übergang: "Jetzt verstehen wir [Grundlage]! Zur nächsten Grundlage: [nächste]..."
4. EINE Frage zur nächsten Grundlage
BEISPIEL: "Das ist völlig in Ordnung! Das Magnetfeld der Erde ist wie ein riesiger Schutzschild aus magnetischen Kräften um unseren Planeten. Es entsteht durch flüssiges Eisen im Erdkern und schützt uns vor gefährlichen Teilchen aus dem Weltraum. Jetzt verstehen wir das Magnetfeld! Zur nächsten Grundlage: Sonnenwind. Was denkst du, was Sonnenwind ist?"
WICHTIG: Vollständig aber prägnant halten!`
: `You are a patient learning companion. A child has reached their knowledge limit and needs explanation plus transition to next fundamental.
TASK:
1. Brief acknowledgment: "That's perfectly okay!"
2. BRIEF but complete explanation in 3-4 sentences
3. Transition: "Now we understand [fundamental]! Next fundamental: [next]..."
4. ONE question about next fundamental
EXAMPLE: "That's perfectly okay! Earth's magnetic field is like a giant protective shield of magnetic forces around our planet. It's created by liquid iron in Earth's core and protects us from dangerous particles from space. Now we understand the magnetic field! Next fundamental: solar wind. What do you think solar wind is?"
IMPORTANT: Complete but concise!`;
userPrompt = isGerman
? `Ein Kind hat seine Wissensgrenze bei einer Grundlage erreicht.
Aktuelle Frage: "${question}"
Antwort des Kindes: "${answer}"
Ursprüngliches Thema: "${originalTopic}"
Bisherige Fragen: ${recentQuestions || 'Keine'}
ERKLÄRE die aktuelle Grundlage KURZ und GEHE ZUR NÄCHSTEN GRUNDLAGE ÜBER.
Beispiel für Sonnenbrand:
- Erste Grundlage (UV-Strahlung) erklären → Zur zweiten Grundlage (Hautzellen) übergehen
- Zweite Grundlage (Hautzellen) erklären → Zur dritten Grundlage (Lichtenergie) übergehen`
: `A child has reached their knowledge limit on a fundamental.
Current question: "${question}"
Child's answer: "${answer}"
Original topic: "${originalTopic}"
Previous questions: ${recentQuestions || 'None'}
EXPLAIN the current fundamental BRIEFLY and MOVE TO THE NEXT FUNDAMENTAL.
Example for sunburn:
- First fundamental (UV radiation) explain → Move to second fundamental (skin cells)
- Second fundamental (skin cells) explain → Move to third fundamental (light energy)`;
} else if (context === 'repeated_dont_know' || dontKnowCount >= 2) {
// Child has said "don't know" multiple times - provide explanation
systemPrompt = isGerman
? `Du bist ein geduldiger Lernbegleiter. Ein Kind hat mehrmals "weiß nicht" gesagt und braucht jetzt eine klare, verständliche Erklärung.
AUFGABE: Gib eine vollständige, aber kindgerechte Erklärung, die:
1. Altersgerecht ist (8-12 Jahre)
2. Mit einfachen Worten erklärt
3. Alltagsbeispiele verwendet
4. Neugier für weitere Entdeckungen weckt
5. Ermutigend und positiv ist
STIL: Freundlich, klar, mit konkreten Beispielen. Verwende "Du" und mache es persönlich.`
: `You are a patient learning companion. A child has said "don't know" multiple times and now needs a clear, understandable explanation.
TASK: Give a complete but child-friendly explanation that:
1. Is age-appropriate (8-12 years)
2. Uses simple words
3. Uses everyday examples
4. Sparks curiosity for further discoveries
5. Is encouraging and positive
STYLE: Friendly, clear, with concrete examples. Use "you" and make it personal.`;
userPrompt = isGerman
? `Ein Kind hat mehrmals "weiß nicht" gesagt bei der Frage: "${question}"
Letzte Antwort: "${answer}"
Ursprüngliches Thema: "${originalTopic}"
Bisherige Fragen: ${recentQuestions || 'Keine'}
Erkläre jetzt freundlich und verständlich, worum es geht.`
: `A child has said "don't know" multiple times to the question: "${question}"
Latest answer: "${answer}"
Original topic: "${originalTopic}"
Previous questions: ${recentQuestions || 'None'}
Now explain friendly and understandably what this is about.`;
} else if (context === 'confusion_explanation') {
// Child is confused and needs clarification
systemPrompt = isGerman
? `Du bist ein geduldiger Lernbegleiter. Ein Kind ist verwirrt und braucht eine klare, beruhigende Erklärung.
AUFGABE: Gib eine beruhigende Erklärung, die Verwirrung auflöst:
1. Beginne mit Verständnis für die Verwirrung
2. Erkläre einfach und klar
3. Verwende bekannte Vergleiche
4. Baue das Vertrauen des Kindes wieder auf
5. Lade zu weiteren Fragen ein
STIL: Beruhigend, verständnisvoll, ermutigend.`
: `You are a patient learning companion. A child is confused and needs a clear, reassuring explanation.
TASK: Give a reassuring explanation that resolves confusion:
1. Start with understanding for the confusion
2. Explain simply and clearly
3. Use familiar comparisons
4. Rebuild the child's confidence
5. Invite further questions
STYLE: Reassuring, understanding, encouraging.`;
userPrompt = isGerman
? `Ein Kind ist verwirrt bei der Frage: "${question}"
Antwort des Kindes: "${answer}"
Ursprüngliches Thema: "${originalTopic}"
Hilf dem Kind, die Verwirrung zu überwinden.`
: `A child is confused about the question: "${question}"
Child's answer: "${answer}"
Original topic: "${originalTopic}"
Help the child overcome the confusion.`;
} else if (isDisengaged || isGivingShortAnswers) {
// Child is disengaged - change approach but STAY ON TOPIC
systemPrompt = isGerman
? `Du bist ein geduldiger Lernbegleiter. Ein Kind zeigt Desinteresse beim aktuellen Thema und braucht einen völlig neuen Ansatz - aber BLEIBE beim GLEICHEN Thema!
WICHTIG: WECHSLE NICHT das Thema, sondern nur den ANSATZ!
AUFGABE: Verändere deinen Ansatz komplett, aber bleibe beim ursprünglichen Thema:
1. Erkenne das Desinteresse an ("Das kann ich verstehen...")
2. Bleibe beim GLEICHEN Thema "${originalTopic}", aber probiere einen völlig anderen Ansatz:
- Persönliche Verbindung: "Hast du schon mal...?"
- Vorstellungskraft: "Stell dir vor, du wärst..."
- Gefühle: "Was würde sich wohl anfühlen...?"
- Vergleiche: "Ist das ähnlich wie...?"
3. Mache das Thema persönlicher und relevanter
4. Stelle EINE kurze, neue Frage
BEISPIEL: Wenn Thema "Wie kann ein Vogel fliegen?" ist, frage: "Hast du schon mal einen Vogel ganz nah gesehen?" oder "Stell dir vor, du könntest fliegen wie ein Vogel - was würdest du zuerst machen?"
STIL: Kurz, persönlich, aber immer beim ursprünglichen Thema bleiben.`
: `You are a patient learning companion. A child is showing disinterest in the current topic and needs a completely new approach - but STAY ON THE SAME TOPIC!
IMPORTANT: Do NOT change the topic, only change the APPROACH!
TASK: Change your approach completely, but stay with the original topic:
1. Acknowledge the disinterest ("I can understand that...")
2. Stay with the SAME topic "${originalTopic}", but try a completely different approach:
- Personal connection: "Have you ever...?"
- Imagination: "Imagine if you were..."
- Feelings: "What would it feel like...?"
- Comparisons: "Is this similar to...?"
3. Make the topic more personal and relevant
4. Ask ONE short, new question
EXAMPLE: If topic is "How can a bird fly?", ask: "Have you ever seen a bird up close?" or "Imagine if you could fly like a bird - what would you do first?"
STYLE: Short, personal, but always stay with the original topic.`;
userPrompt = isGerman
? `Ein Kind wirkt desinteressiert. Letzte Antwort: "${answer}"
Das ursprüngliche Thema war: "${originalTopic}"
Bisherige Fragen: ${recentQuestions || 'Keine'}
WICHTIG: Bleibe beim gleichen Thema "${originalTopic}", aber verändere deinen Ansatz komplett. Mache es persönlicher und interessanter, aber verlasse NICHT das ursprüngliche Thema.`
: `A child seems disinterested. Latest answer: "${answer}"
The original topic was: "${originalTopic}"
Previous questions: ${recentQuestions || 'None'}
IMPORTANT: Stay with the same topic "${originalTopic}", but change your approach completely. Make it more personal and interesting, but do NOT leave the original topic.`;
} else {
// Standard response - continue with fundamentals approach
systemPrompt = isGerman
? `Du bist ein geduldiger Lernbegleiter für Kinder (8-12 Jahre). Verwende den FUNDAMENTALS FIRST Ansatz!
BISHERIGE UNTERHALTUNG:
- Ursprüngliche Frage: "${originalTopic}"
- Aktuelle Frage: "${question}"
- Antwort des Kindes: "${answer}"
AUFGABE: Führe das systematische Lernen der Grundlagen fort:
1. ANERKENNE kurz die Antwort des Kindes (1 Satz)
2. FÜHRE die Grundlagen-Erkundung weiter fort mit EINER gezielten Frage
SYSTEMATISCHER ANSATZ:
- Wenn das Kind noch bei der ersten Grundlage ist → vertiefe diese Grundlage
- Wenn die erste Grundlage verstanden ist → gehe zur nächsten Grundlage über
- Baue systematisch Wissen auf, Schritt für Schritt
ANTWORTFORMAT:
"[Kurze Anerkennung]! [Eine Frage zur aktuellen oder nächsten Grundlage]?"
STIL: Strukturiert, aufbauend, nur EINE Frage. MAXIMAL 25 Wörter!`
: `You are a patient learning companion for children (8-12 years). Use the FUNDAMENTALS FIRST approach!
PREVIOUS CONVERSATION:
- Original question: "${originalTopic}"
- Current question: "${question}"
- Child's answer: "${answer}"
TASK: Continue the systematic learning of fundamentals:
1. ACKNOWLEDGE the child's answer briefly (1 sentence)
2. CONTINUE the fundamentals exploration with ONE targeted question
SYSTEMATIC APPROACH:
- If the child is still on the first fundamental → deepen this fundamental
- If the first fundamental is understood → move to the next fundamental
- Build knowledge systematically, step by step
RESPONSE FORMAT:
"[Brief acknowledgment]! [One question about current or next fundamental]?"
STYLE: Structured, building, only ONE question. MAXIMUM 25 words!`;
userPrompt = isGerman
? `Kind hat geantwortet: "${answer}"
Auf die Frage: "${question}"
BEREITS GESTELLTE FRAGEN (vermeide Wiederholung):
${recentQuestions || 'Keine bisherigen Fragen'}
Ursprüngliches Thema: "${originalTopic}"
FÜHRE DEN FUNDAMENTALS FIRST ANSATZ FORT:
- Anerkenne die Antwort kurz
- Stelle eine Frage, die systematisch das Verständnis der Grundlagen aufbaut
- Wenn eine Grundlage verstanden ist, gehe zur nächsten über
BEISPIEL: "Genau! Was passiert wohl mit UV-Strahlung, wenn sie auf die Haut trifft?"
Bleibe strukturiert und systematisch!`
: `Child answered: "${answer}"
To the question: "${question}"
ALREADY ASKED QUESTIONS (avoid repetition):
${recentQuestions || 'No previous questions'}
Original topic: "${originalTopic}"
CONTINUE THE FUNDAMENTALS FIRST APPROACH:
- Acknowledge the answer briefly
- Ask a question that systematically builds understanding of fundamentals
- If one fundamental is understood, move to the next
EXAMPLE: "Exactly! What do you think happens when UV radiation hits the skin?"
Stay structured and systematic!`;
}
try {
const completion = await openai.chat.completions.create({
model: "gpt-4o-mini",
messages: [
{ role: "system", content: systemPrompt },
{ role: "user", content: userPrompt }
],
max_tokens: hasReachedKnowledgeLimit ? 350 : 120, // Even more tokens for comprehensive knowledge limit explanations
temperature: 0.7
});
const response = completion.choices[0]?.message?.content || '';
console.log('✅ Conversation response generated:', response);
return response;
} catch (error) {
console.log('❌ Conversation response error:', error.message);
throw error;
}
}
// API endpoint to get the next fundamental concept
app.post('/api/next-fundamental', async (req, res) => {
const { context, sessionId, language, currentTopic } = req.body;
// Accept both 'next_fundamental' context and requests without context for flexibility
if (context && context !== 'next_fundamental') {
return res.status(400).json({ error: 'Invalid context' });
}
try {
console.log(`🌟 Generating next fundamental for session ${sessionId}`);
// Default language is German
const isEnglish = language === 'en';
const nextFundamental = {
summary: isEnglish
? "Let's explore another important concept: forces and motion. Everything in our world moves because of different forces acting upon it."
: "Lass uns eine andere wichtige Grundlage erforschen: Kräfte und Bewegung. Alles in unserer Welt bewegt sich aufgrund verschiedener Kräfte, die darauf einwirken.",
question: isEnglish
? "Have you ever wondered why a ball stops rolling after you kick it? What do you think causes it to slow down and stop?"
: "Hast du dich schon einmal gefragt, warum ein Ball aufhört zu rollen, nachdem du ihn getreten hast? Was denkst du, verursacht seine Verlangsamung und sein Anhalten?"
};
// Return in the expected format to match other endpoints
res.json({
success: true,
guidance: {
type: 'next-fundamental',
steps: [{
id: 1,
text: nextFundamental.summary + '\n\n' + nextFundamental.question,
type: 'question'
}]
},
nextFundamental: nextFundamental
});
} catch (error) {
console.error('Error generating next fundamental:', error);
res.status(500).json({
success: false,
error: 'Failed to generate next fundamental',
guidance: {
type: 'fallback',
steps: [{
id: 1,
text: language === 'en'
? 'Let\'s talk about another important concept... What do you already know about cause and effect?'
: 'Lass uns über eine andere wichtige Grundlage sprechen... Was weißt du bereits über Ursache und Wirkung?',
type: 'question'
}]
}
});
}
});