import gradio as gr from transformers import AutoModelForCausalLM, AutoTokenizer, pipeline import torch from datetime import datetime import pandas as pd import matplotlib.pyplot as plt import seaborn as sns import os import json from pathlib import Path from typing import Optional # ====================== # CONFIGURATION # ====================== class Config: MODEL_ID = "Edraky/Edraky-AI" THEME = "soft" MAX_TOKENS = 1000 TEMPERATURE = 0.7 TOP_P = 0.9 HISTORY_FILE = "chat_history.csv" CONFIG_FILE = "config.json" DEFAULT_SETTINGS = { "language": "ar", "theme": THEME, "max_tokens": MAX_TOKENS, "temperature": TEMPERATURE, "top_p": TOP_P } @classmethod def load_settings(cls): if os.path.exists(cls.CONFIG_FILE): with open(cls.CONFIG_FILE, 'r', encoding='utf-8') as f: return json.load(f) return cls.DEFAULT_SETTINGS @classmethod def save_settings(cls, settings): with open(cls.CONFIG_FILE, 'w', encoding='utf-8') as f: json.dump(settings, f, ensure_ascii=False, indent=4) # ====================== # MODEL LOADING (WITH GPU SUPPORT) # ====================== def load_model(): """Load the model and tokenizer with GPU support if available""" try: tokenizer = AutoTokenizer.from_pretrained( Config.MODEL_ID, trust_remote_code=True ) if torch.cuda.is_available(): device = "cuda" torch_dtype = torch.float16 print("GPU available - using CUDA with float16") else: device = "cpu" torch_dtype = torch.float32 print("Using CPU with float32") model = AutoModelForCausalLM.from_pretrained( Config.MODEL_ID, torch_dtype=torch_dtype, device_map="auto" if device == "cuda" else None, trust_remote_code=True ) if device == "cpu": model = model.to(device) pipe = pipeline( "text-generation", model=model, tokenizer=tokenizer, device=device ) return pipe, tokenizer, device except Exception as e: raise RuntimeError(f"Failed to load model: {str(e)}") try: pipe, tokenizer, device = load_model() print(f"Model successfully loaded on {device.upper()}") except RuntimeError as e: print(f"Critical error: {e}") pipe, tokenizer, device = None, None, "cpu" # ====================== # UTILITY FUNCTIONS # ====================== def ensure_history_file(): """Ensure history file exists with proper columns""" if not os.path.exists(Config.HISTORY_FILE): pd.DataFrame(columns=["timestamp", "language", "prompt", "response"]).to_csv( Config.HISTORY_FILE, index=False ) def log_conversation(prompt: str, response: str, language: str) -> None: """Log conversation to CSV file with language tracking""" try: ensure_history_file() df = pd.DataFrame({ "timestamp": [datetime.now().strftime("%Y-%m-%d %H:%M:%S")], "language": [language], "prompt": [prompt], "response": [response] }) df.to_csv( Config.HISTORY_FILE, mode="a", header=False, index=False ) except Exception as e: print(f"Error logging conversation: {e}") def generate_response( prompt: str, language: str = "ar", max_tokens: int = Config.MAX_TOKENS, temperature: float = Config.TEMPERATURE, top_p: float = Config.TOP_P ) -> str: """Generate response from the model with enhanced parameters""" if pipe is None or tokenizer is None: return "Error: Model not loaded properly. Please try again later." try: system_message = { "ar": """ أنت إدراكي (Edraky)، مساعد ذكي متعدد اللغات صُمم لدعم الطلاب - خاصة في النظام التعليمي المصري. مهمتك هي تقديم شروحات واضحة ودقيقة وجذابة عبر مجموعة واسعة من المواد الأكاديمية، خاصة لمرحلتي الإعدادي والثانوي. أنت لطيف، مشجع، وتحترم الجميع دائماً. 🎓 المبادئ الأساسية: 1. استخدم لغة مبسطة يفهمها الطلاب بمختلف مستوياتهم - لا تعقد الأمور. 2. شجع الفضول دائماً، حتى لو كان السؤال بسيطاً أو غير صحيح. 3. علّم بالعربية والإنجليزية حسب لغة المستخدم. إذا تحدث المستخدم بالعربية، فاستخدم العربية الفصحى ما لم يُطلب غير ذلك. 4. كن واعياً ثقافياً بمصر والعالم العربي. استخدم أمثلة ذات صلة عند الشرح. 5. لا تكذب أو تختلق معلومات. إذا كان شيء غير معروف أو غير واضح، قل ذلك بصدق. 6. ابقَ مركزاً على كونك مساعداً تعليمياً: لا تمزح بطريقة تشتت الانتباه، ولا تقدم ردوداً خارج الموضوع إلا إذا طُلب ذلك بوضوح. 7. رد بحماس مثل معلم أو مرشد شغوف يؤمن بأن كل طالب يمكنه التفوق. 8. إذا كان السؤال غير واضح، فاسأل لتساعد المستخدم على التوضيح بدلاً من التخمين الخاطئ. 🛠️ الميزات التي تدعمها (إذا كانت متاحة): - سجل الأسئلة السابقة - إدخال صوتي - رفع ملفات (صور، PDF) للمساعدة في الواجبات المدرسية - تبديل اللغة حسب الطلب - تلخيص أو شرح أو ترجمة النصوص الصعبة 🌟 النبرة: ودودة، احترافية، وتحفيزية. تصرف مثل الأخ/الأخت الأكبر الذكي أو المدرس المتفاني. 🧠 يمكنك التفكير خطوة بخطوة. مسموح لك بتقسيم المشكلات المعقدة إلى أجزاء، خاصة في الرياضيات والعلوم والنحو. 💡 أنت تساعد المستخدمين ليس فقط في الحصول على الإجابات - بل في فهم كيف ولماذا. """, "en": """ You are Edraky (إدراكي), a smart, multilingual AI assistant built to support students—especially in the Egyptian educational system. Your role is to provide clear, accurate, and engaging explanations across a wide range of academic subjects, especially for preparatory and secondary levels. You are kind, encouraging, and always respectful. 🎓 Key Principles: 1. Use simplified language that students of all levels can understand—don't overcomplicate. 2. Always encourage curiosity, even if a question is simple or incorrect. 3. Teach in both Arabic and English, depending on the user's input. If the user speaks Arabic, prefer using Modern Standard Arabic unless otherwise specified. 4. Be culturally aware of Egypt and the Arab world. Use relevant examples when explaining. 5. Never lie or make up facts. If something is unknown or unclear, say so honestly. 6. Stay focused on being an educational assistant: no jokes that distract, no off-topic replies unless clearly allowed. 7. Respond warmly like a passionate teacher or mentor who believes every student can shine. 8. If the question is unclear, ask questions to help the user clarify instead of guessing wrong. 🛠️ Features You Support (if implemented): - History of previous questions - Voice input - Uploads (images, PDFs) for help with scanned homework - Language switching on demand - Summarizing, explaining, or translating difficult texts 🌟 Tone: Friendly, professional, and motivational. Act like a smart older brother/sister or a dedicated tutor. 🧠 You can think step-by-step. You're also allowed to break complex problems into parts, especially in math, science, and grammar. 💡 You help users not just get answers—but understand how and why. """ }.get(language, "ar") messages = [ {"role": "system", "content": system_message}, {"role": "user", "content": prompt} ] outputs = pipe( messages, max_new_tokens=max_tokens, temperature=temperature, top_p=top_p, do_sample=True, pad_token_id=tokenizer.eos_token_id ) try: response = outputs[0]['generated_text'][-1]['content'] except Exception: response = outputs[0]['generated_text'] if 'generated_text' in outputs[0] else str(outputs) log_conversation(prompt, response, language) return response except Exception as e: return f"Error generating response: {str(e)}" def show_history() -> pd.DataFrame: """Display conversation history with proper formatting""" try: ensure_history_file() df = pd.read_csv(Config.HISTORY_FILE) if not df.empty: df['timestamp'] = pd.to_datetime(df['timestamp']) return df.sort_values('timestamp', ascending=False) return pd.DataFrame() except Exception as e: print(f"Error reading history: {e}") return pd.DataFrame() def analyze_history() -> Optional[plt.Figure]: """Create visualizations of conversation history""" try: df = show_history() if df.empty: return None plt.figure(figsize=(12, 8)) ax1 = plt.subplot2grid((2, 2), (0, 0)) ax2 = plt.subplot2grid((2, 2), (0, 1)) ax3 = plt.subplot2grid((2, 2), (1, 0), colspan=2) df['date'] = df['timestamp'].dt.date daily_counts = df.groupby('date').size() daily_counts.plot(kind='bar', ax=ax1, color='#4e79a7') ax1.set_title("Daily Conversation Count") ax1.set_xlabel("Date") ax1.set_ylabel("Count") lang_dist = df['language'].value_counts() lang_dist.plot(kind='pie', ax=ax2, autopct='%1.1f%%', colors=['#f28e2b', '#e15759'], labels=['Arabic', 'English']) ax2.set_title("Language Distribution") ax2.set_ylabel("") df['hour'] = df['timestamp'].dt.hour hourly = df.groupby('hour').size() sns.lineplot(x=hourly.index, y=hourly.values, ax=ax3, color='#59a14f') ax3.set_title("Hourly Activity") ax3.set_xlabel("Hour of Day") ax3.set_ylabel("Conversations") plt.tight_layout() return plt.gcf() except Exception as e: print(f"Error analyzing history: {e}") return None def clear_history() -> pd.DataFrame: """Clear conversation history""" try: pd.DataFrame(columns=["timestamp", "language", "prompt", "response"]).to_csv( Config.HISTORY_FILE, index=False ) return pd.DataFrame() except Exception as e: print(f"Error clearing history: {e}") return show_history() # ====================== # GRADIO INTERFACE # ====================== def get_css() -> str: """Return custom CSS for the interface""" return """ .gradio-container { font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif; } .header { text-align: center; padding: 20px; background: linear-gradient(135deg, #6e48aa 0%, #9d50bb 100%); color: white; border-radius: 8px; margin-bottom: 20px; } .header h1 { margin: 0; font-size: 2.5em; } .header p { margin: 5px 0 0; font-size: 1.1em; } .footer { text-align: center; margin-top: 20px; padding: 10px; color: #666; font-size: 0.9em; border-top: 1px solid #eee; } .rtl-text { text-align: right; direction: rtl; } .ltr-text { text-align: left; direction: ltr; } .settings-section { background: #f9f9f9; padding: 15px; border-radius: 8px; margin-bottom: 15px; border: 1px solid #eee; } """ def create_header(): """Create the header section""" gr.Markdown("""
مساعد ذكي للطلاب في المواد الدراسية | Smart Student Assistant