# ============================================================= # 🤖 شاØĒ؊ Ø¨ŲˆØĒ - 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('
🤖 شاØĒ؊ Ø¨ŲˆØĒ
') gr.HTML( '
' "ŲŠØšŲ…Ų„ Ø¨Ų†Ų…ŲˆØ°ØŦŲŽŲŠ Groq (LLaMA 3) ؈ Kimi (Moonshot) Ø¨Ø§Ų„ØĒŲˆØ§Ø˛ŲŠ" "
" ) 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("đŸŸĸ Groq Response — LLaMA 3-70B") 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("đŸ”ĩ Kimi Response — Moonshot") 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, )