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"""
{timer_text}
Question {index + 1} of {total_questions} | Score: {score}/{index if index > 0 else 0}
{bar}

{q['question']}

""" 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"""
{emoji}

{time_msg}Quiz Complete!

{score}/{total_questions}
{percentage:.1f}%
Grade: {grade}

{message}

Questions answered: {answered}/{total_questions}
""" 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("""

Student Facilitator

Your AI-powered academic companion

""") 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("""
🔐 Secure • 📊 Analytics • 🎓 Free for Students
""") 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("""

🎓 Student Facilitator

Essay • PDF • Quiz • Translate • Dashboard
""") 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"""
🤖 {gemini_status} 🌐 {groq_status}
""") 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(""" """) 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)