kawkabelaloom/PDF
0
1"""2🤖 نظام RAG كامل للمستندات - نسخة HuggingFace Spaces3🎯 إصدار نظيف بدون أي تبعيات لـ Google Colab4📚 يدعم العربية والإنجليزية - معالجة ملفات PDF كبيرة5"""6 7# ==================== 1️⃣ استيراد المكتبات ====================8import os9import sys10import numpy as np11import faiss12import nltk13from pypdf import PdfReader14from sentence_transformers import SentenceTransformer15from nltk.tokenize import word_tokenize16import pickle17import warnings18warnings.filterwarnings('ignore')19 20# ==================== 2️⃣ تحميل بيانات NLTK (مرة واحدة) ====================21def 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️⃣ فئات النظام الأساسية ====================31class PDFProcessor:32 """معالج PDF ذكي"""33 34 def __init__(self, chunk_size=350, overlap=70):35 self.chunk_size = chunk_size36 self.overlap = overlap37 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 continue66 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_data76 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 = 087 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 continue97 98 # تقسيم النص مع التداخل99 start = 0100 while start < len(words):101 end = start + self.chunk_size102 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 += 1117 118 start += self.chunk_size - self.overlap119 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_chunks127 128 129class VectorStore:130 """مخزن المتجهات باستخدام FAISS"""131 132 def __init__(self, model_name="sentence-transformers/paraphrase-multilingual-MiniLM-L12-v2"):133 self.model_name = model_name134 self.model = None135 self.index = None136 self.chunks = None137 self.embeddings = None138 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 True148 except Exception as e:149 print(f"❌ خطأ في تحميل النموذج: {e}")150 return False151 152 def create_embeddings(self, chunks):153 """إنشاء Embeddings للنصوص"""154 print(f"🧠 جاري إنشاء Embeddings لـ {len(chunks)} جزء...")155 156 self.chunks = chunks157 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=True166 )167 168 print(f"✅ تم إنشاء {len(self.embeddings)} متجه embedding")169 return True170 171 except Exception as e:172 print(f"❌ خطأ في إنشاء Embeddings: {e}")173 return False174 175 def build_index(self):176 """بناء فهرس FAISS"""177 if self.embeddings is None:178 print("❌ لا توجد embeddings لبناء الفهرس")179 return False180 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 True193 194 except Exception as e:195 print(f"❌ خطأ في بناء الفهرس: {e}")196 return False197 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 True210 211 except Exception as e:212 print(f"❌ خطأ في حفظ الفهرس: {e}")213 return False214 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 True228 229 except Exception as e:230 print(f"❌ خطأ في تحميل الفهرس: {e}")231 return False232 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 * 3244 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 break266 267 return results268 269 270class RAGSystem:271 """النظام الرئيسي RAG"""272 273 def __init__(self):274 self.processor = PDFProcessor()275 self.vector_store = VectorStore()276 self.is_ready = False277 278 def initialize(self):279 """تهيئة النظام"""280 print("=" * 60)281 print("🤖 نظام RAG للمستندات الذكي")282 print("=" * 60)283 284 # تحميل موارد NLTK285 download_nltk_resources()286 287 # تحميل نموذج Embeddings288 if not self.vector_store.load_model():289 return False290 291 self.is_ready = True292 return True293 294 def process_pdf(self, pdf_path):295 """معالجة ملف PDF جديد"""296 if not self.is_ready:297 print("❌ النظام غير مهيء")298 return False299 300 # قراءة PDF301 pages_data = self.processor.read_pdf(pdf_path)302 if not pages_data:303 return False304 305 # تقسيم النص306 chunks = self.processor.chunk_text(pages_data)307 if not chunks:308 return False309 310 # إنشاء embeddings وفهرس311 if not self.vector_store.create_embeddings(chunks):312 return False313 314 if not self.vector_store.build_index():315 return False316 317 print("✨ تم معالجة المستند بنجاح!")318 return True319 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) * 100361 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 evaluation389 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 False398 399 if self.vector_store.load_index(path):400 self.is_ready = True401 return True402 return False403 404 405# ==================== 4️⃣ واجهة Streamlit لـ HuggingFace ====================406def create_streamlit_app():407 """إنشاء واجهة ويب باستخدام Streamlit"""408 409 try:410 import streamlit as st411 from streamlit.runtime.uploaded_file_manager import UploadedFile412 413 # إعداد صفحة Streamlit414 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 return470 471 rag_system = st.session_state.rag_system472 473 # الشريط الجانبي474 with st.sidebar:475 st.header("⚙️ الإعدادات")476 477 # تحميل ملف PDF478 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.name497 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=100522 )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 = q537 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_threshold565 )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️⃣ التشغيل الرئيسي ====================628def 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 return641 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 break660 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 continue672 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 break702 703 else:704 print("⚠️ أمر غير معروف")705 706 707# ==================== 6️⃣ نقطة الدخول الرئيسية ====================708if __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}")