File size: 6,829 Bytes
12304e1
 
f43073f
12304e1
 
 
f43073f
 
12304e1
 
 
 
 
3b0947d
12304e1
 
 
 
 
 
 
 
 
 
 
 
 
 
f43073f
 
12304e1
3b0947d
 
 
 
 
 
f43073f
 
e59f4bc
12304e1
 
 
 
 
 
 
 
 
 
 
 
f43073f
12304e1
f43073f
9b8da68
 
f43073f
12304e1
 
 
 
3b0947d
12304e1
 
3b0947d
 
e59f4bc
 
3b0947d
 
e59f4bc
3b0947d
 
12304e1
 
 
 
 
f43073f
12304e1
 
 
f43073f
12304e1
 
 
 
 
 
 
 
 
 
 
 
f43073f
12304e1
e59f4bc
f43073f
 
 
 
 
 
12304e1
 
 
f43073f
12304e1
f43073f
 
 
 
12304e1
 
 
f43073f
12304e1
 
 
 
 
 
f43073f
12304e1
 
 
 
 
 
 
 
 
f43073f
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
import os
import streamlit as st
import tempfile
from langchain_community.document_loaders import PyPDFLoader
from langchain_text_splitters import RecursiveCharacterTextSplitter
from langchain_community.vectorstores import FAISS
from langchain_community.embeddings import HuggingFaceEmbeddings
from langchain_community.llms import HuggingFaceHub
from langchain.chains import create_retrieval_chain
from langchain.chains.combine_documents import create_stuff_documents_chain
from langchain_core.prompts import ChatPromptTemplate

# 1. إعدادات الصفحة ودعم الواجهة العربية (RTL)
st.set_page_config(page_title="مساعد الملفات الذكي المجاني", page_icon="🤖", layout="centered")

st.markdown("""
    <style>
    body, .main, .block-container, div[data-testid="stChatMessage"] {
        direction: RTL;
        text-align: right;
    }
    .stAlert {
        direction: RTL;
        text-align: right;
    }
    </style>
    """, unsafe_allow_html=True)

st.title("🤖 مساعد المستندات الذكي المجاني (RAG App)")
st.subheader("ارفع ملفاتك واسألها مجاناً بالكامل عبر السحابة")

# زر جانبي لتنظيف الذاكرة في حال حدوث أي تداخل حسابي في المصفوفات
if st.sidebar.button("🔄 إعادة تهيئة التطبيق وتنظيف الذاكرة"):
    st.session_state.chat_history = []
    st.session_state.vector_store = None
    st.rerun()

# 2. التحقق من وجود مفتاح Hugging Face
if "HF_TOKEN" not in os.environ:
    st.error("⚠️ لم يتم العثور على مفتاح 'HF_TOKEN' في الإعدادات السحابية (Secrets) الخاصة بـ Space.")
    st.stop()

# 3. تهيئة الذاكرة السحابية المؤقتة للمحادثة وقاعدة المتجهات
if "chat_history" not in st.session_state:
    st.session_state.chat_history = []
if "vector_store" not in st.session_state:
    st.session_state.vector_store = None

# 4. واجهة رفع ومعالجة الملفات سحابياً
uploaded_file = st.file_uploader("اختر ملف PDF لبدء المعالجة", type=["pdf"])

if uploaded_file and st.session_state.vector_store is None:
    with st.spinner("جاري معالجة وتحليل الملف سحابياً عبر النماذج المجانية..."):
        try:
            # استخدام مسار مؤقت آمن لتفادي Permission Denied
            with tempfile.NamedTemporaryFile(delete=False, suffix=".pdf") as temp_file:
                temp_file.write(uploaded_file.getbuffer())
                temp_file_path = temp_file.name
            
            loader = PyPDFLoader(temp_file_path)
            docs = loader.load()
            
            text_splitter = RecursiveCharacterTextSplitter(chunk_size=500, chunk_overlap=50)
            final_documents = text_splitter.split_documents(docs)
            
            # 💡 تعديل أمني: تهيئة النموذج بخصائص تضمن توافق الأبعاد الحسابية للمتجهات ومنع تضارب الذاكرة
            encode_kwargs = {'normalize_embeddings': True}
            embeddings = HuggingFaceEmbeddings(
                model_name="Alibaba-NLP/gte-multilingual-base",
                model_kwargs={"trust_remote_code": True},
                encode_kwargs=encode_kwargs
            )
            
            # بناء قاعدة البيانات مباشرة مع المتجهات المحدثة
            st.session_state.vector_store = FAISS.from_documents(final_documents, embeddings)
            
            if os.path.exists(temp_file_path):
                os.remove(temp_file_path)
                
            st.success("✅ تم تحليل المستند وبناء قاعدة البيانات المجانية بنجاح!")
        except Exception as e:
            st.error(f"❌ حدث خطأ أثناء معالجة الملف: {str(e)}")

# 5. نظام الاستعلام والـ RAG المجاني
if st.session_state.vector_store is not None:
    for message in st.session_state.chat_history:
        with st.chat_message(message["role"]):
            st.write(message["content"])

    user_query = st.chat_input("اسأل أي شيء حول المستند...")
    
    if user_query:
        with st.chat_message("user"):
            st.write(user_query)
        st.session_state.chat_history.append({"role": "user", "content": user_query})
        
        with st.spinner("جاري استخراج الإجابة الذكية مجاناً..."):
            try:
                # استدعاء نموذج محادثة (Qwen-2.5) مجاناً بالكامل
                llm = HuggingFaceHub(
                    repo_id="Qwen/Qwen2.5-7B-Instruct",
                    model_kwargs={"temperature": 0.1, "max_new_tokens": 512},
                    huggingfacehub_api_token=os.environ["HF_TOKEN"]
                )
                
                retriever = st.session_state.vector_store.as_retriever(search_kwargs={"k": 3})
                
                system_prompt = (
                    "<|im_start|>system\n"
                    "أنت مساعد ذكي متخصص في الإجابة على الأسئلة بناءً على المستندات المرفقة فقط.\n"
                    "استخدم السياق التالي بدقة للإجابة على سؤال المستخدم باللغة العربية. إذا لم تجد الإجابة، قل لا أعرف ولطفاً لا تبتكر.\n\n"
                    "السياق:\n{context}<|im_end|>\n"
                    "<|im_start|>user\n{input}<|im_end|>\n"
                    "<|im_start|>assistant\n"
                )
                
                prompt = ChatPromptTemplate.from_messages([
                    ("text", system_prompt)
                ])
                
                question_answer_chain = create_stuff_documents_chain(llm, prompt)
                rag_chain = create_retrieval_chain(retriever, question_answer_chain)
                
                response = rag_chain.invoke({"input": user_query})
                answer = response["answer"].split("<|im_start|>assistant\n")[-1].replace("<|im_end|>", "").strip()
                
                with st.chat_message("assistant"):
                    st.write(answer)
                st.session_state.chat_history.append({"role": "assistant", "content": answer})
                
            except Exception as e:
                st.error(f"⚠️ عذراً، حدث خطأ أثناء معالجة الطلب: {str(e)}")
else:
    if not uploaded_file:
        st.info("💡 يرجى رفع ملف PDF من الأعلى لتتمكن من البدء في طرح الأسئلة مجاناً.")