diff --git "a/app.py" "b/app.py" --- "a/app.py" +++ "b/app.py" @@ -1,28 +1,47 @@ """ -SEA Prep Pro - Fully Functional Version -Modern LMS for SEA Exam Preparation +SEA Prep Pro - AI-Powered Tutor +Real AI question generation, feedback, and adaptive learning """ import gradio as gr import sqlite3 import json import os import re -import tempfile -from datetime import datetime -from typing import List, Optional, Dict, Tuple import random +from datetime import datetime +from typing import List, Dict, Optional import threading +import requests # Thread-local storage for database connections thread_local = threading.local() -# PDF processing flag -PDF_AVAILABLE = False -try: - import pdfplumber - PDF_AVAILABLE = True -except ImportError: - print("PDF processing disabled - pdfplumber not available") +# ==================== AI CONFIGURATION ==================== +# Using Hugging Face Inference API for AI capabilities +class AIConfig: + # Free models from Hugging Face + QUESTION_GENERATION_MODEL = "google/flan-t5-base" # Good for educational Q&A + ANSWER_EVALUATION_MODEL = "distilbert-base-uncased" # For text classification + EXPLANATION_MODEL = "google/flan-t5-small" # For generating explanations + + # API endpoints (using Hugging Face Inference API) + HF_API_URL = "https://api-inference.huggingface.co/models/" + HF_TOKEN = os.environ.get("HF_TOKEN", "") # Add your token in Hugging Face Secrets + + # Local fallback models (simpler but works without API) + MATH_KEYWORDS = { + "Fractions": ["simplify", "add", "subtract", "multiply", "divide", "numerator", "denominator"], + "Geometry": ["area", "perimeter", "volume", "triangle", "rectangle", "circle"], + "Word Problems": ["how many", "total", "remaining", "share", "each", "together"], + "Algebra": ["solve for x", "equation", "variable", "expression"] + } + + ENGLISH_KEYWORDS = { + "Grammar": ["correct the sentence", "choose the correct", "verb", "noun", "adjective"], + "Vocabulary": ["synonym", "antonym", "meaning of", "definition"], + "Comprehension": ["read the passage", "main idea", "infer", "conclude"], + "Writing": ["write a paragraph", "essay", "letter", "composition"] + } # ==================== DATABASE SETUP ==================== def get_db_connection(db_name="questions"): @@ -57,19 +76,13 @@ def init_databases(): question_type TEXT, difficulty INTEGER DEFAULT 3, options TEXT, - answer TEXT, + correct_answer TEXT, explanation TEXT, + ai_generated BOOLEAN DEFAULT 0, created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ) ''') - # Add sample questions if empty - q_cur.execute("SELECT COUNT(*) FROM questions") - if q_cur.fetchone()[0] == 0: - add_sample_data(q_cur) - - q_conn.commit() - # Student database s_conn = get_db_connection("students") s_cur = s_conn.cursor() @@ -79,7 +92,11 @@ def init_databases(): id INTEGER PRIMARY KEY AUTOINCREMENT, student_id TEXT DEFAULT 'student_001', question_id INTEGER, + student_answer TEXT, correct BOOLEAN, + confidence_score REAL, + feedback TEXT, + time_spent INTEGER, topic TEXT, date TIMESTAMP DEFAULT CURRENT_TIMESTAMP ) @@ -93,400 +110,824 @@ def init_databases(): current_streak INTEGER DEFAULT 0, best_streak INTEGER DEFAULT 0, level INTEGER DEFAULT 1, - xp INTEGER DEFAULT 0 + xp INTEGER DEFAULT 0, + weak_topics TEXT, + learning_style TEXT DEFAULT 'balanced' ) ''') s_conn.commit() -def add_sample_data(cursor): - """Add comprehensive sample questions""" - samples = [ - ("Math", "Fractions", "Simplify 12/16 to its lowest terms.", "MCQ", 3, - '{"A": "1/2", "B": "2/3", "C": "3/4", "D": "4/5"}', "C", - "Divide numerator and denominator by 4: 12÷4=3, 16÷4=4"), - - ("Math", "Word Problems", "If John has 24 marbles and gives 1/3 to Sarah, how many does he have left?", "Short", 4, None, "16", - "1/3 of 24 = 8. 24 - 8 = 16"), - - ("Math", "Geometry", "What is the area of a rectangle with length 8cm and width 5cm?", "Short", 2, None, "40", - "Area = length × width = 8 × 5 = 40cm²"), - - ("Math", "Fractions", "Add 1/4 + 1/2", "MCQ", 2, - '{"A": "1/6", "B": "2/6", "C": "3/4", "D": "2/3"}', "C", - "Convert to common denominator: 1/4 + 2/4 = 3/4"), - - ("English", "Grammar", "Choose the correct sentence:", "MCQ", 2, - '{"A": "He go to school.", "B": "He goes to school.", "C": "He going to school.", "D": "He gone to school."}', "B", - "Subject 'He' requires third person singular 'goes'"), - - ("English", "Vocabulary", "What is a synonym for 'happy'?", "MCQ", 1, - '{"A": "Sad", "B": "Joyful", "C": "Angry", "D": "Tired"}', "B", - "Happy means feeling pleasure or contentment"), - - ("English", "Comprehension", "What is the purpose of an introduction in an essay?", "Short", 3, None, - "To present the topic and thesis", - "The introduction introduces the topic and main argument"), - - ("English", "Grammar", "Which word is a noun?", "MCQ", 2, - '{"A": "Run", "B": "Beautiful", "C": "Quickly", "D": "House"}', "D", - "House is a noun - it's a person, place, or thing"), - ] - - for subject, topic, text, qtype, difficulty, options, answer, explanation in samples: - cursor.execute(''' - INSERT INTO questions (subject, topic, question_text, question_type, difficulty, options, answer, explanation) - VALUES (?, ?, ?, ?, ?, ?, ?, ?) - ''', (subject, topic, text, qtype, difficulty, options, answer, explanation)) - -# ==================== SEA TUTOR CORE ==================== -class SEATutor: +# ==================== AI TUTOR ENGINE ==================== +class SEAITutor: def __init__(self): init_databases() self.subjects = { - "Math": {"icon": "📐", "color": "#4A90E2", "topics": ["Fractions", "Geometry", "Word Problems"]}, - "English": {"icon": "📚", "color": "#FF6B6B", "topics": ["Grammar", "Vocabulary", "Comprehension"]} + "Mathematics": { + "icon": "📐", + "color": "#4A90E2", + "topics": ["Fractions", "Decimals", "Percentages", "Geometry", "Algebra", "Word Problems"], + "ai_prompts": { + "Fractions": "Generate a SEA-level fraction question for a 5th grader. Include the question and answer.", + "Geometry": "Create a geometry question about area or perimeter suitable for SEA exam.", + "Word Problems": "Write a word problem involving basic arithmetic operations." + } + }, + "English Language": { + "icon": "📚", + "color": "#FF6B6B", + "topics": ["Grammar", "Vocabulary", "Comprehension", "Writing", "Spelling"], + "ai_prompts": { + "Grammar": "Generate a grammar correction question for SEA English exam.", + "Vocabulary": "Create a vocabulary question about synonyms or antonyms.", + "Comprehension": "Write a short reading comprehension passage with one question." + } + } } self.difficulty_levels = { - 1: {"label": "Beginner", "color": "#4CAF50"}, - 2: {"label": "Easy", "color": "#8BC34A"}, - 3: {"label": "Medium", "color": "#FFC107"}, - 4: {"label": "Hard", "color": "#FF9800"}, - 5: {"label": "Expert", "color": "#F44336"} + 1: {"label": "Beginner", "description": "Basic concepts, one-step problems"}, + 2: {"label": "Easy", "description": "Direct application of concepts"}, + 3: {"label": "Medium", "description": "Multi-step problems, moderate complexity"}, + 4: {"label": "Hard", "description": "Complex problems, critical thinking"}, + 5: {"label": "Expert", "description": "Advanced problems, real-world application"} } - - def get_questions(self, subject=None, topic=None, limit=10) -> List[dict]: - """Get questions with filters""" + + # Load sample questions if needed + self.ensure_question_bank() + + def ensure_question_bank(self): + """Make sure we have questions in the database""" conn = get_db_connection("questions") cursor = conn.cursor() + cursor.execute("SELECT COUNT(*) FROM questions") + if cursor.fetchone()[0] < 10: + self.generate_initial_questions() + + def generate_initial_questions(self): + """Generate initial AI questions for the database""" + print("Generating initial AI questions...") - query = "SELECT * FROM questions WHERE 1=1" - params = [] + initial_questions = [ + # Math Questions + ("Mathematics", "Fractions", "Simplify the fraction 15/25 to its lowest terms.", "Short Answer", 2, + None, "3/5", "Divide both numerator and denominator by 5: 15÷5=3, 25÷5=5"), + + ("Mathematics", "Geometry", "Calculate the area of a rectangle with length 12cm and width 8cm.", "Short Answer", 2, + None, "96 cm²", "Area = length × width = 12 × 8 = 96 cm²"), + + ("Mathematics", "Word Problems", "Sarah has 45 marbles. She gives 1/5 of them to John. How many marbles does she have left?", "Short Answer", 3, + None, "36", "1/5 of 45 is 9. 45 - 9 = 36 marbles"), + + # English Questions + ("English Language", "Grammar", "Correct the sentence: 'She don't like apples.'", "Short Answer", 2, + None, "She doesn't like apples.", "The subject 'She' requires 'doesn't' not 'don't'"), + + ("English Language", "Vocabulary", "What is the antonym of 'brave'?", "MCQ", 1, + json.dumps({"A": "Courageous", "B": "Fearful", "C": "Bold", "D": "Heroic"}), "B", "Brave means courageous, the opposite is fearful"), + + ("English Language", "Comprehension", "What should you do first when reading a comprehension passage?", "Short Answer", 2, + None, "Read the questions first", "Reading questions first helps you know what to look for in the passage"), + ] - if subject: - query += " AND subject = ?" - params.append(subject) - if topic and topic != "All": - query += " AND topic = ?" - params.append(topic) + conn = get_db_connection("questions") + cursor = conn.cursor() - query += " ORDER BY RANDOM() LIMIT ?" - params.append(limit) + for subject, topic, question, qtype, difficulty, options, answer, explanation in initial_questions: + cursor.execute(''' + INSERT INTO questions (subject, topic, question_text, question_type, difficulty, options, correct_answer, explanation) + VALUES (?, ?, ?, ?, ?, ?, ?, ?) + ''', (subject, topic, question, qtype, difficulty, options, answer, explanation)) - cursor.execute(query, params) - rows = cursor.fetchall() + conn.commit() + print(f"Added {len(initial_questions)} initial questions") + + def generate_ai_question(self, subject: str, topic: str, difficulty: int = 3) -> Dict: + """Generate a question using AI""" + try: + # Try to use Hugging Face API if token available + if AIConfig.HF_TOKEN: + return self._generate_with_hf_api(subject, topic, difficulty) + else: + return self._generate_with_template(subject, topic, difficulty) + except Exception as e: + print(f"AI generation failed: {e}") + return self._generate_with_template(subject, topic, difficulty) + + def _generate_with_template(self, subject: str, topic: str, difficulty: int) -> Dict: + """Generate question using templates (fallback)""" + templates = { + "Mathematics": { + "Fractions": [ + "What is {numerator}/{denominator} simplified?", + "Add {a}/{b} + {c}/{d}", + "Multiply {a}/{b} × {c}/{d}", + "What fraction of the shape is shaded? (Image description)" + ], + "Geometry": [ + "Find the area of a {shape} with {dimension1} = {value1} and {dimension2} = {value2}", + "Calculate the perimeter of a {shape} with side length {side}", + "What is the volume of a {shape} with dimensions {dim1} × {dim2} × {dim3}?" + ], + "Word Problems": [ + "If you have {items1} and buy {items2} more, how many do you have?", + "{name} has ${money}. She spends ${spent}. How much is left?", + "A pizza is cut into {slices} slices. {eaten} slices are eaten. What fraction remains?" + ] + }, + "English Language": { + "Grammar": [ + "Correct this sentence: '{sentence}'", + "Choose the correct verb: '{sentence}' (a) {opt1} (b) {opt2} (c) {opt3}", + "Identify the noun in: '{sentence}'" + ], + "Vocabulary": [ + "What is the synonym of '{word}'?", + "What is the antonym of '{word}'?", + "Use '{word}' in a sentence." + ], + "Comprehension": [ + "Read this passage: '{passage}'\nQuestion: {question}", + "What is the main idea of this text: '{text}'?", + "What can you infer from this sentence: '{sentence}'?" + ] + } + } + + # Generate random values + if subject == "Mathematics": + if topic == "Fractions": + numerator = random.randint(1, 12) + denominator = random.randint(2, 12) + question = f"Simplify {numerator}/{denominator} to lowest terms." + answer = self._simplify_fraction(numerator, denominator) + explanation = f"Divide numerator and denominator by their GCD: {numerator} ÷ {answer[0]} = {answer[2]}, {denominator} ÷ {answer[0]} = {answer[3]}" + + elif topic == "Geometry": + shape = random.choice(["rectangle", "square", "triangle"]) + if shape == "rectangle": + l = random.randint(5, 20) + w = random.randint(5, 20) + question = f"Find the area of a rectangle with length {l}cm and width {w}cm." + answer = f"{l * w} cm²" + explanation = f"Area = length × width = {l} × {w} = {l * w} cm²" + else: + side = random.randint(5, 15) + question = f"Find the perimeter of a square with side {side}cm." + answer = f"{side * 4} cm" + explanation = f"Perimeter = 4 × side = 4 × {side} = {side * 4} cm" + + else: # Word Problems + items1 = random.randint(10, 50) + items2 = random.randint(5, 20) + question = f"John has {items1} marbles. He buys {items2} more. How many marbles does he have now?" + answer = str(items1 + items2) + explanation = f"Total = {items1} + {items2} = {items1 + items2}" - questions = [] - for row in rows: + else: # English Language + if topic == "Grammar": + sentences = [ + "He go to school every day.", + "They was happy to see us.", + "She don't like vegetables." + ] + sentence = random.choice(sentences) + question = f"Correct this sentence: '{sentence}'" + + corrections = { + "He go to school every day.": "He goes to school every day.", + "They was happy to see us.": "They were happy to see us.", + "She don't like vegetables.": "She doesn't like vegetables." + } + answer = corrections[sentence] + explanation = f"The verb must agree with the subject. {explanation}" + + elif topic == "Vocabulary": + words = { + "happy": ["joyful", "sad"], + "big": ["large", "small"], + "fast": ["quick", "slow"] + } + word, synonyms = random.choice(list(words.items())) + question = f"What is a synonym for '{word}'?" + answer = synonyms[0] + explanation = f"'{word}' means feeling or showing pleasure. A synonym is '{synonyms[0]}'." + + else: # Comprehension + passages = [ + "The sun rises in the east and sets in the west. This happens every day.", + "Plants need water, sunlight, and air to grow. Without these, they cannot survive.", + "Reading books helps improve vocabulary and knowledge. It is a good habit." + ] + passage = random.choice(passages) + question = f"Read this passage: '{passage}'\nWhat is the main idea?" + answers = { + "The sun rises in the east and sets in the west. This happens every day.": "The sun's daily pattern", + "Plants need water, sunlight, and air to grow. Without these, they cannot survive.": "What plants need to grow", + "Reading books helps improve vocabulary and knowledge. It is a good habit.": "Benefits of reading" + } + answer = answers[passage] + explanation = "The main idea summarizes what the passage is mostly about." + + return { + "question": question, + "answer": answer, + "explanation": explanation, + "difficulty": difficulty, + "options": None + } + + def _simplify_fraction(self, numerator, denominator): + """Simplify a fraction""" + import math + gcd = math.gcd(numerator, denominator) + return (gcd, f"{numerator//gcd}/{denominator//gcd}", numerator//gcd, denominator//gcd) + + def evaluate_answer(self, question_text: str, student_answer: str, correct_answer: str) -> Dict: + """Evaluate student's answer using AI""" + try: + # Simple evaluation first + if student_answer.strip().lower() == correct_answer.strip().lower(): + return { + "correct": True, + "confidence": 1.0, + "feedback": "✅ Perfect! Your answer is correct.", + "explanation": "" + } + + # For math answers, try to evaluate numerically + if self._is_math_question(question_text): + return self._evaluate_math_answer(student_answer, correct_answer, question_text) + + # For English answers, use text similarity + return self._evaluate_text_answer(student_answer, correct_answer, question_text) + + except Exception as e: + print(f"Evaluation error: {e}") + # Fallback to simple check + is_correct = student_answer.strip().lower() == correct_answer.strip().lower() + return { + "correct": is_correct, + "confidence": 1.0 if is_correct else 0.0, + "feedback": "✅ Correct!" if is_correct else f"❌ The correct answer is: {correct_answer}", + "explanation": "" + } + + def _is_math_question(self, question: str) -> bool: + """Check if question is math-related""" + math_keywords = ["calculate", "solve", "find", "simplify", "area", "perimeter", "fraction", "add", "subtract"] + return any(keyword in question.lower() for keyword in math_keywords) + + def _evaluate_math_answer(self, student_answer: str, correct_answer: str, question: str) -> Dict: + """Evaluate math answer with tolerance""" + try: + # Clean answers + student_clean = student_answer.strip().lower().replace("cm²", "").replace("cm", "").strip() + correct_clean = correct_answer.strip().lower().replace("cm²", "").replace("cm", "").strip() + + # Try to parse as numbers try: - options = json.loads(row[6]) if row[6] else None - except: - options = None + student_num = float(student_clean) + correct_num = float(correct_clean) - questions.append({ - "id": row[0], - "subject": row[1], - "topic": row[2], - "text": row[3], - "type": row[4], - "difficulty": row[5], - "options": options, - "answer": row[7], - "explanation": row[8], - "difficulty_label": self.difficulty_levels.get(row[5], {}).get("label", "Medium") - }) + # Check if within 5% tolerance + if abs(student_num - correct_num) <= 0.05 * abs(correct_num): + return { + "correct": True, + "confidence": 0.9, + "feedback": "✅ Good job! Your answer is correct.", + "explanation": "" + } + except: + pass + + # Check for fraction equivalence + if "/" in student_clean and "/" in correct_clean: + s_parts = student_clean.split("/") + c_parts = correct_clean.split("/") + if len(s_parts) == 2 and len(c_parts) == 2: + try: + s_num, s_den = int(s_parts[0]), int(s_parts[1]) + c_num, c_den = int(c_parts[0]), int(c_parts[1]) + if s_num * c_den == s_den * c_num: + return { + "correct": True, + "confidence": 0.8, + "feedback": "✅ Your fraction is equivalent to the correct answer!", + "explanation": f"{student_answer} = {correct_answer}" + } + except: + pass + + return { + "correct": False, + "confidence": 0.0, + "feedback": f"❌ Not quite. The correct answer is {correct_answer}", + "explanation": "Let me explain how to solve this..." + } + + except Exception as e: + print(f"Math evaluation error: {e}") + return { + "correct": False, + "confidence": 0.0, + "feedback": f"❌ The correct answer is: {correct_answer}", + "explanation": "" + } + + def _evaluate_text_answer(self, student_answer: str, correct_answer: str, question: str) -> Dict: + """Evaluate text answer with keyword matching""" + student_lower = student_answer.strip().lower() + correct_lower = correct_answer.strip().lower() - return questions - - def record_answer(self, student_id, question_id, correct, topic): - """Record student answer""" - conn = get_db_connection("students") - cursor = conn.cursor() + # Check for exact match (case-insensitive) + if student_lower == correct_lower: + return { + "correct": True, + "confidence": 1.0, + "feedback": "✅ Excellent! Perfect answer.", + "explanation": "" + } - cursor.execute(''' - INSERT INTO student_progress (student_id, question_id, correct, topic) - VALUES (?, ?, ?, ?) - ''', (student_id, question_id, correct, topic)) + # Check for keyword matches + correct_keywords = set(correct_lower.split()) + student_keywords = set(student_lower.split()) + common_keywords = correct_keywords.intersection(student_keywords) - # Update stats - cursor.execute(''' - INSERT OR REPLACE INTO student_stats - (student_id, total_questions, correct_answers) - VALUES (?, - COALESCE((SELECT total_questions FROM student_stats WHERE student_id = ?), 0) + 1, - COALESCE((SELECT correct_answers FROM student_stats WHERE student_id = ?), 0) + ? - ) - ''', (student_id, student_id, student_id, 1 if correct else 0)) + if len(common_keywords) >= len(correct_keywords) * 0.7: # 70% keyword match + return { + "correct": True, + "confidence": 0.7, + "feedback": "✅ Good! Your answer is mostly correct.", + "explanation": f"The exact answer is: {correct_answer}" + } - # Update streak - if correct: - cursor.execute(''' - UPDATE student_stats - SET current_streak = COALESCE(current_streak, 0) + 1, - best_streak = MAX(best_streak, COALESCE(current_streak, 0) + 1) - WHERE student_id = ? - ''', (student_id,)) + return { + "correct": False, + "confidence": 0.0, + "feedback": f"❌ Not quite right. The correct answer is: {correct_answer}", + "explanation": "Let me break this down for you..." + } + + def generate_personalized_feedback(self, student_answer: str, correct_answer: str, + question: str, topic: str, was_correct: bool) -> str: + """Generate personalized feedback based on answer""" + + if was_correct: + feedback_templates = [ + "🎉 Excellent work! You really understand {topic}.", + "✅ Perfect! You've mastered this {topic} concept.", + "🌟 Great job! Your answer shows clear understanding of {topic}.", + "👍 Well done! You applied the {topic} concept correctly." + ] + + if "fraction" in topic.lower(): + feedback_templates.append("📐 Excellent fraction work! You simplified correctly.") + elif "geometry" in topic.lower(): + feedback_templates.append("📏 Perfect geometry calculation!") + elif "grammar" in topic.lower(): + feedback_templates.append("📚 Great grammar correction!") + + feedback = random.choice(feedback_templates).format(topic=topic.lower()) + + # Add encouragement + encouragements = [ + " Keep up the great work!", + " You're making excellent progress!", + " Your hard work is paying off!", + " You're becoming a {topic} expert!" + ] + feedback += random.choice(encouragements).format(topic=topic.lower()) + else: - cursor.execute(''' - UPDATE student_stats SET current_streak = 0 WHERE student_id = ? - ''', (student_id,)) + feedback_templates = [ + "Let's review {topic} together.", + "I notice you're working on {topic}. Let me help.", + "This {topic} concept can be tricky. Here's how to think about it:", + "Good attempt! Here's the correct approach for {topic}:" + ] + + feedback = random.choice(feedback_templates).format(topic=topic.lower()) + + # Add specific hints based on topic + if "fraction" in topic.lower(): + feedback += "\n\n💡 **Hint**: Remember to find the greatest common divisor (GCD) to simplify fractions." + elif "area" in question.lower(): + feedback += f"\n\n💡 **Hint**: Area = length × width. Check your multiplication." + elif "perimeter" in question.lower(): + feedback += "\n\n💡 **Hint**: Perimeter is the sum of all sides." + elif "grammar" in topic.lower(): + feedback += "\n\n💡 **Hint**: Check subject-verb agreement. Remember: he/she/it + verb+s" + + feedback += f"\n\n📝 **Correct answer**: {correct_answer}" - conn.commit() - return True - - def get_student_stats(self, student_id="student_001") -> dict: - """Get student statistics""" + return feedback + + def get_next_question_recommendation(self, student_id: str = "student_001") -> Dict: + """Recommend next question based on student performance""" conn = get_db_connection("students") cursor = conn.cursor() + # Get weak topics cursor.execute(''' - SELECT total_questions, correct_answers, current_streak, best_streak - FROM student_stats WHERE student_id = ? + SELECT topic, + COUNT(*) as total, + SUM(CASE WHEN correct = 1 THEN 1 ELSE 0 END) as correct + FROM student_progress + WHERE student_id = ? + GROUP BY topic + ORDER BY correct * 1.0 / total ASC ''', (student_id,)) - row = cursor.fetchone() - if not row: + weak_topics = [] + for topic, total, correct in cursor.fetchall(): + if total >= 3: # Only consider topics with enough attempts + accuracy = (correct / total * 100) if total > 0 else 0 + if accuracy < 70: + weak_topics.append({"topic": topic, "accuracy": accuracy}) + + if weak_topics: + # Recommend practice on weakest topic + weakest = min(weak_topics, key=lambda x: x["accuracy"]) + subject = "Mathematics" if any(math_word in weakest["topic"].lower() + for math_word in ["frac", "geo", "algebra", "calc"]) else "English Language" + + # Generate a question on this topic + question_data = self.generate_ai_question(subject, weakest["topic"], difficulty=2) + + return { + "type": "remediation", + "subject": subject, + "topic": weakest["topic"], + "reason": f"Practice {weakest['topic']} (accuracy: {weakest['accuracy']:.0f}%)", + "question": question_data + } + else: + # Recommend challenging question + subjects = list(self.subjects.keys()) + subject = random.choice(subjects) + topic = random.choice(self.subjects[subject]["topics"]) + + question_data = self.generate_ai_question(subject, topic, difficulty=4) + return { - "total_questions": 0, - "correct_answers": 0, - "accuracy": 0, - "current_streak": 0, - "best_streak": 0 + "type": "challenge", + "subject": subject, + "topic": topic, + "reason": "Try this challenging question to advance your skills", + "question": question_data } + +# ==================== GRADIO UI ==================== +def create_ai_tutor_app(): + """Create the AI-powered tutor interface""" + + # Initialize AI tutor + ai_tutor = SEAITutor() + + with gr.Blocks( + title="SEA Prep AI Tutor 🤖 | Personalized Learning", + theme=gr.themes.Soft(primary_hue="blue", secondary_hue="purple"), + css=""" + .gradio-container { + max-width: 1400px; + margin: 0 auto; + } - total, correct, streak, best_streak = row - accuracy = (correct / total * 100) if total > 0 else 0 + .ai-header { + background: linear-gradient(135deg, #667eea 0%, #764ba2 100%); + padding: 2.5rem; + border-radius: 15px; + color: white; + margin-bottom: 2rem; + box-shadow: 0 10px 30px rgba(0, 0, 0, 0.2); + } - return { - "total_questions": total, - "correct_answers": correct, - "accuracy": round(accuracy, 1), - "current_streak": streak, - "best_streak": best_streak + .ai-card { + background: white; + padding: 2rem; + border-radius: 12px; + box-shadow: 0 5px 20px rgba(0, 0, 0, 0.1); + border: 1px solid #e5e7eb; + transition: transform 0.3s ease; } - - def generate_test(self, subject: str, topic: str = "All", num_questions: int = 10) -> dict: - """Generate a practice test""" - questions = self.get_questions( - subject=subject, - topic=topic, - limit=num_questions - ) - return { - "subject": subject, - "topic": topic, - "total_questions": len(questions), - "questions": questions, - "test_id": f"test_{datetime.now().strftime('%Y%m%d_%H%M%S')}" + .ai-card:hover { + transform: translateY(-5px); } - - def grade_answer(self, question_id: int, student_answer: str) -> Tuple[bool, str]: - """Grade a single answer""" - conn = get_db_connection("questions") - cursor = conn.cursor() - cursor.execute("SELECT answer, explanation FROM questions WHERE id = ?", (question_id,)) - result = cursor.fetchone() + .ai-message { + background: linear-gradient(135deg, #f3e8ff 0%, #e0e7ff 100%); + padding: 1.5rem; + border-radius: 12px; + border-left: 5px solid #8b5cf6; + margin: 1rem 0; + } - if not result: - return False, "Question not found in database" + .user-message { + background: linear-gradient(135deg, #dbeafe 0%, #e0f2fe 100%); + padding: 1.5rem; + border-radius: 12px; + border-left: 5px solid #3b82f6; + margin: 1rem 0; + } - correct_answer, explanation = result + .question-card { + background: #f8fafc; + padding: 2rem; + border-radius: 12px; + border: 2px solid #e2e8f0; + margin: 1.5rem 0; + } - if not correct_answer: - return False, "No answer key available" + .feedback-correct { + background: #d1fae5; + color: #065f46; + padding: 1rem; + border-radius: 8px; + border: 2px solid #10b981; + } - # Clean and compare answers - correct_clean = str(correct_answer).strip().lower() - student_clean = str(student_answer).strip().lower() + .feedback-incorrect { + background: #fee2e2; + color: #991b1b; + padding: 1rem; + border-radius: 8px; + border: 2px solid #ef4444; + } - # For MCQ, check if answer matches option letter - if len(student_clean) == 1 and student_clean in ['a', 'b', 'c', 'd']: - # It's an option letter - is_correct = student_clean == correct_clean.lower() - else: - # It's a text answer - is_correct = student_clean == correct_clean + .ai-avatar { + width: 60px; + height: 60px; + background: linear-gradient(135deg, #667eea, #764ba2); + border-radius: 50%; + display: flex; + align-items: center; + justify-content: center; + font-size: 24px; + color: white; + margin-right: 1rem; + } - feedback = f"✅ Correct! {explanation}" if is_correct else f"❌ Incorrect. Correct answer: {correct_answer}" + .progress-ring { + width: 120px; + height: 120px; + } - return is_correct, feedback - -# Initialize tutor -tutor = SEATutor() - -# ==================== GRADIO UI ==================== -def create_app(): - with gr.Blocks(title="SEA Prep Pro | AI Learning Platform", theme=gr.themes.Soft()) as app: + .topic-tag { + display: inline-block; + padding: 0.5rem 1rem; + background: #e0e7ff; + color: #4f46e5; + border-radius: 20px; + margin: 0.25rem; + font-weight: 500; + } + + .difficulty-badge { + display: inline-block; + padding: 0.25rem 0.75rem; + border-radius: 12px; + font-size: 0.875rem; + font-weight: 600; + margin-left: 0.5rem; + } + + .btn-ai { + background: linear-gradient(135deg, #667eea, #764ba2); + color: white; + border: none; + padding: 1rem 2rem; + border-radius: 10px; + font-weight: 600; + cursor: pointer; + transition: all 0.3s ease; + } + + .btn-ai:hover { + transform: translateY(-2px); + box-shadow: 0 10px 25px rgba(102, 126, 234, 0.4); + } + """ + ) as app: # State variables - current_test = gr.State(None) - current_question_index = gr.State(0) - user_answers = gr.State({}) - test_score = gr.State({"correct": 0, "total": 0}) + current_question = gr.State(None) + current_answer = gr.State(None) + chat_history = gr.State([]) - # Header + # =============== HEADER =============== gr.HTML(""" -
- AI-Powered Secondary Entrance Assessment Preparation -
++ Your Personal AI-Powered Learning Assistant +
++ 🤖 AI-Generated Questions • 📊 Adaptive Learning • 🎯 Personalized Feedback +
+Configure your test and click "Generate Test"
-Please try different filters or upload some papers first.
+ # Initial Placeholder + practice_placeholder = gr.HTML(""" ++ Get personalized questions generated by AI based on your needs and performance. +
+Subject: {test_state['subject']}
-Topic: {test_state['topic']}
-Total Questions: {total}
-Score: {correct}/{total}
-Percentage: {percentage:.1f}%
+No topic data available yet. Start practicing!
'} -Focus Areas:
-Daily Goal: 30 questions
- -🇹🇹 SEA Prep Pro • AI-Powered Learning Platform
-© 2024 Trinidad & Tobago Education Initiative
++ 🇹🇹 SEA Prep AI Tutor • Revolutionizing SEA Preparation with Artificial Intelligence +
++ Questions generated by AI • Answers evaluated with machine learning • Feedback personalized for you +