chatchatpot / app.py
eisssa107's picture
Update app.py
dc7cffc verified
Raw
History Blame Contribute Delete
12.5 kB
# =============================================================
# 🤖 شاتي بوت - Gradio Frontend
# يتصل بـ n8n على Hugging Face
# =============================================================
import gradio as gr
import requests
import json
import uuid
import time
import logging
from typing import Tuple
# ─────────────────────────────────────────────
# ⚙️ الإعدادات — عدّل السطر التالي فقط
# ─────────────────────────────────────────────
N8N_WEBHOOK_URL = "https://eisssa107-testn8n.hf.space/webhook/chatbot"
REQUEST_TIMEOUT_SECONDS = 60
# ─────────────────────────────────────────────
# 🔧 إعداد الـ Logging
# ─────────────────────────────────────────────
logging.basicConfig(
level=logging.INFO,
format="%(asctime)s [%(levelname)s] %(message)s",
datefmt="%Y-%m-%d %H:%M:%S",
)
logger = logging.getLogger(__name__)
# ─────────────────────────────────────────────
# 🧠 Session ID ثابت لكل تشغيل
# ─────────────────────────────────────────────
FIXED_SESSION_ID = str(uuid.uuid4())
logger.info(f"Session ID: {FIXED_SESSION_ID}")
# ─────────────────────────────────────────────
# 🌐 الاتصال بـ n8n Webhook
# ─────────────────────────────────────────────
def call_n8n_webhook(message: str, session_id: str) -> Tuple[bool, str, str, str]:
payload = {
"message": message.strip(),
"session_id": session_id,
"timestamp": time.strftime("%Y-%m-%dT%H:%M:%SZ", time.gmtime()),
}
logger.info(f"Sending | session={session_id[:8]} | msg={message[:50]}...")
try:
response = requests.post(
url=N8N_WEBHOOK_URL,
json=payload,
timeout=REQUEST_TIMEOUT_SECONDS,
headers={
"Content-Type": "application/json",
"Accept": "application/json",
},
)
if response.status_code == 404:
return False, "", "", (
"❌ خطأ 404: الـ Webhook غير موجود.\n"
"تأكد إن الـ Workflow مفعّل (Publish) في n8n."
)
if response.status_code == 500:
return False, "", "", (
"❌ خطأ 500: خطأ داخلي في n8n.\n"
"افتح n8n وتحقق من Executions."
)
if response.status_code != 200:
return False, "", "", f"❌ خطأ HTTP {response.status_code}: {response.reason}"
try:
data = response.json()
except (json.JSONDecodeError, ValueError) as e:
return False, "", "", (
f"❌ خطأ في قراءة الرد:\n{str(e)}\n\n"
f"الرد الخام:\n{response.text[:200]}"
)
if not data.get("success", False):
return False, "", "", f"⚠️ خطأ من n8n: {data.get('error', 'خطأ غير محدد')}"
groq_reply = data.get("groq_reply", "لا يوجد رد من Groq")
kimi_reply = data.get("kimi_reply", "لا يوجد رد من Kimi")
logger.info("✅ تم الرد بنجاح")
return True, groq_reply, kimi_reply, ""
except requests.exceptions.ConnectionError:
return False, "", "", (
"❌ تعذّر الاتصال بـ n8n.\n"
f"تأكد إن الـ Space شغّال:\n{N8N_WEBHOOK_URL}"
)
except requests.exceptions.Timeout:
return False, "", "", (
f"⏱️ انتهت مهلة الانتظار ({REQUEST_TIMEOUT_SECONDS} ثانية).\n"
"النماذج قد تكون بطيئة، حاول مرة أخرى."
)
except Exception as e:
logger.exception(f"خطأ غير متوقع: {e}")
return False, "", "", f"❌ خطأ غير متوقع:\n{str(e)}"
# ─────────────────────────────────────────────
# 🎯 دالة المعالجة
# ─────────────────────────────────────────────
def process_message(user_message: str, session_state: dict) -> Tuple[str, str, str, dict]:
if not user_message or not user_message.strip():
return "", "", "⚠️ الرجاء كتابة رسالة أولاً.", session_state
session_id = session_state.get("session_id", FIXED_SESSION_ID)
success, groq_reply, kimi_reply, error = call_n8n_webhook(user_message, session_id)
if not success:
return "", "", error, session_state
session_state["message_count"] = session_state.get("message_count", 0) + 1
status = (
f"✅ تم الإرسال | "
f"Session: {session_id[:8]}... | "
f"رسائل: {session_state['message_count']}"
)
return groq_reply, kimi_reply, status, session_state
# ─────────────────────────────────────────────
# 🗑️ دالة المسح
# ─────────────────────────────────────────────
def clear_all() -> Tuple[str, str, str, str, dict]:
new_session = {
"session_id": str(uuid.uuid4()),
"message_count": 0,
}
logger.info(f"Session جديد: {new_session['session_id']}")
return (
"",
"",
"",
f"🔄 تم المسح | Session جديد: {new_session['session_id'][:8]}...",
new_session,
)
# ─────────────────────────────────────────────
# 🎨 CSS
# ─────────────────────────────────────────────
CSS = """
body, .gradio-container {
font-family: 'Segoe UI', 'Cairo', Tahoma, sans-serif !important;
}
.main-title {
text-align: center;
font-size: 2.2rem;
font-weight: 800;
margin-bottom: 6px;
background: linear-gradient(135deg, #6366f1, #8b5cf6, #06b6d4);
-webkit-background-clip: text;
-webkit-text-fill-color: transparent;
background-clip: text;
}
.subtitle {
text-align: center;
color: #6b7280;
font-size: 0.95rem;
margin-bottom: 20px;
}
.groq-box textarea {
border: 2px solid #10b981 !important;
border-radius: 12px !important;
background: #f0fdf4 !important;
font-size: 1rem !important;
line-height: 1.7 !important;
min-height: 180px !important;
}
.kimi-box textarea {
border: 2px solid #3b82f6 !important;
border-radius: 12px !important;
background: #eff6ff !important;
font-size: 1rem !important;
line-height: 1.7 !important;
min-height: 180px !important;
}
.input-box textarea {
border: 2px solid #8b5cf6 !important;
border-radius: 12px !important;
font-size: 1.05rem !important;
}
.send-btn {
background: linear-gradient(135deg, #6366f1, #8b5cf6) !important;
color: white !important;
border: none !important;
border-radius: 10px !important;
font-size: 1.1rem !important;
font-weight: 600 !important;
padding: 12px 28px !important;
}
.clear-btn {
background: linear-gradient(135deg, #ef4444, #f97316) !important;
color: white !important;
border: none !important;
border-radius: 10px !important;
font-size: 1.05rem !important;
font-weight: 600 !important;
padding: 12px 28px !important;
}
.status-box textarea {
background: #1e1e2e !important;
color: #a6e3a1 !important;
font-family: 'Consolas', monospace !important;
font-size: 0.85rem !important;
border-radius: 8px !important;
border: 1px solid #313244 !important;
}
"""
# ─────────────────────────────────────────────
# 🖥️ واجهة Gradio
# ─────────────────────────────────────────────
with gr.Blocks(
css=CSS,
title="🤖 شاتي بوت",
theme=gr.themes.Soft(
primary_hue="violet",
secondary_hue="cyan",
neutral_hue="slate",
),
) as demo:
session_state = gr.State(
value={
"session_id": FIXED_SESSION_ID,
"message_count": 0,
}
)
gr.HTML('<div class="main-title">🤖 شاتي بوت</div>')
gr.HTML(
'<div class="subtitle">'
"يعمل بنموذجَي <b>Groq (LLaMA 3)</b> و <b>Kimi (Moonshot)</b> بالتوازي"
"</div>"
)
with gr.Row():
input_box = gr.Textbox(
label="💬 اكتب رسالتك هنا",
placeholder="مثال: ما هي عاصمة مصر؟",
lines=3,
max_lines=6,
elem_classes=["input-box"],
)
with gr.Row():
send_btn = gr.Button("🚀 إرسال", variant="primary", elem_classes=["send-btn"], scale=2)
clear_btn = gr.Button("🗑️ مسح الكل", variant="secondary", elem_classes=["clear-btn"], scale=1)
with gr.Row():
with gr.Column(scale=1):
gr.HTML("<b>🟢 Groq Response — LLaMA 3-70B</b>")
groq_output = gr.Textbox(
label="",
lines=8,
max_lines=20,
interactive=False,
placeholder="سيظهر هنا رد Groq...",
elem_classes=["groq-box"],
show_copy_button=True,
)
with gr.Column(scale=1):
gr.HTML("<b>🔵 Kimi Response — Moonshot</b>")
kimi_output = gr.Textbox(
label="",
lines=8,
max_lines=20,
interactive=False,
placeholder="سيظهر هنا رد Kimi...",
elem_classes=["kimi-box"],
show_copy_button=True,
)
status_box = gr.Textbox(
label="📊 الحالة",
lines=2,
interactive=False,
value=f"⏳ جاهز | Session: {FIXED_SESSION_ID[:8]}...",
elem_classes=["status-box"],
)
with gr.Accordion("ℹ️ معلومات", open=False):
gr.Markdown(
f"""
| المعلومة | القيمة |
|----------|--------|
| **Session ID** | `{FIXED_SESSION_ID}` |
| **Webhook** | `{N8N_WEBHOOK_URL}` |
| **Timeout** | `{REQUEST_TIMEOUT_SECONDS}` ثانية |
| **Groq Model** | `llama3-70b-8192` |
| **Kimi Model** | `moonshot-v1-8k` |
"""
)
send_btn.click(
fn=process_message,
inputs=[input_box, session_state],
outputs=[groq_output, kimi_output, status_box, session_state],
)
input_box.submit(
fn=process_message,
inputs=[input_box, session_state],
outputs=[groq_output, kimi_output, status_box, session_state],
)
clear_btn.click(
fn=clear_all,
inputs=[],
outputs=[input_box, groq_output, kimi_output, status_box, session_state],
)
# ─────────────────────────────────────────────
# 🚀 تشغيل
# ─────────────────────────────────────────────
if __name__ == "__main__":
logger.info("=" * 50)
logger.info("🤖 شاتي بوت يبدأ...")
logger.info(f" Webhook : {N8N_WEBHOOK_URL}")
logger.info(f" Session : {FIXED_SESSION_ID}")
logger.info("=" * 50)
demo.launch(
server_name="0.0.0.0",
server_port=7860,
share=False,
show_error=True,
)