# ============================================================= # đ¤ Ø´Ø§ØĒŲ Ø¨ŲØĒ - 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('