reply fields added
This commit is contained in:
@@ -1059,12 +1059,30 @@ class KidsAIExplorer {
|
|||||||
const feedbackClass = feedback.type === 'positive' ? 'positive-feedback' :
|
const feedbackClass = feedback.type === 'positive' ? 'positive-feedback' :
|
||||||
feedback.type === 'neutral' ? 'neutral-feedback' : 'encouraging-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 = `
|
feedbackDiv.innerHTML = `
|
||||||
<div class="ai-feedback ${feedbackClass}">
|
<div class="ai-feedback ${feedbackClass}">
|
||||||
<div class="feedback-icon">🤖</div>
|
<div class="feedback-icon">🤖</div>
|
||||||
<div class="feedback-content">
|
<div class="feedback-content">
|
||||||
<p class="feedback-text">${feedback.response}</p>
|
<p class="feedback-text">${feedback.response}</p>
|
||||||
<small class="feedback-source">✨ AI Teacher</small>
|
<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>
|
||||||
</div>
|
</div>
|
||||||
`;
|
`;
|
||||||
@@ -1080,6 +1098,11 @@ class KidsAIExplorer {
|
|||||||
feedbackDiv.style.transform = 'translateY(0)';
|
feedbackDiv.style.transform = 'translateY(0)';
|
||||||
}, 100);
|
}, 100);
|
||||||
|
|
||||||
|
// If AI asked a question, set up the reply functionality
|
||||||
|
if (isAIQuestion) {
|
||||||
|
this.setupAIConversation(feedbackDiv);
|
||||||
|
}
|
||||||
|
|
||||||
// Update share button to "shared" state
|
// Update share button to "shared" state
|
||||||
shareButton.innerHTML = `<span class="btn-icon">✓</span> ${this.currentLanguage === 'de' ? 'Geteilt!' : 'Shared!'}`;
|
shareButton.innerHTML = `<span class="btn-icon">✓</span> ${this.currentLanguage === 'de' ? 'Geteilt!' : 'Shared!'}`;
|
||||||
shareButton.disabled = false;
|
shareButton.disabled = false;
|
||||||
@@ -1094,6 +1117,155 @@ class KidsAIExplorer {
|
|||||||
}
|
}
|
||||||
}, { once: true });
|
}, { 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
|
// 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 to calculate the correct answer for simple math expressions
|
||||||
function calculateMathAnswer(question) {
|
function calculateMathAnswer(question) {
|
||||||
const questionLower = question.toLowerCase().trim();
|
const questionLower = question.toLowerCase().trim();
|
||||||
|
|||||||
@@ -1555,3 +1555,186 @@ body {
|
|||||||
align-self: center;
|
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