- Add question type classification (mathematical, factual_simple, exploratory) - Implement conditional UI: no text fields for math questions - Add math-specific step-by-step guidance without revealing answers - Enhanced AI prompts for subject-specific teaching approaches - Remove unnecessary action buttons (Research Ideas, Try Experiments, Discuss with Others) - Improve user experience with focused, educational interactions
344 lines
13 KiB
JavaScript
344 lines
13 KiB
JavaScript
const express = require('express');
|
|
const cors = require('cors');
|
|
const path = require('path');
|
|
const fetch = require('node-fetch');
|
|
const OpenAI = require('openai');
|
|
|
|
// Load environment variables from .env file
|
|
require('dotenv').config();
|
|
|
|
const app = express();
|
|
const PORT = process.env.PORT || 3002;
|
|
|
|
// OpenAI Configuration (Primary AI service)
|
|
const openai = new OpenAI({
|
|
apiKey: process.env.OPENAI_API_KEY
|
|
});
|
|
|
|
// Hugging Face API configuration (Backup)
|
|
const HF_MODELS = [
|
|
'microsoft/DialoGPT-small',
|
|
'google/flan-t5-small',
|
|
'facebook/blenderbot-400M-distill',
|
|
'microsoft/DialoGPT-medium'
|
|
];
|
|
|
|
const HF_API_TOKEN = process.env.HUGGING_FACE_TOKEN;
|
|
|
|
// Educational prompts for AI to guide thinking instead of giving direct answers
|
|
const EDUCATIONAL_PROMPTS = {
|
|
en: {
|
|
systemPrompt: "You are an educational assistant for children. Instead of giving direct answers, ask 2-3 guiding questions that help children think through the problem themselves. Be encouraging and use simple language. Focus on the thinking process, not the answer.",
|
|
prefix: "That's a great question! Let me help you think through this step by step. Instead of telling you the answer, here are some questions to guide your thinking:"
|
|
},
|
|
de: {
|
|
systemPrompt: "Du bist ein Lernassistent für Kinder. Anstatt direkte Antworten zu geben, stelle 2-3 Leitfragen, die Kindern helfen, das Problem selbst zu durchdenken. Sei ermutigend und verwende einfache Sprache. Konzentriere dich auf den Denkprozess, nicht auf die Antwort.",
|
|
prefix: "Das ist eine tolle Frage! Lass mich dir helfen, Schritt für Schritt darüber nachzudenken. Anstatt dir die Antwort zu sagen, hier sind einige Fragen, die dein Denken leiten:"
|
|
}
|
|
};
|
|
|
|
// Middleware
|
|
app.use(cors());
|
|
app.use(express.json());
|
|
app.use(express.static('.'));
|
|
|
|
// Serve the main page
|
|
app.get('/', (req, res) => {
|
|
res.sendFile(path.join(__dirname, 'index.html'));
|
|
});
|
|
|
|
// API endpoint for AI-powered educational guidance
|
|
app.post('/api/ask', async (req, res) => {
|
|
const { question, language = 'en' } = req.body;
|
|
|
|
if (!question || question.trim().length === 0) {
|
|
return res.status(400).json({
|
|
success: false,
|
|
error: 'Question is required'
|
|
});
|
|
}
|
|
|
|
try {
|
|
// Get AI-powered guidance
|
|
const aiGuidance = await getAIGuidance(question, language);
|
|
|
|
res.json({
|
|
success: true,
|
|
guidance: aiGuidance,
|
|
question: question,
|
|
language: language
|
|
});
|
|
|
|
} catch (error) {
|
|
console.error('AI API Error:', error);
|
|
|
|
// Fallback to rule-based guidance if AI fails
|
|
const fallbackGuidance = getFallbackGuidance(question, language);
|
|
|
|
res.json({
|
|
success: true,
|
|
guidance: fallbackGuidance,
|
|
question: question,
|
|
language: language,
|
|
fallback: true
|
|
});
|
|
}
|
|
});
|
|
|
|
// Function to get OpenAI-powered educational guidance (Primary)
|
|
async function getOpenAIGuidance(question, language) {
|
|
console.log('🤖 Calling OpenAI with GPT-3.5-turbo...');
|
|
|
|
const isGerman = language === 'de';
|
|
|
|
const systemPrompt = isGerman
|
|
? "Du bist ein pädagogischer Assistent für Kinder. Anstatt direkte Antworten zu geben, stellst du 3-4 durchdachte Leitfragen, die Kindern helfen, selbst über das Problem nachzudenken. Verwende einfache Sprache, sei ermutigend und konzentriere dich auf den Denkprozess. Formatiere als nummerierte Liste."
|
|
: "You are an educational assistant for children. Instead of giving direct answers, provide 3-4 thoughtful guiding questions that help children think through the problem themselves. Use simple language, be encouraging, and focus on the thinking process. Format as a numbered list.";
|
|
|
|
const userPrompt = isGerman
|
|
? `Ein Kind hat gefragt: "${question}". Hilf ihm dabei, selbst über die Antwort nachzudenken, indem du Leitfragen stellst.`
|
|
: `A child asked: "${question}". Help them think through the answer themselves by providing guiding questions.`;
|
|
|
|
try {
|
|
const completion = await openai.chat.completions.create({
|
|
model: "gpt-3.5-turbo",
|
|
messages: [
|
|
{ role: "system", content: systemPrompt },
|
|
{ role: "user", content: userPrompt }
|
|
],
|
|
max_tokens: 200,
|
|
temperature: 0.7
|
|
});
|
|
|
|
const aiResponse = completion.choices[0]?.message?.content || '';
|
|
console.log('✅ OpenAI response received:', aiResponse.substring(0, 100) + '...');
|
|
|
|
// Parse the response into steps
|
|
const steps = parseOpenAIResponseToSteps(aiResponse, language);
|
|
|
|
return {
|
|
type: 'ai-powered',
|
|
steps: steps,
|
|
encouragement: getRandomEncouragement(language),
|
|
source: 'OpenAI GPT-3.5'
|
|
};
|
|
|
|
} catch (error) {
|
|
console.log('❌ OpenAI error:', error.message);
|
|
throw error;
|
|
}
|
|
}
|
|
|
|
// Function to parse OpenAI response into thinking steps
|
|
function parseOpenAIResponseToSteps(text, language) {
|
|
const lines = text.split('\n').filter(line => line.trim());
|
|
const steps = [];
|
|
|
|
lines.forEach((line, index) => {
|
|
const trimmed = line.trim();
|
|
// Look for numbered items or questions
|
|
if (trimmed && (trimmed.match(/^\d+\./) || trimmed.includes('?') || trimmed.length > 10)) {
|
|
// Clean up numbering and formatting
|
|
const cleaned = trimmed.replace(/^\d+\.\s*/, '').replace(/^-\s*/, '').trim();
|
|
if (cleaned.length > 5) {
|
|
steps.push({
|
|
id: steps.length + 1,
|
|
text: cleaned,
|
|
type: 'question'
|
|
});
|
|
}
|
|
}
|
|
});
|
|
|
|
// Ensure we have at least 2 steps
|
|
if (steps.length < 2) {
|
|
const fallback = getFallbackGuidance('', language);
|
|
return fallback.steps.slice(0, 3);
|
|
}
|
|
|
|
return steps.slice(0, 4); // Limit to 4 steps max
|
|
}
|
|
|
|
// Function to get Hugging Face guidance (Backup)
|
|
async function getHuggingFaceGuidance(question, language) {
|
|
console.log('🤖 Trying Hugging Face as backup...');
|
|
|
|
// Try each model until one works
|
|
for (let i = 0; i < HF_MODELS.length; i++) {
|
|
const model = HF_MODELS[i];
|
|
const apiUrl = `https://api-inference.huggingface.co/models/${model}`;
|
|
|
|
try {
|
|
console.log(`📡 Trying model ${i + 1}/${HF_MODELS.length}: ${model}`);
|
|
|
|
const educationalPrompt = `Help a child think about this question: "${question}".
|
|
Don't give the answer. Instead, ask 3 guiding questions that help them discover it themselves.
|
|
Format: 1. [question] 2. [question] 3. [question]`;
|
|
|
|
const response = await fetch(apiUrl, {
|
|
method: 'POST',
|
|
headers: {
|
|
'Authorization': HF_API_TOKEN ? `Bearer ${HF_API_TOKEN}` : undefined,
|
|
'Content-Type': 'application/json',
|
|
},
|
|
body: JSON.stringify({
|
|
inputs: educationalPrompt,
|
|
parameters: {
|
|
max_length: 120,
|
|
temperature: 0.8,
|
|
do_sample: true,
|
|
pad_token_id: 50256
|
|
}
|
|
})
|
|
});
|
|
|
|
if (response.ok) {
|
|
const data = await response.json();
|
|
|
|
if (data.error && data.error.includes('loading')) {
|
|
console.log(`⏳ Model ${model} is loading, trying next...`);
|
|
continue;
|
|
}
|
|
|
|
if (data.error) {
|
|
console.log(`❌ Model ${model} error:`, data.error);
|
|
continue;
|
|
}
|
|
|
|
console.log(`✅ Success with model ${model}`);
|
|
|
|
let aiText = data[0]?.generated_text || '';
|
|
|
|
if (aiText.trim().length > 10) {
|
|
const steps = parseHuggingFaceResponseToSteps(aiText, language);
|
|
|
|
return {
|
|
type: 'ai-powered',
|
|
steps: steps,
|
|
encouragement: getRandomEncouragement(language),
|
|
source: `Hugging Face (${model.split('/')[1]})`
|
|
};
|
|
}
|
|
}
|
|
|
|
} catch (error) {
|
|
console.log(`⚠️ Model ${model} failed:`, error.message);
|
|
continue;
|
|
}
|
|
}
|
|
|
|
throw new Error('All Hugging Face models are currently unavailable');
|
|
}
|
|
|
|
// Function to parse Hugging Face response into thinking steps
|
|
function parseHuggingFaceResponseToSteps(text, language) {
|
|
const lines = text.split('\n').filter(line => line.trim());
|
|
const steps = [];
|
|
|
|
lines.forEach((line, index) => {
|
|
const trimmed = line.trim();
|
|
if (trimmed && trimmed.length > 10) {
|
|
const cleaned = trimmed.replace(/^\d+\.\s*/, '').replace(/^-\s*/, '');
|
|
if (cleaned.length > 5) {
|
|
steps.push({
|
|
id: index + 1,
|
|
text: cleaned,
|
|
type: 'question'
|
|
});
|
|
}
|
|
}
|
|
});
|
|
|
|
if (steps.length < 2) {
|
|
const fallback = getFallbackGuidance('', language);
|
|
return fallback.steps.slice(0, 3);
|
|
}
|
|
|
|
return steps.slice(0, 4);
|
|
}
|
|
|
|
// Updated main function that tries OpenAI first, then Hugging Face, then local fallback
|
|
async function getAIGuidance(question, language) {
|
|
// Try OpenAI first (most reliable)
|
|
if (process.env.OPENAI_API_KEY) {
|
|
try {
|
|
return await getOpenAIGuidance(question, language);
|
|
} catch (error) {
|
|
console.log('⚠️ OpenAI failed, trying Hugging Face...');
|
|
}
|
|
}
|
|
|
|
// Try Hugging Face as backup
|
|
if (process.env.HUGGING_FACE_TOKEN) {
|
|
try {
|
|
return await getHuggingFaceGuidance(question, language);
|
|
} catch (error) {
|
|
console.log('⚠️ Hugging Face failed, using local fallback...');
|
|
}
|
|
}
|
|
|
|
// Final fallback - this will throw error to trigger local guidance
|
|
throw new Error('All AI services are currently unavailable');
|
|
}
|
|
|
|
// Fallback guidance for when AI is unavailable
|
|
function getFallbackGuidance(question, language) {
|
|
const isGerman = language === 'de';
|
|
|
|
const fallbackSteps = isGerman ? [
|
|
{ id: 1, text: "Was weißt du bereits über dieses Thema?", type: 'question' },
|
|
{ id: 2, text: "Welche Teile der Frage verstehst du, und welche sind unklar?", type: 'question' },
|
|
{ id: 3, text: "Wo könntest du mehr Informationen finden?", type: 'question' },
|
|
{ id: 4, text: "Kannst du das Problem in kleinere Teile aufteilen?", type: 'question' }
|
|
] : [
|
|
{ id: 1, text: "What do you already know about this topic?", type: 'question' },
|
|
{ id: 2, text: "Which parts of the question do you understand, and which are unclear?", type: 'question' },
|
|
{ id: 3, text: "Where could you find more information about this?", type: 'question' },
|
|
{ id: 4, text: "Can you break this problem down into smaller parts?", type: 'question' }
|
|
];
|
|
|
|
return {
|
|
type: 'rule-based',
|
|
steps: fallbackSteps,
|
|
encouragement: getRandomEncouragement(language),
|
|
source: 'Educational Framework'
|
|
};
|
|
}
|
|
|
|
// Get random encouragement
|
|
function getRandomEncouragement(language) {
|
|
const encouragements = language === 'de' ? [
|
|
"Großartige Frage! Du denkst wie ein echter Forscher! 🔬",
|
|
"Super! Lass uns das zusammen herausfinden! 🚀",
|
|
"Wow, das ist eine kluge Frage! 🤔",
|
|
"Du bist auf dem richtigen Weg! 🌟"
|
|
] : [
|
|
"Great question! You're thinking like a real scientist! 🔬",
|
|
"Awesome! Let's figure this out together! 🚀",
|
|
"Wow, that's a smart question! 🤔",
|
|
"You're on the right track! 🌟"
|
|
];
|
|
|
|
return encouragements[Math.floor(Math.random() * encouragements.length)];
|
|
}
|
|
|
|
// Health check endpoint
|
|
app.get('/api/health', (req, res) => {
|
|
res.json({
|
|
status: 'healthy',
|
|
timestamp: new Date().toISOString(),
|
|
ai_services: {
|
|
openai: !!process.env.OPENAI_API_KEY,
|
|
huggingface: !!process.env.HUGGING_FACE_TOKEN
|
|
}
|
|
});
|
|
});
|
|
|
|
app.listen(PORT, () => {
|
|
console.log(`🚀 KidsAI Explorer server running on port ${PORT}`);
|
|
console.log(`📖 Visit http://localhost:${PORT} to start exploring!`);
|
|
console.log(`🤖 AI Services: OpenAI=${!!process.env.OPENAI_API_KEY}, HuggingFace=${!!process.env.HUGGING_FACE_TOKEN}`);
|
|
});
|
|
|
|
module.exports = app;
|