Files
kidsai/html/kidsai/script-new.js
root c7ea7a3286 Add robust translation handling and safe template string method
🔧 Translation Improvements:
- Enhanced getTranslation() with better error checking
- Added t() method as safe alternative for template strings
- Added extra validation for translation key existence
- Better error logging for debugging translation issues

 Result: More robust translation system that handles edge cases
2025-06-30 10:12:25 +02:00

766 lines
29 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
this.thinkingSteps.innerHTML = '';
// Initialize conversation state
this.currentStep = 0;
this.userAnswers = [];
// 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') || "Great question! Let's explore this together step by step! 🚀";
const detectiveHelpText = this.getTranslation('detective-help') || "Instead of giving you the answer right away, I'll help you think through this like a detective! 🕵️";
const aiTeacherText = this.getTranslation('ai-teacher') || "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 || [
"What do you already know about this topic?",
"What do you think might be the reason for this?",
"Can you think of any examples or similar situations?"
];
// Create chat interface
this.conversationContainer = conversationContainer;
this.questions = questions;
this.currentQuestionIndex = 0;
// 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';
console.log('📤 Sending request to /api/respond-to-answer:', { answer, currentQuestion });
// 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: 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);
if (data.success && data.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>${data.response}</p>
</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';
console.log('📤 Sending chat request to /api/respond-to-answer:', { answer, currentQuestion });
// 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: 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);
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 AI response
setTimeout(() => {
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.getTranslation('great-thinking-fallback')}</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') || "I want to explore this deeper";
const continueLearningText = this.getTranslation('continue-learning') || "Continue with next topic";
const tellMeMoreText = this.getTranslation('tell-me-more') || "Tell me more! 🤔";
const nextQuestionText = this.getTranslation('next-question') || "Next question! ➡️";
const choiceContainer = document.createElement('div');
choiceContainer.className = 'choice-container';
choiceContainer.innerHTML = `
<div class="choice-prompt">
<p>💭 ${exploreDeeperText} oder ${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'
})
});
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);
// Add choice buttons again for further exploration
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>🔍 Das ist ein wirklich interessantes Thema! Hast du schon mal daran gedacht, wie komplex die Wissenschaft dahinter ist? Möchtest du noch mehr darüber erfahren?</p>
</div>
`;
this.conversationContainer.appendChild(explorationBubble);
setTimeout(() => {
explorationBubble.classList.add('visible');
this.scrollToBottomSmoothly();
}, 100);
setTimeout(() => {
this.addContinueChoiceButtons();
}, 2000);
}
}
// 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();
}
// ...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');