From c277dcf90d0cd02308d2844424f69dfa23e72d74 Mon Sep 17 00:00:00 2001 From: root Date: Sun, 29 Jun 2025 13:30:47 +0200 Subject: [PATCH] reply fields added --- html/kidsai/script.js | 172 +++++++++++++++++++++++++++++++++++++++ html/kidsai/server.js | 76 ++++++++++++++++++ html/kidsai/style.css | 183 ++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 431 insertions(+) diff --git a/html/kidsai/script.js b/html/kidsai/script.js index 1e363c4..c06c44c 100644 --- a/html/kidsai/script.js +++ b/html/kidsai/script.js @@ -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 = `
`; @@ -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 = ` ${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 = ` ${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 = `💬 ${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 = `💬 ${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 = ` +
+
👤
+
${userReply}
+
+ `; + + // Add AI's response + const aiDiv = document.createElement('div'); + aiDiv.className = 'conversation-ai'; + aiDiv.innerHTML = ` +
+
🤖
+
${aiResponse}
+
+ `; + + // 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 = ` +
+ 🌟 +

${this.currentLanguage === 'de' + ? 'Tolle Unterhaltung! Du hast großartig mitgedacht.' + : 'Great conversation! You thought along wonderfully.'}

+
+ `; + + conversationDiv.appendChild(completionDiv); + + setTimeout(() => { + completionDiv.style.opacity = '1'; + completionDiv.style.transform = 'translateY(0)'; + }, 100); + } } // Initialize the app when DOM is loaded diff --git a/html/kidsai/server.js b/html/kidsai/server.js index 51677b6..65f92cf 100644 --- a/html/kidsai/server.js +++ b/html/kidsai/server.js @@ -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(); diff --git a/html/kidsai/style.css b/html/kidsai/style.css index 57a68fe..605502a 100755 --- a/html/kidsai/style.css +++ b/html/kidsai/style.css @@ -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; + } +}