1104 lines
45 KiB
JavaScript
1104 lines
45 KiB
JavaScript
// KidsAI Explorer - Interactive Learning Assistant
|
|
class KidsAIExplorer {
|
|
constructor() {
|
|
this.currentLanguage = localStorage.getItem('kidsai-language') || 'en';
|
|
|
|
// Get DOM elements with error handling
|
|
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');
|
|
|
|
// Ensure loading is hidden initially
|
|
if (this.loadingOverlay) {
|
|
this.loadingOverlay.classList.add('hidden');
|
|
}
|
|
|
|
// Initialize only if we have the required elements
|
|
if (this.questionInput && this.askButton) {
|
|
this.initializeLanguage();
|
|
this.initializeEventListeners();
|
|
this.initializeAnimations();
|
|
} else {
|
|
console.error('Required DOM elements not found');
|
|
}
|
|
}
|
|
|
|
initializeLanguage() {
|
|
// Set active language button
|
|
this.langButtons.forEach(btn => {
|
|
btn.classList.toggle('active', btn.dataset.lang === this.currentLanguage);
|
|
});
|
|
|
|
// Apply translations
|
|
this.applyTranslations();
|
|
}
|
|
|
|
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];
|
|
}
|
|
});
|
|
|
|
// Update placeholder
|
|
const placeholder = document.querySelector('[data-translate-placeholder]');
|
|
if (placeholder) {
|
|
const key = placeholder.getAttribute('data-translate-placeholder');
|
|
if (t[key]) {
|
|
placeholder.placeholder = t[key];
|
|
}
|
|
}
|
|
} catch (error) {
|
|
console.error('Error applying translations:', error);
|
|
}
|
|
}
|
|
|
|
switchLanguage(lang) {
|
|
this.currentLanguage = lang;
|
|
localStorage.setItem('kidsai-language', lang);
|
|
|
|
// Update active button
|
|
this.langButtons.forEach(btn => {
|
|
btn.classList.toggle('active', btn.dataset.lang === lang);
|
|
});
|
|
|
|
// Apply translations
|
|
this.applyTranslations();
|
|
|
|
// Clear thinking section if visible
|
|
if (!this.thinkingSection.classList.contains('hidden')) {
|
|
this.thinkingSection.classList.add('hidden');
|
|
this.thinkingSteps.innerHTML = '';
|
|
}
|
|
}
|
|
|
|
initializeEventListeners() {
|
|
// Language switching
|
|
this.langButtons.forEach(btn => {
|
|
btn.addEventListener('click', () => {
|
|
this.switchLanguage(btn.dataset.lang);
|
|
});
|
|
});
|
|
|
|
// Main question submission
|
|
this.askButton.addEventListener('click', () => this.handleQuestion());
|
|
this.questionInput.addEventListener('keypress', (e) => {
|
|
if (e.key === 'Enter' && !e.shiftKey) {
|
|
e.preventDefault();
|
|
this.handleQuestion();
|
|
}
|
|
});
|
|
|
|
// Suggestion cards
|
|
this.suggestionCards.forEach(card => {
|
|
card.addEventListener('click', () => {
|
|
const questionKey = `data-question-${this.currentLanguage}`;
|
|
const question = card.getAttribute(questionKey);
|
|
this.questionInput.value = question;
|
|
this.handleQuestion();
|
|
});
|
|
});
|
|
|
|
}
|
|
|
|
initializeAnimations() {
|
|
// Add floating animation to suggestion cards
|
|
this.suggestionCards.forEach((card, index) => {
|
|
card.style.animationDelay = `${index * 0.1}s`;
|
|
card.style.animation = 'fadeInUp 0.6s ease-out both';
|
|
});
|
|
}
|
|
|
|
async handleQuestion() {
|
|
const question = this.questionInput.value.trim();
|
|
if (!question) {
|
|
this.showMessage('Please ask me something first! 🤔', 'warning');
|
|
return;
|
|
}
|
|
|
|
this.showLoading();
|
|
|
|
try {
|
|
// Call the AI backend
|
|
const response = await fetch('/api/ask', {
|
|
method: 'POST',
|
|
headers: {
|
|
'Content-Type': 'application/json',
|
|
},
|
|
body: JSON.stringify({
|
|
question: question,
|
|
language: this.currentLanguage
|
|
})
|
|
});
|
|
|
|
if (!response.ok) {
|
|
throw new Error(`HTTP error! status: ${response.status}`);
|
|
}
|
|
|
|
const data = await response.json();
|
|
|
|
if (data.success) {
|
|
// Display AI-powered guidance
|
|
this.displayAIGuidance(data.guidance, data.fallback);
|
|
} else {
|
|
throw new Error(data.error || 'Unknown error');
|
|
}
|
|
|
|
} catch (error) {
|
|
console.error('Error getting AI guidance:', error);
|
|
|
|
// Fallback to local guidance if backend fails
|
|
this.showMessage('Using offline guidance...', 'info');
|
|
const localGuidance = this.generateThinkingGuidance(question);
|
|
this.displayThinkingProcess(localGuidance);
|
|
} finally {
|
|
this.hideLoading();
|
|
}
|
|
}
|
|
|
|
displayAIGuidance(guidance, isFallback) {
|
|
// Clear any previous content
|
|
this.thinkingSteps.innerHTML = '';
|
|
|
|
// Show encouragement
|
|
const encouragementDiv = document.createElement('div');
|
|
encouragementDiv.className = 'encouragement-message';
|
|
encouragementDiv.innerHTML = `
|
|
<div class="encouragement-text">
|
|
${guidance.encouragement}
|
|
${isFallback ? '<small>(Offline mode)</small>' : '<small>(✨ AI-powered)</small>'}
|
|
</div>
|
|
`;
|
|
this.thinkingSteps.appendChild(encouragementDiv);
|
|
|
|
// Show thinking steps from AI
|
|
guidance.steps.forEach((step, index) => {
|
|
setTimeout(() => {
|
|
const stepDiv = document.createElement('div');
|
|
stepDiv.className = 'thinking-step';
|
|
|
|
// For math questions, don't show text fields - just show the guidance
|
|
if (guidance.questionType === 'mathematical' || guidance.questionType === 'factual_simple') {
|
|
stepDiv.innerHTML = `
|
|
<div class="step-number">${step.id}</div>
|
|
<div class="step-content">
|
|
<div class="step-text">${step.text}</div>
|
|
</div>
|
|
`;
|
|
} else {
|
|
// For exploratory questions, show interactive text fields for thinking
|
|
stepDiv.innerHTML = `
|
|
<div class="step-number">${step.id}</div>
|
|
<div class="step-content">
|
|
<div class="step-text">${step.text}</div>
|
|
<div class="step-thinking-space">
|
|
<textarea
|
|
placeholder="${this.currentLanguage === 'de' ? 'Deine Gedanken hier...' : 'Your thoughts here...'}"
|
|
rows="2"
|
|
class="thinking-textarea"
|
|
data-step="${step.id}"
|
|
></textarea>
|
|
<button class="share-thought-btn" data-step="${step.id}" style="display: none;">
|
|
<span class="btn-icon">💭</span>
|
|
${this.currentLanguage === 'de' ? 'Gedanken teilen' : 'Share Thought'}
|
|
</button>
|
|
<div class="thinking-feedback" data-step="${step.id}" style="display: none;"></div>
|
|
</div>
|
|
</div>
|
|
`;
|
|
|
|
// Add interactive event listeners
|
|
setTimeout(() => {
|
|
this.setupThinkingInteraction(stepDiv, step.id);
|
|
}, 100);
|
|
}
|
|
stepDiv.style.opacity = '0';
|
|
stepDiv.style.transform = 'translateY(20px)';
|
|
this.thinkingSteps.appendChild(stepDiv);
|
|
|
|
// Animate in
|
|
setTimeout(() => {
|
|
stepDiv.style.transition = 'all 0.4s ease-out';
|
|
stepDiv.style.opacity = '1';
|
|
stepDiv.style.transform = 'translateY(0)';
|
|
}, 50);
|
|
}, index * 800);
|
|
});
|
|
|
|
// Add answer reveal button only for exploratory questions
|
|
if (guidance.showAnswerReveal === true) {
|
|
setTimeout(() => {
|
|
this.addAnswerRevealSection();
|
|
}, guidance.steps.length * 800 + 1000);
|
|
} else {
|
|
// For mathematical/factual questions, add "Check My Work" button
|
|
setTimeout(() => {
|
|
this.addCheckWorkSection(guidance.questionType);
|
|
}, guidance.steps.length * 800 + 1000);
|
|
}
|
|
|
|
// Show the thinking section
|
|
setTimeout(() => {
|
|
this.thinkingSection.classList.remove('hidden');
|
|
}, 500);
|
|
}
|
|
|
|
addCompletionMessage(questionType) {
|
|
const completionSection = document.createElement('div');
|
|
completionSection.className = 'completion-message';
|
|
|
|
let message;
|
|
if (questionType === 'mathematical') {
|
|
message = this.currentLanguage === 'de'
|
|
? '🎉 Großartig! Du hast die Antwort selbst gefunden! Mathematik macht Spaß, wenn man Schritt für Schritt vorgeht.'
|
|
: '🎉 Amazing! You found the answer yourself! Math is fun when you work through it step by step.';
|
|
} else {
|
|
message = this.currentLanguage === 'de'
|
|
? '🌟 Super! Du hast durch eigenes Nachdenken die Antwort entdeckt. Das ist echtes Lernen!'
|
|
: '🌟 Awesome! You discovered the answer through your own thinking. That\'s real learning!';
|
|
}
|
|
|
|
completionSection.innerHTML = `
|
|
<div class="completion-box">
|
|
<div class="completion-text">${message}</div>
|
|
<div class="completion-footer">
|
|
<button class="new-question-btn" id="new-question-btn">
|
|
${this.currentLanguage === 'de' ? '🚀 Neue Frage stellen' : '🚀 Ask Another Question'}
|
|
</button>
|
|
</div>
|
|
</div>
|
|
`;
|
|
|
|
completionSection.style.opacity = '0';
|
|
completionSection.style.transform = 'translateY(20px)';
|
|
this.thinkingSteps.appendChild(completionSection);
|
|
|
|
// Animate in
|
|
setTimeout(() => {
|
|
completionSection.style.transition = 'all 0.6s ease-out';
|
|
completionSection.style.opacity = '1';
|
|
completionSection.style.transform = 'translateY(0)';
|
|
}, 100);
|
|
|
|
// Add click handler for new question button
|
|
const newQuestionBtn = completionSection.querySelector('#new-question-btn');
|
|
newQuestionBtn.addEventListener('click', () => {
|
|
this.resetForNewQuestion();
|
|
});
|
|
}
|
|
|
|
resetForNewQuestion() {
|
|
this.questionInput.value = '';
|
|
this.thinkingSection.classList.add('hidden');
|
|
this.thinkingSteps.innerHTML = '';
|
|
this.questionInput.focus();
|
|
}
|
|
|
|
addAnswerRevealSection() {
|
|
const revealSection = document.createElement('div');
|
|
revealSection.className = 'answer-reveal-section';
|
|
revealSection.innerHTML = `
|
|
<div class="reveal-prompt">
|
|
<h4>${this.currentLanguage === 'de' ? '🤔 Bereit für die Antwort?' : '🤔 Ready for the Answer?'}</h4>
|
|
<p>${this.currentLanguage === 'de'
|
|
? 'Nachdem du über die Fragen nachgedacht hast, möchtest du die richtige Antwort erfahren?'
|
|
: 'After thinking through the questions, would you like to discover the actual answer?'}</p>
|
|
<button id="reveal-answer-btn" class="reveal-answer-btn">
|
|
<span class="btn-icon">🎯</span>
|
|
${this.currentLanguage === 'de' ? 'Antwort zeigen!' : 'Reveal Answer!'}
|
|
</button>
|
|
</div>
|
|
<div id="answer-content" class="answer-content hidden">
|
|
<!-- Answer will be loaded here -->
|
|
</div>
|
|
`;
|
|
|
|
revealSection.style.opacity = '0';
|
|
revealSection.style.transform = 'translateY(20px)';
|
|
this.thinkingSteps.appendChild(revealSection);
|
|
|
|
// Animate in
|
|
setTimeout(() => {
|
|
revealSection.style.transition = 'all 0.6s ease-out';
|
|
revealSection.style.opacity = '1';
|
|
revealSection.style.transform = 'translateY(0)';
|
|
}, 100);
|
|
|
|
// Add click handler for reveal button
|
|
document.getElementById('reveal-answer-btn').addEventListener('click', () => {
|
|
this.revealAnswer();
|
|
});
|
|
}
|
|
|
|
async revealAnswer() {
|
|
const revealBtn = document.getElementById('reveal-answer-btn');
|
|
const answerContent = document.getElementById('answer-content');
|
|
const currentQuestion = this.questionInput.value.trim();
|
|
|
|
// Show loading state
|
|
revealBtn.innerHTML = `<span class="btn-icon">⏳</span> ${this.currentLanguage === 'de' ? 'Lädt...' : 'Loading...'}`;
|
|
revealBtn.disabled = true;
|
|
|
|
try {
|
|
const response = await fetch('/api/reveal-answer', {
|
|
method: 'POST',
|
|
headers: {
|
|
'Content-Type': 'application/json',
|
|
},
|
|
body: JSON.stringify({
|
|
question: currentQuestion,
|
|
language: this.currentLanguage
|
|
})
|
|
});
|
|
|
|
if (!response.ok) {
|
|
throw new Error(`HTTP error! status: ${response.status}`);
|
|
}
|
|
|
|
const data = await response.json();
|
|
|
|
if (data.success) {
|
|
// Display the answer
|
|
answerContent.innerHTML = `
|
|
<div class="answer-box">
|
|
<div class="answer-header">
|
|
<span class="answer-icon">💡</span>
|
|
<h4>${this.currentLanguage === 'de' ? 'Die Antwort:' : 'The Answer:'}</h4>
|
|
<small class="answer-source">${data.fallback ? '📚 Local' : '✨ AI'} • ${data.answer.source}</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.classList.remove('hidden');
|
|
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 answer:', error);
|
|
revealBtn.innerHTML = `<span class="btn-icon">❌</span> ${this.currentLanguage === 'de' ? 'Fehler' : 'Error'}`;
|
|
setTimeout(() => {
|
|
revealBtn.innerHTML = `<span class="btn-icon">🎯</span> ${this.currentLanguage === 'de' ? 'Nochmal versuchen' : 'Try Again'}`;
|
|
revealBtn.disabled = false;
|
|
}, 2000);
|
|
}
|
|
}
|
|
|
|
generateThinkingGuidance(question) {
|
|
// This is where the AI magic happens - generating guided questions instead of answers
|
|
const questionLower = question.toLowerCase();
|
|
const t = translations[this.currentLanguage];
|
|
|
|
// Categories of questions and their thinking frameworks
|
|
const frameworks = t.thinkingFrameworks;
|
|
|
|
// Determine which framework to use
|
|
let selectedFramework = frameworks.general;
|
|
|
|
// Check for science keywords (works for both languages)
|
|
const scienceKeywords = ['why', 'warum', 'how', 'wie', 'what happens', 'was passiert', 'science', 'wissenschaft', 'nature', 'natur', 'sky', 'himmel', 'water', 'wasser', 'animals', 'tiere', 'plants', 'pflanzen', 'earth', 'erde', 'space', 'weltall'];
|
|
const mathKeywords = ['calculate', 'rechnen', 'math', 'mathe', 'numbers', 'zahlen', 'count', 'zählen', 'add', 'addieren', 'subtract', 'subtrahieren', 'multiply', 'multiplizieren', 'divide', 'dividieren', 'solve', 'lösen'];
|
|
const techKeywords = ['computer', 'internet', 'phone', 'telefon', 'robot', 'roboter', 'machine', 'maschine', 'technology', 'technologie', 'digital'];
|
|
|
|
if (scienceKeywords.some(keyword => questionLower.includes(keyword))) {
|
|
selectedFramework = frameworks.science;
|
|
} else if (mathKeywords.some(keyword => questionLower.includes(keyword))) {
|
|
selectedFramework = frameworks.math;
|
|
} else if (techKeywords.some(keyword => questionLower.includes(keyword))) {
|
|
selectedFramework = frameworks.technology;
|
|
}
|
|
|
|
return {
|
|
question: question,
|
|
framework: selectedFramework,
|
|
encouragement: this.getEncouragement(),
|
|
actionSuggestions: this.getActionSuggestions(questionLower)
|
|
};
|
|
}
|
|
|
|
getEncouragement() {
|
|
const t = translations[this.currentLanguage];
|
|
const encouragements = t.encouragements;
|
|
return encouragements[Math.floor(Math.random() * encouragements.length)];
|
|
}
|
|
|
|
getActionSuggestions(questionLower) {
|
|
const t = translations[this.currentLanguage];
|
|
const suggestions = {
|
|
research: [],
|
|
experiment: [],
|
|
discuss: []
|
|
};
|
|
|
|
// Add specific suggestions based on question content (works for both languages)
|
|
if (questionLower.includes('plant') || questionLower.includes('grow') || questionLower.includes('pflanze') || questionLower.includes('wachsen')) {
|
|
if (this.currentLanguage === 'de') {
|
|
suggestions.research.push("Informiere dich über den Lebenszyklus von Pflanzen");
|
|
suggestions.experiment.push("Versuche, Samen unter verschiedenen Bedingungen wachsen zu lassen");
|
|
suggestions.discuss.push("Frag einen Gärtner nach Pflanzenpflege");
|
|
} else {
|
|
suggestions.research.push("Look up the life cycle of plants");
|
|
suggestions.experiment.push("Try growing seeds in different conditions");
|
|
suggestions.discuss.push("Ask a gardener about plant care");
|
|
}
|
|
}
|
|
|
|
if (questionLower.includes('sky') || questionLower.includes('blue') || questionLower.includes('himmel') || questionLower.includes('blau')) {
|
|
if (this.currentLanguage === 'de') {
|
|
suggestions.research.push("Lerne über Licht und wie es sich bewegt");
|
|
suggestions.experiment.push("Benutze ein Prisma, um Licht in Farben aufzuteilen");
|
|
suggestions.discuss.push("Sprich mit einem Wissenschaftslehrer über Licht");
|
|
} else {
|
|
suggestions.research.push("Learn about light and how it travels");
|
|
suggestions.experiment.push("Use a prism to split light into colors");
|
|
suggestions.discuss.push("Talk to a science teacher about light");
|
|
}
|
|
}
|
|
|
|
// Default suggestions if none match
|
|
if (suggestions.research.length === 0) {
|
|
if (this.currentLanguage === 'de') {
|
|
suggestions.research = [
|
|
"Suche nach kinderfreundlichen Artikeln über dein Thema",
|
|
"Finde Bücher in der Bibliothek über dieses Thema",
|
|
"Schaue Lernvideos (mit einem Erwachsenen)"
|
|
];
|
|
} else {
|
|
suggestions.research = [
|
|
"Search for kid-friendly articles about your topic",
|
|
"Find books in the library about this subject",
|
|
"Watch educational videos (with a grown-up)"
|
|
];
|
|
}
|
|
}
|
|
|
|
if (suggestions.experiment.length === 0) {
|
|
if (this.currentLanguage === 'de') {
|
|
suggestions.experiment = [
|
|
"Denke an sichere Wege, deine Ideen zu testen",
|
|
"Mache Beobachtungen und schreibe sie auf",
|
|
"Versuche einfache Experimente mit Haushaltsgegenständen"
|
|
];
|
|
} else {
|
|
suggestions.experiment = [
|
|
"Think of safe ways to test your ideas",
|
|
"Make observations and take notes",
|
|
"Try simple experiments with household items"
|
|
];
|
|
}
|
|
}
|
|
|
|
if (suggestions.discuss.length === 0) {
|
|
if (this.currentLanguage === 'de') {
|
|
suggestions.discuss = [
|
|
"Frag deinen Lehrer, was er denkt",
|
|
"Sprich mit Familienmitgliedern über ihre Erfahrungen",
|
|
"Teile deine Frage mit Freunden"
|
|
];
|
|
} else {
|
|
suggestions.discuss = [
|
|
"Ask your teacher what they think",
|
|
"Talk to family members about their experiences",
|
|
"Share your question with friends"
|
|
];
|
|
}
|
|
}
|
|
|
|
return suggestions;
|
|
}
|
|
|
|
displayThinkingProcess(guidance) {
|
|
const t = translations[this.currentLanguage];
|
|
|
|
// Clear previous content
|
|
this.thinkingSteps.innerHTML = '';
|
|
|
|
// Add encouragement
|
|
const encouragementDiv = document.createElement('div');
|
|
encouragementDiv.className = 'thinking-step highlight';
|
|
encouragementDiv.innerHTML = `
|
|
<h4>🎉 ${guidance.encouragement}</h4>
|
|
<p>${this.currentLanguage === 'de' ? 'Lass uns das Schritt für Schritt erforschen und dir helfen, ein fantastischer Problemlöser zu werden!' : 'Let\'s explore this step by step and help you become a fantastic problem solver!'}</p>
|
|
`;
|
|
this.thinkingSteps.appendChild(encouragementDiv);
|
|
|
|
// Add thinking steps with animation delay
|
|
guidance.framework.steps.forEach((step, index) => {
|
|
setTimeout(() => {
|
|
const stepDiv = document.createElement('div');
|
|
stepDiv.className = 'thinking-step';
|
|
stepDiv.innerHTML = `
|
|
<h4>${step.title}</h4>
|
|
<p>${step.content}</p>
|
|
`;
|
|
this.thinkingSteps.appendChild(stepDiv);
|
|
|
|
// All steps are now displayed
|
|
if (index === guidance.framework.steps.length - 1) {
|
|
setTimeout(() => {
|
|
// Completion message could be added here if needed
|
|
}, 300);
|
|
}
|
|
}, index * 500);
|
|
});
|
|
|
|
// Show thinking section
|
|
this.thinkingSection.classList.remove('hidden');
|
|
this.thinkingSection.scrollIntoView({ behavior: 'smooth', block: 'start' });
|
|
}
|
|
|
|
showLoading() {
|
|
if (this.loadingOverlay) {
|
|
this.loadingOverlay.classList.remove('hidden');
|
|
}
|
|
}
|
|
|
|
hideLoading() {
|
|
if (this.loadingOverlay) {
|
|
this.loadingOverlay.classList.add('hidden');
|
|
}
|
|
}
|
|
|
|
showMessage(message, type = 'info') {
|
|
// Create a temporary message element
|
|
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;
|
|
animation: slideInRight 0.3s ease-out;
|
|
max-width: 300px;
|
|
`;
|
|
|
|
document.body.appendChild(messageDiv);
|
|
|
|
// Remove after 3 seconds
|
|
setTimeout(() => {
|
|
messageDiv.style.animation = 'slideOutRight 0.3s ease-out';
|
|
setTimeout(() => messageDiv.remove(), 300);
|
|
}, 3000);
|
|
}
|
|
|
|
delay(ms) {
|
|
return new Promise(resolve => setTimeout(resolve, ms));
|
|
}
|
|
|
|
addCheckWorkSection(questionType) {
|
|
const checkWorkSection = document.createElement('div');
|
|
checkWorkSection.className = 'check-work-section';
|
|
|
|
if (questionType === 'mathematical') {
|
|
// For math questions, ask for the answer
|
|
checkWorkSection.innerHTML = `
|
|
<div class="check-work-prompt">
|
|
<h4>${this.currentLanguage === 'de' ? '🧮 Was ist deine Antwort?' : '🧮 What is your answer?'}</h4>
|
|
<p>${this.currentLanguage === 'de'
|
|
? 'Nachdem du die Schritte durchdacht hast, was ist dein Ergebnis?'
|
|
: 'After thinking through the steps, what is your result?'}</p>
|
|
<div class="answer-input-section">
|
|
<input type="text" id="math-answer-input" placeholder="${this.currentLanguage === 'de' ? 'Deine Antwort...' : 'Your answer...'}" />
|
|
<button id="check-answer-btn" class="check-work-btn">
|
|
<span class="btn-icon">✓</span>
|
|
${this.currentLanguage === 'de' ? 'Antwort prüfen!' : 'Check Answer!'}
|
|
</button>
|
|
</div>
|
|
</div>
|
|
`;
|
|
} else {
|
|
// For other questions, check engagement
|
|
checkWorkSection.innerHTML = `
|
|
<div class="check-work-prompt">
|
|
<h4>${this.currentLanguage === 'de' ? '🤔 Fertig mit dem Nachdenken?' : '🤔 Finished Thinking?'}</h4>
|
|
<p>${this.currentLanguage === 'de'
|
|
? 'Hast du alle Schritte durchgedacht und eine Antwort gefunden? Klicke hier, wenn du bereit bist!'
|
|
: 'Have you thought through all the steps and found an answer? Click when you\'re ready!'}</p>
|
|
<button id="check-work-btn" class="check-work-btn">
|
|
<span class="btn-icon">🎯</span>
|
|
${this.currentLanguage === 'de' ? 'Meine Arbeit überprüfen!' : 'Check My Work!'}
|
|
</button>
|
|
</div>
|
|
`;
|
|
}
|
|
|
|
checkWorkSection.style.opacity = '0';
|
|
checkWorkSection.style.transform = 'translateY(20px)';
|
|
this.thinkingSteps.appendChild(checkWorkSection);
|
|
|
|
// Animate in
|
|
setTimeout(() => {
|
|
checkWorkSection.style.transition = 'all 0.6s ease-out';
|
|
checkWorkSection.style.opacity = '1';
|
|
checkWorkSection.style.transform = 'translateY(0)';
|
|
}, 100);
|
|
|
|
// Add click handlers
|
|
if (questionType === 'mathematical') {
|
|
const checkAnswerBtn = checkWorkSection.querySelector('#check-answer-btn');
|
|
const answerInput = checkWorkSection.querySelector('#math-answer-input');
|
|
|
|
checkAnswerBtn.addEventListener('click', () => {
|
|
this.checkMathAnswer(answerInput.value.trim(), checkWorkSection, questionType);
|
|
});
|
|
|
|
answerInput.addEventListener('keypress', (e) => {
|
|
if (e.key === 'Enter') {
|
|
this.checkMathAnswer(answerInput.value.trim(), checkWorkSection, questionType);
|
|
}
|
|
});
|
|
} else {
|
|
const checkWorkBtn = checkWorkSection.querySelector('#check-work-btn');
|
|
checkWorkBtn.addEventListener('click', () => {
|
|
const hasEngaged = this.checkIfChildEngaged();
|
|
|
|
if (hasEngaged) {
|
|
checkWorkSection.remove();
|
|
this.addCompletionMessage(questionType);
|
|
} else {
|
|
this.showEncouragementMessage();
|
|
}
|
|
});
|
|
}
|
|
}
|
|
|
|
async checkMathAnswer(userAnswer, checkWorkSection, questionType) {
|
|
if (!userAnswer) {
|
|
this.showAnswerRequiredMessage();
|
|
return;
|
|
}
|
|
|
|
try {
|
|
// Get the correct answer from the backend
|
|
const response = await fetch('/api/check-math-answer', {
|
|
method: 'POST',
|
|
headers: {
|
|
'Content-Type': 'application/json',
|
|
},
|
|
body: JSON.stringify({
|
|
question: this.questionInput.value.trim(),
|
|
userAnswer: userAnswer,
|
|
language: this.currentLanguage
|
|
})
|
|
});
|
|
|
|
const data = await response.json();
|
|
|
|
if (data.correct) {
|
|
// Correct answer!
|
|
checkWorkSection.remove();
|
|
this.addCompletionMessage(questionType);
|
|
} else {
|
|
// Wrong answer - encourage to try again
|
|
this.showTryAgainMessage(data.hint);
|
|
}
|
|
} catch (error) {
|
|
console.error('Error checking answer:', error);
|
|
// If backend fails, just show completion (fallback)
|
|
checkWorkSection.remove();
|
|
this.addCompletionMessage(questionType);
|
|
}
|
|
}
|
|
|
|
showAnswerRequiredMessage() {
|
|
const messageDiv = document.createElement('div');
|
|
messageDiv.className = 'encouragement-popup';
|
|
messageDiv.innerHTML = `
|
|
<div class="encouragement-content">
|
|
<span class="encouragement-icon">📝</span>
|
|
<p>${this.currentLanguage === 'de'
|
|
? 'Bitte gib deine Antwort ein!'
|
|
: 'Please enter your answer!'}</p>
|
|
</div>
|
|
`;
|
|
|
|
this.showTemporaryMessage(messageDiv);
|
|
}
|
|
|
|
showTryAgainMessage(hint) {
|
|
// Remove any existing try-again messages
|
|
const existingTryAgain = this.thinkingSteps.querySelector('.try-again-section');
|
|
if (existingTryAgain) {
|
|
existingTryAgain.remove();
|
|
}
|
|
|
|
const tryAgainSection = document.createElement('div');
|
|
tryAgainSection.className = 'try-again-section';
|
|
tryAgainSection.innerHTML = `
|
|
<div class="try-again-content">
|
|
<span class="try-again-icon">🤔</span>
|
|
<p class="try-again-text">${this.currentLanguage === 'de'
|
|
? 'Das ist nicht ganz richtig. Keine Sorge, das passiert!'
|
|
: 'That\'s not quite right. Don\'t worry, it happens!'}</p>
|
|
${hint ? `<small class="try-again-hint">${hint}</small>` : ''}
|
|
<div class="try-again-buttons">
|
|
<button class="more-hints-btn" id="more-hints-btn">
|
|
<span class="btn-icon">💡</span>
|
|
${this.currentLanguage === 'de' ? 'Mehr Hinweise' : 'More Hints'}
|
|
</button>
|
|
<button class="try-again-btn" id="try-again-btn">
|
|
<span class="btn-icon">🔄</span>
|
|
${this.currentLanguage === 'de' ? 'Nochmal versuchen' : 'Try Again'}
|
|
</button>
|
|
</div>
|
|
</div>
|
|
`;
|
|
|
|
tryAgainSection.style.opacity = '0';
|
|
tryAgainSection.style.transform = 'translateY(20px)';
|
|
this.thinkingSteps.appendChild(tryAgainSection);
|
|
|
|
// Animate in
|
|
setTimeout(() => {
|
|
tryAgainSection.style.transition = 'all 0.6s ease-out';
|
|
tryAgainSection.style.opacity = '1';
|
|
tryAgainSection.style.transform = 'translateY(0)';
|
|
}, 100);
|
|
|
|
// Add click handlers
|
|
const moreHintsBtn = tryAgainSection.querySelector('#more-hints-btn');
|
|
const tryAgainBtn = tryAgainSection.querySelector('#try-again-btn');
|
|
|
|
moreHintsBtn.addEventListener('click', () => {
|
|
this.showMoreHints();
|
|
});
|
|
|
|
tryAgainBtn.addEventListener('click', () => {
|
|
tryAgainSection.remove();
|
|
// Focus back on the answer input
|
|
const answerInput = this.thinkingSteps.querySelector('#math-answer-input');
|
|
if (answerInput) {
|
|
answerInput.focus();
|
|
answerInput.select();
|
|
}
|
|
});
|
|
}
|
|
|
|
async showMoreHints() {
|
|
const currentQuestion = this.questionInput.value.trim();
|
|
|
|
try {
|
|
// Show loading state
|
|
const moreHintsBtn = this.thinkingSteps.querySelector('#more-hints-btn');
|
|
if (moreHintsBtn) {
|
|
moreHintsBtn.innerHTML = `<span class="btn-icon">⏳</span> ${this.currentLanguage === 'de' ? 'Lädt...' : 'Loading...'}`;
|
|
moreHintsBtn.disabled = true;
|
|
}
|
|
|
|
const response = await fetch('/api/get-more-hints', {
|
|
method: 'POST',
|
|
headers: {
|
|
'Content-Type': 'application/json',
|
|
},
|
|
body: JSON.stringify({
|
|
question: currentQuestion,
|
|
language: this.currentLanguage,
|
|
previousHints: [] // Could track previous hints if needed
|
|
})
|
|
});
|
|
|
|
if (!response.ok) {
|
|
throw new Error(`HTTP error! status: ${response.status}`);
|
|
}
|
|
|
|
const data = await response.json();
|
|
|
|
if (data.success) {
|
|
// Remove try-again section
|
|
const tryAgainSection = this.thinkingSteps.querySelector('.try-again-section');
|
|
if (tryAgainSection) {
|
|
tryAgainSection.remove();
|
|
}
|
|
|
|
// Show additional hints
|
|
this.displayAdditionalHints(data.hints);
|
|
} else {
|
|
throw new Error(data.error || 'Failed to get hints');
|
|
}
|
|
|
|
} catch (error) {
|
|
console.error('Error getting more hints:', error);
|
|
|
|
// Show fallback encouragement
|
|
const fallbackMessage = this.currentLanguage === 'de'
|
|
? 'Versuche es mit deinen Fingern zu zählen oder male kleine Kreise!'
|
|
: 'Try counting with your fingers or drawing small circles!';
|
|
|
|
this.showSimpleMessage(fallbackMessage, '💡');
|
|
}
|
|
}
|
|
|
|
displayAdditionalHints(hints) {
|
|
const hintsSection = document.createElement('div');
|
|
hintsSection.className = 'additional-hints-section';
|
|
hintsSection.innerHTML = `
|
|
<div class="additional-hints-header">
|
|
<h4>💡 ${this.currentLanguage === 'de' ? 'Zusätzliche Hinweise:' : 'Additional Hints:'}</h4>
|
|
<p>${hints.encouragement}</p>
|
|
</div>
|
|
`;
|
|
|
|
// Add each hint with animation
|
|
hints.steps.forEach((hint, index) => {
|
|
setTimeout(() => {
|
|
const hintDiv = document.createElement('div');
|
|
hintDiv.className = 'additional-hint';
|
|
hintDiv.innerHTML = `
|
|
<div class="hint-number">${hint.id}</div>
|
|
<div class="hint-text">${hint.text}</div>
|
|
`;
|
|
|
|
hintDiv.style.opacity = '0';
|
|
hintDiv.style.transform = 'translateY(20px)';
|
|
hintsSection.appendChild(hintDiv);
|
|
|
|
// Animate in
|
|
setTimeout(() => {
|
|
hintDiv.style.transition = 'all 0.4s ease-out';
|
|
hintDiv.style.opacity = '1';
|
|
hintDiv.style.transform = 'translateY(0)';
|
|
}, 50);
|
|
}, index * 600);
|
|
});
|
|
|
|
// Add "Try Again" button after all hints
|
|
setTimeout(() => {
|
|
const tryAgainDiv = document.createElement('div');
|
|
tryAgainDiv.className = 'hints-try-again';
|
|
tryAgainDiv.innerHTML = `
|
|
<button class="try-again-after-hints-btn" id="try-again-after-hints">
|
|
<span class="btn-icon">🎯</span>
|
|
${this.currentLanguage === 'de' ? 'Jetzt versuchen!' : 'Try Now!'}
|
|
</button>
|
|
`;
|
|
|
|
hintsSection.appendChild(tryAgainDiv);
|
|
|
|
// Add click handler
|
|
const tryBtn = tryAgainDiv.querySelector('#try-again-after-hints');
|
|
tryBtn.addEventListener('click', () => {
|
|
hintsSection.remove();
|
|
// Focus on answer input
|
|
const answerInput = this.thinkingSteps.querySelector('#math-answer-input');
|
|
if (answerInput) {
|
|
answerInput.focus();
|
|
answerInput.select();
|
|
}
|
|
});
|
|
}, hints.steps.length * 600 + 500);
|
|
|
|
hintsSection.style.opacity = '0';
|
|
hintsSection.style.transform = 'translateY(20px)';
|
|
this.thinkingSteps.appendChild(hintsSection);
|
|
|
|
// Animate in
|
|
setTimeout(() => {
|
|
hintsSection.style.transition = 'all 0.6s ease-out';
|
|
hintsSection.style.opacity = '1';
|
|
hintsSection.style.transform = 'translateY(0)';
|
|
}, 100);
|
|
}
|
|
|
|
showSimpleMessage(message, icon) {
|
|
const messageDiv = document.createElement('div');
|
|
messageDiv.className = 'simple-message';
|
|
messageDiv.innerHTML = `
|
|
<div class="simple-message-content">
|
|
<span class="simple-message-icon">${icon}</span>
|
|
<p>${message}</p>
|
|
</div>
|
|
`;
|
|
|
|
this.showTemporaryMessage(messageDiv);
|
|
}
|
|
|
|
showTemporaryMessage(messageDiv) {
|
|
messageDiv.style.opacity = '0';
|
|
messageDiv.style.transform = 'scale(0.8)';
|
|
this.thinkingSteps.appendChild(messageDiv);
|
|
|
|
setTimeout(() => {
|
|
messageDiv.style.transition = 'all 0.4s ease-out';
|
|
messageDiv.style.opacity = '1';
|
|
messageDiv.style.transform = 'scale(1)';
|
|
}, 50);
|
|
|
|
setTimeout(() => {
|
|
messageDiv.style.transition = 'all 0.4s ease-out';
|
|
messageDiv.style.opacity = '0';
|
|
messageDiv.style.transform = 'scale(0.8)';
|
|
setTimeout(() => {
|
|
if (messageDiv.parentNode) {
|
|
messageDiv.remove();
|
|
}
|
|
}, 400);
|
|
}, 3000);
|
|
}
|
|
|
|
setupThinkingInteraction(stepDiv, stepId) {
|
|
const textarea = stepDiv.querySelector(`textarea[data-step="${stepId}"]`);
|
|
const shareButton = stepDiv.querySelector(`button[data-step="${stepId}"]`);
|
|
const feedbackDiv = stepDiv.querySelector(`div[data-step="${stepId}"]`);
|
|
|
|
if (!textarea || !shareButton || !feedbackDiv) return;
|
|
|
|
// Show share button when text is entered
|
|
textarea.addEventListener('input', () => {
|
|
const hasText = textarea.value.trim().length > 0;
|
|
shareButton.style.display = hasText ? 'inline-block' : 'none';
|
|
});
|
|
|
|
// Handle sharing thoughts with AI
|
|
shareButton.addEventListener('click', () => {
|
|
const thought = textarea.value.trim();
|
|
if (thought) {
|
|
this.shareThoughtWithAI(stepId, thought, feedbackDiv, shareButton);
|
|
}
|
|
});
|
|
|
|
// Allow Enter key to share (but Shift+Enter for new line)
|
|
textarea.addEventListener('keypress', (e) => {
|
|
if (e.key === 'Enter' && !e.shiftKey) {
|
|
e.preventDefault();
|
|
const thought = textarea.value.trim();
|
|
if (thought) {
|
|
this.shareThoughtWithAI(stepId, thought, feedbackDiv, shareButton);
|
|
}
|
|
}
|
|
});
|
|
}
|
|
|
|
async shareThoughtWithAI(stepId, thought, feedbackDiv, shareButton) {
|
|
const currentQuestion = this.questionInput.value.trim();
|
|
|
|
try {
|
|
// Show loading state
|
|
shareButton.innerHTML = `<span class="btn-icon">⏳</span> ${this.currentLanguage === 'de' ? 'Denke...' : 'Thinking...'}`;
|
|
shareButton.disabled = true;
|
|
|
|
const response = await fetch('/api/thinking-feedback', {
|
|
method: 'POST',
|
|
headers: {
|
|
'Content-Type': 'application/json',
|
|
},
|
|
body: JSON.stringify({
|
|
question: currentQuestion,
|
|
stepNumber: stepId,
|
|
userThought: thought,
|
|
language: this.currentLanguage
|
|
})
|
|
});
|
|
|
|
if (!response.ok) {
|
|
throw new Error(`HTTP error! status: ${response.status}`);
|
|
}
|
|
|
|
const data = await response.json();
|
|
|
|
if (data.success) {
|
|
this.displayThinkingFeedback(feedbackDiv, data.feedback, shareButton);
|
|
} else {
|
|
throw new Error(data.error || 'Failed to get feedback');
|
|
}
|
|
|
|
} catch (error) {
|
|
console.error('Error getting thinking feedback:', error);
|
|
|
|
// Show fallback positive feedback
|
|
const fallbackFeedback = {
|
|
response: this.currentLanguage === 'de'
|
|
? "Interessante Gedanken! Du denkst gut nach. 👍"
|
|
: "Interesting thoughts! You're thinking well. 👍",
|
|
type: 'positive'
|
|
};
|
|
|
|
this.displayThinkingFeedback(feedbackDiv, fallbackFeedback, shareButton);
|
|
}
|
|
}
|
|
|
|
displayThinkingFeedback(feedbackDiv, feedback, shareButton) {
|
|
// Determine feedback style based on type
|
|
const feedbackClass = feedback.type === 'positive' ? 'positive-feedback' :
|
|
feedback.type === 'neutral' ? 'neutral-feedback' : 'encouraging-feedback';
|
|
|
|
feedbackDiv.innerHTML = `
|
|
<div class="ai-feedback ${feedbackClass}">
|
|
<div class="feedback-icon">🤖</div>
|
|
<div class="feedback-content">
|
|
<p class="feedback-text">${feedback.response}</p>
|
|
<small class="feedback-source">✨ AI Teacher</small>
|
|
</div>
|
|
</div>
|
|
`;
|
|
|
|
// Show feedback with animation
|
|
feedbackDiv.style.display = 'block';
|
|
feedbackDiv.style.opacity = '0';
|
|
feedbackDiv.style.transform = 'translateY(-10px)';
|
|
|
|
setTimeout(() => {
|
|
feedbackDiv.style.transition = 'all 0.4s ease-out';
|
|
feedbackDiv.style.opacity = '1';
|
|
feedbackDiv.style.transform = 'translateY(0)';
|
|
}, 100);
|
|
|
|
// Update share button to "shared" state
|
|
shareButton.innerHTML = `<span class="btn-icon">✓</span> ${this.currentLanguage === 'de' ? 'Geteilt!' : 'Shared!'}`;
|
|
shareButton.disabled = false;
|
|
shareButton.style.background = 'rgba(76, 175, 80, 0.8)';
|
|
|
|
// Add option to share again after editing
|
|
const textarea = feedbackDiv.parentElement.querySelector('textarea');
|
|
textarea.addEventListener('input', () => {
|
|
if (shareButton.innerHTML.includes('✓')) {
|
|
shareButton.innerHTML = `<span class="btn-icon">💭</span> ${this.currentLanguage === 'de' ? 'Neue Gedanken teilen' : 'Share New Thoughts'}`;
|
|
shareButton.style.background = '';
|
|
}
|
|
}, { once: true });
|
|
}
|
|
}
|
|
|
|
// Initialize the app when DOM is loaded
|
|
document.addEventListener('DOMContentLoaded', function() {
|
|
window.kidsAI = new KidsAIExplorer();
|
|
});
|
|
|