Spaces:
Sleeping
Sleeping
File size: 7,371 Bytes
26274a1 abcd1c7 5c42cdf 26274a1 5c42cdf 26274a1 abcd1c7 26274a1 abcd1c7 5c42cdf abcd1c7 26274a1 abcd1c7 26274a1 abcd1c7 26274a1 abcd1c7 26274a1 abcd1c7 26274a1 5c42cdf abcd1c7 26274a1 abcd1c7 26274a1 5c42cdf 26274a1 | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 | # ======================== دالة المحادثة المطورة (مع الدرس الكامل) ========================
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 |