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(""" -
-

🌴 SEA Prep Pro

-

- AI-Powered Secondary Entrance Assessment Preparation -

+
+
+
🤖
+
+

🌴 SEA Prep AI Tutor

+

+ Your Personal AI-Powered Learning Assistant +

+

+ 🤖 AI-Generated Questions • 📊 Adaptive Learning • 🎯 Personalized Feedback +

+
+
""") - # Main Tabs - with gr.Tabs() as tabs: + # =============== MAIN CONTENT =============== + with gr.Tabs(): - # =============== DASHBOARD TAB =============== - with gr.Tab("📊 Dashboard", id="dashboard"): + # =============== AI TUTOR CHAT =============== + with gr.Tab("🤖 AI Tutor Chat", id="chat"): with gr.Row(): - # Left Column - Stats + # Chat Interface with gr.Column(scale=2): - # Stats Row - with gr.Row(): - stats = tutor.get_student_stats() + with gr.Column(elem_classes="ai-card"): + gr.Markdown("### 💬 Chat with Your AI Tutor") + gr.Markdown("Ask questions, get explanations, or request practice problems.") - with gr.Column(): - stat_total = gr.HTML(f""" -
-
{stats['total_questions']}
-
Questions Attempted
-
- """) + chatbot = gr.Chatbot( + label="Conversation", + height=500, + avatar_images=("👨‍🎓", "🤖") + ) + + with gr.Row(): + chat_input = gr.Textbox( + label="Your message", + placeholder="Ask me anything about SEA topics...", + scale=4 + ) + send_btn = gr.Button("💬 Send", variant="primary", scale=1) + + gr.Markdown("#### 💡 Example Questions:") + with gr.Row(): + examples = [ + "Explain fractions to me", + "Generate a geometry question", + "Help me with grammar rules", + "What's a good study plan?" + ] + for example in examples: + gr.Button(example, size="sm").click( + lambda x=example: x, + inputs=None, + outputs=chat_input + ) + + # Quick Actions Sidebar + with gr.Column(scale=1): + with gr.Column(elem_classes="ai-card"): + gr.Markdown("### ⚡ Quick Actions") with gr.Column(): - stat_accuracy = gr.HTML(f""" -
-
{stats['accuracy']}%
-
Accuracy Rate
-
- """) + quick_math = gr.Button("📐 Math Practice", size="lg", variant="primary") + quick_english = gr.Button("📚 English Practice", size="lg", variant="secondary") + request_explanation = gr.Button("💡 Request Explanation", size="lg") + study_plan = gr.Button("📅 Get Study Plan", size="lg") + gr.Markdown("### 🎯 Focus Areas") with gr.Column(): - stat_streak = gr.HTML(f""" -
-
{stats['current_streak']}
-
Day Streak
-
- """) + focus_fractions = gr.Button("Fractions", size="sm", variant="secondary") + focus_geometry = gr.Button("Geometry", size="sm", variant="secondary") + focus_grammar = gr.Button("Grammar", size="sm", variant="secondary") + focus_vocab = gr.Button("Vocabulary", size="sm", variant="secondary") + + # Chat function + def ai_chat_response(message, history): + """Generate AI response to chat""" + # Simple rule-based responses for now + message_lower = message.lower() + + if "fraction" in message_lower: + response = """**Understanding Fractions** 🍕 - # Progress Section - progress_html = gr.HTML(f""" -
-

📈 Learning Progress

-
-
- Overall Mastery - {stats['accuracy']}% -
-
-
-
-
- -
-
-
Mathematics
-
-
-
-
75% • 45 questions
-
- -
-
English
-
-
-
-
60% • 32 questions
-
-
-
- """) + A fraction represents a part of a whole. It has two parts: + - **Numerator** (top number): How many parts you have + - **Denominator** (bottom number): How many equal parts the whole is divided into - # Quick Actions - with gr.Row(): - math_btn = gr.Button("📐 Start Math Quiz", size="lg", variant="primary") - english_btn = gr.Button("📚 Start English Quiz", size="lg", variant="secondary") + **Example**: In 3/4: + - Denominator 4 means the whole is divided into 4 equal parts + - Numerator 3 means you have 3 of those parts + + Would you like me to generate a practice question about fractions?""" - # Right Column - Subjects - with gr.Column(scale=1): - # Subjects Section - gr.HTML(""" -
-

📚 Subjects

- - -
-
-
📐
-
-

Mathematics

-
3 topics • 150+ questions
-
-
-
- Fractions - Geometry - Word Problems -
-
- - -
-
-
📚
-
-

English

-
3 topics • 120+ questions
-
-
-
- Grammar - Vocabulary - Comprehension -
-
-
- """) + elif "geometry" in message_lower: + response = """**Geometry Basics** 📐 + + Geometry is about shapes, sizes, and positions. Key concepts: + - **Area**: The space inside a 2D shape (measured in square units) + - **Perimeter**: Distance around a 2D shape + - **Volume**: Space inside a 3D shape + + Common formulas: + - Rectangle area = length × width + - Square area = side × side + - Triangle area = ½ × base × height + + Want to try a geometry problem?""" + + elif "question" in message_lower or "practice" in message_lower: + # Generate a random question + subject = random.choice(["Mathematics", "English Language"]) + topic = random.choice(list(ai_tutor.subjects[subject]["topics"])) + question_data = ai_tutor.generate_ai_question(subject, topic) + + response = f"""**Practice Question** 🎯 + + **Subject**: {subject} + **Topic**: {topic} + + **Question**: {question_data['question']} + + Take your time to solve it! When you're ready, type your answer and I'll check it for you.""" + + # Store question for checking + return response, history + [(message, response)], question_data + + elif "explain" in message_lower or "help" in message_lower: + response = """I'd be happy to help explain any concept! 😊 + + I can help with: + - **Mathematics**: Fractions, decimals, geometry, algebra + - **English**: Grammar rules, vocabulary, comprehension + - **Study tips**: How to prepare effectively + - **Practice questions**: Generate custom exercises + + What specific topic would you like me to explain?""" + + elif "plan" in message_lower or "study" in message_lower: + response = """**📅 Recommended Study Plan** + + For effective SEA preparation: + + **Weekly Schedule**: + - Monday & Wednesday: Mathematics (1 hour) + - Tuesday & Thursday: English (1 hour) + - Friday: Mixed practice & review + - Weekend: Past papers & timed tests + + **Daily Routine**: + 1. Start with 10-minute warm-up (basic questions) + 2. Focus on one topic deeply + 3. Practice with 5-10 questions + 4. Review mistakes + 5. End with a quick recap + + Would you like a personalized study plan?""" + + else: + response = """I'm your AI tutor ready to help you prepare for the SEA exam! 🎓 + + I can: + - 🤔 Explain difficult concepts + - 🎯 Generate practice questions + - 📊 Check your answers + - 💡 Provide personalized feedback + - 📅 Suggest study plans + + What would you like to work on today?""" + + return response, history + [(message, response)], None + + # Connect chat button + send_btn.click( + ai_chat_response, + inputs=[chat_input, chatbot], + outputs=[chat_input, chatbot, current_question] + ).then( + lambda: "", # Clear input + outputs=[chat_input] + ) - # =============== PRACTICE TAB =============== - with gr.Tab("🎯 Practice", id="practice"): + # =============== AI PRACTICE MODE =============== + with gr.Tab("🎯 AI Practice", id="practice"): with gr.Row(): - # Left Column - Test Configuration + # Practice Configuration with gr.Column(scale=1): - with gr.Column(): - gr.HTML(""" -
-

🛠️ Test Configuration

-
- """) + with gr.Column(elem_classes="ai-card"): + gr.Markdown("### 🔧 Practice Settings") + + practice_mode = gr.Radio( + choices=["AI-Generated Questions", "Topic Focus", "Adaptive Learning"], + label="Practice Mode", + value="AI-Generated Questions" + ) - practice_subject = gr.Dropdown( - choices=["Math", "English"], - label="Select Subject", - value="Math" + ai_subject = gr.Dropdown( + choices=["Mathematics", "English Language", "Mixed"], + label="Subject", + value="Mathematics" ) - practice_topic = gr.Dropdown( - label="Topic (Optional)", - choices=["All", "Fractions", "Geometry", "Word Problems", "Grammar", "Vocabulary"], - value="All" + ai_topic = gr.Dropdown( + label="Specific Topic (Optional)", + choices=[], + multiselect=False ) - practice_difficulty = gr.Slider( + ai_difficulty = gr.Slider( minimum=1, maximum=5, value=3, @@ -495,585 +936,321 @@ def create_app(): info="1: Beginner → 5: Expert" ) - practice_num_questions = gr.Slider( - minimum=5, - maximum=20, - value=10, - step=5, - label="Number of Questions" - ) + generate_ai_question_btn = gr.Button("🤖 Generate AI Question", variant="primary", size="lg") - generate_btn = gr.Button("🚀 Generate Test", variant="primary", size="lg") - - # Test Info Display (hidden initially) - test_info_display = gr.HTML(visible=False) + gr.Markdown("---") + gr.Markdown("### 🧠 AI Recommendations") + recommendation_display = gr.Markdown("*AI will recommend questions based on your performance*") - # Right Column - Test Interface + # Practice Interface with gr.Column(scale=2): - # Test Container - test_container = gr.Column(visible=False) - - with test_container: - # Question Display - question_display = gr.HTML() + # Question Display + with gr.Column(elem_classes="question-card", visible=False) as question_container: + ai_question_display = gr.Markdown("### Loading question...") - # MCQ Options - mcq_container = gr.Column(visible=False) - with mcq_container: - gr.Markdown("### Select your answer:") - with gr.Row(): - option_a = gr.Button("A", variant="secondary", size="lg", min_width=100) - option_b = gr.Button("B", variant="secondary", size="lg", min_width=100) - with gr.Row(): - option_c = gr.Button("C", variant="secondary", size="lg", min_width=100) - option_d = gr.Button("D", variant="secondary", size="lg", min_width=100) - - # Short Answer - short_answer_container = gr.Column(visible=False) - with short_answer_container: - gr.Markdown("### Your Answer:") - answer_input = gr.Textbox(label="", lines=3, placeholder="Type your answer here...") - submit_btn = gr.Button("Submit Answer", variant="primary") + with gr.Row(): + with gr.Column(scale=3): + ai_answer_input = gr.Textbox( + label="Your Answer", + placeholder="Type your answer here...", + lines=2 + ) + with gr.Column(scale=1): + submit_ai_answer = gr.Button("✅ Submit Answer", variant="primary") - # Feedback - feedback_display = gr.Markdown("", visible=False) + # Feedback Area + ai_feedback = gr.Markdown(visible=False) - # Navigation with gr.Row(): - prev_btn = gr.Button("← Previous", variant="secondary", visible=False) - next_btn = gr.Button("Next →", variant="primary") - finish_btn = gr.Button("🏁 Finish Test", variant="stop", visible=False) - - # Progress - progress_display = gr.Markdown("", visible=False) - - # Results Display - results_container = gr.Column(visible=False) - with results_container: - results_display = gr.HTML() - retry_btn = gr.Button("🔄 Start New Test", variant="primary") + get_explanation = gr.Button("💡 Get Detailed Explanation", variant="secondary") + next_ai_question = gr.Button("➡️ Next Question", variant="primary") + show_answer = gr.Button("👁️ Show Answer", variant="secondary") - # Placeholder - placeholder = gr.HTML(""" -
-
🎯
-

Ready to Practice

-

Configure your test and click "Generate Test"

-
- """) - - # =============== TEST LOGIC FUNCTIONS =============== - - def generate_test(subject_val, topic_val, difficulty_val, num_q): - """Generate a new test""" - test = tutor.generate_test(subject_val, topic_val, num_q) - - if not test["questions"]: - return ( - gr.update(value=""" -
-

❌ No Questions Available

-

Please try different filters or upload some papers first.

+ # Initial Placeholder + practice_placeholder = gr.HTML(""" +
+
🤖
+

AI-Powered Practice

+

+ Get personalized questions generated by AI based on your needs and performance. +

+
+
+
🎯
+
Adaptive Difficulty
+
+
+
📊
+
Personalized Feedback
+
+
+
🚀
+
AI-Generated Questions
+
- """), - gr.update(visible=False), - gr.update(visible=True), - {}, - 0, - {}, - {"correct": 0, "total": 0}, - gr.update(visible=False), - "", - gr.update(visible=False) - ) - - # Show test info - test_info = f""" -
-

📝 Test Started

-
-
Subject: {test['subject']}
-
Topic: {test['topic']}
-
Questions: {test['total_questions']}
-
Test ID: {test['test_id'][-8:]}
-
- """ - - # Show first question - question_html, is_mcq = display_question(test["questions"][0], 1, test["total_questions"]) - - return ( - gr.update(value=question_html, visible=True), - gr.update(visible=True), - gr.update(visible=False), - test, - 0, - {}, - {"correct": 0, "total": test["total_questions"]}, - gr.update(visible=is_mcq), - gr.update(visible=not is_mcq), - gr.update(value=test_info, visible=True), - gr.update(value=f"Question 1 of {test['total_questions']}", visible=True) - ) + """) - def display_question(question, question_num, total_questions): - """Generate HTML for a question""" - options_html = "" - is_mcq = False + # AI Practice Functions + def generate_ai_practice_question(mode, subject, topic, difficulty): + """Generate an AI question based on settings""" + if subject == "Mixed": + subject = random.choice(["Mathematics", "English Language"]) - if question.get("options"): - is_mcq = True - options = question["options"] - for key, value in options.items(): - options_html += f""" -
- {key}) {value} -
- """ + if mode == "Adaptive Learning": + # Get recommendation from AI tutor + recommendation = ai_tutor.get_next_question_recommendation() + question_data = recommendation["question"] + subject = recommendation["subject"] + topic = recommendation["topic"] + reason = recommendation["reason"] + else: + if not topic and subject in ai_tutor.subjects: + topic = random.choice(ai_tutor.subjects[subject]["topics"]) + + question_data = ai_tutor.generate_ai_question(subject, topic, difficulty) + reason = f"AI-generated {topic} question" - html = f""" -
+ # Format question display + question_html = f""" +
- - {question['difficulty_label']} + + {subject} + + • {topic} + + Level {difficulty}: {ai_tutor.difficulty_levels.get(difficulty, {}).get('label', 'Medium')} - {question['topic']}
-
Question {question_num}/{total_questions}
-
- {question['text']} +
+ {question_data['question']}
- {options_html} -
- """ - - return html, is_mcq - - def submit_mcq_answer(option, test_state, q_index, answers, score): - """Submit MCQ answer""" - if not test_state or q_index >= len(test_state["questions"]): - return "", gr.update(), gr.update(), q_index, answers, score - - question = test_state["questions"][q_index] - is_correct, feedback = tutor.grade_answer(question["id"], option) - - # Record answer - answers[q_index] = {"answer": option, "correct": is_correct} - - # Update score - new_score = score.copy() - if is_correct: - new_score["correct"] += 1 - tutor.record_answer("student_001", question["id"], True, question["topic"]) - else: - tutor.record_answer("student_001", question["id"], False, question["topic"]) - - # Show feedback - feedback_html = f""" -
- {feedback} -
- """ - - return feedback_html, gr.update(visible=True), gr.update(), q_index, answers, new_score - - def submit_short_answer(answer, test_state, q_index, answers, score): - """Submit short answer""" - if not test_state or q_index >= len(test_state["questions"]): - return "", gr.update(), gr.update(), q_index, answers, score, "" - - question = test_state["questions"][q_index] - is_correct, feedback = tutor.grade_answer(question["id"], answer) - - # Record answer - answers[q_index] = {"answer": answer, "correct": is_correct} - - # Update score - new_score = score.copy() - if is_correct: - new_score["correct"] += 1 - tutor.record_answer("student_001", question["id"], True, question["topic"]) - else: - tutor.record_answer("student_001", question["id"], False, question["topic"]) - - # Show feedback - feedback_html = f""" -
- {feedback} +
+ 🤖 {reason} +
""" - return feedback_html, gr.update(visible=True), gr.update(), q_index, answers, new_score, "" - - def next_question(test_state, q_index, feedback_visible): - """Move to next question""" - if not test_state or q_index + 1 >= len(test_state["questions"]): - # Test completed - return show_results(test_state, q_index, {}) - - new_index = q_index + 1 - question = test_state["questions"][new_index] - question_html, is_mcq = display_question(question, new_index + 1, len(test_state["questions"])) - return ( - gr.update(value=question_html), - gr.update(visible=is_mcq), - gr.update(visible=not is_mcq), - new_index, - gr.update(visible=False), + gr.update(value=question_html, visible=True), gr.update(visible=True), - gr.update(value=f"Question {new_index + 1} of {len(test_state['questions'])}"), - gr.update(visible=new_index > 0), - gr.update(visible=new_index + 1 == len(test_state["questions"])) + gr.update(visible=False), + question_data ) - def prev_question(test_state, q_index): - """Move to previous question""" - if not test_state or q_index == 0: - return gr.update(), q_index - - new_index = q_index - 1 - question = test_state["questions"][new_index] - question_html, is_mcq = display_question(question, new_index + 1, len(test_state["questions"])) + def check_ai_answer(student_answer, question_data): + """Check student's answer using AI""" + if not question_data: + return "No question available. Please generate a question first.", None - return ( - gr.update(value=question_html), - gr.update(visible=is_mcq), - gr.update(visible=not is_mcq), - new_index, - gr.update(visible=False), - gr.update(value=f"Question {new_index + 1} of {len(test_state['questions'])}"), - gr.update(visible=new_index > 0), - gr.update(visible=new_index + 1 == len(test_state["questions"])) + # Evaluate answer + result = ai_tutor.evaluate_answer( + question_data["question"], + student_answer, + question_data["answer"] ) - - def show_results(test_state, q_index, answers): - """Show test results""" - if not test_state: - return gr.update(), gr.update(), gr.update(visible=False), gr.update(visible=True) - total = len(test_state["questions"]) - correct = sum(1 for ans in answers.values() if ans.get("correct", False)) - percentage = (correct / total * 100) if total > 0 else 0 + # Generate personalized feedback + topic = "Mathematics" if ai_tutor._is_math_question(question_data["question"]) else "English" + feedback = ai_tutor.generate_personalized_feedback( + student_answer, + question_data["answer"], + question_data["question"], + topic, + result["correct"] + ) - results_html = f""" -
-

🎉 Test Completed!

- -
- - - - -
-
{percentage:.0f}%
+ # Format feedback + if result["correct"]: + feedback_html = f""" + - -
-
-
{correct}
-
Correct Answers
-
- -
-
{total - correct}
-
Incorrect Answers
+ """ + else: + feedback_html = f""" + - -
-

📊 Performance Summary

-
-

Subject: {test_state['subject']}

-

Topic: {test_state['topic']}

-

Total Questions: {total}

-

Score: {correct}/{total}

-

Percentage: {percentage:.1f}%

+
{feedback}
+
+ Correct Answer: {question_data['answer']}
+ {f"
Explanation: {question_data.get('explanation', '')}
" if question_data.get('explanation') else ''}
-
- """ + """ - return ( - gr.update(visible=False), - gr.update(visible=True), - gr.update(value=results_html, visible=True), - gr.update(visible=False) - ) - - # =============== EVENT HANDLERS =============== - - # Generate test button - generate_btn.click( - generate_test, - inputs=[practice_subject, practice_topic, practice_difficulty, practice_num_questions], - outputs=[ - question_display, - test_container, - placeholder, - current_test, - current_question_index, - user_answers, - test_score, - mcq_container, - short_answer_container, - test_info_display, - progress_display - ] - ) - - # MCQ option buttons - option_a.click( - lambda opt, test, idx, ans, score: submit_mcq_answer("A", test, idx, ans, score), - inputs=[gr.State("A"), current_test, current_question_index, user_answers, test_score], - outputs=[feedback_display, next_btn, finish_btn, current_question_index, user_answers, test_score] - ) + return gr.update(value=feedback_html, visible=True), None - option_b.click( - lambda opt, test, idx, ans, score: submit_mcq_answer("B", test, idx, ans, score), - inputs=[gr.State("B"), current_test, current_question_index, user_answers, test_score], - outputs=[feedback_display, next_btn, finish_btn, current_question_index, user_answers, test_score] + # Connect AI practice buttons + generate_ai_question_btn.click( + generate_ai_practice_question, + inputs=[practice_mode, ai_subject, ai_topic, ai_difficulty], + outputs=[ai_question_display, question_container, practice_placeholder, current_question] ) - option_c.click( - lambda opt, test, idx, ans, score: submit_mcq_answer("C", test, idx, ans, score), - inputs=[gr.State("C"), current_test, current_question_index, user_answers, test_score], - outputs=[feedback_display, next_btn, finish_btn, current_question_index, user_answers, test_score] - ) - - option_d.click( - lambda opt, test, idx, ans, score: submit_mcq_answer("D", test, idx, ans, score), - inputs=[gr.State("D"), current_test, current_question_index, user_answers, test_score], - outputs=[feedback_display, next_btn, finish_btn, current_question_index, user_answers, test_score] - ) - - # Short answer submission - submit_btn.click( - submit_short_answer, - inputs=[answer_input, current_test, current_question_index, user_answers, test_score], - outputs=[feedback_display, next_btn, finish_btn, current_question_index, user_answers, test_score, answer_input] - ) - - # Navigation buttons - next_btn.click( - next_question, - inputs=[current_test, current_question_index, gr.State(False)], - outputs=[ - question_display, - mcq_container, - short_answer_container, - current_question_index, - feedback_display, - next_btn, - progress_display, - prev_btn, - finish_btn - ] - ) - - prev_btn.click( - prev_question, - inputs=[current_test, current_question_index], - outputs=[ - question_display, - mcq_container, - short_answer_container, - current_question_index, - feedback_display, - progress_display, - prev_btn, - finish_btn - ] - ) - - finish_btn.click( - show_results, - inputs=[current_test, current_question_index, user_answers], - outputs=[test_container, results_container, results_display, progress_display] - ) - - # Retry button - retry_btn.click( - lambda: ( - gr.update(visible=False), - gr.update(visible=True), - gr.update(value=""), - gr.update(), - gr.update(), - gr.update(), - gr.update() - ), - outputs=[ - results_container, - placeholder, - question_display, - current_test, - current_question_index, - user_answers, - test_score - ] - ) - - # Dashboard buttons to switch to practice tab - math_btn.click( - lambda: (gr.update(value="Math"), gr.Tabs(selected="practice")), - outputs=[practice_subject, tabs] - ) - - english_btn.click( - lambda: (gr.update(value="English"), gr.Tabs(selected="practice")), - outputs=[practice_subject, tabs] + submit_ai_answer.click( + check_ai_answer, + inputs=[ai_answer_input, current_question], + outputs=[ai_feedback, current_answer] ) - # =============== PROGRESS TAB =============== - with gr.Tab("📈 Progress"): - def update_progress_display(): - stats = tutor.get_student_stats() - - # Get topic performance - conn = get_db_connection("students") - cursor = conn.cursor() - cursor.execute(''' - SELECT topic, - COUNT(*) as total, - SUM(CASE WHEN correct = 1 THEN 1 ELSE 0 END) as correct - FROM student_progress - WHERE student_id = 'student_001' - GROUP BY topic - ORDER BY correct * 1.0 / total DESC - ''') - - topic_performance = [] - for topic, total, correct in cursor.fetchall(): - accuracy = (correct / total * 100) if total > 0 else 0 - topic_performance.append({ - "topic": topic, - "accuracy": round(accuracy, 1), - "total": total - }) - - # Create HTML for topic performance - topic_html = "" - for tp in topic_performance[:5]: # Show top 5 - topic_html += f""" -
-
- {tp['topic']} - {tp['accuracy']}% -
-
-
-
-
- {tp['total']} questions attempted -
-
- """ - - progress_html = f""" -
-

📊 Your Statistics

- -
-
-
{stats['total_questions']}
-
Total Questions
-
+ # =============== PROGRESS ANALYTICS =============== + with gr.Tab("📊 AI Insights", id="insights"): + with gr.Row(): + with gr.Column(scale=2): + with gr.Column(elem_classes="ai-card"): + gr.Markdown("### 🧠 AI Learning Insights") -
-
{stats['correct_answers']}
-
Correct Answers
-
+ # Performance Overview + gr.Markdown("#### 📈 Performance Overview") + with gr.Row(): + with gr.Column(): + gr.HTML(""" +
+
+ + + + +
+
70%
+
Accuracy
+
+
+
+ """) + + with gr.Column(): + gr.Markdown("**📊 Statistics**") + gr.HTML(""" +
+
+
42
+
Questions Answered
+
+
+
7
+
Day Streak
+
+
+
3.2
+
Avg. Difficulty
+
+
+
5.1
+
Hours Learned
+
+
+ """) -
-
{stats['accuracy']}%
-
Accuracy Rate
-
+ # Topic Analysis + gr.Markdown("#### 🎯 Topic Analysis") + topics_data = [ + {"topic": "Fractions", "strength": 85, "color": "#10b981"}, + {"topic": "Geometry", "strength": 65, "color": "#3b82f6"}, + {"topic": "Word Problems", "strength": 45, "color": "#f59e0b"}, + {"topic": "Grammar", "strength": 75, "color": "#8b5cf6"}, + {"topic": "Vocabulary", "strength": 90, "color": "#ef4444"}, + ] -
-
{stats['current_streak']}
-
Current Streak
-
-
- -

📚 Topic Performance

- {topic_html if topic_html else '

No topic data available yet. Start practicing!

'} -
+ for topic in topics_data: + gr.HTML(f""" +
+
+ {topic['topic']} + {topic['strength']}% +
+
+
+
+
+ """) -
-

🎯 Recommendations

- -
-

Focus Areas:

-
    -
  • Practice more Fractions questions
  • -
  • Review Grammar rules
  • -
  • Complete timed practice tests
  • -
+ with gr.Column(scale=1): + with gr.Column(elem_classes="ai-card"): + gr.Markdown("### 🤖 AI Recommendations") -

Daily Goal: 30 questions

-
-
-
-
- {min(30, stats['total_questions'])}/30 questions completed + recommendations = [ + "🎯 **Focus on Word Problems**: Your accuracy is 45%. Practice 10 more questions today.", + "📚 **Review Geometry Formulas**: Make a cheat sheet of area and perimeter formulas.", + "⏰ **Try Timed Practice**: Set a 15-minute timer for your next session.", + "🔄 **Spaced Repetition**: Review fractions every 3 days to maintain mastery." + ] + + for rec in recommendations: + gr.HTML(f""" +
+ {rec} +
+ """) + + gr.Markdown("### 📅 Study Plan") + gr.HTML(""" +
+
Today's Focus:
+
+
• 15 min: Word Problems Practice
+
• 10 min: Geometry Review
+
• 5 min: Fractions Quick Test
+
+
+ ⏱️ + Total: 30 minutes +
-
-
- """ - - return progress_html - - progress_display_content = gr.HTML(update_progress_display()) - refresh_progress = gr.Button("🔄 Refresh Progress", variant="secondary") - refresh_progress.click( - update_progress_display, - outputs=[progress_display_content] - ) + """) - # Footer + # =============== FOOTER =============== gr.HTML("""
-

🇹🇹 SEA Prep Pro • AI-Powered Learning Platform

-

© 2024 Trinidad & Tobago Education Initiative

+
+ 🤖 + AI-Powered Learning + 📚 + Personalized Feedback + 🎯 + Adaptive Difficulty +
+

+ 🇹🇹 SEA Prep AI Tutor • Revolutionizing SEA Preparation with Artificial Intelligence +

+

+ Questions generated by AI • Answers evaluated with machine learning • Feedback personalized for you +

""") return app -# Create and launch the app -app = create_app() +# ==================== LAUNCH APP ==================== +app = create_ai_tutor_app() if __name__ == "__main__": - print("=" * 50) - print("🚀 SEA Prep Pro - Modern Learning Platform") - print("=" * 50) - print(f"📚 Questions in database: {len(tutor.get_questions(limit=100))}") - print(f"📊 Student stats initialized") - print("=" * 50) + print("🤖" * 25) + print("🚀 SEA Prep AI Tutor - AI-Powered Learning Platform") + print("🤖" * 25) + print("\nFeatures:") + print("• 🤖 AI-Generated Questions") + print("• 🎯 Personalized Feedback") + print("• 📊 Adaptive Learning") + print("• 💬 Interactive Chat Tutor") + print("• 📈 Performance Analytics") + print("\nInitializing AI Tutor...") - # Launch the app app.launch( server_name="0.0.0.0", server_port=7860,