Riy777 commited on
Commit
c05fb24
·
verified ·
1 Parent(s): fd020c2

Create app.py

Browse files
Files changed (1) hide show
  1. app.py +813 -0
app.py ADDED
@@ -0,0 +1,813 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import os
2
+ import logging
3
+ import asyncio
4
+ import re
5
+ from telegram import Update, InlineKeyboardButton, InlineKeyboardMarkup
6
+ from telegram.ext import Application, CommandHandler, CallbackQueryHandler, MessageHandler, filters, ContextTypes, ConversationHandler
7
+ from huggingface_hub import HfApi, hf_hub_download, list_repo_files
8
+ from openai import OpenAI
9
+ import pickle
10
+ import json
11
+ from datetime import datetime
12
+ import PyPDF2
13
+ import fitz # PyMuPDF
14
+ from PIL import Image
15
+ import io
16
+ import requests
17
+
18
+ # تكوين السجلات
19
+ logging.basicConfig(
20
+ format='%(asctime)s - %(name)s - %(levelname)s - %(message)s',
21
+ level=logging.INFO
22
+ )
23
+ logger = logging.getLogger(__name__)
24
+
25
+ # التوكنات - سيتم تعيينها في متغيرات البيئة
26
+ TELEGRAM_BOT_TOKEN = os.environ.get('TELEGRAM_BOT_TOKEN')
27
+ NVAPI_API_KEY = os.environ.get('NVAPI_API_KEY')
28
+
29
+ # تكوين عميل NVIDIA
30
+ nvidia_client = OpenAI(
31
+ base_url="https://integrate.api.nvidia.com/v1",
32
+ api_key=NVAPI_API_KEY
33
+ )
34
+
35
+ # مستودع Hugging Face للمواد
36
+ REPO_ID = "Riy777/Study"
37
+
38
+ # حالات المحادثة
39
+ SELECTING_SUBJECT, SELECTING_ACTION, WAITING_FOR_QUESTION = range(3)
40
+
41
+ class MedicalLabBot:
42
+ def __init__(self):
43
+ self.conversation_memory = {}
44
+ self.available_materials = {}
45
+ self.file_cache = {} # تخزين مؤقت للمحتوى المستخرج
46
+ self.load_all_materials()
47
+
48
+ def load_all_materials(self):
49
+ """تحميل جميع المواد والملفات من Hugging Face"""
50
+ try:
51
+ logger.info("جاري تحميل قائمة المواد من Hugging Face...")
52
+ all_files = list_repo_files(repo_id=REPO_ID, repo_type="dataset")
53
+
54
+ materials = {}
55
+
56
+ for file_path in all_files:
57
+ try:
58
+ # استخراج اسم المادة من المسار
59
+ path_parts = file_path.split('/')
60
+
61
+ if len(path_parts) >= 2:
62
+ subject = path_parts[0] # اسم المجلد (المادة)
63
+ file_name = path_parts[-1]
64
+
65
+ if subject not in materials:
66
+ materials[subject] = {
67
+ 'files': [],
68
+ 'file_details': {}
69
+ }
70
+
71
+ # استخراج معلومات الملف
72
+ file_info = self.extract_file_info(file_name, file_path)
73
+ materials[subject]['files'].append(file_info)
74
+ materials[subject]['file_details'][file_name] = file_info
75
+
76
+ else:
77
+ # ملفات في المجلد الرئيسي
78
+ if 'general' not in materials:
79
+ materials['general'] = {
80
+ 'files': [],
81
+ 'file_details': {}
82
+ }
83
+ file_info = self.extract_file_info(file_path, file_path)
84
+ materials['general']['files'].append(file_info)
85
+ materials['general']['file_details'][file_path] = file_info
86
+
87
+ except Exception as e:
88
+ logger.error(f"خطأ في معالجة الملف {file_path}: {e}")
89
+ continue
90
+
91
+ self.available_materials = materials
92
+ logger.info(f"تم تحميل {len(materials)} مادة بنجاح")
93
+ for subject, data in materials.items():
94
+ logger.info(f"المادة: {subject} - عدد الملفات: {len(data['files'])}")
95
+
96
+ except Exception as e:
97
+ logger.error(f"خطأ في تحميل المواد: {e}")
98
+ self.available_materials = {'Biochemistry': {'files': [], 'file_details': {}}}
99
+
100
+ def extract_file_info(self, file_name, file_path):
101
+ """استخراج معلومات الملف من اسمه"""
102
+ file_info = {
103
+ 'name': file_name,
104
+ 'path': file_path,
105
+ 'extension': file_name.split('.')[-1].lower() if '.' in file_name else '',
106
+ 'lecture_number': None,
107
+ 'type': 'unknown'
108
+ }
109
+
110
+ # البحث عن رقم المحاضرة في اسم الملف
111
+ numbers = re.findall(r'\d+', file_name)
112
+ if numbers:
113
+ file_info['lecture_number'] = int(numbers[0])
114
+
115
+ # تحديد نوع الملف
116
+ file_lower = file_name.lower()
117
+ if any(term in file_lower for term in ['lecture', 'محاضرة', 'lec', 'week']):
118
+ file_info['type'] = 'lecture'
119
+ elif any(term in file_lower for term in ['lab', 'مختبر', 'عملي']):
120
+ file_info['type'] = 'lab'
121
+ elif any(term in file_lower for term in ['exam', 'امتحان', 'quiz', 'test']):
122
+ file_info['type'] = 'exam'
123
+ elif any(term in file_lower for term in ['summary', 'ملخص', 'review']):
124
+ file_info['type'] = 'summary'
125
+
126
+ return file_info
127
+
128
+ async def download_and_extract_content(self, file_path):
129
+ """تحميل الملف واستخراج محتواه"""
130
+ try:
131
+ # التحقق من التخزين المؤقت
132
+ if file_path in self.file_cache:
133
+ return self.file_cache[file_path]
134
+
135
+ logger.info(f"جاري تحميل الملف: {file_path}")
136
+
137
+ # تحميل الملف من Hugging Face
138
+ local_path = hf_hub_download(
139
+ repo_id=REPO_ID,
140
+ filename=file_path,
141
+ repo_type="dataset"
142
+ )
143
+
144
+ content = ""
145
+ file_ext = file_path.split('.')[-1].lower()
146
+
147
+ if file_ext == 'pdf':
148
+ content = await self.extract_pdf_content(local_path)
149
+ elif file_ext in ['doc', 'docx']:
150
+ content = await self.extract_doc_content(local_path)
151
+ elif file_ext in ['txt', 'md']:
152
+ with open(local_path, 'r', encoding='utf-8') as f:
153
+ content = f.read()
154
+ else:
155
+ content = f"نوع الملف {file_ext} غير مدعوم حالياً للقراءة المباشرة"
156
+
157
+ # التخزين المؤقت
158
+ self.file_cache[file_path] = content
159
+ return content
160
+
161
+ except Exception as e:
162
+ logger.error(f"خطأ في تحميل الملف {file_path}: {e}")
163
+ return f"❌ تعذر تحميل محتوى الملف: {str(e)}"
164
+
165
+ async def extract_pdf_content(self, pdf_path):
166
+ """استخراج النص من ملف PDF مع التعامل مع الصور"""
167
+ try:
168
+ text_content = ""
169
+
170
+ # الطريقة 1: استخدام PyPDF2 للنص الأساسي
171
+ try:
172
+ with open(pdf_path, 'rb') as file:
173
+ pdf_reader = PyPDF2.PdfReader(file)
174
+ for page_num in range(len(pdf_reader.pages)):
175
+ page = pdf_reader.pages[page_num]
176
+ text_content += f"\n--- الصفحة {page_num + 1} ---\n"
177
+ text_content += page.extract_text() + "\n"
178
+ except Exception as e:
179
+ logger.warning(f"PyPDF2 failed: {e}")
180
+
181
+ # إذا كان النص قليلاً، نستخدم PyMuPDF الذي يعمل أفضل مع الملفات المعقدة
182
+ if len(text_content.strip()) < 100:
183
+ try:
184
+ doc = fitz.open(pdf_path)
185
+ for page_num in range(doc.page_count):
186
+ page = doc[page_num]
187
+ text_content += f"\n--- الصفحة {page_num + 1} ---\n"
188
+ text_content += page.get_text() + "\n"
189
+ doc.close()
190
+ except Exception as e:
191
+ logger.warning(f"PyMuPDF failed: {e}")
192
+
193
+ # إذا لم يتم استخراج نص كافي، نعتبر أن الملف يحتوي على صور بشكل أساسي
194
+ if len(text_content.strip()) < 50:
195
+ text_content = "📄 هذا الملف يحتوي بشكل رئيسي على صور أو رسومات. " \
196
+ "يرجى طلب شرح محتوى معين أو طرح أسئلة محددة عن الملف."
197
+
198
+ return text_content
199
+
200
+ except Exception as e:
201
+ logger.error(f"خطأ في استخراج محتوى PDF: {e}")
202
+ return f"❌ حدث خطأ في قراءة ملف PDF: {str(e)}"
203
+
204
+ async def extract_doc_content(self, doc_path):
205
+ """استخراج النص من ملف Word"""
206
+ try:
207
+ # هذه وظيفة مبسطة - في الإنتاج الحقيقي تحتاج python-docx
208
+ content = "📝 محتوى ملف Word: "
209
+ content += "هذا النموذج يدعم قراءة ملفات Word بشكل أساسي. "
210
+ content += "للاستفادة الكاملة، يرجى تحويل الملف إلى PDF أو طلب شرح محتوى معين."
211
+ return content
212
+ except Exception as e:
213
+ logger.error(f"خطأ في استخراج محتوى Word: {e}")
214
+ return f"❌ حدث خطأ في قراءة ملف Word: {str(e)}"
215
+
216
+ def get_user_memory(self, user_id):
217
+ """استرجاع ذاكرة المستخدم"""
218
+ if user_id not in self.conversation_memory:
219
+ self.conversation_memory[user_id] = {
220
+ 'conversation_history': [],
221
+ 'last_subject': None,
222
+ 'last_file': None,
223
+ 'preferences': {},
224
+ 'current_files': []
225
+ }
226
+ return self.conversation_memory[user_id]
227
+
228
+ def update_user_memory(self, user_id, message, response, subject=None, file_path=None):
229
+ """تحديث ذاكرة المستخدم"""
230
+ memory = self.get_user_memory(user_id)
231
+
232
+ memory['conversation_history'].append({
233
+ 'timestamp': datetime.now().isoformat(),
234
+ 'user_message': message,
235
+ 'bot_response': response,
236
+ 'subject': subject,
237
+ 'file_path': file_path
238
+ })
239
+
240
+ if len(memory['conversation_history']) > 20:
241
+ memory['conversation_history'] = memory['conversation_history'][-20:]
242
+
243
+ if subject:
244
+ memory['last_subject'] = subject
245
+ if file_path:
246
+ memory['last_file'] = file_path
247
+
248
+ async def start(self, update: Update, context: ContextTypes.DEFAULT_TYPE):
249
+ """بدء المحادثة وعرض القائمة الرئيسية"""
250
+ user_id = update.effective_user.id
251
+
252
+ welcome_text = """
253
+ 🏥 **مرحباً بك في بوت المختبرات الطبية الذكي** 🔬
254
+
255
+ 📚 **المواد المتاحة حالياً:**
256
+ """
257
+
258
+ # عرض المواد المتاحة
259
+ for subject in self.available_materials.keys():
260
+ file_count = len(self.available_materials[subject]['files'])
261
+ welcome_text += f"\n• {subject} ({file_count} ملف)"
262
+
263
+ welcome_text += "\n\nاختر المادة التي تريد البدء بها:"
264
+
265
+ keyboard = self.create_subjects_keyboard()
266
+ reply_markup = InlineKeyboardMarkup(keyboard)
267
+
268
+ await update.message.reply_text(welcome_text, reply_markup=reply_markup)
269
+ return SELECTING_SUBJECT
270
+
271
+ def create_subjects_keyboard(self):
272
+ """إنشاء لوحة مفاتيح للمواد المتاحة"""
273
+ keyboard = []
274
+ for subject in self.available_materials.keys():
275
+ file_count = len(self.available_materials[subject]['files'])
276
+ display_name = f"{subject} ({file_count})"
277
+ keyboard.append([InlineKeyboardButton(display_name, callback_data=f"subject_{subject}")])
278
+
279
+ keyboard.append([InlineKeyboardButton("🔄 تحديث قائمة المواد", callback_data="refresh_materials")])
280
+ keyboard.append([InlineKeyboardButton("❓ مساعدة", callback_data="general_help")])
281
+ return keyboard
282
+
283
+ async def handle_subject_selection(self, update: Update, context: ContextTypes.DEFAULT_TYPE):
284
+ """معالجة اختيار المادة"""
285
+ query = update.callback_query
286
+ await query.answer()
287
+
288
+ user_id = query.from_user.id
289
+ callback_data = query.data
290
+
291
+ if callback_data == "general_help":
292
+ return await self.handle_general_help(query, context)
293
+ elif callback_data == "refresh_materials":
294
+ self.load_all_materials()
295
+ keyboard = self.create_subjects_keyboard()
296
+ reply_markup = InlineKeyboardMarkup(keyboard)
297
+ await query.edit_message_text("✅ تم تحديث قائمة المواد\nاختر المادة:", reply_markup=reply_markup)
298
+ return SELECTING_SUBJECT
299
+
300
+ subject = callback_data.replace("subject_", "")
301
+ context.user_data['current_subject'] = subject
302
+
303
+ memory = self.get_user_memory(user_id)
304
+ memory['last_subject'] = subject
305
+
306
+ # عرض خيارات للمادة المحددة
307
+ subject_files = self.available_materials[subject]['files']
308
+ subject_name = subject.replace('_', ' ').title()
309
+
310
+ keyboard = [
311
+ [InlineKeyboardButton("📖 شرح محاضرة محددة", callback_data="explain_lecture")],
312
+ [InlineKeyboardButton("🔍 استعراض جميع الملفات", callback_data="browse_files")],
313
+ [InlineKeyboardButton("❓ أسئلة عن المادة", callback_data="generate_questions")],
314
+ [InlineKeyboardButton("📝 ملخص المادة", callback_data="summarize_content")],
315
+ [InlineKeyboardButton("🧪 تفسير مفهوم", callback_data="explain_concept")],
316
+ [InlineKeyboardButton("🏠 القائمة الرئيسية", callback_data="main_menu")]
317
+ ]
318
+ reply_markup = InlineKeyboardMarkup(keyboard)
319
+
320
+ await query.edit_message_text(
321
+ f"📚 **{subject_name}**\n\n"
322
+ f"عدد الملفات المتاحة: {len(subject_files)}\n"
323
+ f"اختر الخدمة التي تريدها:",
324
+ reply_markup=reply_markup,
325
+ parse_mode='Markdown'
326
+ )
327
+ return SELECTING_ACTION
328
+
329
+ async def handle_action_selection(self, update: Update, context: ContextTypes.DEFAULT_TYPE):
330
+ """معالجة اختيار الإجراء"""
331
+ query = update.callback_query
332
+ await query.answer()
333
+
334
+ action = query.data
335
+ user_id = query.from_user.id
336
+ subject = context.user_data.get('current_subject', 'general')
337
+
338
+ if action == "main_menu":
339
+ keyboard = self.create_subjects_keyboard()
340
+ reply_markup = InlineKeyboardMarkup(keyboard)
341
+ await query.edit_message_text("اختر المادة:", reply_markup=reply_markup)
342
+ return SELECTING_SUBJECT
343
+
344
+ elif action == "browse_files":
345
+ return await self.browse_available_files(query, context)
346
+
347
+ elif action == "explain_lecture":
348
+ files_list = await self.get_files_list_text(subject)
349
+ await query.edit_message_text(
350
+ f"📖 **شرح محاضرة**\n\n"
351
+ f"{files_list}\n"
352
+ f"اكتب رقم المحاضرة أو اسمها:\n"
353
+ f"مثال: '1' أو 'محاضرة 3' أو 'الملف الثاني'"
354
+ )
355
+ context.user_data['waiting_for'] = 'lecture_explanation'
356
+ return WAITING_FOR_QUESTION
357
+
358
+ elif action == "generate_questions":
359
+ questions = await self.generate_questions_for_subject(subject, user_id)
360
+ await query.edit_message_text(questions)
361
+ return SELECTING_ACTION
362
+
363
+ elif action == "explain_concept":
364
+ await query.edit_message_text(
365
+ "🧪 **تفسير مفهوم**\n\n"
366
+ "ما هو المفهوم أو المصطلح الذي تريد شرحه؟\n"
367
+ "مثال: 'ما هو تحليل الإنزيمات' أو 'اشرح لي تحليل البول'"
368
+ )
369
+ context.user_data['waiting_for'] = 'concept_explanation'
370
+ return WAITING_FOR_QUESTION
371
+
372
+ elif action == "summarize_content":
373
+ summary = await self.generate_summary(subject, user_id)
374
+ await query.edit_message_text(summary)
375
+ return SELECTING_ACTION
376
+
377
+ async def get_files_list_text(self, subject):
378
+ """إنشاء نص لقائمة الملفات"""
379
+ files = self.available_materials[subject]['files']
380
+
381
+ if not files:
382
+ return "❌ لا توجد ملفات متاحة لهذه المادة."
383
+
384
+ files_text = "📁 **الملفات المتاحة:**\n\n"
385
+ for i, file_info in enumerate(files[:15], 1): # عرض أول 15 ملف فقط
386
+ file_name = file_info['name']
387
+ lecture_num = file_info['lecture_number']
388
+ file_type = file_info['type']
389
+
390
+ type_emoji = {
391
+ 'lecture': '📖',
392
+ 'lab': '🧪',
393
+ 'exam': '📝',
394
+ 'summary': '📋',
395
+ 'unknown': '📄'
396
+ }.get(file_type, '📄')
397
+
398
+ num_text = f" - المحاضرة {lecture_num}" if lecture_num else ""
399
+ files_text += f"{i}. {type_emoji} {file_name}{num_text}\n"
400
+
401
+ if len(files) > 15:
402
+ files_text += f"\n... وغيرها {len(files) - 15} ملف آخر"
403
+
404
+ return files_text
405
+
406
+ async def browse_available_files(self, query, context):
407
+ """عرض الملفات المتاحة للمادة"""
408
+ subject = context.user_data.get('current_subject')
409
+ files_text = await self.get_files_list_text(subject)
410
+
411
+ keyboard = [
412
+ [InlineKeyboardButton("📖 طلب شرح ملف", callback_data="explain_lecture")],
413
+ [InlineKeyboardButton("🔙 العودة", callback_data="back_to_actions")]
414
+ ]
415
+ reply_markup = InlineKeyboardMarkup(keyboard)
416
+
417
+ await query.edit_message_text(files_text, reply_markup=reply_markup)
418
+ return SELECTING_ACTION
419
+
420
+ async def handle_message(self, update: Update, context: ContextTypes.DEFAULT_TYPE):
421
+ """معالجة الرسائل النصية من المستخدم"""
422
+ user_message = update.message.text
423
+ user_id = update.effective_user.id
424
+ waiting_for = context.user_data.get('waiting_for')
425
+ subject = context.user_data.get('current_subject', 'general')
426
+
427
+ await update.message.chat.send_action(action="typing")
428
+
429
+ try:
430
+ if waiting_for == 'lecture_explanation':
431
+ response = await self.explain_lecture(user_message, subject, user_id)
432
+ elif waiting_for == 'concept_explanation':
433
+ response = await self.explain_concept(user_message, subject, user_id)
434
+ else:
435
+ response = await self.process_general_query(user_message, subject, user_id)
436
+
437
+ await update.message.reply_text(response, parse_mode='Markdown')
438
+
439
+ keyboard = [
440
+ [InlineKeyboardButton("🔄 أسئلة أخرى", callback_data="more_questions")],
441
+ [InlineKeyboardButton("📚 تغيير المادة", callback_data="change_subject")],
442
+ [InlineKeyboardButton("🏠 القائمة الرئيسية", callback_data="main_menu")]
443
+ ]
444
+ reply_markup = InlineKeyboardMarkup(keyboard)
445
+
446
+ await update.message.reply_text("ماذا تريد أن تفعل بعد؟", reply_markup=reply_markup)
447
+
448
+ context.user_data['waiting_for'] = None
449
+ return SELECTING_ACTION
450
+
451
+ except Exception as e:
452
+ logger.error(f"Error processing message: {e}")
453
+ await update.message.reply_text("❌ حدث خطأ أثناء معالجة طلبك. يرجى المحاولة مرة أخرى.")
454
+ return SELECTING_ACTION
455
+
456
+ async def explain_lecture(self, lecture_request, subject, user_id):
457
+ """شرح محاضرة محددة"""
458
+ try:
459
+ # البحث عن الملف المطلوب
460
+ target_file_info = await self.find_requested_file(lecture_request, subject)
461
+
462
+ if not target_file_info:
463
+ files_list = await self.get_files_list_text(subject)
464
+ return f"❌ لم أتمكن من العثور على الملف المطلوب.\n\n{files_list}"
465
+
466
+ file_path = target_file_info['path']
467
+ file_name = target_file_info['name']
468
+
469
+ # إعلام المستخدم بأننا نحميل المحتوى
470
+ loading_msg = f"⏳ جاري تحميل وتحليل الملف: {file_name} ..."
471
+
472
+ # تحميل واستخراج محتوى الملف
473
+ file_content = await self.download_and_extract_content(file_path)
474
+
475
+ # استخدام الذكاء الاصطناعي لشرح المحتوى
476
+ prompt = f"""
477
+ قم بشرح المحتوى التالي من ملف تعليمي في مجال المختبرات الطبية:
478
+
479
+ اسم الملف: {file_name}
480
+ المادة: {subject}
481
+
482
+ المحتوى:
483
+ {file_content}
484
+
485
+ المتطلبات:
486
+ 1. ابدأ بملخص عام عن محتوى الملف
487
+ 2. اشرح المفاهيم الأساسية بشكل منظم
488
+ 3. ركز على النقاط العملية والتطبيقية
489
+ 4. استخدم لغة عربية واضحة ومبسطة
490
+ 5. أضف أمثلة عملية من مجال المختبرات الطبية
491
+ 6. انهي بخلاصة أهم النقاط
492
+
493
+ إذا كان المحتوى يحتوي على صور أو رسومات، ركز على شرح المفهوم العلمي وراءها.
494
+ """
495
+
496
+ response = await self.call_nvidia_ai(prompt, user_id)
497
+ self.update_user_memory(user_id, f"شرح {file_name}", response, subject, file_path)
498
+
499
+ return f"**📖 شرح {file_name}**\n\n{response}"
500
+
501
+ except Exception as e:
502
+ logger.error(f"Error explaining lecture: {e}")
503
+ return "❌ عذراً، حدث خطأ في شرح المحاضرة. يرجى المحاولة مرة أخرى."
504
+
505
+ async def find_requested_file(self, request, subject):
506
+ """البحث عن الملف المطلوب بناءً على طلب المستخدم"""
507
+ files = self.available_materials[subject]['files']
508
+
509
+ if not files:
510
+ return None
511
+
512
+ # البحث برقم المحاضرة
513
+ numbers = re.findall(r'\d+', request)
514
+ if numbers:
515
+ requested_num = int(numbers[0])
516
+ for file_info in files:
517
+ if file_info['lecture_number'] == requested_num:
518
+ return file_info
519
+
520
+ # البحث بكلمات مفتاحية
521
+ request_lower = request.lower()
522
+ for file_info in files:
523
+ file_name_lower = file_info['name'].lower()
524
+ if any(term in file_name_lower for term in request_lower.split()):
525
+ return file_info
526
+
527
+ # إذا لم يتم العثور، نعيد أول ملف
528
+ return files[0]
529
+
530
+ async def explain_concept(self, concept, subject, user_id):
531
+ """شرح مفهوم معين باستخدام محتوى الملفات"""
532
+ try:
533
+ # جمع عينات من محتوى الملفات لفهم السياق
534
+ context_content = ""
535
+ sample_files = self.available_materials[subject]['files'][:3] # عينات من أول 3 ملفات
536
+
537
+ for file_info in sample_files:
538
+ file_content = await self.download_and_extract_content(file_info['path'])
539
+ context_content += f"\n--- من {file_info['name']} ---\n{file_content[:500]}...\n"
540
+
541
+ prompt = f"""
542
+ اشرح المفهوم التالي في مجال {subject} في المختبرات الطبية:
543
+ "{concept}"
544
+
545
+ السياق من المواد الدراسية:
546
+ {context_content}
547
+
548
+ قدم شرحاً شاملاً يتضمن:
549
+ 1. التعريف العلمي الدقيق
550
+ 2. الأهمية الطبية والتشخيصية
551
+ 3. طريقة إجراء التحليل (إن وجد)
552
+ 4. تفسير النتائج
553
+ 5. القيم الطبيعية (إن وجدت)
554
+ 6. الأهمية السريرية
555
+
556
+ استخدم اللغة العربية الواضحة مع أمثلة عملية من محتوى المواد.
557
+ """
558
+
559
+ response = await self.call_nvidia_ai(prompt, user_id)
560
+ self.update_user_memory(user_id, concept, response, subject)
561
+
562
+ return response
563
+
564
+ except Exception as e:
565
+ logger.error(f"Error explaining concept: {e}")
566
+ return await self.fallback_explain_concept(concept, subject, user_id)
567
+
568
+ async def fallback_explain_concept(self, concept, subject, user_id):
569
+ """شرح احتياطي بدون محتوى الملفات"""
570
+ prompt = f"""
571
+ اشرح المفهوم التالي في مجال {subject} في المختبرات الطبية:
572
+ "{concept}"
573
+
574
+ كخبير في المختبرات الطبية، قدم:
575
+ 1. تعريف واضح ومبسط
576
+ 2. الأهمية التشخيصية
577
+ 3. التطبيقات العملية
578
+ 4. المعلومات الأساسية التي يجب على طالب المختبرات معرفتها
579
+
580
+ استخدم لغة عربية واضحة مع أمثلة عملية.
581
+ """
582
+
583
+ return await self.call_nvidia_ai(prompt, user_id)
584
+
585
+ async def generate_questions_for_subject(self, subject, user_id):
586
+ """توليد أسئلة عن المادة باستخدام محتوى حقيقي"""
587
+ try:
588
+ # جمع محتوى من عدة ملفات للسياق
589
+ context_content = ""
590
+ for file_info in self.available_materials[subject]['files'][:2]:
591
+ file_content = await self.download_and_extract_content(file_info['path'])
592
+ context_content += f"\n--- {file_info['name']} ---\n{file_content[:300]}\n"
593
+
594
+ prompt = f"""
595
+ بناءً على المحتوى التالي من مواد {subject} في المختبرات الطبية:
596
+ {context_content}
597
+
598
+ قم بتوليد 5 أسئلة متنوعة مع إجاباتها:
599
+ - سؤالين اختيار من متعدد
600
+ - سؤالين صح أم خطأ مع التصحيح
601
+ - سؤال تطبيقي عملي
602
+
603
+ ركز على الأسئلة العملية والتطبيقية في مجال المختبرات.
604
+ اكتب باللغة العربية.
605
+ """
606
+
607
+ response = await self.call_nvidia_ai(prompt, user_id)
608
+ self.update_user_memory(user_id, "توليد أسئلة", response, subject)
609
+
610
+ return f"**❓ أسئلة {subject}**\n\n{response}"
611
+
612
+ except Exception as e:
613
+ logger.error(f"Error generating questions: {e}")
614
+ return await self.fallback_generate_questions(subject, user_id)
615
+
616
+ async def fallback_generate_questions(self, subject, user_id):
617
+ """توليد أسئلة احتياطي"""
618
+ prompt = f"""
619
+ قم بتوليد 5 أسئلة مهمة في مجال {subject} في المختبرات الطبية:
620
+
621
+ 1. سؤال اختيار من متعدد عن مفهوم أساسي
622
+ 2. سؤال صح أم خطأ عن إجراء عملي
623
+ 3. سؤال تفسير نتائج تحليل
624
+ 4. سؤال عن القيم الطبيعية
625
+ 5. سؤال تطبيقي عن حالة سريرية
626
+
627
+ قدم الأسئلة مع الإجابات المفصلة.
628
+ اكتب باللغة العربية.
629
+ """
630
+
631
+ return await self.call_nvidia_ai(prompt, user_id)
632
+
633
+ async def generate_summary(self, subject, user_id):
634
+ """توليد ملخص للمادة"""
635
+ prompt = f"""
636
+ قدم ملخصاً شاملاً لمادة {subject} في المختبرات الطبية.
637
+
638
+ المتطلبات:
639
+ - ابدأ بنظرة عامة عن أهمية المادة
640
+ - أهم المواضيع والمفاهيم
641
+ - التطبيقات العملية في المختبر
642
+ - النقاط الأساسية التي يجب التركيز عليها
643
+ - نصائح للدراسة والفهم
644
+
645
+ استخدم لغة عربية واضحة ومنظمة.
646
+ اجعل الملخص مفيداً للمراجعة والدراسة.
647
+ """
648
+
649
+ response = await self.call_nvidia_ai(prompt, user_id)
650
+ self.update_user_memory(user_id, "طلب ملخص", response, subject)
651
+
652
+ return f"**📝 ملخص {subject}**\n\n{response}"
653
+
654
+ async def process_general_query(self, query, subject, user_id):
655
+ """معالجة الاستفسارات العامة مع الذاكرة"""
656
+ memory = self.get_user_memory(user_id)
657
+
658
+ context = f"المادة الحالية: {subject}\n\n"
659
+ if memory['conversation_history']:
660
+ context += "المحادثة السابقة:\n"
661
+ for conv in memory['conversation_history'][-3:]:
662
+ context += f"- السؤال: {conv['user_message']}\n"
663
+
664
+ prompt = f"""
665
+ أنت مساعد متخصص في المختبرات الطبية.
666
+
667
+ {context}
668
+
669
+ السؤال الجديد: {query}
670
+
671
+ قدم إجابة دقيقة ومفيدة تراعي:
672
+ - التخصص الدقيق (المختبرات الطبية)
673
+ - السياق السابق للمحادثة
674
+ - الدقة العلمية
675
+ - الوضوح والتبسيط
676
+ - التطبيقات العملية
677
+
678
+ اكتب باللغة العربية الفصحى الواضحة.
679
+ """
680
+
681
+ response = await self.call_nvidia_ai(prompt, user_id)
682
+ self.update_user_memory(user_id, query, response, subject)
683
+
684
+ return response
685
+
686
+ async def call_nvidia_ai(self, prompt, user_id):
687
+ """الاتصال بنموذج NVIDIA AI"""
688
+ try:
689
+ completion = nvidia_client.chat.completions.create(
690
+ model="openai/gpt-oss-120b",
691
+ messages=[{"role": "user", "content": prompt}],
692
+ temperature=0.7,
693
+ top_p=0.9,
694
+ max_tokens=2048,
695
+ stream=False
696
+ )
697
+
698
+ reasoning = getattr(completion.choices[0].message, "reasoning_content", None)
699
+ if reasoning:
700
+ logger.info(f"Reasoning content for user {user_id}: {reasoning}")
701
+
702
+ return completion.choices[0].message.content
703
+
704
+ except Exception as e:
705
+ logger.error(f"Error calling NVIDIA AI: {e}")
706
+ return "❌ عذراً، حدث خطأ في الاتصال بالذكاء الاصطناعي. يرجى المحاولة مرة أخرى لاحقاً."
707
+
708
+ async def handle_general_help(self, query, context):
709
+ """تقديم المساعدة العامة"""
710
+ help_text = """
711
+ 🤖 **كيفية استخدام البوت:**
712
+
713
+ 📚 **المواد المتاحة:**
714
+ """
715
+
716
+ for subject in self.available_materials.keys():
717
+ file_count = len(self.available_materials[subject]['files'])
718
+ help_text += f"\n• {subject} ({file_count} ملف)"
719
+
720
+ help_text += """
721
+
722
+ 💡 **الاستخدام:**
723
+ 1. اختر المادة من القائمة
724
+ 2. اختر الخدمة المطلوبة
725
+ 3. اتبع التعليمات
726
+
727
+ 🎯 **الخدمات المتاحة:**
728
+ - شرح محاضرات محددة
729
+ - توليد أسئلة للمراجعة
730
+ - تلخيص المحتوى
731
+ - تفسير المفاهيم
732
+ - استعراض الملفات
733
+
734
+ ✍️ **أمثلة على الطلبات:**
735
+ - "محاضرة 1" أو "الملف الثالث"
736
+ - "اسئلة عن الكيمياء الحيوية"
737
+ - "ما هو تحليل الدم الكامل"
738
+ """
739
+
740
+ keyboard = [[InlineKeyboardButton("🏠 العودة للقائمة الرئيسية", callback_data="main_menu")]]
741
+ reply_markup = InlineKeyboardMarkup(keyboard)
742
+
743
+ await query.edit_message_text(help_text, reply_markup=reply_markup)
744
+ return SELECTING_SUBJECT
745
+
746
+ async def handle_callback_query(self, update: Update, context: ContextTypes.DEFAULT_TYPE):
747
+ """معالجة استعلامات الـ Callback"""
748
+ query = update.callback_query
749
+ await query.answer()
750
+
751
+ if query.data == "more_questions":
752
+ await query.edit_message_text("💬 اكتب سؤالك أو طلبك:")
753
+ context.user_data['waiting_for'] = 'general'
754
+ return WAITING_FOR_QUESTION
755
+
756
+ elif query.data == "change_subject":
757
+ keyboard = self.create_subjects_keyboard()
758
+ reply_markup = InlineKeyboardMarkup(keyboard)
759
+ await query.edit_message_text("اختر المادة:", reply_markup=reply_markup)
760
+ return SELECTING_SUBJECT
761
+
762
+ elif query.data == "back_to_actions":
763
+ subject = context.user_data.get('current_subject')
764
+ subject_name = subject.replace('_', ' ').title() if subject else "المادة"
765
+
766
+ keyboard = [
767
+ [InlineKeyboardButton("📖 شرح محاضرة محددة", callback_data="explain_lecture")],
768
+ [InlineKeyboardButton("���� استعراض جميع الملفات", callback_data="browse_files")],
769
+ [InlineKeyboardButton("❓ أسئلة عن المادة", callback_data="generate_questions")],
770
+ [InlineKeyboardButton("📝 ملخص المادة", callback_data="summarize_content")],
771
+ [InlineKeyboardButton("🧪 تفسير مفهوم", callback_data="explain_concept")],
772
+ [InlineKeyboardButton("🏠 القائمة الرئيسية", callback_data="main_menu")]
773
+ ]
774
+ reply_markup = InlineKeyboardMarkup(keyboard)
775
+
776
+ await query.edit_message_text(
777
+ f"📚 **{subject_name}**\n\nاختر الخدمة التي تريدها:",
778
+ reply_markup=reply_markup,
779
+ parse_mode='Markdown'
780
+ )
781
+ return SELECTING_ACTION
782
+
783
+ def main():
784
+ """الدالة الرئيسية لتشغيل البوت"""
785
+ bot = MedicalLabBot()
786
+
787
+ application = Application.builder().token(TELEGRAM_BOT_TOKEN).build()
788
+
789
+ conv_handler = ConversationHandler(
790
+ entry_points=[CommandHandler('start', bot.start)],
791
+ states={
792
+ SELECTING_SUBJECT: [
793
+ CallbackQueryHandler(bot.handle_subject_selection, pattern='^subject_|general_help|refresh_materials$')
794
+ ],
795
+ SELECTING_ACTION: [
796
+ CallbackQueryHandler(bot.handle_action_selection),
797
+ CallbackQueryHandler(bot.handle_subject_selection, pattern='^subject_')
798
+ ],
799
+ WAITING_FOR_QUESTION: [
800
+ MessageHandler(filters.TEXT & ~filters.COMMAND, bot.handle_message)
801
+ ]
802
+ },
803
+ fallbacks=[CommandHandler('start', bot.start)]
804
+ )
805
+
806
+ application.add_handler(conv_handler)
807
+ application.add_handler(CallbackQueryHandler(bot.handle_callback_query, pattern='^more_questions|change_subject|back_to_actions$'))
808
+
809
+ logger.info("Starting Medical Lab Bot...")
810
+ application.run_polling()
811
+
812
+ if __name__ == '__main__':
813
+ main()