Asem75 commited on
Commit
e7a4f4d
ยท
verified ยท
1 Parent(s): 5f15195

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +548 -166
app.py CHANGED
@@ -1,113 +1,202 @@
1
  import os
 
2
  import json
 
 
3
  import gradio as gr
4
  from gradio_client import Client
5
  from huggingface_hub import HfApi, hf_hub_download
6
  import torch
7
- from transformers import AutoModelForCausalLM, AutoTokenizer
 
 
 
 
8
 
9
  # โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•
10
  # ุงู„ุฅุนุฏุงุฏุงุช ูˆุงู„ุฑูˆุงุจุท ุจูŠู† ุงู„ู…ุณุงุญุงุช
11
- # โš ๏ธ ุชุฃูƒุฏ ู…ู† ู‡ุฐู‡ ุงู„ุฃุณู…ุงุก ุจุงู„ุถุจุท โ€” ุงุณุชูู†ุชุฌุช ู…ู† ุฑุณุงู„ุชูƒุŒ ุตุญู‘ุญู‡ุง ุฅุฐุง ุงุฎุชู„ูุช
12
  # โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•
13
  RADAR_SPACE_URL = "Asem75/Aiocr_Radar"
14
- MEDIA_SPACE_URL = "Asem75/My_teacher_enemation" # ู…ุณุงุญุฉ ุงู„ุตูˆุฑ ูˆุงู„ุฃู†ู…ูŠุดู† (SD-Turbo + AnimateDiff)
15
- CONTROLLER_SPACE_URL = "Asem75/My_teacher_controller" # ู…ุณุงุญุฉ ุชู‡ูŠุฆุฉ ุงู„ุฎุฒู†ุฉ (ู„ู„ู…ุฑุฌุนูŠุฉ ูู‚ุทุŒ ุงู„ุชู‡ูŠุฆุฉ ู…ูุถู…ูŽู‘ู†ุฉ ู‡ู†ุง ู…ุจุงุดุฑุฉ)
16
  VAULT_REPO_ID = "Asem75/aiocr_asistant"
17
 
18
  MODEL_ID = "Qwen/Qwen2.5-3B-Instruct"
 
 
19
  DEVICE = "cpu"
 
20
 
21
  RADAR_API_KEY = os.environ.get("INTERNAL_API_KEY", "").strip()
22
  HF_TOKEN = os.environ.get("HF_TOKEN", "").strip()
23
 
24
  if not HF_TOKEN:
25
- print("โš ๏ธ ุชู†ุจูŠู‡: HF_TOKEN ุบูŠุฑ ู…ุถุจูˆุท ููŠ ุฃุณุฑุงุฑ ู‡ุฐู‡ ุงู„ู…ุณุงุญุฉ โ€” ุงู„ู‚ุฑุงุกุฉ/ุงู„ูƒุชุงุจุฉ ููŠ ุงู„ุฎุฒู†ุฉ ู‚ุฏ ุชูุดู„ ุฅุฐุง ูƒุงู†ุช ุฎุงุตุฉ.")
26
 
27
  _hf_api = HfApi(token=HF_TOKEN) if HF_TOKEN else HfApi()
28
 
29
- print("โณ ุชุญู…ูŠู„ ู†ู…ูˆุฐุฌ ูƒูˆูŠู† (ุงู„ุนู‚ู„ ุงู„ู…ุฏุจุฑ)...")
30
- _tokenizer = AutoTokenizer.from_pretrained(MODEL_ID)
31
- _model = AutoModelForCausalLM.from_pretrained(
32
  MODEL_ID, torch_dtype=torch.float32, device_map="cpu", low_cpu_mem_usage=True
33
  )
34
  print("โœ… ูƒูˆูŠู† ุฌุงู‡ุฒ")
35
 
36
- # โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•
37
- # ุชู‡ูŠุฆุฉ ู…ู„ูุงุช ุงู„ุฎุฒู†ุฉ โ€” ู†ูุณ ุจู†ูŠุฉ ูƒูˆุฏ "controller" ุงู„ุฐูŠ ุฃุฑุณู„ุชู‡ุŒ ู…ูุถู…ูŽู‘ู†ุฉ
38
- # ู‡ู†ุง ู…ุจุงุดุฑุฉ (ู„ุง ุชุนุชู…ุฏ ุนู„ู‰ ุงุณุชุฏุนุงุก ู…ุณุงุญุฉ ู‚ุฏ ุชูƒูˆู† ู†ุงุฆู…ุฉ ุนู†ุฏ ูƒู„ ุฅู‚ู„ุงุน).
39
- #
40
- # ๐Ÿ› ๏ธ ุงู„ุฅุตู„ุงุญ ุงู„ุฐูŠ ูˆุฌุฏุชู‡ ููŠ ู…ุฑุงุฌุนุชูŠ: ุฑูุน ุงู„ู…ู„ู ุงู„ุจุฏูŠู„ (ุนู†ุฏ ุนุฏู… ูˆุฌูˆุฏู‡)
41
- # ู„ู… ูŠูƒู† ู…ุญู…ูŠุงู‹ ุจู€ try/except ุฎุงุต ุจู‡ โ€” ุฃูŠ ูุดู„ ููŠ ุงู„ุฑูุน (ุชูˆูƒู† ุฎุงุทุฆ/ุตู„ุงุญูŠุงุช)
42
- # ูƒุงู† ุณูŠุชุณุจุจ ุจุงู†ู‡ูŠุงุฑ ุงู„ู…ุณุงุญุฉ ุจุงู„ูƒุงู…ู„ ุนู†ุฏ ุงู„ุฅู‚ู„ุงุน. ุงู„ุขู† ู…ุญู…ูŠ ูˆูŠูุณุฌูŽู‘ู„
43
- # ุงู„ูุดู„ ููŠ ุงู„ุณุฌู„ ุจุฏู„ ุฅุณู‚ุงุท ุงู„ุชุทุจูŠู‚.
44
- # โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•
45
- VAULT_STRUCTURE = {
46
- "curriculum_plans/lessons_division.json": {
47
- "description": "ุชู‚ุณูŠู… ุงู„ุฏุฑูˆุณ ูˆุงู„ู…ูˆุงุฏ ุงู„ุชุนู„ูŠู…ูŠุฉ ุจูŠู† ุงู„ุนู„ู…ูŠ ูˆุงู„ุฃุฏุจูŠ ูˆุงู„ูˆุณูˆู…",
48
- "tracks": {"scientific": [], "literary": []}
49
- },
50
- "curriculum_plans/co_planning_qwen.json": {
51
- "description": "ู…ู„ู ุงู„ุชุฎุทูŠุท ุงู„ู…ุดุชุฑูƒ ู…ุน ู…ูˆุฏูŠู„ ูƒูˆูŠู† ู„ุชุญุฏูŠุฏ ุงู„ุฃู‡ุฏุงู ุงู„ุชุฑุจูˆูŠุฉ",
52
- "qwen_guidelines": {}, "shared_plans": []
53
- },
54
- "learning_gaps/student_levels.json": {
55
- "description": "ุณุฌู„ ู…ุณุชูˆูŠุงุช ุงู„ุทู„ุงุจ ูˆุชุตู†ูŠู ู…ู‡ุงุฑุงุชู‡ู… ุงู„ุชุนู„ูŠู…ูŠุฉ",
56
- "levels": {"beginner": [], "intermediate": [], "advanced": []}
57
- },
58
- "learning_gaps/gap_analysis.json": {
59
- "description": "ุชุญู„ูŠู„ ุงู„ูุฌูˆุฉ ุงู„ุชุนู„ูŠู…ูŠุฉ ูˆุฎุทุท ุงู„ู…ุนุงู„ุฌุฉ ุงู„ูุฑุฏูŠุฉ ู„ู„ุทู„ุงุจ",
60
- "detected_gaps": [], "remedial_actions": []
61
- },
62
- "teacher_student_interaction/interaction_logs.json": {
63
- "description": "ุณุฌู„ุงุช ุนู„ุงู‚ุฉ ุงู„ุทุงู„ุจ ุจุงู„ุฃุณุชุงุฐ ูˆุงู„ุฏุนู… ุงู„ุชูˆุฌูŠู‡ูŠ",
64
- "teacher_notes": [], "student_feedback": []
65
- },
66
- }
67
 
 
68
 
69
- def initialize_vault_files():
70
- log_lines = []
71
- for file_path, default_content in VAULT_STRUCTURE.items():
72
- try:
73
- hf_hub_download(repo_id=VAULT_REPO_ID, filename=file_path, repo_type="dataset", token=HF_TOKEN or None)
74
- log_lines.append(f"โœ… {file_path} ู…ูˆุฌูˆุฏ ู…ุณุจู‚ุงู‹ ูˆุฌุงู‡ุฒ.")
75
- except Exception:
76
- try:
77
- local_filename = f"/tmp/{os.path.basename(file_path)}"
78
- with open(local_filename, "w", encoding="utf-8") as f:
79
- json.dump(default_content, f, ensure_ascii=False, indent=4)
80
- _hf_api.upload_file(
81
- path_or_fileobj=local_filename, path_in_repo=file_path,
82
- repo_id=VAULT_REPO_ID, repo_type="dataset", token=HF_TOKEN or None,
83
- )
84
- os.remove(local_filename)
85
- log_lines.append(f"๐Ÿš€ ุชู… ุฅู†ุดุงุก ูˆุฑูุน: {file_path}")
86
- except Exception as e_upload:
87
- log_lines.append(f"โŒ ูุดู„ ุฅู†ุดุงุก {file_path}: {e_upload}")
88
- return "\n".join(log_lines)
89
-
90
-
91
- print("โณ ูุญุต ูˆุชู‡ูŠุฆุฉ ู…ู„ูุงุช ุงู„ุฎุฒู†ุฉ...")
92
- VAULT_INIT_LOG = initialize_vault_files()
93
- print(VAULT_INIT_LOG)
94
-
95
- # โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•
96
- # ุงู„ู…ุนู„ู…ูˆู† ูˆุงู„ุฃุตูˆุงุช (Tab 1 โ€” ุญุฌุฒ ู…ุจุฏุฆูŠุŒ ุชุทูˆูŠุฑ ูƒุงู…ู„ ู„ุงุญู‚ุงู‹)
97
- # โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•
98
- TEACHERS = [
99
- ("ุงู„ุฃุณุชุงุฐ ุนุจูˆุฏ", "ar-JO-TaimNeural"),
100
- ("ุงู„ุฃุณุชุงุฐุฉ ุณู„ู…ู‰", "ar-EG-SalmaNeural"),
101
- ("ุงู„ุฃุณุชุงุฐ ุญุงู…ุฏ", "ar-SA-HamedNeural"),
102
  ]
103
 
 
 
 
 
 
 
 
 
 
 
 
 
104
  # โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•
105
- # ุงู„ุฑุจุท ุจู…ุณุงุญุฉ ุงู„ุฑุงุฏุงุฑ (ุตูˆุช) โ€” ุจู†ูุณ ู†ู…ุท ุงู„ุญู…ุงูŠุฉ ุจู…ูุชุงุญ ุณุฑูŠ ุงู„ู…ูุนุชู…ุฏ ุณุงุจู‚ุงู‹
106
  # โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•
107
- def synth_via_radar(text, speaker_name):
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
108
  try:
109
  client = Client(RADAR_SPACE_URL)
110
- audio_url = client.predict(text, speaker_name, RADAR_API_KEY, api_name="/synth_arabic")
111
  if audio_url and isinstance(audio_url, (list, tuple)):
112
  audio_url = audio_url[0]
113
  return str(audio_url).strip() if audio_url else None
@@ -115,135 +204,428 @@ def synth_via_radar(text, speaker_name):
115
  print(f"โš ๏ธ ุฎุทุฃ ุงู„ุฑุงุฏุงุฑ: {e}")
116
  return None
117
 
118
-
119
  # โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•
120
- # ุงู„ุฑุจุท ุจู…ุณุงุญุฉ ุงู„ูˆุณุงุฆุท (ุตูˆุฑ/ุฃู†ู…ูŠุดู†)
121
- # โš ๏ธ ู…ู„ุงุญุธุฉ ุฃู…ุงู†: ู†ู‚ุทุชุง API ู‡ุฐู‡ (generate_image, generate_animation) ููŠ
122
- # ู…ุณุงุญุฉ ุงู„ูˆุณุงุฆุท ุบูŠุฑ ู…ุญู…ูŠุชูŠู† ุจุฃูŠ ู…ูุชุงุญ ุณุฑูŠ ุญุงู„ูŠุงู‹ (ุจุฎู„ุงู ุงู„ุฑุงุฏุงุฑ). ุฅุฐุง
123
- # ุฃุฑุฏุช ุญู…ุงูŠุชู‡ู…ุง ุจู†ูุณ ู…ุจุฏุฃ INTERNAL_API_KEYุŒ ุฃุฎุจุฑู†ูŠ ูˆุฃุถูŠูู‡ ู‡ู†ุงูƒ ุฃูŠุถุงู‹.
124
  # โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•
125
- def generate_image_via_media(prompt):
126
  try:
127
- client = Client(MEDIA_SPACE_URL)
128
- return client.predict(prompt, api_name="/generate_image")
 
 
 
 
 
 
129
  except Exception as e:
130
- print(f"โš ๏ธ ุฎุทุฃ ู…ุณุงุญุฉ ุงู„ูˆุณุงุฆุท (ุตูˆุฑุฉ): {e}")
131
- return None
132
 
133
 
134
- def generate_animation_via_media(prompt):
 
 
135
  try:
136
- client = Client(MEDIA_SPACE_URL)
137
- return client.predict(prompt, api_name="/generate_animation")
 
 
138
  except Exception as e:
139
- print(f"โš ๏ธ ุฎุทุฃ ู…ุณุงุญุฉ ุงู„ูˆุณุงุฆุท (ุฃู†ู…ูŠุดู†): {e}")
140
  return None
141
 
142
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
143
  # โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•
144
- # ุงู„ุนู‚ู„ ุงู„ู…ุฏุจุฑ (ูƒูˆูŠู†)
145
  # โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•
146
- def ask_teacher(prompt, history):
147
- history = history or []
148
- messages = [{
149
- "role": "system",
150
- "content": "ุฃู†ุช ูƒูˆูŠู†ุŒ ุงู„ุนู‚ู„ ุงู„ู…ุฏุจุฑ ู„ู…ู†ุตุฉ ุชุนู„ูŠู…ูŠุฉ ุนุฑุจูŠุฉ. ุชุณุงุนุฏ ููŠ ุตูŠุงุบุฉ ุฏุฑูˆุณ ูˆู…ุญุชูˆู‰ ุชุนู„ูŠู…ูŠ ู‚ุตูŠุฑ ูˆูˆุงุถุญ ุจุงู„ุนุฑุจูŠุฉุŒ ูˆุชุฌูŠุจ ุจุฅูŠุฌุงุฒ ูˆูˆุถูˆุญ."
151
- }]
152
- messages.extend(history)
153
- messages.append({"role": "user", "content": prompt})
154
-
155
- text_prompt = _tokenizer.apply_chat_template(messages, tokenize=False, add_generation_prompt=True)
156
- inputs = _tokenizer([text_prompt], return_tensors="pt").to(DEVICE)
157
 
158
- with torch.no_grad():
159
- out_ids = _model.generate(
160
- **inputs, max_new_tokens=200, temperature=0.5, do_sample=True,
161
- repetition_penalty=1.15, pad_token_id=_tokenizer.eos_token_id
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
162
  )
163
- out_ids = [o[len(i):] for i, o in zip(inputs.input_ids, out_ids)]
164
- response = _tokenizer.batch_decode(out_ids, skip_special_tokens=True)[0].strip()
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
165
 
166
- new_history = history + [{"role": "user", "content": prompt}, {"role": "assistant", "content": response}]
167
- return response, new_history
168
 
 
 
169
 
170
- def integration_test(prompt, teacher_choice, gen_image, gen_anim, history_state):
171
- if not prompt or not prompt.strip():
172
- return "โš ๏ธ ุงูƒุชุจ ู†ุตุงู‹ ุฃูˆู„ุงู‹", history_state, None, None, None
173
 
174
- response, new_history = ask_teacher(prompt, history_state)
175
- speaker_name = dict(TEACHERS).get(teacher_choice, "ar-JO-TaimNeural")
176
 
177
- audio_path = synth_via_radar(response, speaker_name)
178
- image_path = generate_image_via_media(response) if gen_image else None
179
- anim_path = generate_animation_via_media(response) if gen_anim else None
180
 
181
- return response, new_history, audio_path, image_path, anim_path
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
182
 
183
 
184
  # โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•
185
- # ุงู„ูˆุงุฌู‡ุฉ: ุณุชุฉ ุชุจูˆูŠุจุงุช ุญุฌุฒ ู…ูƒุงู† + ู„ูˆุญุฉ ูุญุต ุงู„ุฑุจุท
186
  # โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•
187
- with gr.Blocks(title="My Teacher โ€” ุงู„ู…ู†ุตุฉ ุงู„ุฑุฆูŠุณูŠุฉ", theme=gr.themes.Soft()) as demo:
 
188
  gr.Markdown("# ๐ŸŽ“ ุงู„ู…ู†ุตุฉ ุงู„ุชุนู„ูŠู…ูŠุฉ ุงู„ุฐูƒูŠุฉ ุงู„ู…ูˆุญุฏุฉ")
189
 
190
- qwen_history_state = gr.State([])
 
 
 
191
 
192
  with gr.Tabs():
193
- with gr.Tab("๐Ÿ‘จโ€๐Ÿซ ุฅุนุฏุงุฏุงุช ุงู„ู…ุนู„ู… ูˆุงู„ุตูˆุช"):
194
- gr.Markdown(
195
- "๐Ÿšง **ู‚ูŠุฏ ุงู„ุชุทูˆูŠุฑ.** ุณูŠุชู… ู‡ู†ุง: ุชุตูุญ ุงู„ู…ุนู„ู…ูŠู† ุงู„ู…ุชุงุญูŠู†ุŒ ูˆุงุฎุชูŠุงุฑ "
196
- "ุงู„ุฃุณุชุงุฐ ุงู„ู…ู†ุงุณุจุŒ ูˆุฑุจุท ูƒู„ ู…ุนู„ู… ุจุฎุงู…ุฉ ุตูˆุชูŠุฉ ุนุจุฑ edge-tts/ุงู„ุฑุงุฏุงุฑ."
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
197
  )
198
 
199
- with gr.Tab("๐ŸŽฌ ุงู„ููŠุฏูŠูˆู‡ุงุช ุงู„ุชุนู„ูŠู…ูŠุฉ ุงู„ู…ุณุฌู„ุฉ"):
200
- gr.Markdown("๐Ÿšง **ู‚ูŠุฏ ุงู„ุชุทูˆูŠุฑ.** ู…ูƒุชุจุฉ ุงู„ุฏุฑูˆุณ ูˆุงู„ููŠุฏูŠูˆู‡ุงุช ุงู„ู…ุณุฌู„ุฉ ู…ุณุจู‚ุงู‹ ู„ู„ู…ุฑุงุฌุนุฉ.")
 
201
 
 
 
 
 
 
 
202
  with gr.Tab("๐Ÿ—‚๏ธ ุฎุทุท ุงู„ุฏุฑูˆุณ ูˆุงู„ุชู‚ุณูŠู…"):
203
- gr.Markdown(
204
- "๐Ÿšง **ู‚ูŠุฏ ุงู„ุชุทูˆูŠุฑ.** ู†ุงูุฐุฉ ุชูุงุนู„ูŠุฉ ู…ุน ูƒูˆูŠู† ู„ู„ุชุฎุทูŠุท ุงู„ู…ุดุชุฑูƒุŒ "
205
- "ูˆุชู‚ุณูŠู… ุงู„ู…ูˆุงุฏ ุจูŠู† ุงู„ุนู„ู…ูŠ ูˆุงู„ุฃุฏุจูŠ."
206
- )
207
 
208
  with gr.Tab("๐Ÿ“Š ุงู„ู…ุณุชูˆูŠุงุช ูˆุชุญู„ูŠู„ ุงู„ูุฌูˆุฉ ุงู„ุชุนู„ูŠู…ูŠุฉ"):
209
- gr.Markdown(
210
- "๐Ÿšง **ู‚ูŠุฏ ุงู„ุชุทูˆูŠุฑ.** ุชุชุจุน ู…ุณุชูˆู‰ ุงู„ุทุงู„ุจ ูˆุชุญู„ูŠู„ ุงู„ุฃุฎุทุงุก "
211
- "(ุจุงู„ุงุณุชุนุงู†ุฉ ุจู€ CAMeL-BERT) ูˆุฎุทุท ุณุฏ ุงู„ูุฌูˆุงุช."
212
- )
213
 
214
  with gr.Tab("๐Ÿ’ฌ ุนู„ุงู‚ุฉ ุงู„ุทุงู„ุจ ูˆุงู„ุฃุณุชุงุฐ"):
215
- gr.Markdown("๐Ÿšง **ู‚ูŠุฏ ุงู„ุชุทูˆูŠุฑ.** ุงู„ุฑุณุงุฆู„ ุงู„ุชูˆุฌูŠู‡ูŠุฉุŒ ุงู„ุชู‚ูŠูŠู…ุงุชุŒ ูˆุงู„ุฏุนู… ุงู„ูุฑุฏูŠ.")
216
 
217
  with gr.Tab("๐Ÿ–ผ๏ธ ุฃุฑุดูŠู ูˆุฅุฏุงุฑุฉ ุงู„ูˆุณุงุฆุท"):
218
- gr.Markdown("๐Ÿšง **ู‚ูŠุฏ ุงู„ุชุทูˆูŠุฑ.** ูู‡ุฑุณุฉ ุงู„ุตูˆุฑ ูˆุงู„ุฃู†ู…ูŠุดู† ุงู„ู…ูˆู„ู‘ุฏุฉ ู„ู…ู†ุน ุฅุนุงุฏุฉ ุงู„ุชูˆู„ูŠุฏ.")
219
-
220
- with gr.Tab("๐Ÿ”— ู„ูˆุญุฉ ูุญุต ุงู„ุฑุจุท (API)"):
221
- gr.Markdown(
222
- "ุชุฌุฑุจุฉ ู…ุจุงุดุฑุฉ ุชูุซุจุช ุฃู† **ูƒูˆูŠู† + ุงู„ุฑุงุฏุงุฑ (ุตูˆุช) + ู…ุณุงุญุฉ ุงู„ูˆุณุงุฆุท (ุตูˆุฑุฉ/ุฃู†ู…ูŠุดู†)** "
223
- "ู…ุฑุจูˆุทูˆู† ูุนู„ูŠุงู‹ ุนุจุฑ APIุŒ ู‚ุจู„ ุจู†ุงุก ุงู„ุชุจูˆูŠุจุงุช ุงู„ุณุช ุจุงู„ุชูุตูŠู„."
224
- )
225
- prompt_in = gr.Textbox(label="โœ๏ธ ุงูƒุชุจ ููƒุฑุฉ ุฃูˆ ุณุคุงู„ุงู‹ ุชุนู„ูŠู…ูŠุงู‹", lines=3)
226
- teacher_pick = gr.Dropdown(choices=[t[0] for t in TEACHERS], value=TEACHERS[0][0], label="๐ŸŽ™๏ธ ุงุฎุชุฑ ุงู„ู…ุนู„ู…/ุงู„ุตูˆุช")
227
- with gr.Row():
228
- want_image = gr.Checkbox(label="๐Ÿ–ผ๏ธ ุชูˆู„ูŠุฏ ุตูˆุฑุฉ ุชูˆุถูŠุญูŠุฉ", value=False)
229
- want_anim = gr.Checkbox(label="๐ŸŽฌ ุชูˆู„ูŠุฏ ุฃู†ู…ูŠุดู† (ุฃุจุทุฃ ุจูƒุซูŠุฑ)", value=False)
230
- run_btn = gr.Button("๐Ÿš€ ุฌุฑู‘ุจ ุงู„ุฑุจุท ุงู„ูƒุงู…ู„", variant="primary")
231
-
232
- response_out = gr.Textbox(label="๐Ÿ“ ุฑุฏ ูƒูˆูŠู†", lines=4, interactive=False)
233
- audio_out = gr.Audio(label="๐Ÿ”Š ุงู„ุตูˆุช (ุนุจุฑ ุงู„ุฑุงุฏุงุฑ)", type="filepath", autoplay=True)
234
- with gr.Row():
235
- image_out = gr.Image(label="๐Ÿ–ผ๏ธ ุงู„ุตูˆุฑุฉ (ุนุจุฑ ู…ุณุงุญุฉ ุงู„ูˆุณุงุฆุท)")
236
- anim_out = gr.Image(label="๐ŸŽฌ ุงู„ุฃู†ู…ูŠุดู† GIF (ุนุจุฑ ู…ุณุงุญุฉ ุงู„ูˆุณุงุฆุท)")
237
-
238
- run_btn.click(
239
- fn=integration_test,
240
- inputs=[prompt_in, teacher_pick, want_image, want_anim, qwen_history_state],
241
- outputs=[response_out, qwen_history_state, audio_out, image_out, anim_out],
242
- api_name=False,
243
- )
244
-
245
- with gr.Accordion("๐Ÿ—„๏ธ ุณุฌู„ ุชู‡ูŠุฆุฉ ุงู„ุฎุฒู†ุฉ ุนู†ุฏ ุงู„ุฅู‚ู„ุงุน", open=False):
246
- gr.Textbox(value=VAULT_INIT_LOG, lines=8, interactive=False, show_label=False)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
247
 
248
  if __name__ == "__main__":
249
- demo.launch(server_name="0.0.0.0", server_port=7860)
 
1
  import os
2
+ import re
3
  import json
4
+ import time
5
+ import warnings
6
  import gradio as gr
7
  from gradio_client import Client
8
  from huggingface_hub import HfApi, hf_hub_download
9
  import torch
10
+ from PIL import Image
11
+ import sympy as sp
12
+ from transformers import AutoModelForCausalLM, AutoTokenizer, TrOCRProcessor, VisionEncoderDecoderModel
13
+
14
+ warnings.filterwarnings("ignore")
15
 
16
  # โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•
17
  # ุงู„ุฅุนุฏุงุฏุงุช ูˆุงู„ุฑูˆุงุจุท ุจูŠู† ุงู„ู…ุณุงุญุงุช
 
18
  # โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•
19
  RADAR_SPACE_URL = "Asem75/Aiocr_Radar"
20
+ CONTROLLER_SPACE_URL = "Asem75/My_teacher_controller"
21
+ MEDIA_SPACE_URL = "Asem75/My_teacher_enemation"
22
  VAULT_REPO_ID = "Asem75/aiocr_asistant"
23
 
24
  MODEL_ID = "Qwen/Qwen2.5-3B-Instruct"
25
+ WHISPER_MODEL = "openai/whisper-medium"
26
+ OCR_MODEL_ID = "RayR1/trocr-base-arabic-handwritten" # ุชุตุญูŠุญ: ุฑุงุฌุน ู…ู„ู Chat ู„ู„ุชูุตูŠู„
27
  DEVICE = "cpu"
28
+ MAX_LESSON_CHARS = 1200
29
 
30
  RADAR_API_KEY = os.environ.get("INTERNAL_API_KEY", "").strip()
31
  HF_TOKEN = os.environ.get("HF_TOKEN", "").strip()
32
 
33
  if not HF_TOKEN:
34
+ print("โš ๏ธ ุชู†ุจูŠู‡: HF_TOKEN ุบูŠุฑ ู…ุถุจูˆุท โ€” ุญูุธ ู…ู„ู ุงู„ุทุงู„ุจ ู‚ุฏ ูŠูุดู„.")
35
 
36
  _hf_api = HfApi(token=HF_TOKEN) if HF_TOKEN else HfApi()
37
 
38
+ print("โณ ุชุญู…ูŠู„ ู†ู…ูˆุฐุฌ ูƒูˆูŠู†...")
39
+ _llm_tokenizer = AutoTokenizer.from_pretrained(MODEL_ID)
40
+ _llm_model = AutoModelForCausalLM.from_pretrained(
41
  MODEL_ID, torch_dtype=torch.float32, device_map="cpu", low_cpu_mem_usage=True
42
  )
43
  print("โœ… ูƒูˆูŠู† ุฌุงู‡ุฒ")
44
 
45
+ # ู‚ุงุฆู…ุฉ ุฃุตูˆุงุช ู…ุงูŠูƒุฑูˆุณูˆูุช ุงู„ุนุฑุจูŠุฉ ุงู„ู…ูˆุซูŽู‘ู‚ุฉ (ู†ูุณ ุง๏ฟฝ๏ฟฝู‚ุงุฆู…ุฉ ุงู„ู…ุนุชู…ุฏุฉ ููŠ ุงู„ุฑุงุฏุงุฑ)
46
+ MICROSOFT_VOICES = [
47
+ "ar-EG-SalmaNeural", "ar-EG-ShakirNeural", "ar-JO-SanaNeural", "ar-JO-TaimNeural",
48
+ "ar-SA-ZariyahNeural", "ar-SA-HamedNeural", "ar-AE-FatimaNeural", "ar-AE-HamdanNeural",
49
+ "ar-IQ-RanaNeural", "ar-IQ-BasselNeural", "ar-LB-LaylaNeural", "ar-LB-RamiNeural",
50
+ "ar-SY-AmanyNeural", "ar-SY-LaithNeural", "ar-MA-MounaNeural", "ar-MA-JamalNeural",
51
+ "ar-TN-ReemNeural", "ar-TN-HediNeural", "ar-DZ-AminaNeural", "ar-DZ-IsmaelNeural",
52
+ "ar-LY-ImanNeural", "ar-LY-OmarNeural", "ar-KW-NouraNeural", "ar-KW-FahedNeural",
53
+ "ar-QA-AmalNeural", "ar-QA-MoazNeural", "ar-OM-AyshaNeural", "ar-OM-AbdullahNeural",
54
+ "ar-BH-LailaNeural", "ar-BH-AliNeural", "ar-YE-MaryamNeural", "ar-YE-SalehNeural",
55
+ ]
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
56
 
57
+ VOICE_ROLES = ["ู…ุญุงูˆุฑ", "ุดุฑุญ", "ุฃุณุฆู„ุฉ"]
58
 
59
+ DEFAULT_TEACHER_PREFS = [
60
+ {"name": "ุงู„ู…ุนู„ู… ุงู„ุฃูˆู„", "ู…ุญุงูˆุฑ": "ar-JO-TaimNeural", "ุดุฑุญ": "ar-SA-HamedNeural", "ุฃุณุฆู„ุฉ": "ar-EG-ShakirNeural"},
61
+ {"name": "ุงู„ู…ุนู„ู…ุฉ ุงู„ุซุงู†ูŠุฉ", "ู…ุญุงูˆุฑ": "ar-EG-SalmaNeural", "ุดุฑุญ": "ar-AE-FatimaNeural", "ุฃุณุฆู„ุฉ": "ar-LB-LaylaNeural"},
62
+ {"name": "ุงู„ู…ุนู„ู… ุงู„ุซุงู„ุซ", "ู…ุญุงูˆุฑ": "ar-SA-HamedNeural", "ุดุฑุญ": "ar-JO-TaimNeural", "ุฃุณุฆู„ุฉ": "ar-IQ-BasselNeural"},
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
63
  ]
64
 
65
+ THEME_CSS = {
66
+ "๐ŸŒž ู†ู‡ุงุฑูŠ": "",
67
+ "๐ŸŒ™ ู„ูŠู„ูŠ": "body, .gradio-container { background-color:#0b0f19 !important; color:#f3f4f6 !important; } .gr-button { background:#1e293b !important; color:#f3f4f6 !important; }",
68
+ "๐ŸŽจ ู…ู„ูˆู‘ู†": "body, .gradio-container { background: linear-gradient(135deg,#fef3c7,#dbeafe) !important; } .gr-button { background: linear-gradient(135deg,#f59e0b,#3b82f6) !important; color:white !important; }",
69
+ }
70
+
71
+
72
+ def apply_theme(theme_name):
73
+ css = THEME_CSS.get(theme_name, "")
74
+ return f"<style>{css}</style>"
75
+
76
+
77
  # โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•
78
+ # ๐Ÿ”— ุงู„ุฑุจุท ุจู€ Controller (Phi-4-mini โ€” ุฃุณุชุงุฐ ุนู„ู…ูŠุŒ ู†ุต ูู‚ุทุŒ ู„ุง ุตูˆุช ุฅุทู„ุงู‚ุงู‹)
79
  # โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•
80
+ def ask_science_teacher_via_controller(text, task_type="teach", history=None):
81
+ try:
82
+ client = Client(CONTROLLER_SPACE_URL)
83
+ return client.predict(text, task_type, history or [], api_name="/ask_science_teacher")
84
+ except Exception as e:
85
+ print(f"โš ๏ธ ุฎุทุฃ ุงู„ูƒู†ุชุฑูˆู„ุฑ: {e}")
86
+ return None
87
+
88
+
89
+ def deliver_via_qwen(original_question, phi_content, history):
90
+ """Phi ู„ุง ูŠุชุญุฏุซ ู…ุน ุงู„ุทุงู„ุจ ู…ุจุงุดุฑุฉ ุฃุจุฏุงู‹ โ€” ูŠู…ุฑ ุนุจุฑ ูƒูˆูŠู† ุฏุงุฆู…ุงู‹."""
91
+ delivery_prompt = (
92
+ f"ุฒู…ูŠู„ูƒ ุงู„ู…ุนู„ู… ุงู„๏ฟฝ๏ฟฝุชุฎุตุต ุจุงู„ุนู„ูˆู… (Phi) ุฃุนุทุงูƒ ู‡ุฐุง ุงู„ู…ุญุชูˆู‰ ู„ุณุคุงู„ ุงู„ุทุงู„ุจ \"{original_question}\":\n"
93
+ f"{phi_content}\n\nุฃุนุฏ ุตูŠุงุบุชู‡ ุจุฃุณู„ูˆุจูƒ ุงู„ูˆุฏูˆุฏ ู…ุจุงุดุฑุฉ ู„ู„ุทุงู„ุจ (30 ูƒู„ู…ุฉ ูƒุญุฏ ุฃู‚ุตู‰ุŒ ุฎุทูˆุงุชุŒ ุณุคุงู„ ุฎุชุงู…ูŠ)."
94
+ )
95
+ return ask_teacher(delivery_prompt, history)
96
+
97
+
98
+ def ask_teacher(prompt, history):
99
+ history = history or []
100
+ messages = [{"role": "system", "content": "ุฃู†ุช ูƒูˆูŠู†ุŒ ุงู„ุนู‚ู„ ุงู„ู…ุฏุจุฑ ู„ู…ู†ุตุฉ ุชุนู„ูŠู…ูŠุฉ ุนุฑุจูŠุฉ. ุชุณุงุนุฏ ููŠ ุตูŠุงุบุฉ ู…ุญุชูˆู‰ ุชุนู„ูŠู…ูŠ ู‚ุตูŠุฑ ูˆูˆุงุถุญ."}]
101
+ messages.extend(history)
102
+ messages.append({"role": "user", "content": prompt})
103
+ text_prompt = _llm_tokenizer.apply_chat_template(messages, tokenize=False, add_generation_prompt=True)
104
+ inputs = _llm_tokenizer([text_prompt], return_tensors="pt").to(DEVICE)
105
+ with torch.no_grad():
106
+ out_ids = _llm_model.generate(**inputs, max_new_tokens=200, temperature=0.5, do_sample=True, repetition_penalty=1.15, pad_token_id=_llm_tokenizer.eos_token_id)
107
+ out_ids = [o[len(i):] for i, o in zip(inputs.input_ids, out_ids)]
108
+ response = _llm_tokenizer.batch_decode(out_ids, skip_special_tokens=True)[0].strip()
109
+ new_history = history + [{"role": "user", "content": prompt}, {"role": "assistant", "content": response}]
110
+ return response, new_history
111
+
112
+
113
+ def generate_image_via_media(prompt):
114
+ try:
115
+ client = Client(MEDIA_SPACE_URL)
116
+ return client.predict(prompt, RADAR_API_KEY, api_name="/generate_image")
117
+ except Exception as e:
118
+ print(f"โš ๏ธ ุฎุทุฃ ู…ุณุงุญุฉ ุงู„ูˆุณุงุฆุท: {e}")
119
+ return None
120
+
121
+ # โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•
122
+ # ๐ŸŽ™๏ธ ุฎุท ู…ุนุงู„ุฌุฉ ุงู„ุตูˆุช ุงู„ู…ุชู‚ุฏู… (4 ู…ุฑุงุญู„) โ€” ูƒู„ ู…ุฑุญู„ุฉ ู…ุญู…ูŠุฉ ุจุดูƒู„ ู…ุณุชู‚ู„
123
+ # โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•
124
+ def normalize_audio_pydub(input_path):
125
+ try:
126
+ from pydub import AudioSegment, effects
127
+ sound = AudioSegment.from_file(input_path)
128
+ normalized = effects.normalize(sound)
129
+ output_path = "/tmp/step1_normalized.wav"
130
+ normalized.export(output_path, format="wav")
131
+ return output_path
132
+ except Exception as e:
133
+ print(f"โš ๏ธ ูุดู„ุช ุฎุทูˆุฉ ุชุทุจูŠุน ุงู„ุตูˆุช: {e}")
134
+ return input_path
135
+
136
+
137
+ _df_model, _df_state = None, None
138
+
139
+ def denoise_with_deepfilternet(input_path):
140
+ global _df_model, _df_state
141
+ try:
142
+ from df.enhance import enhance as df_enhance, init_df, load_audio as df_load_audio, save_audio as df_save_audio
143
+ if _df_model is None:
144
+ print("โณ ุชุญู…ูŠู„ DeepFilterNet...")
145
+ _df_model, _df_state, _ = init_df()
146
+ audio, _ = df_load_audio(input_path, sr=_df_state.sr())
147
+ enhanced = df_enhance(_df_model, _df_state, audio)
148
+ output_path = "/tmp/step2_denoised.wav"
149
+ df_save_audio(output_path, enhanced, _df_state.sr())
150
+ return output_path
151
+ except Exception as e:
152
+ print(f"โš ๏ธ ูุดู„ุช ุฎุทูˆุฉ ุชุตููŠุฉ ุงู„ุถูˆุถุงุก: {e}")
153
+ return input_path
154
+
155
+
156
+ _metricgan_model = None
157
+
158
+ def isolate_speaker_metricgan(input_path):
159
+ global _metricgan_model
160
+ try:
161
+ import torchaudio
162
+ from speechbrain.inference.enhancement import SpectralMaskEnhancement
163
+ if _metricgan_model is None:
164
+ print("โณ ุชุญู…ูŠู„ MetricGAN+...")
165
+ _metricgan_model = SpectralMaskEnhancement.from_hparams(
166
+ source="speechbrain/metricgan-plus-voicebank", savedir="/tmp/pretrained_metricgan",
167
+ )
168
+ noisy = _metricgan_model.load_audio(input_path).unsqueeze(0)
169
+ enhanced = _metricgan_model.enhance_batch(noisy, lengths=torch.tensor([1.0]))
170
+ output_path = "/tmp/step3_isolated.wav"
171
+ torchaudio.save(output_path, enhanced.cpu(), 16000)
172
+ return output_path
173
+ except Exception as e:
174
+ print(f"โš ๏ธ ูุดู„ุช ุฎุทูˆุฉ ุนุฒู„ ุงู„ู…ุชุญุฏุซ: {e}")
175
+ return input_path
176
+
177
+
178
+ def preprocess_student_audio(raw_audio_path):
179
+ return isolate_speaker_metricgan(denoise_with_deepfilternet(normalize_audio_pydub(raw_audio_path)))
180
+
181
+
182
+ _whisper_pipe = None
183
+
184
+ def load_whisper():
185
+ global _whisper_pipe
186
+ if _whisper_pipe is not None:
187
+ return _whisper_pipe
188
+ from transformers import pipeline
189
+ print("โณ ุชุญู…ูŠู„ Whisper-medium...")
190
+ _whisper_pipe = pipeline("automatic-speech-recognition", model=WHISPER_MODEL, device=DEVICE, chunk_length_s=30)
191
+ return _whisper_pipe
192
+
193
+ # โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•
194
+ # ุงู„ุฑุงุฏุงุฑ โ€” ุงู„ุตูˆุช ูู‚ุท ู…ู† ู‡ู†ุงุŒ ุฏุงุฆู…ุงู‹
195
+ # โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•
196
+ def call_radar_api(text, voice):
197
  try:
198
  client = Client(RADAR_SPACE_URL)
199
+ audio_url = client.predict(text, voice, RADAR_API_KEY, api_name="/synth_arabic")
200
  if audio_url and isinstance(audio_url, (list, tuple)):
201
  audio_url = audio_url[0]
202
  return str(audio_url).strip() if audio_url else None
 
204
  print(f"โš ๏ธ ุฎุทุฃ ุงู„ุฑุงุฏุงุฑ: {e}")
205
  return None
206
 
 
207
  # โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•
208
+ # ุงู„ุฎุฒู†ุฉ: ุงู„ุฏุฑูˆุณ + ู…ู„ู ุงู„ุทุงู„ุจ
 
 
 
209
  # โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•
210
+ def list_lessons_in_vault(grade, semester, subject):
211
  try:
212
+ folder_path = f"Curriculum_Core/{grade}/{semester}/{subject}/"
213
+ all_files = _hf_api.list_repo_files(repo_id=VAULT_REPO_ID, repo_type="dataset")
214
+ lesson_files = [f for f in all_files if f.startswith(folder_path) and f.lower().endswith(('.txt', '.md', '.json'))]
215
+ choices = []
216
+ for f in sorted(lesson_files):
217
+ display_name = re.sub(r'\.(txt|md|json)$', '', os.path.basename(f), flags=re.IGNORECASE)
218
+ choices.append((display_name, f))
219
+ return choices
220
  except Exception as e:
221
+ print(f"โš ๏ธ ูุดู„ ุณุฑุฏ ุงู„ุฏุฑูˆุณ: {e}")
222
+ return []
223
 
224
 
225
+ def load_lesson_content(lesson_path):
226
+ if not lesson_path:
227
+ return None
228
  try:
229
+ local_path = hf_hub_download(repo_id=VAULT_REPO_ID, filename=lesson_path, repo_type="dataset")
230
+ with open(local_path, "r", encoding="utf-8") as f:
231
+ content = f.read()
232
+ return content[:MAX_LESSON_CHARS] if len(content) > MAX_LESSON_CHARS else content
233
  except Exception as e:
234
+ print(f"โš ๏ธ ูุดู„ ุชุญู…ูŠู„ ุงู„ุฏุฑุณ: {e}")
235
  return None
236
 
237
 
238
+ def save_student_profile(grade, semester, subject, lesson_path, user_name, track=None):
239
+ if not HF_TOKEN:
240
+ return
241
+ try:
242
+ profile = {
243
+ "user_name": user_name, "grade": grade, "semester": semester,
244
+ "subject": subject, "lesson": lesson_path, "track": track,
245
+ "last_updated": time.strftime("%Y-%m-%d %H:%M:%S")
246
+ }
247
+ _hf_api.upload_file(
248
+ path_or_fileobj=json.dumps(profile, ensure_ascii=False, indent=2).encode("utf-8"),
249
+ path_in_repo=f"Student_Hub/User_{user_name}/profile.json",
250
+ repo_id=VAULT_REPO_ID, repo_type="dataset", token=HF_TOKEN
251
+ )
252
+ except Exception as e:
253
+ print(f"โš ๏ธ ุฎุทุฃ ููŠ ุงู„ุญูุธ: {e}")
254
+
255
  # โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•
256
+ # ุงู„ุตููˆู/ุงู„ูุตูˆู„/ุงู„ู…ูˆุงุฏ
257
  # โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•
258
+ GRADES = [
259
+ "ุงู„ุฑูˆุถุฉ", "ุงู„ุตู ุงู„ุฃูˆู„", "ุงู„ุตู ุงู„ุซุงู†ูŠ", "ุงู„ุตู ุงู„ุซุงู„ุซ", "ุงู„ุตู ุงู„ุฑุงุจุน",
260
+ "ุงู„ุตู ุงู„ุฎุงู…ุณ", "ุงู„ุตู ุงู„ุณุงุฏุณ", "ุงู„ุตู ุงู„ุณุงุจุน", "ุงู„ุตู ุงู„ุซุงู…ู†", "ุงู„ุตู ุงู„ุชุงุณุน",
261
+ "ุงู„ุตู ุงู„ุนุงุดุฑ", "ุงู„ุตู ุงู„ุญุงุฏูŠ ุนุดุฑ", "ุงู„ุตู ุงู„ุซุงู†ูŠ ุนุดุฑ (ุงู„ุชูˆุฌูŠู‡ูŠ)"
262
+ ]
263
+ SEMESTERS = ["ุงู„ูุตู„ ุงู„ุฃูˆู„", "ุงู„ูุตู„ ุงู„ุซุงู†ูŠ"]
264
+ DEFAULT_SUBJECTS = ["๐Ÿงฎ ุงู„ุฑูŠุงุถูŠุงุช", "๐Ÿ”ฌ ุงู„ุนู„ูˆู…", "๐Ÿ“— ุงู„ู„ุบุฉ ุงู„ุนุฑุจูŠุฉ", "๐Ÿ•Œ ุงู„ุชุฑุจูŠุฉ ุงู„ุฅุณู„ุงู…ูŠุฉ", "๐Ÿ”ค ุงู„ู„ุบุฉ ุงู„ุฅู†ุฌู„ูŠุฒูŠุฉ"]
 
 
 
 
265
 
266
+ # โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•
267
+ # ุฏุงู„ุฉ ุงู„ู…ุญุงุฏุซุฉ ุงู„ุฑุฆูŠุณูŠุฉ ู„ุบุฑูุฉ ุงู„ุตู
268
+ # โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•
269
+ def teacher_chat(audio_mic, direct_text, subject, grade, semester, lesson_path, track, student_name,
270
+ chat_history_state, teacher_prefs, active_idx):
271
+ if direct_text and direct_text.strip():
272
+ user_text = direct_text.strip()
273
+ else:
274
+ if audio_mic is None:
275
+ return chat_history_state, None, "๐ŸŽค ุณุฌู‘ู„ ุณุคุงู„ูƒ ุฃูˆ ุงุณุชุฎุฏู… ุฃุญุฏ ุฃุฏูˆุงุชูŠ"
276
+ try:
277
+ processed_audio = preprocess_student_audio(audio_mic)
278
+ whisper = load_whisper()
279
+ result = whisper(processed_audio, generate_kwargs={"language": "arabic"})
280
+ user_text = result["text"].strip()
281
+ except Exception as e:
282
+ return chat_history_state, None, f"โš ๏ธ ุฎุทุฃ: {e}"
283
+ if not user_text:
284
+ return chat_history_state, None, "โŒ ู„ู… ูŠุชู… ุงู„ุชุนุฑู ุนู„ู‰ ูƒู„ุงู…"
285
+
286
+ print(f"๐Ÿ—ฃ๏ธ {student_name}: {user_text}")
287
+ lesson_content = load_lesson_content(lesson_path)
288
+
289
+ is_science = subject in ("๐Ÿงฎ ุงู„ุฑูŠุงุถูŠุงุช", "๐Ÿ”ฌ ุงู„ุนู„ูˆู…")
290
+ if is_science:
291
+ phi_content = ask_science_teacher_via_controller(user_text, "teach", chat_history_state)
292
+ if phi_content:
293
+ cleaned_response, new_history = deliver_via_qwen(user_text, phi_content, chat_history_state)
294
+ else:
295
+ cleaned_response, new_history = ask_teacher(user_text, chat_history_state)
296
+ else:
297
+ student_name_clean = (student_name or "ุงู„ุทุงู„ุจ").strip()
298
+ context_str = f"ู…ุงุฏุฉ {subject} - {semester}" + (f" - ุชุฎุตุต {track}" if track else "")
299
+ system_prompt = (
300
+ f"ุฃู†ุช ุงู„ุฃุณุชุงุฐ ุนุจูˆุฏุŒ ู…ุนู„ู… ุฎุจูŠุฑ ูˆุฏูˆุฏ ุฌุฏุงู‹. ุชุชุญุฏุซ ุงู„ุขู† ู…ุน ุทุงู„ุจูƒ {student_name_clean}. ุงู„ุณูŠุงู‚: {context_str}.\n"
301
+ f"- ุฎุงุทุจ {student_name_clean} ุจุงุณู…ู‡ ู…ู† ูˆู‚ุช ู„ุขุฎุฑ ุจุดูƒู„ ุทุจูŠุนูŠ.\n"
302
+ "- ุฅุฐุง ูƒุงู†ุช ุชุญูŠุฉุŒ ุฑุฏ ุจุชุญูŠุฉ ูˆุฏูˆุฏุฉ ู‚ุตูŠุฑุฉ ูู‚ุท.\n"
303
+ "- ุฅุฐุง ูƒุงู† ุณุคุงู„ุงู‹ ุชุนู„ูŠู…ูŠุงู‹: ุฃุฌุจ ุจุฅูŠุฌุงุฒ (30 ูƒู„ู…ุฉ)ุŒ ุฎุทูˆุงุชุŒ ุณุคุงู„ ุฎุชุงู…ูŠ.\n"
304
+ "- ุฅุฐุง ูˆุฑุฏุช ู…ุนุงุฏู„ุฉ LaTeX ุจูŠู† $$ุŒ ูุณู‘ุฑู‡ุง ุจูˆุถูˆุญ."
305
  )
306
+ if lesson_content:
307
+ system_prompt += f"\n\n๐Ÿ“– ุงู„ุฏุฑุณ ุงู„ู…ุฑุฌุนูŠ (ู…ุณุงุนุฏ ูู‚ุท):\n```\n{lesson_content}\n```"
308
+ messages = [{"role": "system", "content": system_prompt}] + chat_history_state + [{"role": "user", "content": user_text}]
309
+ text_prompt = _llm_tokenizer.apply_chat_template(messages, tokenize=False, add_generation_prompt=True)
310
+ inputs = _llm_tokenizer([text_prompt], return_tensors="pt").to(DEVICE)
311
+ with torch.no_grad():
312
+ out_ids = _llm_model.generate(**inputs, max_new_tokens=200, temperature=0.4, do_sample=True, repetition_penalty=1.15, pad_token_id=_llm_tokenizer.eos_token_id)
313
+ out_ids = [o[len(i):] for i, o in zip(inputs.input_ids, out_ids)]
314
+ cleaned_response = _llm_tokenizer.batch_decode(out_ids, skip_special_tokens=True)[0].strip()
315
+ if not cleaned_response.endswith(('.', 'ุŸ', '!')):
316
+ cleaned_response += '.'
317
+ new_history = chat_history_state + [{"role": "user", "content": user_text}, {"role": "assistant", "content": cleaned_response}]
318
+
319
+ save_student_profile(grade, semester, subject, lesson_path, student_name, track)
320
+
321
+ # ๐ŸŽ™๏ธ ุตูˆุช "ุงู„ู…ุญุงูˆุฑ" ู„ู„ู…ุญุงุฏุซุฉ ุงู„ุนุงุฏูŠุฉ ุญุงู„ูŠุงู‹ (ุฃุตูˆุงุช ุงู„ุดุฑุญ/ุงู„ุฃุณุฆู„ุฉ
322
+ # ุณุชููุนูŽู‘ู„ ุชู„ู‚ุงุฆูŠุงู‹ ุนู†ุฏ ุจู†ุงุก ู…ู†ุทู‚ ุงู„ุญุตุฉ ุงู„ู…ู‚ุณู‘ู…ุฉ ู„ุงุญู‚ุงู‹)
323
+ try:
324
+ voice = teacher_prefs[active_idx]["ู…ุญุงูˆุฑ"]
325
+ except Exception:
326
+ voice = "ar-JO-TaimNeural"
327
+ audio_path = call_radar_api(cleaned_response, voice)
328
+ status = "โœ… ุชู… ุงู„ุฑุฏ" if audio_path else "โš ๏ธ ูุดู„ ุงู„ุตูˆุช"
329
+ return new_history, audio_path, status
330
+
331
+
332
+ # โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•
333
+ # ุฃุฏูˆุงุชูŠ: ุขู„ุฉ ุญุงุณุจุฉ + ูƒุงู…ูŠุฑุง OCR + ู„ูˆุญุฉ ู…ูุงุชูŠุญ ุจุฏูŠู„ุฉ
334
+ # โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•
335
+ SYMPY_LOCALS = {"sin": sp.sin, "cos": sp.cos, "tan": sp.tan, "sqrt": sp.sqrt, "pi": sp.pi}
336
+ CALC_BUTTONS = [
337
+ [("7", "7"), ("8", "8"), ("9", "9"), ("รท", "/"), ("โˆš", "sqrt(")],
338
+ [("4", "4"), ("5", "5"), ("6", "6"), ("ร—", "*"), ("^", "**")],
339
+ [("1", "1"), ("2", "2"), ("3", "3"), ("-", "-"), ("ฯ€", "pi")],
340
+ [("0", "0"), (".", "."), ("(", "("), (")", ")"), ("+", "+")],
341
+ [("sin", "sin("), ("cos", "cos("), ("tan", "tan("), ("C", "__CLEAR__"), ("โŒซ", "__BACK__")],
342
+ ]
343
 
 
 
344
 
345
+ def calc_append(current, token):
346
+ return (current or "") + token
347
 
 
 
 
348
 
349
+ def calc_clear():
350
+ return ""
351
 
 
 
 
352
 
353
+ def calc_backspace(current):
354
+ return (current or "")[:-1]
355
+
356
+
357
+ def calc_evaluate(expr):
358
+ if not expr or not expr.strip():
359
+ return expr, "โš ๏ธ ุฃุฏุฎู„ ุนุจุงุฑุฉ ุฃูˆู„ุงู‹"
360
+ try:
361
+ return str(sp.sympify(expr, locals=SYMPY_LOCALS).evalf()), "โœ… ุชู… ุงู„ุญุณุงุจ"
362
+ except Exception as e:
363
+ return expr, f"โš ๏ธ ุชุนุฐุฑ ุงู„ุญุณุงุจ: {e}"
364
+
365
+
366
+ def calc_to_latex_question(expr):
367
+ if not expr or not expr.strip():
368
+ return ""
369
+ try:
370
+ return f"ุงุดุฑุญ ู„ูŠ ุฎุทูˆุงุช ุญู„ ู‡ุฐู‡ ุงู„ู…ุณุฃู„ุฉ: $$ {sp.latex(sp.sympify(expr, locals=SYMPY_LOCALS))} $$"
371
+ except Exception as e:
372
+ return f"ุงุดุฑุญ ู„ูŠ ุฎุทูˆุงุช ุญู„ ู‡ุฐู‡ ุงู„ู…ุณุฃู„ุฉ: {expr}"
373
+
374
+
375
+ _ocr_processor, _ocr_model = None, None
376
+
377
+ def load_ocr():
378
+ global _ocr_processor, _ocr_model
379
+ if _ocr_model is not None:
380
+ return _ocr_processor, _ocr_model
381
+ print("โณ ุชุญู…ูŠู„ ู†ู…ูˆุฐุฌ ู‚ุฑุงุกุฉ ุงู„ุตูˆุฑ ุงู„ุนุฑุจูŠ (TrOCR)...")
382
+ _ocr_processor = TrOCRProcessor.from_pretrained(OCR_MODEL_ID)
383
+ _ocr_model = VisionEncoderDecoderModel.from_pretrained(OCR_MODEL_ID)
384
+ return _ocr_processor, _ocr_model
385
+
386
+
387
+ def read_image_text(image_path):
388
+ if not image_path:
389
+ return "", "โš ๏ธ ู„ู… ูŠุชู… ุฑูุน ุตูˆุฑุฉ"
390
+ try:
391
+ processor, model = load_ocr()
392
+ image = Image.open(image_path).convert("RGB")
393
+ pixel_values = processor(images=image, return_tensors="pt").pixel_values
394
+ generated_ids = model.generate(pixel_values, max_new_tokens=200)
395
+ text = processor.batch_decode(generated_ids, skip_special_tokens=True)[0]
396
+ return text, "โœ… ุชู… ุงุณุชุฎุฑุงุฌ ุงู„ู†ุต"
397
+ except Exception as e:
398
+ return "", f"โš ๏ธ ุฎุทุฃ ููŠ ู‚ุฑุงุกุฉ ุงู„ุตูˆุฑุฉ: {e}"
399
 
400
 
401
  # โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•
402
+ # ุงู„ูˆุงุฌู‡ุฉ
403
  # โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•
404
+ with gr.Blocks(title="My Teacher Lesson", theme=gr.themes.Soft()) as demo:
405
+ theme_html = gr.HTML(apply_theme("๐ŸŒž ู†ู‡ุงุฑูŠ"))
406
  gr.Markdown("# ๐ŸŽ“ ุงู„ู…ู†ุตุฉ ุงู„ุชุนู„ูŠู…ูŠุฉ ุงู„ุฐูƒูŠุฉ ุงู„ู…ูˆุญุฏุฉ")
407
 
408
+ teacher_prefs_state = gr.State([dict(t) for t in DEFAULT_TEACHER_PREFS])
409
+ active_teacher_index_state = gr.State(0)
410
+ chat_history_state = gr.State([])
411
+ subject_choices_state = gr.State(list(DEFAULT_SUBJECTS))
412
 
413
  with gr.Tabs():
414
+ # โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€
415
+ # ุชุจูˆูŠุจ 1: ุบุฑูุฉ ุงู„ุตู (ู…ุฏู…ุฌุฉ ุจุงู„ูƒุงู…ู„ ู…ู† ู…ู„ู Chat)
416
+ # โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€
417
+ with gr.Tab("๐Ÿซ ุบุฑูุฉ ุงู„ุตู"):
418
+ with gr.Row():
419
+ with gr.Column(scale=2):
420
+ with gr.Row():
421
+ student_name_input = gr.Textbox(label="๐Ÿ‘ค ุงุณู… ุงู„ุทุงู„ุจ", value="Divid")
422
+ grade_dropdown = gr.Dropdown(choices=GRADES, value="ุงู„ุตู ุงู„ุนุงุดุฑ", label="๐Ÿ“† ุงู„ุตู")
423
+
424
+ with gr.Row():
425
+ semester_dropdown = gr.Dropdown(choices=SEMESTERS, value="ุงู„ูุตู„ ุงู„ุฃูˆู„", label="๐Ÿ“… ุงู„ูุตู„")
426
+ track_dropdown = gr.Dropdown(choices=[], value=None, label="๐ŸŽฏ ุงู„ุชุฎุตุต (ุชูˆุฌูŠู‡ูŠ)", visible=False, allow_custom_value=True)
427
+
428
+ with gr.Row(visible=False) as track_row:
429
+ new_track_input = gr.Textbox(label="โž• ุชุฎุตุต ุฌุฏูŠุฏ")
430
+ add_track_btn = gr.Button("๐Ÿ’พ ุญูุธ", size="sm")
431
+
432
+ with gr.Row():
433
+ subject_dropdown = gr.Dropdown(choices=DEFAULT_SUBJECTS, value=DEFAULT_SUBJECTS[0], label="๐Ÿ“š ุงู„ู…ุงุฏุฉ")
434
+ add_subject_btn = gr.Button("โž• ุฅุถุงูุฉ ู…ูˆุงุฏ ุฃุฎุฑู‰", size="sm")
435
+
436
+ with gr.Row(visible=False) as new_subject_row:
437
+ new_subject_input = gr.Textbox(label="โœ๏ธ ุงุณู… ุงู„ู…ุงุฏุฉ ุงู„ุฌุฏูŠุฏุฉ")
438
+ confirm_subject_btn = gr.Button("๐Ÿ’พ ุญูุธ", size="sm")
439
+
440
+ with gr.Row():
441
+ lesson_dropdown = gr.Dropdown(choices=[], label="๐Ÿ“– ุงู„ุฏุฑุณ (ู…ู† ุงู„ุฎุฒู†ุฉ)")
442
+ refresh_lessons_btn = gr.Button("๐Ÿ”„", size="sm")
443
+
444
+ chatbot_display = gr.Chatbot(label="ุงู„ุญูˆุงุฑ", height=400)
445
+ mic_input = gr.Audio(label="๐ŸŽค ุณุคุงู„ูƒ", type="filepath", sources=["microphone"])
446
+ send_btn = gr.Button("๐Ÿš€ ุฅุฑุณุงู„", variant="primary")
447
+
448
+ with gr.Accordion("๐Ÿ› ๏ธ ุฃุฏูˆุงุชูŠ", open=False):
449
+ with gr.Tab("๐Ÿงฎ ุงู„ุขู„ุฉ ุงู„ุญุงุณุจุฉ ุงู„ุนู„ู…ูŠุฉ"):
450
+ calc_display = gr.Textbox(label="ุงู„ุนุจุงุฑุฉ", value="")
451
+ for row in CALC_BUTTONS:
452
+ with gr.Row():
453
+ for label, token in row:
454
+ b = gr.Button(label, size="sm")
455
+ if token == "__CLEAR__":
456
+ b.click(fn=calc_clear, outputs=[calc_display], api_name=False)
457
+ elif token == "__BACK__":
458
+ b.click(fn=calc_backspace, inputs=[calc_display], outputs=[calc_display], api_name=False)
459
+ else:
460
+ b.click(fn=lambda cur, t=token: calc_append(cur, t), inputs=[calc_display], outputs=[calc_display], api_name=False)
461
+ with gr.Row():
462
+ calc_eq_btn = gr.Button("๐ŸŸฐ ุญุณุงุจ", size="sm")
463
+ calc_send_btn = gr.Button("๐Ÿ“ค ุญูˆู‘ู„ ู„ู€ LaTeX ูˆุฃุฑุณู„", size="sm")
464
+ calc_status = gr.Textbox(label="ุงู„ุญุงู„ุฉ", interactive=False)
465
+ calc_pending_question = gr.Textbox(visible=False)
466
+ calc_eq_btn.click(fn=calc_evaluate, inputs=[calc_display], outputs=[calc_display, calc_status], api_name=False)
467
+
468
+ with gr.Tab("๐Ÿ“ท ุงู„ูƒุงู…ูŠุฑุง ูˆู‚ุฑุงุกุฉ ุงู„ุตูˆุฑ"):
469
+ camera_input = gr.Image(label="ุตูˆู‘ุฑ/ุงุฑูุน ูˆุฑู‚ุฉ ุงู„ุนู…ู„", type="filepath", sources=["upload", "webcam"])
470
+ ocr_btn = gr.Button("๐Ÿ” ุงู‚ุฑุฃ ุงู„ู†ุต")
471
+ ocr_text_out = gr.Textbox(label="ุงู„ู†ุต ุงู„ู…ุณุชุฎุฑุฌ", lines=3)
472
+ ocr_status = gr.Textbox(label="ุงู„ุญุงู„ุฉ", interactive=False)
473
+ ocr_send_btn = gr.Button("โžก๏ธ ุฃุฑุณู„ ู„ู„ุฃุณุชุงุฐ", variant="primary")
474
+ ocr_btn.click(fn=read_image_text, inputs=[camera_input], outputs=[ocr_text_out, ocr_status], api_name=False)
475
+
476
+ with gr.Tab("โŒจ๏ธ ู„ูˆุญุฉ ุงู„ู…ูุงุชูŠุญ ุงู„ุจุฏูŠู„ุฉ"):
477
+ emergency_text_in = gr.Textbox(label="ุงูƒุชุจ ุณุคุงู„ูƒ ู‡ู†ุง", lines=3)
478
+ emergency_send_btn = gr.Button("โžก๏ธ ุฃุฑุณู„ ู„ู„ุฃุณุชุงุฐ", variant="primary")
479
+
480
+ with gr.Column(scale=1):
481
+ audio_output = gr.Audio(label="๐Ÿ”Š ุงู„ุฑุฏ", type="filepath", autoplay=True)
482
+ status_output = gr.Textbox(label="ุงู„ุญุงู„ุฉ")
483
+
484
+ # โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€
485
+ # ุชุจูˆูŠุจ 2: ุงู„ุฅุนุฏุงุฏุงุช ูˆุงู„ุชูุถูŠู„ุงุช
486
+ # โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€
487
+ with gr.Tab("โš™๏ธ ุงู„ุฅุนุฏุงุฏุงุช ูˆุงู„ุชูุถูŠู„ุงุช"):
488
+ gr.Markdown("### ๐ŸŽจ ู…ุธู‡ุฑ ุงู„ู…ู†ุตุฉ")
489
+ theme_radio = gr.Radio(choices=list(THEME_CSS.keys()), value="๐ŸŒž ู†ู‡ุงุฑูŠ", label="ุงุฎุชุฑ ุงู„ู…ุธู‡ุฑ")
490
+
491
+ gr.Markdown("---\n### ๐Ÿ‘จโ€๐Ÿซ ุนุฏุฏ ุงู„ู…ุนู„ู…ูŠู† ูˆุฃุตูˆุงุช ุงู„ุฃุฏูˆุงุฑ")
492
+ teacher_count_radio = gr.Radio(choices=["2", "3"], value="2", label="ุนุฏุฏ ุงู„ู…ุนู„ู…ูŠู† ุงู„ู…ุชุงุญูŠู†")
493
+ active_teacher_radio = gr.Radio(choices=["ุงู„ู…ุนู„ู… ุงู„ุฃูˆู„", "ุงู„ู…ุนู„ู…ุฉ ุงู„ุซุงู†ูŠุฉ", "ุงู„ู…ุนู„ู… ุงู„ุซุงู„ุซ"], value="ุงู„ู…ุนู„ู… ุงู„ุฃูˆู„", label="ุงู„ู…ุนู„ู… ุงู„ู†ุดุท ุญุงู„ูŠุงู‹ ููŠ ุบุฑูุฉ ุงู„ุตู")
494
+
495
+ teacher_setting_rows = []
496
+ for idx in range(3):
497
+ with gr.Row(visible=(idx < 2)) as row:
498
+ name_box = gr.Textbox(label=f"ุงุณู… ุงู„ู…ุนู„ู… {idx + 1}", value=DEFAULT_TEACHER_PREFS[idx]["name"])
499
+ voice_dd_map = {}
500
+ for role in VOICE_ROLES:
501
+ voice_dd_map[role] = gr.Dropdown(choices=MICROSOFT_VOICES, value=DEFAULT_TEACHER_PREFS[idx][role], label=f"ุตูˆุช {role}")
502
+ teacher_setting_rows.append((row, name_box, voice_dd_map))
503
+
504
+ save_settings_btn = gr.Button("๐Ÿ’พ ุญูุธ ุงู„ุชูุถูŠู„ุงุช", variant="primary")
505
+ settings_status = gr.Textbox(label="ุญุงู„ุฉ ุงู„ุญูุธ", interactive=False)
506
+
507
+ def toggle_teacher_count(count):
508
+ n = int(count)
509
+ return [gr.update(visible=(i < n)) for i in range(3)]
510
+
511
+ teacher_count_radio.change(
512
+ fn=toggle_teacher_count, inputs=[teacher_count_radio],
513
+ outputs=[r[0] for r in teacher_setting_rows], api_name=False
514
+ )
515
+
516
+ def save_all_settings(*args):
517
+ # args = name1, v1_ู…ุญุงูˆุฑ, v1_ุดุฑุญ, v1_ุฃุณุฆู„ุฉ, name2, ..., name3, ...
518
+ prefs = []
519
+ i = 0
520
+ names_voices = list(args)
521
+ for t_idx in range(3):
522
+ name = names_voices[i]; i += 1
523
+ roles = {}
524
+ for role in VOICE_ROLES:
525
+ roles[role] = names_voices[i]; i += 1
526
+ prefs.append({"name": name, **roles})
527
+ return prefs, "โœ… ุชู… ุญูุธ ุงู„ุชูุถูŠู„ุงุช"
528
+
529
+ all_setting_inputs = []
530
+ for row, name_box, voice_dd_map in teacher_setting_rows:
531
+ all_setting_inputs.append(name_box)
532
+ for role in VOICE_ROLES:
533
+ all_setting_inputs.append(voice_dd_map[role])
534
+
535
+ save_settings_btn.click(
536
+ fn=save_all_settings, inputs=all_setting_inputs,
537
+ outputs=[teacher_prefs_state, settings_status], api_name=False
538
  )
539
 
540
+ def set_active_teacher(choice):
541
+ mapping = {"ุงู„ู…ุนู„ู… ุงู„ุฃูˆู„": 0, "ุงู„ู…ุนู„ู…ุฉ ุงู„ุซุงู†ูŠุฉ": 1, "ุงู„ู…ุนู„ู… ุงู„ุซุงู„ุซ": 2}
542
+ return mapping.get(choice, 0)
543
 
544
+ active_teacher_radio.change(fn=set_active_teacher, inputs=[active_teacher_radio], outputs=[active_teacher_index_state], api_name=False)
545
+ theme_radio.change(fn=apply_theme, inputs=[theme_radio], outputs=[theme_html], api_name=False)
546
+
547
+ # โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€
548
+ # ุงู„ุชุจูˆูŠุจุงุช 3-6: ุญุฌุฒ ู…ูƒุงู† โ€” ุณู†ุจู†ูŠู‡ุง ูˆุงุญุฏุฉ ุชู„ูˆ ุงู„ุฃุฎุฑู‰
549
+ # โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€๏ฟฝ๏ฟฝ๏ฟฝโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€
550
  with gr.Tab("๐Ÿ—‚๏ธ ุฎุทุท ุงู„ุฏุฑูˆุณ ูˆุงู„ุชู‚ุณูŠู…"):
551
+ gr.Markdown("๐Ÿšง **ู‚ูŠุฏ ุงู„ุชุทูˆูŠุฑ.** ุณูŠูุจู†ู‰ ู„ุงุญู‚ุงู‹ ุจุงู„ุชุนุงูˆู† ู…ุน ูƒูˆูŠู† ูˆPhi.")
 
 
 
552
 
553
  with gr.Tab("๐Ÿ“Š ุงู„ู…ุณุชูˆูŠุงุช ูˆุชุญู„ูŠู„ ุงู„ูุฌูˆุฉ ุงู„ุชุนู„ูŠู…ูŠุฉ"):
554
+ gr.Markdown("๐Ÿšง **ู‚ูŠุฏ ุงู„ุชุทูˆูŠุฑ.**")
 
 
 
555
 
556
  with gr.Tab("๐Ÿ’ฌ ุนู„ุงู‚ุฉ ุงู„ุทุงู„ุจ ูˆุงู„ุฃุณุชุงุฐ"):
557
+ gr.Markdown("๐Ÿšง **ู‚ูŠุฏ ุงู„ุชุทูˆูŠุฑ.**")
558
 
559
  with gr.Tab("๐Ÿ–ผ๏ธ ุฃุฑุดูŠู ูˆุฅุฏุงุฑุฉ ุงู„ูˆุณุงุฆุท"):
560
+ gr.Markdown("๐Ÿšง **ู‚ูŠุฏ ุงู„ุชุทูˆูŠุฑ.**")
561
+
562
+ # โ”€โ”€โ”€โ”€ ุฑุจุท ุงู„ุฃุญุฏุงุซ: ุบุฑูุฉ ุงู„ุตู โ”€โ”€โ”€โ”€
563
+ def toggle_track(grade):
564
+ if "ุงู„ุชูˆุฌูŠู‡ูŠ" in grade:
565
+ return gr.update(visible=True), gr.update(visible=True), gr.update(visible=True)
566
+ return gr.update(visible=False, value=None), gr.update(visible=False), gr.update(visible=False)
567
+
568
+ grade_dropdown.change(fn=toggle_track, inputs=[grade_dropdown], outputs=[track_dropdown, track_row, add_track_btn], api_name=False)
569
+
570
+ add_subject_btn.click(fn=lambda: gr.update(visible=True), outputs=[new_subject_row], api_name=False)
571
+
572
+ def confirm_new_subject(new_subject, choices):
573
+ new_subject = (new_subject or "").strip()
574
+ if not new_subject:
575
+ return gr.update(), choices, gr.update(visible=False), ""
576
+ if new_subject not in choices:
577
+ choices = choices + [new_subject]
578
+ return gr.update(choices=choices, value=new_subject), choices, gr.update(visible=False), ""
579
+
580
+ confirm_subject_btn.click(fn=confirm_new_subject, inputs=[new_subject_input, subject_choices_state],
581
+ outputs=[subject_dropdown, subject_choices_state, new_subject_row, new_subject_input], api_name=False)
582
+
583
+ def refresh_lessons(grade, semester, subject):
584
+ lessons = list_lessons_in_vault(grade, semester, subject)
585
+ return gr.update(choices=lessons, value=(lessons[0][1] if lessons else None))
586
+
587
+ grade_dropdown.change(fn=refresh_lessons, inputs=[grade_dropdown, semester_dropdown, subject_dropdown], outputs=[lesson_dropdown], api_name=False)
588
+ semester_dropdown.change(fn=refresh_lessons, inputs=[grade_dropdown, semester_dropdown, subject_dropdown], outputs=[lesson_dropdown], api_name=False)
589
+ subject_dropdown.change(fn=refresh_lessons, inputs=[grade_dropdown, semester_dropdown, subject_dropdown], outputs=[lesson_dropdown], api_name=False)
590
+ refresh_lessons_btn.click(fn=refresh_lessons, inputs=[grade_dropdown, semester_dropdown, subject_dropdown], outputs=[lesson_dropdown], api_name=False)
591
+ demo.load(fn=refresh_lessons, inputs=[grade_dropdown, semester_dropdown, subject_dropdown], outputs=[lesson_dropdown])
592
+
593
+ def process_and_display(audio_mic, direct_text, subject, grade, semester, lesson_path, track, student_name,
594
+ history_state, teacher_prefs, active_idx):
595
+ new_history, audio, status = teacher_chat(audio_mic, direct_text, subject, grade, semester, lesson_path,
596
+ track, student_name, history_state, teacher_prefs, active_idx)
597
+ return new_history, new_history, audio, status, None, ""
598
+
599
+ common_inputs_tail = [subject_dropdown, grade_dropdown, semester_dropdown, lesson_dropdown, track_dropdown,
600
+ student_name_input, chat_history_state, teacher_prefs_state, active_teacher_index_state]
601
+
602
+ send_btn.click(
603
+ fn=process_and_display,
604
+ inputs=[mic_input, calc_pending_question] + common_inputs_tail,
605
+ outputs=[chat_history_state, chatbot_display, audio_output, status_output, mic_input, calc_pending_question],
606
+ api_name=False,
607
+ )
608
+
609
+ calc_send_btn.click(fn=calc_to_latex_question, inputs=[calc_display], outputs=[calc_pending_question], api_name=False).then(
610
+ fn=process_and_display,
611
+ inputs=[mic_input, calc_pending_question] + common_inputs_tail,
612
+ outputs=[chat_history_state, chatbot_display, audio_output, status_output, mic_input, calc_pending_question],
613
+ api_name=False,
614
+ )
615
+
616
+ ocr_send_btn.click(
617
+ fn=process_and_display,
618
+ inputs=[mic_input, ocr_text_out] + common_inputs_tail,
619
+ outputs=[chat_history_state, chatbot_display, audio_output, status_output, mic_input, ocr_text_out],
620
+ api_name=False,
621
+ )
622
+
623
+ emergency_send_btn.click(
624
+ fn=process_and_display,
625
+ inputs=[mic_input, emergency_text_in] + common_inputs_tail,
626
+ outputs=[chat_history_state, chatbot_display, audio_output, status_output, mic_input, emergency_text_in],
627
+ api_name=False,
628
+ )
629
 
630
  if __name__ == "__main__":
631
+ demo.launch(server_name="0.0.0.0", server_port=7860)