# ======================== دالة المحادثة المطورة (مع الدرس الكامل) ======================== 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