Asem75 commited on
Commit
26274a1
·
verified ·
1 Parent(s): 93db9f9

Update chat_tab.py

Browse files
Files changed (1) hide show
  1. chat_tab.py +150 -133
chat_tab.py CHANGED
@@ -1,157 +1,174 @@
1
- import os
2
- import json
3
- import time
4
- import gradio as gr
5
- from gradio_client import Client
6
- import torch
7
-
8
- # إعدادات الجهاز والخزنة الخارجية للرادار
9
- DEVICE = "cuda" if torch.cuda.is_available() else "cpu"
10
- DATASET_REPO = "Asem75/aiocr_asistant"
11
-
12
- # ------------------ التحميل الكسول المتباعد (Lazy & Delayed Loading) ------------------
13
- _whisper_pipe = None
14
- _llm_tokenizer = None
15
- _llm_model = None
16
-
17
- def load_whisper_lazy():
18
- """تحميل مؤجل وكسول لـ Whisper لمنع انفجار ذاكرة المساحة الحالية"""
19
- global _whisper_pipe
20
- if _whisper_pipe is not None:
21
- return _whisper_pipe
22
-
23
- print("⏳ بدء تشغيل التحميل المؤجل لـ Whisper...")
24
- time.sleep(2) # تأخير أمني متباعد لحماية السيرفر
25
-
26
- from transformers import pipeline
27
- _whisper_pipe = pipeline(
28
- "automatic-speech-recognition",
29
- model="openai/whisper-base",
30
- device=DEVICE
31
- )
32
- print("✅ تم تحميل Whisper بأمان.")
33
- return _whisper_pipe
34
-
35
- def load_teacher_llm_lazy():
36
- """تحميل كسلان ومتباعد لنموذج مايكروسوفت المستقر Phi-3"""
37
- global _llm_tokenizer, _llm_model
38
- if _llm_model is not None:
39
- return _llm_model, _llm_tokenizer
40
-
41
- print("⏳ تهيئة السيرفر لاستقبال نموذج مايكروسوفت Phi-3...")
42
- time.sleep(3) # تأخير إضافي لفصل العمليات
43
-
44
- from transformers import AutoTokenizer, AutoModelForCausalLM
45
- model_id = "microsoft/Phi-3-mini-4k-instruct"
46
-
47
- _llm_tokenizer = AutoTokenizer.from_pretrained(model_id)
48
- _llm_model = AutoModelForCausalLM.from_pretrained(
49
- model_id,
50
- torch_dtype=torch.float16 if DEVICE == "cuda" else torch.float32,
51
- device_map="auto"
52
- )
53
- print("✅ تم تحميل نموذج الأستاذ بنجاح واستقرار.")
54
- return _llm_model, _llm_tokenizer
55
-
56
- # ------------------ الاتصال بمساحة الرادار (العقل الصوتي والخزنة) ------------------
57
- def call_radar_voice_api(text_content, voice_name, user_email, user_token):
58
- """إرسال النص والمعلومات إلى مساحة الرادار ليتولى فحص الـ VIP وحفظ الملف باسم المستخدم"""
59
- try:
60
- # الاتصال بمساحة الرادار كـ Client خلفي
61
- radar_client = Client("Asem75/Aiocr_Radar")
62
-
63
- # استدعاء نقطة الـ API المفتوحة باسم synth_arabic (بدون شرطة مائلة طبقاً لقواعد جريديو)
64
- # نرسل له البيانات كاملة ليتحقق الرادار من جانبه ويحفظ الملف في المجلد المخصص
65
- audio_url = radar_client.predict(
66
- text=text_content,
67
- speaker_name=voice_name,
68
- email=user_email,
69
- device_token=user_token,
70
- api_name="synth_arabic"
71
- )
72
- return audio_url
73
- except Exception as e:
74
- print(f"⚠️ فشل الاتصال بمساحة الرادار أو تم حظر المستخدم: {e}")
75
- return None
76
-
77
- # ------------------ معالجة الحوار التفاعلي المتدرج ------------------
78
- def teacher_chat_process(audio_mic, chat_history, input_name, input_email, device_token):
79
  if audio_mic is None:
80
  return chat_history, None, "⚠️ الرجاء تسجيل سؤالك أولاً عبر المايكروفون."
81
 
82
- # 1. تحويل صوت الطالب إلى نص
83
  whisper_model = load_whisper_lazy()
84
- transcription = whisper_model(audio_mic, generate_kwargs={"language": "arabic"})["text"]
85
-
 
 
 
86
  if not transcription.strip():
87
  return chat_history, None, "❌ لم يتم التقاط كلمات واضحة."
88
 
89
- # 2. استدعاء نموذج مايكروسوفت Phi-3 للرد المتدرج
90
- model, tokenizer = load_teacher_llm_lazy()
91
-
92
- system_prompt = (
93
- "أنت الأستاذ عبود، معلم خبير وموجه تربوي ذكي. تتحدث باللغة العربية الفصحى وبنبرة مشوقة. "
 
 
 
 
 
 
 
 
 
 
 
 
94
  "قاعدتك الأساسية: لا تعطي الطالب الشرح كاملاً أو الإجابة النهائية دفعة واحدة أبداً! "
95
  "قم بتقسيم المعلومات وتدرج في إعطائها له فكرة فكرة (فقرة قصيرة لا تتجاوز 30 كلمة). "
96
  "في نهاية كل رد، اطرح على الطالب سؤالاً تفاعلياً قصيراً لتتأكد من فهمه قبل الانتقال للخطوة التالية."
97
  )
98
-
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
99
  messages = [{"role": "system", "content": system_prompt}]
100
- for user_msg, ai_msg in chat_history:
101
- messages.append({"role": "user", "content": user_msg})
102
- messages.append({"role": "assistant", "content": ai_msg})
 
 
 
 
 
 
 
103
  messages.append({"role": "user", "content": transcription})
104
-
105
- prompt = tokenizer.apply_chat_template(messages, tokenize=False, add_generation_prompt=True)
 
 
 
 
 
 
 
 
106
  inputs = tokenizer(prompt, return_tensors="pt").to(DEVICE)
107
-
 
 
 
 
 
 
 
108
  with torch.no_grad():
109
- outputs = model.generate(**inputs, max_new_tokens=150, temperature=0.7, do_sample=True)
110
-
111
- full_response = tokenizer.decode(outputs[0][inputs.input_ids.shape[1]:], skip_special_tokens=True)
112
-
113
- chat_history.append((transcription, full_response))
114
-
115
- # 3. إرسال رد الأستاذ الصادر من هنا إلى الرادار لتوليده صوتاً (مع إرسال إيميل وبصمة المستخدم)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
116
  print("🔊 توجيه النص ديناميكياً لمساحة الرادار...")
117
- # نختار صوت شرح مناسب من مايكروسوفت بشكل افتراضي للردود
118
  audio_path = call_radar_voice_api(
119
  text_content=full_response,
120
  voice_name="ar-EG-SalmaNeural",
121
- user_email=input_email.strip(),
122
  user_token=device_token.strip()
123
  )
124
-
125
  if audio_path is None:
126
  status = "❌ فشل التوليد الصوتي (قد يكون المستخدم غير مسجل كـ VIP في الرادار)"
127
  else:
128
- status = f"✅ تم التوليد بنجاح عبر الرادار وحفظ الصوت باسمك."
129
-
130
- return chat_history, audio_path, status
131
-
132
- # ------------------ بناء التبويب الخاص بالمحادثة ------------------
133
- def create_chat_tab(current_user_name, current_user_email, current_device_token):
134
- with gr.Tab("💬 محادثة الأستاذ عبود الصوتية (VIP)"):
135
- gr.Markdown("### 🎙️ حُجرة المحادثة الشخصية المباشرة مع الأستاذ (بواسطة Whisper & Phi-3)")
136
-
137
- with gr.Row():
138
- with gr.Column(scale=2):
139
- chatbot = gr.Chatbot(label="👨‍🏫 النقاش التفاعلي المتدرج مع الأستاذ", bubble_full_width=False, rtl=True)
140
- student_mic = gr.Audio(label="🎤 تحدث الآن مع الأستاذ (اضغط وسجل سؤالك)", type="filepath", sources=["microphone"])
141
- send_btn = gr.Button("🚀 إرسال السؤال الصوتي", variant="primary")
142
-
143
- with gr.Column(scale=1):
144
- gr.Markdown("🔒 **بيانات جدار الحماية البرمجي (المستلمة تلقائياً من نظام التشغيل):**")
145
- gate_name = gr.Textbox(label="اسم المستخدم", value=current_user_name, interactive=False)
146
- gate_email = gr.Textbox(label="البريد الإلكتروني الموثق", value=current_user_email, interactive=False)
147
- gate_token = gr.Textbox(label="بصمة الجهاز المكتشفة", value=current_device_token, interactive=False)
148
-
149
- gr.Markdown("---")
150
- teacher_voice_output = gr.Audio(label="🔊 استمع لرد الأستاذ الشفهي (القادم من الرادار)", type="filepath")
151
- system_status = gr.Textbox(label="حالة بوابة الحماية والمعالجة", interactive=False)
152
-
153
- send_btn.click(
154
- fn=teacher_chat_process,
155
- inputs=[student_mic, chatbot, gate_name, gate_email, gate_token],
156
- outputs=[chatbot, teacher_voice_output, system_status]
157
- )
 
1
+ # ======================== دالة المحادثة المطورة (مع الدرس الكامل) ========================
2
+ def teacher_chat_process(
3
+ audio_mic,
4
+ chat_history,
5
+ student_name,
6
+ user_email,
7
+ device_token,
8
+ grade,
9
+ semester,
10
+ subject,
11
+ lesson,
12
+ track
13
+ ):
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
14
  if audio_mic is None:
15
  return chat_history, None, "⚠️ الرجاء تسجيل سؤالك أولاً عبر المايكروفون."
16
 
17
+ # 1. تحويل الصوت إلى نص
18
  whisper_model = load_whisper_lazy()
19
+ transcription = whisper_model(
20
+ audio_mic,
21
+ generate_kwargs={"language": "arabic"}
22
+ )["text"]
23
+
24
  if not transcription.strip():
25
  return chat_history, None, "❌ لم يتم التقاط كلمات واضحة."
26
 
27
+ print(f"🗣️ الطالب {student_name}: {transcription}")
28
+
29
+ # 2. البحث عن ملف الدرس في الخزنة وحقنه كاملاً
30
+ lesson_content = find_lesson_file_in_vault(grade, semester, subject, lesson)
31
+
32
+ # 3. بناء سياق النموذج
33
+ context_parts = [f"مادة {subject}", f"{lesson}"]
34
+ if semester:
35
+ context_parts.insert(1, f"الفصل {semester}")
36
+ if track:
37
+ context_parts.append(f"تخصص {track}")
38
+ context_str = " - ".join(context_parts)
39
+
40
+ system_prompt_base = (
41
+ f"أنت الأستاذ عبود، معلم خبير وموجه تربوي ذكي. "
42
+ f"السياق الحالي: {context_str}. "
43
+ "تتحدث باللغة العربية الفصحى وبنبرة مشوقة. "
44
  "قاعدتك الأساسية: لا تعطي الطالب الشرح كاملاً أو الإجابة النهائية دفعة واحدة أبداً! "
45
  "قم بتقسيم المعلومات وتدرج في إعطائها له فكرة فكرة (فقرة قصيرة لا تتجاوز 30 كلمة). "
46
  "في نهاية كل رد، اطرح على الطالب سؤالاً تفاعلياً قصيراً لتتأكد من فهمه قبل الانتقال للخطوة التالية."
47
  )
48
+
49
+ # 4. معالجة محتوى الدرس (كاملاً مع تقسيم ذكي إذا لزم الأمر)
50
+ lesson_part = 1
51
+ total_parts = 1
52
+ lesson_text = ""
53
+
54
+ if lesson_content:
55
+ # حساب الحد الأقصى المتاح (4096 توكن ≈ 12000 حرف عربي)
56
+ # نترك مساحة للـ system prompt والتاريخ وسؤال المستخدم والرد (≈ 2000 حرف)
57
+ MAX_CHARS_FOR_LESSON = 10000
58
+
59
+ if len(lesson_content) <= MAX_CHARS_FOR_LESSON:
60
+ # الدرس صغير بما يكفي - نأخذه كاملاً
61
+ lesson_text = lesson_content
62
+ print(f"📚 تم تحميل الدرس كاملاً ({len(lesson_content)} حرف)")
63
+ else:
64
+ # الدرس كبير - نقسمه إلى أجزاء
65
+ total_parts = (len(lesson_content) // MAX_CHARS_FOR_LESSON) + 1
66
+
67
+ # نأخذ الجزء الأول فقط لهذه الجولة
68
+ start = 0
69
+ end = MAX_CHARS_FOR_LESSON
70
+ lesson_text = lesson_content[start:end]
71
+ lesson_part = 1
72
+
73
+ print(f"📚 الدرس كبير ({len(lesson_content)} حرف) - تم تحميل الجزء {lesson_part}/{total_parts}")
74
+
75
+ # إضافة محتوى الدرس إلى system prompt
76
+ if total_parts == 1:
77
+ lesson_header = "\n\n📖 المحتوى الكامل للدرس (المرجع الأساسي لإجاباتك):\n"
78
+ else:
79
+ lesson_header = f"\n\n📖 محتوى الدرس - الجزء {lesson_part}/{total_parts} (المرجع الأساسي لإجاباتك):\n"
80
+
81
+ system_prompt = system_prompt_base + lesson_header + "```\n" + lesson_text + "\n```"
82
+
83
+ if total_parts > 1:
84
+ system_prompt += (
85
+ f"\n⚠️ تنبيه: هذا الجزء {lesson_part} من {total_parts}. "
86
+ "عند الانتهاء من شرح هذا الجزء، أخبر الطالب بأن هناك أجزاء أخرى متاحة للمتابعة."
87
+ )
88
+ else:
89
+ system_prompt = system_prompt_base
90
+ print("⚠️ لم يتم العثور على محتوى الدرس - سيستخدم النموذج معرفته العامة")
91
+
92
+ # 5. استدعاء نموذج Phi-3
93
+ model, tokenizer = load_teacher_llm_lazy()
94
+
95
  messages = [{"role": "system", "content": system_prompt}]
96
+
97
+ # معالجة تاريخ المحادثة
98
+ if chat_history:
99
+ for msg in chat_history:
100
+ if isinstance(msg, dict):
101
+ messages.append({"role": msg["role"], "content": msg["content"]})
102
+ elif isinstance(msg, (list, tuple)) and len(msg) == 2:
103
+ messages.append({"role": "user", "content": str(msg[0])})
104
+ messages.append({"role": "assistant", "content": str(msg[1])})
105
+
106
  messages.append({"role": "user", "content": transcription})
107
+
108
+ # طباعة حجم السياق للمراقبة
109
+ total_chars = sum(len(m["content"]) for m in messages)
110
+ print(f"📊 حجم السياق الكلي: {total_chars} حرف (~{total_chars // 3} توكن تقريبي)")
111
+
112
+ prompt = tokenizer.apply_chat_template(
113
+ messages,
114
+ tokenize=False,
115
+ add_generation_prompt=True
116
+ )
117
  inputs = tokenizer(prompt, return_tensors="pt").to(DEVICE)
118
+
119
+ # التحقق من عدم تجاوز الحد الأقصى
120
+ input_length = inputs.input_ids.shape[1]
121
+ print(f"📊 عدد التوكنز الفعلي: {input_length} (الحد الأقصى: 4096)")
122
+
123
+ if input_length > 4000:
124
+ print("⚠️ تحذير: السياق كبير جداً وقد يتم اقتطاعه تلقائياً")
125
+
126
  with torch.no_grad():
127
+ outputs = model.generate(
128
+ **inputs,
129
+ max_new_tokens=150,
130
+ temperature=0.7,
131
+ do_sample=True,
132
+ pad_token_id=tokenizer.eos_token_id
133
+ )
134
+
135
+ full_response = tokenizer.decode(
136
+ outputs[0][inputs.input_ids.shape[1]:],
137
+ skip_special_tokens=True
138
+ )
139
+
140
+ print(f"🤖 الأستاذ: {full_response[:80]}...")
141
+
142
+ # 6. تحديث تاريخ المحادثة
143
+ if not chat_history:
144
+ chat_history = []
145
+ chat_history.append({"role": "user", "content": transcription})
146
+ chat_history.append({"role": "assistant", "content": full_response})
147
+
148
+ # 7. تحديث الخزنة السحابية
149
+ ensure_vault_structure(
150
+ grade=grade,
151
+ semester=semester,
152
+ subject=subject,
153
+ lesson=lesson,
154
+ user_name=student_name,
155
+ track=track
156
+ )
157
+
158
+ # 8. إرسال الرد إلى الرادار لتوليد الصوت
159
  print("🔊 توجيه النص ديناميكياً لمساحة الرادار...")
 
160
  audio_path = call_radar_voice_api(
161
  text_content=full_response,
162
  voice_name="ar-EG-SalmaNeural",
163
+ user_email=user_email.strip(),
164
  user_token=device_token.strip()
165
  )
166
+
167
  if audio_path is None:
168
  status = "❌ فشل التوليد الصوتي (قد يكون المستخدم غير مسجل كـ VIP في الرادار)"
169
  else:
170
+ status = f"✅ تم التوليد بنجاح | الدرس: {lesson}"
171
+ if total_parts > 1:
172
+ status += f" (الجزء {lesson_part}/{total_parts})"
173
+
174
+ return chat_history, audio_path, status