reply fields added
This commit is contained in:
@@ -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
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user