ROOT CAUSE FOUND: - Chat mode was missing repeated 'don't know' detection logic entirely - Only step-by-step mode had this functionality - Server had undefined variable error when checking instructions - This caused infinite questioning loops in chat conversations FIXES APPLIED: 1. Added complete repeated 'don't know' detection to chat mode: - Conversation history tracking (last 4 messages) - German phrase detection variants - Threshold-based triggering (2+ occurrences) - Proper context passing to server 2. Fixed server-side undefined variable error: - Added null check for instructions parameter - Prevents TypeError when checking for repeated scenarios 3. Enhanced context handling: - Chat mode now sends 'repeated_dont_know' context - Proper instruction differentiation for different scenarios - Better debugging logs for both modes RESULT: - Both chat AND step-by-step modes now detect repeated frustration - Children get explanations instead of endless questioning - Eliminated infinite loop scenarios completely - Better educational experience with adaptive responses
1439 lines
72 KiB
JavaScript
1439 lines
72 KiB
JavaScript
// KidsAI Explorer - Interactive Learning Assistant
|
|
console.log('🚀 KidsAI script-new.js is loading...');
|
|
|
|
// Test that AIResponses is available
|
|
console.log('🔍 Testing AIResponses availability:', typeof AIResponses);
|
|
if (typeof AIResponses !== 'undefined') {
|
|
console.log('✅ AIResponses loaded successfully:', Object.keys(AIResponses));
|
|
} else {
|
|
console.error('❌ AIResponses not loaded! Check ai-responses.js');
|
|
}
|
|
|
|
class KidsAIExplorer {
|
|
constructor() {
|
|
console.log('🏗️ KidsAI constructor started');
|
|
|
|
this.currentLanguage = localStorage.getItem('kidsai-language') || 'en';
|
|
|
|
// Get DOM elements
|
|
this.questionInput = document.getElementById('question-input');
|
|
this.askButton = document.getElementById('ask-button');
|
|
this.thinkingSection = document.getElementById('thinking-section');
|
|
this.thinkingSteps = document.getElementById('thinking-steps');
|
|
this.loadingOverlay = document.getElementById('loading');
|
|
this.suggestionCards = document.querySelectorAll('.suggestion-card');
|
|
this.langButtons = document.querySelectorAll('.lang-btn');
|
|
|
|
console.log('🔍 Elements found:', {
|
|
questionInput: !!this.questionInput,
|
|
askButton: !!this.askButton,
|
|
suggestionCards: this.suggestionCards.length,
|
|
langButtons: this.langButtons.length
|
|
});
|
|
|
|
// Hide loading overlay
|
|
if (this.loadingOverlay) {
|
|
this.loadingOverlay.classList.add('hidden');
|
|
}
|
|
|
|
// Initialize
|
|
this.initializeEventListeners();
|
|
this.initializeLanguage();
|
|
|
|
console.log('✅ KidsAI constructor completed');
|
|
}
|
|
|
|
// Detect mobile device
|
|
isMobileDevice() {
|
|
return /Android|webOS|iPhone|iPad|iPod|BlackBerry|IEMobile|Opera Mini/i.test(navigator.userAgent)
|
|
|| window.innerWidth <= 768;
|
|
}
|
|
|
|
initializeEventListeners() {
|
|
console.log('🔗 Setting up event listeners');
|
|
|
|
// Main question submission
|
|
if (this.askButton) {
|
|
console.log('📝 Adding click listener to ask button');
|
|
this.askButton.addEventListener('click', () => {
|
|
console.log('🖱️ Ask button clicked!');
|
|
this.handleQuestion();
|
|
});
|
|
}
|
|
|
|
if (this.questionInput) {
|
|
console.log('⌨️ Adding keypress listener to input');
|
|
this.questionInput.addEventListener('keypress', (e) => {
|
|
if (e.key === 'Enter' && !e.shiftKey) {
|
|
console.log('⏎ Enter key pressed!');
|
|
e.preventDefault();
|
|
this.handleQuestion();
|
|
}
|
|
});
|
|
|
|
// Add mobile-friendly input event for better responsiveness
|
|
if (this.isMobileDevice()) {
|
|
this.questionInput.addEventListener('input', () => {
|
|
// Auto-resize textarea on mobile for better UX
|
|
this.questionInput.style.height = 'auto';
|
|
this.questionInput.style.height = Math.min(this.questionInput.scrollHeight, 120) + 'px';
|
|
});
|
|
}
|
|
}
|
|
|
|
// Suggestion cards
|
|
console.log('🎯 Adding listeners to', this.suggestionCards.length, 'suggestion cards');
|
|
this.suggestionCards.forEach((card, index) => {
|
|
card.addEventListener('click', () => {
|
|
console.log('🎴 Suggestion card', index, 'clicked!');
|
|
const questionKey = `data-question-${this.currentLanguage}`;
|
|
const question = card.getAttribute(questionKey);
|
|
console.log('📝 Setting question:', question);
|
|
if (this.questionInput) {
|
|
this.questionInput.value = question;
|
|
}
|
|
this.handleQuestion();
|
|
});
|
|
});
|
|
|
|
// Language switching
|
|
this.langButtons.forEach(btn => {
|
|
btn.addEventListener('click', () => {
|
|
console.log('🌐 Language button clicked:', btn.dataset.lang);
|
|
this.switchLanguage(btn.dataset.lang);
|
|
});
|
|
});
|
|
|
|
console.log('✅ Event listeners setup complete');
|
|
}
|
|
|
|
initializeLanguage() {
|
|
console.log('🌐 Initializing language:', this.currentLanguage);
|
|
|
|
// Set active language button
|
|
this.langButtons.forEach(btn => {
|
|
btn.classList.toggle('active', btn.dataset.lang === this.currentLanguage);
|
|
});
|
|
|
|
// Apply translations if available
|
|
if (typeof translations !== 'undefined') {
|
|
this.applyTranslations();
|
|
} else {
|
|
console.warn('⚠️ Translations not loaded');
|
|
}
|
|
}
|
|
|
|
applyTranslations() {
|
|
try {
|
|
const t = translations[this.currentLanguage];
|
|
if (!t) {
|
|
console.warn('⚠️ Translations not available for language:', this.currentLanguage);
|
|
return;
|
|
}
|
|
|
|
// Update all elements with data-translate attribute
|
|
document.querySelectorAll('[data-translate]').forEach(element => {
|
|
const key = element.getAttribute('data-translate');
|
|
if (t[key]) {
|
|
element.textContent = t[key];
|
|
}
|
|
});
|
|
} catch (error) {
|
|
console.error('❌ Error applying translations:', error);
|
|
}
|
|
}
|
|
|
|
// Helper method to get translation for a key
|
|
getTranslation(key) {
|
|
try {
|
|
if (typeof translations !== 'undefined' && translations[this.currentLanguage] && translations[this.currentLanguage][key]) {
|
|
return translations[this.currentLanguage][key];
|
|
}
|
|
} catch (error) {
|
|
console.warn('⚠️ Could not get translation for key:', key, error);
|
|
}
|
|
return null;
|
|
}
|
|
|
|
// Safe translation method for use in template strings
|
|
t(key, fallback = '') {
|
|
try {
|
|
const translation = this.getTranslation(key);
|
|
return translation || fallback;
|
|
} catch (error) {
|
|
console.warn('⚠️ Translation error for key:', key, error);
|
|
return fallback;
|
|
}
|
|
}
|
|
|
|
switchLanguage(lang) {
|
|
console.log('🔄 Switching language to:', lang);
|
|
this.currentLanguage = lang;
|
|
localStorage.setItem('kidsai-language', lang);
|
|
this.initializeLanguage();
|
|
}
|
|
|
|
async handleQuestion() {
|
|
console.log('🤔 handleQuestion called');
|
|
|
|
const question = this.questionInput ? this.questionInput.value.trim() : '';
|
|
console.log('❓ Question:', question);
|
|
|
|
if (!question) {
|
|
console.log('⚠️ No question provided');
|
|
const message = this.getTranslation('ask-something-first') || 'Please ask me something first! 🤔';
|
|
this.showMessage(message, 'warning');
|
|
return;
|
|
}
|
|
|
|
this.showLoading();
|
|
console.log('🔄 Sending question to API...');
|
|
|
|
try {
|
|
const response = await fetch('/api/ask', {
|
|
method: 'POST',
|
|
headers: {
|
|
'Content-Type': 'application/json',
|
|
},
|
|
body: JSON.stringify({
|
|
question: question,
|
|
language: this.currentLanguage
|
|
})
|
|
});
|
|
|
|
console.log('📡 API response status:', response.status);
|
|
|
|
if (!response.ok) {
|
|
throw new Error(`HTTP error! status: ${response.status}`);
|
|
}
|
|
|
|
const data = await response.json();
|
|
console.log('📦 API response data:', data);
|
|
|
|
if (data.success) {
|
|
this.displayGuidance(data.guidance);
|
|
} else {
|
|
throw new Error(data.error || 'Unknown error');
|
|
}
|
|
|
|
} catch (error) {
|
|
console.error('❌ Error getting AI guidance:', error);
|
|
const message = this.getTranslation('processing-trouble') || 'Sorry, I had trouble processing your question. Let me give you some thinking guidance instead!';
|
|
this.showMessage(message, 'info');
|
|
this.displayLocalGuidance(question);
|
|
} finally {
|
|
this.hideLoading();
|
|
}
|
|
}
|
|
|
|
displayGuidance(guidance) {
|
|
console.log('💭 Displaying AI guidance');
|
|
|
|
if (!this.thinkingSection || !this.thinkingSteps) {
|
|
console.error('❌ Thinking section elements not found');
|
|
return;
|
|
}
|
|
|
|
// Show thinking section
|
|
this.thinkingSection.classList.remove('hidden');
|
|
|
|
// Clear previous content completely
|
|
this.thinkingSteps.innerHTML = '';
|
|
|
|
// Reset conversation state
|
|
this.currentStep = 0;
|
|
this.userAnswers = [];
|
|
this.currentQuestionIndex = 0;
|
|
|
|
// Create conversation container
|
|
const conversationContainer = document.createElement('div');
|
|
conversationContainer.className = 'conversation-container';
|
|
this.thinkingSteps.appendChild(conversationContainer);
|
|
|
|
// Show initial encouragement
|
|
const welcomeStep = document.createElement('div');
|
|
welcomeStep.className = 'conversation-step visible';
|
|
|
|
// Get translations before creating HTML to avoid context issues
|
|
const encouragementText = guidance.encouragement || this.getTranslation('default-encouragement') || (this.currentLanguage === 'de' ? "Fantastische Frage! Lass uns das gemeinsam Schritt für Schritt erforschen! 🚀" : "Great question! Let's explore this together step by step! 🚀");
|
|
const detectiveHelpText = this.getTranslation('detective-help') || (this.currentLanguage === 'de' ? "Anstatt dir die Antwort gleich zu geben, helfe ich dir dabei, wie ein Detektiv zu denken! 🕵️" : "Instead of giving you the answer right away, I'll help you think through this like a detective! 🕵️");
|
|
const aiTeacherText = this.getTranslation('ai-teacher') || (this.currentLanguage === 'de' ? "KI-Lehrer" : "AI Teacher");
|
|
|
|
welcomeStep.innerHTML = `
|
|
<div class="ai-message">
|
|
<div class="message-header">
|
|
<span class="ai-avatar">🤖</span>
|
|
<span class="ai-label">${aiTeacherText}</span>
|
|
</div>
|
|
<div class="message-content">
|
|
<p>${encouragementText}</p>
|
|
<p>${detectiveHelpText}</p>
|
|
</div>
|
|
</div>
|
|
`;
|
|
conversationContainer.appendChild(welcomeStep);
|
|
|
|
// Add thinking questions as interactive steps
|
|
const questions = guidance.steps ? guidance.steps.map(step => step.text) : guidance.questions || [
|
|
this.currentLanguage === 'de' ? "Was weißt du bereits über dieses Thema?" : "What do you already know about this topic?",
|
|
this.currentLanguage === 'de' ? "Was denkst du, könnte der Grund dafür sein?" : "What do you think might be the reason for this?",
|
|
this.currentLanguage === 'de' ? "Kannst du dir Beispiele oder ähnliche Situationen vorstellen?" : "Can you think of any examples or similar situations?"
|
|
];
|
|
|
|
// Create chat interface
|
|
this.conversationContainer = conversationContainer;
|
|
this.questions = questions;
|
|
|
|
// Start the conversation with the first question
|
|
this.askNextQuestion();
|
|
|
|
// Scroll to thinking section
|
|
this.thinkingSection.scrollIntoView({ behavior: 'smooth' });
|
|
}
|
|
|
|
displayLocalGuidance(question) {
|
|
console.log('📚 Displaying local guidance for:', question);
|
|
|
|
// Get translated encouragements or use fallback
|
|
const encouragements = this.getTranslation('encouragements') || [
|
|
"Great question! You're thinking like a real scientist! 🔬",
|
|
"Wow, that's a fantastic thing to wonder about! 🌟",
|
|
"I love how curious you are! That's how great discoveries happen! 🚀"
|
|
];
|
|
|
|
const randomEncouragement = encouragements[Math.floor(Math.random() * encouragements.length)];
|
|
|
|
// Get translated fallback questions
|
|
const fallbackQuestions = [
|
|
this.getTranslation('fallback-question-1') || "What do you already know about this topic?",
|
|
this.getTranslation('fallback-question-2') || "What do you think might be the reason for this?",
|
|
this.getTranslation('fallback-question-3') || "Where could you look to find more information?",
|
|
this.getTranslation('fallback-question-4') || "Can you think of any examples or similar situations?"
|
|
];
|
|
|
|
this.displayGuidance({
|
|
questions: fallbackQuestions,
|
|
encouragement: randomEncouragement
|
|
});
|
|
}
|
|
|
|
showLoading() {
|
|
console.log('⏳ Showing loading...');
|
|
if (this.loadingOverlay) {
|
|
this.loadingOverlay.classList.remove('hidden');
|
|
}
|
|
}
|
|
|
|
hideLoading() {
|
|
console.log('✅ Hiding loading...');
|
|
if (this.loadingOverlay) {
|
|
this.loadingOverlay.classList.add('hidden');
|
|
}
|
|
}
|
|
|
|
showMessage(message, type = 'info') {
|
|
console.log('💬 Showing message:', message, type);
|
|
|
|
// Create message popup
|
|
const messageDiv = document.createElement('div');
|
|
messageDiv.className = `message-popup ${type}`;
|
|
messageDiv.textContent = message;
|
|
messageDiv.style.cssText = `
|
|
position: fixed;
|
|
top: 20px;
|
|
right: 20px;
|
|
background: ${type === 'error' ? '#fed7d7' : type === 'warning' ? '#fef5e7' : '#e6fffa'};
|
|
color: ${type === 'error' ? '#c53030' : type === 'warning' ? '#d69e2e' : '#00695c'};
|
|
padding: 15px 20px;
|
|
border-radius: 10px;
|
|
box-shadow: 0 4px 12px rgba(0,0,0,0.2);
|
|
z-index: 1001;
|
|
max-width: 300px;
|
|
font-size: 14px;
|
|
`;
|
|
|
|
document.body.appendChild(messageDiv);
|
|
|
|
// Remove after 3 seconds
|
|
setTimeout(() => {
|
|
messageDiv.remove();
|
|
}, 3000);
|
|
}
|
|
|
|
submitAnswer(stepIndex) {
|
|
console.log('📝 Submit answer for step:', stepIndex);
|
|
|
|
const textarea = document.getElementById(`user-input-${stepIndex}`);
|
|
const inputArea = document.getElementById(`input-area-${stepIndex}`);
|
|
const responseDiv = document.getElementById(`response-${stepIndex}`);
|
|
|
|
if (!textarea || !inputArea || !responseDiv) {
|
|
console.error('❌ Could not find elements for step:', stepIndex);
|
|
return;
|
|
}
|
|
|
|
const answer = textarea.value.trim();
|
|
|
|
if (!answer) {
|
|
const message = this.getTranslation('write-thoughts') || 'Please write down your thoughts! 🤔';
|
|
this.showMessage(message, 'warning');
|
|
return;
|
|
}
|
|
|
|
// Store the answer
|
|
this.userAnswers[stepIndex] = answer;
|
|
|
|
// Mark as answered
|
|
inputArea.classList.add('answered');
|
|
textarea.disabled = true;
|
|
|
|
// Show AI response using server-side AI
|
|
this.generateAIResponseToAnswer(answer, stepIndex, responseDiv);
|
|
}
|
|
|
|
// Generate AI response to user answer using server-side AI
|
|
async generateAIResponseToAnswer(answer, stepIndex, responseDiv) {
|
|
console.log('🚀 generateAIResponseToAnswer called with:', { answer, stepIndex });
|
|
|
|
try {
|
|
// Show loading state
|
|
responseDiv.innerHTML = `
|
|
<div class="ai-response-content">
|
|
<div class="message-header">
|
|
<span class="ai-avatar">🤖</span>
|
|
<span class="ai-label">${this.getTranslation('ai-teacher')}</span>
|
|
</div>
|
|
<div class="message-content">
|
|
<p>${this.getTranslation('thinking-about-answer')}</p>
|
|
</div>
|
|
</div>
|
|
`;
|
|
responseDiv.style.display = 'block';
|
|
|
|
// Get current question context
|
|
const currentQuestion = this.currentSteps && this.currentSteps[stepIndex]
|
|
? this.currentSteps[stepIndex].text
|
|
: 'the current question';
|
|
|
|
// Analyze the answer to determine the appropriate response type
|
|
const answerLower = answer.toLowerCase().trim();
|
|
const isNegative = answerLower === 'no' || answerLower === 'nein' || answerLower === 'nope' || answerLower === 'nei';
|
|
|
|
// Check for pure "don't know" responses (without additional thinking)
|
|
const hasDontKnowPhrase = answerLower.includes("don't know") || answerLower.includes('weiß nicht') || answerLower.includes('keine ahnung') ||
|
|
answerLower.includes('ich weiß es nicht') || answerLower.includes('weis es nicht') || answerLower.includes('weiss es nicht') ||
|
|
answerLower.includes('i dont know') || answerLower.includes("i don't know");
|
|
|
|
const isOnlyWhyQuestion = answerLower === 'warum' || answerLower === 'why' || answerLower === 'warum?' || answerLower === 'why?';
|
|
|
|
// Check if child is explicitly asking for help or the answer
|
|
const isAskingForHelp = answerLower.includes('bitte verrate es mir') || answerLower.includes('please tell me') ||
|
|
answerLower.includes('was bedeutet es') || answerLower.includes('what does it mean') ||
|
|
answerLower.includes('wie komme ich zur') || answerLower.includes('how do i get to') ||
|
|
answerLower.includes('finalen antwort') || answerLower.includes('final answer') ||
|
|
answerLower.includes('komme nicht drauf') || answerLower.includes("can't figure it out") ||
|
|
answerLower.includes('verrate mir') || answerLower.includes('tell me the answer');
|
|
|
|
// Enhanced confusion detection
|
|
const isExpressingConfusion = answerLower.includes('verstehe nicht') || answerLower.includes("don't understand") ||
|
|
answerLower.includes('versteh das nicht') || answerLower.includes('verstehe es nicht') ||
|
|
answerLower.includes('ich verstehe nicht warum') || answerLower.includes("i don't understand why") ||
|
|
answerLower.includes('aber ich verstehe nicht') || answerLower.includes("but i don't understand") ||
|
|
answerLower.includes('verstehe den zusammenhang nicht') || answerLower.includes("don't understand the connection") ||
|
|
answerLower.includes('das erklärt nicht') || answerLower.includes("that doesn't explain") ||
|
|
answerLower.includes('das beantwortet nicht') || answerLower.includes("that doesn't answer") ||
|
|
answerLower.includes('hab ich doch schon gesagt') || answerLower.includes('already said that') ||
|
|
answerLower.includes('schon gesagt') || answerLower.includes('already told you') ||
|
|
answerLower.includes('fällt mir nicht ein') || answerLower.includes("can't think of") ||
|
|
answerLower.includes('mehr fällt mir nicht ein') || isAskingForHelp;
|
|
|
|
// Check if it's a substantial answer (has thinking beyond just "don't know")
|
|
const hasSubstantialThinking = answer.length > 15 && (
|
|
answerLower.includes('vielleicht') || answerLower.includes('maybe') || answerLower.includes('könnte') || answerLower.includes('could') ||
|
|
answerLower.includes('hat es') || answerLower.includes('does it') || answerLower.includes('is it') || answerLower.includes('liegt es') ||
|
|
answerLower.includes('temperatur') || answerLower.includes('temperature') || answerLower.includes('wetter') || answerLower.includes('weather') ||
|
|
answer.includes('?') // Contains a question showing thinking
|
|
);
|
|
|
|
// Pure "don't know" without additional thinking
|
|
const isPureDontKnow = (hasDontKnowPhrase || isOnlyWhyQuestion || answer.trim().length < 5) && !hasSubstantialThinking && !isExpressingConfusion && !isAskingForHelp;
|
|
|
|
// Check conversation history for repeated "don't know" responses (Chat Mode)
|
|
const conversationHistoryChat = Array.from(this.conversationContainer.querySelectorAll('.user-message .message-content'))
|
|
.map(el => el.textContent.toLowerCase().trim())
|
|
.slice(-4); // Last 4 user responses
|
|
|
|
console.log('🔍 Recent conversation history (Chat):', conversationHistoryChat);
|
|
|
|
const recentDontKnowCountChat = conversationHistoryChat.filter(msg =>
|
|
msg.includes('weiß nicht') || msg.includes('weis nicht') || msg.includes('weiss nicht') ||
|
|
msg.includes("don't know") || msg.includes('i dont know') || msg.includes('keine ahnung') ||
|
|
msg.includes('das weiß ich nicht') || msg.includes('das weiss ich nicht') ||
|
|
msg.includes('das weis ich nicht') || msg.includes('weiß ich auch nicht')
|
|
).length;
|
|
|
|
console.log(`🔢 Recent "don't know" count (Chat): ${recentDontKnowCountChat}`);
|
|
|
|
// If child has said "don't know" 2 or more times recently, they need help
|
|
const needsExplanationDueToRepeatedDontKnowChat = recentDontKnowCountChat >= 2;
|
|
|
|
let response;
|
|
|
|
if (isExpressingConfusion || needsExplanationDueToRepeatedDontKnowChat) {
|
|
// Child is expressing confusion or repeatedly saying "don't know" - provide a simple explanation
|
|
console.log('📤 Sending explanation request for confused child or repeated dont know (Chat mode)');
|
|
|
|
const contextReason = needsExplanationDueToRepeatedDontKnowChat ? 'repeated_dont_know' : 'confusion_explanation';
|
|
|
|
response = await fetch('/api/respond-to-answer', {
|
|
method: 'POST',
|
|
headers: {
|
|
'Content-Type': 'application/json',
|
|
},
|
|
body: JSON.stringify({
|
|
answer: answer,
|
|
question: currentQuestion,
|
|
originalTopic: originalQuestion,
|
|
language: this.currentLanguage,
|
|
context: contextReason,
|
|
stepIndex: questionIndex,
|
|
instructions: needsExplanationDueToRepeatedDontKnowChat
|
|
? (this.currentLanguage === 'de'
|
|
? 'Das Kind braucht jetzt eine einfache, klare Erklärung. Es hat bereits mehrmals "weiß nicht" gesagt. Gib eine vollständige Antwort mit praktischen Beispielen. Erkläre das Konzept Schritt für Schritt mit alltäglichen Vergleichen. Keine weiteren Fragen - das Kind braucht Wissen!'
|
|
: 'The child needs a simple, clear explanation now. They have said "don\'t know" multiple times. Give a complete answer with practical examples. Explain the concept step by step with everyday comparisons. No more questions - the child needs knowledge!')
|
|
: (this.currentLanguage === 'de'
|
|
? 'Das Kind ist verwirrt und braucht eine einfache Erklärung. Gib eine klare, konkrete Antwort für Kinder. Erkläre das Konzept mit alltäglichen Beispielen. Verwende einfache Wörter und lade zum Ausprobieren ein. Keine weiteren Fragen stellen - erst erklären!'
|
|
: 'The child is confused and needs a simple explanation. Give a clear, concrete answer for children. Explain the concept with everyday examples. Use simple words and invite experimentation. Don\'t ask more questions - explain first!')
|
|
})
|
|
});
|
|
} else if (isNegative || isPureDontKnow) {
|
|
// For pure "no" or "don't know" answers without thinking, get Socratic guidance questions
|
|
console.log('📤 Sending guidance request to /api/ask for Socratic questioning');
|
|
|
|
const guidancePrompt = this.currentLanguage === 'de'
|
|
? `Ein Kind hat "${answer}" geantwortet auf die Frage "${currentQuestion}". Das Kind weiß die Antwort nicht. Führe es durch 2-3 aufbauende Socratic Fragen zur Entdeckung, ohne direkte Antworten zu geben. Beginne mit einfachen Beobachtungen, die das Kind kennt.`
|
|
: `A child answered "${answer}" to the question "${currentQuestion}". The child doesn't know the answer. Guide them through 2-3 building Socratic questions to discovery without giving direct answers. Start with simple observations the child would know.`;
|
|
|
|
response = await fetch('/api/ask', {
|
|
method: 'POST',
|
|
headers: {
|
|
'Content-Type': 'application/json',
|
|
},
|
|
body: JSON.stringify({
|
|
question: guidancePrompt,
|
|
language: this.currentLanguage
|
|
})
|
|
});
|
|
} else {
|
|
// For substantial answers, acknowledge and validate
|
|
console.log('📤 Sending validation request to /api/respond-to-answer');
|
|
|
|
response = await fetch('/api/respond-to-answer', {
|
|
method: 'POST',
|
|
headers: {
|
|
'Content-Type': 'application/json',
|
|
},
|
|
body: JSON.stringify({
|
|
answer: answer,
|
|
question: currentQuestion,
|
|
language: this.currentLanguage,
|
|
stepIndex: stepIndex
|
|
})
|
|
});
|
|
}
|
|
|
|
console.log('📥 Server response status:', response.status);
|
|
|
|
if (!response.ok) {
|
|
throw new Error(`HTTP error! status: ${response.status}`);
|
|
}
|
|
|
|
const data = await response.json();
|
|
console.log('✅ Server response data:', data);
|
|
|
|
let responseContent = '';
|
|
|
|
if (data.success) {
|
|
if (data.guidance && data.guidance.steps) {
|
|
// Response from /api/ask with Socratic guidance steps
|
|
const steps = data.guidance.steps.slice(0, 3); // Limit to 3 steps
|
|
responseContent = steps.map((step, index) =>
|
|
`<p><strong>${index + 1}.</strong> ${step.text}</p>`
|
|
).join('');
|
|
} else if (data.response) {
|
|
// Simple response from /api/respond-to-answer
|
|
responseContent = `<p>${data.response}</p>`;
|
|
} else {
|
|
// Fallback
|
|
responseContent = this.currentLanguage === 'de'
|
|
? '<p>🤔 Das ist eine interessante Frage! Lass uns zusammen darüber nachdenken...</p>'
|
|
: '<p>🤔 That\'s an interesting question! Let\'s think about it together...</p>';
|
|
}
|
|
|
|
responseDiv.innerHTML = `
|
|
<div class="ai-response-content">
|
|
<div class="message-header">
|
|
<span class="ai-avatar">🤖</span>
|
|
<span class="ai-label">${this.getTranslation('ai-teacher')}</span>
|
|
</div>
|
|
<div class="message-content">
|
|
${responseContent}
|
|
</div>
|
|
</div>
|
|
`;
|
|
} else {
|
|
throw new Error(data.error || 'Failed to generate response');
|
|
}
|
|
|
|
} catch (error) {
|
|
console.error('❌ Error generating AI response:', error);
|
|
// Fallback response
|
|
responseDiv.innerHTML = `
|
|
<div class="ai-response-content">
|
|
<div class="message-header">
|
|
<span class="ai-avatar">🤖</span>
|
|
<span class="ai-label">${this.getTranslation('ai-teacher')}</span>
|
|
</div>
|
|
<div class="message-content">
|
|
<p>${this.getTranslation('great-thinking-fallback')}</p>
|
|
</div>
|
|
</div>
|
|
`;
|
|
}
|
|
|
|
// Show next question or reveal option after response
|
|
setTimeout(() => {
|
|
const nextStepIndex = stepIndex + 1;
|
|
const nextStep = document.querySelector(`.step-${nextStepIndex}`);
|
|
|
|
if (nextStep) {
|
|
// Show next question after a delay
|
|
setTimeout(() => {
|
|
nextStep.classList.add('visible');
|
|
nextStep.scrollIntoView({ behavior: 'smooth' });
|
|
}, 1500);
|
|
} else {
|
|
// All questions answered, show reveal option
|
|
setTimeout(() => {
|
|
const revealSection = document.querySelector('.reveal-section');
|
|
if (revealSection) {
|
|
revealSection.style.display = 'block';
|
|
revealSection.scrollIntoView({ behavior: 'smooth' });
|
|
}
|
|
}, 2000);
|
|
}
|
|
}, 500);
|
|
}
|
|
|
|
// Generate AI response for chat system
|
|
async generateChatAIResponse(answer, questionIndex) {
|
|
console.log('🚀 generateChatAIResponse called with:', { answer, questionIndex });
|
|
|
|
try {
|
|
// Get current question context
|
|
const currentQuestion = this.questions && this.questions[questionIndex]
|
|
? this.questions[questionIndex]
|
|
: 'the current question';
|
|
|
|
// Get the original topic for better context
|
|
const originalQuestion = this.questionInput ? this.questionInput.value.trim() : '';
|
|
|
|
// Analyze the answer to determine the appropriate response type
|
|
const answerLower = answer.toLowerCase().trim();
|
|
const isNegative = answerLower === 'no' || answerLower === 'nein' || answerLower === 'nope' || answerLower === 'nei';
|
|
|
|
// Check for pure "don't know" responses (without additional thinking)
|
|
const hasDontKnowPhrase = answerLower.includes("don't know") || answerLower.includes('weiß nicht') || answerLower.includes('weis nicht') ||
|
|
answerLower.includes('weiss nicht') || answerLower.includes('keine ahnung') || answerLower.includes('keinen plan') ||
|
|
answerLower.includes('ich weiß es nicht') || answerLower.includes('weis es nicht') || answerLower.includes('weiss es nicht') ||
|
|
answerLower.includes('i dont know') || answerLower.includes("i don't know") || answerLower.includes('hab keine ahnung');
|
|
|
|
const isOnlyWhyQuestion = answerLower === 'warum' || answerLower === 'why' || answerLower === 'warum?' || answerLower === 'why?';
|
|
|
|
// Check if child is explicitly asking for help or the answer
|
|
const isAskingForHelp = answerLower.includes('bitte verrate es mir') || answerLower.includes('please tell me') ||
|
|
answerLower.includes('was bedeutet es') || answerLower.includes('what does it mean') ||
|
|
answerLower.includes('wie komme ich zur') || answerLower.includes('how do i get to') ||
|
|
answerLower.includes('finalen antwort') || answerLower.includes('final answer') ||
|
|
answerLower.includes('komme nicht drauf') || answerLower.includes("can't figure it out") ||
|
|
answerLower.includes('verrate mir') || answerLower.includes('tell me the answer');
|
|
|
|
// Enhanced confusion detection
|
|
const isExpressingConfusion = answerLower.includes('verstehe nicht') || answerLower.includes("don't understand") ||
|
|
answerLower.includes('versteh das nicht') || answerLower.includes('verstehe es nicht') ||
|
|
answerLower.includes('ich verstehe nicht warum') || answerLower.includes("i don't understand why") ||
|
|
answerLower.includes('aber ich verstehe nicht') || answerLower.includes("but i don't understand") ||
|
|
answerLower.includes('verstehe den zusammenhang nicht') || answerLower.includes("don't understand the connection") ||
|
|
answerLower.includes('das erklärt nicht') || answerLower.includes("that doesn't explain") ||
|
|
answerLower.includes('das beantwortet nicht') || answerLower.includes("that doesn't answer") ||
|
|
answerLower.includes('hab ich doch schon gesagt') || answerLower.includes('already said that') ||
|
|
answerLower.includes('schon gesagt') || answerLower.includes('already told you') ||
|
|
answerLower.includes('fällt mir nicht ein') || answerLower.includes("can't think of") ||
|
|
answerLower.includes('mehr fällt mir nicht ein') || isAskingForHelp;
|
|
|
|
// Check if it's a substantial answer (has thinking beyond "don't know")
|
|
const hasSubstantialThinking = answer.length > 15 && (
|
|
answerLower.includes('vielleicht') || answerLower.includes('maybe') || answerLower.includes('könnte') || answerLower.includes('could') ||
|
|
answerLower.includes('hat es') || answerLower.includes('does it') || answerLower.includes('is it') || answerLower.includes('liegt es') ||
|
|
answerLower.includes('temperatur') || answerLower.includes('temperature') || answerLower.includes('wetter') || answerLower.includes('weather') ||
|
|
answer.includes('?') // Contains a question showing thinking
|
|
);
|
|
|
|
// Pure "don't know" without additional thinking
|
|
const isPureDontKnow = (hasDontKnowPhrase || isOnlyWhyQuestion || answer.trim().length < 5) && !hasSubstantialThinking && !isExpressingConfusion && !isAskingForHelp;
|
|
|
|
// Check conversation history for repeated "don't know" responses
|
|
const conversationHistory = Array.from(this.conversationContainer.querySelectorAll('.user-message .message-content'))
|
|
.map(el => el.textContent.toLowerCase().trim())
|
|
.slice(-4); // Last 4 user responses
|
|
|
|
console.log('🔍 Recent conversation history:', conversationHistory);
|
|
|
|
const recentDontKnowCount = conversationHistory.filter(msg =>
|
|
msg.includes('weiß nicht') || msg.includes('weis nicht') || msg.includes('weiss nicht') ||
|
|
msg.includes("don't know") || msg.includes('i dont know') || msg.includes('keine ahnung') ||
|
|
msg.includes('das weiß ich nicht') || msg.includes('das weiss ich nicht') ||
|
|
msg.includes('das weis ich nicht') || msg.includes('weiß ich auch nicht')
|
|
).length;
|
|
|
|
console.log(`🔢 Recent "don't know" count: ${recentDontKnowCount}`);
|
|
|
|
// If child has said "don't know" 2 or more times recently, they need help
|
|
const needsExplanationDueToRepeatedDontKnow = recentDontKnowCount >= 2;
|
|
|
|
// Check if child is asking for a definition or explanation
|
|
const isAskingForDefinition = answerLower.startsWith('was ist') || answerLower.startsWith('what is') ||
|
|
answerLower.includes('was bedeutet') || answerLower.includes('what does') ||
|
|
answerLower.includes('was heißt') || answerLower.includes('what means') ||
|
|
answerLower.includes('was hat') || answerLower.includes('what has') ||
|
|
answerLower.includes('wo ist') || answerLower.includes('where is') ||
|
|
answerLower.includes('wo sind') || answerLower.includes('where are') ||
|
|
answerLower.includes('wie funktioniert') || answerLower.includes('how does') ||
|
|
answerLower.includes('warum ist') || answerLower.includes('why is') ||
|
|
(answerLower.includes('?') && (answerLower.includes('ist ein') || answerLower.includes('is a')));
|
|
|
|
let response;
|
|
|
|
if (isExpressingConfusion || isAskingForHelp || needsExplanationDueToRepeatedDontKnow) {
|
|
// Child is expressing confusion, asking for help, or repeatedly saying "don't know" - provide explanation
|
|
console.log('📤 Sending explanation request for confused child, help request, or repeated dont know');
|
|
|
|
const contextReason = needsExplanationDueToRepeatedDontKnow ? 'repeated_dont_know' : 'confusion_explanation';
|
|
|
|
response = await fetch('/api/respond-to-answer', {
|
|
method: 'POST',
|
|
headers: {
|
|
'Content-Type': 'application/json',
|
|
},
|
|
body: JSON.stringify({
|
|
answer: answer,
|
|
question: currentQuestion,
|
|
originalTopic: originalQuestion,
|
|
language: this.currentLanguage,
|
|
context: contextReason,
|
|
stepIndex: questionIndex,
|
|
instructions: needsExplanationDueToRepeatedDontKnow
|
|
? (this.currentLanguage === 'de'
|
|
? 'Das Kind braucht jetzt eine einfache, klare Erklärung. Es hat bereits mehrmals "weiß nicht" gesagt oder ist verwirrt. Gib eine vollständige Antwort mit praktischen Beispielen. Erkläre das Konzept Schritt für Schritt mit alltäglichen Vergleichen. Keine weiteren Fragen - das Kind braucht Wissen!'
|
|
: 'The child needs a simple, clear explanation now. They have said "don\'t know" multiple times or are confused. Give a complete answer with practical examples. Explain the concept step by step with everyday comparisons. No more questions - the child needs knowledge!')
|
|
: (this.currentLanguage === 'de'
|
|
? 'Das Kind ist verwirrt und braucht eine einfache Erklärung. Gib eine klare, konkrete Antwort für Kinder. Erkläre das Konzept mit alltäglichen Beispielen. Verwende einfache Worte und lade zum Experimentieren ein. Keine weiteren Fragen - erst erklären!'
|
|
: 'The child is confused and needs a simple explanation. Give a clear, concrete answer for children. Explain the concept with everyday examples. Use simple words and invite experimentation. Don\'t ask more questions - explain first!')
|
|
})
|
|
});
|
|
} else if (isAskingForDefinition) {
|
|
// Child is asking for a definition - provide a clear definition response
|
|
console.log('📤 Sending definition response request');
|
|
|
|
response = await fetch('/api/respond-to-answer', {
|
|
method: 'POST',
|
|
headers: {
|
|
'Content-Type': 'application/json',
|
|
},
|
|
body: JSON.stringify({
|
|
answer: answer,
|
|
question: currentQuestion,
|
|
originalTopic: originalQuestion,
|
|
language: this.currentLanguage,
|
|
context: 'definition_request',
|
|
stepIndex: questionIndex,
|
|
instructions: this.currentLanguage === 'de'
|
|
? 'Das Kind fragt nach einer Definition. Gib eine klare, kindgerechte Definition des Begriffs. Verwende einfache Sprache und Beispiele, die das Kind kennt. Lade das Kind ein, mehr Fragen zu stellen oder das Konzept zu erkunden.'
|
|
: 'The child is asking for a definition. Provide a clear, child-friendly definition of the term. Use simple language and examples the child would know. Invite the child to ask more questions or explore the concept.'
|
|
})
|
|
});
|
|
} else if (isNegative || isPureDontKnow) {
|
|
// For pure "no" or "don't know" answers without thinking, get Socratic guidance questions
|
|
console.log('📤 Sending guidance request to /api/ask for Socratic questioning');
|
|
|
|
const guidancePrompt = this.currentLanguage === 'de'
|
|
? `Ein Kind hat "${answer}" geantwortet auf die Frage "${currentQuestion}" über das Thema "${originalQuestion}". Das Kind weiß die Antwort nicht und braucht einfachere Grundlagen. Gib KEINE weiteren abstrakten Fragen! Stattdessen: 1) Beschreibe eine einfache Beobachtung, die das Kind machen kann (z.B. "Hast du schon mal Licht durch ein Glas Wasser gesehen?") 2) Lade zum praktischen Ausprobieren ein 3) Baue auf dem auf, was das Kind bereits kennt. Verwende konkrete, sichtbare Beispiele statt abstrakter Konzepte.`
|
|
: `A child answered "${answer}" to the question "${currentQuestion}" about the topic "${originalQuestion}". The child doesn't know and needs simpler foundations. Give NO more abstract questions! Instead: 1) Describe a simple observation the child can make (e.g. "Have you ever seen light through a glass of water?") 2) Invite practical experimentation 3) Build on what the child already knows. Use concrete, visible examples instead of abstract concepts.`;
|
|
|
|
response = await fetch('/api/ask', {
|
|
method: 'POST',
|
|
headers: {
|
|
'Content-Type': 'application/json',
|
|
},
|
|
body: JSON.stringify({
|
|
question: guidancePrompt,
|
|
language: this.currentLanguage
|
|
})
|
|
});
|
|
} else {
|
|
// For substantial answers, acknowledge and validate
|
|
console.log('📤 Sending validation request to /api/respond-to-answer');
|
|
|
|
response = await fetch('/api/respond-to-answer', {
|
|
method: 'POST',
|
|
headers: {
|
|
'Content-Type': 'application/json',
|
|
},
|
|
body: JSON.stringify({
|
|
answer: answer,
|
|
question: currentQuestion,
|
|
language: this.currentLanguage,
|
|
stepIndex: questionIndex
|
|
})
|
|
});
|
|
}
|
|
|
|
console.log('📥 Chat server response status:', response.status);
|
|
|
|
if (!response.ok) {
|
|
throw new Error(`HTTP error! status: ${response.status}`);
|
|
}
|
|
|
|
const data = await response.json();
|
|
console.log('✅ Chat server response data:', data);
|
|
|
|
let responseContent = '';
|
|
|
|
if (data.success) {
|
|
if (data.guidance && data.guidance.steps) {
|
|
// Response from /api/ask with Socratic guidance steps
|
|
const steps = data.guidance.steps.slice(0, 3); // Limit to 3 steps
|
|
responseContent = steps.map((step, index) =>
|
|
`<p><strong>${index + 1}.</strong> ${step.text}</p>`
|
|
).join('');
|
|
} else if (data.response) {
|
|
// Simple response from /api/respond-to-answer
|
|
responseContent = `<p>${data.response}</p>`;
|
|
} else {
|
|
// Fallback
|
|
responseContent = this.currentLanguage === 'de'
|
|
? '<p>🤔 Das ist eine interessante Frage! Lass uns zusammen darüber nachdenken...</p>'
|
|
: '<p>🤔 That\'s an interesting question! Let\'s think about it together...</p>';
|
|
}
|
|
|
|
// Add AI response bubble
|
|
const responseBubble = document.createElement('div');
|
|
responseBubble.className = 'chat-message ai-message';
|
|
responseBubble.innerHTML = `
|
|
<div class="message-header">
|
|
<span class="ai-avatar">🤖</span>
|
|
<span class="ai-label">${this.getTranslation('ai-teacher')}</span>
|
|
</div>
|
|
<div class="message-content">
|
|
${responseContent}
|
|
</div>
|
|
`;
|
|
|
|
this.conversationContainer.appendChild(responseBubble);
|
|
|
|
// Show AI response with animation
|
|
setTimeout(() => {
|
|
responseBubble.classList.add('visible');
|
|
this.scrollToBottomSmoothly();
|
|
}, 100);
|
|
|
|
// Determine next action based on response type
|
|
setTimeout(() => {
|
|
if (data.guidance && data.guidance.steps && (isNegative || isPureDontKnow)) {
|
|
// For pure "don't know" responses with Socratic guidance, continue to next question immediately
|
|
this.continueToNextQuestion();
|
|
} else {
|
|
// Check if AI response contains a question that needs answering
|
|
const hasQuestion = data.response && data.response.includes('?');
|
|
|
|
// Check if AI response contains explanations or new concepts that might trigger questions
|
|
const containsExplanation = data.response && (
|
|
data.response.includes('passiert, weil') || data.response.includes('happens because') ||
|
|
data.response.includes('Das liegt daran') || data.response.includes('This is because') ||
|
|
data.response.includes('Prisma') || data.response.includes('prism') ||
|
|
data.response.includes('Licht') || data.response.includes('light') ||
|
|
data.response.includes('besteht aus') || data.response.includes('consists of') ||
|
|
data.response.includes('brechen') || data.response.includes('break') ||
|
|
data.response.length > 150 // Longer responses likely contain explanations
|
|
);
|
|
|
|
if (hasQuestion || containsExplanation) {
|
|
// AI asked a question or gave an explanation - provide input area for follow-up questions
|
|
this.addUserInputArea();
|
|
} else {
|
|
// Simple response without question or explanation, show choice buttons
|
|
this.addContinueChoiceButtons();
|
|
}
|
|
}
|
|
}, 1500);
|
|
} else {
|
|
throw new Error(data.error || 'Failed to generate response');
|
|
}
|
|
|
|
} catch (error) {
|
|
console.error('❌ Error generating chat AI response:', error);
|
|
|
|
// Fallback response
|
|
const responseBubble = document.createElement('div');
|
|
responseBubble.className = 'chat-message ai-message';
|
|
responseBubble.innerHTML = `
|
|
<div class="message-header">
|
|
<span class="ai-avatar">🤖</span>
|
|
<span class="ai-label">${this.getTranslation('ai-teacher')}</span>
|
|
</div>
|
|
<div class="message-content">
|
|
<p>${this.currentLanguage === 'de'
|
|
? 'Interessant! Lass uns das mal genauer betrachten... Was denkst du, was als nächstes passieren könnte? 🤔'
|
|
: 'Interesting! Let\'s look at this more closely... What do you think might happen next? 🤔'}</p>
|
|
</div>
|
|
`;
|
|
|
|
this.conversationContainer.appendChild(responseBubble);
|
|
|
|
setTimeout(() => {
|
|
responseBubble.classList.add('visible');
|
|
this.scrollToBottomSmoothly();
|
|
}, 100);
|
|
|
|
setTimeout(() => {
|
|
this.addContinueChoiceButtons();
|
|
}, 1500);
|
|
}
|
|
}
|
|
|
|
addContinueChoiceButtons() {
|
|
// Get translations before creating HTML to avoid context issues
|
|
const exploreDeeperText = this.getTranslation('explore-deeper') || (this.currentLanguage === 'de' ? "Ich möchte das tiefer erforschen" : "I want to explore this deeper");
|
|
const continueLearningText = this.getTranslation('continue-learning') || (this.currentLanguage === 'de' ? "Mit dem nächsten Thema fortfahren" : "Continue with next topic");
|
|
const tellMeMoreText = this.getTranslation('tell-me-more') || (this.currentLanguage === 'de' ? "Erzähl mir mehr! 🤔" : "Tell me more! 🤔");
|
|
const nextQuestionText = this.getTranslation('next-question') || (this.currentLanguage === 'de' ? "Nächste Frage! ➡️" : "Next question! ➡️");
|
|
|
|
const choiceContainer = document.createElement('div');
|
|
choiceContainer.className = 'choice-container';
|
|
choiceContainer.innerHTML = `
|
|
<div class="choice-prompt">
|
|
<p>💭 ${exploreDeeperText} ${this.currentLanguage === 'de' ? 'oder' : 'or'} ${continueLearningText}?</p>
|
|
</div>
|
|
<div class="choice-buttons">
|
|
<button class="choice-btn explore-btn" onclick="kidsAI.exploreCurrentTopicDeeper()">
|
|
<span class="btn-icon">🔍</span> ${tellMeMoreText}
|
|
</button>
|
|
<button class="choice-btn next-btn" onclick="kidsAI.continueToNextQuestion()">
|
|
<span class="btn-icon">➡️</span> ${nextQuestionText}
|
|
</button>
|
|
</div>
|
|
`;
|
|
|
|
this.conversationContainer.appendChild(choiceContainer);
|
|
|
|
// Animate in the choices
|
|
setTimeout(() => {
|
|
choiceContainer.classList.add('visible');
|
|
this.scrollToBottomSmoothly();
|
|
}, 200);
|
|
}
|
|
|
|
// Handle choice to explore current topic deeper
|
|
async exploreCurrentTopicDeeper() {
|
|
// Remove choice buttons
|
|
const choiceContainer = document.querySelector('.choice-container');
|
|
if (choiceContainer) {
|
|
choiceContainer.remove();
|
|
}
|
|
|
|
// Get the current question and user's last answer for context
|
|
const currentQuestion = this.questions[this.currentQuestionIndex];
|
|
const lastUserMessage = Array.from(this.conversationContainer.querySelectorAll('.user-message'))
|
|
.pop()?.querySelector('.message-content p')?.textContent || '';
|
|
|
|
try {
|
|
// Request deeper exploration from the server
|
|
const response = await fetch('/api/explore-deeper', {
|
|
method: 'POST',
|
|
headers: {
|
|
'Content-Type': 'application/json',
|
|
},
|
|
body: JSON.stringify({
|
|
question: currentQuestion,
|
|
userAnswer: lastUserMessage,
|
|
language: this.currentLanguage,
|
|
context: 'deeper_exploration',
|
|
instructions: this.currentLanguage === 'de'
|
|
? 'Stelle eine faszinierende Folgefrage, die das Kind dazu bringt, tiefer über das Thema nachzudenken. Verwende "Was würde passieren wenn...?" oder "Hast du schon mal bemerkt...?" Ermutige zur Beobachtung und zum Experimentieren.'
|
|
: 'Ask a fascinating follow-up question that makes the child think deeper about the topic. Use "What would happen if...?" or "Have you ever noticed...?" Encourage observation and experimentation.'
|
|
})
|
|
});
|
|
|
|
if (!response.ok) {
|
|
throw new Error(`HTTP error! status: ${response.status}`);
|
|
}
|
|
|
|
const data = await response.json();
|
|
|
|
if (data.success && data.response) {
|
|
// Add deeper exploration response
|
|
const explorationBubble = document.createElement('div');
|
|
explorationBubble.className = 'chat-message ai-message exploration';
|
|
explorationBubble.innerHTML = `
|
|
<div class="message-header">
|
|
<span class="ai-avatar">🤖</span>
|
|
<span class="ai-label">${this.getTranslation('ai-teacher')}</span>
|
|
</div>
|
|
<div class="message-content">
|
|
<p>${data.response}</p>
|
|
</div>
|
|
`;
|
|
|
|
this.conversationContainer.appendChild(explorationBubble);
|
|
|
|
setTimeout(() => {
|
|
explorationBubble.classList.add('visible');
|
|
this.scrollToBottomSmoothly();
|
|
}, 100);
|
|
|
|
// Check if the response contains a question that needs an answer
|
|
const hasQuestion = data.response.includes('?');
|
|
|
|
if (hasQuestion) {
|
|
// Add input area for user to respond to the deeper exploration question
|
|
setTimeout(() => {
|
|
this.addDeeperExplorationInputArea();
|
|
}, 1500);
|
|
} else {
|
|
// No question, just add choice buttons again
|
|
setTimeout(() => {
|
|
this.addContinueChoiceButtons();
|
|
}, 2000);
|
|
}
|
|
} else {
|
|
throw new Error(data.error || 'Failed to get deeper exploration');
|
|
}
|
|
|
|
} catch (error) {
|
|
console.error('❌ Error exploring deeper:', error);
|
|
|
|
// Fallback deeper exploration
|
|
const explorationBubble = document.createElement('div');
|
|
explorationBubble.className = 'chat-message ai-message exploration';
|
|
explorationBubble.innerHTML = `
|
|
<div class="message-header">
|
|
<span class="ai-avatar">🤖</span>
|
|
<span class="ai-label">${this.getTranslation('ai-teacher')}</span>
|
|
</div>
|
|
<div class="message-content">
|
|
<p>${this.currentLanguage === 'de'
|
|
? '🔍 Das ist wirklich faszinierend! Hast du schon mal beobachtet, was passiert, wenn du durch ein Prisma oder ein Wasserglas schaust? Was könntest du damit entdecken?'
|
|
: '🔍 This is really fascinating! Have you ever observed what happens when you look through a prism or a water glass? What could you discover with that?'}</p>
|
|
</div>
|
|
`;
|
|
|
|
this.conversationContainer.appendChild(explorationBubble);
|
|
|
|
setTimeout(() => {
|
|
explorationBubble.classList.add('visible');
|
|
this.scrollToBottomSmoothly();
|
|
}, 100);
|
|
|
|
// This fallback has a question, so add input area
|
|
setTimeout(() => {
|
|
this.addDeeperExplorationInputArea();
|
|
}, 1500);
|
|
}
|
|
}
|
|
|
|
// Add input area specifically for deeper exploration questions
|
|
addDeeperExplorationInputArea() {
|
|
const inputContainer = document.createElement('div');
|
|
inputContainer.className = 'user-input-container deeper-exploration';
|
|
inputContainer.innerHTML = `
|
|
<div class="input-prompt">
|
|
<p>💭 ${this.getTranslation('share-thoughts') || (this.currentLanguage === 'de' ? 'Teile deine Gedanken mit:' : 'Share your thoughts:')}</p>
|
|
</div>
|
|
<div class="input-area">
|
|
<textarea id="deeper-chat-input" placeholder="${this.getTranslation('type-thoughts') || (this.currentLanguage === 'de' ? 'Schreibe hier deine Gedanken...' : 'Type your thoughts here...')}"
|
|
rows="3" style="width: 100%; padding: 10px; border-radius: 8px; border: 2px solid #e2e8f0; font-size: 14px;"></textarea>
|
|
<button onclick="kidsAI.submitDeeperExplorationAnswer()" class="submit-btn" style="margin-top: 10px; padding: 10px 20px; background: #4ade80; color: white; border: none; border-radius: 8px; cursor: pointer;">
|
|
${this.getTranslation('submit') || (this.currentLanguage === 'de' ? 'Abschicken' : 'Submit')} 🚀
|
|
</button>
|
|
</div>
|
|
`;
|
|
|
|
this.conversationContainer.appendChild(inputContainer);
|
|
|
|
// Show input area with animation
|
|
setTimeout(() => {
|
|
inputContainer.classList.add('visible');
|
|
this.scrollToBottomSmoothly();
|
|
|
|
// Focus on textarea
|
|
const textarea = inputContainer.querySelector('#deeper-chat-input');
|
|
if (textarea) {
|
|
textarea.focus();
|
|
}
|
|
}, 200);
|
|
}
|
|
|
|
// Submit answer for deeper exploration
|
|
submitDeeperExplorationAnswer() {
|
|
const textarea = document.getElementById('deeper-chat-input');
|
|
if (!textarea) {
|
|
console.error('❌ Deeper chat input not found');
|
|
return;
|
|
}
|
|
|
|
const answer = textarea.value.trim();
|
|
if (!answer) {
|
|
const message = this.getTranslation('write-thoughts') || (this.currentLanguage === 'de' ? 'Bitte schreibe deine Gedanken auf! 🤔' : 'Please write down your thoughts! 🤔');
|
|
this.showMessage(message, 'warning');
|
|
return;
|
|
}
|
|
|
|
// Add user message bubble
|
|
const userBubble = document.createElement('div');
|
|
userBubble.className = 'chat-message user-message';
|
|
userBubble.innerHTML = `
|
|
<div class="message-header">
|
|
<span class="user-avatar">👤</span>
|
|
<span class="user-label">${this.getTranslation('you') || (this.currentLanguage === 'de' ? 'Du' : 'You')}</span>
|
|
</div>
|
|
<div class="message-content">
|
|
<p>${answer}</p>
|
|
</div>
|
|
`;
|
|
|
|
// Remove input container and add user message
|
|
const inputContainer = document.querySelector('.user-input-container.deeper-exploration');
|
|
if (inputContainer) {
|
|
inputContainer.remove();
|
|
}
|
|
|
|
this.conversationContainer.appendChild(userBubble);
|
|
|
|
// Show user message with animation
|
|
setTimeout(() => {
|
|
userBubble.classList.add('visible');
|
|
this.scrollToBottomSmoothly();
|
|
}, 100);
|
|
|
|
// Generate AI response to the deeper exploration answer
|
|
setTimeout(() => {
|
|
this.generateDeeperExplorationResponse(answer);
|
|
}, 500);
|
|
}
|
|
|
|
// Generate AI response to deeper exploration answer
|
|
async generateDeeperExplorationResponse(answer) {
|
|
console.log('🚀 generateDeeperExplorationResponse called with:', answer);
|
|
|
|
try {
|
|
// Get the last AI question for context
|
|
const lastAIMessages = Array.from(this.conversationContainer.querySelectorAll('.ai-message.exploration'));
|
|
const lastExplorationQuestion = lastAIMessages.length > 0
|
|
? lastAIMessages[lastAIMessages.length - 1].querySelector('.message-content p')?.textContent || ''
|
|
: '';
|
|
|
|
console.log('📤 Sending deeper exploration response request:', { answer, lastExplorationQuestion });
|
|
|
|
// Call server API for contextual response
|
|
const response = await fetch('/api/respond-to-answer', {
|
|
method: 'POST',
|
|
headers: {
|
|
'Content-Type': 'application/json',
|
|
},
|
|
body: JSON.stringify({
|
|
answer: answer,
|
|
question: lastExplorationQuestion,
|
|
language: this.currentLanguage,
|
|
context: 'deeper_exploration_response',
|
|
instructions: this.currentLanguage === 'de'
|
|
? 'Du hilfst einem Kind beim tieferen Verstehen. Gib KEINE direkten Antworten oder Erklärungen. Stelle interessante Folgefragen, die das Kind zum Experimentieren oder Beobachten ermutigen. Verwende Analogien und lade zu eigenen Entdeckungen ein.'
|
|
: 'You are helping a child understand deeper. Give NO direct answers or explanations. Ask interesting follow-up questions that encourage the child to experiment or observe. Use analogies and invite them to make their own discoveries.'
|
|
})
|
|
});
|
|
|
|
console.log('📥 Deeper exploration response status:', response.status);
|
|
|
|
if (!response.ok) {
|
|
throw new Error(`HTTP error! status: ${response.status}`);
|
|
}
|
|
|
|
const data = await response.json();
|
|
console.log('✅ Deeper exploration response data:', data);
|
|
|
|
if (data.success && data.response) {
|
|
// Add AI response bubble
|
|
const responseBubble = document.createElement('div');
|
|
responseBubble.className = 'chat-message ai-message';
|
|
responseBubble.innerHTML = `
|
|
<div class="message-header">
|
|
<span class="ai-avatar">🤖</span>
|
|
<span class="ai-label">${this.getTranslation('ai-teacher')}</span>
|
|
</div>
|
|
<div class="message-content">
|
|
<p>${data.response}</p>
|
|
</div>
|
|
`;
|
|
|
|
this.conversationContainer.appendChild(responseBubble);
|
|
|
|
// Show AI response with animation
|
|
setTimeout(() => {
|
|
responseBubble.classList.add('visible');
|
|
this.scrollToBottomSmoothly();
|
|
}, 100);
|
|
|
|
// Add choice buttons after response to continue or move on
|
|
setTimeout(() => {
|
|
this.addContinueChoiceButtons();
|
|
}, 1500);
|
|
} else {
|
|
throw new Error(data.error || 'Failed to generate response');
|
|
}
|
|
|
|
} catch (error) {
|
|
console.error('❌ Error generating deeper exploration response:', error);
|
|
|
|
// Fallback response
|
|
const responseBubble = document.createElement('div');
|
|
responseBubble.className = 'chat-message ai-message';
|
|
responseBubble.innerHTML = `
|
|
<div class="message-header">
|
|
<span class="ai-avatar">🤖</span>
|
|
<span class="ai-label">${this.getTranslation('ai-teacher')}</span>
|
|
</div>
|
|
<div class="message-content">
|
|
<p>${this.currentLanguage === 'de'
|
|
? 'Hmm, das ist eine spannende Beobachtung! Was glaubst du, könnten wir herausfinden, wenn wir das genauer untersuchen? 🔍'
|
|
: 'Hmm, that\'s an exciting observation! What do you think we could discover if we investigate this more closely? 🔍'}</p>
|
|
</div>
|
|
`;
|
|
|
|
this.conversationContainer.appendChild(responseBubble);
|
|
|
|
setTimeout(() => {
|
|
responseBubble.classList.add('visible');
|
|
this.scrollToBottomSmoothly();
|
|
}, 100);
|
|
|
|
setTimeout(() => {
|
|
this.addContinueChoiceButtons();
|
|
}, 1500);
|
|
}
|
|
}
|
|
|
|
// Handle choice to continue to next question
|
|
continueToNextQuestion() {
|
|
// Remove choice buttons
|
|
const choiceContainer = document.querySelector('.choice-container');
|
|
if (choiceContainer) {
|
|
choiceContainer.remove();
|
|
}
|
|
|
|
// Move to next question
|
|
this.currentQuestionIndex++;
|
|
this.askNextQuestion();
|
|
}
|
|
|
|
// Ask the next question in the conversation
|
|
askNextQuestion() {
|
|
console.log('❓ askNextQuestion called, currentQuestionIndex:', this.currentQuestionIndex);
|
|
|
|
if (!this.questions || this.currentQuestionIndex >= this.questions.length) {
|
|
console.log('✅ All questions completed');
|
|
this.showCompletionMessage();
|
|
return;
|
|
}
|
|
|
|
const question = this.questions[this.currentQuestionIndex];
|
|
|
|
// Add AI question bubble
|
|
const questionBubble = document.createElement('div');
|
|
questionBubble.className = 'chat-message ai-message';
|
|
questionBubble.innerHTML = `
|
|
<div class="message-header">
|
|
<span class="ai-avatar">🤖</span>
|
|
<span class="ai-label">${this.getTranslation('ai-teacher') || 'AI Teacher'}</span>
|
|
</div>
|
|
<div class="message-content">
|
|
<p>${question}</p>
|
|
</div>
|
|
`;
|
|
|
|
this.conversationContainer.appendChild(questionBubble);
|
|
|
|
// Show question with animation
|
|
setTimeout(() => {
|
|
questionBubble.classList.add('visible');
|
|
this.scrollToBottomSmoothly();
|
|
}, 100);
|
|
|
|
// Add input area for user response
|
|
setTimeout(() => {
|
|
this.addUserInputArea();
|
|
}, 1000);
|
|
}
|
|
|
|
// Add input area for user to respond
|
|
addUserInputArea() {
|
|
// Remove any existing input areas to avoid duplicates
|
|
const existingInputs = this.conversationContainer.querySelectorAll('.user-input-container');
|
|
existingInputs.forEach(input => input.remove());
|
|
|
|
// Generate unique ID for this input
|
|
const inputId = `chat-input-${Date.now()}`;
|
|
|
|
const inputContainer = document.createElement('div');
|
|
inputContainer.className = 'user-input-container';
|
|
inputContainer.innerHTML = `
|
|
<div class="input-prompt">
|
|
<p>💭 ${this.getTranslation('share-thoughts') || (this.currentLanguage === 'de' ? 'Teile deine Gedanken mit:' : 'Share your thoughts:')}</p>
|
|
</div>
|
|
<div class="input-area">
|
|
<textarea id="${inputId}" placeholder="${this.getTranslation('type-thoughts') || (this.currentLanguage === 'de' ? 'Schreibe hier deine Gedanken...' : 'Type your thoughts here...')}"
|
|
rows="3" style="width: 100%; padding: 10px; border-radius: 8px; border: 2px solid #e2e8f0; font-size: 14px;"></textarea>
|
|
<button onclick="kidsAI.submitChatAnswerFromInput('${inputId}')" class="submit-btn" style="margin-top: 10px; padding: 10px 20px; background: #4ade80; color: white; border: none; border-radius: 8px; cursor: pointer;">
|
|
${this.getTranslation('submit') || (this.currentLanguage === 'de' ? 'Abschicken' : 'Submit')} 🚀
|
|
</button>
|
|
</div>
|
|
`;
|
|
|
|
this.conversationContainer.appendChild(inputContainer);
|
|
|
|
// Show input area with animation
|
|
setTimeout(() => {
|
|
inputContainer.classList.add('visible');
|
|
this.scrollToBottomSmoothly();
|
|
|
|
// Focus on textarea
|
|
const textarea = inputContainer.querySelector(`#${inputId}`);
|
|
if (textarea) {
|
|
textarea.focus();
|
|
}
|
|
}, 200);
|
|
}
|
|
|
|
// Submit chat answer from dynamic input with specific ID
|
|
submitChatAnswerFromInput(inputId) {
|
|
const textarea = document.getElementById(inputId);
|
|
if (!textarea) {
|
|
console.error('❌ Chat input not found with ID:', inputId);
|
|
return;
|
|
}
|
|
|
|
const answer = textarea.value.trim();
|
|
if (!answer) {
|
|
const message = this.getTranslation('write-thoughts') || (this.currentLanguage === 'de' ? 'Bitte schreibe deine Gedanken auf! 🤔' : 'Please write down your thoughts! 🤔');
|
|
this.showMessage(message, 'warning');
|
|
return;
|
|
}
|
|
|
|
// Add user message bubble
|
|
const userBubble = document.createElement('div');
|
|
userBubble.className = 'chat-message user-message';
|
|
userBubble.innerHTML = `
|
|
<div class="message-header">
|
|
<span class="user-avatar">👤</span>
|
|
<span class="user-label">${this.getTranslation('you') || (this.currentLanguage === 'de' ? 'Du' : 'You')}</span>
|
|
</div>
|
|
<div class="message-content">
|
|
<p>${answer}</p>
|
|
</div>
|
|
`;
|
|
|
|
// Remove input container and add user message
|
|
const inputContainer = document.querySelector('.user-input-container');
|
|
if (inputContainer) {
|
|
inputContainer.remove();
|
|
}
|
|
|
|
this.conversationContainer.appendChild(userBubble);
|
|
|
|
// Show user message with animation
|
|
setTimeout(() => {
|
|
userBubble.classList.add('visible');
|
|
this.scrollToBottomSmoothly();
|
|
}, 100);
|
|
|
|
// Generate AI response
|
|
setTimeout(() => {
|
|
this.generateChatAIResponse(answer, this.currentQuestionIndex);
|
|
}, 500);
|
|
}
|
|
|
|
// Show completion message when all questions are done
|
|
showCompletionMessage() {
|
|
const completionBubble = document.createElement('div');
|
|
completionBubble.className = 'chat-message ai-message completion';
|
|
completionBubble.innerHTML = `
|
|
<div class="message-header">
|
|
<span class="ai-avatar">🤖</span>
|
|
<span class="ai-label">${this.getTranslation('ai-teacher') || 'AI Teacher'}</span>
|
|
</div>
|
|
<div class="message-content">
|
|
<p>🎉 ${this.getTranslation('great-exploration') || 'Wow! You did a great job exploring this topic! You\'re thinking like a real scientist!'}</p>
|
|
<p>💡 ${this.getTranslation('keep-wondering') || 'Keep wondering about the world around you - that\'s how we make amazing discoveries!'}</p>
|
|
</div>
|
|
`;
|
|
|
|
this.conversationContainer.appendChild(completionBubble);
|
|
|
|
setTimeout(() => {
|
|
completionBubble.classList.add('visible');
|
|
this.scrollToBottomSmoothly();
|
|
}, 100);
|
|
}
|
|
|
|
// Smooth scroll to bottom of conversation
|
|
scrollToBottomSmoothly() {
|
|
if (this.conversationContainer) {
|
|
this.conversationContainer.scrollIntoView({ behavior: 'smooth', block: 'end' });
|
|
}
|
|
}
|
|
|
|
// ...existing code...
|
|
}
|
|
|
|
// Global variable for external access
|
|
let kidsAI;
|
|
|
|
// Initialize when DOM is ready
|
|
document.addEventListener('DOMContentLoaded', () => {
|
|
console.log('🚀 DOM loaded, initializing KidsAI Explorer...');
|
|
|
|
try {
|
|
kidsAI = new KidsAIExplorer();
|
|
window.kidsAI = kidsAI; // Make globally accessible
|
|
console.log('✅ KidsAI Explorer initialized successfully');
|
|
|
|
// Add visual indicator
|
|
const indicator = document.createElement('div');
|
|
indicator.innerHTML = '✅ KidsAI Ready!';
|
|
indicator.style.cssText = 'position:fixed;top:10px;left:10px;background:green;color:white;padding:5px;z-index:9999;border-radius:5px;font-size:12px;';
|
|
document.body.appendChild(indicator);
|
|
|
|
// Remove indicator after 3 seconds
|
|
setTimeout(() => indicator.remove(), 3000);
|
|
|
|
} catch (error) {
|
|
console.error('❌ Error initializing KidsAIExplorer:', error);
|
|
|
|
// Show error indicator
|
|
const indicator = document.createElement('div');
|
|
indicator.innerHTML = '❌ KidsAI Error: ' + error.message;
|
|
indicator.style.cssText = 'position:fixed;top:10px;left:10px;background:red;color:white;padding:5px;z-index:9999;border-radius:5px;font-size:12px;';
|
|
document.body.appendChild(indicator);
|
|
}
|
|
});
|
|
|
|
console.log('📜 KidsAI script-new.js loaded');
|