CoolFace
Apppublic

kawkabelaloom/PDF

sourceHugging Faceapache-2.0updated 9mo agoView on Hugging Face
0likes
app.py730 linesDownload Raw Back to root
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}")