reply fields added

This commit is contained in:
root
2025-06-29 13:30:47 +02:00
parent c33f444704
commit c277dcf90d
3 changed files with 431 additions and 0 deletions

View File

@@ -1059,12 +1059,30 @@ class KidsAIExplorer {
const feedbackClass = feedback.type === 'positive' ? 'positive-feedback' :
feedback.type === 'neutral' ? 'neutral-feedback' : 'encouraging-feedback';
// Check if AI is asking a question (contains question mark)
const isAIQuestion = feedback.response.includes('?');
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>
${isAIQuestion ? `
<div class="ai-question-reply" style="margin-top: 15px;">
<div class="reply-input-section">
<textarea
class="ai-reply-input"
placeholder="${this.currentLanguage === 'de' ? 'Deine Antwort auf die Frage...' : 'Your answer to the question...'}"
rows="2"></textarea>
<button class="reply-to-ai-btn">
<span class="btn-icon">💬</span>
${this.currentLanguage === 'de' ? 'Antworten' : 'Reply'}
</button>
</div>
<div class="ai-conversation" style="display: none;"></div>
</div>
` : ''}
</div>
</div>
`;
@@ -1080,6 +1098,11 @@ class KidsAIExplorer {
feedbackDiv.style.transform = 'translateY(0)';
}, 100);
// If AI asked a question, set up the reply functionality
if (isAIQuestion) {
this.setupAIConversation(feedbackDiv);
}
// Update share button to "shared" state
shareButton.innerHTML = `<span class="btn-icon">✓</span> ${this.currentLanguage === 'de' ? 'Geteilt!' : 'Shared!'}`;
shareButton.disabled = false;
@@ -1094,6 +1117,155 @@ class KidsAIExplorer {
}
}, { once: true });
}
setupAIConversation(feedbackDiv) {
const replyInput = feedbackDiv.querySelector('.ai-reply-input');
const replyButton = feedbackDiv.querySelector('.reply-to-ai-btn');
const conversationDiv = feedbackDiv.querySelector('.ai-conversation');
const handleReply = async () => {
const userReply = replyInput.value.trim();
if (!userReply) {
replyInput.focus();
return;
}
// Show loading state
replyButton.innerHTML = `<span class="btn-icon">⏳</span> ${this.currentLanguage === 'de' ? 'Lädt...' : 'Loading...'}`;
replyButton.disabled = true;
try {
// Get AI response to the user's reply
const response = await fetch('/api/ai-conversation', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify({
originalQuestion: this.questionInput.value.trim(),
userReply: userReply,
language: this.currentLanguage
})
});
if (!response.ok) {
throw new Error(`HTTP error! status: ${response.status}`);
}
const data = await response.json();
if (data.success) {
this.displayConversationExchange(conversationDiv, userReply, data.aiResponse);
// Clear input and reset button
replyInput.value = '';
replyButton.innerHTML = `<span class="btn-icon">💬</span> ${this.currentLanguage === 'de' ? 'Antworten' : 'Reply'}`;
replyButton.disabled = false;
// If AI asks another question, keep conversation going
if (data.aiResponse.includes('?')) {
replyInput.placeholder = this.currentLanguage === 'de' ? 'Deine nächste Antwort...' : 'Your next answer...';
replyInput.focus();
} else {
// Conversation ended, show completion
replyInput.style.display = 'none';
replyButton.style.display = 'none';
this.showConversationCompletion(conversationDiv);
}
} else {
throw new Error(data.error || 'Conversation failed');
}
} catch (error) {
console.error('Error in AI conversation:', error);
// Show fallback response
const fallbackResponse = this.currentLanguage === 'de'
? "Das ist eine interessante Antwort! Du denkst gut über das Thema nach."
: "That's an interesting answer! You're thinking well about this topic.";
this.displayConversationExchange(conversationDiv, userReply, fallbackResponse);
// Reset button
replyButton.innerHTML = `<span class="btn-icon">💬</span> ${this.currentLanguage === 'de' ? 'Antworten' : 'Reply'}`;
replyButton.disabled = false;
}
};
// Add click handler for reply button
replyButton.addEventListener('click', handleReply);
// Add enter key handler for reply input
replyInput.addEventListener('keypress', (e) => {
if (e.key === 'Enter' && !e.shiftKey) {
e.preventDefault();
handleReply();
}
});
}
displayConversationExchange(conversationDiv, userReply, aiResponse) {
// Show conversation area if hidden
conversationDiv.style.display = 'block';
// Add user's reply
const userDiv = document.createElement('div');
userDiv.className = 'conversation-user';
userDiv.innerHTML = `
<div class="conversation-bubble user-bubble">
<div class="bubble-icon">👤</div>
<div class="bubble-text">${userReply}</div>
</div>
`;
// Add AI's response
const aiDiv = document.createElement('div');
aiDiv.className = 'conversation-ai';
aiDiv.innerHTML = `
<div class="conversation-bubble ai-bubble">
<div class="bubble-icon">🤖</div>
<div class="bubble-text">${aiResponse}</div>
</div>
`;
// Add with animation
conversationDiv.appendChild(userDiv);
conversationDiv.appendChild(aiDiv);
// Animate in
setTimeout(() => {
userDiv.style.opacity = '1';
userDiv.style.transform = 'translateY(0)';
}, 100);
setTimeout(() => {
aiDiv.style.opacity = '1';
aiDiv.style.transform = 'translateY(0)';
}, 300);
// Scroll to show new conversation
conversationDiv.scrollIntoView({ behavior: 'smooth', block: 'nearest' });
}
showConversationCompletion(conversationDiv) {
const completionDiv = document.createElement('div');
completionDiv.className = 'conversation-completion';
completionDiv.innerHTML = `
<div class="completion-message">
<span class="completion-icon">🌟</span>
<p>${this.currentLanguage === 'de'
? 'Tolle Unterhaltung! Du hast großartig mitgedacht.'
: 'Great conversation! You thought along wonderfully.'}</p>
</div>
`;
conversationDiv.appendChild(completionDiv);
setTimeout(() => {
completionDiv.style.opacity = '1';
completionDiv.style.transform = 'translateY(0)';
}, 100);
}
}
// Initialize the app when DOM is loaded

View File

@@ -288,6 +288,82 @@ app.post('/api/thinking-feedback', async (req, res) => {
}
});
// API endpoint for AI conversation (when child replies to AI questions)
app.post('/api/ai-conversation', async (req, res) => {
const { originalQuestion, userReply, language = 'en' } = req.body;
if (!originalQuestion || !userReply) {
return res.status(400).json({
success: false,
error: 'Original question and user reply are required'
});
}
try {
// Get AI response to continue the conversation
const aiResponse = await getConversationResponse(originalQuestion, userReply, language);
res.json({
success: true,
aiResponse: aiResponse,
originalQuestion: originalQuestion,
userReply: userReply,
language: language
});
} catch (error) {
console.error('AI Conversation Error:', error);
// Fallback response if AI fails
const fallbackResponse = language === 'de'
? "Das ist eine interessante Antwort! Du denkst gut über das Thema nach."
: "That's an interesting answer! You're thinking well about this topic.";
res.json({
success: true,
aiResponse: fallbackResponse,
originalQuestion: originalQuestion,
userReply: userReply,
language: language,
fallback: true
});
}
});
// Generate conversational AI response
async function getConversationResponse(originalQuestion, userReply, language) {
const isGerman = language === 'de';
const systemPrompt = isGerman
? "Du bist ein geduldiger Lehrer, der mit einem Kind über ein interessantes Thema spricht. Das Kind hat gerade auf eine deiner Fragen geantwortet. Reagiere natürlich auf ihre Antwort: anerkenne was sie gesagt haben, baue darauf auf, und stelle wenn nötig eine weiterführende Frage um ihr Verständnis zu vertiefen. Sei ermutigend und neugierig. Halte deine Antworten kurz (1-2 Sätze) und altersgerecht."
: "You are a patient teacher having a conversation with a child about an interesting topic. The child just answered one of your questions. Respond naturally to their answer: acknowledge what they said, build upon it, and ask a follow-up question if needed to deepen their understanding. Be encouraging and curious. Keep your responses short (1-2 sentences) and age-appropriate.";
const userPrompt = isGerman
? `Ursprüngliche Frage: "${originalQuestion}"\nKind hat geantwortet: "${userReply}"\n\nReagiere natürlich auf ihre Antwort und führe das Gespräch weiter.`
: `Original question: "${originalQuestion}"\nChild replied: "${userReply}"\n\nRespond naturally to their answer and continue the conversation.`;
try {
const completion = await openai.chat.completions.create({
model: "gpt-3.5-turbo",
messages: [
{ role: "system", content: systemPrompt },
{ role: "user", content: userPrompt }
],
max_tokens: 150,
temperature: 0.8
});
const aiResponse = completion.choices[0]?.message?.content || '';
console.log(`✅ AI conversation response: ${aiResponse.substring(0, 100)}...`);
return aiResponse;
} catch (error) {
console.log('❌ AI conversation error:', error.message);
throw error;
}
}
// Function to calculate the correct answer for simple math expressions
function calculateMathAnswer(question) {
const questionLower = question.toLowerCase().trim();

View File

@@ -1555,3 +1555,186 @@ body {
align-self: center;
}
}
/* AI Conversation Styles */
.ai-question-reply {
border-top: 1px solid rgba(255, 255, 255, 0.2);
padding-top: 15px;
margin-top: 15px;
}
.reply-input-section {
display: flex;
gap: 10px;
align-items: flex-end;
margin-bottom: 15px;
}
.ai-reply-input {
flex: 1;
padding: 10px 12px;
border: 2px solid rgba(255, 255, 255, 0.3);
border-radius: 12px;
background: rgba(255, 255, 255, 0.2);
color: white;
font-size: 0.9rem;
resize: vertical;
min-height: 44px;
backdrop-filter: blur(10px);
}
.ai-reply-input::placeholder {
color: rgba(255, 255, 255, 0.7);
}
.ai-reply-input:focus {
outline: none;
border-color: rgba(255, 255, 255, 0.6);
background: rgba(255, 255, 255, 0.3);
}
.reply-to-ai-btn {
background: rgba(255, 255, 255, 0.2);
border: 2px solid rgba(255, 255, 255, 0.3);
color: white;
padding: 10px 16px;
border-radius: 12px;
font-size: 0.9rem;
font-weight: 600;
cursor: pointer;
transition: all 0.3s ease;
backdrop-filter: blur(10px);
white-space: nowrap;
}
.reply-to-ai-btn:hover:not(:disabled) {
background: rgba(255, 255, 255, 0.3);
border-color: rgba(255, 255, 255, 0.5);
transform: translateY(-1px);
}
.reply-to-ai-btn:disabled {
opacity: 0.6;
cursor: not-allowed;
transform: none;
}
/* Conversation Exchange Styles */
.ai-conversation {
max-height: 300px;
overflow-y: auto;
padding: 10px 0;
border-radius: 12px;
}
.conversation-user,
.conversation-ai {
margin-bottom: 12px;
opacity: 0;
transform: translateY(10px);
transition: all 0.4s ease-out;
}
.conversation-bubble {
display: flex;
gap: 10px;
align-items: flex-start;
padding: 12px;
border-radius: 12px;
max-width: 90%;
}
.user-bubble {
background: rgba(255, 255, 255, 0.15);
border: 1px solid rgba(255, 255, 255, 0.2);
margin-left: auto;
flex-direction: row-reverse;
}
.ai-bubble {
background: rgba(76, 175, 80, 0.2);
border: 1px solid rgba(76, 175, 80, 0.3);
margin-right: auto;
}
.bubble-icon {
font-size: 1.2rem;
flex-shrink: 0;
width: 30px;
height: 30px;
display: flex;
align-items: center;
justify-content: center;
border-radius: 50%;
background: rgba(255, 255, 255, 0.2);
}
.bubble-text {
color: white;
font-size: 0.9rem;
line-height: 1.4;
flex: 1;
}
.user-bubble .bubble-text {
text-align: right;
}
/* Conversation Completion */
.conversation-completion {
text-align: center;
margin-top: 15px;
opacity: 0;
transform: translateY(10px);
transition: all 0.4s ease-out;
}
.completion-message {
background: linear-gradient(145deg, #FFD700, #FFA500);
border-radius: 15px;
padding: 15px;
color: white;
font-weight: 600;
}
.completion-icon {
font-size: 1.5rem;
display: block;
margin-bottom: 8px;
}
.completion-message p {
margin: 0;
font-size: 0.9rem;
}
/* Mobile responsiveness for conversation */
@media (max-width: 768px) {
.reply-input-section {
flex-direction: column;
gap: 10px;
}
.reply-to-ai-btn {
width: 100%;
}
.conversation-bubble {
max-width: 95%;
padding: 10px;
}
.bubble-icon {
width: 25px;
height: 25px;
font-size: 1rem;
}
.bubble-text {
font-size: 0.85rem;
}
.ai-conversation {
max-height: 250px;
}
}