- Complete KidsAI Explorer application - Multi-language support (English/German) - AI-powered educational guidance using OpenAI - Interactive chat interface for children - Proper placeholder translation fixes - Mobile-responsive design - Educational framework for critical thinking
1056 lines
44 KiB
JavaScript
1056 lines
44 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');
|
|
}
|
|
|
|
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();
|
|
}
|
|
});
|
|
}
|
|
|
|
// 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;
|
|
}
|
|
|
|
console.log('🌐 Applying translations for language:', this.currentLanguage);
|
|
|
|
// 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];
|
|
}
|
|
});
|
|
|
|
// Update all elements with data-translate-placeholder attribute
|
|
const placeholderElements = document.querySelectorAll('[data-translate-placeholder]');
|
|
console.log('🔍 Found placeholder elements:', placeholderElements.length);
|
|
|
|
placeholderElements.forEach(element => {
|
|
const key = element.getAttribute('data-translate-placeholder');
|
|
console.log('🔑 Translating placeholder key:', key, 'to:', t[key]);
|
|
if (t[key]) {
|
|
element.placeholder = t[key];
|
|
console.log('✅ Updated placeholder for element:', element.tagName, 'to:', t[key]);
|
|
}
|
|
});
|
|
} catch (error) {
|
|
console.error('❌ Error applying translations:', error);
|
|
}
|
|
}
|
|
|
|
switchLanguage(lang) {
|
|
console.log('🔄 Switching language to:', lang);
|
|
this.currentLanguage = lang;
|
|
localStorage.setItem('kidsai-language', lang);
|
|
|
|
// Force re-apply translations after language switch
|
|
setTimeout(() => {
|
|
this.initializeLanguage();
|
|
// Double-check placeholder specifically
|
|
if (this.questionInput && typeof translations !== 'undefined') {
|
|
const t = translations[this.currentLanguage];
|
|
if (t && t['question-placeholder']) {
|
|
this.questionInput.placeholder = t['question-placeholder'];
|
|
console.log('🔄 Force-updated placeholder to:', t['question-placeholder']);
|
|
}
|
|
}
|
|
}, 50);
|
|
}
|
|
|
|
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');
|
|
this.showMessage('Please ask me something first! 🤔', '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);
|
|
this.showMessage('Sorry, I had trouble processing your question. Let me give you some thinking guidance instead!', '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 for current language
|
|
const t = (typeof translations !== 'undefined' && translations[this.currentLanguage])
|
|
? translations[this.currentLanguage]
|
|
: translations['en'];
|
|
|
|
const encouragementText = guidance.encouragement || t['default-encouragement'] || "Great question! Let's explore this together step by step! 🚀";
|
|
const detectiveText = t['detective-help'] || "Instead of giving you the answer right away, I'll help you think through this like a detective! 🕵️";
|
|
const aiTeacherLabel = t['ai-teacher'] || "AI Teacher";
|
|
|
|
welcomeStep.innerHTML = `
|
|
<div class="ai-message">
|
|
<div class="message-header">
|
|
<span class="ai-avatar">🤖</span>
|
|
<span class="ai-label">${aiTeacherLabel}</span>
|
|
</div>
|
|
<div class="message-content">
|
|
<p>${encouragementText}</p>
|
|
<p>${detectiveText}</p>
|
|
</div>
|
|
</div>
|
|
`;
|
|
conversationContainer.appendChild(welcomeStep);
|
|
|
|
// Add thinking questions as interactive steps
|
|
const fallbackQuestions = [
|
|
t['fallback-question-1'] || "What do you already know about this topic?",
|
|
t['fallback-question-2'] || "What do you think might be the reason for this?",
|
|
t['fallback-question-3'] || "Where could you look to find more information?",
|
|
t['fallback-question-4'] || "Can you think of any examples or similar situations?"
|
|
];
|
|
|
|
const questions = guidance.steps ? guidance.steps.map(step => step.text) : guidance.questions || fallbackQuestions;
|
|
|
|
// 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 translations for current language
|
|
const t = (typeof translations !== 'undefined' && translations[this.currentLanguage])
|
|
? translations[this.currentLanguage]
|
|
: translations['en'];
|
|
|
|
const encouragements = t.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)];
|
|
|
|
this.displayGuidance({
|
|
questions: [
|
|
"What do you already know about this topic?",
|
|
"What do you think might be the reason for this?",
|
|
"Where could you look to find more information?",
|
|
"Can you think of any examples or similar situations?"
|
|
],
|
|
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) {
|
|
this.showMessage(
|
|
this.currentLanguage === 'de' ? 'Bitte schreibe deine Gedanken auf! 🤔' : 'Please write down your thoughts! 🤔',
|
|
'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">AI Teacher</span>
|
|
</div>
|
|
<div class="message-content">
|
|
<p>🤔 Thinking about your 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">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">AI Teacher</span>
|
|
</div>
|
|
<div class="message-content">
|
|
<p>🌟 Great thinking! Keep exploring - every thought helps us learn!</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">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.conversationContainer.scrollTop = this.conversationContainer.scrollHeight;
|
|
}, 100);
|
|
|
|
// Ask next question
|
|
setTimeout(() => {
|
|
this.currentQuestionIndex++; // Move to next question
|
|
this.askNextQuestion();
|
|
}, 2000);
|
|
} 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">AI Teacher</span>
|
|
</div>
|
|
<div class="message-content">
|
|
<p>🌟 Great thinking! Keep exploring - every thought helps us learn!</p>
|
|
</div>
|
|
`;
|
|
|
|
this.conversationContainer.appendChild(responseBubble);
|
|
|
|
setTimeout(() => {
|
|
responseBubble.classList.add('visible');
|
|
this.conversationContainer.scrollTop = this.conversationContainer.scrollHeight;
|
|
}, 100);
|
|
|
|
setTimeout(() => {
|
|
this.currentQuestionIndex++; // Move to next question
|
|
this.askNextQuestion();
|
|
}, 2000);
|
|
}
|
|
}
|
|
|
|
askNextQuestion() {
|
|
if (this.currentQuestionIndex >= this.questions.length) {
|
|
// All questions asked, add and show reveal section
|
|
const revealDiv = document.createElement('div');
|
|
revealDiv.className = 'reveal-section';
|
|
revealDiv.innerHTML = `
|
|
<div class="reveal-prompt">
|
|
<p>🎉 Great thinking! You've explored this question step by step!</p>
|
|
<p>Would you like to see how close your thoughts were to the scientific explanation?</p>
|
|
<button class="reveal-btn" onclick="kidsAI.revealAnswer('${this.questionInput.value}')">
|
|
<span class="btn-icon">🎯</span> Reveal Answer!
|
|
</button>
|
|
</div>
|
|
<div class="answer-content" id="answer-content" style="display: none;"></div>
|
|
`;
|
|
this.conversationContainer.appendChild(revealDiv);
|
|
|
|
setTimeout(() => {
|
|
revealDiv.classList.add('visible');
|
|
this.conversationContainer.scrollTop = this.conversationContainer.scrollHeight;
|
|
}, 500);
|
|
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">AI Teacher</span>
|
|
</div>
|
|
<div class="message-content">
|
|
<p><strong>${this.currentQuestionIndex + 1})</strong> ${question}</p>
|
|
</div>
|
|
`;
|
|
|
|
this.conversationContainer.appendChild(questionBubble);
|
|
|
|
// Add input area for user response
|
|
const inputContainer = document.createElement('div');
|
|
inputContainer.className = 'chat-input-container';
|
|
inputContainer.innerHTML = `
|
|
<div class="input-area active" id="input-area-${this.currentQuestionIndex}">
|
|
<textarea
|
|
id="user-input-${this.currentQuestionIndex}"
|
|
placeholder="Type your thoughts here..."
|
|
class="chat-textarea"
|
|
></textarea>
|
|
<button
|
|
class="reply-btn"
|
|
onclick="kidsAI.submitChatAnswer(${this.currentQuestionIndex})"
|
|
>
|
|
<span class="btn-icon">💬</span> Send
|
|
</button>
|
|
</div>
|
|
`;
|
|
|
|
this.conversationContainer.appendChild(inputContainer);
|
|
|
|
// Animate in the question
|
|
setTimeout(() => {
|
|
questionBubble.classList.add('visible');
|
|
inputContainer.classList.add('visible');
|
|
|
|
// Smooth scroll to bottom of conversation instead of specific element
|
|
setTimeout(() => {
|
|
this.conversationContainer.scrollTop = this.conversationContainer.scrollHeight;
|
|
}, 100);
|
|
|
|
// Focus on the textarea
|
|
const textarea = document.getElementById(`user-input-${this.currentQuestionIndex}`);
|
|
if (textarea) {
|
|
textarea.focus();
|
|
}
|
|
}, 300);
|
|
}
|
|
|
|
submitChatAnswer(questionIndex) {
|
|
const textarea = document.getElementById(`user-input-${questionIndex}`);
|
|
const inputContainer = document.getElementById(`input-area-${questionIndex}`);
|
|
|
|
if (!textarea || !inputContainer) {
|
|
console.error('❌ Could not find elements for question:', questionIndex);
|
|
return;
|
|
}
|
|
|
|
const answer = textarea.value.trim();
|
|
|
|
if (!answer) {
|
|
this.showMessage(
|
|
this.currentLanguage === 'de' ? 'Bitte schreibe deine Gedanken auf! 🤔' : 'Please write down your thoughts! 🤔',
|
|
'warning'
|
|
);
|
|
return;
|
|
}
|
|
|
|
// Store the answer
|
|
this.userAnswers[questionIndex] = answer;
|
|
|
|
// Disable input
|
|
inputContainer.classList.add('answered');
|
|
textarea.disabled = true;
|
|
|
|
// Add user message bubble
|
|
const userBubble = document.createElement('div');
|
|
userBubble.className = 'chat-message user-message';
|
|
userBubble.innerHTML = `
|
|
<div class="message-content">
|
|
<p>${answer}</p>
|
|
</div>
|
|
`;
|
|
|
|
this.conversationContainer.appendChild(userBubble);
|
|
|
|
// Show user message and then AI response
|
|
setTimeout(() => {
|
|
userBubble.classList.add('visible');
|
|
|
|
// Generate AI response using server-side AI
|
|
this.generateChatAIResponse(answer, questionIndex);
|
|
|
|
}, 300);
|
|
}
|
|
|
|
generateContextualResponse(answer, questionIndex) {
|
|
const answerLower = answer.toLowerCase();
|
|
const currentQuestion = this.questions[questionIndex];
|
|
const questionLower = currentQuestion ? currentQuestion.toLowerCase() : '';
|
|
|
|
// Ensure AIResponses is available
|
|
if (typeof AIResponses === 'undefined') {
|
|
console.warn('⚠️ AIResponses not loaded, using fallback');
|
|
console.log('Available global objects:', Object.keys(window));
|
|
return "🌟 Great thinking! Keep exploring - you're doing wonderfully!";
|
|
}
|
|
|
|
console.log('🔍 Generating response for:', { answer: answerLower, question: questionLower });
|
|
|
|
// Handle "I don't know" or help-seeking responses
|
|
if (answerLower.includes('don\'t know') || answerLower.includes('no idea')) {
|
|
return AIResponses.helpSeeking.dontKnow;
|
|
}
|
|
if (answerLower.includes('what is it')) {
|
|
return AIResponses.helpSeeking.whatIsIt;
|
|
}
|
|
if (answerLower.includes('tell me')) {
|
|
return AIResponses.helpSeeking.tellMe;
|
|
}
|
|
|
|
// Handle very short positive answers like "yes"
|
|
if (answerLower === 'yes' || answerLower === 'yeah' || answerLower === 'yep') {
|
|
if (questionLower.includes('bicycle') || questionLower.includes('pedal')) {
|
|
return AIResponses.shortAnswers.yes.bicycle;
|
|
} else if (questionLower.includes('heard of') || questionLower.includes('know')) {
|
|
return AIResponses.shortAnswers.yes.knowledge;
|
|
} else {
|
|
return AIResponses.shortAnswers.yes.general;
|
|
}
|
|
}
|
|
|
|
// Handle "no" responses
|
|
if (answerLower === 'no' || answerLower === 'nope') {
|
|
return AIResponses.shortAnswers.no;
|
|
}
|
|
|
|
// Topic-specific contextual responses - BICYCLE
|
|
if (questionLower.includes('bicycle') || questionLower.includes('pedal') || questionLower.includes('chain')) {
|
|
if (answerLower.includes('brake') && (answerLower.includes('slow') || answerLower.includes('stop'))) {
|
|
return AIResponses.bicycle.brake.slowDown;
|
|
} else if (answerLower.includes('brake')) {
|
|
return AIResponses.bicycle.brake.technical;
|
|
} else if (answerLower.includes('pedal') || answerLower.includes('chain') || answerLower.includes('wheel')) {
|
|
return AIResponses.bicycle.mechanics.pedalChainWheel;
|
|
} else if (answerLower.includes('gear') || answerLower.includes('speed')) {
|
|
return AIResponses.bicycle.mechanics.gearSpeed;
|
|
} else if (answerLower.includes('slow') || answerLower.includes('stop')) {
|
|
return AIResponses.bicycle.mechanics.slowStop;
|
|
}
|
|
}
|
|
|
|
// CAR vs BICYCLE comparison
|
|
if (questionLower.includes('car') && questionLower.includes('bicycle')) {
|
|
if (answerLower.includes('engine')) {
|
|
return AIResponses.car.comparison.engine;
|
|
} else if (answerLower.includes('gear') || answerLower.includes('transmission')) {
|
|
return AIResponses.car.comparison.gearTransmission;
|
|
} else if (answerLower.includes('pedal') || answerLower.includes('brake')) {
|
|
return AIResponses.car.comparison.pedalBrake;
|
|
}
|
|
}
|
|
|
|
// CAR BRAKE responses
|
|
if (questionLower.includes('slow') || questionLower.includes('stop') || questionLower.includes('car')) {
|
|
if (answerLower.includes('brake') && (answerLower.includes('pedal') || answerLower.includes('system'))) {
|
|
return AIResponses.car.brake.perfectAnswer;
|
|
} else if (answerLower.includes('brake') && !answerLower.includes('clutch')) {
|
|
return AIResponses.car.brake.justBrake;
|
|
} else if (answerLower.includes('wheel') || answerLower.includes('tire')) {
|
|
return AIResponses.car.brake.wheelTire;
|
|
} else if (answerLower.includes('pedal') && !answerLower.includes('brake')) {
|
|
return AIResponses.car.brake.pedal;
|
|
}
|
|
}
|
|
|
|
// CLUTCH responses
|
|
if (questionLower.includes('clutch') || questionLower.includes('gears') || questionLower.includes('switch gears')) {
|
|
if (answerLower.includes('clutch')) {
|
|
return AIResponses.car.clutch.perfect;
|
|
} else if (answerLower.includes('transmission') || answerLower.includes('gearbox')) {
|
|
return AIResponses.car.clutch.transmission;
|
|
} else if (answerLower.includes('separate') || answerLower.includes('disconnect')) {
|
|
return AIResponses.car.clutch.separate;
|
|
} else if (answerLower.includes('different') && answerLower.includes('brake')) {
|
|
return AIResponses.car.clutch.different;
|
|
} else if (answerLower.includes('engine') && answerLower.includes('transmission')) {
|
|
return AIResponses.car.clutch.engineTransmission;
|
|
} else {
|
|
return AIResponses.car.clutch.general;
|
|
}
|
|
}
|
|
|
|
// TRAFFIC LIGHT / ENGINE RUNNING responses
|
|
if (questionLower.includes('traffic light') || questionLower.includes('stopped') || questionLower.includes('engine running')) {
|
|
if (answerLower.includes('clutch') || answerLower.includes('neutral')) {
|
|
return AIResponses.car.trafficLight.clutchNeutral;
|
|
} else if (answerLower.includes('disconnect') || answerLower.includes('separate')) {
|
|
return AIResponses.car.trafficLight.disconnect;
|
|
} else if (answerLower.includes('brake') || answerLower.includes('park')) {
|
|
return AIResponses.car.trafficLight.brakePark;
|
|
}
|
|
}
|
|
|
|
// BIRD FLIGHT responses
|
|
if (questionLower.includes('bird') || questionLower.includes('wing') || questionLower.includes('fly')) {
|
|
if (answerLower.includes('push') || answerLower.includes('air') || answerLower.includes('lift')) {
|
|
return AIResponses.birds.pushAirLift;
|
|
} else if (answerLower.includes('feather') || answerLower.includes('airflow')) {
|
|
return AIResponses.birds.featherAirflow;
|
|
} else if (answerLower.includes('flap') || answerLower.includes('move')) {
|
|
return AIResponses.birds.flapMove;
|
|
}
|
|
}
|
|
|
|
// MECHANICAL understanding responses
|
|
if (answerLower.includes('connect') || answerLower.includes('control')) {
|
|
return AIResponses.mechanical.connectControl;
|
|
}
|
|
|
|
// PNEUMATIC tools responses
|
|
if (questionLower.includes('pneumatic') || questionLower.includes('compressed air') || questionLower.includes('air hammer')) {
|
|
if (answerLower.includes('compress') && answerLower.includes('air')) {
|
|
return AIResponses.pneumatic.compressedAir;
|
|
} else if (answerLower.includes('air pressure') || answerLower.includes('pressure')) {
|
|
return AIResponses.pneumatic.airPressure;
|
|
} else if (answerLower.includes('direction') || answerLower.includes('releasing')) {
|
|
return AIResponses.pneumatic.direction;
|
|
} else if (answerLower.includes('balloon') || answerLower.includes('inflate')) {
|
|
return AIResponses.pneumatic.balloon;
|
|
} else if (answerLower.includes('release') || answerLower.includes('strikes')) {
|
|
return AIResponses.pneumatic.release;
|
|
} else {
|
|
return AIResponses.pneumatic.general;
|
|
}
|
|
}
|
|
|
|
// COMPUTER/CPU responses
|
|
if (questionLower.includes('cpu') || questionLower.includes('processor') || questionLower.includes('computer')) {
|
|
if (answerLower.includes('compute') || answerLower.includes('process')) {
|
|
return AIResponses.computer.cpu.computes;
|
|
} else if (answerLower.includes('don\'t know') || answerLower.includes('no idea')) {
|
|
return AIResponses.computer.cpu.dontKnow;
|
|
} else if (answerLower.includes('silicon')) {
|
|
return AIResponses.computer.cpu.silicon;
|
|
} else if (answerLower.includes('electric') || answerLower.includes('conduct')) {
|
|
return AIResponses.computer.cpu.electricity;
|
|
} else if (answerLower.includes('circuit')) {
|
|
return AIResponses.computer.cpu.circuits;
|
|
} else if (answerLower.includes('material')) {
|
|
return AIResponses.computer.cpu.materials;
|
|
} else {
|
|
return AIResponses.computer.general;
|
|
}
|
|
}
|
|
|
|
// Generic responses based on answer quality and engagement
|
|
if (answer.length > 25) {
|
|
return AIResponses.generic.veryDetailed;
|
|
} else if (answer.length > 15) {
|
|
return AIResponses.generic.detailed;
|
|
} else if (answer.length > 8) {
|
|
return AIResponses.generic.medium;
|
|
} else if (answer.length > 3) {
|
|
return AIResponses.generic.short;
|
|
} else {
|
|
return AIResponses.generic.veryShort;
|
|
}
|
|
}
|
|
|
|
async revealAnswer(question) {
|
|
console.log('🎯 Revealing answer for:', question);
|
|
|
|
const revealBtn = document.querySelector('.reveal-btn');
|
|
const answerContent = document.getElementById('answer-content');
|
|
|
|
if (!revealBtn || !answerContent) {
|
|
console.error('❌ Could not find reveal elements');
|
|
return;
|
|
}
|
|
|
|
// Show loading state
|
|
revealBtn.innerHTML = '<span class="btn-icon">⏳</span> Getting Answer...';
|
|
revealBtn.disabled = true;
|
|
|
|
try {
|
|
const response = await fetch('/api/reveal-answer', {
|
|
method: 'POST',
|
|
headers: {
|
|
'Content-Type': 'application/json',
|
|
},
|
|
body: JSON.stringify({
|
|
question: question,
|
|
userAnswers: this.userAnswers,
|
|
language: this.currentLanguage
|
|
})
|
|
});
|
|
|
|
if (!response.ok) {
|
|
throw new Error(`HTTP error! status: ${response.status}`);
|
|
}
|
|
|
|
const data = await response.json();
|
|
|
|
if (data.success && data.answer) {
|
|
// Display the answer
|
|
answerContent.innerHTML = `
|
|
<div class="final-answer">
|
|
<div class="answer-header">
|
|
<span class="answer-icon">💡</span>
|
|
<h4>${this.currentLanguage === 'de' ? 'Die Antwort:' : 'The Answer:'}</h4>
|
|
<small class="answer-source">✨ AI • Scientific Explanation</small>
|
|
</div>
|
|
<div class="answer-text">
|
|
${data.answer.text}
|
|
</div>
|
|
<div class="answer-footer">
|
|
<p>🎉 ${this.currentLanguage === 'de'
|
|
? 'Großartig! Wie nah warst du mit deinen Überlegungen?'
|
|
: 'Awesome! How close were your thoughts to the answer?'}</p>
|
|
</div>
|
|
</div>
|
|
`;
|
|
answerContent.style.display = 'block';
|
|
revealBtn.style.display = 'none';
|
|
|
|
// Animate answer in
|
|
answerContent.style.opacity = '0';
|
|
answerContent.style.transform = 'translateY(20px)';
|
|
setTimeout(() => {
|
|
answerContent.style.transition = 'all 0.6s ease-out';
|
|
answerContent.style.opacity = '1';
|
|
answerContent.style.transform = 'translateY(0)';
|
|
}, 100);
|
|
|
|
} else {
|
|
throw new Error(data.error || 'Failed to get answer');
|
|
}
|
|
|
|
} catch (error) {
|
|
console.error('❌ Error getting final answer:', error);
|
|
|
|
// Fallback answer display
|
|
revealBtn.innerHTML = `<span class="btn-icon">🎯</span> ${this.currentLanguage === 'de' ? 'Antwort zeigen!' : 'Reveal Answer!'}`;
|
|
revealBtn.disabled = false;
|
|
|
|
answerContent.innerHTML = `
|
|
<div class="final-answer fallback">
|
|
<div class="answer-header">
|
|
<span class="answer-icon">🤔</span>
|
|
<h4>${this.currentLanguage === 'de' ? 'Hmm...' : 'Hmm...'}</h4>
|
|
</div>
|
|
<div class="answer-text">
|
|
<p>${this.currentLanguage === 'de'
|
|
? 'Ich kann die Antwort gerade nicht abrufen. Das ist eine tolle Frage! Du hast schon toll nachgedacht. Frag gerne einen Erwachsenen oder schaue in einem Buch nach.'
|
|
: 'I can\'t fetch the answer right now. That\'s a great question! You\'ve done great thinking already. Feel free to ask an adult or look it up in a book.'}</p>
|
|
</div>
|
|
</div>
|
|
`;
|
|
answerContent.style.display = 'block';
|
|
}
|
|
}
|
|
|
|
// Debug function to manually test placeholder translation
|
|
debugTranslatePlaceholder() {
|
|
console.log('🧪 DEBUG: Manually testing placeholder translation');
|
|
console.log('Current language:', this.currentLanguage);
|
|
console.log('Available translations:', typeof translations !== 'undefined' ? Object.keys(translations) : 'not loaded');
|
|
|
|
if (typeof translations !== 'undefined') {
|
|
const t = translations[this.currentLanguage];
|
|
if (t && t['question-placeholder']) {
|
|
console.log('German placeholder text:', t['question-placeholder']);
|
|
const input = document.getElementById('question-input');
|
|
if (input) {
|
|
input.placeholder = t['question-placeholder'];
|
|
console.log('✅ Manually updated placeholder');
|
|
} else {
|
|
console.log('❌ Input element not found');
|
|
}
|
|
} else {
|
|
console.log('❌ Translation not found for question-placeholder');
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// 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');
|
|
|
|
// Force placeholder translation after a short delay to ensure everything is loaded
|
|
setTimeout(() => {
|
|
console.log('🔄 Force applying placeholder translation on load...');
|
|
if (kidsAI && typeof translations !== 'undefined') {
|
|
const currentLang = kidsAI.currentLanguage;
|
|
const input = document.getElementById('question-input');
|
|
if (input && translations[currentLang] && translations[currentLang]['question-placeholder']) {
|
|
input.placeholder = translations[currentLang]['question-placeholder'];
|
|
console.log('✅ Force-set placeholder on load to:', translations[currentLang]['question-placeholder']);
|
|
}
|
|
}
|
|
}, 100);
|
|
|
|
// 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');
|