Spaces:
Sleeping
Sleeping
| # ============================================================= | |
| # 🤖 شاتي بوت - 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, | |
| ) |