Update app.py
Browse files
app.py
CHANGED
|
@@ -1,7 +1,11 @@
|
|
| 1 |
# app.py
|
| 2 |
# ThinkPal โ Hugging Face Space (Gradio)
|
| 3 |
|
| 4 |
-
import os
|
|
|
|
|
|
|
|
|
|
|
|
|
| 5 |
from difflib import get_close_matches, SequenceMatcher
|
| 6 |
|
| 7 |
import gradio as gr
|
|
@@ -29,23 +33,27 @@ else:
|
|
| 29 |
# -----------------------------
|
| 30 |
# Storage helpers (JSON)
|
| 31 |
# -----------------------------
|
| 32 |
-
def load_students()
|
| 33 |
if not os.path.exists(DATA_FILE):
|
| 34 |
return {}
|
| 35 |
with open(DATA_FILE, "r", encoding="utf-8") as f:
|
| 36 |
return json.load(f)
|
| 37 |
|
| 38 |
-
|
|
|
|
| 39 |
with open(DATA_FILE, "w", encoding="utf-8") as f:
|
| 40 |
json.dump(data, f, indent=2, ensure_ascii=False)
|
| 41 |
|
| 42 |
-
|
|
|
|
| 43 |
return sorted(load_students().keys())
|
| 44 |
|
| 45 |
-
|
|
|
|
| 46 |
return load_students().get(student_id)
|
| 47 |
|
| 48 |
-
|
|
|
|
| 49 |
students = load_students()
|
| 50 |
# unique random id (short)
|
| 51 |
new_id = f"student_{uuid.uuid4().hex[:8]}"
|
|
@@ -53,7 +61,8 @@ def add_student(data: dict) -> str:
|
|
| 53 |
save_students(students)
|
| 54 |
return new_id
|
| 55 |
|
| 56 |
-
|
|
|
|
| 57 |
students = load_students()
|
| 58 |
if student_id not in students:
|
| 59 |
return f"โ {student_id} not found."
|
|
@@ -106,16 +115,18 @@ FAQ_SYNONYMS = [
|
|
| 106 |
"community", "mentors", "progress tracking", "insights meaning"
|
| 107 |
]
|
| 108 |
|
| 109 |
-
|
| 110 |
-
|
|
|
|
| 111 |
# remove diacritics / normalize Arabic
|
| 112 |
t = "".join(c for c in unicodedata.normalize("NFD", t) if unicodedata.category(c) != "Mn")
|
| 113 |
t = re.sub(r"[^\w\s\u0600-\u06FF]", " ", t)
|
| 114 |
t = re.sub(r"\s+", " ", t)
|
| 115 |
return t
|
| 116 |
|
| 117 |
-
|
| 118 |
-
|
|
|
|
| 119 |
return None
|
| 120 |
ui = _normalize(user_input)
|
| 121 |
|
|
@@ -133,10 +144,9 @@ def find_faq_answer(user_input: str, cutoff: float = 0.6) -> str | None:
|
|
| 133 |
best_q = max(candidates, key=lambda q: SequenceMatcher(None, ui, _normalize(q)).ratio())
|
| 134 |
if SequenceMatcher(None, ui, _normalize(best_q)).ratio() >= cutoff:
|
| 135 |
# map a synonym to a reasonable FAQ (heuristic)
|
| 136 |
-
# for stopping/resume
|
| 137 |
if any(k in ui for k in ["ูุต ุงูุทุฑูู", "ูู ูููุช", "stop halfway", "resume"]):
|
| 138 |
-
|
| 139 |
-
|
| 140 |
if best_q in FAQS:
|
| 141 |
return FAQS[best_q]
|
| 142 |
return None
|
|
@@ -165,7 +175,8 @@ Sections required:
|
|
| 165 |
4) Milestones (by Phase)
|
| 166 |
"""
|
| 167 |
|
| 168 |
-
|
|
|
|
| 169 |
mapping = [
|
| 170 |
("learning_style", "Learning Style"),
|
| 171 |
("academic_progress", "Academic Progress"),
|
|
@@ -183,14 +194,15 @@ def _compose_profile(student: dict) -> str:
|
|
| 183 |
]
|
| 184 |
parts = []
|
| 185 |
for key, label in mapping:
|
| 186 |
-
val = student.get(key)
|
| 187 |
if isinstance(val, list):
|
| 188 |
val = ", ".join(val)
|
| 189 |
if val:
|
| 190 |
parts.append(f"{label}: {val}")
|
| 191 |
return " | ".join(parts) if parts else "No student data provided."
|
| 192 |
|
| 193 |
-
|
|
|
|
| 194 |
profile = _compose_profile(student or {})
|
| 195 |
prompt = f"""Student Profile: {profile}
|
| 196 |
|
|
@@ -209,9 +221,11 @@ Formatting:
|
|
| 209 |
except Exception as e:
|
| 210 |
return f"(Gemini error fallback) {str(e)[:160]}"
|
| 211 |
# fallback (no API key)
|
| 212 |
-
|
|
|
|
|
|
|
| 213 |
|
| 214 |
-
def generate_ai_insights(student
|
| 215 |
profile = _compose_profile(student or {})
|
| 216 |
prompt = f"""Analyze the following student profile and provide insights:
|
| 217 |
{profile}
|
|
@@ -233,7 +247,7 @@ Requirements:
|
|
| 233 |
# -----------------------------
|
| 234 |
# Chat logic
|
| 235 |
# -----------------------------
|
| 236 |
-
def chat(student_id
|
| 237 |
"""Returns (roadmap_text, insights_text, chatbot_response_text)"""
|
| 238 |
roadmap, insights, reply = "", "", ""
|
| 239 |
student = get_student(student_id)
|
|
@@ -269,6 +283,7 @@ ALL_FIELDS = [
|
|
| 269 |
"preferred_study_environment", "community_groups"
|
| 270 |
]
|
| 271 |
|
|
|
|
| 272 |
def create_student(
|
| 273 |
learning_style, academic_progress, personality, interests, goals, level,
|
| 274 |
preferred_methods, iq_level, eq_level, decision_making_style,
|
|
@@ -297,7 +312,8 @@ def create_student(
|
|
| 297 |
gr.Dropdown.update(choices=list_student_ids(), value=new_id)
|
| 298 |
)
|
| 299 |
|
| 300 |
-
|
|
|
|
| 301 |
s = get_student(student_id)
|
| 302 |
if not s:
|
| 303 |
# return empties
|
|
@@ -318,6 +334,7 @@ def load_student_to_form(student_id: str):
|
|
| 318 |
", ".join(s.get("community_groups", [])),
|
| 319 |
]
|
| 320 |
|
|
|
|
| 321 |
def apply_update(
|
| 322 |
student_id,
|
| 323 |
learning_style, academic_progress, personality, interests, goals, level,
|
|
@@ -458,6 +475,10 @@ with gr.Blocks(theme=THEME, css="""
|
|
| 458 |
fn=load_student_to_form,
|
| 459 |
inputs=[target_id],
|
| 460 |
outputs=[u_learning_style, u_academic_progress, u_personality,
|
|
|
|
|
|
|
|
|
|
|
|
|
| 461 |
u_interests, u_goals, u_level, u_preferred_methods,
|
| 462 |
u_iq_level, u_eq_level, u_decision_style, u_motivation_level,
|
| 463 |
u_study_env, u_community_groups]
|
|
|
|
| 1 |
# app.py
|
| 2 |
# ThinkPal โ Hugging Face Space (Gradio)
|
| 3 |
|
| 4 |
+
import os
|
| 5 |
+
import json
|
| 6 |
+
import uuid
|
| 7 |
+
import re
|
| 8 |
+
import unicodedata
|
| 9 |
from difflib import get_close_matches, SequenceMatcher
|
| 10 |
|
| 11 |
import gradio as gr
|
|
|
|
| 33 |
# -----------------------------
|
| 34 |
# Storage helpers (JSON)
|
| 35 |
# -----------------------------
|
| 36 |
+
def load_students():
|
| 37 |
if not os.path.exists(DATA_FILE):
|
| 38 |
return {}
|
| 39 |
with open(DATA_FILE, "r", encoding="utf-8") as f:
|
| 40 |
return json.load(f)
|
| 41 |
|
| 42 |
+
|
| 43 |
+
def save_students(data):
|
| 44 |
with open(DATA_FILE, "w", encoding="utf-8") as f:
|
| 45 |
json.dump(data, f, indent=2, ensure_ascii=False)
|
| 46 |
|
| 47 |
+
|
| 48 |
+
def list_student_ids():
|
| 49 |
return sorted(load_students().keys())
|
| 50 |
|
| 51 |
+
|
| 52 |
+
def get_student(student_id):
|
| 53 |
return load_students().get(student_id)
|
| 54 |
|
| 55 |
+
|
| 56 |
+
def add_student(data):
|
| 57 |
students = load_students()
|
| 58 |
# unique random id (short)
|
| 59 |
new_id = f"student_{uuid.uuid4().hex[:8]}"
|
|
|
|
| 61 |
save_students(students)
|
| 62 |
return new_id
|
| 63 |
|
| 64 |
+
|
| 65 |
+
def update_student(student_id, updates):
|
| 66 |
students = load_students()
|
| 67 |
if student_id not in students:
|
| 68 |
return f"โ {student_id} not found."
|
|
|
|
| 115 |
"community", "mentors", "progress tracking", "insights meaning"
|
| 116 |
]
|
| 117 |
|
| 118 |
+
|
| 119 |
+
def _normalize(text):
|
| 120 |
+
t = (text or "").lower().strip()
|
| 121 |
# remove diacritics / normalize Arabic
|
| 122 |
t = "".join(c for c in unicodedata.normalize("NFD", t) if unicodedata.category(c) != "Mn")
|
| 123 |
t = re.sub(r"[^\w\s\u0600-\u06FF]", " ", t)
|
| 124 |
t = re.sub(r"\s+", " ", t)
|
| 125 |
return t
|
| 126 |
|
| 127 |
+
|
| 128 |
+
def find_faq_answer(user_input, cutoff=0.6):
|
| 129 |
+
if not user_input:
|
| 130 |
return None
|
| 131 |
ui = _normalize(user_input)
|
| 132 |
|
|
|
|
| 144 |
best_q = max(candidates, key=lambda q: SequenceMatcher(None, ui, _normalize(q)).ratio())
|
| 145 |
if SequenceMatcher(None, ui, _normalize(best_q)).ratio() >= cutoff:
|
| 146 |
# map a synonym to a reasonable FAQ (heuristic)
|
|
|
|
| 147 |
if any(k in ui for k in ["ูุต ุงูุทุฑูู", "ูู ูููุช", "stop halfway", "resume"]):
|
| 148 |
+
# key exists in FAQS
|
| 149 |
+
return FAQS.get("ูู ูููุช ูู ูุต ุงูุทุฑููุ") or FAQS.get("ูู ูููุช ูู ูุต ุงูุทุฑูู") or FAQS.get("What if I stop halfway?")
|
| 150 |
if best_q in FAQS:
|
| 151 |
return FAQS[best_q]
|
| 152 |
return None
|
|
|
|
| 175 |
4) Milestones (by Phase)
|
| 176 |
"""
|
| 177 |
|
| 178 |
+
|
| 179 |
+
def _compose_profile(student):
|
| 180 |
mapping = [
|
| 181 |
("learning_style", "Learning Style"),
|
| 182 |
("academic_progress", "Academic Progress"),
|
|
|
|
| 194 |
]
|
| 195 |
parts = []
|
| 196 |
for key, label in mapping:
|
| 197 |
+
val = student.get(key) if student else None
|
| 198 |
if isinstance(val, list):
|
| 199 |
val = ", ".join(val)
|
| 200 |
if val:
|
| 201 |
parts.append(f"{label}: {val}")
|
| 202 |
return " | ".join(parts) if parts else "No student data provided."
|
| 203 |
|
| 204 |
+
|
| 205 |
+
def get_gemini_response(query, student=None):
|
| 206 |
profile = _compose_profile(student or {})
|
| 207 |
prompt = f"""Student Profile: {profile}
|
| 208 |
|
|
|
|
| 221 |
except Exception as e:
|
| 222 |
return f"(Gemini error fallback) {str(e)[:160]}"
|
| 223 |
# fallback (no API key)
|
| 224 |
+
# keep simulated short to avoid huge outputs in UI
|
| 225 |
+
return f"(Simulated) {prompt[:400]}..."
|
| 226 |
+
|
| 227 |
|
| 228 |
+
def generate_ai_insights(student):
|
| 229 |
profile = _compose_profile(student or {})
|
| 230 |
prompt = f"""Analyze the following student profile and provide insights:
|
| 231 |
{profile}
|
|
|
|
| 247 |
# -----------------------------
|
| 248 |
# Chat logic
|
| 249 |
# -----------------------------
|
| 250 |
+
def chat(student_id, message):
|
| 251 |
"""Returns (roadmap_text, insights_text, chatbot_response_text)"""
|
| 252 |
roadmap, insights, reply = "", "", ""
|
| 253 |
student = get_student(student_id)
|
|
|
|
| 283 |
"preferred_study_environment", "community_groups"
|
| 284 |
]
|
| 285 |
|
| 286 |
+
|
| 287 |
def create_student(
|
| 288 |
learning_style, academic_progress, personality, interests, goals, level,
|
| 289 |
preferred_methods, iq_level, eq_level, decision_making_style,
|
|
|
|
| 312 |
gr.Dropdown.update(choices=list_student_ids(), value=new_id)
|
| 313 |
)
|
| 314 |
|
| 315 |
+
|
| 316 |
+
def load_student_to_form(student_id):
|
| 317 |
s = get_student(student_id)
|
| 318 |
if not s:
|
| 319 |
# return empties
|
|
|
|
| 334 |
", ".join(s.get("community_groups", [])),
|
| 335 |
]
|
| 336 |
|
| 337 |
+
|
| 338 |
def apply_update(
|
| 339 |
student_id,
|
| 340 |
learning_style, academic_progress, personality, interests, goals, level,
|
|
|
|
| 475 |
fn=load_student_to_form,
|
| 476 |
inputs=[target_id],
|
| 477 |
outputs=[u_learning_style, u_academic_progress, u_personality,
|
| 478 |
+
u_interests, u_goals, u_level, u_preferred_methods,
|
| 479 |
+
u_iq_level, u_eq_level, u_decision_style, u_motivation_level,
|
| 480 |
+
u_study_env, u_u_community_groups] if False else
|
| 481 |
+
[u_learning_style, u_academic_progress, u_personality,
|
| 482 |
u_interests, u_goals, u_level, u_preferred_methods,
|
| 483 |
u_iq_level, u_eq_level, u_decision_style, u_motivation_level,
|
| 484 |
u_study_env, u_community_groups]
|