Update app.py
Browse files
app.py
CHANGED
|
@@ -1,76 +1,85 @@
|
|
| 1 |
import os
|
| 2 |
import uuid
|
| 3 |
import gradio as gr
|
| 4 |
-
import
|
|
|
|
| 5 |
from openai import OpenAI
|
| 6 |
-
from chromadb.utils import embedding_functions
|
| 7 |
-
from pypdf import PdfReader
|
| 8 |
-
from docx import Document
|
| 9 |
|
| 10 |
-
# --- 1. إعداد
|
| 11 |
-
|
| 12 |
-
|
| 13 |
-
|
| 14 |
-
collection = chroma_client.get_or_create_collection(name="yousef_vault", embedding_function=ef)
|
| 15 |
|
| 16 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 17 |
|
| 18 |
-
# --- 2.
|
| 19 |
-
def
|
| 20 |
-
if file is None: return "⚠️ لم يتم اختيار ملف."
|
| 21 |
-
text = ""
|
| 22 |
try:
|
| 23 |
-
|
| 24 |
-
|
| 25 |
-
reader = PdfReader(file.name)
|
| 26 |
-
for page in reader.pages:
|
| 27 |
-
text += (page.extract_text() or "") + "\n"
|
| 28 |
-
elif file_ext == ".docx":
|
| 29 |
-
doc = Document(file.name)
|
| 30 |
-
text = "\n".join([para.text for para in doc.paragraphs])
|
| 31 |
-
elif file_ext in [".txt", ".md"]:
|
| 32 |
-
with open(file.name, "r", encoding="utf-8", errors="ignore") as f:
|
| 33 |
-
text = f.read()
|
| 34 |
|
| 35 |
-
|
| 36 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
| 37 |
|
| 38 |
-
|
| 39 |
-
|
| 40 |
-
|
|
|
|
|
|
|
| 41 |
|
| 42 |
-
return
|
| 43 |
-
except
|
| 44 |
-
return
|
| 45 |
|
| 46 |
-
# --- 3. دالة ال
|
| 47 |
-
def predict(message, history, system_prompt,
|
| 48 |
-
#
|
| 49 |
-
|
| 50 |
-
api_key = os.getenv("HF_TOKEN")
|
| 51 |
|
| 52 |
-
if not
|
| 53 |
-
yield "⚠️ يرجى
|
| 54 |
return
|
| 55 |
|
| 56 |
-
client = OpenAI(base_url="https://router.huggingface.co/v1", api_key=
|
| 57 |
-
active_model =
|
|
|
|
|
|
|
| 58 |
|
| 59 |
-
#
|
| 60 |
-
|
| 61 |
-
|
|
|
|
| 62 |
|
| 63 |
-
messages = [{"role": "system", "content": f"{system_prompt}\n\n[CONTEXT]:\n{context}"}]
|
| 64 |
-
# تحويل التاريخ إلى صيغة OpenAI المتوافقة
|
| 65 |
for msg in history:
|
| 66 |
messages.append({"role": msg["role"], "content": msg["content"]})
|
|
|
|
| 67 |
messages.append({"role": "user", "content": message})
|
| 68 |
|
| 69 |
try:
|
| 70 |
response = client.chat.completions.create(
|
| 71 |
model=active_model,
|
| 72 |
messages=messages,
|
| 73 |
-
temperature=float(temperature),
|
| 74 |
stream=True
|
| 75 |
)
|
| 76 |
|
|
@@ -80,38 +89,31 @@ def predict(message, history, system_prompt, temperature, custom_model):
|
|
| 80 |
partial_message += chunk.choices[0].delta.content
|
| 81 |
yield partial_message
|
| 82 |
except Exception as e:
|
| 83 |
-
yield f"❌ ف
|
| 84 |
|
| 85 |
-
# --- 4.
|
| 86 |
with gr.Blocks(fill_height=True) as demo:
|
| 87 |
with gr.Sidebar():
|
| 88 |
-
gr.Markdown("#
|
|
|
|
| 89 |
|
| 90 |
-
|
| 91 |
-
|
| 92 |
-
|
| 93 |
-
|
| 94 |
-
|
| 95 |
-
gr.Markdown("---")
|
| 96 |
-
file_box = gr.File(label="ارفع مسوداتك", file_types=[".pdf", ".docx", ".txt"])
|
| 97 |
-
status_msg = gr.Markdown("*الذاكرة بانتظار الملفات...*")
|
| 98 |
-
|
| 99 |
-
file_box.change(process_document, inputs=file_box, outputs=status_msg)
|
| 100 |
|
| 101 |
-
|
| 102 |
-
|
| 103 |
-
|
| 104 |
-
if all_data['ids']: collection.delete(ids=all_data['ids'])
|
| 105 |
-
return "🗑 تم تصفير الذاكرة."
|
| 106 |
-
clear_btn.click(clear_memory, outputs=status_msg)
|
| 107 |
|
| 108 |
-
# التعديل هنا: حذف type="messages" لأنه أصبح افتراضياً أو غير مطلوب بهذا الشكل
|
| 109 |
gr.ChatInterface(
|
| 110 |
predict,
|
| 111 |
-
additional_inputs=[system_input,
|
| 112 |
-
fill_height=True
|
|
|
|
|
|
|
| 113 |
)
|
| 114 |
|
| 115 |
if __name__ == "__main__":
|
| 116 |
-
|
| 117 |
-
demo.launch(theme=gr.themes.Soft(primary_hue="indigo"))
|
|
|
|
| 1 |
import os
|
| 2 |
import uuid
|
| 3 |
import gradio as gr
|
| 4 |
+
from supabase import create_client, Client
|
| 5 |
+
from sentence_transformers import SentenceTransformer
|
| 6 |
from openai import OpenAI
|
|
|
|
|
|
|
|
|
|
| 7 |
|
| 8 |
+
# --- 1. الإعدادات ---
|
| 9 |
+
URL = os.environ.get("SUPABASE_URL")
|
| 10 |
+
KEY = os.environ.get("SUPABASE_KEY")
|
| 11 |
+
supabase: Client = create_client(URL, KEY)
|
|
|
|
| 12 |
|
| 13 |
+
model_st = SentenceTransformer('all-MiniLM-L6-v2')
|
| 14 |
+
DEFAULT_MODEL = "MiniMaxAI/MiniMax-M2.1"
|
| 15 |
+
DEFAULT_SYSTEM = """أنت محرك رواية تفاعلية 'لورد الغوامض' متطور وخبير في ألعاب الأدوار (RPG). مهمتك هي سرد قصة حية وتفاعلية بناءً على قراراتي.
|
| 16 |
+
1. أسلوب السرد:
|
| 17 |
+
استخدم أسلوب السرد الوصفي العميق (Show, Don't Tell). ركز على الحواس: الروائح، الأصوات، برودة الهواء، وتعبيرات الوجوه.
|
| 18 |
+
حافظ على نبرة سوداوية مليئة بالرعب والخيال الملحمي.
|
| 19 |
+
اكتب بضمير المخاطب "أنت".
|
| 20 |
+
2. القواعد التفاعلية:
|
| 21 |
+
لا تتحدث أبداً بلسان شخصيتي. انتظر قراراتي وردود أفعالي.
|
| 22 |
+
انهِ كل رد بوصف للموقف الحالي الذي يواجهني، متبوعاً بسؤال ضمني أو خيارات مفتوحة.
|
| 23 |
+
اجعل العالم يستجيب لقراراتي؛ إذا فشلت في مهمة، يجب أن تكون هناك عواقب دائمة في القصة.
|
| 24 |
+
3. إدارة العالم والشخصيات:
|
| 25 |
+
احتفظ بـ "سجل حي" في ذاكرتك لكل الشخصيات الجانبية التي أقابلها ومواقعها وعلاقتها بي.
|
| 26 |
+
التزم بالمنطق الداخلي للعالم (السحر له ثمن، وفقدان العقل خطر دائم).
|
| 27 |
+
4. تعليمات السياق الضخم:
|
| 28 |
+
تذكر التفاصيل الدقيقة واستخدمها كعناصر مفاجئة لاحقاً. راقب التطور النفسي لشخصيتي.
|
| 29 |
+
تعليمات البداية:
|
| 30 |
+
اطرح سؤالاً على المستخدم هل يريد البدء كـ 'كلاين' أم كـ 'شخصية' جديدة، وإن اختار شخصية جديدة وضح له أنه سيبدأ قبل 10 أيام من انتقال كلاين إلى هذا العالم."""
|
| 31 |
|
| 32 |
+
# --- 2. محرك جلب البيانات ---
|
| 33 |
+
def get_brain_context(query, selected_volume):
|
|
|
|
|
|
|
| 34 |
try:
|
| 35 |
+
vol_res = supabase.table("novel_vault_v3").select("metadata->>text").eq("metadata->>source", selected_volume).execute()
|
| 36 |
+
full_vol_text = " ".join([d['text'] for d in vol_res.data]) if vol_res.data else ""
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 37 |
|
| 38 |
+
query_vec = model_st.encode(query).tolist()
|
| 39 |
+
search_res = supabase.rpc('match_documents', {
|
| 40 |
+
'query_embedding': query_vec,
|
| 41 |
+
'match_threshold': 0.25,
|
| 42 |
+
'match_count': 10
|
| 43 |
+
}).execute()
|
| 44 |
|
| 45 |
+
other_ctx = ""
|
| 46 |
+
if search_res.data:
|
| 47 |
+
for item in search_res.data:
|
| 48 |
+
if item['metadata'].get('source') != selected_volume:
|
| 49 |
+
other_ctx += f"\n[سياق خارجي]: {item['content']}\n"
|
| 50 |
|
| 51 |
+
return full_vol_text, other_ctx
|
| 52 |
+
except:
|
| 53 |
+
return "", ""
|
| 54 |
|
| 55 |
+
# --- 3. دالة التنبؤ (تستخدم توكن المستخدم تلقائياً) ---
|
| 56 |
+
def predict(message, history, system_prompt, model_id, selected_volume, request: gr.Request):
|
| 57 |
+
# هنا يتم جلب التوكن من حساب المستخدم المسجل في HF تلقائياً
|
| 58 |
+
token = request.auth_token if request else None
|
|
|
|
| 59 |
|
| 60 |
+
if not token:
|
| 61 |
+
yield "⚠️ يرجى تسجيل الدخول عبر زر 'Login with Hugging Face' بالأعلى لاستخدام رصيدك."
|
| 62 |
return
|
| 63 |
|
| 64 |
+
client = OpenAI(base_url="https://router.huggingface.co/v1", api_key=token)
|
| 65 |
+
active_model = model_id.strip() if model_id.strip() else DEFAULT_MODEL
|
| 66 |
+
|
| 67 |
+
vol_text, other_text = get_brain_context(message, selected_volume)
|
| 68 |
|
| 69 |
+
# حقن السياق والمجلد الكامل
|
| 70 |
+
context_injection = f"\n\n[FULL VOLUME CONTEXT]:\n{vol_text[:150000]}\n\n[RAG SEARCH]:\n{other_text}"
|
| 71 |
+
|
| 72 |
+
messages = [{"role": "system", "content": f"{system_prompt}{context_injection}"}]
|
| 73 |
|
|
|
|
|
|
|
| 74 |
for msg in history:
|
| 75 |
messages.append({"role": msg["role"], "content": msg["content"]})
|
| 76 |
+
|
| 77 |
messages.append({"role": "user", "content": message})
|
| 78 |
|
| 79 |
try:
|
| 80 |
response = client.chat.completions.create(
|
| 81 |
model=active_model,
|
| 82 |
messages=messages,
|
|
|
|
| 83 |
stream=True
|
| 84 |
)
|
| 85 |
|
|
|
|
| 89 |
partial_message += chunk.choices[0].delta.content
|
| 90 |
yield partial_message
|
| 91 |
except Exception as e:
|
| 92 |
+
yield f"❌ خطأ في الاتصال: {str(e)}"
|
| 93 |
|
| 94 |
+
# --- 4. واجهة Gradio ---
|
| 95 |
with gr.Blocks(fill_height=True) as demo:
|
| 96 |
with gr.Sidebar():
|
| 97 |
+
gr.Markdown("# 🕸️ محرك لورد الغوامض التفاعلي")
|
| 98 |
+
gr.LoginButton() # زر الدخول لتمرير التوكن
|
| 99 |
|
| 100 |
+
vol_list = gr.Dropdown(
|
| 101 |
+
choices=["Volume1-Clown.pdf", "Volume2-Faceless.pdf", "Volume3-Traveler.pdf", "Volume4-Undying.pdf"],
|
| 102 |
+
label="📖 المجلد النشط",
|
| 103 |
+
value="Volume1-Clown.pdf"
|
| 104 |
+
)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 105 |
|
| 106 |
+
with gr.Accordion("⚙️ الإعدادات", open=False):
|
| 107 |
+
model_input = gr.Textbox(label="Model ID", value=DEFAULT_MODEL)
|
| 108 |
+
system_input = gr.TextArea(label="System Prompt", value=DEFAULT_SYSTEM, lines=15)
|
|
|
|
|
|
|
|
|
|
| 109 |
|
|
|
|
| 110 |
gr.ChatInterface(
|
| 111 |
predict,
|
| 112 |
+
additional_inputs=[system_input, model_input, vol_list],
|
| 113 |
+
fill_height=True,
|
| 114 |
+
type="messages",
|
| 115 |
+
examples=[["ابدأ اللعبة"], ["من أنا؟"]]
|
| 116 |
)
|
| 117 |
|
| 118 |
if __name__ == "__main__":
|
| 119 |
+
demo.launch()
|
|
|