File size: 13,815 Bytes
e5fc7b3
4344aef
e5fc7b3
 
 
5a8c946
e5fc7b3
 
 
 
 
4344aef
 
e5fc7b3
4344aef
5a8c946
 
e5fc7b3
 
 
4344aef
5a8c946
e5fc7b3
5a8c946
 
e5fc7b3
20aed75
 
 
 
 
 
 
5a8c946
 
e5fc7b3
5a8c946
e5fc7b3
 
4344aef
e5fc7b3
4344aef
5a8c946
e5fc7b3
4344aef
 
5a8c946
 
4344aef
5a8c946
 
 
4344aef
 
 
 
 
 
 
 
 
 
5a8c946
 
 
4344aef
5a8c946
 
4344aef
 
 
 
 
 
 
 
 
 
 
 
5a8c946
4344aef
 
 
 
 
 
5a8c946
4344aef
5a8c946
 
4344aef
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
e5fc7b3
5a8c946
 
e5fc7b3
4344aef
e5fc7b3
 
4344aef
e5fc7b3
5a8c946
 
 
4344aef
5a8c946
e5fc7b3
5a8c946
 
 
 
4344aef
5a8c946
 
4344aef
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
e5fc7b3
4344aef
 
 
 
e5fc7b3
 
4344aef
e5fc7b3
4344aef
 
 
20aed75
4344aef
 
20aed75
 
4344aef
 
 
 
20aed75
4344aef
 
 
 
 
5a8c946
4344aef
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
e5fc7b3
4344aef
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
5a8c946
4344aef
 
 
 
 
 
 
 
 
 
 
 
 
e5fc7b3
4344aef
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
e5fc7b3
4344aef
 
 
 
 
 
e5fc7b3
4344aef
20aed75
4344aef
 
 
 
 
 
 
 
 
 
20aed75
4344aef
 
 
 
 
5a8c946
4344aef
 
 
 
 
 
e5fc7b3
4344aef
e5fc7b3
4344aef
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
"""
🤖 نظام RAG للمستندات - إصدار Gradio لـ HuggingFace
"""

import os
import tempfile
import numpy as np
import faiss
import nltk
from pypdf import PdfReader
from sentence_transformers import SentenceTransformer
import gradio as gr
import time

# ==================== تهيئة النظام ====================
class FlowRAGSystem:
    def __init__(self):
        self.model = None
        self.index = None
        self.chunks = None
        self.current_file = None
        self.is_ready = False
    
    def initialize(self):
        """تهيئة النظام"""
        try:
            # تحميل موارد NLTK
            try:
                nltk.download('punkt', quiet=True)
                nltk.download('punkt_tab', quiet=True)
            except:
                pass
            
            self.model = SentenceTransformer(
                "sentence-transformers/paraphrase-multilingual-MiniLM-L12-v2"
            )
            self.is_ready = True
            return True
        except Exception as e:
            return f"❌ خطأ في تحميل النموذج: {str(e)}"
    
    def process_pdf(self, pdf_file):
        """معالجة ملف PDF"""
        try:
            self.current_file = pdf_file.name
            
            # حفظ الملف المؤقت
            with tempfile.NamedTemporaryFile(delete=False, suffix=".pdf") as tmp_file:
                tmp_file.write(pdf_file.read())
                pdf_path = tmp_file.name
            
            # قراءة PDF
            reader = PdfReader(pdf_path)
            pages_data = []
            
            for i, page in enumerate(reader.pages):
                text = page.extract_text()
                if text and text.strip():
                    pages_data.append({
                        'page': i + 1,
                        'text': text.strip()
                    })
            
            if not pages_data:
                os.unlink(pdf_path)
                return "❌ لم يتم العثور على نص في الملف"
            
            # تقسيم النص
            self.chunks = []
            for page in pages_data:
                words = page['text'].split()
                
                # تقسيم إلى أجزاء 200 كلمة مع تداخل 40
                chunk_size = 200
                overlap = 40
                
                start = 0
                while start < len(words):
                    end = start + chunk_size
                    chunk_words = words[start:end]
                    
                    if chunk_words:
                        self.chunks.append({
                            'text': ' '.join(chunk_words),
                            'page': page['page'],
                            'word_count': len(chunk_words)
                        })
                    
                    start += chunk_size - overlap
            
            # إنشاء embeddings
            if len(self.chunks) > 0:
                chunk_texts = [chunk['text'] for chunk in self.chunks]
                embeddings = self.model.encode(
                    chunk_texts,
                    normalize_embeddings=True,
                    show_progress_bar=False
                )
                
                # بناء الفهرس
                dimension = embeddings.shape[1]
                self.index = faiss.IndexFlatIP(dimension)
                faiss.normalize_L2(embeddings)
                self.index.add(embeddings)
            else:
                os.unlink(pdf_path)
                return "❌ لم يتم إنشاء أي أجزاء نصية"
            
            # تنظيف الملف المؤقت
            os.unlink(pdf_path)
            
            return f"✅ تم معالجة المستند بنجاح!\n📊 {len(pages_data)} صفحة → {len(self.chunks)} جزء نصي"
            
        except Exception as e:
            return f"❌ خطأ في معالجة PDF: {str(e)}"
    
    def search(self, query, top_k=3):
        """بحث في المستند"""
        if not self.is_ready or self.index is None:
            return "❌ يرجى معالجة مستند أولاً"
        
        try:
            query_embedding = self.model.encode([query], normalize_embeddings=True)
            scores, indices = self.index.search(query_embedding, top_k)
            
            results = []
            for i, (score, idx) in enumerate(zip(scores[0], indices[0])):
                if 0 <= idx < len(self.chunks):
                    chunk = self.chunks[idx]
                    
                    # تحديد لون التشابه
                    similarity_score = float(score)
                    if similarity_score >= 0.5:
                        sim_color = "#28a745"  # أخضر
                        sim_text = "ممتاز"
                    elif similarity_score >= 0.3:
                        sim_color = "#ffc107"  # أصفر
                        sim_text = "جيد"
                    else:
                        sim_color = "#dc3545"  # أحمر
                        sim_text = "ضعيف"
                    
                    results.append(f"""
                    <div style="background: #f8f9fa; border-radius: 10px; padding: 1.5rem; 
                    margin: 1rem 0; border-left: 5px solid {sim_color}; box-shadow: 0 2px 4px rgba(0,0,0,0.1);">
                        <h4 style="margin-top: 0;">🏆 النتيجة #{i+1}</h4>
                        <p style="margin-bottom: 0.5rem;">
                            <span style="color: {sim_color}; font-weight: bold;">التشابه: {score*100:.1f}% ({sim_text})</span> | 
                            📖 الصفحة: {chunk['page']} | 
                            🔢 الكلمات: {chunk['word_count']}
                        </p>
                        <hr style="margin: 0.5rem 0;">
                        <p>{chunk['text'][:400]}...</p>
                    </div>
                    """)
            
            if not results:
                return "❌ لم أجد نتائج ذات صلة في المستند"
            
            return f"<h3>🔍 تم العثور على {len(results)} نتيجة:</h3>" + "".join(results)
            
        except Exception as e:
            return f"❌ خطأ في البحث: {str(e)}"

# ==================== إنشاء النظام ====================
rag_system = FlowRAGSystem()
init_result = rag_system.initialize()

# ==================== واجهة Gradio ====================
with gr.Blocks(title="🤖 نظام RAG الذكي للمستندات", theme=gr.themes.Soft()) as demo:
    
    # العنوان
    gr.Markdown("""
    # 🤖 نظام RAG الذكي للمستندات
    ### بحث دلالي متقدم في ملفات PDF - يدعم العربية والإنجليزية
    """)
    
    # منطقة التنبيهات
    if init_result is not True:
        gr.Warning(f"⚠️ {init_result}")
    else:
        gr.Info("✅ النظام جاهز للاستخدام")
    
    with gr.Row():
        with gr.Column(scale=2):
            # قسم رفع الملف
            with gr.Group():
                gr.Markdown("## 📁 رفع ومعالجة المستند")
                file_input = gr.File(
                    label="اختر ملف PDF",
                    file_types=[".pdf"],
                    type="binary"
                )
                process_btn = gr.Button("🚀 معالجة المستند", variant="primary")
                process_output = gr.Markdown(label="حالة المعالجة")
            
            # قسم البحث
            with gr.Group():
                gr.Markdown("## 💬 اسأل عن المستند")
                question_input = gr.Textbox(
                    label="اكتب سؤالك هنا",
                    placeholder="مثال: ما هي حالة التدفق؟ أو What is flow state?",
                    lines=3
                )
                
                with gr.Row():
                    top_k_slider = gr.Slider(
                        minimum=1, maximum=5, value=3,
                        label="عدد النتائج"
                    )
                    search_btn = gr.Button("🔍 ابحث في المستند", variant="primary")
                
                search_output = gr.HTML(label="نتائج البحث")
        
        with gr.Column(scale=1):
            # الشريط الجانبي
            with gr.Group():
                gr.Markdown("## 💡 أسئلة سريعة")
                
                example_questions = [
                    "ما هي حالة التدفق؟",
                    "What is flow state?",
                    "ما هي عناصر التجربة المثلى؟",
                    "كيف يحقق الإنسان السعادة في العمل؟",
                    "ما هو دور التركيز في التدفق؟"
                ]
                
                for question in example_questions:
                    gr.Button(
                        question,
                        size="sm",
                    ).click(
                        fn=lambda q=question: q,
                        inputs=[],
                        outputs=[question_input]
                    )
            
            with gr.Group():
                gr.Markdown("## 🎯 نصائح البحث")
                gr.Markdown("""
                **لأفضل النتائج:**
                
                • استخدم مصطلحات محددة  
                • جرب اللغتين (عربي/إنجليزي)  
                • اطرح أسئلة واضحة  
                
                **مثال:**  
                ✅ "ما هي خصائص flow state؟"  
                ❌ "اشرح لي"
                """)
            
            with gr.Group():
                gr.Markdown("## 📊 معلومات النظام")
                status_text = gr.Markdown("📄 لم يتم معالجة أي مستند بعد")
                
                # تحديث حالة النظام
                def update_status():
                    if rag_system.current_file:
                        file_info = f"📄 الملف: {rag_system.current_file}"
                        if rag_system.chunks:
                            chunks_info = f" | 📊 الأجزاء: {len(rag_system.chunks)}"
                            if rag_system.index:
                                vectors_info = f" | 🧮 المتجهات: {rag_system.index.ntotal}"
                                return file_info + chunks_info + vectors_info
                            return file_info + chunks_info
                        return file_info
                    return "📄 لم يتم معالجة أي مستند بعد"
                
                status_display = gr.Markdown(update_status())
    
    # نصائح إضافية
    gr.Markdown("---")
    with gr.Row():
        with gr.Column():
            gr.Markdown("### 📚 عن النظام")
            gr.Markdown("""
            **التقنيات المستخدمة:**
            
            • 🤖 **Sentence Transformers** - نماذج embedding متعددة اللغات  
            • ⚡ **FAISS** - بحث سريع في المتجهات  
            • 📄 **PyPDF** - معالجة ملفات PDF  
            • 🌐 **Gradio** - واجهة مستخدم تفاعلية
            """)
        
        with gr.Column():
            gr.Markdown("### 🌍 الدعم اللغوي")
            gr.Markdown("""
            **اللغات المدعومة:**
            
            • العربية - البحث والنتائج  
            • الإنجليزية - البحث والنتائج  
            • الفرنسية، الإسبانية، الألمانية - البحث الأساسي
            
            **المميزات:**  
            ✓ بحث دلالي ذكي  
            ✓ نتائج مرتبة حسب الصلة  
            ✓ دعم ملفات كبيرة
            """)
    
    # تذييل الصفحة
    gr.Markdown("---")
    gr.Markdown("""
    <div style="text-align: center; color: #666;">
        <p>🤖 نظام RAG للمستندات | إصدار HuggingFace Spaces</p>
        <p>تقنية: FAISS + Sentence Transformers + Gradio | يدعم العربية والإنجليزية</p>
    </div>
    """)
    
    # ==================== معالجة الأحداث ====================
    def process_file(file):
        if file is None:
            return "⚠️ يرجى اختيار ملف PDF أولاً"
        
        result = rag_system.process_pdf(file)
        return result
    
    def search_query(question, top_k):
        if not question:
            return "⚠️ يرجى إدخال سؤال"
        
        return rag_system.search(question, int(top_k))
    
    # ربط الأحداث
    process_btn.click(
        fn=process_file,
        inputs=[file_input],
        outputs=[process_output]
    ).then(
        fn=update_status,
        inputs=[],
        outputs=[status_display]
    )
    
    search_btn.click(
        fn=search_query,
        inputs=[question_input, top_k_slider],
        outputs=[search_output]
    )
    
    # معالجة ضغط Enter في حقل السؤال
    question_input.submit(
        fn=search_query,
        inputs=[question_input, top_k_slider],
        outputs=[search_output]
    )

# ==================== تشغيل التطبيق ====================
if __name__ == "__main__":
    demo.launch(
        server_name="0.0.0.0",
        server_port=7860,
        share=False
    )