Files
kidsai/html/kidsai/server.js
root e361c2c5fb FINAL FIX: Complete repeated 'don't know' detection system working
AUTOMATED CURL TESTING RESULTS:
 Normal answers get Socratic questions
 First 'don't know' gets another question
 Repeated 'don't know' gets explanation instead of questions!

BUGS FIXED:
- Added missing originalTopic parameter to server endpoint
- Fixed undefined variable reference in repeated don't know handler
- Server now properly extracts originalTopic from request body

VERIFICATION COMPLETE:
- Server logs show: '🎯 Detected repeated don't know - providing explanation'
- System switches from questioning to explaining mode automatically
- Both chat and step-by-step modes have full detection logic
- German and English phrase detection working correctly

The infinite questioning loop issue is completely resolved!
Children now get appropriate help when they repeatedly say 'I don't know'.
2025-06-30 16:23:53 +02:00

962 lines
47 KiB
JavaScript
Executable File
Raw Blame History

This file contains invisible Unicode characters
This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. 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();
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;
// Educational prompts for AI to guide thinking instead of giving direct answers
const EDUCATIONAL_PROMPTS = {
en: {
systemPrompt: "You are an educational assistant for children. Instead of giving direct answers, ask 2-3 guiding questions that help children think through the problem themselves. Be encouraging and use simple language. Focus on the thinking process, not the 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 Lernassistent für Kinder. Anstatt direkte Antworten zu geben, stelle 2-3 Leitfragen, die Kindern helfen, das Problem selbst zu durchdenken. Sei ermutigend und verwende einfache Sprache. Konzentriere dich auf den Denkprozess, nicht auf die 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' } = req.body;
if (!question || question.trim().length === 0) {
return res.status(400).json({
success: false,
error: 'Question is required'
});
}
try {
// Get AI-powered guidance
const aiGuidance = await getAIGuidance(question, language);
res.json({
success: true,
guidance: aiGuidance,
question: question,
language: language
});
} 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
});
}
});
// 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) {
console.log('🤖 Calling OpenAI with GPT-4o-mini...');
const isGerman = language === 'de';
const systemPrompt = isGerman
? "Du bist ein geduldiger Lehrer für Kinder. Anstatt direkte Antworten zu geben, führe das Kind Schritt für Schritt zum Verständnis. Stelle genau 3-4 aufbauende Fragen, die das Kind zum Nachdenken anregen und ihm helfen, die Antwort selbst zu entdecken. Jede Frage sollte auf der vorherigen aufbauen und dem Kind helfen, das Konzept zu verstehen. Verwende einfache Sprache und ermutigende Worte. Formatiere IMMER als nummerierte Liste: 1. [Frage] 2. [Frage] 3. [Frage] usw. Gib NUR die nummerierten Fragen zurück, keine Einleitung."
: "You are a patient teacher for children. Instead of giving direct answers, guide the child step by step to understanding. Ask exactly 3-4 building questions that encourage the child to think and help them discover the answer themselves. Each question should build on the previous one and help the child understand the concept. Use simple language and encouraging words. ALWAYS format as a numbered list: 1. [Question] 2. [Question] 3. [Question] etc. Return ONLY the numbered questions, no introduction.";
const userPrompt = isGerman
? `Ein Kind hat gefragt: "${question}". Führe es mit genau 3-4 nummerierten Fragen Schritt für Schritt zum Verständnis, ohne die Antwort direkt zu verraten. Beispiel: 1. Wie fühlt sich Wasser an, wenn du es berührst? 2. Was passiert... usw. NUR nummerierte Fragen, keine Einleitung!`
: `A child asked: "${question}". Guide them with exactly 3-4 numbered questions step by step to understanding without giving away the answer directly. Example: 1. How does water feel when you touch it? 2. What happens... etc. ONLY numbered questions, no introduction!`;
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 response:', text);
const lines = text.split('\n').filter(line => line.trim());
const steps = [];
lines.forEach((line, index) => {
const trimmed = line.trim();
console.log(`🔍 Processing line ${index}: "${trimmed}"`);
// Look for numbered items (1. 2. 3. etc.)
const numberedMatch = trimmed.match(/^\d+\.\s*(.+)/);
if (numberedMatch) {
const questionText = numberedMatch[1].trim();
if (questionText.length > 5) {
steps.push({
id: steps.length + 1,
text: questionText,
type: 'question'
});
console.log(`✅ Added step: ${questionText}`);
}
}
// Also look for lines with question marks that might not be numbered
else if (trimmed.includes('?') && trimmed.length > 10 && !trimmed.toLowerCase().includes('frage') && !trimmed.toLowerCase().includes('question')) {
steps.push({
id: steps.length + 1,
text: trimmed,
type: 'question'
});
console.log(`✅ Added question: ${trimmed}`);
}
});
console.log(`📋 Total steps parsed: ${steps.length}`);
// Ensure we have at least 2 steps
if (steps.length < 2) {
console.log('⚠️ Not enough steps, using fallback');
const fallback = getFallbackGuidance('', language);
return fallback.steps.slice(0, 3);
}
return steps.slice(0, 4); // Limit to 4 steps max
}
// 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) {
// Try OpenAI first (most reliable)
if (process.env.OPENAI_API_KEY) {
try {
return await getOpenAIGuidance(question, language);
} 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
if (questionLower.includes('bird') || questionLower.includes('fly') || questionLower.includes('wing')) {
const birdQuestions = isGerman ? [
{ id: 1, text: "Was weißt du bereits über Vögel und ihre Flügel?", type: 'question' },
{ id: 2, text: "Wie denkst du, nutzen Vögel ihre Flügel, um sich durch die Luft zu bewegen?", type: 'question' },
{ id: 3, text: "Kannst du an andere Tiere denken, die fliegen können, und wie unterscheiden sie sich in ihren Flugfähigkeiten?", type: 'question' }
] : [
{ id: 1, text: "What do you already know about this topic?", type: 'question' },
{ id: 2, text: "How do birds use their wings to generate lift and propel themselves through the air?", type: 'question' },
{ id: 3, text: "Can you think of any other animals that can fly like birds, and how do they differ in their flying abilities?", type: 'question' }
];
return {
type: 'topic-specific',
steps: birdQuestions,
encouragement: getRandomEncouragement(language, question),
source: 'Educational Framework'
};
}
if (questionLower.includes('sky') || questionLower.includes('blue') || questionLower.includes('himmel') || questionLower.includes('blau')) {
const skyQuestions = isGerman ? [
{ id: 1, text: "Was weißt du bereits über Licht und Farben?", type: 'question' },
{ id: 2, text: "Was passiert mit Licht, wenn es durch die Luft geht?", type: 'question' },
{ id: 3, text: "Warum siehst du manche Farben deutlicher als andere?", type: 'question' }
] : [
{ id: 1, text: "What do you already know about light and colors?", type: 'question' },
{ id: 2, text: "What happens to light when it travels through the air?", type: 'question' },
{ id: 3, text: "Why do you see some colors more clearly than others?", type: 'question' }
];
return {
type: 'topic-specific',
steps: skyQuestions,
encouragement: getRandomEncouragement(language, question),
source: 'Educational Framework'
};
}
// Generic fallback questions
const fallbackSteps = isGerman ? [
{ id: 1, text: "Was weißt du bereits über dieses Thema?", type: 'question' },
{ id: 2, text: "Welche Teile der Frage verstehst du, und welche sind unklar?", type: 'question' },
{ id: 3, text: "Wo könntest du mehr Informationen finden?", type: 'question' },
{ id: 4, text: "Kannst du das Problem in kleinere Teile aufteilen?", type: 'question' }
] : [
{ id: 1, text: "What do you already know about this topic?", type: 'question' },
{ id: 2, text: "Which parts of the question do you understand, and which are unclear?", type: 'question' },
{ id: 3, text: "Where could you find more information about this?", type: 'question' },
{ id: 4, text: "Can you break this problem down into smaller parts?", 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 hilfreicher Assistent für Kinder. Gib eine klare, einfache und altersgerechte Antwort auf die Frage. Verwende einfache Sprache und erkläre es so, dass ein Kind es verstehen kann. Halte die Antwort kurz aber vollständig."
: "You are a helpful assistant for children. Provide a clear, simple, and age-appropriate answer to the question. Use simple language and explain it in a way a child can understand. Keep the answer concise but complete.";
const userPrompt = isGerman
? `Ein Kind möchte wissen: "${question}". Gib eine einfache, klare Antwort.`
: `A child wants to know: "${question}". Provide a simple, clear answer.`;
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: 150,
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';
// Simple pattern matching for common questions
const questionLower = question.toLowerCase();
if (questionLower.includes('sky') || questionLower.includes('himmel') || questionLower.includes('blue') || questionLower.includes('blau')) {
return {
type: 'rule-based',
text: isGerman
? "Der Himmel ist blau, weil winzig kleine Teilchen in der Luft das blaue Licht mehr streuen als andere Farben. Es ist wie wenn du blaues Licht durch viele kleine Glaskugeln scheinst!"
: "The sky is blue because tiny particles in the air scatter blue light more than other colors. It's like shining blue light through many tiny glass balls!",
source: 'Educational Framework'
};
}
if (questionLower.includes('bird') || questionLower.includes('fly') || questionLower.includes('vogel') || questionLower.includes('fliegen')) {
return {
type: 'rule-based',
text: isGerman
? "Vögel können fliegen, weil ihre Flügel eine besondere Form haben und sie sehr leichte, hohle Knochen haben. Sie schlagen mit den Flügeln und erzeugen Auftrieb, der sie in die Luft hebt!"
: "Birds can fly because their wings have a special shape and they have very light, hollow bones. They flap their wings to create lift that pushes them up into the air!",
source: 'Educational Framework'
};
}
if (questionLower.includes('1+1') || questionLower.includes('add') || questionLower.includes('plus')) {
return {
type: 'rule-based',
text: isGerman
? "1 + 1 = 2! Wenn du einen Apfel hast und noch einen dazu bekommst, hast du zwei Äpfel. Das ist Addition - zusammenzählen!"
: "1 + 1 = 2! When you have one apple and get another one, you have two apples. That's addition - adding together!",
source: 'Educational Framework'
};
}
// Generic fallback
return {
type: 'rule-based',
text: isGerman
? "Das ist eine tolle Frage! Die Antwort hängt von vielen Faktoren ab. Ich empfehle dir, in einem Buch nachzuschauen oder einen Erwachsenen zu fragen, der mehr darüber weiß."
: "That's a great question! The answer depends on many factors. I recommend looking it up in a book or asking an adult who knows more about this topic.",
source: 'Educational Framework'
};
}
// API endpoint for responding to user answers contextually
app.post('/api/respond-to-answer', async (req, res) => {
try {
const { answer, question, language = 'en', stepIndex, context, instructions, originalTopic } = req.body;
if (!answer || !question) {
return res.status(400).json({
success: false,
error: 'Answer and question are required'
});
}
console.log(`📝 Generating response to answer: "${answer}" for question: "${question}" with context: ${context}`);
// Handle confusion explanation requests specially
if (context === 'confusion_explanation' && instructions) {
const explanationPrompt = `${instructions}
CHILD'S CONFUSED RESPONSE: "${answer}"
ORIGINAL QUESTION: "${question}"
The child is expressing confusion and needs help understanding. Provide a clear, simple explanation using everyday examples and concrete comparisons that a child can understand.`;
// Try OpenAI first for explanations
if (process.env.OPENAI_API_KEY) {
try {
const completion = await openai.chat.completions.create({
model: "gpt-4o-mini",
messages: [
{
role: "system",
content: explanationPrompt
}
],
max_tokens: 400,
temperature: 0.6
});
const aiResponse = completion.choices[0]?.message?.content?.trim();
if (aiResponse && aiResponse.length > 10) {
console.log('✅ OpenAI confusion explanation generated successfully');
return res.json({
success: true,
response: aiResponse,
source: 'OpenAI GPT-4o-mini (Confusion Explanation)'
});
}
} catch (openaiError) {
console.log('❌ OpenAI error for confusion explanation:', openaiError.message);
}
}
// Fallback for confusion explanations
const isGerman = language === 'de';
const { originalTopic } = req.body;
// Try to provide topic-specific fallback explanations
let confusionFallback;
const topicLower = (originalTopic || question || '').toLowerCase();
if (topicLower.includes('wasser') || topicLower.includes('water') || topicLower.includes('nass') || topicLower.includes('wet')) {
confusionFallback = isGerman ?
'Wasser ist "nass", weil es eine Flüssigkeit ist, die an anderen Oberflächen haftet und sie benetzt. Wenn Wassermoleküle auf deine Haut oder andere Materialien treffen, bleiben sie dort haften und fühlen sich feucht an. Das ist das Gefühl, das wir "nass" nennen! Du kannst das ausprobieren: Tropfe etwas Wasser auf verschiedene Materialien und schau, wie es sich verhält. 💧' :
'Water is "wet" because it\'s a liquid that sticks to other surfaces and makes them moist. When water molecules touch your skin or other materials, they stay there and feel damp. That\'s the feeling we call "wet"! You can try this: drop some water on different materials and see how it behaves. 💧';
} else if (topicLower.includes('vögel') || topicLower.includes('birds') || topicLower.includes('fliegen') || topicLower.includes('fly') || topicLower.includes('flügel') || topicLower.includes('wings')) {
confusionFallback = isGerman ?
'Vögel können fliegen, weil sie besondere Körperteile haben! Ihre Flügel sind leicht aber stark, ihre Knochen sind hohl (wie Strohhalme), und sie haben kräftige Brustmuskeln, die die Flügel bewegen. Wenn sie die Flügel auf und ab schlagen, drücken sie die Luft nach unten, und das hebt sie nach oben - wie beim Schwimmen, aber in der Luft! Du kannst das ausprobieren: Wedel mit den Armen und spür, wie du die Luft bewegst! 🐦' :
'Birds can fly because they have special body parts! Their wings are light but strong, their bones are hollow (like straws), and they have powerful chest muscles that move the wings. When they flap their wings up and down, they push air downward, and that lifts them up - like swimming, but in the air! You can try this: wave your arms and feel how you move the air! 🐦';
} else if (topicLower.includes('jahreszeiten') || topicLower.includes('seasons') || topicLower.includes('winter') || topicLower.includes('sommer') || topicLower.includes('summer') || topicLower.includes('erde') || topicLower.includes('earth') || topicLower.includes('position') || topicLower.includes('heiß') || topicLower.includes('kalt') || topicLower.includes('hot') || topicLower.includes('cold')) {
confusionFallback = isGerman ?
'Die Erde ist wie ein Ball, der um die Sonne kreist! Sie ist etwas schief geneigt, wie ein Ball, der leicht zur Seite hängt. Wenn unser Teil der Erde zur Sonne geneigt ist, bekommen wir mehr Sonnenlicht und es wird Sommer. Wenn wir von der Sonne weg geneigt sind, bekommen wir weniger Licht und es wird Winter! Du kannst das mit einer Taschenlampe und einem Ball ausprobieren - halte den Ball schief und leuchte ihn an! 🌍☀️' :
'The Earth is like a ball that goes around the Sun! It\'s tilted a bit, like a ball that hangs slightly to one side. When our part of Earth tilts toward the Sun, we get more sunlight and it becomes summer. When we tilt away from the Sun, we get less light and it becomes winter! You can try this with a flashlight and a ball - hold the ball tilted and shine light on it! 🌍☀️';
} else if (topicLower.includes('regenbogen') || topicLower.includes('rainbow') || topicLower.includes('farben') || topicLower.includes('colors')) {
confusionFallback = isGerman ?
'Das passiert, weil Sonnenlicht aus vielen verschiedenen Farben besteht! Wenn Licht durch Regentropfen geht, werden diese Farben getrennt - wie bei einem Prisma. Du kannst das selbst ausprobieren: Halte ein Glas Wasser ins Sonnenlicht und schau, welche Farben entstehen! 🌈' :
'This happens because sunlight is made of many different colors! When light goes through raindrops, these colors get separated - just like with a prism. You can try this yourself: hold a glass of water in sunlight and see what colors appear! 🌈';
} else {
confusionFallback = isGerman ?
'Das ist eine sehr gute Frage! Manchmal sind Dinge komplizierter, als sie auf den ersten Blick scheinen. Lass mich dir das einfacher erklären: Alles hat bestimmte Eigenschaften, die wir mit unseren Sinnen wahrnehmen können. Was du fragst, hat mit diesen grundlegenden Eigenschaften zu tun. Hast du noch spezielle Fragen dazu? 🤔' :
'That\'s a very good question! Sometimes things are more complicated than they seem at first. Let me explain this more simply: Everything has certain properties that we can perceive with our senses. What you\'re asking about relates to these basic properties. Do you have any specific questions about it? 🤔';
}
return res.json({
success: true,
response: confusionFallback,
source: 'Fallback Confusion Explanation'
});
}
// Handle definition/explanation requests specially
if (context === 'definition_request' && instructions) {
const definitionPrompt = `${instructions}
CHILD'S QUESTION: "${answer}"
CONTEXT: We were discussing "${question}"
The child is asking for a definition, explanation, or clarification about location/relationships. Provide a clear, child-friendly answer with simple examples and invite further questions. If asking about location (where is X?), explain in simple terms. If asking about relationships (what does X have to do with Y?), explain the connection clearly.`;
// Try OpenAI first for definitions
if (process.env.OPENAI_API_KEY) {
try {
const completion = await openai.chat.completions.create({
model: "gpt-4o-mini",
messages: [
{
role: "system",
content: definitionPrompt
}
],
max_tokens: 300,
temperature: 0.6
});
const aiResponse = completion.choices[0]?.message?.content?.trim();
if (aiResponse && aiResponse.length > 10) {
console.log('✅ OpenAI definition response generated successfully');
return res.json({
success: true,
response: aiResponse,
source: 'OpenAI GPT-4o-mini (Definition Response)'
});
}
} catch (openaiError) {
console.log('❌ OpenAI error for definition response:', openaiError.message);
}
}
// Fallback for definition requests
const isGerman = language === 'de';
const definitionFallback = isGerman ?
'Das ist eine tolle Frage! Ein Regenbogen entsteht, wenn Sonnenlicht durch kleine Wassertropfen in der Luft geht. Das Licht wird dabei in alle seine Farben aufgeteilt - rot, orange, gelb, grün, blau, indigo und violett. Du kannst das auch mit einem Gartenschlauch ausprobieren! Was möchtest du noch wissen? 🌈' :
'That\'s a great question! A rainbow happens when sunlight goes through tiny water droplets in the air. The light gets split into all its colors - red, orange, yellow, green, blue, indigo, and violet. You can try this with a garden hose too! What else would you like to know? 🌈';
return res.json({
success: true,
response: definitionFallback,
source: 'Fallback Definition Response'
});
}
// Create contextual prompt for responding to the user's answer (existing logic)
const isGerman = language === 'de';
// Check if this looks like a repeated "don't know" scenario
const answerLowerChat = answer.toLowerCase().trim();
const isDontKnowResponse = answerLowerChat.includes("don't know") || answerLowerChat.includes("weiß nicht") ||
answerLowerChat.includes("weis nicht") || answerLowerChat.includes("weiss nicht") ||
answerLowerChat.includes("keine ahnung") || answerLowerChat.includes("ich weiß es nicht") ||
answerLowerChat.includes("das weiß ich nicht") || answerLowerChat.includes("weiß ich auch nicht");
console.log(`🔍 Answer analysis - isDontKnow: ${isDontKnowResponse}, answer: "${answer}"`);
// If this is a "don't know" response and we're in a loop, provide explanation
if (isDontKnowResponse && (context === 'repeated_dont_know' ||
(instructions && (instructions.includes('mehrmals') || instructions.includes('multiple times'))))) {
console.log('🎯 Detected repeated "don\'t know" - providing explanation instead of more questions');
const explanationPrompt = isGerman ?
`Das Kind braucht jetzt eine klare Erklärung, da es mehrmals "weiß nicht" gesagt hat.
URSPRÜNGLICHE FRAGE: "${originalTopic || question}"
CONTEXT: Wiederholte "weiß nicht" Antworten
Gib eine einfache, verständliche Erklärung für Kinder. Verwende Beispiele aus dem Alltag. Erkläre Schritt für Schritt. Keine weiteren Fragen - das Kind braucht jetzt Wissen!` :
`The child needs a clear explanation now since they've said "don't know" multiple times.
ORIGINAL QUESTION: "${originalTopic || question}"
CONTEXT: Repeated "don't know" responses
Give a simple, understandable explanation for children. Use everyday examples. Explain step by step. No more questions - the child needs knowledge now!`;
try {
const completion = await openai.chat.completions.create({
model: "gpt-4o-mini",
messages: [
{
role: "system",
content: explanationPrompt
}
],
max_tokens: 300,
temperature: 0.6
});
const aiResponse = completion.choices[0]?.message?.content?.trim();
if (aiResponse && aiResponse.length > 10) {
console.log('✅ OpenAI explanation for repeated "don\'t know" generated successfully');
return res.json({
success: true,
response: aiResponse,
source: 'OpenAI GPT-4o-mini (Repeated Don\'t Know Explanation)'
});
}
} catch (openaiError) {
console.log('❌ OpenAI error for repeated don\'t know explanation:', openaiError.message);
}
}
const contextualPrompt = isGerman ?
`Du bist ein Socratic Lernbegleiter für Kinder. Ein Kind hat gerade auf eine Frage geantwortet.
FRAGE: "${question}"
ANTWORT DES KINDES: "${answer}"
Das Kind zeigt eigenes Denken. Deine Aufgabe ist es, eine Socratic Folgefrage zu stellen, die:
1. Auf der spezifischen Antwort des Kindes aufbaut
2. Das Kind dazu ermutigt, tiefer zu denken
3. Eine konkrete Folgefrage stellt, die zur Entdeckung führt
WICHTIG:
- Stelle EINE durchdachte Folgefrage
- Gib KEINE Erklärungen oder Antworten
- Baue direkt auf der Antwort des Kindes auf
- Verwende "Was denkst du..." oder "Hast du schon mal bemerkt..."
- Nur 1-2 Sätze` :
`You are a Socratic learning companion for children. A child just answered a question.
QUESTION: "${question}"
CHILD'S ANSWER: "${answer}"
The child is showing their own thinking. Your task is to ask a Socratic follow-up question that:
1. Builds directly on the child's specific answer
2. Encourages the child to think deeper
3. Asks one concrete follow-up question that leads to discovery
IMPORTANT:
- Ask ONE thoughtful follow-up question
- Give NO explanations or answers
- Build directly on the child's answer
- Use "What do you think..." or "Have you ever noticed..."
- Only 1-2 sentences`;
// Try OpenAI first
if (process.env.OPENAI_API_KEY) {
try {
const completion = await openai.chat.completions.create({
model: "gpt-4o-mini",
messages: [
{
role: "system",
content: contextualPrompt
}
],
max_tokens: 200,
temperature: 0.7
});
const aiResponse = completion.choices[0]?.message?.content?.trim();
if (aiResponse && aiResponse.length > 10) {
console.log('✅ OpenAI response generated successfully');
return res.json({
success: true,
response: aiResponse,
source: 'OpenAI GPT-4o-mini'
});
}
} catch (openaiError) {
console.log('❌ OpenAI error:', openaiError.message);
}
}
// Fallback: Generate a contextual response based on answer content
const answerLower = answer.toLowerCase();
let fallbackResponse;
if (answerLower.includes("don't know") || answerLower.includes("weiß nicht") || answerLower.includes("keine ahnung") ||
answerLower.includes("ich weiß es nicht") || answerLower === "warum" || answerLower === "why" || answer.trim().length < 5) {
fallbackResponse = isGerman ?
"🌟 Perfekt! Lass uns das gemeinsam herausfinden." :
"🌟 Perfect! Let's figure this out together.";
} else if (answerLower.includes('temperatur') || answerLower.includes('temperature')) {
fallbackResponse = isGerman ?
`🤔 Interessante Überlegung zur Temperatur! Was denkst du, wie Temperatur dabei eine Rolle spielen könnte?` :
`🤔 Interesting thinking about temperature! What do you think - how might temperature play a role in this?`;
} else if (answerLower.includes('wetter') || answerLower.includes('weather') || answerLower.includes('regen') || answerLower.includes('rain')) {
fallbackResponse = isGerman ?
`<EFBFBD> Gute Beobachtung zum Wetter! Hast du schon mal genau beobachtet, was nach einem Regenschauer passiert?` :
`🌦️ Good observation about weather! Have you ever closely watched what happens after a rain shower?`;
} else if (answer.length < 15) {
fallbackResponse = isGerman ?
`💡 Guter Gedanke! Was denkst du, könnte als nächstes passieren?` :
`💡 Good thinking! What do you think might happen next?`;
} else {
fallbackResponse = isGerman ?
`🎯 Das ist eine durchdachte Antwort! Was würdest du beobachten, um das herauszufinden?` :
`🎯 That's thoughtful reasoning! What would you observe to find that out?`;
}
res.json({
success: true,
response: fallbackResponse,
source: 'Contextual Fallback'
});
} catch (error) {
console.error('❌ Error in respond-to-answer:', error);
res.status(500).json({
success: false,
error: 'Failed to generate response'
});
}
});
// 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: 150,
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'
});
}
});