kawkabelaloom commited on
Commit
62167ba
·
verified ·
1 Parent(s): c74af43

requirements.txt

Browse files

gradio
faiss-cpu
sentence-transformers
pypdf

Files changed (1) hide show
  1. app.py +730 -0
app.py ADDED
@@ -0,0 +1,730 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """
2
+ 🤖 نظام RAG كامل للمستندات - نسخة HuggingFace Spaces
3
+ 🎯 إصدار نظيف بدون أي تبعيات لـ Google Colab
4
+ 📚 يدعم العربية والإنجليزية - معالجة ملفات PDF كبيرة
5
+ """
6
+
7
+ # ==================== 1️⃣ استيراد المكتبات ====================
8
+ import os
9
+ import sys
10
+ import numpy as np
11
+ import faiss
12
+ import nltk
13
+ from pypdf import PdfReader
14
+ from sentence_transformers import SentenceTransformer
15
+ from nltk.tokenize import word_tokenize
16
+ import pickle
17
+ import warnings
18
+ warnings.filterwarnings('ignore')
19
+
20
+ # ==================== 2️⃣ تحميل بيانات NLTK (مرة واحدة) ====================
21
+ def download_nltk_resources():
22
+ """تحميل موارد NLTK المطلوبة"""
23
+ try:
24
+ nltk.download('punkt', quiet=True)
25
+ nltk.download('punkt_tab', quiet=True)
26
+ print("✅ موارد NLTK جاهزة")
27
+ except Exception as e:
28
+ print(f"⚠️ ملاحظة: بعض موارد NLTK غير متوفرة: {e}")
29
+
30
+ # ==================== 3️⃣ فئات النظام الأساسية ====================
31
+ class PDFProcessor:
32
+ """معالج PDF ذكي"""
33
+
34
+ def __init__(self, chunk_size=350, overlap=70):
35
+ self.chunk_size = chunk_size
36
+ self.overlap = overlap
37
+
38
+ def read_pdf(self, pdf_path):
39
+ """قراءة PDF واستخراج النص"""
40
+ print(f"📖 جاري قراءة: {os.path.basename(pdf_path)}")
41
+
42
+ try:
43
+ reader = PdfReader(pdf_path)
44
+ total_pages = len(reader.pages)
45
+ pages_data = []
46
+
47
+ for i in range(total_pages):
48
+ try:
49
+ page = reader.pages[i]
50
+ text = page.extract_text()
51
+
52
+ if text and text.strip():
53
+ pages_data.append({
54
+ 'page_num': i + 1,
55
+ 'text': text.strip(),
56
+ 'char_count': len(text)
57
+ })
58
+
59
+ # عرض التقدم
60
+ if (i + 1) % 100 == 0 or i == total_pages - 1:
61
+ print(f" 📄 تمت {i + 1}/{total_pages} صفحة")
62
+
63
+ except Exception as page_error:
64
+ print(f" ⚠️ خطأ في صفحة {i+1}: {page_error}")
65
+ continue
66
+
67
+ print(f"✅ تم قراءة {len(pages_data)} صفحة تحتوي على نص")
68
+
69
+ if pages_data:
70
+ total_chars = sum(p['char_count'] for p in pages_data)
71
+ total_words = sum(len(p['text'].split()) for p in pages_data)
72
+ print(f" 📊 إجمالي الأحرف: {total_chars:,}")
73
+ print(f" 📊 إجمالي الكلمات: {total_words:,}")
74
+
75
+ return pages_data
76
+
77
+ except Exception as e:
78
+ print(f"❌ فشل في قراءة PDF: {e}")
79
+ return []
80
+
81
+ def chunk_text(self, pages_data):
82
+ """تقسيم النص إلى أجزاء ذكية"""
83
+ print(f"✂️ جاري تقسيم النص إلى أجزاء...")
84
+
85
+ all_chunks = []
86
+ chunk_id = 0
87
+
88
+ for page in pages_data:
89
+ text = page['text']
90
+ page_num = page['page_num']
91
+
92
+ # استخدام تقسيم بسيط للكلمات
93
+ words = text.split()
94
+
95
+ if len(words) == 0:
96
+ continue
97
+
98
+ # تقسيم النص مع التداخل
99
+ start = 0
100
+ while start < len(words):
101
+ end = start + self.chunk_size
102
+ chunk_words = words[start:end]
103
+
104
+ if chunk_words:
105
+ chunk_text = ' '.join(chunk_words)
106
+
107
+ all_chunks.append({
108
+ 'chunk_id': chunk_id,
109
+ 'text': chunk_text,
110
+ 'page': page_num,
111
+ 'word_count': len(chunk_words),
112
+ 'start_word': start,
113
+ 'end_word': min(end, len(words))
114
+ })
115
+
116
+ chunk_id += 1
117
+
118
+ start += self.chunk_size - self.overlap
119
+
120
+ print(f"✅ تم إنشاء {len(all_chunks)} جزء نصي")
121
+
122
+ if all_chunks:
123
+ avg_words = sum(c['word_count'] for c in all_chunks) // len(all_chunks)
124
+ print(f" 📊 متوسط الكلمات لكل جزء: {avg_words}")
125
+
126
+ return all_chunks
127
+
128
+
129
+ class VectorStore:
130
+ """مخزن المتجهات باستخدام FAISS"""
131
+
132
+ def __init__(self, model_name="sentence-transformers/paraphrase-multilingual-MiniLM-L12-v2"):
133
+ self.model_name = model_name
134
+ self.model = None
135
+ self.index = None
136
+ self.chunks = None
137
+ self.embeddings = None
138
+
139
+ def load_model(self):
140
+ """تحميل نموذج Embeddings"""
141
+ print(f"🚀 جاري تحميل نموذج: {self.model_name}")
142
+
143
+ try:
144
+ self.model = SentenceTransformer(self.model_name)
145
+ print(f"✅ تم تحميل النموذج بنجاح")
146
+ print(f" 📏 أبعاد المتجهات: {self.model.get_sentence_embedding_dimension()}")
147
+ return True
148
+ except Exception as e:
149
+ print(f"❌ خطأ في تحميل النموذج: {e}")
150
+ return False
151
+
152
+ def create_embeddings(self, chunks):
153
+ """إنشاء Embeddings للنصوص"""
154
+ print(f"🧠 جاري إنشاء Embeddings لـ {len(chunks)} جزء...")
155
+
156
+ self.chunks = chunks
157
+ chunk_texts = [chunk['text'] for chunk in chunks]
158
+
159
+ try:
160
+ self.embeddings = self.model.encode(
161
+ chunk_texts,
162
+ show_progress_bar=True,
163
+ normalize_embeddings=True,
164
+ batch_size=32,
165
+ convert_to_numpy=True
166
+ )
167
+
168
+ print(f"✅ تم إنشاء {len(self.embeddings)} متجه embedding")
169
+ return True
170
+
171
+ except Exception as e:
172
+ print(f"❌ خطأ في إنشاء Embeddings: {e}")
173
+ return False
174
+
175
+ def build_index(self):
176
+ """بناء فهرس FAISS"""
177
+ if self.embeddings is None:
178
+ print("❌ لا توجد embeddings لبناء الفهرس")
179
+ return False
180
+
181
+ print("🔧 جاري بناء Vector Store...")
182
+
183
+ try:
184
+ dimension = self.embeddings.shape[1]
185
+ self.index = faiss.IndexFlatIP(dimension)
186
+
187
+ # تطبيع وإضافة المتجهات
188
+ faiss.normalize_L2(self.embeddings)
189
+ self.index.add(self.embeddings)
190
+
191
+ print(f"✅ تم بناء Vector Store: {self.index.ntotal} متجه")
192
+ return True
193
+
194
+ except Exception as e:
195
+ print(f"❌ خطأ في بناء الفهرس: {e}")
196
+ return False
197
+
198
+ def save_index(self, path="vector_store"):
199
+ """حفظ الفهرس للاستخدام المستقبلي"""
200
+ try:
201
+ # حفظ الفهرس
202
+ faiss.write_index(self.index, f"{path}.faiss")
203
+
204
+ # حفظ البيانات النصية
205
+ with open(f"{path}_chunks.pkl", "wb") as f:
206
+ pickle.dump(self.chunks, f)
207
+
208
+ print(f"💾 تم حفظ الفهرس والبيانات في: {path}")
209
+ return True
210
+
211
+ except Exception as e:
212
+ print(f"❌ خطأ في حفظ الفهرس: {e}")
213
+ return False
214
+
215
+ def load_index(self, path="vector_store"):
216
+ """تحميل الفهرس المحفوظ"""
217
+ try:
218
+ # تحميل الفهرس
219
+ self.index = faiss.read_index(f"{path}.faiss")
220
+
221
+ # تحميل البيانات النصية
222
+ with open(f"{path}_chunks.pkl", "rb") as f:
223
+ self.chunks = pickle.load(f)
224
+
225
+ print(f"📂 تم تحميل الفهرس: {self.index.ntotal} متجه")
226
+ print(f"📂 تم تحميل البيانات: {len(self.chunks)} جزء")
227
+ return True
228
+
229
+ except Exception as e:
230
+ print(f"❌ خطأ في تحميل الفهرس: {e}")
231
+ return False
232
+
233
+ def search(self, query, top_k=5, similarity_threshold=0.25):
234
+ """بحث دلالي في المستندات"""
235
+ if self.index is None or self.model is None or self.chunks is None:
236
+ print("❌ النظام غير مهيء للبحث")
237
+ return []
238
+
239
+ # إنشاء embedding للاستعلام
240
+ query_embedding = self.model.encode([query], normalize_embeddings=True)
241
+
242
+ # البحث عن عدد أكبر ثم تصفية
243
+ search_k = top_k * 3
244
+ scores, indices = self.index.search(query_embedding, search_k)
245
+
246
+ # تجميع النتائج المؤهلة
247
+ results = []
248
+ for i, (score, idx) in enumerate(zip(scores[0], indices[0])):
249
+ # التحقق من أن الفهرس صالح والتشابه مقبول
250
+ if 0 <= idx < len(self.chunks) and score >= similarity_threshold:
251
+ chunk = self.chunks[idx]
252
+ results.append({
253
+ 'rank': len(results) + 1,
254
+ 'score': float(score),
255
+ 'similarity_percent': f"{score * 100:.1f}%",
256
+ 'similarity_raw': score,
257
+ 'text': chunk['text'],
258
+ 'page': chunk['page'],
259
+ 'word_count': chunk['word_count'],
260
+ 'preview': chunk['text'][:150] + "..." if len(chunk['text']) > 150 else chunk['text']
261
+ })
262
+
263
+ # التوقف عند الوصول إلى العدد المطلوب
264
+ if len(results) >= top_k:
265
+ break
266
+
267
+ return results
268
+
269
+
270
+ class RAGSystem:
271
+ """النظام الرئيسي RAG"""
272
+
273
+ def __init__(self):
274
+ self.processor = PDFProcessor()
275
+ self.vector_store = VectorStore()
276
+ self.is_ready = False
277
+
278
+ def initialize(self):
279
+ """تهيئة النظام"""
280
+ print("=" * 60)
281
+ print("🤖 نظام RAG للمستندات الذكي")
282
+ print("=" * 60)
283
+
284
+ # تحميل موارد NLTK
285
+ download_nltk_resources()
286
+
287
+ # تحميل نموذج Embeddings
288
+ if not self.vector_store.load_model():
289
+ return False
290
+
291
+ self.is_ready = True
292
+ return True
293
+
294
+ def process_pdf(self, pdf_path):
295
+ """معالجة ملف PDF جديد"""
296
+ if not self.is_ready:
297
+ print("❌ النظام غير مهيء")
298
+ return False
299
+
300
+ # قراءة PDF
301
+ pages_data = self.processor.read_pdf(pdf_path)
302
+ if not pages_data:
303
+ return False
304
+
305
+ # تقسيم النص
306
+ chunks = self.processor.chunk_text(pages_data)
307
+ if not chunks:
308
+ return False
309
+
310
+ # إنشاء embeddings وفهرس
311
+ if not self.vector_store.create_embeddings(chunks):
312
+ return False
313
+
314
+ if not self.vector_store.build_index():
315
+ return False
316
+
317
+ print("✨ تم معالجة المستند بنجاح!")
318
+ return True
319
+
320
+ def ask_question(self, question, top_k=3, similarity_threshold=0.25):
321
+ """طرح سؤال على النظام"""
322
+ if not self.is_ready or self.vector_store.index is None:
323
+ return {
324
+ 'success': False,
325
+ 'error': 'النظام غير مهيء. يرجى معالجة مستند أولاً.',
326
+ 'results': []
327
+ }
328
+
329
+ print(f"\n🔍 البحث عن: '{question}'")
330
+
331
+ # البحث في المستند
332
+ results = self.vector_store.search(question, top_k, similarity_threshold)
333
+
334
+ if not results:
335
+ return {
336
+ 'success': False,
337
+ 'error': 'لم أجد نتائج ذات صلة في المستند.',
338
+ 'results': []
339
+ }
340
+
341
+ # تقييم جودة النتائج
342
+ evaluation = self._evaluate_results(results)
343
+
344
+ return {
345
+ 'success': True,
346
+ 'question': question,
347
+ 'results': results,
348
+ 'evaluation': evaluation,
349
+ 'total_results': len(results),
350
+ 'best_similarity': results[0]['similarity_percent'] if results else "0%"
351
+ }
352
+
353
+ def _evaluate_results(self, results):
354
+ """تقييم جودة نتائج البحث"""
355
+ if not results:
356
+ return "❌ لا توجد نتائج للتقييم"
357
+
358
+ # حساب متوسط التشابه
359
+ similarities = [r['similarity_raw'] for r in results]
360
+ avg_similarity = sum(similarities) / len(similarities) * 100
361
+
362
+ # تحديد الجودة
363
+ if avg_similarity >= 50:
364
+ quality = "ممتازة 🏆"
365
+ emoji = "✅"
366
+ elif avg_similarity >= 40:
367
+ quality = "جيدة 👍"
368
+ emoji = "✓"
369
+ elif avg_similarity >= 30:
370
+ quality = "متوسطة ⚠️"
371
+ emoji = "~"
372
+ else:
373
+ quality = "ضعيفة ❌"
374
+ emoji = "✗"
375
+
376
+ # حساب تغطية الصفحات
377
+ unique_pages = len(set(r['page'] for r in results))
378
+
379
+ evaluation = f"""
380
+ 📊 **تقرير التقييم:**
381
+ {emoji} **الجودة:** {quality}
382
+ 📈 **متوسط التشابه:** {avg_similarity:.1f}%
383
+ 🔢 **أفضل نتيجة:** {results[0]['similarity_percent']}
384
+ 📖 **صفحات مختلفة:** {unique_pages}
385
+ 📝 **إجمالي النتائج:** {len(results)}
386
+ """
387
+
388
+ return evaluation
389
+
390
+ def save_state(self, path="rag_system_state"):
391
+ """حفظ حالة النظام"""
392
+ return self.vector_store.save_index(path)
393
+
394
+ def load_state(self, path="rag_system_state"):
395
+ """تحميل حالة النظام"""
396
+ if not self.vector_store.load_model():
397
+ return False
398
+
399
+ if self.vector_store.load_index(path):
400
+ self.is_ready = True
401
+ return True
402
+ return False
403
+
404
+
405
+ # ==================== 4️⃣ واجهة Streamlit لـ HuggingFace ====================
406
+ def create_streamlit_app():
407
+ """إنشاء واجهة ويب باستخدام Streamlit"""
408
+
409
+ try:
410
+ import streamlit as st
411
+ from streamlit.runtime.uploaded_file_manager import UploadedFile
412
+
413
+ # إعداد صفحة Streamlit
414
+ st.set_page_config(
415
+ page_title="نظام RAG الذكي للمستندات",
416
+ page_icon="🤖",
417
+ layout="wide"
418
+ )
419
+
420
+ # CSS مخصص
421
+ st.markdown("""
422
+ <style>
423
+ .main-header {
424
+ text-align: center;
425
+ padding: 1rem;
426
+ background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
427
+ color: white;
428
+ border-radius: 10px;
429
+ margin-bottom: 2rem;
430
+ }
431
+ .result-card {
432
+ background: #f8f9fa;
433
+ border-radius: 10px;
434
+ padding: 1rem;
435
+ margin: 1rem 0;
436
+ border-left: 5px solid #667eea;
437
+ }
438
+ .similarity-high {
439
+ color: #28a745;
440
+ font-weight: bold;
441
+ }
442
+ .similarity-medium {
443
+ color: #ffc107;
444
+ font-weight: bold;
445
+ }
446
+ .similarity-low {
447
+ color: #dc3545;
448
+ font-weight: bold;
449
+ }
450
+ </style>
451
+ """, unsafe_allow_html=True)
452
+
453
+ # العنوان الرئيسي
454
+ st.markdown("""
455
+ <div class="main-header">
456
+ <h1>🤖 نظام RAG الذكي للمستندات</h1>
457
+ <p>بحث ذكي في ملفات PDF - يدعم العربية والإنجليزية</p>
458
+ </div>
459
+ """, unsafe_allow_html=True)
460
+
461
+ # تهيئة النظام في حالة الجلسة
462
+ if 'rag_system' not in st.session_state:
463
+ with st.spinner("🚀 جاري تهيئة النظام..."):
464
+ st.session_state.rag_system = RAGSystem()
465
+ if st.session_state.rag_system.initialize():
466
+ st.success("✅ تم تهيئة النظام بنجاح!")
467
+ else:
468
+ st.error("❌ فشل في تهيئة النظام")
469
+ return
470
+
471
+ rag_system = st.session_state.rag_system
472
+
473
+ # الشريط الجانبي
474
+ with st.sidebar:
475
+ st.header("⚙️ الإعدادات")
476
+
477
+ # تحميل ملف PDF
478
+ st.subheader("📁 رفع ملف PDF")
479
+ uploaded_file = st.file_uploader(
480
+ "اختر ملف PDF",
481
+ type=["pdf"],
482
+ help="يمكنك رفع أي ملف PDF للبحث فيه"
483
+ )
484
+
485
+ if uploaded_file is not None:
486
+ # حفظ الملف المؤقت
487
+ temp_path = f"temp_{uploaded_file.name}"
488
+ with open(temp_path, "wb") as f:
489
+ f.write(uploaded_file.getbuffer())
490
+
491
+ # معالجة الملف
492
+ if st.button("🚀 معالجة المستند", type="primary"):
493
+ with st.spinner("جاري معالجة المستند..."):
494
+ if rag_system.process_pdf(temp_path):
495
+ st.success(f"✅ تم معالجة: {uploaded_file.name}")
496
+ st.session_state.processed_file = uploaded_file.name
497
+ else:
498
+ st.error("❌ فشل في معالجة الملف")
499
+
500
+ # إعدادات البحث
501
+ st.subheader("🔍 إعدادات البحث")
502
+ top_k = st.slider("عدد النتائج", 1, 10, 3)
503
+ similarity_threshold = st.slider("عتبة التشابه", 0.0, 1.0, 0.25, 0.05)
504
+
505
+ # معلومات النظام
506
+ st.subheader("📊 معلومات النظام")
507
+ if rag_system.is_ready and rag_system.vector_store.chunks:
508
+ st.info(f"📄 الأجزاء النصية: {len(rag_system.vector_store.chunks)}")
509
+ st.info(f"🧮 المتجهات: {rag_system.vector_store.index.ntotal if rag_system.vector_store.index else 0}")
510
+
511
+ # المنطقة الرئيسية
512
+ col1, col2 = st.columns([2, 1])
513
+
514
+ with col1:
515
+ st.header("💬 اسأل عن المستند")
516
+
517
+ # حقل إدخال السؤال
518
+ question = st.text_area(
519
+ "اكتب سؤالك هنا",
520
+ placeholder="مثال: ما هي حالة التدفق؟ أو What is flow state?",
521
+ height=100
522
+ )
523
+
524
+ # أزرار الأسئلة السريعة
525
+ st.subheader("💡 أسئلة سريعة")
526
+ quick_questions = [
527
+ "ما هي حالة التدفق؟",
528
+ "What is flow state?",
529
+ "ما هي عناصر التجربة المثلى؟",
530
+ "كيف يحقق الإنسان السعادة في العمل؟"
531
+ ]
532
+
533
+ cols = st.columns(4)
534
+ for idx, q in enumerate(quick_questions):
535
+ if cols[idx].button(q, use_container_width=True):
536
+ question = q
537
+
538
+ with col2:
539
+ st.header("🎯 نصائح البحث")
540
+ st.info("""
541
+ **للحصول على أفضل النتائج:**
542
+
543
+ 1. استخدم مصطلحات محددة
544
+ 2. جرب اللغتين (عربي/إنجليزي)
545
+ 3. اطرح أسئلة واضحة
546
+ 4. استخدم مصطلحات الكتاب
547
+
548
+ **مثال:**
549
+ ✅ "ما هي خصائص flow state؟"
550
+ ❌ "شرح لي"
551
+ """)
552
+
553
+ # زر البحث
554
+ if st.button("🔍 ابحث في المستند", type="primary", use_container_width=True):
555
+ if not question:
556
+ st.warning("⚠️ يرجى إدخال سؤال")
557
+ elif not (rag_system.is_ready and rag_system.vector_store.chunks):
558
+ st.error("❌ يرجى معالجة مستند أولاً")
559
+ else:
560
+ with st.spinner("جاري البحث..."):
561
+ result = rag_system.ask_question(
562
+ question,
563
+ top_k=top_k,
564
+ similarity_threshold=similarity_threshold
565
+ )
566
+
567
+ if result['success']:
568
+ # عرض التقييم
569
+ with st.expander("📊 تقرير التقييم", expanded=True):
570
+ st.markdown(result['evaluation'])
571
+
572
+ # عرض النتائج
573
+ st.subheader(f"📄 النتائج ({len(result['results'])})")
574
+
575
+ for r in result['results']:
576
+ # تحديد لون التشابه
577
+ similarity = r['similarity_raw']
578
+ if similarity >= 0.5:
579
+ sim_class = "similarity-high"
580
+ elif similarity >= 0.3:
581
+ sim_class = "similarity-medium"
582
+ else:
583
+ sim_class = "similarity-low"
584
+
585
+ # عرض البطاقة
586
+ with st.container():
587
+ st.markdown(f"""
588
+ <div class="result-card">
589
+ <h4>🏆 النتيجة #{r['rank']}</h4>
590
+ <p><span class="{sim_class}">التشابه: {r['similarity_percent']}</span> | 📖 الصفحة: {r['page']} | 🔢 الكلمات: {r['word_count']}</p>
591
+ <hr>
592
+ <p>{r['text']}</p>
593
+ </div>
594
+ """, unsafe_allow_html=True)
595
+ else:
596
+ st.error(result['error'])
597
+
598
+ # قسم الأمثلة التوضيحية
599
+ with st.expander("📖 أمثلة توضيحية", expanded=False):
600
+ st.markdown("""
601
+ **مستند كتاب Flow:**
602
+ - "ما هي حالة التدفق flow state؟"
603
+ - "What are the characteristics of optimal experience?"
604
+ - "كيف يرتبط التحدي بالمهارة في نظرية التدفق؟"
605
+
606
+ **مستندات أخرى:**
607
+ - "ما هو الموضوع الرئيسي؟"
608
+ - "ما هي النقاط المهمة؟"
609
+ - "هل هناك أمثلة عملية؟"
610
+ """)
611
+
612
+ # تذييل الصفحة
613
+ st.markdown("---")
614
+ st.markdown("""
615
+ <div style="text-align: center; color: #666;">
616
+ <p>🤖 نظام RAG للمستندات | إصدار HuggingFace</p>
617
+ <p>تقنية: FAISS + Sentence Transformers + Streamlit</p>
618
+ </div>
619
+ """, unsafe_allow_html=True)
620
+
621
+ except ImportError:
622
+ print("⚠️ Streamlit غير مثبت. لتشغيل الواجهة:")
623
+ print(" pip install streamlit")
624
+ print(" streamlit run app.py")
625
+
626
+
627
+ # ==================== 5️⃣ التشغيل الرئيسي ====================
628
+ def main_cli():
629
+ """واجهة سطر الأوامر"""
630
+ print("=" * 60)
631
+ print("🤖 نظام RAG للمستندات - واجهة سطر الأوامر")
632
+ print("=" * 60)
633
+
634
+ # إنشاء النظام
635
+ rag_system = RAGSystem()
636
+
637
+ # تهيئة النظام
638
+ if not rag_system.initialize():
639
+ print("❌ فشل في تهيئة النظام")
640
+ return
641
+
642
+ # قائمة الأوامر
643
+ commands = """
644
+ 🎮 الأوامر المتاحة:
645
+ 1. معالجة - معالجة ملف PDF جديد
646
+ 2. بحث - البحث في المستند
647
+ 3. حفظ - حفظ حالة النظام
648
+ 4. تحميل - تحميل حالة محفوظة
649
+ 5. خروج - إنهاء البرنامج
650
+ 6. ويب - تشغيل واجهة الويب (يتطلب Streamlit)
651
+ """
652
+
653
+ while True:
654
+ print("\n" + commands)
655
+ command = input("\n📝 أدخل الأمر: ").strip().lower()
656
+
657
+ if command in ['خروج', 'exit', '5']:
658
+ print("👋 مع السلامة!")
659
+ break
660
+
661
+ elif command in ['معالجة', '1']:
662
+ pdf_path = input("📁 أدخل مسار ملف PDF: ").strip()
663
+ if os.path.exists(pdf_path):
664
+ rag_system.process_pdf(pdf_path)
665
+ else:
666
+ print(f"❌ الملف غير موجود: {pdf_path}")
667
+
668
+ elif command in ['بحث', '2']:
669
+ if not rag_system.is_ready or rag_system.vector_store.index is None:
670
+ print("❌ يرجى معالجة مستند أولاً")
671
+ continue
672
+
673
+ question = input("🧠 أدخل سؤالك: ").strip()
674
+ if question:
675
+ result = rag_system.ask_question(question)
676
+
677
+ if result['success']:
678
+ print(result['evaluation'])
679
+ for r in result['results']:
680
+ print(f"\n🏆 النتيجة #{r['rank']}")
681
+ print(f" 📈 التشابه: {r['similarity_percent']}")
682
+ print(f" 📖 الصفحة: {r['page']}")
683
+ print(f" 📝 المحتوى: {r['text'][:200]}...")
684
+ else:
685
+ print(result['error'])
686
+
687
+ elif command in ['حفظ', '3']:
688
+ path = input("💾 أدخل اسم الملف للحفظ (دون امتداد): ").strip()
689
+ if path:
690
+ rag_system.save_state(path)
691
+
692
+ elif command in ['تحميل', '4']:
693
+ path = input("📂 أدخل اسم الملف للتحميل (دون امتداد): ").strip()
694
+ if path:
695
+ rag_system.load_state(path)
696
+
697
+ elif command in ['ويب', '6']:
698
+ print("🌐 جاري تشغيل واجهة الويب...")
699
+ print(" تأكد من تثبيت Streamlit أولاً: pip install streamlit")
700
+ print(" ثم شغل: streamlit run app.py")
701
+ break
702
+
703
+ else:
704
+ print("⚠️ أمر غير معروف")
705
+
706
+
707
+ # ==================== 6️⃣ نقطة الدخول الرئيسية ====================
708
+ if __name__ == "__main__":
709
+ # اختيار وضع التشغيل
710
+ print("=" * 60)
711
+ print("🎮 اختر وضع التشغيل:")
712
+ print("1. واجهة سطر الأوامر (CLI)")
713
+ print("2. واجهة الويب (يتطلب Streamlit)")
714
+ print("=" * 60)
715
+
716
+ try:
717
+ choice = input("📝 أدخل رقم الخيار: ").strip()
718
+
719
+ if choice == "1":
720
+ main_cli()
721
+ elif choice == "2":
722
+ create_streamlit_app()
723
+ else:
724
+ print("⚠️ خيار غير صحيح، تشغيل CLI...")
725
+ main_cli()
726
+
727
+ except KeyboardInterrupt:
728
+ print("\n\n👋 تم إيقاف البرنامج")
729
+ except Exception as e:
730
+ print(f"\n❌ حدث خطأ: {e}")