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 json | |
| import sqlite3 | |
| from datetime import datetime, timedelta | |
| from pathlib import Path | |
| from groq import Groq | |
| # ==================== CONFIGURATION ==================== | |
| try: | |
| from google import genai | |
| from google.genai import types | |
| GENAI_AVAILABLE = True | |
| except ImportError: | |
| GENAI_AVAILABLE = False | |
| genai = None | |
| types = 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 (persistent storage) | |
| DATA_DIR = Path("/tmp/data") if os.path.exists("/tmp") else Path("./data") | |
| DATA_DIR.mkdir(exist_ok=True) | |
| DB_PATH = DATA_DIR / "student_ai_suite.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): | |
| """Get database connection with row factory""" | |
| conn = sqlite3.connect(self.db_path) | |
| conn.row_factory = sqlite3.Row | |
| return conn | |
| def init_database(self): | |
| """Initialize database tables""" | |
| conn = self.get_connection() | |
| cursor = conn.cursor() | |
| # Users table | |
| 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, | |
| role TEXT DEFAULT 'user', | |
| created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, | |
| last_login TIMESTAMP, | |
| is_active BOOLEAN DEFAULT 1, | |
| is_dev BOOLEAN DEFAULT 0 | |
| ) | |
| """) | |
| # Sessions table for tracking active sessions | |
| 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 TIMESTAMP DEFAULT CURRENT_TIMESTAMP, | |
| expires_at TIMESTAMP NOT NULL, | |
| ip_address TEXT, | |
| user_agent TEXT, | |
| is_active BOOLEAN DEFAULT 1, | |
| FOREIGN KEY (user_id) REFERENCES users(id) ON DELETE CASCADE | |
| ) | |
| """) | |
| # Activity logs (optional - for analytics) | |
| cursor.execute(""" | |
| CREATE TABLE IF NOT EXISTS activity_logs ( | |
| id INTEGER PRIMARY KEY AUTOINCREMENT, | |
| user_id INTEGER, | |
| action TEXT NOT NULL, | |
| tool_used TEXT, | |
| input_summary TEXT, | |
| output_summary TEXT, | |
| timestamp TIMESTAMP DEFAULT CURRENT_TIMESTAMP, | |
| execution_time_ms INTEGER, | |
| FOREIGN KEY (user_id) REFERENCES users(id) ON DELETE SET NULL | |
| ) | |
| """) | |
| # User preferences | |
| cursor.execute(""" | |
| CREATE TABLE IF NOT EXISTS user_preferences ( | |
| id INTEGER PRIMARY KEY AUTOINCREMENT, | |
| user_id INTEGER UNIQUE NOT NULL, | |
| default_essay_type TEXT DEFAULT 'Argumentative', | |
| default_essay_tone TEXT DEFAULT 'Academic', | |
| default_word_count INTEGER DEFAULT 500, | |
| default_quiz_questions INTEGER DEFAULT 5, | |
| default_quiz_time INTEGER DEFAULT 2, | |
| created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, | |
| updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, | |
| FOREIGN KEY (user_id) REFERENCES users(id) ON DELETE CASCADE | |
| ) | |
| """) | |
| conn.commit() | |
| conn.close() | |
| print(f"✅ Database initialized at {self.db_path}") | |
| # ==================== USER OPERATIONS ==================== | |
| def create_user(self, username, password_hash, name, email, role="user", is_dev=False): | |
| """Create new user""" | |
| try: | |
| conn = self.get_connection() | |
| cursor = conn.cursor() | |
| cursor.execute(""" | |
| INSERT INTO users (username, password_hash, name, email, role, is_dev) | |
| VALUES (?, ?, ?, ?, ?, ?) | |
| """, (username, password_hash, name, email, role, is_dev)) | |
| user_id = cursor.lastrowid | |
| # Create default preferences | |
| cursor.execute(""" | |
| INSERT INTO user_preferences (user_id) VALUES (?) | |
| """, (user_id,)) | |
| conn.commit() | |
| conn.close() | |
| return True, user_id | |
| except sqlite3.IntegrityError: | |
| return False, "Username already exists" | |
| except Exception as e: | |
| return False, str(e) | |
| def get_user_by_username(self, username): | |
| """Get user by 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() | |
| if user: | |
| return dict(user) | |
| return None | |
| def get_user_by_id(self, user_id): | |
| """Get user by ID""" | |
| conn = self.get_connection() | |
| cursor = conn.cursor() | |
| cursor.execute(""" | |
| SELECT * FROM users WHERE id = ? AND is_active = 1 | |
| """, (user_id,)) | |
| user = cursor.fetchone() | |
| conn.close() | |
| if user: | |
| return dict(user) | |
| return None | |
| def update_last_login(self, user_id): | |
| """Update last login timestamp""" | |
| conn = self.get_connection() | |
| cursor = conn.cursor() | |
| cursor.execute(""" | |
| UPDATE users SET last_login = CURRENT_TIMESTAMP WHERE id = ? | |
| """, (user_id,)) | |
| conn.commit() | |
| conn.close() | |
| # ==================== SESSION OPERATIONS ==================== | |
| def create_session(self, user_id, session_token, expires_at, ip_address=None, user_agent=None): | |
| """Create new session""" | |
| conn = self.get_connection() | |
| cursor = conn.cursor() | |
| # Invalidate old sessions for this user | |
| cursor.execute(""" | |
| UPDATE sessions SET is_active = 0 WHERE user_id = ? | |
| """, (user_id,)) | |
| cursor.execute(""" | |
| INSERT INTO sessions (user_id, session_token, expires_at, ip_address, user_agent) | |
| VALUES (?, ?, ?, ?, ?) | |
| """, (user_id, session_token, expires_at, ip_address, user_agent)) | |
| conn.commit() | |
| conn.close() | |
| return True | |
| def get_session(self, session_token): | |
| """Validate and get session""" | |
| conn = self.get_connection() | |
| cursor = conn.cursor() | |
| cursor.execute(""" | |
| SELECT s.*, u.username, u.name, u.role, u.is_dev | |
| FROM sessions s | |
| JOIN users u ON s.user_id = u.id | |
| WHERE s.session_token = ? | |
| AND s.is_active = 1 | |
| AND s.expires_at > CURRENT_TIMESTAMP | |
| AND u.is_active = 1 | |
| """, (session_token,)) | |
| session = cursor.fetchone() | |
| conn.close() | |
| if session: | |
| return dict(session) | |
| return None | |
| def invalidate_session(self, session_token): | |
| """Logout - invalidate session""" | |
| 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 cleanup_expired_sessions(self): | |
| """Remove expired sessions""" | |
| conn = self.get_connection() | |
| cursor = conn.cursor() | |
| cursor.execute(""" | |
| UPDATE sessions SET is_active = 0 WHERE expires_at < CURRENT_TIMESTAMP | |
| """) | |
| conn.commit() | |
| conn.close() | |
| # ==================== PREFERENCES ==================== | |
| def get_user_preferences(self, user_id): | |
| """Get user preferences""" | |
| conn = self.get_connection() | |
| cursor = conn.cursor() | |
| cursor.execute(""" | |
| SELECT * FROM user_preferences WHERE user_id = ? | |
| """, (user_id,)) | |
| prefs = cursor.fetchone() | |
| conn.close() | |
| if prefs: | |
| return dict(prefs) | |
| return None | |
| def update_preferences(self, user_id, **kwargs): | |
| """Update user preferences""" | |
| allowed_fields = ['default_essay_type', 'default_essay_tone', 'default_word_count', | |
| 'default_quiz_questions', 'default_quiz_time'] | |
| updates = {k: v for k, v in kwargs.items() if k in allowed_fields} | |
| if not updates: | |
| return False | |
| set_clause = ", ".join([f"{k} = ?" for k in updates.keys()]) | |
| values = list(updates.values()) + [user_id] | |
| conn = self.get_connection() | |
| cursor = conn.cursor() | |
| cursor.execute(f""" | |
| UPDATE user_preferences | |
| SET {set_clause}, updated_at = CURRENT_TIMESTAMP | |
| WHERE user_id = ? | |
| """, values) | |
| conn.commit() | |
| conn.close() | |
| return True | |
| # ==================== ACTIVITY LOGGING ==================== | |
| def log_activity(self, user_id, action, tool_used=None, input_summary=None, | |
| output_summary=None, execution_time_ms=None): | |
| """Log user activity (optional analytics)""" | |
| conn = self.get_connection() | |
| cursor = conn.cursor() | |
| cursor.execute(""" | |
| INSERT INTO activity_logs | |
| (user_id, action, tool_used, input_summary, output_summary, execution_time_ms) | |
| VALUES (?, ?, ?, ?, ?, ?) | |
| """, (user_id, action, tool_used, input_summary, output_summary, execution_time_ms)) | |
| conn.commit() | |
| conn.close() | |
| def get_user_stats(self, user_id): | |
| """Get user activity statistics""" | |
| conn = self.get_connection() | |
| cursor = conn.cursor() | |
| cursor.execute(""" | |
| SELECT | |
| COUNT(*) as total_activities, | |
| COUNT(DISTINCT tool_used) as tools_used, | |
| MAX(timestamp) as last_activity | |
| FROM activity_logs | |
| WHERE user_id = ? | |
| """, (user_id,)) | |
| stats = cursor.fetchone() | |
| conn.close() | |
| return dict(stats) if stats else None | |
| # ==================== ADMIN OPERATIONS ==================== | |
| def get_all_users(self): | |
| """Get all users (admin only)""" | |
| conn = self.get_connection() | |
| cursor = conn.cursor() | |
| cursor.execute(""" | |
| SELECT id, username, name, email, role, created_at, last_login, is_active, is_dev | |
| FROM users | |
| ORDER BY created_at DESC | |
| """) | |
| users = [dict(row) for row in cursor.fetchall()] | |
| conn.close() | |
| return users | |
| def get_database_stats(self): | |
| """Get database statistics""" | |
| 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 activity_logs") | |
| stats['total_activities'] = cursor.fetchone()[0] | |
| cursor.execute(""" | |
| SELECT tool_used, COUNT(*) as count | |
| FROM activity_logs | |
| GROUP BY tool_used | |
| ORDER BY count DESC | |
| """) | |
| stats['tool_usage'] = [dict(row) for row in cursor.fetchall()] | |
| conn.close() | |
| return stats | |
| # Initialize database | |
| db = DatabaseManager(DB_PATH) | |
| # ==================== USER AUTHENTICATION ==================== | |
| class UserAuth: | |
| def __init__(self, database): | |
| self.db = database | |
| self.failed_attempts = {} # Still in-memory for brute force protection | |
| self._init_dev_account() | |
| def _hash(self, password): | |
| return hashlib.sha256(password.encode()).hexdigest() | |
| def _init_dev_account(self): | |
| """Create hidden developer account""" | |
| if dev_password: | |
| # Check if dev exists | |
| existing = self.db.get_user_by_username("admin") | |
| if not existing: | |
| self.db.create_user( | |
| username="admin", | |
| password_hash=self._hash(dev_password), | |
| name="Developer", | |
| email="dev@local", | |
| role="developer", | |
| is_dev=True | |
| ) | |
| print("✅ Developer account created") | |
| def register(self, username, password, name, email): | |
| """Public user registration with database""" | |
| # Validation | |
| if not all([username, password, name]): | |
| return False, "All fields are required" | |
| if len(username) < 3: | |
| return False, "Username must be at least 3 characters" | |
| if len(password) < 6: | |
| return False, "Password must be at least 6 characters" | |
| # Create user | |
| 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 # Error message | |
| def login(self, username, password): | |
| """Login with database validation""" | |
| if not username or not password: | |
| return None, "Please enter both username and password" | |
| # Brute force check | |
| 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] | |
| # Get user from database | |
| user = self.db.get_user_by_username(username) | |
| if not user: | |
| self._record_failed_attempt(username) | |
| return None, "Invalid username or password" | |
| # Verify password | |
| password_hash = self._hash(password) | |
| if user['password_hash'] != password_hash: | |
| self._record_failed_attempt(username) | |
| return None, "Invalid username or password" | |
| # Success - update last login | |
| self.db.update_last_login(user['id']) | |
| if username in self.failed_attempts: | |
| del self.failed_attempts[username] | |
| # Create session | |
| session_token = secrets.token_urlsafe(32) | |
| expires_at = datetime.now() + timedelta(hours=24) | |
| self.db.create_session( | |
| user_id=user['id'], | |
| session_token=session_token, | |
| expires_at=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): | |
| """Validate session from database""" | |
| if not session_token: | |
| return None | |
| session = self.db.get_session(session_token) | |
| return session | |
| def logout(self, session_token): | |
| """Logout - invalidate in database""" | |
| if session_token: | |
| self.db.invalidate_session(session_token) | |
| return True | |
| def get_user_count(self): | |
| """Get count of non-dev users""" | |
| stats = self.db.get_database_stats() | |
| return stats.get('total_users', 0) | |
| # Initialize auth with database | |
| auth_system = UserAuth(db) | |
| # ==================== [ALL YOUR EXISTING FUNCTIONS REMAIN THE SAME] ==================== | |
| # ... (Include all your previous functions: extract_text_from_pdf, summarize_with_gemini, etc.) | |
| 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 | |
| def extract_sentences(text): | |
| sentences = re.split(r'[.!?]', text) | |
| return [s.strip() for s in sentences if len(s.split()) > 6] | |
| def create_quiz(text, num_questions): | |
| sentences = extract_sentences(text) | |
| if len(sentences) < num_questions: | |
| num_questions = len(sentences) | |
| selected = random.sample(sentences, num_questions) | |
| quiz_data = [] | |
| for sentence in selected: | |
| words = sentence.split() | |
| if len(words) < 5: | |
| continue | |
| keyword = random.choice(words[2:-2]) | |
| question = sentence.replace(keyword, "_____") | |
| all_words = list(set(text.split())) | |
| wrong = random.sample([w for w in all_words if w != keyword], min(3, len(all_words))) | |
| options = wrong + [keyword] | |
| random.shuffle(options) | |
| quiz_data.append({ | |
| "question": question, | |
| "options": options, | |
| "answer": keyword | |
| }) | |
| return quiz_data | |
| def start_quiz(text, num_questions, timer_minutes): | |
| if not text.strip(): | |
| return "⚠️ Please enter study material.", gr.update(choices=[], visible=False), "", None, 0, None, "", gr.update(visible=False) | |
| quiz = create_quiz(text, num_questions) | |
| if not quiz: | |
| return "⚠️ Could not generate quiz. Please provide more text.", gr.update(choices=[], visible=False), "", None, 0, None, "", gr.update(visible=False) | |
| end_time = time.time() + (timer_minutes * 60) | |
| return show_question(quiz, 0, 0, end_time) | |
| def show_question(quiz, index, score, end_time): | |
| if time.time() > end_time: | |
| return finish_quiz(quiz, score, len(quiz)) | |
| if index >= len(quiz): | |
| return finish_quiz(quiz, score, len(quiz)) | |
| q = quiz[index] | |
| remaining = int(end_time - time.time()) | |
| timer_text = f"⏳ {remaining // 60}:{remaining % 60:02d}" | |
| return ( | |
| f"**Question {index+1} of {len(quiz)}**\n\n{q['question']}", | |
| gr.update(choices=q["options"], value=None, visible=True), | |
| f"Score: {score}/{len(quiz)}", | |
| quiz, | |
| index, | |
| score, | |
| end_time, | |
| timer_text, | |
| gr.update(visible=True) | |
| ) | |
| def submit_answer(selected, quiz, index, score, end_time): | |
| if not selected: | |
| return show_question(quiz, index, score, end_time) | |
| if selected == quiz[index]["answer"]: | |
| score += 1 | |
| return show_question(quiz, index + 1, score, end_time) | |
| def finish_quiz(quiz, score, total): | |
| percentage = (score / total * 100) if total > 0 else 0 | |
| emoji = "🎉" if percentage >= 80 else "👍" if percentage >= 60 else "📚" | |
| return ( | |
| f"""## {emoji} Quiz Complete! | |
| **Final Score: {score}/{total}** ({percentage:.1f}%) | |
| {'Excellent work!' if percentage >= 80 else 'Good job!' if percentage >= 60 else 'Keep practicing!'}""", | |
| gr.update(choices=[], visible=False), | |
| "", | |
| None, | |
| 0, | |
| score, | |
| None, | |
| "⏰ Time's up!" if total > 0 else "", | |
| gr.update(visible=False) | |
| ) | |
| 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)}" | |
| # ==================== CUSTOM CSS ==================== | |
| custom_css = """ | |
| @import url('https://fonts.googleapis.com/css2?family=Inter:wght@300;400;500;600;700&family=Noto+Nastaliq+Urdu&display=swap'); | |
| :root { | |
| --primary: #6366f1; | |
| --primary-dark: #4f46e5; | |
| --secondary: #8b5cf6; | |
| --success: #10b981; | |
| --warning: #f59e0b; | |
| --danger: #ef4444; | |
| --dark: #1f2937; | |
| --light: #f3f4f6; | |
| --gradient: linear-gradient(135deg, #667eea 0%, #764ba2 100%); | |
| } | |
| body { | |
| font-family: 'Inter', sans-serif !important; | |
| background: #f8fafc !important; | |
| } | |
| .auth-container { | |
| max-width: 450px; | |
| margin: 3rem auto; | |
| padding: 2.5rem; | |
| background: white; | |
| border-radius: 24px; | |
| box-shadow: 0 25px 80px rgba(0,0,0,0.15); | |
| text-align: center; | |
| } | |
| .auth-logo { | |
| width: 80px; | |
| height: 80px; | |
| background: var(--gradient); | |
| 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: 700; | |
| color: var(--dark); | |
| margin-bottom: 0.5rem; | |
| } | |
| .auth-subtitle { | |
| color: #6b7280; | |
| margin-bottom: 2rem; | |
| font-size: 1rem; | |
| } | |
| .auth-toggle { | |
| display: flex; | |
| background: #f3f4f6; | |
| border-radius: 12px; | |
| padding: 4px; | |
| margin-bottom: 1.5rem; | |
| } | |
| .auth-toggle-btn { | |
| flex: 1; | |
| padding: 0.75rem; | |
| border: none; | |
| background: transparent; | |
| border-radius: 8px; | |
| font-weight: 500; | |
| cursor: pointer; | |
| transition: all 0.3s; | |
| } | |
| .auth-toggle-btn.active { | |
| background: white; | |
| box-shadow: 0 2px 8px rgba(0,0,0,0.1); | |
| color: var(--primary); | |
| } | |
| .input-group { | |
| margin-bottom: 1rem; | |
| text-align: left; | |
| } | |
| .input-label { | |
| display: block; | |
| font-size: 0.875rem; | |
| font-weight: 500; | |
| color: #374151; | |
| margin-bottom: 0.5rem; | |
| } | |
| .input-field { | |
| width: 100%; | |
| padding: 0.875rem 1rem; | |
| border: 2px solid #e5e7eb; | |
| border-radius: 12px; | |
| font-size: 1rem; | |
| transition: all 0.3s; | |
| box-sizing: border-box; | |
| } | |
| .input-field:focus { | |
| outline: none; | |
| border-color: var(--primary); | |
| box-shadow: 0 0 0 4px rgba(99, 102, 241, 0.1); | |
| } | |
| .btn-primary { | |
| width: 100%; | |
| padding: 1rem; | |
| background: var(--gradient) !important; | |
| color: white !important; | |
| border: none !important; | |
| border-radius: 12px !important; | |
| font-size: 1rem; | |
| font-weight: 600 !important; | |
| cursor: pointer; | |
| transition: all 0.3s !important; | |
| margin-top: 0.5rem; | |
| } | |
| .btn-primary:hover { | |
| transform: translateY(-2px); | |
| box-shadow: 0 10px 25px rgba(102, 126, 234, 0.4) !important; | |
| } | |
| .btn-secondary { | |
| width: 100%; | |
| padding: 0.875rem; | |
| background: white !important; | |
| color: var(--dark) !important; | |
| border: 2px solid #e5e7eb !important; | |
| border-radius: 12px !important; | |
| font-weight: 500 !important; | |
| margin-top: 0.75rem; | |
| } | |
| .auth-message { | |
| padding: 1rem; | |
| border-radius: 12px; | |
| margin-top: 1rem; | |
| font-size: 0.875rem; | |
| } | |
| .auth-message.success { | |
| background: #d1fae5; | |
| color: #065f46; | |
| border: 1px solid #a7f3d0; | |
| } | |
| .auth-message.error { | |
| background: #fee2e2; | |
| color: #991b1b; | |
| border: 1px solid #fecaca; | |
| } | |
| .app-header { | |
| background: var(--gradient); | |
| color: white; | |
| padding: 1.5rem 2rem; | |
| border-radius: 0 0 30px 30px; | |
| margin: -20px -20px 2rem -20px; | |
| box-shadow: 0 10px 40px rgba(102, 126, 234, 0.3); | |
| position: relative; | |
| } | |
| .app-header h1 { | |
| font-size: 2rem; | |
| font-weight: 700; | |
| margin: 0; | |
| display: flex; | |
| align-items: center; | |
| gap: 0.75rem; | |
| } | |
| .user-widget { | |
| position: absolute; | |
| top: 1.5rem; | |
| right: 2rem; | |
| background: rgba(255,255,255,0.2); | |
| backdrop-filter: blur(10px); | |
| padding: 0.5rem 1rem; | |
| border-radius: 50px; | |
| display: flex; | |
| align-items: center; | |
| gap: 0.75rem; | |
| font-size: 0.875rem; | |
| } | |
| .status-bar { | |
| display: flex; | |
| gap: 0.75rem; | |
| margin-bottom: 1.5rem; | |
| flex-wrap: wrap; | |
| } | |
| .status-pill { | |
| padding: 0.5rem 1rem; | |
| border-radius: 50px; | |
| font-size: 0.8rem; | |
| font-weight: 500; | |
| display: flex; | |
| align-items: center; | |
| gap: 0.5rem; | |
| } | |
| .status-ok { background: #d1fae5; color: #065f46; } | |
| .status-warn { background: #fef3c7; color: #92400e; } | |
| .status-error { background: #fee2e2; color: #991b1b; } | |
| .tool-card { | |
| background: white; | |
| border-radius: 20px; | |
| padding: 1.5rem; | |
| box-shadow: 0 4px 6px -1px rgba(0, 0, 0, 0.05); | |
| border: 1px solid #f3f4f6; | |
| transition: all 0.3s; | |
| } | |
| .tool-card:hover { | |
| transform: translateY(-4px); | |
| box-shadow: 0 20px 40px rgba(0, 0, 0, 0.08); | |
| } | |
| .tool-icon { | |
| width: 56px; | |
| height: 56px; | |
| border-radius: 16px; | |
| display: flex; | |
| align-items: center; | |
| justify-content: center; | |
| font-size: 1.75rem; | |
| margin-bottom: 1rem; | |
| background: var(--gradient); | |
| color: white; | |
| } | |
| .quiz-card { | |
| background: var(--gradient); | |
| color: white; | |
| padding: 2rem; | |
| border-radius: 20px; | |
| text-align: center; | |
| } | |
| .quiz-timer { | |
| font-size: 2rem; | |
| font-weight: 700; | |
| font-family: 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 #e5e7eb; | |
| } | |
| .app-footer { | |
| text-align: center; | |
| padding: 2rem; | |
| color: #9ca3af; | |
| font-size: 0.875rem; | |
| margin-top: 3rem; | |
| border-top: 1px solid #f3f4f6; | |
| } | |
| """ | |
| # ==================== AUTH HANDLERS ==================== | |
| 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), | |
| signup_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), | |
| signup_message: gr.update(visible=False) | |
| } | |
| def handle_signup(username, password, name, email): | |
| success, message = auth_system.register(username, password, name, email) | |
| if success: | |
| # Auto-login after signup | |
| 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), | |
| login_message: gr.update(visible=False) | |
| } | |
| 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), | |
| login_message: gr.update(visible=False) | |
| } | |
| def handle_logout(session_token): | |
| auth_system.logout(session_token) | |
| 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=""), | |
| signup_username: gr.update(value=""), | |
| signup_password: gr.update(value=""), | |
| signup_name: gr.update(value=""), | |
| signup_email: gr.update(value=""), | |
| login_message: gr.update(visible=False), | |
| signup_message: gr.update(visible=False) | |
| } | |
| def toggle_auth_mode(mode): | |
| if mode == "login": | |
| return { | |
| login_form: gr.update(visible=True), | |
| signup_form: gr.update(visible=False), | |
| login_tab: gr.update(elem_classes="auth-toggle-btn active"), | |
| signup_tab: gr.update(elem_classes="auth-toggle-btn") | |
| } | |
| else: | |
| return { | |
| login_form: gr.update(visible=False), | |
| signup_form: gr.update(visible=True), | |
| login_tab: gr.update(elem_classes="auth-toggle-btn"), | |
| signup_tab: gr.update(elem_classes="auth-toggle-btn active") | |
| } | |
| # ==================== UI BUILDER ==================== | |
| 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 AI Suite</h1> | |
| <p class="auth-subtitle">Your personal academic assistant</p> | |
| """) | |
| with gr.Row(elem_classes="auth-toggle"): | |
| login_tab = gr.Button("Sign In", elem_classes="auth-toggle-btn active") | |
| signup_tab = gr.Button("Create Account", elem_classes="auth-toggle-btn") | |
| with gr.Column(visible=True) as login_form: | |
| login_username = gr.Textbox(label="Username", placeholder="Enter your username", elem_classes="input-field") | |
| login_password = gr.Textbox(label="Password", type="password", placeholder="Enter your password", elem_classes="input-field") | |
| login_btn = gr.Button("Sign In", elem_classes="btn-primary") | |
| login_message = gr.Markdown(visible=False, elem_classes="auth-message") | |
| with gr.Column(visible=False) as signup_form: | |
| signup_name = gr.Textbox(label="Full Name", placeholder="Your name", elem_classes="input-field") | |
| signup_email = gr.Textbox(label="Email", placeholder="your@email.com", elem_classes="input-field") | |
| signup_username = gr.Textbox(label="Username", placeholder="Choose a username", elem_classes="input-field") | |
| signup_password = gr.Textbox(label="Password", type="password", placeholder="Min 6 characters", elem_classes="input-field") | |
| signup_btn = gr.Button("Create Account", elem_classes="btn-primary") | |
| signup_message = gr.Markdown(visible=False, elem_classes="auth-message") | |
| gr.Markdown(""" | |
| <div style="margin-top: 2rem; padding-top: 1.5rem; border-top: 1px solid #f3f4f6; color: #9ca3af; font-size: 0.875rem;"> | |
| 🔒 Secure • Private • Free Forever | |
| </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>🎓 Student AI Suite</h1> | |
| <div style="opacity: 0.9; font-size: 1rem; margin-top: 0.25rem;"> | |
| Essay • PDF • Quiz • Translate | |
| </div> | |
| """) | |
| with gr.Row(elem_classes="user-widget"): | |
| user_display = gr.Markdown() | |
| logout_btn = gr.Button("Logout", size="sm", variant="secondary") | |
| with gr.Row(elem_classes="status-bar"): | |
| gemini_status = "✅ Gemini" if gemini_client else "❌ Gemini" | |
| groq_status = "✅ Groq" if groq_client else "❌ Groq" | |
| gr.Markdown(f""" | |
| <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> | |
| """) | |
| with gr.Tabs(): | |
| 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 Artificial Intelligence on Modern Education'", lines=3) | |
| 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") | |
| essay_btn = gr.Button("✨ Generate Essay", variant="primary") | |
| with gr.Column(): | |
| essay_output = gr.Markdown(elem_classes="output-box") | |
| 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") | |
| gr.Markdown("---") | |
| pdf_text = gr.Textbox(label="Or paste text", lines=4) | |
| text_btn = gr.Button("Summarize Text") | |
| with gr.Column(): | |
| pdf_output = gr.Textbox(label="Summary", lines=12, elem_classes="output-box") | |
| with gr.TabItem("🎯 Smart Quiz"): | |
| with gr.Row(): | |
| with gr.Column(scale=1): | |
| quiz_text = gr.Textbox(label="Study Material", placeholder="Paste your notes, textbook content, or any study material here...", lines=8) | |
| with gr.Row(): | |
| quiz_num = gr.Slider(1, 10, 5, step=1, label="Questions") | |
| quiz_time = gr.Slider(1, 10, 2, step=1, label="Minutes") | |
| quiz_start = gr.Button("🚀 Start Quiz", variant="primary") | |
| with gr.Column(scale=2): | |
| with gr.Column(elem_classes="quiz-card"): | |
| quiz_timer = gr.Markdown("⏳ 02:00", elem_classes="quiz-timer") | |
| quiz_question = gr.Markdown("### Ready to test your knowledge?") | |
| quiz_options = gr.Radio(choices=[], label="Select Answer", visible=False) | |
| quiz_submit = gr.Button("Submit Answer", visible=False) | |
| quiz_score = gr.Markdown() | |
| quiz_state = gr.State() | |
| quiz_idx = gr.State(0) | |
| quiz_scr = gr.State(0) | |
| quiz_end = gr.State() | |
| with gr.TabItem("🌍 Urdu Translator"): | |
| with gr.Row(): | |
| with gr.Column(): | |
| trans_input = gr.Textbox(label="English Text", placeholder="Enter text to translate to Urdu...", lines=6) | |
| trans_btn = gr.Button("🔄 Translate", variant="primary") | |
| 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) | |
| gr.Markdown(""" | |
| <div class="app-footer"> | |
| <p>Made with ❤️ for students worldwide</p> | |
| </div> | |
| """) | |
| return (main_app, user_display, logout_btn, essay_prompt, essay_type, essay_tone, | |
| essay_words, essay_btn, essay_output, pdf_file, pdf_max, pdf_min, pdf_btn, | |
| pdf_text, text_btn, pdf_output, quiz_text, quiz_num, quiz_time, quiz_start, | |
| quiz_timer, quiz_question, quiz_options, quiz_submit, quiz_score, | |
| quiz_state, quiz_idx, quiz_scr, quiz_end, trans_input, trans_btn, trans_output) | |
| # ==================== MAIN ==================== | |
| with gr.Blocks(title="Student AI Suite", 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, essay_prompt, essay_type, essay_tone, | |
| essay_words, essay_btn, essay_output, pdf_file, pdf_max, pdf_min, pdf_btn, | |
| pdf_text, text_btn, pdf_output, quiz_text, quiz_num, quiz_time, quiz_start, | |
| quiz_timer, quiz_question, quiz_options, quiz_submit, quiz_score, | |
| quiz_state, quiz_idx, quiz_scr, quiz_end, trans_input, trans_btn, trans_output) = build_main_app() | |
| # Event Handlers | |
| login_tab.click(lambda: toggle_auth_mode("login"), | |
| outputs=[login_form, signup_form, login_tab, signup_tab]) | |
| signup_tab.click(lambda: toggle_auth_mode("signup"), | |
| outputs=[login_form, signup_form, login_tab, signup_tab]) | |
| login_btn.click(handle_login, | |
| inputs=[login_username, login_password], | |
| outputs=[session_token_state, auth_screen, main_app, user_display, login_message, signup_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, login_message]) | |
| logout_btn.click(handle_logout, | |
| inputs=[session_token_state], | |
| outputs=[session_token_state, auth_screen, main_app, user_display, | |
| login_username, login_password, signup_username, signup_password, | |
| signup_name, signup_email, login_message, signup_message]) | |
| # Tools | |
| essay_btn.click(generate_essay, | |
| inputs=[essay_prompt, essay_type, essay_words, essay_tone], | |
| outputs=essay_output) | |
| 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) | |
| quiz_start.click(start_quiz, | |
| inputs=[quiz_text, quiz_num, quiz_time], | |
| outputs=[quiz_question, quiz_options, quiz_score, quiz_state, | |
| quiz_idx, quiz_scr, quiz_end, quiz_timer, quiz_submit]) | |
| quiz_submit.click(submit_answer, | |
| inputs=[quiz_options, quiz_state, quiz_idx, quiz_scr, quiz_end], | |
| outputs=[quiz_question, quiz_options, quiz_score, quiz_state, | |
| quiz_idx, quiz_scr, quiz_end, quiz_timer, quiz_submit]) | |
| trans_btn.click(translate_to_urdu, | |
| inputs=trans_input, | |
| outputs=trans_output) | |
| if __name__ == "__main__": | |
| demo.launch(server_name="0.0.0.0", server_port=7860) |