Spaces:
Sleeping
Sleeping
| # ======================== دالة المحادثة المطورة (مع الدرس الكامل) ======================== | |
| def teacher_chat_process( | |
| audio_mic, | |
| chat_history, | |
| student_name, | |
| user_email, | |
| device_token, | |
| grade, | |
| semester, | |
| subject, | |
| lesson, | |
| track | |
| ): | |
| if audio_mic is None: | |
| return chat_history, None, "⚠️ الرجاء تسجيل سؤالك أولاً عبر المايكروفون." | |
| # 1. تحويل الصوت إلى نص | |
| whisper_model = load_whisper_lazy() | |
| transcription = whisper_model( | |
| audio_mic, | |
| generate_kwargs={"language": "arabic"} | |
| )["text"] | |
| if not transcription.strip(): | |
| return chat_history, None, "❌ لم يتم التقاط كلمات واضحة." | |
| print(f"🗣️ الطالب {student_name}: {transcription}") | |
| # 2. البحث عن ملف الدرس في الخزنة وحقنه كاملاً | |
| lesson_content = find_lesson_file_in_vault(grade, semester, subject, lesson) | |
| # 3. بناء سياق النموذج | |
| context_parts = [f"مادة {subject}", f"{lesson}"] | |
| if semester: | |
| context_parts.insert(1, f"الفصل {semester}") | |
| if track: | |
| context_parts.append(f"تخصص {track}") | |
| context_str = " - ".join(context_parts) | |
| system_prompt_base = ( | |
| f"أنت الأستاذ عبود، معلم خبير وموجه تربوي ذكي. " | |
| f"السياق الحالي: {context_str}. " | |
| "تتحدث باللغة العربية الفصحى وبنبرة مشوقة. " | |
| "قاعدتك الأساسية: لا تعطي الطالب الشرح كاملاً أو الإجابة النهائية دفعة واحدة أبداً! " | |
| "قم بتقسيم المعلومات وتدرج في إعطائها له فكرة فكرة (فقرة قصيرة لا تتجاوز 30 كلمة). " | |
| "في نهاية كل رد، اطرح على الطالب سؤالاً تفاعلياً قصيراً لتتأكد من فهمه قبل الانتقال للخطوة التالية." | |
| ) | |
| # 4. معالجة محتوى الدرس (كاملاً مع تقسيم ذكي إذا لزم الأمر) | |
| lesson_part = 1 | |
| total_parts = 1 | |
| lesson_text = "" | |
| if lesson_content: | |
| # حساب الحد الأقصى المتاح (4096 توكن ≈ 12000 حرف عربي) | |
| # نترك مساحة للـ system prompt والتاريخ وسؤال المستخدم والرد (≈ 2000 حرف) | |
| MAX_CHARS_FOR_LESSON = 10000 | |
| if len(lesson_content) <= MAX_CHARS_FOR_LESSON: | |
| # الدرس صغير بما يكفي - نأخذه كاملاً | |
| lesson_text = lesson_content | |
| print(f"📚 تم تحميل الدرس كاملاً ({len(lesson_content)} حرف)") | |
| else: | |
| # الدرس كبير - نقسمه إلى أجزاء | |
| total_parts = (len(lesson_content) // MAX_CHARS_FOR_LESSON) + 1 | |
| # نأخذ الجزء الأول فقط لهذه الجولة | |
| start = 0 | |
| end = MAX_CHARS_FOR_LESSON | |
| lesson_text = lesson_content[start:end] | |
| lesson_part = 1 | |
| print(f"📚 الدرس كبير ({len(lesson_content)} حرف) - تم تحميل الجزء {lesson_part}/{total_parts}") | |
| # إضافة محتوى الدرس إلى system prompt | |
| if total_parts == 1: | |
| lesson_header = "\n\n📖 المحتوى الكامل للدرس (المرجع الأساسي لإجاباتك):\n" | |
| else: | |
| lesson_header = f"\n\n📖 محتوى الدرس - الجزء {lesson_part}/{total_parts} (المرجع الأساسي لإجاباتك):\n" | |
| system_prompt = system_prompt_base + lesson_header + "```\n" + lesson_text + "\n```" | |
| if total_parts > 1: | |
| system_prompt += ( | |
| f"\n⚠️ تنبيه: هذا الجزء {lesson_part} من {total_parts}. " | |
| "عند الانتهاء من شرح هذا الجزء، أخبر الطالب بأن هناك أجزاء أخرى متاحة للمتابعة." | |
| ) | |
| else: | |
| system_prompt = system_prompt_base | |
| print("⚠️ لم يتم العثور على محتوى الدرس - سيستخدم النموذج معرفته العامة") | |
| # 5. استدعاء نموذج Phi-3 | |
| model, tokenizer = load_teacher_llm_lazy() | |
| messages = [{"role": "system", "content": system_prompt}] | |
| # معالجة تاريخ المحادثة | |
| if chat_history: | |
| for msg in chat_history: | |
| if isinstance(msg, dict): | |
| messages.append({"role": msg["role"], "content": msg["content"]}) | |
| elif isinstance(msg, (list, tuple)) and len(msg) == 2: | |
| messages.append({"role": "user", "content": str(msg[0])}) | |
| messages.append({"role": "assistant", "content": str(msg[1])}) | |
| messages.append({"role": "user", "content": transcription}) | |
| # طباعة حجم السياق للمراقبة | |
| total_chars = sum(len(m["content"]) for m in messages) | |
| print(f"📊 حجم السياق الكلي: {total_chars} حرف (~{total_chars // 3} توكن تقريبي)") | |
| prompt = tokenizer.apply_chat_template( | |
| messages, | |
| tokenize=False, | |
| add_generation_prompt=True | |
| ) | |
| inputs = tokenizer(prompt, return_tensors="pt").to(DEVICE) | |
| # التحقق من عدم تجاوز الحد الأقصى | |
| input_length = inputs.input_ids.shape[1] | |
| print(f"📊 عدد التوكنز الفعلي: {input_length} (الحد الأقصى: 4096)") | |
| if input_length > 4000: | |
| print("⚠️ تحذير: السياق كبير جداً وقد يتم اقتطاعه تلقائياً") | |
| with torch.no_grad(): | |
| outputs = model.generate( | |
| **inputs, | |
| max_new_tokens=150, | |
| temperature=0.7, | |
| do_sample=True, | |
| pad_token_id=tokenizer.eos_token_id | |
| ) | |
| full_response = tokenizer.decode( | |
| outputs[0][inputs.input_ids.shape[1]:], | |
| skip_special_tokens=True | |
| ) | |
| print(f"🤖 الأستاذ: {full_response[:80]}...") | |
| # 6. تحديث تاريخ المحادثة | |
| if not chat_history: | |
| chat_history = [] | |
| chat_history.append({"role": "user", "content": transcription}) | |
| chat_history.append({"role": "assistant", "content": full_response}) | |
| # 7. تحديث الخزنة السحابية | |
| ensure_vault_structure( | |
| grade=grade, | |
| semester=semester, | |
| subject=subject, | |
| lesson=lesson, | |
| user_name=student_name, | |
| track=track | |
| ) | |
| # 8. إرسال الرد إلى الرادار لتوليد الصوت | |
| print("🔊 توجيه النص ديناميكياً لمساحة الرادار...") | |
| audio_path = call_radar_voice_api( | |
| text_content=full_response, | |
| voice_name="ar-EG-SalmaNeural", | |
| user_email=user_email.strip(), | |
| user_token=device_token.strip() | |
| ) | |
| if audio_path is None: | |
| status = "❌ فشل التوليد الصوتي (قد يكون المستخدم غير مسجل كـ VIP في الرادار)" | |
| else: | |
| status = f"✅ تم التوليد بنجاح | الدرس: {lesson}" | |
| if total_parts > 1: | |
| status += f" (الجزء {lesson_part}/{total_parts})" | |
| return chat_history, audio_path, status |