AbdulWahab14's picture
Update app.py
ba86c5c verified
import gradio as gr
from transformers import pipeline
import PyPDF2
import re
import os
import io
import random
import time
import hashlib
import secrets
import sqlite3
import tempfile
from datetime import datetime, timedelta
from pathlib import Path
from groq import Groq
# Try to import audio libraries (optional)
try:
import speech_recognition as sr
SPEECH_AVAILABLE = True
except ImportError:
SPEECH_AVAILABLE = False
try:
from gtts import gTTS
TTS_AVAILABLE = True
except ImportError:
TTS_AVAILABLE = False
# ==================== CONFIGURATION ====================
try:
from google import genai
GENAI_AVAILABLE = True
except ImportError:
GENAI_AVAILABLE = False
genai = None
# Environment variables
hf_token = os.environ.get("HF_TOKEN")
gemini_key = os.environ.get("GEMINI_API_KEY")
groq_key = os.environ.get("GROQ_API_KEY")
dev_password = os.environ.get("DEV_PASSWORD", "dev123")
# Database path
DATA_DIR = Path("/tmp/data") if os.path.exists("/tmp") else Path("./data")
DATA_DIR.mkdir(exist_ok=True)
DB_PATH = DATA_DIR / "student_facilitator.db"
# API Clients
gemini_client = None
groq_client = None
if gemini_key and GENAI_AVAILABLE:
try:
gemini_client = genai.Client(api_key=gemini_key)
except:
try:
import google.generativeai as old_genai
old_genai.configure(api_key=gemini_key)
gemini_client = old_genai
except:
pass
if groq_key:
try:
groq_client = Groq(api_key=groq_key)
except:
pass
# Lazy load summarizer
summarizer = None
def load_summarizer():
global summarizer
if summarizer is None:
try:
summarizer = pipeline("summarization", model="sshleifer/distilbart-cnn-12-6", device=-1)
except:
pass
return summarizer
# ==================== DATABASE MANAGER ====================
class DatabaseManager:
def __init__(self, db_path):
self.db_path = db_path
self.init_database()
def get_connection(self):
conn = sqlite3.connect(self.db_path)
conn.row_factory = sqlite3.Row
return conn
def init_database(self):
conn = self.get_connection()
cursor = conn.cursor()
cursor.execute("PRAGMA foreign_keys = ON")
cursor.execute("""
CREATE TABLE IF NOT EXISTS users (
id INTEGER PRIMARY KEY AUTOINCREMENT,
username TEXT UNIQUE NOT NULL,
password_hash TEXT NOT NULL,
name TEXT NOT NULL,
email TEXT UNIQUE NOT NULL,
role TEXT DEFAULT 'student',
created_at TEXT DEFAULT (datetime('now')),
last_login TEXT,
is_active INTEGER DEFAULT 1,
is_dev INTEGER DEFAULT 0
)
""")
cursor.execute("""
CREATE TABLE IF NOT EXISTS sessions (
id INTEGER PRIMARY KEY AUTOINCREMENT,
user_id INTEGER NOT NULL,
session_token TEXT UNIQUE NOT NULL,
created_at TEXT DEFAULT (datetime('now')),
expires_at TEXT NOT NULL,
is_active INTEGER DEFAULT 1,
FOREIGN KEY (user_id) REFERENCES users(id) ON DELETE CASCADE
)
""")
cursor.execute("""
CREATE TABLE IF NOT EXISTS quiz_records (
id INTEGER PRIMARY KEY AUTOINCREMENT,
user_id INTEGER NOT NULL,
score INTEGER NOT NULL,
total_questions INTEGER NOT NULL,
percentage REAL NOT NULL,
material_summary TEXT,
timestamp TEXT DEFAULT (datetime('now')),
FOREIGN KEY (user_id) REFERENCES users(id) ON DELETE CASCADE
)
""")
cursor.execute("""
CREATE TABLE IF NOT EXISTS activity_logs (
id INTEGER PRIMARY KEY AUTOINCREMENT,
user_id INTEGER,
action TEXT NOT NULL,
tool_used TEXT,
timestamp TEXT DEFAULT (datetime('now')),
FOREIGN KEY (user_id) REFERENCES users(id) ON DELETE SET NULL
)
""")
conn.commit()
conn.close()
print(f"Database initialized at {self.db_path}")
def create_user(self, username, password_hash, name, email, is_dev=False):
try:
conn = self.get_connection()
cursor = conn.cursor()
cursor.execute("""
INSERT INTO users (username, password_hash, name, email, is_dev)
VALUES (?, ?, ?, ?, ?)
""", (username, password_hash, name, email, is_dev))
user_id = cursor.lastrowid
conn.commit()
conn.close()
return True, user_id
except sqlite3.IntegrityError as e:
if "username" in str(e).lower():
return False, "Username already exists"
elif "email" in str(e).lower():
return False, "Email already registered"
return False, str(e)
except Exception as e:
return False, str(e)
def get_user_by_username(self, username):
conn = self.get_connection()
cursor = conn.cursor()
cursor.execute("SELECT * FROM users WHERE username = ? AND is_active = 1", (username,))
user = cursor.fetchone()
conn.close()
return dict(user) if user else None
def update_last_login(self, user_id):
conn = self.get_connection()
cursor = conn.cursor()
cursor.execute("UPDATE users SET last_login = datetime('now') WHERE id = ?", (user_id,))
conn.commit()
conn.close()
def create_session(self, user_id, session_token, expires_at):
conn = self.get_connection()
cursor = conn.cursor()
cursor.execute("UPDATE sessions SET is_active = 0 WHERE user_id = ?", (user_id,))
cursor.execute("""
INSERT INTO sessions (user_id, session_token, expires_at)
VALUES (?, ?, ?)
""", (user_id, session_token, expires_at))
conn.commit()
conn.close()
return True
def get_session(self, session_token):
conn = self.get_connection()
cursor = conn.cursor()
cursor.execute("""
SELECT s.*, u.username, u.name, u.role, u.is_dev, u.id as user_id
FROM sessions s
JOIN users u ON s.user_id = u.id
WHERE s.session_token = ? AND s.is_active = 1
AND datetime(s.expires_at) > datetime('now')
AND u.is_active = 1
""", (session_token,))
session = cursor.fetchone()
conn.close()
return dict(session) if session else None
def invalidate_session(self, session_token):
conn = self.get_connection()
cursor = conn.cursor()
cursor.execute("UPDATE sessions SET is_active = 0 WHERE session_token = ?", (session_token,))
conn.commit()
conn.close()
return True
def save_quiz_record(self, user_id, score, total, percentage, material_summary=None):
conn = self.get_connection()
cursor = conn.cursor()
cursor.execute("""
INSERT INTO quiz_records (user_id, score, total_questions, percentage, material_summary)
VALUES (?, ?, ?, ?, ?)
""", (user_id, score, total, percentage, material_summary))
conn.commit()
conn.close()
return True
def get_user_quiz_stats(self, user_id):
conn = self.get_connection()
cursor = conn.cursor()
cursor.execute("""
SELECT COUNT(*) as total_quizzes,
AVG(percentage) as avg_score,
MAX(percentage) as best_score,
MIN(percentage) as worst_score
FROM quiz_records WHERE user_id = ?
""", (user_id,))
stats = cursor.fetchone()
cursor.execute("""
SELECT score, total_questions, percentage, timestamp
FROM quiz_records WHERE user_id = ? ORDER BY timestamp DESC LIMIT 10
""", (user_id,))
history = [dict(row) for row in cursor.fetchall()]
conn.close()
return dict(stats) if stats else None, history
def get_database_stats(self):
conn = self.get_connection()
cursor = conn.cursor()
stats = {}
cursor.execute("SELECT COUNT(*) FROM users WHERE is_dev = 0")
stats['total_users'] = cursor.fetchone()[0]
cursor.execute("SELECT COUNT(*) FROM sessions WHERE is_active = 1")
stats['active_sessions'] = cursor.fetchone()[0]
cursor.execute("SELECT COUNT(*) FROM quiz_records")
stats['total_quizzes'] = cursor.fetchone()[0]
conn.close()
return stats
db = DatabaseManager(DB_PATH)
# ==================== AUTHENTICATION ====================
class UserAuth:
def __init__(self, database):
self.db = database
self.failed_attempts = {}
self._init_dev_account()
def _hash(self, password):
return hashlib.sha256(password.encode()).hexdigest()
def _init_dev_account(self):
existing = self.db.get_user_by_username("admin")
if not existing and dev_password:
self.db.create_user(
username="admin",
password_hash=self._hash(dev_password),
name="Developer",
email="dev@studentfacilitator.local",
is_dev=True
)
print("Developer account created")
def validate_email(self, email):
if not email or len(email) < 12:
return False, "Email must be at least 12 characters"
if "@" not in email:
return False, "Email must contain @"
if "." not in email.split("@")[-1]:
return False, "Email must have valid domain"
if email.count("@") != 1:
return False, "Email must contain exactly one @"
local, domain = email.split("@")
if len(local) < 1 or len(domain) < 3:
return False, "Invalid email format"
return True, "Valid"
def validate_username(self, username):
if not username or len(username) < 3:
return False, "Username must be at least 3 characters"
if not username.isalnum():
return False, "Username must be alphanumeric only"
return True, "Valid"
def register(self, username, password, name, email):
valid, msg = self.validate_username(username)
if not valid:
return False, msg
valid, msg = self.validate_email(email)
if not valid:
return False, msg
if not password or len(password) < 6:
return False, "Password must be at least 6 characters"
if not name or len(name.strip()) < 2:
return False, "Please enter your full name"
password_hash = self._hash(password)
success, result = self.db.create_user(username, password_hash, name, email)
if success:
return True, "Account created successfully!"
else:
return False, result
def login(self, username, password):
if not username or not password:
return None, "Please enter both username and password"
if username in self.failed_attempts:
if self.failed_attempts[username]["count"] >= 5:
last = self.failed_attempts[username]["last_attempt"]
if datetime.now() - last < timedelta(minutes=15):
return None, "Too many failed attempts. Try again in 15 minutes."
else:
del self.failed_attempts[username]
user = self.db.get_user_by_username(username)
if not user:
self._record_failed_attempt(username)
return None, "Invalid username or password"
if user['password_hash'] != self._hash(password):
self._record_failed_attempt(username)
return None, "Invalid username or password"
self.db.update_last_login(user['id'])
if username in self.failed_attempts:
del self.failed_attempts[username]
session_token = secrets.token_urlsafe(32)
expires_at = datetime.now() + timedelta(hours=24)
self.db.create_session(user['id'], session_token, expires_at)
return session_token, {
'user_id': user['id'],
'username': user['username'],
'name': user['name'],
'role': user['role'],
'is_dev': user['is_dev']
}
def _record_failed_attempt(self, username):
if username not in self.failed_attempts:
self.failed_attempts[username] = {"count": 0, "last_attempt": datetime.now()}
self.failed_attempts[username]["count"] += 1
self.failed_attempts[username]["last_attempt"] = datetime.now()
def validate_session(self, session_token):
if not session_token:
return None
return self.db.get_session(session_token)
def logout(self, session_token):
if session_token:
self.db.invalidate_session(session_token)
return True
auth_system = UserAuth(db)
# ==================== AUDIO FUNCTIONS ====================
def speech_to_text(audio_file):
if not SPEECH_AVAILABLE:
return "Speech recognition not available"
if not audio_file:
return "No audio recorded"
try:
recognizer = sr.Recognizer()
with sr.AudioFile(audio_file) as source:
audio = recognizer.record(source)
text = recognizer.recognize_google(audio)
return text
except sr.UnknownValueError:
return "Could not understand audio"
except sr.RequestError:
return "Speech recognition service unavailable"
except Exception as e:
return f"Error: {str(e)}"
def process_audio_input(audio_file, current_text):
if not audio_file:
return current_text
text = speech_to_text(audio_file)
if text.startswith("Error:") or text.startswith("Speech") or text.startswith("No audio") or text.startswith("Could not"):
return current_text
return current_text + " " + text if current_text else text
def text_to_speech(text, lang='en'):
if not TTS_AVAILABLE:
return None, "Text-to-speech not available"
if not text:
return None, "No text to speak"
try:
tts = gTTS(text=text, lang=lang, slow=False)
with tempfile.NamedTemporaryFile(delete=False, suffix=".mp3") as f:
tts.save(f.name)
return f.name, "Audio generated"
except Exception as e:
return None, f"TTS error: {str(e)}"
def speak_text(text, lang='en'):
if not text:
return None
audio_file, error = text_to_speech(text, lang)
return audio_file
# ==================== CORE FUNCTIONS ====================
def extract_text_from_pdf(pdf_file):
if pdf_file is None:
return None, "Please upload a PDF file."
try:
if isinstance(pdf_file, str):
with open(pdf_file, 'rb') as f:
pdf_reader = PyPDF2.PdfReader(f)
text = ""
for page in pdf_reader.pages:
page_text = page.extract_text()
if page_text:
text += page_text + "\n"
else:
if hasattr(pdf_file, 'read'):
pdf_bytes = pdf_file.read()
if hasattr(pdf_file, 'seek'):
pdf_file.seek(0)
else:
pdf_bytes = pdf_file
if isinstance(pdf_bytes, bytes):
pdf_stream = io.BytesIO(pdf_bytes)
else:
pdf_stream = io.BytesIO(pdf_bytes.encode() if isinstance(pdf_bytes, str) else pdf_bytes)
pdf_reader = PyPDF2.PdfReader(pdf_stream)
text = ""
for page in pdf_reader.pages:
page_text = page.extract_text()
if page_text:
text += page_text + "\n"
text = re.sub(r'\s+', ' ', text).strip()
if len(text) < 50:
return None, "Could not extract text. PDF may be image-based or scanned."
return text, None
except Exception as e:
return None, f"Error reading PDF: {str(e)}"
def summarize_with_gemini(text, max_length, min_length):
if not gemini_client:
return None
try:
if hasattr(gemini_client, 'models'):
prompt = f"Summarize the following text in {min_length}-{max_length} words:\n\n{text[:15000]}"
try:
response = gemini_client.models.generate_content(
model="gemini-2.5-flash",
contents=prompt
)
return response.text
except:
if hasattr(gemini_client, 'GenerativeModel'):
model = gemini_client.GenerativeModel('gemini-2.5-flash')
response = model.generate_content(prompt)
return response.text
elif hasattr(gemini_client, 'GenerativeModel'):
model = gemini_client.GenerativeModel('gemini-2.5-flash')
prompt = f"Summarize the following text in {min_length}-{max_length} words:\n\n{text[:15000]}"
response = model.generate_content(prompt)
return response.text
except Exception as e:
print(f"Gemini error: {e}")
return None
def summarize_pdf(pdf_file, max_length, min_length):
text, error = extract_text_from_pdf(pdf_file)
if error:
return error
gemini_result = summarize_with_gemini(text, max_length, min_length)
if gemini_result:
return gemini_result
summ = load_summarizer()
if summ:
try:
result = summ(text[:3500], max_length=max_length, min_length=min_length, do_sample=False)
return result[0]['summary_text']
except Exception as e:
return f"Error: {str(e)}"
return "Error: No summarization available"
def generate_essay_with_gemini(prompt, essay_type, word_count, tone):
if not gemini_client:
return None
try:
full_prompt = f"""Write a {essay_type} essay in {tone} tone (~{word_count} words).
Topic: {prompt}
Requirements: Engaging intro, structured body, strong conclusion."""
if hasattr(gemini_client, 'models'):
response = gemini_client.models.generate_content(
model="gemini-2.5-flash",
contents=full_prompt
)
essay = response.text.strip()
else:
model = gemini_client.GenerativeModel('gemini-2.5-flash')
response = model.generate_content(full_prompt)
essay = response.text.strip()
word_count_actual = len(essay.split())
return f"""# {essay_type} Essay: {prompt[:50]}{'...' if len(prompt) > 50 else ''}
{essay}
---
*~{word_count_actual} words | {tone} tone*"""
except Exception as e:
print(f"Essay error: {e}")
return None
def generate_essay(prompt, essay_type, word_count, tone):
if not prompt or len(prompt.strip()) < 10:
return "Please provide a detailed prompt (at least 10 characters)."
result = generate_essay_with_gemini(prompt, essay_type, word_count, tone)
if result:
return result
return "❌ Essay generation failed. Please check Gemini API configuration."
def summarize_text(text, max_length, min_length):
if len(text.strip()) < 100:
return "Please provide at least 100 characters."
gemini_result = summarize_with_gemini(text, max_length, min_length)
if gemini_result:
return gemini_result
summ = load_summarizer()
if summ:
try:
result = summ(text[:3500], max_length=max_length, min_length=min_length, do_sample=False)
return result[0]['summary_text']
except Exception as e:
return f"Error: {str(e)}"
return "Error: No summarization available"
# ==================== QUIZ FUNCTIONS - FIXED ====================
def extract_sentences(text):
if not text or len(text.strip()) < 50:
return []
text = re.sub(r'\s+', ' ', text.strip())
sentences = re.split(r'[.!?]+', text)
valid_sentences = []
for s in sentences:
s = s.strip()
words = s.split()
if 8 <= len(words) <= 20:
if s and s[0].isupper():
valid_sentences.append(s)
return valid_sentences[:20]
def create_quiz(text, num_questions):
sentences = extract_sentences(text)
if len(sentences) < num_questions:
num_questions = max(1, len(sentences))
if num_questions == 0:
return []
selected = random.sample(sentences, num_questions)
quiz_data = []
for sentence in selected:
words = sentence.split()
if len(words) < 5:
continue
candidates = [w for w in words[2:-2] if len(w) > 3 and w.isalpha()]
if not candidates:
candidates = [w for w in words[2:-2] if len(w) > 2]
if not candidates:
continue
keyword = random.choice(candidates)
question = sentence.replace(keyword, "________", 1)
all_words = list(set([w for w in text.split() if len(w) > 3 and w.isalpha() and w.lower() != keyword.lower()]))
if len(all_words) < 3:
wrong = ["alternative", "option", "choice", "selection"][:3]
else:
wrong = random.sample(all_words, min(3, len(all_words)))
wrong = [w for w in wrong if w.lower() != keyword.lower()]
while len(wrong) < 3:
wrong.append(f"option_{len(wrong)+1}")
options = wrong[:3] + [keyword]
random.shuffle(options)
quiz_data.append({
"question": question,
"options": options,
"answer": keyword,
"full_sentence": sentence
})
return quiz_data
def start_quiz(text, num_questions, timer_minutes):
if not text or not text.strip():
return (
"⚠️ Please enter study material first!",
gr.update(choices=[], visible=False),
"",
None, 0, 0, None, "⏳ --:--", gr.update(visible=False), ""
)
if len(text.strip()) < 100:
return (
"⚠️ Please provide at least 100 characters!",
gr.update(choices=[], visible=False),
"",
None, 0, 0, None, "⏳ --:--", gr.update(visible=False), ""
)
quiz = create_quiz(text, num_questions)
if not quiz or len(quiz) == 0:
return (
"⚠️ Could not generate quiz. Add more detailed material!",
gr.update(choices=[], visible=False),
"",
None, 0, 0, None, "⏳ --:--", gr.update(visible=False), ""
)
actual_questions = len(quiz)
end_time = time.time() + (timer_minutes * 60)
material_summary = text[:300] + "..." if len(text) > 300 else text
return show_question(quiz, 0, 0, end_time, actual_questions, material_summary)
def show_question(quiz, index, score, end_time, total_questions, material_summary):
remaining = int(end_time - time.time())
if remaining <= 0:
return finish_quiz(quiz, score, index, total_questions, material_summary, time_up=True)
if index >= len(quiz):
return finish_quiz(quiz, score, len(quiz), total_questions, material_summary)
q = quiz[index]
mins = remaining // 60
secs = remaining % 60
timer_text = f"⏳ {mins:02d}:{secs:02d}"
progress = (index / total_questions) * 20
bar = "█" * int(progress) + "░" * (20 - int(progress))
question_html = f"""
<div style="background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
color: white; padding: 2rem; border-radius: 20px; text-align: center;">
<div style="font-size: 3rem; font-weight: bold; margin-bottom: 1rem;">{timer_text}</div>
<div style="background: rgba(255,255,255,0.2); padding: 0.5rem; border-radius: 10px; margin-bottom: 1rem;">
Question {index + 1} of {total_questions} | Score: {score}/{index if index > 0 else 0}
</div>
<div style="font-family: monospace; margin-bottom: 1.5rem;">{bar}</div>
<h3 style="font-size: 1.3rem; line-height: 1.6; margin-bottom: 2rem;">{q['question']}</h3>
</div>
"""
return (
question_html,
gr.update(choices=q['options'], value=None, visible=True),
f"Score: {score}/{index if index > 0 else 0}",
quiz, index, score, end_time, timer_text,
gr.update(visible=True), material_summary
)
def submit_answer(selected, quiz, index, score, end_time, total_questions, material_summary, session_token):
if not selected:
return show_question(quiz, index, score, end_time, total_questions, material_summary)
correct_answer = quiz[index]['answer']
is_correct = selected == correct_answer
new_score = score + (1 if is_correct else 0)
new_index = index + 1
if new_index >= len(quiz):
percentage = (new_score / total_questions * 100) if total_questions > 0 else 0
if session_token:
session = auth_system.validate_session(session_token)
if session:
db.save_quiz_record(session['user_id'], new_score, total_questions, percentage, material_summary)
return finish_quiz(quiz, new_score, len(quiz), total_questions, material_summary)
return show_question(quiz, new_index, new_score, end_time, total_questions, material_summary)
def finish_quiz(quiz, score, answered, total_questions, material_summary, time_up=False):
percentage = (score / total_questions * 100) if total_questions > 0 else 0
if percentage >= 90:
grade, emoji, message = "A", "🏆", "Outstanding! Excellent mastery!"
elif percentage >= 80:
grade, emoji, message = "B", "🌟", "Great job! Very good understanding!"
elif percentage >= 70:
grade, emoji, message = "C", "👍", "Good work! Keep practicing!"
elif percentage >= 60:
grade, emoji, message = "D", "📚", "Passing, but more study needed!"
else:
grade, emoji, message = "F", "💪", "Keep trying! Review the material!"
time_msg = "⏰ Time's up! " if time_up else ""
result_html = f"""
<div style="background: linear-gradient(135deg, #11998e 0%, #38ef7d 100%);
color: white; padding: 2.5rem; border-radius: 25px; text-align: center;
box-shadow: 0 20px 60px rgba(0,0,0,0.3);">
<div style="font-size: 4rem; margin-bottom: 1rem;">{emoji}</div>
<h2 style="font-size: 2rem; margin-bottom: 1rem;">{time_msg}Quiz Complete!</h2>
<div style="background: rgba(255,255,255,0.2); padding: 1.5rem; border-radius: 15px; margin: 1.5rem 0;">
<div style="font-size: 3rem; font-weight: bold;">{score}/{total_questions}</div>
<div style="font-size: 1.5rem;">{percentage:.1f}%</div>
<div style="font-size: 2rem; margin-top: 0.5rem;">Grade: {grade}</div>
</div>
<p style="font-size: 1.2rem; margin-bottom: 1rem;">{message}</p>
<div style="font-size: 0.9rem; opacity: 0.9;">Questions answered: {answered}/{total_questions}</div>
</div>
"""
return (
result_html, gr.update(choices=[], visible=False),
f"Final Score: {score}/{total_questions}", quiz, total_questions, score, None,
"⏰ Finished", gr.update(visible=False), material_summary
)
def translate_to_urdu(text):
if not text or not text.strip():
return "Please enter some text to translate."
if not groq_client:
return "❌ Groq API not configured."
try:
chat_completion = groq_client.chat.completions.create(
messages=[
{
"role": "system",
"content": "You are a professional English to Urdu translator. Translate accurately to Urdu (اردو) using natural language. Respond ONLY with the translation."
},
{
"role": "user",
"content": f"Translate to Urdu:\n\n{text}"
}
],
model="llama-3.3-70b-versatile",
temperature=0.3,
max_completion_tokens=2048,
)
return chat_completion.choices[0].message.content
except Exception as e:
return f"Error: {str(e)}"
# ==================== DASHBOARD ====================
def generate_ascii_chart(data, title, width=40):
if not data:
return "No data available"
max_val = max(data.values()) if data else 1
lines = [f"\n {title}", " " + "=" * width]
for label, value in data.items():
bar_len = int((value / max_val) * (width - 10))
bar = "█" * bar_len
lines.append(f" {label:8}{bar:<30} {value}")
lines.append(" " + "=" * width)
return "\n".join(lines)
def get_user_dashboard_data(session_token):
session = auth_system.validate_session(session_token)
if not session:
return "Please login to view dashboard", "", ""
user_id = session['user_id']
name = session['name']
stats, history = db.get_user_quiz_stats(user_id)
if not stats or stats['total_quizzes'] == 0:
return f"## 📊 {name}'s Dashboard\n\nNo quiz records yet. Take a quiz to see your progress!", "", ""
progress_data = {}
for i, record in enumerate(reversed(history[-7:])):
date = record['timestamp'][:10]
progress_data[date] = record['percentage']
progress_chart = generate_ascii_chart(progress_data, "Quiz Performance Over Time")
ranges = {"0-40%": 0, "41-60%": 0, "61-80%": 0, "81-100%": 0}
for record in history:
p = record['percentage']
if p <= 40: ranges["0-40%"] += 1
elif p <= 60: ranges["41-60%"] += 1
elif p <= 80: ranges["61-80%"] += 1
else: ranges["81-100%"] += 1
distribution_chart = generate_ascii_chart(ranges, "Score Distribution")
summary = f"""
## 📊 {name}'s Learning Dashboard
### 🎯 Overall Statistics
• **Total Quizzes Taken:** {stats['total_quizzes']}
• **Average Score:** {stats['avg_score']:.1f}%
• **Best Score:** {stats['best_score']:.1f}%
• **Improvement Needed:** {stats['worst_score']:.1f}%
### 📈 Recent Activity
"""
for record in history[:5]:
date = record['timestamp'][:10]
score_emoji = "🌟" if record['percentage'] >= 80 else "👍" if record['percentage'] >= 60 else "📚"
summary += f"\n{score_emoji} **{date}:** {record['score']}/{record['total_questions']} ({record['percentage']:.1f}%)"
return summary, progress_chart, distribution_chart
# ==================== CUSTOM CSS ====================
custom_css = """
@import url('https://fonts.googleapis.com/css2?family=Inter:wght@300;400;500;600;700;800&family=Noto+Nastaliq+Urdu&display=swap');
:root {
--primary: #6366f1;
--primary-dark: #4f46e5;
--secondary: #8b5cf6;
--success: #10b981;
--warning: #f59e0b;
--danger: #ef4444;
--dark: #0f172a;
--light: #f8fafc;
}
body {
font-family: 'Inter', sans-serif !important;
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%) !important;
min-height: 100vh;
}
.auth-container {
max-width: 450px;
margin: 3rem auto;
padding: 2.5rem;
background: rgba(255, 255, 255, 0.95);
border-radius: 24px;
box-shadow: 0 25px 80px rgba(0,0,0,0.15);
text-align: center;
}
.auth-logo {
width: 80px;
height: 80px;
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
border-radius: 20px;
display: flex;
align-items: center;
justify-content: center;
margin: 0 auto 1.5rem;
font-size: 2.5rem;
color: white;
}
.auth-title {
font-size: 1.875rem;
font-weight: 800;
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
-webkit-background-clip: text;
-webkit-text-fill-color: transparent;
margin-bottom: 0.5rem;
}
.app-header {
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
color: white;
padding: 2rem;
border-radius: 0 0 30px 30px;
margin: -20px -20px 2rem -20px;
}
.app-title {
font-size: 2rem;
font-weight: 700;
margin: 0;
}
.user-widget {
position: absolute;
top: 1.5rem;
right: 2rem;
background: rgba(255,255,255,0.2);
padding: 0.5rem 1rem;
border-radius: 50px;
display: flex;
align-items: center;
gap: 0.75rem;
font-size: 0.875rem;
}
.status-pill {
padding: 0.5rem 1rem;
border-radius: 50px;
font-size: 0.8rem;
font-weight: 600;
}
.status-ok { background: #d1fae5; color: #065f46; }
.status-error { background: #fee2e2; color: #991b1b; }
.tool-card {
background: white;
border-radius: 20px;
padding: 1.5rem;
box-shadow: 0 4px 20px rgba(0,0,0,0.08);
border: 1px solid #f1f5f9;
}
.quiz-container {
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
color: white;
padding: 2rem;
border-radius: 20px;
text-align: center;
box-shadow: 0 20px 60px rgba(102, 126, 234, 0.3);
}
.quiz-timer {
font-size: 3rem;
font-weight: bold;
font-family: 'Courier New', monospace;
background: rgba(255,255,255,0.2);
padding: 1rem;
border-radius: 16px;
margin-bottom: 1.5rem;
}
.urdu-text {
font-family: 'Noto Nastaliq Urdu', serif !important;
font-size: 1.5em !important;
line-height: 2 !important;
direction: rtl !important;
text-align: right !important;
background: #f8fafc;
padding: 1.5rem;
border-radius: 16px;
border: 2px solid #e2e8f0;
}
.btn-primary {
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%) !important;
color: white !important;
border: none !important;
border-radius: 12px !important;
padding: 1rem !important;
font-weight: 600 !important;
}
.app-footer {
text-align: center;
padding: 2rem;
color: #64748b;
font-size: 0.875rem;
margin-top: 2rem;
border-top: 1px solid #e2e8f0;
}
"""
# ==================== UI BUILDERS ====================
def build_auth_screen():
with gr.Column(visible=True, elem_classes="auth-container") as auth_screen:
gr.Markdown("""
<div class="auth-logo">🎓</div>
<h1 class="auth-title">Student Facilitator</h1>
<p style="color: #64748b; margin-bottom: 2rem;">Your AI-powered academic companion</p>
""")
with gr.Row():
login_tab = gr.Button("Sign In", variant="primary")
signup_tab = gr.Button("Create Account")
with gr.Column(visible=True) as login_form:
login_username = gr.Textbox(label="Username", placeholder="Enter username")
login_password = gr.Textbox(label="Password", type="password", placeholder="Enter password")
login_btn = gr.Button("Sign In", variant="primary")
login_message = gr.Markdown(visible=False)
with gr.Column(visible=False) as signup_form:
signup_name = gr.Textbox(label="Full Name", placeholder="Your name")
signup_email = gr.Textbox(label="Email", placeholder="your@email.com")
signup_username = gr.Textbox(label="Username", placeholder="Choose username (min 3 chars)")
signup_password = gr.Textbox(label="Password", type="password", placeholder="Min 6 characters")
signup_btn = gr.Button("Create Account", variant="primary")
signup_message = gr.Markdown(visible=False)
gr.Markdown("""
<div style="margin-top: 2rem; padding-top: 1.5rem; border-top: 1px solid #f1f5f9; color: #94a3b8; font-size: 0.875rem;">
🔐 Secure • 📊 Analytics • 🎓 Free for Students
</div>
""")
return (auth_screen, login_tab, signup_tab, login_form, signup_form,
login_username, login_password, login_btn, login_message,
signup_name, signup_email, signup_username, signup_password, signup_btn, signup_message)
def build_main_app():
with gr.Column(visible=False) as main_app:
with gr.Row(elem_classes="app-header"):
gr.Markdown("""
<h1 class="app-title">🎓 Student Facilitator</h1>
<div style="opacity: 0.9; font-size: 1rem; margin-top: 0.5rem;">
Essay • PDF • Quiz • Translate • Dashboard
</div>
""")
with gr.Row(elem_classes="user-widget"):
user_display = gr.Markdown()
logout_btn = gr.Button("Logout", size="sm")
with gr.Row():
gemini_status = "✅ Gemini" if gemini_client else "❌ Gemini"
groq_status = "✅ Groq" if groq_client else "❌ Groq"
gr.Markdown(f"""
<div style="padding: 1rem;">
<span class="status-pill {'status-ok' if gemini_client else 'status-error'}">🤖 {gemini_status}</span>
<span class="status-pill {'status-ok' if groq_client else 'status-error'}">🌐 {groq_status}</span>
</div>
""")
with gr.Tabs():
# DASHBOARD
with gr.TabItem("📊 Dashboard"):
with gr.Row():
with gr.Column():
dashboard_stats = gr.Markdown()
dashboard_chart1 = gr.Markdown()
with gr.Column():
dashboard_chart2 = gr.Markdown()
refresh_dashboard_btn = gr.Button("🔄 Refresh Dashboard", variant="primary")
# ESSAY & PDF
with gr.TabItem("📝 Essay & PDF"):
with gr.Tabs():
with gr.Tab("✍️ Essay Generator"):
with gr.Row():
with gr.Column():
essay_prompt = gr.Textbox(label="Essay Topic", placeholder="e.g., 'Impact of AI on Education'", lines=3)
essay_mic = gr.Audio(label="🎤 Voice Input", sources=["microphone"], type="filepath")
with gr.Row():
essay_type = gr.Dropdown(["Argumentative", "Expository", "Descriptive", "Persuasive"], value="Argumentative", label="Type")
essay_tone = gr.Dropdown(["Academic", "Formal", "Neutral"], value="Academic", label="Tone")
essay_words = gr.Slider(200, 1000, 500, step=50, label="Word Count")
with gr.Row():
essay_btn = gr.Button("✨ Generate Essay", variant="primary")
essay_speak_btn = gr.Button("🔊 Read Essay")
essay_output = gr.Markdown()
essay_audio = gr.Audio(label="Essay Audio")
with gr.Tab("📄 PDF Summarizer"):
with gr.Row():
with gr.Column():
pdf_file = gr.File(label="Upload PDF", file_types=[".pdf"], type="binary")
with gr.Row():
pdf_max = gr.Slider(50, 500, 200, step=10, label="Max Words")
pdf_min = gr.Slider(20, 200, 50, step=10, label="Min Words")
pdf_btn = gr.Button("📝 Summarize PDF", variant="primary")
pdf_text = gr.Textbox(label="Or paste text", lines=4)
pdf_mic = gr.Audio(label="🎤 Voice", sources=["microphone"], type="filepath")
text_btn = gr.Button("Summarize Text")
with gr.Column():
pdf_output = gr.Textbox(label="Summary", lines=12)
pdf_speak_btn = gr.Button("🔊 Read Summary")
pdf_audio = gr.Audio(label="Summary Audio")
# QUIZ - FIXED
with gr.TabItem("🎯 Smart Quiz"):
with gr.Row():
with gr.Column(scale=1):
gr.Markdown("### 📖 Study Material")
quiz_text = gr.Textbox(label="Paste your notes here", placeholder="Example: Photosynthesis is the process by which plants convert light energy...", lines=10)
gr.Markdown("### ⚙️ Settings")
quiz_num = gr.Slider(3, 15, 5, step=1, label="Questions")
quiz_time = gr.Slider(1, 10, 3, step=1, label="Minutes")
quiz_start = gr.Button("🚀 Start Quiz", variant="primary")
with gr.Column(scale=2):
quiz_timer = gr.Markdown("⏳ 03:00", elem_classes="quiz-timer")
quiz_progress = gr.Markdown("Ready to start?")
quiz_question = gr.Markdown("### 🎯 Enter material and click Start!")
quiz_options = gr.Radio(choices=[], label="Select answer:", visible=False)
quiz_submit = gr.Button("✅ Submit Answer", visible=False)
quiz_state = gr.State()
quiz_idx = gr.State(0)
quiz_scr = gr.State(0)
quiz_end = gr.State()
quiz_material = gr.State("")
# TRANSLATOR
with gr.TabItem("🌍 Urdu Translator"):
with gr.Row():
with gr.Column():
trans_input = gr.Textbox(label="English Text", placeholder="Enter text to translate...", lines=6)
trans_mic = gr.Audio(label="🎤 Voice Input", sources=["microphone"], type="filepath")
trans_btn = gr.Button("🔄 Translate", variant="primary")
trans_speak_btn = gr.Button("🔊 Read Urdu")
gr.Examples(examples=["Hello, how are you?", "Pakistan is beautiful", "I love learning"], inputs=trans_input)
with gr.Column():
trans_output = gr.Textbox(label="اردو ترجمہ", lines=6, elem_classes="urdu-text", interactive=False)
trans_audio = gr.Audio(label="Urdu Audio")
gr.Markdown("""
<div class="app-footer">
<p>🎓 Student Facilitator © 2024 | Made with ❤️ for students worldwide</p>
</div>
""")
return (main_app, user_display, logout_btn,
dashboard_stats, dashboard_chart1, dashboard_chart2, refresh_dashboard_btn,
essay_prompt, essay_mic, essay_type, essay_tone, essay_words, essay_btn, essay_output, essay_speak_btn, essay_audio,
pdf_file, pdf_max, pdf_min, pdf_btn, pdf_text, pdf_mic, text_btn, pdf_output, pdf_speak_btn, pdf_audio,
quiz_text, quiz_num, quiz_time, quiz_start, quiz_timer, quiz_progress, quiz_question, quiz_options, quiz_submit,
quiz_state, quiz_idx, quiz_scr, quiz_end, quiz_material,
trans_input, trans_mic, trans_btn, trans_output, trans_speak_btn, trans_audio)
# ==================== MAIN APPLICATION ====================
with gr.Blocks(title="Student Facilitator", css=custom_css) as demo:
session_token_state = gr.State("")
(auth_screen, login_tab, signup_tab, login_form, signup_form,
login_username, login_password, login_btn, login_message,
signup_name, signup_email, signup_username, signup_password, signup_btn, signup_message) = build_auth_screen()
(main_app, user_display, logout_btn,
dashboard_stats, dashboard_chart1, dashboard_chart2, refresh_dashboard_btn,
essay_prompt, essay_mic, essay_type, essay_tone, essay_words, essay_btn, essay_output, essay_speak_btn, essay_audio,
pdf_file, pdf_max, pdf_min, pdf_btn, pdf_text, pdf_mic, text_btn, pdf_output, pdf_speak_btn, pdf_audio,
quiz_text, quiz_num, quiz_time, quiz_start, quiz_timer, quiz_progress, quiz_question, quiz_options, quiz_submit,
quiz_state, quiz_idx, quiz_scr, quiz_end, quiz_material,
trans_input, trans_mic, trans_btn, trans_output, trans_speak_btn, trans_audio) = build_main_app()
# Auth handlers
def toggle_auth_mode(mode):
if mode == "login":
return {login_form: gr.update(visible=True), signup_form: gr.update(visible=False)}
else:
return {login_form: gr.update(visible=False), signup_form: gr.update(visible=True)}
login_tab.click(lambda: toggle_auth_mode("login"), outputs=[login_form, signup_form])
signup_tab.click(lambda: toggle_auth_mode("signup"), outputs=[login_form, signup_form])
def handle_login(username, password):
session_token, user_data = auth_system.login(username, password)
if session_token:
return {
session_token_state: gr.update(value=session_token),
auth_screen: gr.update(visible=False),
main_app: gr.update(visible=True),
user_display: gr.update(value=f"👤 {user_data['name']}"),
login_message: gr.update(visible=False)
}
else:
return {
session_token_state: gr.update(value=""),
auth_screen: gr.update(visible=True),
main_app: gr.update(visible=False),
user_display: gr.update(value=""),
login_message: gr.update(value=f"❌ {user_data}", visible=True)
}
def handle_signup(username, password, name, email):
success, message = auth_system.register(username, password, name, email)
if success:
session_token, user_data = auth_system.login(username, password)
if session_token:
return {
session_token_state: gr.update(value=session_token),
auth_screen: gr.update(visible=False),
main_app: gr.update(visible=True),
user_display: gr.update(value=f"👤 {user_data['name']}"),
signup_message: gr.update(value=f"✅ {message}", visible=True)
}
return {
session_token_state: gr.update(value=""),
auth_screen: gr.update(visible=True),
main_app: gr.update(visible=False),
user_display: gr.update(value=""),
signup_message: gr.update(value=f"❌ {message}", visible=True)
}
def handle_logout():
return {
session_token_state: gr.update(value=""),
auth_screen: gr.update(visible=True),
main_app: gr.update(visible=False),
user_display: gr.update(value=""),
login_username: gr.update(value=""),
login_password: gr.update(value="")
}
login_btn.click(handle_login, inputs=[login_username, login_password],
outputs=[session_token_state, auth_screen, main_app, user_display, login_message])
signup_btn.click(handle_signup, inputs=[signup_username, signup_password, signup_name, signup_email],
outputs=[session_token_state, auth_screen, main_app, user_display, signup_message])
logout_btn.click(handle_logout, outputs=[session_token_state, auth_screen, main_app, user_display, login_username, login_password])
# Dashboard
def update_dashboard(token):
stats, chart1, chart2 = get_user_dashboard_data(token)
return stats, chart1, chart2
refresh_dashboard_btn.click(update_dashboard, inputs=[session_token_state],
outputs=[dashboard_stats, dashboard_chart1, dashboard_chart2])
# Essay
essay_mic.change(process_audio_input, inputs=[essay_mic, essay_prompt], outputs=essay_prompt)
essay_btn.click(generate_essay, inputs=[essay_prompt, essay_type, essay_words, essay_tone], outputs=essay_output)
essay_speak_btn.click(lambda x: speak_text(x, 'en'), inputs=essay_output, outputs=essay_audio)
# PDF
pdf_mic.change(process_audio_input, inputs=[pdf_mic, pdf_text], outputs=pdf_text)
pdf_btn.click(summarize_pdf, inputs=[pdf_file, pdf_max, pdf_min], outputs=pdf_output)
text_btn.click(summarize_text, inputs=[pdf_text, pdf_max, pdf_min], outputs=pdf_output)
pdf_speak_btn.click(lambda x: speak_text(x, 'en'), inputs=pdf_output, outputs=pdf_audio)
# Quiz - FIXED
quiz_start.click(start_quiz,
inputs=[quiz_text, quiz_num, quiz_time],
outputs=[quiz_question, quiz_options, quiz_progress, quiz_state, quiz_idx, quiz_scr, quiz_end, quiz_timer, quiz_submit, quiz_material])
quiz_submit.click(submit_answer,
inputs=[quiz_options, quiz_state, quiz_idx, quiz_scr, quiz_end, quiz_num, quiz_material, session_token_state],
outputs=[quiz_question, quiz_options, quiz_progress, quiz_state, quiz_idx, quiz_scr, quiz_end, quiz_timer, quiz_submit, quiz_material])
# Translator
trans_mic.change(process_audio_input, inputs=[trans_mic, trans_input], outputs=trans_input)
trans_btn.click(translate_to_urdu, inputs=trans_input, outputs=trans_output)
trans_speak_btn.click(lambda x: speak_text(x, 'ur'), inputs=trans_output, outputs=trans_audio)
if __name__ == "__main__":
demo.launch(server_name="0.0.0.0", server_port=7860)