Spaces:
Configuration error
Configuration error
Update app.py
Browse files
app.py
CHANGED
|
@@ -1,7 +1,6 @@
|
|
| 1 |
"""
|
| 2 |
SEA Prep Pro - AI-Powered Tutor 🇹🇹
|
| 3 |
FULLY FREE VERSION - No API Keys Required!
|
| 4 |
-
Uses local models and open-source libraries
|
| 5 |
"""
|
| 6 |
import gradio as gr
|
| 7 |
import sqlite3
|
|
@@ -9,39 +8,16 @@ import json
|
|
| 9 |
import os
|
| 10 |
import re
|
| 11 |
import random
|
| 12 |
-
import requests
|
| 13 |
-
from datetime import datetime, timedelta
|
| 14 |
-
from typing import List, Dict, Optional, Tuple
|
| 15 |
-
import threading
|
| 16 |
-
import time
|
| 17 |
-
import hashlib
|
| 18 |
import math
|
|
|
|
|
|
|
| 19 |
|
| 20 |
-
# ==================== FREE AI
|
| 21 |
-
# We'll use rule-based AI and local models via Hugging Face's free inference API
|
| 22 |
-
# No API key needed for public models!
|
| 23 |
-
|
| 24 |
class FreeAITutor:
|
| 25 |
-
"""Free AI tutor using rule-based generation
|
| 26 |
|
| 27 |
def __init__(self):
|
| 28 |
-
self.
|
| 29 |
-
"addition": r"(\d+)\s*\+\s*(\d+)",
|
| 30 |
-
"subtraction": r"(\d+)\s*-\s*(\d+)",
|
| 31 |
-
"multiplication": r"(\d+)\s*[x×]\s*(\d+)",
|
| 32 |
-
"division": r"(\d+)\s*÷\s*(\d+)",
|
| 33 |
-
"fraction": r"(\d+)/(\d+)"
|
| 34 |
-
}
|
| 35 |
-
|
| 36 |
-
self.grammar_rules = {
|
| 37 |
-
"is_are": [("he is", "she is", "it is"), ("they are", "we are", "you are")],
|
| 38 |
-
"was_were": [("i was", "he was", "she was"), ("they were", "we were")],
|
| 39 |
-
"has_have": [("he has", "she has"), ("i have", "they have", "we have")]
|
| 40 |
-
}
|
| 41 |
-
|
| 42 |
-
def generate_math_question(self, topic, difficulty):
|
| 43 |
-
"""Generate math questions using templates"""
|
| 44 |
-
templates = {
|
| 45 |
"Fractions": [
|
| 46 |
"Simplify the fraction {num}/{den}.",
|
| 47 |
"Add {a}/{b} + {c}/{d}.",
|
|
@@ -49,42 +25,33 @@ class FreeAITutor:
|
|
| 49 |
"What is {num}/{den} of {whole}?",
|
| 50 |
"Convert {decimal} to a fraction."
|
| 51 |
],
|
| 52 |
-
"Decimals": [
|
| 53 |
-
"Add {a}.{b} + {c}.{d}",
|
| 54 |
-
"Subtract {a}.{b} - {c}.{d}",
|
| 55 |
-
"Multiply {a}.{b} × {c}",
|
| 56 |
-
"Divide {a}.{b} ÷ {c}",
|
| 57 |
-
"Round {num}.{dec} to the nearest {place}."
|
| 58 |
-
],
|
| 59 |
-
"Percentages": [
|
| 60 |
-
"What is {percent}% of {number}?",
|
| 61 |
-
"{number} is what percent of {total}?",
|
| 62 |
-
"Increase {number} by {percent}%.",
|
| 63 |
-
"Decrease {number} by {percent}%.",
|
| 64 |
-
"Find the percentage change from {old} to {new}."
|
| 65 |
-
],
|
| 66 |
"Geometry": [
|
| 67 |
"Find the area of a rectangle with length {l}cm and width {w}cm.",
|
| 68 |
"Calculate the perimeter of a square with side {s}cm.",
|
| 69 |
"Find the area of a triangle with base {b}cm and height {h}cm.",
|
| 70 |
"Calculate the circumference of a circle with radius {r}cm (use π=3.14).",
|
| 71 |
"Find the volume of a cube with side {s}cm."
|
| 72 |
-
],
|
| 73 |
-
"Algebra": [
|
| 74 |
-
"Solve for x: {a}x + {b} = {c}",
|
| 75 |
-
"Simplify: {a}x + {b}y + {c}x - {d}y",
|
| 76 |
-
"Expand: {a}(x + {b})",
|
| 77 |
-
"Factor: {a}x + {b}",
|
| 78 |
-
"Evaluate {a}x² + {b}x + {c} when x = {d}"
|
| 79 |
]
|
| 80 |
}
|
| 81 |
|
| 82 |
-
|
| 83 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 84 |
|
| 85 |
-
#
|
| 86 |
-
|
| 87 |
-
max_val = multipliers.get(difficulty, 50)
|
| 88 |
|
| 89 |
values = {
|
| 90 |
'num': random.randint(1, max_val),
|
|
@@ -95,46 +62,28 @@ class FreeAITutor:
|
|
| 95 |
'd': random.randint(2, max_val),
|
| 96 |
'whole': random.randint(10, 100),
|
| 97 |
'decimal': round(random.uniform(0.1, 0.9), 2),
|
| 98 |
-
'percent': random.randint(1, 100),
|
| 99 |
-
'number': random.randint(1, max_val),
|
| 100 |
-
'total': random.randint(10, 100),
|
| 101 |
-
'old': random.randint(10, 100),
|
| 102 |
-
'new': random.randint(10, 100),
|
| 103 |
'l': random.randint(5, 20),
|
| 104 |
'w': random.randint(3, 15),
|
| 105 |
's': random.randint(4, 12),
|
| 106 |
'r': random.randint(2, 10),
|
| 107 |
'h': random.randint(3, 12),
|
| 108 |
'b': random.randint(4, 16),
|
| 109 |
-
'x': random.randint(1, 10),
|
| 110 |
-
'y': random.randint(1, 10),
|
| 111 |
-
'place': random.choice(["whole number", "tenth", "hundredth"]),
|
| 112 |
'topic': topic
|
| 113 |
}
|
| 114 |
|
| 115 |
question = template.format(**values)
|
| 116 |
-
|
| 117 |
-
# Calculate answer
|
| 118 |
-
answer = self._calculate_answer(question, topic, values)
|
| 119 |
|
| 120 |
return question, answer
|
| 121 |
|
| 122 |
-
def _calculate_answer(self, question,
|
| 123 |
-
"""Calculate answer for math
|
| 124 |
try:
|
| 125 |
if "Simplify the fraction" in question:
|
| 126 |
num, den = values['num'], values['den']
|
| 127 |
gcd = math.gcd(num, den)
|
| 128 |
return f"{num//gcd}/{den//gcd}"
|
| 129 |
|
| 130 |
-
elif "Add" in question and "/" in question:
|
| 131 |
-
# Fraction addition
|
| 132 |
-
a, b, c, d = values['a'], values['b'], values['c'], values['d']
|
| 133 |
-
lcm = b * d // math.gcd(b, d)
|
| 134 |
-
num = a * (lcm // b) + c * (lcm // d)
|
| 135 |
-
gcd = math.gcd(num, lcm)
|
| 136 |
-
return f"{num//gcd}/{lcm//gcd}"
|
| 137 |
-
|
| 138 |
elif "area of a rectangle" in question:
|
| 139 |
return f"{values['l'] * values['w']} cm²"
|
| 140 |
|
|
@@ -145,270 +94,39 @@ class FreeAITutor:
|
|
| 145 |
return f"{0.5 * values['b'] * values['h']} cm²"
|
| 146 |
|
| 147 |
elif "circumference of a circle" in question:
|
| 148 |
-
return f"{2 * 3.14 * values['r']} cm"
|
| 149 |
|
| 150 |
elif "volume of a cube" in question:
|
| 151 |
return f"{values['s'] ** 3} cm³"
|
| 152 |
|
| 153 |
-
elif "
|
| 154 |
-
a, b, c = values['a'], values['b'], values['c']
|
| 155 |
-
|
| 156 |
-
|
| 157 |
-
|
| 158 |
-
return f"{
|
| 159 |
|
| 160 |
else:
|
| 161 |
-
|
| 162 |
-
expr = question.lower()
|
| 163 |
-
expr = expr.replace("what is", "").replace("calculate", "").replace("find", "")
|
| 164 |
-
expr = expr.replace("×", "*").replace("x", "*").replace("÷", "/")
|
| 165 |
-
|
| 166 |
-
# Remove units and text
|
| 167 |
-
expr = re.sub(r'[a-zA-Z\s]+', '', expr)
|
| 168 |
-
expr = expr.strip(" .?")
|
| 169 |
|
| 170 |
-
|
| 171 |
-
result = eval(expr)
|
| 172 |
-
return str(round(result, 2))
|
| 173 |
-
except:
|
| 174 |
-
return "Calculate step by step"
|
| 175 |
-
|
| 176 |
-
except Exception as e:
|
| 177 |
-
print(f"Calculation error: {e}")
|
| 178 |
return "Practice this concept"
|
| 179 |
|
| 180 |
-
def
|
| 181 |
-
"""Generate English questions using templates"""
|
| 182 |
-
templates = {
|
| 183 |
-
"Grammar": [
|
| 184 |
-
"Correct the sentence: '{sentence}'",
|
| 185 |
-
"Choose the correct word: '{sentence}'",
|
| 186 |
-
"Identify the error: '{sentence}'",
|
| 187 |
-
"Complete the sentence: '{sentence}'",
|
| 188 |
-
"Rewrite in past tense: '{sentence}'"
|
| 189 |
-
],
|
| 190 |
-
"Vocabulary": [
|
| 191 |
-
"What is a synonym for '{word}'?",
|
| 192 |
-
"What is the antonym of '{word}'?",
|
| 193 |
-
"Use '{word}' in a sentence.",
|
| 194 |
-
"What does '{word}' mean?",
|
| 195 |
-
"Which word doesn't belong: '{words}'?"
|
| 196 |
-
],
|
| 197 |
-
"Comprehension": [
|
| 198 |
-
"Read this passage: '{passage}'. Answer: {question}",
|
| 199 |
-
"What is the main idea? '{passage}'",
|
| 200 |
-
"What can you infer from: '{passage}'?",
|
| 201 |
-
"Who is the main character? '{passage}'",
|
| 202 |
-
"What happened first? '{passage}'"
|
| 203 |
-
],
|
| 204 |
-
"Writing": [
|
| 205 |
-
"Write a paragraph about '{topic}'.",
|
| 206 |
-
"Write a letter to your teacher about '{topic}'.",
|
| 207 |
-
"Write a story that includes: '{elements}'",
|
| 208 |
-
"Describe your favorite place in Trinidad.",
|
| 209 |
-
"Write instructions for '{activity}'."
|
| 210 |
-
]
|
| 211 |
-
}
|
| 212 |
-
|
| 213 |
-
# Word banks
|
| 214 |
-
vocabulary = {
|
| 215 |
-
"easy": ["happy", "big", "small", "fast", "slow", "hot", "cold", "good", "bad"],
|
| 216 |
-
"medium": ["excited", "enormous", "minuscule", "rapid", "gradual", "scorching", "freezing", "excellent", "terrible"],
|
| 217 |
-
"hard": ["ecstatic", "colossal", "microscopic", "expeditious", "leisurely", "blistering", "glacial", "superb", "atrocious"]
|
| 218 |
-
}
|
| 219 |
-
|
| 220 |
-
grammar_sentences = {
|
| 221 |
-
"easy": [
|
| 222 |
-
"He go to school.",
|
| 223 |
-
"They is happy.",
|
| 224 |
-
"I has a book.",
|
| 225 |
-
"She don't like it.",
|
| 226 |
-
"We was playing."
|
| 227 |
-
],
|
| 228 |
-
"medium": [
|
| 229 |
-
"Neither of the boys have finished.",
|
| 230 |
-
"Each of the students are here.",
|
| 231 |
-
"The team are winning.",
|
| 232 |
-
"One of the books were missing.",
|
| 233 |
-
"Everybody know the answer."
|
| 234 |
-
],
|
| 235 |
-
"hard": [
|
| 236 |
-
"The data suggests that the hypothesis are correct.",
|
| 237 |
-
"A number of students was absent.",
|
| 238 |
-
"The committee have made their decision.",
|
| 239 |
-
"Physics are my favorite subject.",
|
| 240 |
-
"The police is investigating."
|
| 241 |
-
]
|
| 242 |
-
}
|
| 243 |
-
|
| 244 |
-
passages = [
|
| 245 |
-
"The Caribbean sun shone brightly on the beaches of Trinidad. Children played in the sand while their parents watched from under colorful umbrellas. The sound of steelpan music floated on the breeze.",
|
| 246 |
-
"In the rainforest, brightly colored birds flew between the trees. Monkeys chattered in the canopy, and exotic flowers bloomed everywhere. The air was filled with the sounds of nature.",
|
| 247 |
-
"During Carnival, the streets of Port of Spain come alive with music and dancing. People in elaborate costumes parade through the streets, celebrating with joy and energy.",
|
| 248 |
-
"The market was bustling with activity. Vendors sold fresh fruits, vegetables, and spices. The smell of curry and fresh bread filled the air as people shopped for their dinner ingredients."
|
| 249 |
-
]
|
| 250 |
-
|
| 251 |
-
difficulty_key = ["easy", "medium", "hard", "hard", "hard"][difficulty-1]
|
| 252 |
-
|
| 253 |
-
if topic in templates:
|
| 254 |
-
template = random.choice(templates[topic])
|
| 255 |
-
|
| 256 |
-
if topic == "Grammar":
|
| 257 |
-
sentence = random.choice(grammar_sentences[difficulty_key])
|
| 258 |
-
question = template.format(sentence=sentence)
|
| 259 |
-
answer = self._correct_grammar(sentence)
|
| 260 |
-
|
| 261 |
-
elif topic == "Vocabulary":
|
| 262 |
-
word = random.choice(vocabulary[difficulty_key])
|
| 263 |
-
question = template.format(word=word)
|
| 264 |
-
answer = self._get_word_info(word, template)
|
| 265 |
-
|
| 266 |
-
elif topic == "Comprehension":
|
| 267 |
-
passage = random.choice(passages)
|
| 268 |
-
questions = [
|
| 269 |
-
"What is happening?",
|
| 270 |
-
"Where does this take place?",
|
| 271 |
-
"Who is mentioned?",
|
| 272 |
-
"What time of day is it?",
|
| 273 |
-
"What sounds are mentioned?"
|
| 274 |
-
]
|
| 275 |
-
q = random.choice(questions)
|
| 276 |
-
question = template.format(passage=passage, question=q)
|
| 277 |
-
answer = self._comprehension_answer(passage, q)
|
| 278 |
-
|
| 279 |
-
elif topic == "Writing":
|
| 280 |
-
writing_topics = ["your family", "your school", "Carnival", "the beach", "a rainforest adventure"]
|
| 281 |
-
topic_word = random.choice(writing_topics)
|
| 282 |
-
question = template.format(topic=topic_word)
|
| 283 |
-
answer = "Write a well-structured paragraph with complete sentences."
|
| 284 |
-
|
| 285 |
-
else:
|
| 286 |
-
question = f"Practice {topic}."
|
| 287 |
-
answer = "Practice answer"
|
| 288 |
-
|
| 289 |
-
return question, answer
|
| 290 |
-
|
| 291 |
-
return f"Practice {topic} questions.", "Check your textbook."
|
| 292 |
-
|
| 293 |
-
def _correct_grammar(self, sentence):
|
| 294 |
-
"""Correct grammar errors in sentences"""
|
| 295 |
-
corrections = {
|
| 296 |
-
"He go to school.": "He goes to school.",
|
| 297 |
-
"They is happy.": "They are happy.",
|
| 298 |
-
"I has a book.": "I have a book.",
|
| 299 |
-
"She don't like it.": "She doesn't like it.",
|
| 300 |
-
"We was playing.": "We were playing.",
|
| 301 |
-
"Neither of the boys have finished.": "Neither of the boys has finished.",
|
| 302 |
-
"Each of the students are here.": "Each of the students is here.",
|
| 303 |
-
"The team are winning.": "The team is winning.",
|
| 304 |
-
"One of the books were missing.": "One of the books was missing.",
|
| 305 |
-
"Everybody know the answer.": "Everybody knows the answer."
|
| 306 |
-
}
|
| 307 |
-
return corrections.get(sentence, "Sentence appears to be correct.")
|
| 308 |
-
|
| 309 |
-
def _get_word_info(self, word, template):
|
| 310 |
-
"""Get word synonyms/antonyms"""
|
| 311 |
-
synonyms = {
|
| 312 |
-
"happy": "joyful, cheerful",
|
| 313 |
-
"big": "large, huge",
|
| 314 |
-
"small": "little, tiny",
|
| 315 |
-
"fast": "quick, rapid",
|
| 316 |
-
"slow": "gradual, unhurried",
|
| 317 |
-
"hot": "warm, boiling",
|
| 318 |
-
"cold": "chilly, freezing",
|
| 319 |
-
"good": "excellent, great",
|
| 320 |
-
"bad": "poor, terrible"
|
| 321 |
-
}
|
| 322 |
-
|
| 323 |
-
antonyms = {
|
| 324 |
-
"happy": "sad",
|
| 325 |
-
"big": "small",
|
| 326 |
-
"small": "big",
|
| 327 |
-
"fast": "slow",
|
| 328 |
-
"slow": "fast",
|
| 329 |
-
"hot": "cold",
|
| 330 |
-
"cold": "hot",
|
| 331 |
-
"good": "bad",
|
| 332 |
-
"bad": "good"
|
| 333 |
-
}
|
| 334 |
-
|
| 335 |
-
if "synonym" in template:
|
| 336 |
-
return synonyms.get(word, "Look up in dictionary")
|
| 337 |
-
elif "antonym" in template:
|
| 338 |
-
return antonyms.get(word, "Look up in dictionary")
|
| 339 |
-
else:
|
| 340 |
-
return "Check dictionary for meaning"
|
| 341 |
-
|
| 342 |
-
def _comprehension_answer(self, passage, question):
|
| 343 |
-
"""Generate comprehension answers"""
|
| 344 |
-
if "What is happening" in question:
|
| 345 |
-
return "People are enjoying outdoor activities in Trinidad."
|
| 346 |
-
elif "Where" in question:
|
| 347 |
-
if "beach" in passage.lower():
|
| 348 |
-
return "At the beach in Trinidad"
|
| 349 |
-
elif "rainforest" in passage.lower():
|
| 350 |
-
return "In a rainforest"
|
| 351 |
-
elif "Carnival" in passage.lower():
|
| 352 |
-
return "In Port of Spain during Carnival"
|
| 353 |
-
elif "market" in passage.lower():
|
| 354 |
-
return "At a market"
|
| 355 |
-
elif "Who" in question:
|
| 356 |
-
return "People, children, parents, vendors (depending on passage)"
|
| 357 |
-
elif "time of day" in question:
|
| 358 |
-
return "During the day"
|
| 359 |
-
elif "sounds" in question:
|
| 360 |
-
return "Music, nature sounds, chatter"
|
| 361 |
-
|
| 362 |
-
return "Read the passage carefully for the answer."
|
| 363 |
-
|
| 364 |
-
def evaluate_answer(self, student_answer, correct_answer, question):
|
| 365 |
"""Evaluate student's answer"""
|
| 366 |
student = str(student_answer).strip().lower()
|
| 367 |
correct = str(correct_answer).strip().lower()
|
| 368 |
|
| 369 |
-
# Basic matching
|
| 370 |
if student == correct:
|
| 371 |
-
return {
|
| 372 |
-
"correct": True,
|
| 373 |
-
"confidence": 1.0,
|
| 374 |
-
"feedback": "✅ Perfect answer!",
|
| 375 |
-
"score": 1
|
| 376 |
-
}
|
| 377 |
-
|
| 378 |
-
# For math: check numeric equivalence
|
| 379 |
-
try:
|
| 380 |
-
# Extract numbers
|
| 381 |
-
s_num = re.findall(r"[-+]?\d*\.\d+|\d+", student)
|
| 382 |
-
c_num = re.findall(r"[-+]?\d*\.\d+|\d+", correct)
|
| 383 |
-
|
| 384 |
-
if s_num and c_num:
|
| 385 |
-
if abs(float(s_num[0]) - float(c_num[0])) < 0.01:
|
| 386 |
-
return {
|
| 387 |
-
"correct": True,
|
| 388 |
-
"confidence": 0.9,
|
| 389 |
-
"feedback": "✅ Numerically correct!",
|
| 390 |
-
"score": 0.9
|
| 391 |
-
}
|
| 392 |
-
except:
|
| 393 |
-
pass
|
| 394 |
|
| 395 |
# Partial credit for showing work
|
| 396 |
-
if len(student) > 10
|
| 397 |
-
return {
|
| 398 |
-
"correct": False,
|
| 399 |
-
"confidence": 0.5,
|
| 400 |
-
"feedback": "⚠️ Good attempt. The correct answer is: " + correct_answer,
|
| 401 |
-
"score": 0.5
|
| 402 |
-
}
|
| 403 |
|
| 404 |
-
return {
|
| 405 |
-
"correct": False,
|
| 406 |
-
"confidence": 0.0,
|
| 407 |
-
"feedback": "❌ Incorrect. The correct answer is: " + correct_answer,
|
| 408 |
-
"score": 0
|
| 409 |
-
}
|
| 410 |
|
| 411 |
-
# ==================== DATABASE
|
| 412 |
class ThreadLocal(threading.local):
|
| 413 |
def __init__(self):
|
| 414 |
self.connections = {}
|
|
@@ -416,679 +134,309 @@ class ThreadLocal(threading.local):
|
|
| 416 |
thread_local = ThreadLocal()
|
| 417 |
|
| 418 |
def get_db_connection(db_name="questions"):
|
| 419 |
-
"""Get
|
| 420 |
if not hasattr(thread_local, 'connections'):
|
| 421 |
thread_local.connections = {}
|
| 422 |
|
| 423 |
if db_name not in thread_local.connections:
|
| 424 |
-
|
| 425 |
-
os.makedirs(db_dir, exist_ok=True)
|
| 426 |
-
|
| 427 |
-
conn = sqlite3.connect(
|
| 428 |
-
os.path.join(db_dir, f"{db_name}.db"),
|
| 429 |
-
check_same_thread=False
|
| 430 |
-
)
|
| 431 |
thread_local.connections[db_name] = conn
|
| 432 |
|
| 433 |
return thread_local.connections[db_name]
|
| 434 |
|
| 435 |
def init_databases():
|
| 436 |
-
"""Initialize
|
| 437 |
-
# Questions
|
| 438 |
-
|
| 439 |
-
|
| 440 |
-
|
| 441 |
-
q_cur.execute('''
|
| 442 |
CREATE TABLE IF NOT EXISTS questions (
|
| 443 |
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
| 444 |
subject TEXT,
|
| 445 |
topic TEXT,
|
| 446 |
question_text TEXT,
|
| 447 |
-
|
| 448 |
-
difficulty INTEGER DEFAULT 3,
|
| 449 |
-
options TEXT,
|
| 450 |
correct_answer TEXT,
|
| 451 |
-
explanation TEXT,
|
| 452 |
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
|
| 453 |
)
|
| 454 |
''')
|
| 455 |
|
| 456 |
-
# Student
|
| 457 |
-
|
| 458 |
-
|
| 459 |
-
|
| 460 |
-
s_cur.execute('''
|
| 461 |
CREATE TABLE IF NOT EXISTS student_progress (
|
| 462 |
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
| 463 |
-
student_id TEXT
|
| 464 |
question_id INTEGER,
|
| 465 |
student_answer TEXT,
|
| 466 |
correct BOOLEAN,
|
| 467 |
score REAL,
|
| 468 |
-
|
| 469 |
-
topic TEXT,
|
| 470 |
-
difficulty INTEGER,
|
| 471 |
-
date TIMESTAMP DEFAULT CURRENT_TIMESTAMP
|
| 472 |
)
|
| 473 |
''')
|
| 474 |
|
| 475 |
-
|
| 476 |
CREATE TABLE IF NOT EXISTS student_stats (
|
| 477 |
student_id TEXT PRIMARY KEY,
|
| 478 |
total_questions INTEGER DEFAULT 0,
|
| 479 |
correct_answers INTEGER DEFAULT 0,
|
| 480 |
total_score REAL DEFAULT 0,
|
| 481 |
-
|
| 482 |
-
best_streak INTEGER DEFAULT 0,
|
| 483 |
-
level INTEGER DEFAULT 1,
|
| 484 |
-
last_active TIMESTAMP DEFAULT CURRENT_TIMESTAMP
|
| 485 |
)
|
| 486 |
''')
|
| 487 |
|
| 488 |
-
# Add sample questions if empty
|
| 489 |
-
q_cur.execute("SELECT COUNT(*) FROM questions")
|
| 490 |
-
if q_cur.fetchone()[0] == 0:
|
| 491 |
-
add_sample_questions()
|
| 492 |
-
|
| 493 |
-
s_conn.commit()
|
| 494 |
-
q_conn.commit()
|
| 495 |
-
|
| 496 |
-
def add_sample_questions():
|
| 497 |
-
"""Add sample questions to database"""
|
| 498 |
-
conn = get_db_connection("questions")
|
| 499 |
-
cur = conn.cursor()
|
| 500 |
-
|
| 501 |
-
sample_questions = [
|
| 502 |
-
("Mathematics", "Fractions", "Simplify 12/16", "Short Answer", 2, None, "3/4", "Divide numerator and denominator by 4"),
|
| 503 |
-
("Mathematics", "Geometry", "Find the area of a rectangle with length 8cm and width 5cm", "Short Answer", 2, None, "40 cm²", "Area = length × width = 8 × 5"),
|
| 504 |
-
("English", "Grammar", "Correct: He go to school every day", "Short Answer", 1, None, "He goes to school every day", "Use 'goes' for third person singular"),
|
| 505 |
-
("English", "Vocabulary", "What is a synonym for happy?", "Short Answer", 1, None, "joyful", "Synonyms are words with similar meanings"),
|
| 506 |
-
("Mathematics", "Percentages", "What is 20% of 50?", "Short Answer", 2, None, "10", "20% of 50 = 0.20 × 50 = 10")
|
| 507 |
-
]
|
| 508 |
-
|
| 509 |
-
for q in sample_questions:
|
| 510 |
-
cur.execute('''
|
| 511 |
-
INSERT INTO questions (subject, topic, question_text, question_type, difficulty, options, correct_answer, explanation)
|
| 512 |
-
VALUES (?, ?, ?, ?, ?, ?, ?, ?)
|
| 513 |
-
''', q)
|
| 514 |
-
|
| 515 |
conn.commit()
|
| 516 |
|
| 517 |
-
# ==================== MAIN
|
| 518 |
class SEAITutor:
|
| 519 |
def __init__(self):
|
| 520 |
-
self.
|
| 521 |
init_databases()
|
| 522 |
-
print("
|
| 523 |
|
| 524 |
self.subjects = {
|
| 525 |
"Mathematics": {
|
| 526 |
-
"
|
| 527 |
-
"
|
| 528 |
-
"description": "Practice SEA-level math problems"
|
| 529 |
},
|
| 530 |
"English": {
|
| 531 |
-
"
|
| 532 |
-
"
|
| 533 |
-
"description": "Improve English language skills"
|
| 534 |
}
|
| 535 |
}
|
| 536 |
|
| 537 |
self.difficulty_levels = {
|
| 538 |
-
1:
|
| 539 |
-
2: {"label": "Easy", "color": "#8BC34A"},
|
| 540 |
-
3: {"label": "Medium", "color": "#FFC107"},
|
| 541 |
-
4: {"label": "Hard", "color": "#FF9800"},
|
| 542 |
-
5: {"label": "Expert", "color": "#F44336"}
|
| 543 |
}
|
| 544 |
|
| 545 |
self.student_id = "student_001"
|
| 546 |
|
| 547 |
-
def generate_question(self, subject, topic, difficulty
|
| 548 |
"""Generate a new question"""
|
| 549 |
-
|
| 550 |
-
|
| 551 |
-
|
| 552 |
-
|
| 553 |
-
|
| 554 |
-
|
| 555 |
-
|
| 556 |
-
|
| 557 |
-
|
| 558 |
-
|
| 559 |
-
|
| 560 |
-
|
| 561 |
-
|
| 562 |
-
|
| 563 |
-
|
| 564 |
-
|
| 565 |
-
|
| 566 |
-
|
| 567 |
-
|
| 568 |
-
|
| 569 |
-
|
| 570 |
-
|
| 571 |
-
|
| 572 |
-
|
| 573 |
-
"difficulty_label": self.difficulty_levels.get(difficulty, {}).get("label", "Medium"),
|
| 574 |
-
"correct_answer": answer,
|
| 575 |
-
"explanation": f"Practice {topic} to improve your skills."
|
| 576 |
-
}
|
| 577 |
-
|
| 578 |
-
except Exception as e:
|
| 579 |
-
print(f"Error generating question: {e}")
|
| 580 |
-
# Return a fallback question
|
| 581 |
-
return {
|
| 582 |
-
"id": None,
|
| 583 |
-
"subject": subject,
|
| 584 |
-
"topic": topic,
|
| 585 |
-
"text": f"What is an example of a {topic.lower()} question in {subject}?",
|
| 586 |
-
"difficulty": difficulty,
|
| 587 |
-
"difficulty_label": self.difficulty_levels.get(difficulty, {}).get("label", "Medium"),
|
| 588 |
-
"correct_answer": "Practice this topic regularly.",
|
| 589 |
-
"explanation": "Keep practicing to improve!"
|
| 590 |
-
}
|
| 591 |
|
| 592 |
def submit_answer(self, question_id, student_answer):
|
| 593 |
-
"""Submit and evaluate
|
| 594 |
-
|
| 595 |
-
|
| 596 |
-
|
| 597 |
-
|
| 598 |
-
|
| 599 |
-
|
| 600 |
-
|
| 601 |
-
|
| 602 |
-
|
| 603 |
-
result = cursor.fetchone()
|
| 604 |
-
if not result:
|
| 605 |
-
return {
|
| 606 |
-
"correct": False,
|
| 607 |
-
"feedback": "Question not found.",
|
| 608 |
-
"score": 0,
|
| 609 |
-
"correct_answer": ""
|
| 610 |
-
}
|
| 611 |
-
|
| 612 |
-
question_text, correct_answer, explanation, topic, difficulty = result
|
| 613 |
-
|
| 614 |
-
# Evaluate using free AI
|
| 615 |
-
evaluation = self.free_ai.evaluate_answer(student_answer, correct_answer, question_text)
|
| 616 |
-
|
| 617 |
-
# Record progress
|
| 618 |
-
self._record_progress(question_id, student_answer, evaluation["correct"],
|
| 619 |
-
evaluation["score"], evaluation["feedback"], topic, difficulty)
|
| 620 |
-
|
| 621 |
-
return {
|
| 622 |
-
"correct": evaluation["correct"],
|
| 623 |
-
"feedback": evaluation["feedback"],
|
| 624 |
-
"score": evaluation["score"],
|
| 625 |
-
"correct_answer": correct_answer,
|
| 626 |
-
"explanation": explanation
|
| 627 |
-
}
|
| 628 |
-
|
| 629 |
-
except Exception as e:
|
| 630 |
-
print(f"Error submitting answer: {e}")
|
| 631 |
-
return {
|
| 632 |
-
"correct": False,
|
| 633 |
-
"feedback": "Error processing answer. Please try again.",
|
| 634 |
-
"score": 0,
|
| 635 |
-
"correct_answer": "",
|
| 636 |
-
"explanation": ""
|
| 637 |
-
}
|
| 638 |
-
|
| 639 |
-
def _record_progress(self, question_id, student_answer, correct, score, feedback, topic, difficulty):
|
| 640 |
-
"""Record student progress"""
|
| 641 |
-
conn = get_db_connection("students")
|
| 642 |
-
cursor = conn.cursor()
|
| 643 |
|
| 644 |
-
|
| 645 |
-
|
| 646 |
-
|
| 647 |
-
|
| 648 |
-
|
| 649 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
| 650 |
|
| 651 |
# Update stats
|
| 652 |
-
|
| 653 |
-
INSERT OR REPLACE INTO student_stats
|
| 654 |
-
(student_id, total_questions, correct_answers, total_score, last_active)
|
| 655 |
VALUES (?,
|
| 656 |
COALESCE((SELECT total_questions FROM student_stats WHERE student_id = ?), 0) + 1,
|
| 657 |
COALESCE((SELECT correct_answers FROM student_stats WHERE student_id = ?), 0) + ?,
|
| 658 |
-
COALESCE((SELECT total_score FROM student_stats WHERE student_id = ?), 0) + ?
|
| 659 |
-
CURRENT_TIMESTAMP
|
| 660 |
)
|
| 661 |
-
''', (self.student_id, self.student_id, self.student_id,
|
| 662 |
-
self.student_id, score))
|
| 663 |
-
|
| 664 |
-
# Update streak
|
| 665 |
-
if correct:
|
| 666 |
-
cursor.execute('''
|
| 667 |
-
UPDATE student_stats
|
| 668 |
-
SET current_streak = COALESCE(current_streak, 0) + 1,
|
| 669 |
-
best_streak = MAX(best_streak, COALESCE(current_streak, 0) + 1)
|
| 670 |
-
WHERE student_id = ?
|
| 671 |
-
''', (self.student_id,))
|
| 672 |
-
else:
|
| 673 |
-
cursor.execute('''
|
| 674 |
-
UPDATE student_stats SET current_streak = 0 WHERE student_id = ?
|
| 675 |
-
''', (self.student_id,))
|
| 676 |
-
|
| 677 |
-
# Update level based on total score
|
| 678 |
-
cursor.execute('''
|
| 679 |
-
UPDATE student_stats
|
| 680 |
-
SET level = CAST(total_score / 100 AS INTEGER) + 1
|
| 681 |
-
WHERE student_id = ? AND level < CAST(total_score / 100 AS INTEGER) + 1
|
| 682 |
-
''', (self.student_id,))
|
| 683 |
|
| 684 |
conn.commit()
|
|
|
|
|
|
|
| 685 |
|
| 686 |
-
def
|
| 687 |
"""Get student statistics"""
|
| 688 |
conn = get_db_connection("students")
|
| 689 |
-
|
| 690 |
-
|
| 691 |
-
|
| 692 |
-
SELECT total_questions, correct_answers, total_score, current_streak, best_streak, level
|
| 693 |
FROM student_stats WHERE student_id = ?
|
| 694 |
''', (self.student_id,))
|
| 695 |
|
| 696 |
-
row =
|
| 697 |
if not row:
|
| 698 |
-
return {
|
| 699 |
-
"total_questions": 0,
|
| 700 |
-
"correct_answers": 0,
|
| 701 |
-
"accuracy": 0,
|
| 702 |
-
"total_score": 0,
|
| 703 |
-
"current_streak": 0,
|
| 704 |
-
"best_streak": 0,
|
| 705 |
-
"level": 1,
|
| 706 |
-
"level_progress": 0
|
| 707 |
-
}
|
| 708 |
|
| 709 |
-
total, correct,
|
| 710 |
accuracy = (correct / total * 100) if total > 0 else 0
|
| 711 |
-
level_progress = (total_score % 100)
|
| 712 |
-
|
| 713 |
-
# Get topic performance
|
| 714 |
-
cursor.execute('''
|
| 715 |
-
SELECT topic,
|
| 716 |
-
COUNT(*) as total,
|
| 717 |
-
AVG(score) as avg_score
|
| 718 |
-
FROM student_progress
|
| 719 |
-
WHERE student_id = ?
|
| 720 |
-
GROUP BY topic
|
| 721 |
-
ORDER BY avg_score ASC
|
| 722 |
-
LIMIT 3
|
| 723 |
-
''', (self.student_id,))
|
| 724 |
-
|
| 725 |
-
weak_topics = []
|
| 726 |
-
for topic, total_q, avg_score in cursor.fetchall():
|
| 727 |
-
weak_topics.append({
|
| 728 |
-
"topic": topic,
|
| 729 |
-
"accuracy": round((avg_score or 0) * 100, 1),
|
| 730 |
-
"total": total_q
|
| 731 |
-
})
|
| 732 |
|
| 733 |
return {
|
| 734 |
"total_questions": total,
|
| 735 |
"correct_answers": correct,
|
| 736 |
"accuracy": round(accuracy, 1),
|
| 737 |
-
"total_score": round(
|
| 738 |
-
"
|
| 739 |
-
"best_streak": best_streak,
|
| 740 |
-
"level": level,
|
| 741 |
-
"level_progress": level_progress,
|
| 742 |
-
"weak_topics": weak_topics
|
| 743 |
}
|
| 744 |
-
|
| 745 |
-
def get_random_tip(self):
|
| 746 |
-
"""Get random study tip"""
|
| 747 |
-
tips = [
|
| 748 |
-
"🎯 Study a little every day instead of cramming before exams.",
|
| 749 |
-
"📝 Practice past SEA exam papers regularly.",
|
| 750 |
-
"⏰ Take short breaks during study sessions to stay focused.",
|
| 751 |
-
"📚 Review your mistakes to learn from them.",
|
| 752 |
-
"🧮 For math, show all your working steps.",
|
| 753 |
-
"📖 Read every day to improve vocabulary and comprehension.",
|
| 754 |
-
"✅ Check your work before submitting answers.",
|
| 755 |
-
"🎨 Use colors and diagrams to remember concepts.",
|
| 756 |
-
"🤔 Ask questions when you don't understand something.",
|
| 757 |
-
"🎵 Study in a quiet place without distractions."
|
| 758 |
-
]
|
| 759 |
-
return random.choice(tips)
|
| 760 |
-
|
| 761 |
-
def get_leaderboard(self):
|
| 762 |
-
"""Get leaderboard data (simulated for demo)"""
|
| 763 |
-
return [
|
| 764 |
-
{"rank": 1, "name": "Aaliyah", "score": 1250, "level": 13},
|
| 765 |
-
{"rank": 2, "name": "Brandon", "score": 1180, "level": 12},
|
| 766 |
-
{"rank": 3, "name": "Chelsea", "score": 1050, "level": 11},
|
| 767 |
-
{"rank": 4, "name": "David", "score": 980, "level": 10},
|
| 768 |
-
{"rank": 5, "name": "Emily", "score": 920, "level": 10}
|
| 769 |
-
]
|
| 770 |
|
| 771 |
-
# Initialize
|
| 772 |
-
|
| 773 |
|
| 774 |
# ==================== GRADIO UI ====================
|
| 775 |
-
def
|
| 776 |
-
"""Create the Gradio interface"""
|
| 777 |
-
|
| 778 |
with gr.Blocks(
|
| 779 |
-
title="
|
| 780 |
-
theme=gr.themes.Soft(
|
| 781 |
-
primary_hue="blue",
|
| 782 |
-
secondary_hue="green"
|
| 783 |
-
),
|
| 784 |
-
css="""
|
| 785 |
-
.gradio-container {
|
| 786 |
-
max-width: 1200px !important;
|
| 787 |
-
margin: 0 auto;
|
| 788 |
-
}
|
| 789 |
-
.header {
|
| 790 |
-
text-align: center;
|
| 791 |
-
padding: 20px;
|
| 792 |
-
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
|
| 793 |
-
color: white;
|
| 794 |
-
border-radius: 10px;
|
| 795 |
-
margin-bottom: 20px;
|
| 796 |
-
}
|
| 797 |
-
.free-badge {
|
| 798 |
-
background: #4CAF50;
|
| 799 |
-
color: white;
|
| 800 |
-
padding: 5px 15px;
|
| 801 |
-
border-radius: 20px;
|
| 802 |
-
font-weight: bold;
|
| 803 |
-
display: inline-block;
|
| 804 |
-
margin: 5px;
|
| 805 |
-
}
|
| 806 |
-
.question-card {
|
| 807 |
-
background: #f8f9fa;
|
| 808 |
-
padding: 20px;
|
| 809 |
-
border-radius: 10px;
|
| 810 |
-
border-left: 5px solid #2196F3;
|
| 811 |
-
margin: 15px 0;
|
| 812 |
-
}
|
| 813 |
-
.feedback-correct {
|
| 814 |
-
background: #d4edda;
|
| 815 |
-
padding: 15px;
|
| 816 |
-
border-radius: 10px;
|
| 817 |
-
border-left: 5px solid #28a745;
|
| 818 |
-
}
|
| 819 |
-
.feedback-incorrect {
|
| 820 |
-
background: #f8d7da;
|
| 821 |
-
padding: 15px;
|
| 822 |
-
border-radius: 10px;
|
| 823 |
-
border-left: 5px solid #dc3545;
|
| 824 |
-
}
|
| 825 |
-
.stat-card {
|
| 826 |
-
text-align: center;
|
| 827 |
-
padding: 15px;
|
| 828 |
-
background: white;
|
| 829 |
-
border-radius: 10px;
|
| 830 |
-
box-shadow: 0 2px 10px rgba(0,0,0,0.1);
|
| 831 |
-
margin: 10px 0;
|
| 832 |
-
}
|
| 833 |
-
.stat-value {
|
| 834 |
-
font-size: 2em;
|
| 835 |
-
font-weight: bold;
|
| 836 |
-
color: #2196F3;
|
| 837 |
-
}
|
| 838 |
-
.tip-box {
|
| 839 |
-
background: #e3f2fd;
|
| 840 |
-
padding: 15px;
|
| 841 |
-
border-radius: 10px;
|
| 842 |
-
font-style: italic;
|
| 843 |
-
margin: 10px 0;
|
| 844 |
-
}
|
| 845 |
-
"""
|
| 846 |
) as demo:
|
| 847 |
|
| 848 |
# Header
|
| 849 |
gr.Markdown("""
|
| 850 |
-
|
| 851 |
-
|
| 852 |
-
<h3>100% Free AI-Powered Secondary Entrance Assessment Preparation</h3>
|
| 853 |
-
<span class="free-badge">NO API KEYS REQUIRED</span>
|
| 854 |
-
<span class="free-badge">COMPLETELY FREE</span>
|
| 855 |
-
<span class="free-badge">LOCAL PROCESSING</span>
|
| 856 |
-
<p>Practice Mathematics and English questions for Trinidad & Tobago SEA exams</p>
|
| 857 |
-
</div>
|
| 858 |
""")
|
| 859 |
|
| 860 |
-
# Main content
|
| 861 |
with gr.Tabs():
|
| 862 |
-
# Practice Tab
|
| 863 |
with gr.TabItem("🎯 Practice"):
|
| 864 |
with gr.Row():
|
| 865 |
with gr.Column(scale=1):
|
| 866 |
-
gr.Markdown("### 📝 Question Settings")
|
| 867 |
subject = gr.Dropdown(
|
| 868 |
-
choices=
|
| 869 |
-
|
| 870 |
-
|
| 871 |
)
|
| 872 |
topic = gr.Dropdown(
|
| 873 |
-
choices=
|
| 874 |
-
|
| 875 |
-
|
| 876 |
)
|
| 877 |
difficulty = gr.Slider(
|
| 878 |
-
|
| 879 |
label="Difficulty Level"
|
| 880 |
)
|
| 881 |
|
| 882 |
-
|
| 883 |
-
def update_topics(subject):
|
| 884 |
-
topics = ai_tutor.subjects.get(subject, {}).get("topics", [])
|
| 885 |
-
return gr.Dropdown.update(choices=topics, value=topics[0] if topics else "")
|
| 886 |
-
|
| 887 |
-
subject.change(update_topics, inputs=subject, outputs=topic)
|
| 888 |
-
|
| 889 |
-
generate_btn = gr.Button("✨ Generate New Question", variant="primary", size="lg")
|
| 890 |
|
| 891 |
gr.Markdown("---")
|
| 892 |
-
gr.
|
| 893 |
-
|
| 894 |
-
|
| 895 |
-
|
| 896 |
with gr.Column(scale=2):
|
| 897 |
-
gr.Markdown("###
|
| 898 |
-
|
| 899 |
-
|
| 900 |
-
gr.Markdown("### ✏️ Your Answer")
|
| 901 |
-
answer_input = gr.Textbox(
|
| 902 |
-
label="Type your answer here:",
|
| 903 |
-
placeholder="Enter your answer...",
|
| 904 |
-
lines=3
|
| 905 |
-
)
|
| 906 |
|
| 907 |
with gr.Row():
|
| 908 |
submit_btn = gr.Button("✅ Submit Answer", variant="primary")
|
| 909 |
-
clear_btn = gr.Button("🗑️ Clear
|
| 910 |
|
| 911 |
-
gr.Markdown("### 💬 Feedback")
|
| 912 |
-
feedback_display = gr.Markdown("")
|
| 913 |
-
|
| 914 |
-
gr.Markdown("### 📖 Explanation")
|
| 915 |
-
explanation_display = gr.Markdown("")
|
| 916 |
|
| 917 |
-
# Progress Tab
|
| 918 |
with gr.TabItem("📈 Progress"):
|
| 919 |
-
|
| 920 |
-
|
| 921 |
-
|
| 922 |
-
|
| 923 |
-
|
| 924 |
-
|
| 925 |
-
|
| 926 |
-
|
| 927 |
-
|
| 928 |
-
gr.Markdown("### 🏆 Leaderboard")
|
| 929 |
-
leaderboard = gr.Dataframe(
|
| 930 |
-
value=ai_tutor.get_leaderboard(),
|
| 931 |
-
headers=["Rank", "Name", "Score", "Level"],
|
| 932 |
-
interactive=False
|
| 933 |
-
)
|
| 934 |
-
|
| 935 |
-
gr.Markdown("### 💡 Study Tip")
|
| 936 |
-
tip_display = gr.Markdown("")
|
| 937 |
-
new_tip_btn = gr.Button("🎲 New Tip")
|
| 938 |
|
| 939 |
-
# About Tab
|
| 940 |
with gr.TabItem("ℹ️ About"):
|
| 941 |
gr.Markdown("""
|
| 942 |
## About SEA Prep Pro
|
| 943 |
|
| 944 |
-
**SEA Prep Pro** is a completely free AI-powered tutoring system
|
| 945 |
-
Trinidad and Tobago students preparing for the Secondary Entrance Assessment (SEA) exam.
|
| 946 |
|
| 947 |
-
###
|
| 948 |
-
-
|
| 949 |
-
-
|
| 950 |
-
-
|
| 951 |
-
-
|
| 952 |
-
-
|
| 953 |
|
| 954 |
-
###
|
| 955 |
-
- **Mathematics**: Fractions, Geometry, Algebra
|
| 956 |
-
- **English**: Grammar, Vocabulary, Comprehension
|
| 957 |
|
| 958 |
-
###
|
| 959 |
1. Select a subject and topic
|
| 960 |
-
2. Choose difficulty level
|
| 961 |
-
3. Generate
|
| 962 |
-
4. Submit answers
|
| 963 |
-
5. Track your progress
|
| 964 |
-
|
| 965 |
-
### 💝 Why It's Free:
|
| 966 |
-
This project was created to provide equal access to quality educational resources
|
| 967 |
-
for all Trinidad and Tobago students, regardless of their financial situation.
|
| 968 |
|
| 969 |
-
|
| 970 |
-
Created with ❤️ for Trinidad and Tobago students
|
| 971 |
-
|
| 972 |
-
***
|
| 973 |
-
|
| 974 |
-
<center>
|
| 975 |
-
<span class="free-badge">EDUCATION FOR ALL</span>
|
| 976 |
-
<span class="free-badge">MADE IN TRINIDAD & TOBAGO</span>
|
| 977 |
-
<span class="free-badge">OPEN SOURCE</span>
|
| 978 |
-
</center>
|
| 979 |
""")
|
| 980 |
|
| 981 |
-
# State
|
| 982 |
-
|
| 983 |
|
| 984 |
-
#
|
| 985 |
-
def
|
| 986 |
-
|
| 987 |
-
|
|
|
|
|
|
|
|
|
|
| 988 |
display = f"""
|
| 989 |
-
**Subject:** {
|
| 990 |
-
**Topic:** {
|
| 991 |
-
**Difficulty:** {
|
| 992 |
|
| 993 |
---
|
| 994 |
|
| 995 |
**Question:**
|
| 996 |
-
{
|
| 997 |
"""
|
| 998 |
-
|
| 999 |
-
return display, question, "", ""
|
| 1000 |
|
| 1001 |
-
|
| 1002 |
-
|
| 1003 |
-
|
| 1004 |
-
return "Please generate a question first!", "", question_state
|
| 1005 |
-
|
| 1006 |
-
result = ai_tutor.submit_answer(question_state["id"], answer)
|
| 1007 |
|
| 1008 |
-
|
| 1009 |
-
|
| 1010 |
-
<div class='{feedback_class}'>
|
| 1011 |
-
**Result:** {result['feedback']}
|
| 1012 |
-
**Score:** {result['score']:.1%}
|
| 1013 |
-
**Correct Answer:** {result['correct_answer']}
|
| 1014 |
-
</div>
|
| 1015 |
-
"""
|
| 1016 |
-
|
| 1017 |
-
explanation = f"**Explanation:** {result['explanation']}"
|
| 1018 |
-
|
| 1019 |
-
return feedback, explanation, question_state
|
| 1020 |
|
| 1021 |
-
# Get stats function
|
| 1022 |
def get_stats_func():
|
| 1023 |
-
|
| 1024 |
-
return stats
|
| 1025 |
-
|
| 1026 |
-
# Get tip function
|
| 1027 |
-
def get_tip():
|
| 1028 |
-
return ai_tutor.get_random_tip()
|
| 1029 |
-
|
| 1030 |
-
# Get recommendations
|
| 1031 |
-
def get_recommendations():
|
| 1032 |
-
stats = ai_tutor.get_student_stats()
|
| 1033 |
-
if stats["weak_topics"]:
|
| 1034 |
-
topic = stats["weak_topics"][0]["topic"]
|
| 1035 |
-
return f"**Focus on:** {topic} \n**Reason:** Lower accuracy in this area \n**Action:** Practice 5-10 {topic} questions daily"
|
| 1036 |
-
else:
|
| 1037 |
-
return "**Great job!** Keep practicing all topics to maintain your skills."
|
| 1038 |
|
| 1039 |
-
|
| 1040 |
-
def clear_answer():
|
| 1041 |
return "", ""
|
| 1042 |
|
| 1043 |
# Event handlers
|
| 1044 |
-
|
| 1045 |
-
|
| 1046 |
-
|
| 1047 |
-
|
| 1048 |
-
)
|
| 1049 |
-
|
| 1050 |
-
submit_btn.click(
|
| 1051 |
-
submit_a,
|
| 1052 |
-
inputs=[answer_input, current_question],
|
| 1053 |
-
outputs=[feedback_display, explanation_display, current_question]
|
| 1054 |
-
)
|
| 1055 |
-
|
| 1056 |
-
refresh_stats.click(
|
| 1057 |
-
get_stats_func,
|
| 1058 |
-
outputs=stats_display
|
| 1059 |
-
)
|
| 1060 |
-
|
| 1061 |
-
clear_btn.click(
|
| 1062 |
-
clear_answer,
|
| 1063 |
-
outputs=[answer_input, feedback_display]
|
| 1064 |
-
)
|
| 1065 |
-
|
| 1066 |
-
# Progress tab functions
|
| 1067 |
-
demo.load(
|
| 1068 |
-
lambda: [get_stats_func(), get_recommendations(), get_tip()],
|
| 1069 |
-
outputs=[progress_stats, recommendations, tip_display]
|
| 1070 |
-
)
|
| 1071 |
-
|
| 1072 |
-
new_tip_btn.click(
|
| 1073 |
-
get_tip,
|
| 1074 |
-
outputs=tip_display
|
| 1075 |
-
)
|
| 1076 |
|
| 1077 |
# Initialize
|
| 1078 |
-
demo.load(
|
| 1079 |
-
get_stats_func,
|
| 1080 |
-
outputs=stats_display
|
| 1081 |
-
)
|
| 1082 |
|
| 1083 |
return demo
|
| 1084 |
|
| 1085 |
-
#
|
| 1086 |
if __name__ == "__main__":
|
| 1087 |
-
app =
|
| 1088 |
-
app.launch(
|
| 1089 |
-
server_name="0.0.0.0",
|
| 1090 |
-
server_port=7860,
|
| 1091 |
-
share=False,
|
| 1092 |
-
show_error=True,
|
| 1093 |
-
debug=True
|
| 1094 |
-
)
|
|
|
|
| 1 |
"""
|
| 2 |
SEA Prep Pro - AI-Powered Tutor 🇹🇹
|
| 3 |
FULLY FREE VERSION - No API Keys Required!
|
|
|
|
| 4 |
"""
|
| 5 |
import gradio as gr
|
| 6 |
import sqlite3
|
|
|
|
| 8 |
import os
|
| 9 |
import re
|
| 10 |
import random
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 11 |
import math
|
| 12 |
+
import threading
|
| 13 |
+
from datetime import datetime
|
| 14 |
|
| 15 |
+
# ==================== FREE AI TUTOR ====================
|
|
|
|
|
|
|
|
|
|
| 16 |
class FreeAITutor:
|
| 17 |
+
"""Free AI tutor using rule-based generation"""
|
| 18 |
|
| 19 |
def __init__(self):
|
| 20 |
+
self.math_templates = {
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 21 |
"Fractions": [
|
| 22 |
"Simplify the fraction {num}/{den}.",
|
| 23 |
"Add {a}/{b} + {c}/{d}.",
|
|
|
|
| 25 |
"What is {num}/{den} of {whole}?",
|
| 26 |
"Convert {decimal} to a fraction."
|
| 27 |
],
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 28 |
"Geometry": [
|
| 29 |
"Find the area of a rectangle with length {l}cm and width {w}cm.",
|
| 30 |
"Calculate the perimeter of a square with side {s}cm.",
|
| 31 |
"Find the area of a triangle with base {b}cm and height {h}cm.",
|
| 32 |
"Calculate the circumference of a circle with radius {r}cm (use π=3.14).",
|
| 33 |
"Find the volume of a cube with side {s}cm."
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 34 |
]
|
| 35 |
}
|
| 36 |
|
| 37 |
+
self.english_templates = {
|
| 38 |
+
"Grammar": [
|
| 39 |
+
"Correct the sentence: '{sentence}'",
|
| 40 |
+
"Choose the correct word: '{sentence}'"
|
| 41 |
+
],
|
| 42 |
+
"Vocabulary": [
|
| 43 |
+
"What is a synonym for '{word}'?",
|
| 44 |
+
"What is the antonym of '{word}'?"
|
| 45 |
+
]
|
| 46 |
+
}
|
| 47 |
+
|
| 48 |
+
def generate_math_question(self, topic, difficulty):
|
| 49 |
+
"""Generate math question"""
|
| 50 |
+
templates = self.math_templates.get(topic, ["Practice {topic}."])
|
| 51 |
+
template = random.choice(templates)
|
| 52 |
|
| 53 |
+
# Generate values based on difficulty
|
| 54 |
+
max_val = {1: 10, 2: 20, 3: 50, 4: 100, 5: 200}[difficulty]
|
|
|
|
| 55 |
|
| 56 |
values = {
|
| 57 |
'num': random.randint(1, max_val),
|
|
|
|
| 62 |
'd': random.randint(2, max_val),
|
| 63 |
'whole': random.randint(10, 100),
|
| 64 |
'decimal': round(random.uniform(0.1, 0.9), 2),
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 65 |
'l': random.randint(5, 20),
|
| 66 |
'w': random.randint(3, 15),
|
| 67 |
's': random.randint(4, 12),
|
| 68 |
'r': random.randint(2, 10),
|
| 69 |
'h': random.randint(3, 12),
|
| 70 |
'b': random.randint(4, 16),
|
|
|
|
|
|
|
|
|
|
| 71 |
'topic': topic
|
| 72 |
}
|
| 73 |
|
| 74 |
question = template.format(**values)
|
| 75 |
+
answer = self._calculate_answer(question, values)
|
|
|
|
|
|
|
| 76 |
|
| 77 |
return question, answer
|
| 78 |
|
| 79 |
+
def _calculate_answer(self, question, values):
|
| 80 |
+
"""Calculate answer for math question"""
|
| 81 |
try:
|
| 82 |
if "Simplify the fraction" in question:
|
| 83 |
num, den = values['num'], values['den']
|
| 84 |
gcd = math.gcd(num, den)
|
| 85 |
return f"{num//gcd}/{den//gcd}"
|
| 86 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 87 |
elif "area of a rectangle" in question:
|
| 88 |
return f"{values['l'] * values['w']} cm²"
|
| 89 |
|
|
|
|
| 94 |
return f"{0.5 * values['b'] * values['h']} cm²"
|
| 95 |
|
| 96 |
elif "circumference of a circle" in question:
|
| 97 |
+
return f"{2 * 3.14 * values['r']:.2f} cm"
|
| 98 |
|
| 99 |
elif "volume of a cube" in question:
|
| 100 |
return f"{values['s'] ** 3} cm³"
|
| 101 |
|
| 102 |
+
elif "Add" in question and "/" in question:
|
| 103 |
+
a, b, c, d = values['a'], values['b'], values['c'], values['d']
|
| 104 |
+
lcm = b * d // math.gcd(b, d)
|
| 105 |
+
num = a * (lcm // b) + c * (lcm // d)
|
| 106 |
+
gcd = math.gcd(num, lcm)
|
| 107 |
+
return f"{num//gcd}/{lcm//gcd}"
|
| 108 |
|
| 109 |
else:
|
| 110 |
+
return "Calculate step by step"
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 111 |
|
| 112 |
+
except Exception:
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 113 |
return "Practice this concept"
|
| 114 |
|
| 115 |
+
def evaluate_answer(self, student_answer, correct_answer):
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 116 |
"""Evaluate student's answer"""
|
| 117 |
student = str(student_answer).strip().lower()
|
| 118 |
correct = str(correct_answer).strip().lower()
|
| 119 |
|
|
|
|
| 120 |
if student == correct:
|
| 121 |
+
return {"correct": True, "score": 1.0, "feedback": "✅ Correct! Well done!"}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 122 |
|
| 123 |
# Partial credit for showing work
|
| 124 |
+
if len(student) > 10:
|
| 125 |
+
return {"correct": False, "score": 0.5, "feedback": "⚠️ Good attempt. Keep practicing!"}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 126 |
|
| 127 |
+
return {"correct": False, "score": 0.0, "feedback": "❌ Incorrect. Try again!"}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 128 |
|
| 129 |
+
# ==================== DATABASE ====================
|
| 130 |
class ThreadLocal(threading.local):
|
| 131 |
def __init__(self):
|
| 132 |
self.connections = {}
|
|
|
|
| 134 |
thread_local = ThreadLocal()
|
| 135 |
|
| 136 |
def get_db_connection(db_name="questions"):
|
| 137 |
+
"""Get database connection"""
|
| 138 |
if not hasattr(thread_local, 'connections'):
|
| 139 |
thread_local.connections = {}
|
| 140 |
|
| 141 |
if db_name not in thread_local.connections:
|
| 142 |
+
conn = sqlite3.connect(f"/tmp/{db_name}.db", check_same_thread=False)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 143 |
thread_local.connections[db_name] = conn
|
| 144 |
|
| 145 |
return thread_local.connections[db_name]
|
| 146 |
|
| 147 |
def init_databases():
|
| 148 |
+
"""Initialize databases"""
|
| 149 |
+
# Questions DB
|
| 150 |
+
conn = get_db_connection("questions")
|
| 151 |
+
cur = conn.cursor()
|
| 152 |
+
cur.execute('''
|
|
|
|
| 153 |
CREATE TABLE IF NOT EXISTS questions (
|
| 154 |
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
| 155 |
subject TEXT,
|
| 156 |
topic TEXT,
|
| 157 |
question_text TEXT,
|
| 158 |
+
difficulty INTEGER,
|
|
|
|
|
|
|
| 159 |
correct_answer TEXT,
|
|
|
|
| 160 |
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
|
| 161 |
)
|
| 162 |
''')
|
| 163 |
|
| 164 |
+
# Student DB
|
| 165 |
+
conn = get_db_connection("students")
|
| 166 |
+
cur = conn.cursor()
|
| 167 |
+
cur.execute('''
|
|
|
|
| 168 |
CREATE TABLE IF NOT EXISTS student_progress (
|
| 169 |
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
| 170 |
+
student_id TEXT,
|
| 171 |
question_id INTEGER,
|
| 172 |
student_answer TEXT,
|
| 173 |
correct BOOLEAN,
|
| 174 |
score REAL,
|
| 175 |
+
timestamp TIMESTAMP DEFAULT CURRENT_TIMESTAMP
|
|
|
|
|
|
|
|
|
|
| 176 |
)
|
| 177 |
''')
|
| 178 |
|
| 179 |
+
cur.execute('''
|
| 180 |
CREATE TABLE IF NOT EXISTS student_stats (
|
| 181 |
student_id TEXT PRIMARY KEY,
|
| 182 |
total_questions INTEGER DEFAULT 0,
|
| 183 |
correct_answers INTEGER DEFAULT 0,
|
| 184 |
total_score REAL DEFAULT 0,
|
| 185 |
+
level INTEGER DEFAULT 1
|
|
|
|
|
|
|
|
|
|
| 186 |
)
|
| 187 |
''')
|
| 188 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 189 |
conn.commit()
|
| 190 |
|
| 191 |
+
# ==================== MAIN APP ====================
|
| 192 |
class SEAITutor:
|
| 193 |
def __init__(self):
|
| 194 |
+
self.ai = FreeAITutor()
|
| 195 |
init_databases()
|
| 196 |
+
print("🚀 SEA Prep Pro - Free AI Tutor Started!")
|
| 197 |
|
| 198 |
self.subjects = {
|
| 199 |
"Mathematics": {
|
| 200 |
+
"topics": ["Fractions", "Geometry", "Algebra", "Word Problems", "Percentages"],
|
| 201 |
+
"icon": "📐"
|
|
|
|
| 202 |
},
|
| 203 |
"English": {
|
| 204 |
+
"topics": ["Grammar", "Vocabulary", "Comprehension", "Writing"],
|
| 205 |
+
"icon": "📚"
|
|
|
|
| 206 |
}
|
| 207 |
}
|
| 208 |
|
| 209 |
self.difficulty_levels = {
|
| 210 |
+
1: "Beginner", 2: "Easy", 3: "Medium", 4: "Hard", 5: "Expert"
|
|
|
|
|
|
|
|
|
|
|
|
|
| 211 |
}
|
| 212 |
|
| 213 |
self.student_id = "student_001"
|
| 214 |
|
| 215 |
+
def generate_question(self, subject, topic, difficulty):
|
| 216 |
"""Generate a new question"""
|
| 217 |
+
if subject == "Mathematics":
|
| 218 |
+
question, answer = self.ai.generate_math_question(topic, difficulty)
|
| 219 |
+
else:
|
| 220 |
+
question = f"Practice {topic} in {subject}. Write your answer below."
|
| 221 |
+
answer = "Sample answer for practice."
|
| 222 |
+
|
| 223 |
+
# Store in DB
|
| 224 |
+
conn = get_db_connection("questions")
|
| 225 |
+
cur = conn.cursor()
|
| 226 |
+
cur.execute('''
|
| 227 |
+
INSERT INTO questions (subject, topic, question_text, difficulty, correct_answer)
|
| 228 |
+
VALUES (?, ?, ?, ?, ?)
|
| 229 |
+
''', (subject, topic, question, difficulty, answer))
|
| 230 |
+
|
| 231 |
+
question_id = cur.lastrowid
|
| 232 |
+
conn.commit()
|
| 233 |
+
|
| 234 |
+
return {
|
| 235 |
+
"id": question_id,
|
| 236 |
+
"text": question,
|
| 237 |
+
"subject": subject,
|
| 238 |
+
"topic": topic,
|
| 239 |
+
"difficulty": self.difficulty_levels.get(difficulty, "Medium")
|
| 240 |
+
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 241 |
|
| 242 |
def submit_answer(self, question_id, student_answer):
|
| 243 |
+
"""Submit and evaluate answer"""
|
| 244 |
+
# Get question
|
| 245 |
+
conn = get_db_connection("questions")
|
| 246 |
+
cur = conn.cursor()
|
| 247 |
+
cur.execute('SELECT correct_answer FROM questions WHERE id = ?', (question_id,))
|
| 248 |
+
row = cur.fetchone()
|
| 249 |
+
|
| 250 |
+
if not row:
|
| 251 |
+
return {"feedback": "Question not found.", "score": 0}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 252 |
|
| 253 |
+
correct_answer = row[0]
|
| 254 |
+
evaluation = self.ai.evaluate_answer(student_answer, correct_answer)
|
| 255 |
+
|
| 256 |
+
# Record progress
|
| 257 |
+
conn = get_db_connection("students")
|
| 258 |
+
cur = conn.cursor()
|
| 259 |
+
cur.execute('''
|
| 260 |
+
INSERT INTO student_progress (student_id, question_id, student_answer, correct, score)
|
| 261 |
+
VALUES (?, ?, ?, ?, ?)
|
| 262 |
+
''', (self.student_id, question_id, student_answer, evaluation["correct"], evaluation["score"]))
|
| 263 |
|
| 264 |
# Update stats
|
| 265 |
+
cur.execute('''
|
| 266 |
+
INSERT OR REPLACE INTO student_stats (student_id, total_questions, correct_answers, total_score)
|
|
|
|
| 267 |
VALUES (?,
|
| 268 |
COALESCE((SELECT total_questions FROM student_stats WHERE student_id = ?), 0) + 1,
|
| 269 |
COALESCE((SELECT correct_answers FROM student_stats WHERE student_id = ?), 0) + ?,
|
| 270 |
+
COALESCE((SELECT total_score FROM student_stats WHERE student_id = ?), 0) + ?
|
|
|
|
| 271 |
)
|
| 272 |
+
''', (self.student_id, self.student_id, self.student_id,
|
| 273 |
+
1 if evaluation["correct"] else 0, self.student_id, evaluation["score"]))
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 274 |
|
| 275 |
conn.commit()
|
| 276 |
+
|
| 277 |
+
return evaluation
|
| 278 |
|
| 279 |
+
def get_stats(self):
|
| 280 |
"""Get student statistics"""
|
| 281 |
conn = get_db_connection("students")
|
| 282 |
+
cur = conn.cursor()
|
| 283 |
+
cur.execute('''
|
| 284 |
+
SELECT total_questions, correct_answers, total_score, level
|
|
|
|
| 285 |
FROM student_stats WHERE student_id = ?
|
| 286 |
''', (self.student_id,))
|
| 287 |
|
| 288 |
+
row = cur.fetchone()
|
| 289 |
if not row:
|
| 290 |
+
return {"total_questions": 0, "correct_answers": 0, "accuracy": 0, "level": 1}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 291 |
|
| 292 |
+
total, correct, score, level = row
|
| 293 |
accuracy = (correct / total * 100) if total > 0 else 0
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 294 |
|
| 295 |
return {
|
| 296 |
"total_questions": total,
|
| 297 |
"correct_answers": correct,
|
| 298 |
"accuracy": round(accuracy, 1),
|
| 299 |
+
"total_score": round(score, 1),
|
| 300 |
+
"level": level
|
|
|
|
|
|
|
|
|
|
|
|
|
| 301 |
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 302 |
|
| 303 |
+
# Initialize
|
| 304 |
+
tutor = SEAITutor()
|
| 305 |
|
| 306 |
# ==================== GRADIO UI ====================
|
| 307 |
+
def create_ui():
|
|
|
|
|
|
|
| 308 |
with gr.Blocks(
|
| 309 |
+
title="SEA Prep Pro - Free AI Tutor 🇹🇹",
|
| 310 |
+
theme=gr.themes.Soft(primary_hue="blue", secondary_hue="purple")
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 311 |
) as demo:
|
| 312 |
|
| 313 |
# Header
|
| 314 |
gr.Markdown("""
|
| 315 |
+
# 🤖 SEA Prep Pro - Free AI Tutor 🇹🇹
|
| 316 |
+
### 100% Free • No API Keys • Trinidad & Tobago SEA Exam Preparation
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 317 |
""")
|
| 318 |
|
|
|
|
| 319 |
with gr.Tabs():
|
|
|
|
| 320 |
with gr.TabItem("🎯 Practice"):
|
| 321 |
with gr.Row():
|
| 322 |
with gr.Column(scale=1):
|
|
|
|
| 323 |
subject = gr.Dropdown(
|
| 324 |
+
choices=["Mathematics", "English"],
|
| 325 |
+
value="Mathematics",
|
| 326 |
+
label="Subject"
|
| 327 |
)
|
| 328 |
topic = gr.Dropdown(
|
| 329 |
+
choices=tutor.subjects["Mathematics"]["topics"],
|
| 330 |
+
value="Fractions",
|
| 331 |
+
label="Topic"
|
| 332 |
)
|
| 333 |
difficulty = gr.Slider(
|
| 334 |
+
1, 5, value=3, step=1,
|
| 335 |
label="Difficulty Level"
|
| 336 |
)
|
| 337 |
|
| 338 |
+
generate_btn = gr.Button("✨ Generate Question", variant="primary")
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 339 |
|
| 340 |
gr.Markdown("---")
|
| 341 |
+
stats = gr.JSON(label="📊 Your Stats")
|
| 342 |
+
refresh_btn = gr.Button("🔄 Refresh Stats")
|
| 343 |
+
|
|
|
|
| 344 |
with gr.Column(scale=2):
|
| 345 |
+
question_display = gr.Markdown("### Your question will appear here")
|
| 346 |
+
answer_input = gr.Textbox(label="Your Answer", lines=3)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 347 |
|
| 348 |
with gr.Row():
|
| 349 |
submit_btn = gr.Button("✅ Submit Answer", variant="primary")
|
| 350 |
+
clear_btn = gr.Button("🗑️ Clear")
|
| 351 |
|
| 352 |
+
feedback = gr.Markdown("### 💬 Feedback")
|
|
|
|
|
|
|
|
|
|
|
|
|
| 353 |
|
|
|
|
| 354 |
with gr.TabItem("📈 Progress"):
|
| 355 |
+
gr.Markdown("### Your Learning Journey")
|
| 356 |
+
progress_stats = gr.JSON()
|
| 357 |
+
gr.Markdown("### 🎯 Tips for Success")
|
| 358 |
+
gr.Markdown("""
|
| 359 |
+
- Practice regularly
|
| 360 |
+
- Review your mistakes
|
| 361 |
+
- Ask for help when needed
|
| 362 |
+
- Stay consistent
|
| 363 |
+
""")
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 364 |
|
|
|
|
| 365 |
with gr.TabItem("ℹ️ About"):
|
| 366 |
gr.Markdown("""
|
| 367 |
## About SEA Prep Pro
|
| 368 |
|
| 369 |
+
**SEA Prep Pro** is a completely free AI-powered tutoring system for Trinidad and Tobago students.
|
|
|
|
| 370 |
|
| 371 |
+
### Features:
|
| 372 |
+
- ✅ 100% Free - No payments ever
|
| 373 |
+
- 🔒 No API Keys Required
|
| 374 |
+
- 🤖 Smart Question Generation
|
| 375 |
+
- 📊 Progress Tracking
|
| 376 |
+
- 🇹🇹 Trinidad & Tobago Focus
|
| 377 |
|
| 378 |
+
### Subjects:
|
| 379 |
+
- **Mathematics**: Fractions, Geometry, Algebra
|
| 380 |
+
- **English**: Grammar, Vocabulary, Comprehension
|
| 381 |
|
| 382 |
+
### How to Use:
|
| 383 |
1. Select a subject and topic
|
| 384 |
+
2. Choose difficulty level
|
| 385 |
+
3. Generate questions
|
| 386 |
+
4. Submit answers
|
| 387 |
+
5. Track your progress
|
|
|
|
|
|
|
|
|
|
|
|
|
| 388 |
|
| 389 |
+
**Made with ❤️ for Trinidad and Tobago students**
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 390 |
""")
|
| 391 |
|
| 392 |
+
# State
|
| 393 |
+
current_q = gr.State()
|
| 394 |
|
| 395 |
+
# Functions
|
| 396 |
+
def update_topics(subject):
|
| 397 |
+
topics = tutor.subjects.get(subject, {}).get("topics", [])
|
| 398 |
+
return gr.Dropdown.update(choices=topics, value=topics[0] if topics else "")
|
| 399 |
+
|
| 400 |
+
def generate(subject, topic, difficulty):
|
| 401 |
+
q = tutor.generate_question(subject, topic, difficulty)
|
| 402 |
display = f"""
|
| 403 |
+
**Subject:** {q['subject']}
|
| 404 |
+
**Topic:** {q['topic']}
|
| 405 |
+
**Difficulty:** {q['difficulty']}
|
| 406 |
|
| 407 |
---
|
| 408 |
|
| 409 |
**Question:**
|
| 410 |
+
{q['text']}
|
| 411 |
"""
|
| 412 |
+
return display, q, ""
|
|
|
|
| 413 |
|
| 414 |
+
def submit(answer, question):
|
| 415 |
+
if not question:
|
| 416 |
+
return "Please generate a question first!", question
|
|
|
|
|
|
|
|
|
|
| 417 |
|
| 418 |
+
result = tutor.submit_answer(question["id"], answer)
|
| 419 |
+
return f"**{result['feedback']}** \n**Score:** {result['score']:.1%}", question
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 420 |
|
|
|
|
| 421 |
def get_stats_func():
|
| 422 |
+
return tutor.get_stats()
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 423 |
|
| 424 |
+
def clear():
|
|
|
|
| 425 |
return "", ""
|
| 426 |
|
| 427 |
# Event handlers
|
| 428 |
+
subject.change(update_topics, subject, topic)
|
| 429 |
+
generate_btn.click(generate, [subject, topic, difficulty], [question_display, current_q, feedback])
|
| 430 |
+
submit_btn.click(submit, [answer_input, current_q], [feedback, current_q])
|
| 431 |
+
refresh_btn.click(get_stats_func, outputs=stats)
|
| 432 |
+
clear_btn.click(clear, outputs=[answer_input, feedback])
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 433 |
|
| 434 |
# Initialize
|
| 435 |
+
demo.load(get_stats_func, outputs=stats)
|
|
|
|
|
|
|
|
|
|
| 436 |
|
| 437 |
return demo
|
| 438 |
|
| 439 |
+
# Launch
|
| 440 |
if __name__ == "__main__":
|
| 441 |
+
app = create_ui()
|
| 442 |
+
app.launch(debug=True)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|