Riy777 commited on
Commit
757ade2
·
verified ·
1 Parent(s): d60f4cf

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +97 -483
app.py CHANGED
@@ -52,25 +52,32 @@ class MedicalLabBot:
52
  self.available_materials = {}
53
  self.file_cache = {}
54
  self.application = None
 
55
  self.load_all_materials()
56
 
57
- def initialize_application(self):
58
- """تهيئة تطبيق التليجرام"""
59
  try:
60
  if not self.application:
61
- # استخدام تكوين مبسط مع إعدادات محددة
62
  self.application = (
63
  Application.builder()
64
  .token(TELEGRAM_BOT_TOKEN)
65
  .build()
66
  )
67
- self.setup_handlers()
 
 
 
 
68
  logger.info("✅ تم تهيئة تطبيق التليجرام بنجاح")
 
69
  except Exception as e:
70
  logger.error(f"❌ خطأ في تهيئة التطبيق: {e}")
71
- raise
 
72
 
73
- def setup_handlers(self):
74
  """إعداد معالجات التليجرام"""
75
  # إعداد Conversation Handler مع إعدادات محسنة
76
  conv_handler = ConversationHandler(
@@ -93,7 +100,7 @@ class MedicalLabBot:
93
  ],
94
  name="medical_lab_conversation",
95
  persistent=False,
96
- per_message=False # هذا الإعداد يقلل التحذيرات
97
  )
98
 
99
  # إضافة الـ handlers
@@ -104,35 +111,6 @@ class MedicalLabBot:
104
  self.application.add_handler(CallbackQueryHandler(self.handle_change_subject, pattern='^change_subject$'))
105
 
106
  logger.info("✅ تم إعداد معالجات التليجرام")
107
-
108
- async def handle_back_actions(self, update: Update, context: ContextTypes.DEFAULT_TYPE):
109
- """معالجة العودة إلى القائمة"""
110
- query = update.callback_query
111
- await query.answer()
112
- return await self.handle_action_selection(update, context)
113
-
114
- async def handle_main_menu(self, update: Update, context: ContextTypes.DEFAULT_TYPE):
115
- """معالجة العودة للقائمة الرئيسية"""
116
- query = update.callback_query
117
- await query.answer()
118
- return await self.start(update, context)
119
-
120
- async def handle_more_questions(self, update: Update, context: ContextTypes.DEFAULT_TYPE):
121
- """معالجة طلب المزيد من الأسئلة"""
122
- query = update.callback_query
123
- await query.answer()
124
- await query.edit_message_text("💬 اكتب سؤالك أو طلبك:")
125
- context.user_data['waiting_for'] = 'general'
126
- return WAITING_FOR_QUESTION
127
-
128
- async def handle_change_subject(self, update: Update, context: ContextTypes.DEFAULT_TYPE):
129
- """معالجة تغيير المادة"""
130
- query = update.callback_query
131
- await query.answer()
132
- keyboard = self.create_subjects_keyboard()
133
- reply_markup = InlineKeyboardMarkup(keyboard)
134
- await query.edit_message_text("اختر المادة:", reply_markup=reply_markup)
135
- return SELECTING_SUBJECT
136
 
137
  def load_all_materials(self):
138
  """تحميل جميع المواد والملفات من Hugging Face"""
@@ -180,145 +158,9 @@ class MedicalLabBot:
180
  except Exception as e:
181
  logger.error(f"❌ خطأ في تحميل المواد: {e}")
182
  self.available_materials = {'Biochemistry': {'files': [], 'file_details': {}}}
183
-
184
- def extract_file_info(self, file_name, file_path):
185
- """استخراج معلومات الملف من اسمه"""
186
- file_info = {
187
- 'name': file_name,
188
- 'path': file_path,
189
- 'extension': file_name.split('.')[-1].lower() if '.' in file_name else '',
190
- 'lecture_number': None,
191
- 'type': 'unknown'
192
- }
193
-
194
- numbers = re.findall(r'\d+', file_name)
195
- if numbers:
196
- file_info['lecture_number'] = int(numbers[0])
197
-
198
- file_lower = file_name.lower()
199
- if any(term in file_lower for term in ['lecture', 'محاضرة', 'lec', 'week']):
200
- file_info['type'] = 'lecture'
201
- elif any(term in file_lower for term in ['lab', 'مختبر', 'عملي']):
202
- file_info['type'] = 'lab'
203
- elif any(term in file_lower for term in ['exam', 'امتحان', 'quiz', 'test']):
204
- file_info['type'] = 'exam'
205
- elif any(term in file_lower for term in ['summary', 'ملخص', 'review']):
206
- file_info['type'] = 'summary'
207
-
208
- return file_info
209
-
210
- async def download_and_extract_content(self, file_path):
211
- """تحميل الملف واستخراج محتواه"""
212
- try:
213
- if file_path in self.file_cache:
214
- return self.file_cache[file_path]
215
-
216
- logger.info(f"📥 جاري تحميل الملف: {file_path}")
217
-
218
- local_path = hf_hub_download(
219
- repo_id=REPO_ID,
220
- filename=file_path,
221
- repo_type="dataset"
222
- )
223
-
224
- content = ""
225
- file_ext = file_path.split('.')[-1].lower()
226
-
227
- if file_ext == 'pdf':
228
- content = await self.extract_pdf_content(local_path)
229
- elif file_ext in ['doc', 'docx']:
230
- content = await self.extract_doc_content(local_path)
231
- elif file_ext in ['txt', 'md']:
232
- with open(local_path, 'r', encoding='utf-8') as f:
233
- content = f.read()
234
- else:
235
- content = f"نوع الملف {file_ext} غير مدعوم حالياً للقراءة المباشرة"
236
-
237
- self.file_cache[file_path] = content
238
- return content
239
-
240
- except Exception as e:
241
- logger.error(f"❌ خطأ في تحميل الملف {file_path}: {e}")
242
- return f"❌ تعذر تحميل محتوى الملف: {str(e)}"
243
-
244
- async def extract_pdf_content(self, pdf_path):
245
- """استخراج النص من ملف PDF"""
246
- try:
247
- text_content = ""
248
-
249
- try:
250
- with open(pdf_path, 'rb') as file:
251
- pdf_reader = PyPDF2.PdfReader(file)
252
- for page_num in range(len(pdf_reader.pages)):
253
- page = pdf_reader.pages[page_num]
254
- text_content += f"\n--- الصفحة {page_num + 1} ---\n"
255
- text_content += page.extract_text() + "\n"
256
- except Exception as e:
257
- logger.warning(f"PyPDF2 failed: {e}")
258
-
259
- if len(text_content.strip()) < 100:
260
- try:
261
- doc = fitz.open(pdf_path)
262
- for page_num in range(doc.page_count):
263
- page = doc[page_num]
264
- text_content += f"\n--- الصفحة {page_num + 1} ---\n"
265
- text_content += page.get_text() + "\n"
266
- doc.close()
267
- except Exception as e:
268
- logger.warning(f"PyMuPDF failed: {e}")
269
-
270
- if len(text_content.strip()) < 50:
271
- text_content = "📄 هذا الملف يحتوي بشكل رئيسي على صور أو رسومات. " \
272
- "يرجى طلب شرح محتوى معين أو طرح أسئلة محددة عن الملف."
273
-
274
- return text_content
275
-
276
- except Exception as e:
277
- logger.error(f"❌ خطأ في استخراج محتوى PDF: {e}")
278
- return f"❌ حدث خطأ في قراءة ملف PDF: {str(e)}"
279
-
280
- async def extract_doc_content(self, doc_path):
281
- """استخراج النص من ملف Word"""
282
- try:
283
- content = "📝 محتوى ملف Word: "
284
- content += "هذا النموذج يدعم قراءة ملفات Word بشكل أساسي. "
285
- content += "للاستفادة الكاملة، يرجى تحويل الملف إلى PDF أو طلب شرح محتوى معين."
286
- return content
287
- except Exception as e:
288
- logger.error(f"❌ خطأ في استخراج محتوى Word: {e}")
289
- return f"❌ حدث خطأ في قراءة ملف Word: {str(e)}"
290
-
291
- def get_user_memory(self, user_id):
292
- """استرجاع ذاكرة المستخدم"""
293
- if user_id not in self.conversation_memory:
294
- self.conversation_memory[user_id] = {
295
- 'conversation_history': [],
296
- 'last_subject': None,
297
- 'last_file': None,
298
- 'preferences': {},
299
- 'current_files': []
300
- }
301
- return self.conversation_memory[user_id]
302
-
303
- def update_user_memory(self, user_id, message, response, subject=None, file_path=None):
304
- """تحديث ذاكرة المستخدم"""
305
- memory = self.get_user_memory(user_id)
306
-
307
- memory['conversation_history'].append({
308
- 'timestamp': datetime.now().isoformat(),
309
- 'user_message': message,
310
- 'bot_response': response,
311
- 'subject': subject,
312
- 'file_path': file_path
313
- })
314
-
315
- if len(memory['conversation_history']) > 20:
316
- memory['conversation_history'] = memory['conversation_history'][-20:]
317
-
318
- if subject:
319
- memory['last_subject'] = subject
320
- if file_path:
321
- memory['last_file'] = file_path
322
 
323
  async def start(self, update: Update, context: ContextTypes.DEFAULT_TYPE):
324
  """بدء المحادثة وعرض القائمة الرئيسية"""
@@ -402,7 +244,36 @@ class MedicalLabBot:
402
  parse_mode='Markdown'
403
  )
404
  return SELECTING_ACTION
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
405
 
 
 
 
 
 
 
 
 
 
406
  async def handle_action_selection(self, update: Update, context: ContextTypes.DEFAULT_TYPE):
407
  """معالجة اختيار الإجراء"""
408
  query = update.callback_query
@@ -447,7 +318,21 @@ class MedicalLabBot:
447
  summary = await self.generate_summary(subject, user_id)
448
  await query.edit_message_text(summary)
449
  return SELECTING_ACTION
450
-
 
 
 
 
 
 
 
 
 
 
 
 
 
 
451
  async def get_files_list_text(self, subject):
452
  """إنشاء نص لقائمة الملفات"""
453
  files = self.available_materials[subject]['files']
@@ -476,21 +361,7 @@ class MedicalLabBot:
476
  files_text += f"\n... وغيرها {len(files) - 15} ملف آخر"
477
 
478
  return files_text
479
-
480
- async def browse_available_files(self, query, context):
481
- """عرض الملفات المتاحة للمادة"""
482
- subject = context.user_data.get('current_subject')
483
- files_text = await self.get_files_list_text(subject)
484
-
485
- keyboard = [
486
- [InlineKeyboardButton("📖 طلب شرح ملف", callback_data="explain_lecture")],
487
- [InlineKeyboardButton("🔙 العودة", callback_data="back_to_actions")]
488
- ]
489
- reply_markup = InlineKeyboardMarkup(keyboard)
490
-
491
- await query.edit_message_text(files_text, reply_markup=reply_markup)
492
- return SELECTING_ACTION
493
-
494
  async def handle_message(self, update: Update, context: ContextTypes.DEFAULT_TYPE):
495
  """معالجة الرسائل النصية من المستخدم"""
496
  user_message = update.message.text
@@ -526,285 +397,10 @@ class MedicalLabBot:
526
  logger.error(f"❌ Error processing message: {e}")
527
  await update.message.reply_text("❌ حدث خطأ أثناء معالجة طلبك. يرجى المحاولة مرة أخرى.")
528
  return SELECTING_ACTION
529
-
530
- async def explain_lecture(self, lecture_request, subject, user_id):
531
- """شرح محاضرة محددة"""
532
- try:
533
- target_file_info = await self.find_requested_file(lecture_request, subject)
534
-
535
- if not target_file_info:
536
- files_list = await self.get_files_list_text(subject)
537
- return f"❌ لم أتمكن من العثور على الملف المطلوب.\n\n{files_list}"
538
-
539
- file_path = target_file_info['path']
540
- file_name = target_file_info['name']
541
-
542
- file_content = await self.download_and_extract_content(file_path)
543
-
544
- prompt = f"""
545
- قم بشرح المحتوى التالي من ملف تعليمي في مجال المختبرات الطبية:
546
-
547
- اسم الملف: {file_name}
548
- المادة: {subject}
549
-
550
- المحتوى:
551
- {file_content}
552
-
553
- المتطلبات:
554
- 1. ابدأ بملخص عام عن محتوى الملف
555
- 2. اشرح المفاهيم الأساسية بشكل منظم
556
- 3. ركز على النقاط العملية والتطبيقية
557
- 4. استخدم لغة عربية واضحة ومبسطة
558
- 5. أضف أمثلة عملية من مجال المختبرات الطبية
559
- 6. انهي بخلاصة أهم النقاط
560
-
561
- إذا كان المحتوى يحتوي على صور أو رسومات، ركز على شرح المفهوم العلمي وراءها.
562
- """
563
-
564
- response = await self.call_nvidia_ai(prompt, user_id)
565
- self.update_user_memory(user_id, f"شرح {file_name}", response, subject, file_path)
566
-
567
- return f"**📖 شرح {file_name}**\n\n{response}"
568
-
569
- except Exception as e:
570
- logger.error(f"❌ Error explaining lecture: {e}")
571
- return "❌ عذراً، حدث خطأ في شرح المحاضرة. يرجى المحاولة مرة أخرى."
572
-
573
- async def find_requested_file(self, request, subject):
574
- """البحث عن الملف المطلوب بناءً على طلب المستخدم"""
575
- files = self.available_materials[subject]['files']
576
-
577
- if not files:
578
- return None
579
-
580
- numbers = re.findall(r'\d+', request)
581
- if numbers:
582
- requested_num = int(numbers[0])
583
- for file_info in files:
584
- if file_info['lecture_number'] == requested_num:
585
- return file_info
586
-
587
- request_lower = request.lower()
588
- for file_info in files:
589
- file_name_lower = file_info['name'].lower()
590
- if any(term in file_name_lower for term in request_lower.split()):
591
- return file_info
592
-
593
- return files[0]
594
-
595
- async def explain_concept(self, concept, subject, user_id):
596
- """شرح مفهوم معين باستخدام محتوى الملفات"""
597
- try:
598
- context_content = ""
599
- sample_files = self.available_materials[subject]['files'][:3]
600
-
601
- for file_info in sample_files:
602
- file_content = await self.download_and_extract_content(file_info['path'])
603
- context_content += f"\n--- من {file_info['name']} ---\n{file_content[:500]}...\n"
604
-
605
- prompt = f"""
606
- اشرح المفهوم التالي في مجال {subject} في المختبرات الطبية:
607
- "{concept}"
608
-
609
- السياق من المواد الدراسية:
610
- {context_content}
611
-
612
- قدم شرحاً شاملاً يتضمن:
613
- 1. التعريف العلمي الدقيق
614
- 2. الأهمية الطبية والتشخيصية
615
- 3. طريقة إجراء التحليل (إن وجد)
616
- 4. تفسير النتائج
617
- 5. القيم الطبيعية (إن وجدت)
618
- 6. الأهمية السريرية
619
-
620
- استخدم اللغة العربية الواضحة مع أمثلة عملية من محتوى المواد.
621
- """
622
-
623
- response = await self.call_nvidia_ai(prompt, user_id)
624
- self.update_user_memory(user_id, concept, response, subject)
625
-
626
- return response
627
-
628
- except Exception as e:
629
- logger.error(f"❌ Error explaining concept: {e}")
630
- return await self.fallback_explain_concept(concept, subject, user_id)
631
-
632
- async def fallback_explain_concept(self, concept, subject, user_id):
633
- """شرح احتياطي بدون محتوى الملفات"""
634
- prompt = f"""
635
- اشرح المفهوم التالي في مجال {subject} في المختبرات الطبية:
636
- "{concept}"
637
-
638
- كخبير في المختبرات الطبية، قدم:
639
- 1. تعريف واضح ومبسط
640
- 2. الأهمية التشخيصية
641
- 3. التطبيقات العملية
642
- 4. المعلومات الأساسية التي يجب على طالب المختبرات معرفتها
643
-
644
- استخدم لغة عربية واضحة مع أمثلة عملية.
645
- """
646
-
647
- return await self.call_nvidia_ai(prompt, user_id)
648
-
649
- async def generate_questions_for_subject(self, subject, user_id):
650
- """توليد أسئلة عن المادة باستخدام محتوى حقيقي"""
651
- try:
652
- context_content = ""
653
- for file_info in self.available_materials[subject]['files'][:2]:
654
- file_content = await self.download_and_extract_content(file_info['path'])
655
- context_content += f"\n--- {file_info['name']} ---\n{file_content[:300]}\n"
656
-
657
- prompt = f"""
658
- بناءً على المحتوى التالي من مواد {subject} في المختبرات الطبية:
659
- {context_content}
660
-
661
- قم بتوليد 5 أسئلة متنوعة مع إجاباتها:
662
- - سؤالين اختيار من متعدد
663
- - سؤالين صح أم خطأ مع التصحيح
664
- - سؤال تطبيقي عملي
665
-
666
- ركز على الأسئلة العملية والتطبيقية في مجال المختبرات.
667
- اكتب باللغة العربية.
668
- """
669
-
670
- response = await self.call_nvidia_ai(prompt, user_id)
671
- self.update_user_memory(user_id, "توليد أسئلة", response, subject)
672
-
673
- return f"**❓ أسئلة {subject}**\n\n{response}"
674
-
675
- except Exception as e:
676
- logger.error(f"❌ Error generating questions: {e}")
677
- return await self.fallback_generate_questions(subject, user_id)
678
-
679
- async def fallback_generate_questions(self, subject, user_id):
680
- """توليد أسئلة احتياطي"""
681
- prompt = f"""
682
- قم بتوليد 5 أسئلة مهمة في مجال {subject} في المختبرات الطبية:
683
-
684
- 1. سؤال اختيار من متعدد عن مفهوم أساسي
685
- 2. سؤال صح أم خطأ عن إجراء عملي
686
- 3. سؤال تفسير نتائج تحليل
687
- 4. سؤال عن القيم الطبيعية
688
- 5. سؤال تطبيقي عن حالة سريرية
689
-
690
- قدم الأسئلة مع الإجابات المفصلة.
691
- اكتب باللغة العربية.
692
- """
693
-
694
- return await self.call_nvidia_ai(prompt, user_id)
695
-
696
- async def generate_summary(self, subject, user_id):
697
- """توليد ملخص للمادة"""
698
- prompt = f"""
699
- قدم ملخصاً شاملاً لمادة {subject} في المختبرات الطبية.
700
-
701
- المتطلبات:
702
- - ابدأ بنظرة عامة عن أهمية المادة
703
- - أهم المواضيع والمفاهيم
704
- - التطبيقات العملية في المختبر
705
- - النقاط الأساسية التي يجب التركيز عليها
706
- - نصائح للدراسة والفهم
707
-
708
- استخدم لغة عربية واضحة ومنظمة.
709
- اجعل الملخص مفيداً للمراجعة والدراسة.
710
- """
711
-
712
- response = await self.call_nvidia_ai(prompt, user_id)
713
- self.update_user_memory(user_id, "طلب ملخص", response, subject)
714
-
715
- return f"**📝 ملخص {subject}**\n\n{response}"
716
-
717
- async def process_general_query(self, query, subject, user_id):
718
- """معالجة الاستفسارات العامة مع الذاكرة"""
719
- memory = self.get_user_memory(user_id)
720
-
721
- context = f"المادة الحالية: {subject}\n\n"
722
- if memory['conversation_history']:
723
- context += "المحادثة السابقة:\n"
724
- for conv in memory['conversation_history'][-3:]:
725
- context += f"- السؤال: {conv['user_message']}\n"
726
-
727
- prompt = f"""
728
- أنت مساعد متخصص في المختبرات الطبية.
729
-
730
- {context}
731
-
732
- السؤال الجديد: {query}
733
-
734
- قدم إجابة دقيقة ومفيدة تراعي:
735
- - التخصص الدقيق (المختبرات الطبية)
736
- - السياق السابق للمحادثة
737
- - الدقة العلمية
738
- - الوضوح والتبسيط
739
- - التطبيقات العملية
740
-
741
- اكتب باللغة العربية الفصحى الواضحة.
742
- """
743
-
744
- response = await self.call_nvidia_ai(prompt, user_id)
745
- self.update_user_memory(user_id, query, response, subject)
746
-
747
- return response
748
-
749
- async def call_nvidia_ai(self, prompt, user_id):
750
- """الاتصال بنموذج NVIDIA AI"""
751
- try:
752
- completion = nvidia_client.chat.completions.create(
753
- model="openai/gpt-oss-120b",
754
- messages=[{"role": "user", "content": prompt}],
755
- temperature=0.7,
756
- top_p=0.9,
757
- max_tokens=2048,
758
- stream=False
759
- )
760
-
761
- reasoning = getattr(completion.choices[0].message, "reasoning_content", None)
762
- if reasoning:
763
- logger.info(f"🤔 Reasoning content for user {user_id}: {reasoning}")
764
-
765
- return completion.choices[0].message.content
766
-
767
- except Exception as e:
768
- logger.error(f"❌ Error calling NVIDIA AI: {e}")
769
- return "❌ عذراً، حدث خطأ في الاتصال بالذكاء الاصطناعي. يرجى المحاولة مرة أخرى لاحقاً."
770
-
771
- async def handle_general_help(self, query, context):
772
- """تقديم المساعدة العامة"""
773
- help_text = """
774
- 🤖 **كيفية استخدام البوت:**
775
 
776
- 📚 **المواد المتاحة:**
777
- """
778
-
779
- for subject in self.available_materials.keys():
780
- file_count = len(self.available_materials[subject]['files'])
781
- help_text += f"\n• {subject} ({file_count} ملف)"
782
-
783
- help_text += """
784
-
785
- 💡 **الاستخدام:**
786
- 1. اختر المادة من القائمة
787
- 2. اختر الخدمة المطلوبة
788
- 3. اتبع التعليمات
789
-
790
- 🎯 **الخدمات المتاحة:**
791
- - شرح محاضرات محددة
792
- - توليد أسئلة للمراجعة
793
- - تلخيص المحتوى
794
- - تفسير المفاهيم
795
- - استعراض الملفات
796
-
797
- ✍️ **أمثلة على الطلبات:**
798
- - "محاضرة 1" أو "الملف الثالث"
799
- - "اسئلة عن الكيمياء الحيوية"
800
- - "ما هو تحليل الدم الكامل"
801
- """
802
-
803
- keyboard = [[InlineKeyboardButton("🏠 العودة للقائمة الرئيسية", callback_data="main_menu")]]
804
- reply_markup = InlineKeyboardMarkup(keyboard)
805
-
806
- await query.edit_message_text(help_text, reply_markup=reply_markup)
807
- return SELECTING_SUBJECT
808
 
809
  # ========== إنشاء كائن البوت ==========
810
  bot = MedicalLabBot()
@@ -814,14 +410,19 @@ bot = MedicalLabBot()
814
  async def on_startup():
815
  """إعداد Webhook عند بدء التشغيل"""
816
  try:
817
- # تهيئة التطبيق
818
- bot.initialize_application()
819
 
 
 
 
 
820
  if SPACE_URL:
821
  webhook_url = f"{SPACE_URL.rstrip('/')}/telegram"
822
  await bot.application.bot.set_webhook(
823
  url=webhook_url,
824
- allowed_updates=Update.ALL_TYPES
 
825
  )
826
  logger.info(f"✅ Webhook set to: {webhook_url}")
827
  else:
@@ -833,17 +434,20 @@ async def on_startup():
833
  @app.get("/", response_class=HTMLResponse)
834
  async def root():
835
  """الصفحة الرئيسية"""
836
- return """
 
 
 
837
  <html>
838
  <head>
839
  <title>Medical Lab Bot</title>
840
  <style>
841
- body { font-family: Arial, sans-serif; margin: 40px; background: #f5f5f5; }
842
- .container { max-width: 800px; margin: 0 auto; background: white; padding: 30px; border-radius: 15px; box-shadow: 0 4px 6px rgba(0,0,0,0.1); }
843
- .status { padding: 20px; background: #f0f8ff; border-radius: 10px; border-left: 5px solid #007bff; }
844
- h1 { color: #2c3e50; text-align: center; }
845
- ul { line-height: 1.8; }
846
- li { margin: 10px 0; }
847
  </style>
848
  </head>
849
  <body>
@@ -851,9 +455,10 @@ async def root():
851
  <h1>🏥 بوت المختبرات الطبية الذكي</h1>
852
  <div class="status">
853
  <h2>✅ البوت يعمل بنجاح</h2>
854
- <p>البوت متصل بـ Telegram وجاهز لاستقبال الرسائل.</p>
855
- <p><strong>الحالة:</strong> نشط</p>
856
- <p><strong>المواد المحملة:</strong> """ + str(len(bot.available_materials)) + """ مادة</p>
 
857
  </div>
858
  <h3>🎯 المميزات:</h3>
859
  <ul>
@@ -876,10 +481,18 @@ async def root():
876
  async def handle_telegram_update(request: Request):
877
  """معالجة تحديثات Telegram"""
878
  try:
 
 
 
 
 
879
  update_data = await request.json()
880
  update = Update.de_json(update_data, bot.application.bot)
 
 
881
  await bot.application.process_update(update)
882
  return {"status": "ok"}
 
883
  except Exception as e:
884
  logger.error(f"❌ Error processing update: {e}")
885
  raise HTTPException(status_code=400, detail="Invalid update")
@@ -891,8 +504,9 @@ async def health_check():
891
  total_files = sum(len(material['files']) for material in bot.available_materials.values())
892
 
893
  return {
894
- "status": "healthy",
895
  "service": "medical-lab-bot",
 
896
  "materials_loaded": materials_count,
897
  "total_files": total_files,
898
  "timestamp": datetime.now().isoformat()
 
52
  self.available_materials = {}
53
  self.file_cache = {}
54
  self.application = None
55
+ self.is_initialized = False
56
  self.load_all_materials()
57
 
58
+ async def initialize_application(self):
59
+ """تهيئة تطبيق التليجرام بشكل غير متزامن"""
60
  try:
61
  if not self.application:
62
+ logger.info("🔄 جاري تهيئة تطبيق التليجرام...")
63
  self.application = (
64
  Application.builder()
65
  .token(TELEGRAM_BOT_TOKEN)
66
  .build()
67
  )
68
+ await self.setup_handlers()
69
+
70
+ # تهيئة التطبيق بشكل صحيح
71
+ await self.application.initialize()
72
+ self.is_initialized = True
73
  logger.info("✅ تم تهيئة تطبيق التليجرام بنجاح")
74
+ return True
75
  except Exception as e:
76
  logger.error(f"❌ خطأ في تهيئة التطبيق: {e}")
77
+ self.is_initialized = False
78
+ return False
79
 
80
+ async def setup_handlers(self):
81
  """إعداد معالجات التليجرام"""
82
  # إعداد Conversation Handler مع إعدادات محسنة
83
  conv_handler = ConversationHandler(
 
100
  ],
101
  name="medical_lab_conversation",
102
  persistent=False,
103
+ per_message=False
104
  )
105
 
106
  # إضافة الـ handlers
 
111
  self.application.add_handler(CallbackQueryHandler(self.handle_change_subject, pattern='^change_subject$'))
112
 
113
  logger.info("✅ تم إعداد معالجات التليجرام")
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
114
 
115
  def load_all_materials(self):
116
  """تحميل جميع المواد والملفات من Hugging Face"""
 
158
  except Exception as e:
159
  logger.error(f"❌ خطأ في تحميل المواد: {e}")
160
  self.available_materials = {'Biochemistry': {'files': [], 'file_details': {}}}
161
+
162
+ # باقي الدوال تبقى كما هي بدون تغيير...
163
+ # [يتم حذف باقي الكود للتخفيف، ولكن يجب أن يبقى كما هو في الكود السابق]
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
164
 
165
  async def start(self, update: Update, context: ContextTypes.DEFAULT_TYPE):
166
  """بدء المحادثة وعرض القائمة الرئيسية"""
 
244
  parse_mode='Markdown'
245
  )
246
  return SELECTING_ACTION
247
+
248
+ async def handle_back_actions(self, update: Update, context: ContextTypes.DEFAULT_TYPE):
249
+ """معالجة العودة إلى القائمة"""
250
+ query = update.callback_query
251
+ await query.answer()
252
+ return await self.handle_action_selection(update, context)
253
+
254
+ async def handle_main_menu(self, update: Update, context: ContextTypes.DEFAULT_TYPE):
255
+ """معالجة العودة للقائمة الرئيسية"""
256
+ query = update.callback_query
257
+ await query.answer()
258
+ return await self.start(update, context)
259
+
260
+ async def handle_more_questions(self, update: Update, context: ContextTypes.DEFAULT_TYPE):
261
+ """معالجة طلب المزيد من الأسئلة"""
262
+ query = update.callback_query
263
+ await query.answer()
264
+ await query.edit_message_text("💬 اكتب سؤالك أو طلبك:")
265
+ context.user_data['waiting_for'] = 'general'
266
+ return WAITING_FOR_QUESTION
267
 
268
+ async def handle_change_subject(self, update: Update, context: ContextTypes.DEFAULT_TYPE):
269
+ """معالجة تغيير المادة"""
270
+ query = update.callback_query
271
+ await query.answer()
272
+ keyboard = self.create_subjects_keyboard()
273
+ reply_markup = InlineKeyboardMarkup(keyboard)
274
+ await query.edit_message_text("اختر المادة:", reply_markup=reply_markup)
275
+ return SELECTING_SUBJECT
276
+
277
  async def handle_action_selection(self, update: Update, context: ContextTypes.DEFAULT_TYPE):
278
  """معالجة اختيار الإجراء"""
279
  query = update.callback_query
 
318
  summary = await self.generate_summary(subject, user_id)
319
  await query.edit_message_text(summary)
320
  return SELECTING_ACTION
321
+
322
+ async def browse_available_files(self, query, context):
323
+ """عرض الملفات المتاحة للمادة"""
324
+ subject = context.user_data.get('current_subject')
325
+ files_text = await self.get_files_list_text(subject)
326
+
327
+ keyboard = [
328
+ [InlineKeyboardButton("📖 طلب شرح ملف", callback_data="explain_lecture")],
329
+ [InlineKeyboardButton("🔙 العودة", callback_data="back_to_actions")]
330
+ ]
331
+ reply_markup = InlineKeyboardMarkup(keyboard)
332
+
333
+ await query.edit_message_text(files_text, reply_markup=reply_markup)
334
+ return SELECTING_ACTION
335
+
336
  async def get_files_list_text(self, subject):
337
  """إنشاء نص لقائمة الملفات"""
338
  files = self.available_materials[subject]['files']
 
361
  files_text += f"\n... وغيرها {len(files) - 15} ملف آخر"
362
 
363
  return files_text
364
+
 
 
 
 
 
 
 
 
 
 
 
 
 
 
365
  async def handle_message(self, update: Update, context: ContextTypes.DEFAULT_TYPE):
366
  """معالجة الرسائل النصية من المستخدم"""
367
  user_message = update.message.text
 
397
  logger.error(f"❌ Error processing message: {e}")
398
  await update.message.reply_text("❌ حدث خطأ أثناء معالجة طلبك. يرجى المحاولة مرة أخرى.")
399
  return SELECTING_ACTION
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
400
 
401
+ # باقي الدوال (extract_file_info, download_and_extract_content, explain_lecture, etc.)
402
+ # يجب أن تبقى كما هي من الكود السابق...
403
+ # [للتخفيف، يتم حذفها هنا ولكن يجب تضمينها في الكود الكامل]
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
404
 
405
  # ========== إنشاء كائن البوت ==========
406
  bot = MedicalLabBot()
 
410
  async def on_startup():
411
  """إعداد Webhook عند بدء التشغيل"""
412
  try:
413
+ # تهيئة التطبيق أولاً
414
+ success = await bot.initialize_application()
415
 
416
+ if not success:
417
+ logger.error("❌ فشل في تهيئة التطبيق")
418
+ return
419
+
420
  if SPACE_URL:
421
  webhook_url = f"{SPACE_URL.rstrip('/')}/telegram"
422
  await bot.application.bot.set_webhook(
423
  url=webhook_url,
424
+ allowed_updates=Update.ALL_TYPES,
425
+ drop_pending_updates=True
426
  )
427
  logger.info(f"✅ Webhook set to: {webhook_url}")
428
  else:
 
434
  @app.get("/", response_class=HTMLResponse)
435
  async def root():
436
  """الصفحة الرئيسية"""
437
+ materials_count = len(bot.available_materials)
438
+ total_files = sum(len(material['files']) for material in bot.available_materials.values())
439
+
440
+ return f"""
441
  <html>
442
  <head>
443
  <title>Medical Lab Bot</title>
444
  <style>
445
+ body {{ font-family: Arial, sans-serif; margin: 40px; background: #f5f5f5; }}
446
+ .container {{ max-width: 800px; margin: 0 auto; background: white; padding: 30px; border-radius: 15px; box-shadow: 0 4px 6px rgba(0,0,0,0.1); }}
447
+ .status {{ padding: 20px; background: #f0f8ff; border-radius: 10px; border-left: 5px solid #007bff; }}
448
+ h1 {{ color: #2c3e50; text-align: center; }}
449
+ ul {{ line-height: 1.8; }}
450
+ li {{ margin: 10px 0; }}
451
  </style>
452
  </head>
453
  <body>
 
455
  <h1>🏥 بوت المختبرات الطبية الذكي</h1>
456
  <div class="status">
457
  <h2>✅ البوت يعمل بنجاح</h2>
458
+ <p><strong>الحالة:</strong> نشط ومهيأ</p>
459
+ <p><strong>المواد المحملة:</strong> {materials_count} مادة</p>
460
+ <p><strong>إجمالي الملفات:</strong> {total_files} ملف</p>
461
+ <p><strong>تهيئة التطبيق:</strong> {'✅ مكتملة' if bot.is_initialized else '❌ غير مكتملة'}</p>
462
  </div>
463
  <h3>🎯 المميزات:</h3>
464
  <ul>
 
481
  async def handle_telegram_update(request: Request):
482
  """معالجة تحديثات Telegram"""
483
  try:
484
+ # التحقق من أن التطبيق مهيأ
485
+ if not bot.is_initialized:
486
+ logger.error("❌ التطبيق غير مهيأ")
487
+ raise HTTPException(status_code=503, detail="Application not initialized")
488
+
489
  update_data = await request.json()
490
  update = Update.de_json(update_data, bot.application.bot)
491
+
492
+ # معالجة التحديث
493
  await bot.application.process_update(update)
494
  return {"status": "ok"}
495
+
496
  except Exception as e:
497
  logger.error(f"❌ Error processing update: {e}")
498
  raise HTTPException(status_code=400, detail="Invalid update")
 
504
  total_files = sum(len(material['files']) for material in bot.available_materials.values())
505
 
506
  return {
507
+ "status": "healthy" if bot.is_initialized else "unhealthy",
508
  "service": "medical-lab-bot",
509
+ "initialized": bot.is_initialized,
510
  "materials_loaded": materials_count,
511
  "total_files": total_files,
512
  "timestamp": datetime.now().isoformat()