kawkabelaloom commited on
Commit
5a8c946
·
verified ·
1 Parent(s): 185eae5

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +330 -658
app.py CHANGED
@@ -1,730 +1,402 @@
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}")
 
1
  """
2
+ 🤖 نظام RAG المبسط لـ HuggingFace Spaces - بدون pickle5
 
 
3
  """
4
 
 
5
  import os
6
+ import tempfile
7
  import numpy as np
8
  import faiss
9
  import nltk
10
  from pypdf import PdfReader
11
  from sentence_transformers import SentenceTransformer
12
+ import streamlit as st
 
 
 
13
 
14
+ # تحميل موارد NLTK مرة واحدة
15
+ @st.cache_resource
16
+ def load_nltk():
17
  try:
18
  nltk.download('punkt', quiet=True)
19
  nltk.download('punkt_tab', quiet=True)
20
+ except:
21
+ pass
22
+ return True
23
 
24
+ class FlowRAGSystem:
25
+ def __init__(self):
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
26
  self.model = None
27
  self.index = None
28
  self.chunks = None
29
+ self.is_ready = False
 
 
 
 
 
 
 
 
 
 
 
 
 
30
 
31
+ def initialize(self):
32
+ """تهيئة النظام"""
 
 
 
 
 
33
  try:
34
+ self.model = SentenceTransformer(
35
+ "sentence-transformers/paraphrase-multilingual-MiniLM-L12-v2"
 
 
 
 
36
  )
37
+ self.is_ready = True
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
38
  return True
 
39
  except Exception as e:
40
+ st.error(f"خطأ في تحميل النموذج: {e}")
41
  return False
42
 
43
+ def process_pdf(self, pdf_bytes, filename):
44
+ """معالجة ملف PDF"""
45
  try:
46
+ # حفظ الملف المؤقت
47
+ with tempfile.NamedTemporaryFile(delete=False, suffix=".pdf") as tmp_file:
48
+ tmp_file.write(pdf_bytes)
49
+ pdf_path = tmp_file.name
50
+
51
+ # قراءة PDF
52
+ with st.spinner("📖 جاري قراءة المستند..."):
53
+ reader = PdfReader(pdf_path)
54
+ pages_data = []
55
+
56
+ progress_bar = st.progress(0)
57
+ total_pages = len(reader.pages)
58
+
59
+ for i, page in enumerate(reader.pages):
60
+ text = page.extract_text()
61
+ if text and text.strip():
62
+ pages_data.append({
63
+ 'page': i + 1,
64
+ 'text': text.strip()
65
+ })
66
+
67
+ # تحديث شريط التقدم
68
+ if total_pages > 0:
69
+ progress_bar.progress((i + 1) / total_pages)
70
+
71
+ progress_bar.empty()
72
+
73
+ if not pages_data:
74
+ st.error("❌ لم يتم العثور على نص في الملف")
75
+ os.unlink(pdf_path)
76
+ return False
77
+
78
+ # تقسيم النص
79
+ with st.spinner("✂️ جاري تقسيم النص..."):
80
+ self.chunks = []
81
+ for page in pages_data:
82
+ words = page['text'].split()
83
+
84
+ # تقسيم إلى أجزاء 250 كلمة مع تداخل 50
85
+ chunk_size = 250
86
+ overlap = 50
87
+
88
+ start = 0
89
+ while start < len(words):
90
+ end = start + chunk_size
91
+ chunk_words = words[start:end]
92
+
93
+ if chunk_words:
94
+ self.chunks.append({
95
+ 'text': ' '.join(chunk_words),
96
+ 'page': page['page'],
97
+ 'word_count': len(chunk_words)
98
+ })
99
+
100
+ start += chunk_size - overlap
101
+
102
+ # إنشاء embeddings
103
+ with st.spinner("🧠 جاري إنشاء Embeddings..."):
104
+ if len(self.chunks) > 0:
105
+ chunk_texts = [chunk['text'] for chunk in self.chunks]
106
+ embeddings = self.model.encode(
107
+ chunk_texts,
108
+ normalize_embeddings=True,
109
+ show_progress_bar=False
110
+ )
111
+
112
+ # بناء الفهرس
113
+ dimension = embeddings.shape[1]
114
+ self.index = faiss.IndexFlatIP(dimension)
115
+ faiss.normalize_L2(embeddings)
116
+ self.index.add(embeddings)
117
+ else:
118
+ st.error("❌ لم يتم إنشاء أي أجزاء نصية")
119
+ os.unlink(pdf_path)
120
+ return False
121
 
122
+ # تنظيف الملف المؤقت
123
+ os.unlink(pdf_path)
 
124
 
125
+ st.success(f" تم معالجة المستند بنجاح!")
126
+ st.info(f"📊 {len(pages_data)} صفحة → {len(self.chunks)} جزء نصي")
127
  return True
128
 
129
  except Exception as e:
130
+ st.error(f"❌ خطأ في معالجة PDF: {str(e)}")
131
  return False
132
 
133
+ def search(self, query, top_k=3):
134
+ """بحث في المستند"""
135
+ if not self.is_ready or self.index is None:
136
+ return []
137
+
138
  try:
139
+ query_embedding = self.model.encode([query], normalize_embeddings=True)
140
+ scores, indices = self.index.search(query_embedding, top_k)
141
+
142
+ results = []
143
+ for score, idx in zip(scores[0], indices[0]):
144
+ if 0 <= idx < len(self.chunks):
145
+ chunk = self.chunks[idx]
146
+ results.append({
147
+ 'score': float(score),
148
+ 'similarity': f"{score * 100:.1f}%",
149
+ 'text': chunk['text'],
150
+ 'page': chunk['page'],
151
+ 'words': chunk['word_count']
152
+ })
153
 
154
+ return results
 
 
 
 
 
 
155
 
156
  except Exception as e:
157
+ st.error(f"❌ خطأ في البحث: {e}")
 
 
 
 
 
 
158
  return []
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
159
 
160
+ # واجهة Streamlit
161
+ def main():
162
+ # تحميل NLTK
163
+ load_nltk()
164
 
165
+ # إعداد الصفحة
166
+ st.set_page_config(
167
+ page_title="نظام RAG للمستندات",
168
+ page_icon="🤖",
169
+ layout="wide",
170
+ initial_sidebar_state="expanded"
171
+ )
172
 
173
+ # CSS مخصص
174
+ st.markdown("""
175
+ <style>
176
+ .main-header {
177
+ text-align: center;
178
+ padding: 2rem;
179
+ background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
180
+ color: white;
181
+ border-radius: 15px;
182
+ margin-bottom: 2rem;
183
+ }
184
+ .result-card {
185
+ background: #f8f9fa;
186
+ border-radius: 10px;
187
+ padding: 1.5rem;
188
+ margin: 1rem 0;
189
+ border-left: 5px solid #4CAF50;
190
+ box-shadow: 0 2px 4px rgba(0,0,0,0.1);
191
+ }
192
+ .similarity-high { color: #28a745; font-weight: bold; }
193
+ .similarity-medium { color: #ffc107; font-weight: bold; }
194
+ .similarity-low { color: #dc3545; font-weight: bold; }
195
 
196
+ .stButton>button {
197
+ background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
198
+ color: white;
199
+ border: none;
200
+ padding: 0.75rem 1.5rem;
201
+ border-radius: 8px;
202
+ font-weight: bold;
203
+ transition: all 0.3s;
204
+ }
205
+ .stButton>button:hover {
206
+ transform: translateY(-2px);
207
+ box-shadow: 0 4px 8px rgba(0,0,0,0.2);
208
+ }
209
+ </style>
210
+ """, unsafe_allow_html=True)
 
 
 
 
 
 
 
 
 
 
211
 
212
+ # العنوان
213
+ st.markdown("""
214
+ <div class="main-header">
215
+ <h1>🤖 نظام RAG الذكي للمستندات</h1>
216
+ <p>بحث دلالي متقدم في ملفات PDF - يدعم العربية والإنجليزية</p>
217
+ </div>
218
+ """, unsafe_allow_html=True)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
219
 
220
+ # تهيئة النظام في session state
221
+ if 'rag_system' not in st.session_state:
222
+ st.session_state.rag_system = FlowRAGSystem()
223
+ if st.session_state.rag_system.initialize():
224
+ st.sidebar.success("✅ النظام جاهز للاستخدام")
 
 
 
 
 
 
 
 
 
 
 
 
 
 
225
  else:
226
+ st.sidebar.error("❌ خطأ في تهيئة النظام")
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
227
 
228
+ if 'current_file' not in st.session_state:
229
+ st.session_state.current_file = None
 
230
 
231
+ rag_system = st.session_state.rag_system
 
 
 
 
 
 
 
 
 
 
 
 
 
232
 
233
+ # الشريط الجانبي
234
+ with st.sidebar:
235
+ st.header("📁 رفع المستند")
236
+
237
+ uploaded_file = st.file_uploader(
238
+ "اختر ملف PDF",
239
+ type=["pdf"],
240
+ help="يمكنك رفع أي ملف PDF للبحث فيه"
 
241
  )
242
 
243
+ if uploaded_file is not None:
244
+ if st.button("🚀 معالجة المستند", type="primary", use_container_width=True):
245
+ with st.spinner("جاري معالجة المستند..."):
246
+ if rag_system.process_pdf(uploaded_file.getvalue(), uploaded_file.name):
247
+ st.session_state.current_file = uploaded_file.name
248
+ st.rerun()
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
249
 
250
+ st.divider()
 
 
 
 
 
 
251
 
252
+ st.header("🔍 إعدادات البحث")
253
+ top_k = st.slider("عدد النتائج", 1, 5, 3)
 
 
 
 
 
 
 
254
 
255
+ st.divider()
256
 
257
+ st.header("💡 أسئلة سريعة")
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
258
 
259
+ # أمثلة للأسئلة
260
+ example_questions = [
261
+ "ما هي حالة التدفق؟",
262
+ "What is flow state?",
263
+ "ما هي عناصر التجربة المثلى؟",
264
+ "كيف يحقق الإنسان السعادة في العمل؟",
265
+ "ما هو دور التركيز في التدفق؟"
266
+ ]
267
 
268
+ for question in example_questions:
269
+ if st.button(question, use_container_width=True):
270
+ st.session_state.last_question = question
271
+ st.rerun()
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
272
 
273
+ st.divider()
274
+
275
+ # معلومات النظام
276
+ if rag_system.chunks:
277
+ st.header("📊 معلومات النظام")
278
+ st.metric("الأجزاء النصية", len(rag_system.chunks))
279
+ if rag_system.index:
280
+ st.metric("المتجهات", rag_system.index.ntotal)
281
+
282
+ # المنطقة الرئيسية
283
+ col1, col2 = st.columns([3, 1])
284
+
285
+ with col1:
286
+ st.header("💬 اسأل عن المستند")
287
+
288
+ # عرض اسم الملف الحالي
289
+ if st.session_state.current_file:
290
+ st.info(f"📄 الملف الحالي: **{st.session_state.current_file}**")
291
+
292
+ # حقل السؤال
293
+ default_question = st.session_state.get('last_question', '')
294
+ question = st.text_area(
295
+ "اكتب سؤالك هنا",
296
+ value=default_question,
297
+ height=120,
298
+ placeholder="مثال: ما هي حالة التدفق؟ أو What is flow state?\nيمكنك استخدام العربية أو الإنجليزية..."
299
+ )
300
 
301
  # زر البحث
302
+ col_btn1, col_btn2 = st.columns(2)
303
+ with col_btn1:
304
+ if st.button("🔍 ابحث في المستند", type="primary", use_container_width=True):
305
+ if not question:
306
+ st.warning("⚠️ يرجى إدخال سؤال")
307
+ elif rag_system.index is None:
308
+ st.error("❌ يرجى معالجة مستند أولاً (من الشريط الجانبي)")
309
+ else:
310
+ with st.spinner("جاري البحث في المستند..."):
311
+ results = rag_system.search(question, top_k=top_k)
 
 
 
 
 
 
 
 
 
 
312
 
313
+ if results:
314
+ st.success(f"✅ تم العثور على {len(results)} نتيجة")
 
 
 
 
 
 
 
315
 
316
+ for i, result in enumerate(results):
317
+ # تحديد لون التشابه
318
+ similarity_score = float(result['similarity'].replace('%', '')) / 100
319
+ if similarity_score >= 0.5:
320
+ sim_class = "similarity-high"
321
+ elif similarity_score >= 0.3:
322
+ sim_class = "similarity-medium"
323
+ else:
324
+ sim_class = "similarity-low"
325
+
326
+ # عرض البطاقة
327
+ with st.container():
328
+ st.markdown(f"""
329
+ <div class="result-card">
330
+ <h4>🏆 النتيجة #{i+1}</h4>
331
+ <p>
332
+ <span class="{sim_class}">التشابه: {result['similarity']}</span> |
333
+ 📖 الصفحة: {result['page']} |
334
+ 🔢 الكلمات: {result['words']}
335
+ </p>
336
+ <hr>
337
+ <p>{result['text'][:400]}...</p>
338
+ </div>
339
+ """, unsafe_allow_html=True)
340
+
341
+ # زر لعرض النص الكامل
342
+ with st.expander("📖 عرض النص الكامل"):
343
+ st.write(result['text'])
344
+ else:
345
+ st.error("❌ لم أجد نتائج ذات صلة في المستند")
346
 
347
+ with col_btn2:
348
+ if st.button("🧹 مسح النتائج", use_container_width=True):
349
+ if 'last_question' in st.session_state:
350
+ del st.session_state['last_question']
351
+ st.rerun()
352
+
353
+ with col2:
354
+ st.header("🎯 نصائح البحث")
 
 
 
 
 
355
 
356
+ st.info("""
357
+ **لأفضل النتائج:**
 
 
 
 
 
 
358
 
359
+ 🔸 **استخدم مصطلحات محددة**
360
+ "ما هي خصائص flow state؟"
361
+ "اشرح لي"
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
362
 
363
+ 🔸 **جرب اللغتين**
364
+ النموذج يدعم العربية والإنجليزية معاً
 
365
 
366
+ 🔸 **اطلب التفاصيل**
367
+ "ما هي العناصر الثمانية للتدفق؟"
 
 
 
 
368
 
369
+ 🔸 **استخدم أمثلة**
370
+ "اذكر أمثلة على أنشطة تدفق"
371
+ """)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
372
 
373
+ st.divider()
 
 
 
374
 
375
+ st.header("📚 عن النظام")
376
+ st.markdown("""
377
+ **التقنيات المستخدمة:**
 
378
 
379
+ 🤖 **Sentence Transformers**
380
+ نماذج embedding متعددة اللغات
 
 
 
381
 
382
+ • ⚡ **FAISS**
383
+ بحث سريع في المتجهات
384
+
385
+ • 📄 **PyPDF**
386
+ معالجة ملفات PDF
387
+
388
+ • 🌐 **Streamlit**
389
+ واجهة مستخدم تفاعلية
390
+ """)
391
+
392
+ # تذييل الصفحة
393
+ st.divider()
394
+ st.markdown("""
395
+ <div style="text-align: center; color: #666; padding: 2rem;">
396
+ <p>🤖 نظام RAG للمستندات | إصدار HuggingFace Spaces</p>
397
+ <p>تقنية: FAISS + Sentence Transformers + Streamlit | يدعم العربية والإنجليزية</p>
398
+ </div>
399
+ """, unsafe_allow_html=True)
400
 
 
401
  if __name__ == "__main__":
402
+ main()