Files
kidsai/html/kidsai/script.js
2025-06-29 13:15:14 +02:00

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();
});