Spaces:
Sleeping
Sleeping
| 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_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) |