Riy777 commited on
Commit
4716a13
·
verified ·
1 Parent(s): f0ce1c3

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +479 -5
app.py CHANGED
@@ -3,6 +3,7 @@ import logging
3
  import asyncio
4
  import aiohttp
5
  import socket
 
6
  from telegram import Update, InlineKeyboardButton, InlineKeyboardMarkup
7
  from telegram.ext import Application, CommandHandler, CallbackQueryHandler, MessageHandler, filters, ContextTypes, ConversationHandler
8
  from huggingface_hub import HfApi, hf_hub_download, list_repo_files
@@ -98,7 +99,7 @@ class MedicalLabBot:
98
  set_webhook_url,
99
  json={
100
  "url": webhook_url,
101
- "allowed_updates": ["message", "callback_query", "inline_query"],
102
  "drop_pending_updates": True
103
  },
104
  headers={"Host": "api.telegram.org"}
@@ -209,7 +210,11 @@ class MedicalLabBot:
209
  logger.error(f"❌ خطأ في تحميل المواد: {e}")
210
  self.available_materials = {'Biochemistry': {'files': [], 'file_details': {}}}
211
 
212
- # ... (بقية الدوال تبقى كما هي مع إزالة الدوال غير الضرورية)
 
 
 
 
213
 
214
  def extract_file_info(self, file_name, file_path):
215
  """استخراج معلومات الملف (النوع، رقم المحاضرة) من الاسم"""
@@ -217,7 +222,6 @@ class MedicalLabBot:
217
  lecture_num = None
218
  file_type = 'unknown'
219
 
220
- import re
221
  match = re.search(r'(?:lecture|lec|محاضرة)\s*(\d+)', name_lower)
222
  if not match:
223
  match = re.search(r'^(\d+)\s*-|[\s_-](\d+)$', name_lower)
@@ -261,7 +265,218 @@ class MedicalLabBot:
261
  logger.error(f"❌ Error calling NVIDIA API: {e}", exc_info=True)
262
  return f"❌ حدث خطأ أثناء التواصل مع الذكاء الاصطناعي: {e}"
263
 
264
- # ... (استمرار بقية الدوال الأساسية)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
265
 
266
  async def start(self, update: Update, context: ContextTypes.DEFAULT_TYPE):
267
  """بدء المحادثة وعرض القائمة الرئيسية"""
@@ -270,7 +485,9 @@ class MedicalLabBot:
270
 
271
  welcome_text = """
272
  🏥 **مرحباً بك في بوت المختبرات الطبية الذكي** 🔬
 
273
  أنا هنا لمساعدتك في دراسة موادك! 📚
 
274
  **المواد المتاحة حالياً:**
275
  """
276
 
@@ -313,7 +530,264 @@ class MedicalLabBot:
313
  keyboard.append([InlineKeyboardButton("❓ مساعدة", callback_data="general_help")])
314
  return keyboard
315
 
316
- # ... (استمرار بقية الدوال الأساسية)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
317
 
318
  # ========== إنشاء كائن البوت ==========
319
  bot = MedicalLabBot()
 
3
  import asyncio
4
  import aiohttp
5
  import socket
6
+ import re
7
  from telegram import Update, InlineKeyboardButton, InlineKeyboardMarkup
8
  from telegram.ext import Application, CommandHandler, CallbackQueryHandler, MessageHandler, filters, ContextTypes, ConversationHandler
9
  from huggingface_hub import HfApi, hf_hub_download, list_repo_files
 
99
  set_webhook_url,
100
  json={
101
  "url": webhook_url,
102
+ "allowed_updates": ["message", "callback_query"],
103
  "drop_pending_updates": True
104
  },
105
  headers={"Host": "api.telegram.org"}
 
210
  logger.error(f"❌ خطأ في تحميل المواد: {e}")
211
  self.available_materials = {'Biochemistry': {'files': [], 'file_details': {}}}
212
 
213
+ def get_user_memory(self, user_id):
214
+ """الحصول على ذاكرة المستخدم أو إنشاؤها"""
215
+ if user_id not in self.conversation_memory:
216
+ self.conversation_memory[user_id] = {'history': [], 'last_subject': None}
217
+ return self.conversation_memory[user_id]
218
 
219
  def extract_file_info(self, file_name, file_path):
220
  """استخراج معلومات الملف (النوع، رقم المحاضرة) من الاسم"""
 
222
  lecture_num = None
223
  file_type = 'unknown'
224
 
 
225
  match = re.search(r'(?:lecture|lec|محاضرة)\s*(\d+)', name_lower)
226
  if not match:
227
  match = re.search(r'^(\d+)\s*-|[\s_-](\d+)$', name_lower)
 
265
  logger.error(f"❌ Error calling NVIDIA API: {e}", exc_info=True)
266
  return f"❌ حدث خطأ أثناء التواصل مع الذكاء الاصطناعي: {e}"
267
 
268
+ def _find_file_by_query(self, query, subject):
269
+ """البحث عن ملف بناءً على استعلام المستخدم"""
270
+ files = self.available_materials[subject]['files']
271
+ query_lower = query.lower().strip()
272
+
273
+ try:
274
+ match_num = re.findall(r'^\d+$', query_lower)
275
+ if match_num:
276
+ index = int(match_num[0])
277
+ if 1 <= index <= len(files):
278
+ return files[index - 1]
279
+ except (IndexError, ValueError):
280
+ pass
281
+
282
+ match_lec_num = re.search(r'(?:lecture|lec|محاضرة)\s*(\d+)', query_lower)
283
+ if match_lec_num:
284
+ num = int(match_lec_num.group(1))
285
+ for file_info in files:
286
+ if file_info['lecture_number'] == num:
287
+ return file_info
288
+
289
+ best_match = None
290
+ highest_score = 0
291
+ for file_info in files:
292
+ name_lower = file_info['name'].lower()
293
+ query_words = set(query_lower.split())
294
+ name_words = set(re.findall(r'\w+', name_lower))
295
+ common_words = query_words.intersection(name_words)
296
+ score = len(common_words)
297
+
298
+ if score > highest_score:
299
+ highest_score = score
300
+ best_match = file_info
301
+
302
+ return best_match
303
+
304
+ async def download_and_extract_content(self, file_path, subject):
305
+ """تحميل الملف من HF واستخراج النص منه"""
306
+ if file_path in self.file_cache:
307
+ return self.file_cache[file_path]
308
+
309
+ logger.info(f"⏳ Downloading {file_path} from Hugging Face...")
310
+ try:
311
+ local_path = await asyncio.to_thread(
312
+ hf_hub_download,
313
+ repo_id=REPO_ID,
314
+ filename=file_path,
315
+ repo_type="dataset"
316
+ )
317
+
318
+ text_content = ""
319
+ logger.info(f"📄 Extracting content from {local_path}")
320
+
321
+ if local_path.lower().endswith('.pdf'):
322
+ with fitz.open(local_path) as doc:
323
+ for page in doc:
324
+ text_content += page.get_text("text", sort=True)
325
+
326
+ elif local_path.lower().endswith('.txt'):
327
+ with open(local_path, 'r', encoding='utf-8', errors='ignore') as f:
328
+ text_content = f.read()
329
+
330
+ elif local_path.lower().endswith('.docx'):
331
+ doc_obj = await asyncio.to_thread(docx.Document, local_path)
332
+ full_text = []
333
+ for para in doc_obj.paragraphs:
334
+ full_text.append(para.text)
335
+ text_content = '\n'.join(full_text)
336
+
337
+ else:
338
+ return f"Error: Unsupported file type ({os.path.basename(file_path)})."
339
+
340
+ text_content = re.sub(r'\s+\n', '\n', text_content)
341
+ text_content = re.sub(r'\n{3,}', '\n\n', text_content)
342
+ text_content = re.sub(r' +', ' ', text_content).strip()
343
+
344
+ self.file_cache[file_path] = text_content
345
+ return text_content
346
+
347
+ except Exception as e:
348
+ logger.error(f"❌ Error downloading/extracting {file_path}: {e}")
349
+ return f"Error: Could not retrieve or process file {os.path.basename(file_path)}."
350
+
351
+ async def explain_lecture(self, user_query, subject, user_id):
352
+ """شرح محاضرة بناءً على استعلام المستخدم"""
353
+ file_info = self._find_file_by_query(user_query, subject)
354
+
355
+ if not file_info:
356
+ files_list_text = await self.get_files_list_text(subject)
357
+ return f"❌ لم أتمكن من العثور على الملف المطلوب.\n\n{files_list_text}"
358
+
359
+ file_path = file_info['path']
360
+ file_name = file_info['name']
361
+
362
+ content = await self.download_and_extract_content(file_path, subject)
363
+
364
+ if content.startswith("Error:"):
365
+ return f"❌ خطأ في معالجة الملف: {file_name}\n{content}"
366
+
367
+ if not content.strip():
368
+ return f"❌ المحتوى فارغ للملف: {file_name}"
369
+
370
+ memory = self.get_user_memory(user_id)
371
+ memory['history'].append({"role": "user", "content": f"اشرح لي النقاط الأساسية في هذه المحاضرة: {file_name}"})
372
+
373
+ max_content_chars = 7000
374
+ if len(content) > max_content_chars:
375
+ content_snippet = content[:max_content_chars] + "\n\n[... المحتوى مقطوع ...]"
376
+ else:
377
+ content_snippet = content
378
+
379
+ system_prompt = f"أنت مساعد أكاديمي متخصص في مادة {subject}. مهمتك هي شرح النقاط الأساسية في محتوى المحاضرة المقدم لك. ركز على المفاهيم الجوهرية، التعريفات الهامة، النتائج الرئيسية، وأي معلومات ضرورية لفهم الموضوع. قدم الشرح بطريقة منظمة وواضحة باستخدام نقاط Markdown."
380
+
381
+ messages = [
382
+ {"role": "system", "content": system_prompt},
383
+ {"role": "user", "content": f"اسم الملف: {file_name}\n\nالمحتوى:\n```\n{content_snippet}\n```\n\nيرجى شرح النقاط الأساسية في هذا المحتوى."}
384
+ ]
385
+
386
+ response = await self._call_nvidia_api(messages, 1500)
387
+ memory['history'].append({"role": "assistant", "content": response})
388
+ return f"📝 **شرح لأهم نقاط ملف: {file_name}**\n\n{response}"
389
+
390
+ async def explain_concept(self, user_query, subject, user_id):
391
+ """شرح مفهوم أو مصطلح طبي"""
392
+ memory = self.get_user_memory(user_id)
393
+ memory['history'].append({"role": "user", "content": user_query})
394
+
395
+ system_prompt = f"أنت خبير أكاديمي في مجال المختبرات الطبية، متخصص حالياً في مادة {subject}. اشرح المفهوم أو المصطلح التالي ({user_query}) بوضوح ودقة. ابدأ بتعريف أساسي، ثم وضح أهميته وتطبيقاته العملية في المختبر، واربطه بمادة {subject} إن أمكن."
396
+
397
+ messages = [
398
+ {"role": "system", "content": system_prompt},
399
+ *memory['history'][-3:-1],
400
+ {"role": "user", "content": f"اشرح لي المفهوم التالي: {user_query}"}
401
+ ]
402
+
403
+ response = await self._call_nvidia_api(messages, 1000)
404
+ memory['history'].append({"role": "assistant", "content": response})
405
+ return f"🧪 **شرح مفهوم: {user_query}**\n\n{response}"
406
+
407
+ async def process_general_query(self, user_message, subject, user_id):
408
+ """معالجة استعلام عام من المستخدم"""
409
+ memory = self.get_user_memory(user_id)
410
+ history = memory.get('history', [])
411
+
412
+ system_prompt = f"أنت مساعد ذكي متخصص في المختبرات الطبية. المادة الدراسية الحالية التي يركز عليها الطالب هي '{subject}'. أجب على سؤال الطالب ({user_message}) إجابة واضحة ومباشرة."
413
+
414
+ messages = [{"role": "system", "content": system_prompt}]
415
+ messages.extend(history[-4:])
416
+ messages.append({"role": "user", "content": user_message})
417
+
418
+ response = await self._call_nvidia_api(messages)
419
+ memory['history'].append({"role": "user", "content": user_message})
420
+ memory['history'].append({"role": "assistant", "content": response})
421
+ return response
422
+
423
+ async def generate_questions_for_subject(self, subject, user_id):
424
+ """توليد أسئلة من ملف عشوائي في المادة"""
425
+ if subject not in self.available_materials or not self.available_materials[subject]['files']:
426
+ return "❌ لا توجد ملفات في هذه المادة لتوليد أسئلة منها."
427
+
428
+ files = self.available_materials[subject]['files']
429
+ file_info = random.choice(files)
430
+
431
+ file_path = file_info['path']
432
+ file_name = file_info['name']
433
+
434
+ content = await self.download_and_extract_content(file_path, subject)
435
+
436
+ if content.startswith("Error:") or not content.strip():
437
+ return f"❌ لم أتمكن من قراءة محتوى صالح لتوليد أسئلة من ملفات المادة."
438
+
439
+ max_content_chars = 7000
440
+ content_snippet = content[:max_content_chars] if len(content) > max_content_chars else content
441
+
442
+ system_prompt = f"أنت خبير في وضع الأسئلة لمادة {subject}. بناءً على المحتوى التالي من ملف '{file_name}'، قم بإنشاء 5 أسئلة متنوعة لاختبار الفهم."
443
+
444
+ messages = [
445
+ {"role": "system", "content": system_prompt},
446
+ {"role": "user", "content": f"المحتوى:\n```\n{content_snippet}\n```\n\nقم بإنشاء 5 أسئلة متنوعة بناءً على هذا المحتوى."}
447
+ ]
448
+
449
+ response = await self._call_nvidia_api(messages)
450
+ return f"❓ **أسئلة مقترحة من ملف: {file_name}**\n\n{response}"
451
+
452
+ async def generate_summary(self, subject, user_id):
453
+ """تلخيص ملف مهم من المادة"""
454
+ if subject not in self.available_materials or not self.available_materials[subject]['files']:
455
+ return "❌ لا توجد ملفات في هذه المادة لتلخيصها."
456
+
457
+ files = self.available_materials[subject]['files']
458
+ file_info = files[0]
459
+
460
+ file_path = file_info['path']
461
+ file_name = file_info['name']
462
+
463
+ content = await self.download_and_extract_content(file_path, subject)
464
+
465
+ if content.startswith("Error:") or not content.strip():
466
+ return f"❌ لم أتمكن من قراءة ملف ({file_name}) للتلخيص."
467
+
468
+ max_content_chars = 7000
469
+ content_snippet = content[:max_content_chars] if len(content) > max_content_chars else content
470
+
471
+ system_prompt = f"أنت خبير في تلخيص المواد العلمية لمادة {subject}. قم بتلخيص المحتوى التالي من ملف '{file_name}' في 5 نقاط رئيسية وموجزة."
472
+
473
+ messages = [
474
+ {"role": "system", "content": system_prompt},
475
+ {"role": "user", "content": f"المحتوى:\n```\n{content_snippet}\n```\n\nلخص هذا المحتوى في 5 نقاط رئيسية."}
476
+ ]
477
+
478
+ response = await self._call_nvidia_api(messages)
479
+ return f"📋 **ملخص ملف: {file_name}**\n\n{response}"
480
 
481
  async def start(self, update: Update, context: ContextTypes.DEFAULT_TYPE):
482
  """بدء المحادثة وعرض القائمة الرئيسية"""
 
485
 
486
  welcome_text = """
487
  🏥 **مرحباً بك في بوت المختبرات الطبية الذكي** 🔬
488
+
489
  أنا هنا لمساعدتك في دراسة موادك! 📚
490
+
491
  **المواد المتاحة حالياً:**
492
  """
493
 
 
530
  keyboard.append([InlineKeyboardButton("❓ مساعدة", callback_data="general_help")])
531
  return keyboard
532
 
533
+ async def handle_subject_selection(self, update: Update, context: ContextTypes.DEFAULT_TYPE):
534
+ """معالجة اختيار المادة"""
535
+ query = update.callback_query
536
+ await query.answer()
537
+
538
+ user_id = query.from_user.id
539
+ callback_data = query.data
540
+ logger.info(f"User {user_id} selected: {callback_data}")
541
+
542
+ if callback_data == "general_help":
543
+ return await self.handle_general_help(query, context)
544
+ elif callback_data == "refresh_materials":
545
+ await query.edit_message_text("🔄 جاري تحديث قائمة المواد...")
546
+ self.load_all_materials()
547
+ keyboard = self.create_subjects_keyboard()
548
+ reply_markup = InlineKeyboardMarkup(keyboard)
549
+ await query.edit_message_text("✅ تم تحديث قائمة المواد.\nاختر المادة:", reply_markup=reply_markup)
550
+ return SELECTING_SUBJECT
551
+
552
+ subject = callback_data.replace("subject_", "")
553
+ context.user_data['current_subject'] = subject
554
+
555
+ memory = self.get_user_memory(user_id)
556
+ memory['last_subject'] = subject
557
+
558
+ if subject not in self.available_materials:
559
+ await query.edit_message_text("❌ خطأ: المادة المحددة غير موجودة.")
560
+ return await self.start(update, context)
561
+
562
+ subject_files = self.available_materials[subject]['files']
563
+ subject_name = subject.replace('_', ' ').title()
564
+
565
+ keyboard = [
566
+ [InlineKeyboardButton("📖 شرح محاضرة", callback_data="explain_lecture"), InlineKeyboardButton("🔍 استعراض الملفات", callback_data="browse_files")],
567
+ [InlineKeyboardButton("❓ توليد أسئلة", callback_data="generate_questions"), InlineKeyboardButton("📝 تلخيص ملف", callback_data="summarize_content")],
568
+ [InlineKeyboardButton("🧪 تفسير مفهوم", callback_data="explain_concept")],
569
+ [InlineKeyboardButton("🏠 العودة للقائمة الرئيسية", callback_data="main_menu")]
570
+ ]
571
+ reply_markup = InlineKeyboardMarkup(keyboard)
572
+
573
+ await query.edit_message_text(
574
+ f"📚 **{subject_name}**\n\n"
575
+ f"عدد الملفات المتاحة: {len(subject_files)}\n\n"
576
+ f"ماذا تريد أن تفعل؟",
577
+ reply_markup=reply_markup,
578
+ parse_mode='Markdown'
579
+ )
580
+ return SELECTING_ACTION
581
+
582
+ async def handle_general_help(self, query, context):
583
+ """عرض رسالة المساعدة"""
584
+ help_text = """
585
+ ❓ **مساعدة** ❓
586
+
587
+ هذا البوت مصمم لمساعدتك في دراسة مواد المختبرات الطبية.
588
+
589
+ **الخدمات المتاحة:**
590
+ • 📖 شرح محاضرة
591
+ • 🔍 استعراض الملفات
592
+ • ❓ توليد أسئلة
593
+ • 📝 تلخيص ملف
594
+ • 🧪 تفسير مفهوم
595
+ """
596
+ keyboard = [[InlineKeyboardButton("🔙 العودة للقائمة الرئيسية", callback_data="main_menu")]]
597
+ reply_markup = InlineKeyboardMarkup(keyboard)
598
+
599
+ await query.edit_message_text(help_text, reply_markup=reply_markup)
600
+ return SELECTING_SUBJECT
601
+
602
+ async def handle_back_actions(self, update: Update, context: ContextTypes.DEFAULT_TYPE):
603
+ """معالجة العودة إلى قائمة الإجراءات"""
604
+ query = update.callback_query
605
+ await query.answer()
606
+
607
+ subject = context.user_data.get('current_subject')
608
+ if not subject:
609
+ return await self.start(update, context)
610
+
611
+ subject_files = self.available_materials[subject]['files']
612
+ subject_name = subject.replace('_', ' ').title()
613
+
614
+ keyboard = [
615
+ [InlineKeyboardButton("📖 شرح محاضرة", callback_data="explain_lecture"), InlineKeyboardButton("🔍 استعراض الملفات", callback_data="browse_files")],
616
+ [InlineKeyboardButton("❓ توليد أسئلة", callback_data="generate_questions"), InlineKeyboardButton("📝 تلخيص ملف", callback_data="summarize_content")],
617
+ [InlineKeyboardButton("🧪 تفسير مفهوم", callback_data="explain_concept")],
618
+ [InlineKeyboardButton("🏠 العودة للقائمة الرئيسية", callback_data="main_menu")]
619
+ ]
620
+ reply_markup = InlineKeyboardMarkup(keyboard)
621
+
622
+ await query.edit_message_text(
623
+ f"📚 **{subject_name}**\n\n"
624
+ f"ماذا تريد أن تفعل؟",
625
+ reply_markup=reply_markup,
626
+ parse_mode='Markdown'
627
+ )
628
+ return SELECTING_ACTION
629
+
630
+ async def handle_main_menu(self, update: Update, context: ContextTypes.DEFAULT_TYPE):
631
+ """معالجة العودة للقائمة الرئيسية"""
632
+ query = update.callback_query
633
+ await query.answer()
634
+ context.user_data.clear()
635
+ return await self.start(update, context)
636
+
637
+ async def handle_more_questions(self, update: Update, context: ContextTypes.DEFAULT_TYPE):
638
+ """معالجة طلب المزيد من الأسئلة"""
639
+ query = update.callback_query
640
+ await query.answer()
641
+
642
+ subject = context.user_data.get('current_subject', 'عام')
643
+ keyboard = [[InlineKeyboardButton("🔙 العودة لقائمة الخيارات", callback_data="back_to_actions")]]
644
+ reply_markup = InlineKeyboardMarkup(keyboard)
645
+
646
+ await query.edit_message_text(f"💬 اكتب سؤالك أو طلبك الجديد المتعلق بمادة '{subject}':", reply_markup=reply_markup)
647
+ context.user_data['waiting_for'] = 'general'
648
+ return WAITING_FOR_QUESTION
649
+
650
+ async def handle_change_subject(self, update: Update, context: ContextTypes.DEFAULT_TYPE):
651
+ """معالجة تغيير المادة"""
652
+ query = update.callback_query
653
+ await query.answer()
654
+ keyboard = self.create_subjects_keyboard()
655
+ reply_markup = InlineKeyboardMarkup(keyboard)
656
+ await query.edit_message_text("🔄 اختر المادة الجديدة:", reply_markup=reply_markup)
657
+ return SELECTING_SUBJECT
658
+
659
+ async def handle_action_selection(self, update: Update, context: ContextTypes.DEFAULT_TYPE):
660
+ """معالجة اختيار الإجراء"""
661
+ query = update.callback_query
662
+ await query.answer()
663
+
664
+ action = query.data
665
+ user_id = query.from_user.id
666
+ subject = context.user_data.get('current_subject')
667
+
668
+ if not subject:
669
+ await query.edit_message_text("❌ حدث خطأ في السياق.")
670
+ return await self.start(update, context)
671
+
672
+ back_button = InlineKeyboardButton("🔙 رجوع", callback_data="back_to_actions")
673
+ keyboard_with_back = [[back_button]]
674
+ reply_markup_back = InlineKeyboardMarkup(keyboard_with_back)
675
+
676
+ if action == "main_menu":
677
+ return await self.start(update, context)
678
+
679
+ elif action == "browse_files":
680
+ files_text = await self.get_files_list_text(subject)
681
+ keyboard = [[InlineKeyboardButton("📖 طلب شرح ملف محدد", callback_data="explain_lecture")], [back_button]]
682
+ reply_markup = InlineKeyboardMarkup(keyboard)
683
+ await query.edit_message_text(files_text, reply_markup=reply_markup, parse_mode='Markdown')
684
+ return SELECTING_ACTION
685
+
686
+ elif action == "explain_lecture":
687
+ files_list = await self.get_files_list_text(subject)
688
+ await query.edit_message_text(
689
+ f"📖 **شرح محاضرة**\n\n{files_list}\n\n📝 اكتب رقم الملف أو جزءاً من اسمه:",
690
+ reply_markup=reply_markup_back,
691
+ parse_mode='Markdown'
692
+ )
693
+ context.user_data['waiting_for'] = 'lecture_explanation'
694
+ return WAITING_FOR_QUESTION
695
+
696
+ elif action == "generate_questions":
697
+ await query.edit_message_text("⏳ جاري توليد الأسئلة...", reply_markup=reply_markup_back)
698
+ questions = await self.generate_questions_for_subject(subject, user_id)
699
+ await query.message.reply_text(questions, reply_markup=reply_markup_back, parse_mode='Markdown')
700
+ return SELECTING_ACTION
701
+
702
+ elif action == "explain_concept":
703
+ await query.edit_message_text(
704
+ "🧪 **تفسير مفهوم**\n\nما هو المفهوم أو المصطلح الطبي الذي تود شرحه؟",
705
+ reply_markup=reply_markup_back
706
+ )
707
+ context.user_data['waiting_for'] = 'concept_explanation'
708
+ return WAITING_FOR_QUESTION
709
+
710
+ elif action == "summarize_content":
711
+ await query.edit_message_text("⏳ جاري تلخيص الملف...", reply_markup=reply_markup_back)
712
+ summary = await self.generate_summary(subject, user_id)
713
+ await query.message.reply_text(summary, reply_markup=reply_markup_back, parse_mode='Markdown')
714
+ return SELECTING_ACTION
715
+
716
+ await query.message.reply_text("عذراً، لم أتعرف على هذا الخيار.")
717
+ return SELECTING_ACTION
718
+
719
+ async def get_files_list_text(self, subject):
720
+ """إنشاء نص لقائمة الملفات"""
721
+ if subject not in self.available_materials:
722
+ return "❌ خطأ: لم يتم العثور على المادة المحددة."
723
+
724
+ files = self.available_materials[subject]['files']
725
+ if not files:
726
+ return "❌ لا توجد ملفات متاحة لهذه المادة بعد."
727
+
728
+ files_text = f"📁 **الملفات المتاحة لمادة {subject}:**\n\n"
729
+ max_files_to_show = 25
730
+ for i, file_info in enumerate(files[:max_files_to_show], 1):
731
+ file_name = file_info['name']
732
+ lecture_num = file_info['lecture_number']
733
+ file_type = file_info['type']
734
+
735
+ type_emoji = {
736
+ 'lecture': '📖', 'lab': '🧪', 'exam': '📝', 'summary': '📋', 'unknown': '📄'
737
+ }.get(file_type, '📄')
738
+
739
+ num_text = f" (محاضرة {lecture_num})" if lecture_num else ""
740
+ display_name = file_name.replace("_", " ")
741
+ files_text += f"{i}. {type_emoji} `{display_name}`{num_text}\n"
742
+
743
+ if len(files) > max_files_to_show:
744
+ files_text += f"\n... و {len(files) - max_files_to_show} ملفات أخرى."
745
+
746
+ return files_text
747
+
748
+ async def handle_message(self, update: Update, context: ContextTypes.DEFAULT_TYPE):
749
+ """معالجة الرسائل النصية من المستخدم"""
750
+ if not update.message or not update.message.text:
751
+ return
752
+
753
+ user_message = update.message.text
754
+ user_id = update.effective_user.id
755
+ waiting_for = context.user_data.get('waiting_for')
756
+ subject = context.user_data.get('current_subject', 'general')
757
+
758
+ response = "عذراً، لم أفهم طلبك."
759
+ next_state = WAITING_FOR_QUESTION
760
+
761
+ try:
762
+ if waiting_for == 'lecture_explanation':
763
+ response = await self.explain_lecture(user_message, subject, user_id)
764
+ next_state = SELECTING_ACTION
765
+ elif waiting_for == 'concept_explanation':
766
+ response = await self.explain_concept(user_message, subject, user_id)
767
+ next_state = SELECTING_ACTION
768
+ else:
769
+ response = await self.process_general_query(user_message, subject, user_id)
770
+ next_state = SELECTING_ACTION
771
+
772
+ await update.message.reply_text(response, parse_mode='Markdown')
773
+
774
+ if next_state == SELECTING_ACTION:
775
+ context.user_data['waiting_for'] = None
776
+ keyboard = [
777
+ [InlineKeyboardButton("🔄 طرح سؤال آخر", callback_data="more_questions")],
778
+ [InlineKeyboardButton("📚 تغيير المادة", callback_data="change_subject")],
779
+ [InlineKeyboardButton("🏠 القائمة الرئيسية", callback_data="main_menu")]
780
+ ]
781
+ reply_markup = InlineKeyboardMarkup(keyboard)
782
+ await update.message.reply_text("هل تحتاج مساعدة أخرى؟", reply_markup=reply_markup)
783
+
784
+ return next_state
785
+
786
+ except Exception as e:
787
+ logger.error(f"❌ Error processing message: {e}")
788
+ await update.message.reply_text("❌ حدث خطأ أثناء معالجة طلبك.")
789
+ context.user_data['waiting_for'] = None
790
+ return SELECTING_ACTION
791
 
792
  # ========== إنشاء كائن البوت ==========
793
  bot = MedicalLabBot()