| | |
| | |
| |
|
| | import os, json, uuid, re, unicodedata |
| | from difflib import get_close_matches |
| |
|
| | import gradio as gr |
| |
|
| | |
| | try: |
| | import google.generativeai as genai |
| | except Exception: |
| | genai = None |
| |
|
| | |
| | |
| | |
| |
|
| | DATA_FILE = "student_profiles.json" |
| | GEMINI_MODEL = os.getenv("GEMINI_MODEL", "models/gemma-3n-e2b-it") |
| | GEMINI_API_KEY = os.getenv("Gemini_API_KEY") |
| |
|
| | if GEMINI_API_KEY and genai: |
| | genai.configure(api_key=GEMINI_API_KEY) |
| | _gemini_model = genai.GenerativeModel(GEMINI_MODEL) |
| | else: |
| | _gemini_model = None |
| |
|
| | |
| | |
| | |
| | def load_students() -> dict: |
| | if not os.path.exists(DATA_FILE): |
| | return {} |
| | with open(DATA_FILE, "r", encoding="utf-8") as f: |
| | return json.load(f) |
| |
|
| | def save_students(data: dict) -> None: |
| | with open(DATA_FILE, "w", encoding="utf-8") as f: |
| | json.dump(data, f, indent=2, ensure_ascii=False) |
| |
|
| | def list_student_ids() -> list: |
| | return sorted(load_students().keys()) |
| |
|
| | def get_student(student_id: str) -> dict | None: |
| | return load_students().get(student_id) |
| |
|
| | def add_student(data: dict) -> str: |
| | students = load_students() |
| | new_id = f"student_{uuid.uuid4().hex[:8]}" |
| | students[new_id] = data |
| | save_students(students) |
| | return new_id |
| |
|
| | def update_student(student_id: str, updates: dict) -> str: |
| | students = load_students() |
| | if student_id not in students: |
| | return f"❌ {student_id} not found." |
| | for k, v in updates.items(): |
| | if v not in [None, "", []]: |
| | students[student_id][k] = v |
| | save_students(students) |
| | return f"✅ {student_id} updated." |
| |
|
| | |
| | |
| | |
| | FAQS = { |
| | |
| | "إيه هو ThinkPal؟": "ThinkPal منصة بتساعدك تعرف نفسك أكتر وتتعلم بالطريقة اللي تناسبك وكمان تديك خطة تعليمية واضحة خطوة بخطوة علشان توصل لهدفك.", |
| | "هل الموقع للثانوي ولا الجامعة؟": "ThinkPal معمول بالأساس لطلاب الجامعة لكن أي طالب حابب يطور من نفسه أو يكتشف طريقه يقدر يستخدمه.", |
| | "أنا ليه أجاوب على الأسئلة أول ما أدخل؟": "لأن الأسئلة دي بتساعد المنصة تفهم شخصيتك وطريقة تفكيرك وتعلمك ومن هنا تقدر تبني لك خطة تناسبك أنت بالذات.", |
| | "الأسئلة اللي بتظهر صعبة شوية أجاوب إزاي؟": "دا مش امتحان ومفيش إجابة صح أو غلط، جاوب بطريقتك وباللي يمثلك. كل إجابة بتقرب الصورة أكتر.", |
| | "الخطة التعليمية بتكون إيه بالظبط؟": "الخطة عبارة عن خطوات من مستوى مبتدئ لحد مستوى متقدم ومع كل خطوة هتلاقي مصادر موثوقة للتعلم وتدريبات تساعدك تطبق اللي بتتعلمه.", |
| | "هل المصادر كلها مجانية؟": "فيه مصادر كتير مجانية وفيه كمان مصادر مدفوعة بنرشحها أحيانًا علشان تبقى قدامك كل الاختيارات.", |
| | "هل لازم أمشي بالخطة زي ما هي؟": "الخطة معمولة علشان تسهل عليك لكن في الآخر إنت بتختار سرعة العملية التعليمية.", |
| | "يعني ThinkPal بديل للدروس أو الكورسات؟": "لأ ThinkPal مش بديل هو زي دليل أو صديق بيرتب لك الطريق ويقولك تبدأ منين وتروح فين.", |
| | "هل في متابعة لتقدمي؟": "أيوة عندك Dashboard شخصي يوضح إنجازاتك، الاختبارات اللي عملتها، تقييمك، Badges، وأي نقاط محتاجة تشتغل عليها.", |
| | "إيه هي Insights اللي بتظهرلي؟": "دي ملاحظات بتقولك إيه نقاط قوتك وإيه الحاجات اللي محتاجة تحسين وكمان نصايح عملية تساعدك تطور نفسك.", |
| | "هل فيه تواصل مع طلاب تانيين؟": "أيوة فيه مجتمع جوا المنصة تقدر تتكلم فيه مع طلبة زيك وكمان فيه Mentors يساعدوك لو محتاج.", |
| | "هل المنصة بتركز على الدراسة بس؟": "لأ ThinkPal بيساعدك في الدراسة وكمان في تطوير شخصيتك و مهاراتك وحتى في إنك تحدد وتختار مستقبلك بشكل أوضح.", |
| | "الخصوصية آمنة؟": "أكيد بياناتك محمية ومفيش حد يقدر يشوفها غيرك.", |
| | "لو وقفت في نص الطريق؟": "مفيش مشكلة، تقدر ترجع في أي وقت وتكمل من نفس المكان اللي وصلتله.", |
| |
|
| | |
| | "What is ThinkPal?": "ThinkPal is a platform that helps you understand yourself better, learn in the way that suits you, and gives you a clear step-by-step learning plan to achieve your goals.", |
| | "Is the platform for high school or university students?": "ThinkPal is mainly designed for university students, but any student who wants to improve or discover their path can use it.", |
| | "Why should I answer the questions when I first join?": "These questions help the platform understand your personality, thinking, and learning style, so it can build a plan tailored to you.", |
| | "The questions seem difficult, how should I answer?": "This is not an exam, and there are no right or wrong answers. Just answer in your own way with what represents you.", |
| | "What exactly is the learning roadmap?": "It’s a step-by-step plan from beginner to advanced levels. Each step includes trusted resources and exercises to help you apply what you learn.", |
| | "Are all resources free?": "Many resources are free, but we sometimes recommend paid resources to give you all available options.", |
| | "Do I have to follow the roadmap exactly as it is?": "The roadmap is designed to make it easier for you, but in the end, you choose the pace of your learning.", |
| | "Is ThinkPal a replacement for lessons or courses?": "No, ThinkPal is not a replacement. It’s more like a guide or a friend that organizes your path and tells you where to start and where to go.", |
| | "Is my progress tracked?": "Yes, you have a personal dashboard that shows your achievements, completed tests, evaluations, badges, and areas you need to work on.", |
| | "What are the insights shown to me?": "They are notes that highlight your strengths, areas for improvement, and practical tips to help you develop.", |
| | "Can I connect with other students?": "Yes, there’s a community inside the platform where you can talk with other students and get support from mentors if needed.", |
| | "Does the platform focus only on studying?": "No, ThinkPal helps with studying, but also with personal growth, skills development, and even clarifying your future direction.", |
| | "Is my privacy safe?": "Of course, your data is protected and no one can see it except you.", |
| | "What if I stop halfway?": "No problem, you can return anytime and continue from where you left off.", |
| | } |
| |
|
| | def _normalize(text: str) -> str: |
| | t = text.lower().strip() |
| | t = "".join(c for c in unicodedata.normalize("NFD", t) if unicodedata.category(c) != "Mn") |
| | t = re.sub(r"[^\w\s\u0600-\u06FF]", " ", t) |
| | return re.sub(r"\s+", " ", t) |
| |
|
| | def find_faq_answer(user_input: str, cutoff: float = 0.6) -> str | None: |
| | if not user_input: |
| | return None |
| | ui = _normalize(user_input) |
| | questions = list(FAQS.keys()) |
| | norm_qs = {_normalize(q): q for q in questions} |
| | match_keys = get_close_matches(ui, list(norm_qs.keys()), n=1, cutoff=cutoff) |
| | if match_keys: |
| | return FAQS[norm_qs[match_keys[0]]] |
| | return None |
| |
|
| | |
| | |
| | |
| | ROADMAP_QUERY = """ |
| | Generate a personalized learning roadmap for a student, structured into distinct phases: |
| | - Beginner |
| | - Intermediate |
| | - Advanced |
| | - Challenge |
| | |
| | When creating the roadmap, consider the student's: |
| | - Learning style |
| | - Academic progress |
| | - Personality |
| | - Interests |
| | - Goals |
| | - Current level |
| | - Preferred learning methods |
| | - IQ level |
| | - EQ level |
| | - Decision-making style |
| | - Motivation level |
| | - Preferred study environment |
| | |
| | The roadmap should help the student achieve their stated goals based on their unique characteristics. |
| | |
| | For each phase, suggest specific types of resources tailored to the student's profile, such as: |
| | - Online courses |
| | - Books |
| | - Interactive tools |
| | - Hands-on projects |
| | - Community engagement opportunities |
| | |
| | Formatting requirements: |
| | - Use plain text only (no **bold**, no markdown, no emojis, no AI disclaimers). |
| | - Organize information with numbered lists and subheadings. |
| | - Keep the tone professional, concise, and human-like. |
| | |
| | Finally, structure the roadmap into the following sections: |
| | 1. Current Status |
| | 2. Goals |
| | 3. Recommended Resources & Activities (by Phase) |
| | 4. Milestones (by Phase) |
| | """ |
| |
|
| | def get_gemini_response(query: str, student_data: dict | None = None) -> str: |
| | try: |
| | if not genai: |
| | return f"(Simulated) {query[:400]}..." |
| |
|
| | model = genai.GenerativeModel("models/gemma-3n-e2b-it") |
| | personalized_prompt = query |
| |
|
| | if student_data: |
| | profile_parts = [] |
| | for key, label in [ |
| | ("learning_style", "Learning Style"), |
| | ("academic_progress", "Academic Progress"), |
| | ("personality", "Personality"), |
| | ("interests", "Interests"), |
| | ("goals", "Goals"), |
| | ("level", "Level"), |
| | ("preferred_methods", "Preferred Methods"), |
| | ("iq_level", "IQ Level"), |
| | ("eq_level", "EQ Level"), |
| | ("decision_making_style", "Decision-Making Style"), |
| | ("motivation_level", "Motivation Level"), |
| | ("preferred_study_environment", "Preferred Study Environment"), |
| | ("community_groups", "Community Groups"), |
| | ]: |
| | value = student_data.get(key) |
| | if isinstance(value, list): |
| | value = ", ".join(value) |
| | if value: |
| | profile_parts.append(f"{label}: {value}") |
| |
|
| | student_profile = ", ".join(profile_parts) if profile_parts else "No student data provided." |
| |
|
| | personalized_prompt = f""" |
| | The student's profile is: {student_profile}. |
| | |
| | Based on this profile, generate the following: |
| | |
| | {query} |
| | |
| | Formatting requirements: |
| | - Use plain text only (no **bold**, no markdown, no emojis, no AI disclaimers). |
| | - Organize information with numbered lists and subheadings. |
| | - Keep the tone professional, concise, and human-like. |
| | """ |
| |
|
| | response = model.generate_content(personalized_prompt) |
| | return getattr(response, "text", "").strip() or "(Empty response)" |
| |
|
| | except Exception as e: |
| | return f"(Gemini error fallback) {str(e)[:160]}" |
| |
|
| | def generate_ai_insights(student_data: dict) -> str: |
| | if not student_data: |
| | return "Student data not available for generating insights." |
| |
|
| | profile_parts = [] |
| | for key, label in [ |
| | ("learning_style", "Learning Style"), |
| | ("academic_progress", "Academic Progress"), |
| | ("personality", "Personality"), |
| | ("interests", "Interests"), |
| | ("goals", "Goals"), |
| | ("level", "Level"), |
| | ("preferred_methods", "Preferred Methods"), |
| | ("iq_level", "IQ Level"), |
| | ("eq_level", "EQ Level"), |
| | ("decision_making_style", "Decision-Making Style"), |
| | ("motivation_level", "Motivation Level"), |
| | ("preferred_study_environment", "Preferred Study Environment"), |
| | ("community_groups", "Community Groups"), |
| | ]: |
| | value = student_data.get(key) |
| | if isinstance(value, list): |
| | value = ", ".join(value) |
| | if value: |
| | profile_parts.append(f"- {label}: {value}") |
| |
|
| | student_profile = "\n".join(profile_parts) if profile_parts else "No detailed data provided." |
| |
|
| | insights_prompt = f""" |
| | Analyze the following student profile and provide AI-driven insights, |
| | highlighting their strengths and potential areas for improvement. |
| | |
| | Student Profile: |
| | {student_profile} |
| | |
| | Formatting requirements: |
| | - Use plain text only (no **bold**, no markdown, no emojis, no AI disclaimers). |
| | - Organize insights clearly with numbered or bulleted points. |
| | - Keep the tone professional, concise, and human-like. |
| | """ |
| |
|
| | return get_gemini_response(insights_prompt, student_data) |
| |
|
| | |
| | |
| | |
| | def chat(student_id: str, message: str) -> tuple[str, str, str]: |
| | roadmap, insights, reply = "", "", "" |
| | student = get_student(student_id) |
| | if not student: |
| | return ("❌ Student not found.", "", "") |
| | m = (message or "").strip() |
| | if not m: |
| | return ("", "", "Please enter a message.") |
| | if m.lower() == "roadmap": |
| | roadmap = get_gemini_response(ROADMAP_QUERY, student) |
| | elif m.lower() == "insights": |
| | insights = generate_ai_insights(student) |
| | else: |
| | faq = find_faq_answer(m) |
| | reply = faq if faq else get_gemini_response(m, student) |
| | return roadmap, insights, reply |
| |
|
| | |
| | |
| | |
| | FIELDS = [ |
| | "learning_style","academic_progress","personality","interests","goals","level", |
| | "preferred_methods","iq_level","eq_level","decision_making_style", |
| | "motivation_level","preferred_study_environment","community_groups" |
| | ] |
| |
|
| | def create_student(*args): |
| | data = {} |
| | for k, v in zip(FIELDS, args): |
| | if "methods" in k or "groups" in k: |
| | data[k] = v.split(",") if v else [] |
| | else: |
| | data[k] = v |
| | new_id = add_student(data) |
| | return ( |
| | f"🎉 Created {new_id}", |
| | json.dumps(data, ensure_ascii=False, indent=2), |
| | gr.Dropdown.update(choices=list_student_ids(), value=new_id) |
| | ) |
| |
|
| | def load_student_to_form(student_id: str): |
| | s = get_student(student_id) |
| | if not s: return [""] * len(FIELDS) |
| | return [", ".join(v) if isinstance(v, list) else v for v in s.values()] |
| |
|
| | def apply_update(student_id, *args): |
| | updates = {} |
| | for k, v in zip(FIELDS, args): |
| | if "methods" in k or "groups" in k: |
| | updates[k] = v.split(",") if v else [] |
| | else: |
| | updates[k] = v |
| | msg = update_student(student_id, updates) |
| | return msg, json.dumps(get_student(student_id) or {}, ensure_ascii=False, indent=2) |
| |
|
| | |
| | |
| | |
| | THEME = gr.themes.Soft(primary_hue="indigo", secondary_hue="cyan") |
| |
|
| | CUSTOM_CSS = """ |
| | #header {padding: 24px 0 8px;} |
| | #header h1 {margin:0; font-size: 2rem;} |
| | .small {opacity:.85; font-size:.9rem} |
| | .card {border:1px solid #2a2f4a; border-radius:12px; padding:12px;} |
| | .gradio-container {background-color: #0b0e1a !important; color: #e0e0e0 !important;} |
| | textarea, input, select {background-color: #14182b !important; border: 1px solid #2a2f4a !important; color: #e0e0e0 !important;} |
| | button.primary {background: linear-gradient(90deg, #6C63FF, #00BCD4) !important; color: #fff !important;} |
| | """ |
| |
|
| | with gr.Blocks(theme=THEME, css=CUSTOM_CSS) as demo: |
| | gr.HTML("<div id='header'><h1>🎓 ThinkPal – Personalized Learning Assistant</h1></div>") |
| |
|
| | with gr.Tab("💬 Chat"): |
| | with gr.Row(): |
| | student_dd = gr.Dropdown(label="Select Student", choices=list_student_ids() or []) |
| | user_msg = gr.Textbox(label="Message", placeholder="Type roadmap, insights, or a question") |
| | ask_btn = gr.Button("Ask", variant="primary") |
| | chatbot_out = gr.Textbox(label="Response", lines=6, elem_classes=["card"]) |
| | with gr.Row(): |
| | roadmap_out = gr.Textbox(label="Roadmap", lines=10, elem_classes=["card"]) |
| | insights_out = gr.Textbox(label="Insights", lines=10, elem_classes=["card"]) |
| | ask_btn.click(fn=chat, inputs=[student_dd, user_msg], outputs=[roadmap_out, insights_out, chatbot_out]) |
| |
|
| | with gr.Tab("➕ Add Student"): |
| | fields = {k: gr.Textbox(label=k.replace("_", " ").title()) for k in FIELDS} |
| | create_btn = gr.Button("Create Profile", variant="primary") |
| | status_new = gr.Textbox(label="Status") |
| | preview_new = gr.Textbox(label="Saved Profile", lines=10) |
| | create_btn.click(fn=create_student, inputs=list(fields.values()), outputs=[status_new, preview_new, student_dd]) |
| |
|
| | with gr.Tab("✏️ Update Student"): |
| | target_id = gr.Dropdown(label="Student", choices=list_student_ids() or []) |
| | load_btn = gr.Button("Load Profile") |
| | upd_fields = {k: gr.Textbox(label=k.replace("_", " ").title()) for k in FIELDS} |
| | save_btn = gr.Button("Save Changes", variant="primary") |
| | status_upd = gr.Textbox(label="Status") |
| | preview_upd = gr.Textbox(label="Updated Profile", lines=10) |
| | load_btn.click(fn=load_student_to_form, inputs=[target_id], outputs=list(upd_fields.values())) |
| | save_btn.click(fn=apply_update, inputs=[target_id]+list(upd_fields.values()), outputs=[status_upd, preview_upd]) |
| |
|
| | if __name__ == "__main__": |
| | demo.launch() |