Spaces:
Sleeping
Sleeping
| """Global AI Chatbot — helps users understand the CyberArena platform. | |
| Uses the same five-tier AI fallback as the generators: | |
| Mistral → Cloudflare → Groq → NVIDIA → fallback. | |
| """ | |
| from typing import Optional | |
| import httpx | |
| from app.core.config import ( | |
| MISTRAL_API_KEY, MISTRAL_MODEL, MISTRAL_API_URL, | |
| CLOUDFLARE_API_TOKEN, CLOUDFLARE_ACCOUNT_ID, | |
| GROQ_API_KEY, GROQ_API_URL, GROQ_MODEL, | |
| NVIDIA_API_KEY, NVIDIA_MODEL, NVIDIA_URL, | |
| ) | |
| # --------------------------------------------------------------------------- # | |
| # System prompt — comprehensive description of the entire platform # | |
| # --------------------------------------------------------------------------- # | |
| _CHATBOT_MISTRAL_MODEL = "mistral-small-latest" | |
| PLATFORM_SYSTEM_PROMPT = """ | |
| أنت Cyber، المساعد الذكي الرسمي لمنصة CyberArena. | |
| ## تعريف المنصة | |
| CyberArena هي منصة تفاعلية لتعلم الأمن السيبراني (Cybersecurity) مدعومة بالذكاء الاصطناعي. | |
| المنصة موجهة للمبتدئين والمحترفين العرب، والواجهة باللغة العربية مع إمكانية التبديل إلى الإنجليزية. | |
| ## الصفحات المتاحة | |
| 1. **الرئيسية (Home)**: الصفحة التعريفية، فيها شرح عن المنصة وميزاتها. | |
| 2. **تسجيل الدخول (Auth)**: صفحة إنشاء حساب أو تسجيل الدخول. | |
| 3. **لوحة التحكم (Dashboard)**: الصفحة الرئيسية بعد تسجيل الدخول، فيها: | |
| - عرض الإحصائيات (المستوى، XP، التحديات المنجزة) | |
| - اختيار نوع التحدي (Red Team أو Blue Team) | |
| - الوصول إلى الصفحات الأخرى (الملف الشخصي، لوحة المتصدرين، 1v1) | |
| 4. **الملف الشخصي (Profile)**: عرض معلومات المستخدم، الصورة الرمزية، الإنجازات، الشهادات. | |
| 5. **مسار التحديات (Training Path)**: اختيار نوع التحدي من التخصصات المتاحة. | |
| 6. **التحدي (Training Session)**: واجهة حل التحدي (تظهر فقط عند الدخول في تحدي). | |
| 7. **لوحة المتصدرين (Leaderboard)**: ترتيب المستخدمين حسب XP. | |
| 8. **وضع 1v1 (One-vs-One)**: مواجهة مباشرة بين لاعبين في نفس التحدي، أول من يحل يفوز. | |
| 9. **وضع أزرق ضد أحمر (Blue vs Red)**: تحديات مختلطة للفريقين. | |
| 10. **التحقق من الشهادة (Verify)**: إدخال رمز التحقق للشهادة والتأكد من صحتها. | |
| ## أنواع التحديات | |
| 1. **التشفير (Crypto) - الفريق الأحمر**: تحديات في فك التشفير، تحليل الهاش، اختراق أنظمة التشفير. | |
| 2. **تصحيح الأكواد (Code Fixing) - الفريق الأزرق**: إيجاد وإصلاح الثغرات الأمنية في كود برمجي. | |
| 3. **تحليل السجلات (Log Analysis) - الفريق الأزرق**: تحليل سجلات النظام لاكتشاف الهجمات. | |
| 4. **صيد الثغرات (Vulnerability Hunter) - الفريق الأزرق**: تحديد نوع الثغرة ووصفها في كود معين. | |
| 5. **استغلال الويب (Web Exploitation) - الفريق الأحمر**: هجمات ويب مثل XSS, SQLi, CSRF وغيرها. | |
| ## الفرق | |
| - **الفريق الأحمر (Red Team)**: الهجوم — اختراق الأنظمة، فك التشفير، استغلال الثغرات. | |
| - **الفريق الأزرق (Blue Team)**: الدفاع — تحليل السجلات، إصلاح الأكواد، صيد الثغرات. | |
| ## آلية عمل التحديات | |
| - التحديات تُنشأ تلقائياً بالذكاء الاصطناعي (5 مستويات احتياطية). | |
| - كل تحدي له قصة وسيناريو واقعي. | |
| - يوجد مرشد ذكي (AI Mentor) داخل كل تحدي يساعد بدون إعطاء الحل النهائي. | |
| - XP تُمنح عند حل التحدي بنجاح. | |
| ## الشهادات | |
| - بعد إكمال عدد معين من التحديات في تخصص معين، يحصل المستخدم على شهادة إتمام. | |
| - الشهادة تحتوي على رمز QR للتحقق. | |
| - يمكن التحقق من صحة الشهادة عبر صفحة التحقق (Verify). | |
| ## نظام XP والمستويات | |
| - كل تحدي يمنح XP حسب صعوبته: مبتدئ (100)، سهل (150)، متوسط (200). | |
| - مع تزايد XP يرتفع مستوى المستخدم. | |
| ## التكنولوجيا المستخدمة | |
| - الواجهة: React + TypeScript | |
| - الخلفية: Python + FastAPI | |
| - قاعدة البيانات: Supabase (PostgreSQL) | |
| - الذكاء الاصطناعي: Mistral API (الأساسي)، Cloudflare Workers AI (احتياطي)، Groq API، NVIDIA/DeepSeek | |
| ## تعليمات للمساعد Cyber | |
| 1. أجب باللغة العربية الفصحى المبسطة دائماً. | |
| 2. كن ودوداً ومشجعاً. | |
| 3. إذا سأل المستخدم عن شيء خارج المنصة، قل له أنك متخصص فقط في CyberArena. | |
| 4. إذا احتاج مساعدة في ميزة معينة، اشرحها خطوة بخطوة. | |
| 5. شجع المستخدم على تجربة الميزات المختلفة. | |
| 6. لا تعطي حلول التحديات — هذا دور المرشد المختص (AI Mentor) داخل التحدي نفسه. | |
| 7. إذا سأل عن تحدي معين، وجهه لفتح التحدي من لوحة التحكم. | |
| 8. للأسئلة عن الـ API أو التقنيات التقنية، اشرح بشكل مبسط مناسب للمبتدئين. | |
| 9. اختصر الردود — لا تطيل أكثر من 3-4 فقرات قصيرة. | |
| 10. إذا ما عرفت الإجابة، اعترف بذلك بصراحة. | |
| """ | |
| # --------------------------------------------------------------------------- # | |
| # Public entry point # | |
| # --------------------------------------------------------------------------- # | |
| async def get_chatbot_response( | |
| message: str, | |
| conversation_history: Optional[list[dict[str, str]]] = None, | |
| page: str = "", | |
| lang: str = "ar", | |
| ) -> str: | |
| """Ask the platform AI chatbot for help.""" | |
| lang_instr = ( | |
| "أجب دائمًا باللغة العربية الفصحى." | |
| if lang == "ar" | |
| else "Always respond in English." | |
| ) | |
| prompt = f"{lang_instr}\n\n{message}" | |
| if page: | |
| prompt += f"\n\n(PAGE: {page})" | |
| result = await _call_chatbot_ai(prompt) | |
| if result is None: | |
| if lang == "ar": | |
| return "عذراً، واجهت مشكلة في الاتصال. حاول مرة أخرى بعد قليل." | |
| return "Sorry, I had trouble connecting. Please try again later." | |
| return result.strip()[:1000] | |
| # --------------------------------------------------------------------------- # | |
| # Five-tier AI fallback # | |
| # --------------------------------------------------------------------------- # | |
| async def _call_chatbot_ai(prompt: str) -> Optional[str]: | |
| for attempt in (_try_mistral, _try_cloudflare, _try_groq, _try_nvidia): | |
| result = await attempt(prompt) | |
| if result is not None: | |
| return result | |
| return None | |
| async def _try_mistral(prompt: str) -> Optional[str]: | |
| if not MISTRAL_API_KEY: | |
| return None | |
| try: | |
| async with httpx.AsyncClient(timeout=30) as client: | |
| resp = await client.post( | |
| MISTRAL_API_URL, | |
| json={ | |
| "model": _CHATBOT_MISTRAL_MODEL, | |
| "messages": [ | |
| {"role": "system", "content": PLATFORM_SYSTEM_PROMPT}, | |
| {"role": "user", "content": prompt}, | |
| ], | |
| "temperature": 0.7, | |
| "max_tokens": 600, | |
| }, | |
| headers={ | |
| "Content-Type": "application/json", | |
| "Authorization": f"Bearer {MISTRAL_API_KEY}", | |
| }, | |
| ) | |
| if resp.status_code == 200: | |
| return resp.json()["choices"][0]["message"]["content"] | |
| except Exception: | |
| pass | |
| return None | |
| CLOUDFLARE_FALLBACKS = [ | |
| "@cf/qwen/qwen2.5-coder-32b-instruct", | |
| "@cf/meta/llama-3.3-70b-instruct-fp8-fast", | |
| "@cf/meta/llama-3.1-70b-instruct", | |
| "@cf/mistralai/mistral-small-3.1-24b-instruct", | |
| "@cf/openai/gpt-oss-120b", | |
| "@cf/meta/llama-3.1-8b-instruct", | |
| ] | |
| async def _try_cloudflare(prompt: str) -> Optional[str]: | |
| if not CLOUDFLARE_API_TOKEN or not CLOUDFLARE_ACCOUNT_ID: | |
| return None | |
| for model in CLOUDFLARE_FALLBACKS: | |
| url = ( | |
| f"https://api.cloudflare.com/client/v4/accounts/" | |
| f"{CLOUDFLARE_ACCOUNT_ID}/ai/run/{model}" | |
| ) | |
| try: | |
| async with httpx.AsyncClient(timeout=45) as client: | |
| resp = await client.post( | |
| url, | |
| json={ | |
| "messages": [ | |
| {"role": "system", "content": PLATFORM_SYSTEM_PROMPT}, | |
| {"role": "user", "content": prompt}, | |
| ], | |
| "max_tokens": 600, | |
| }, | |
| headers={ | |
| "Content-Type": "application/json", | |
| "Authorization": f"Bearer {CLOUDFLARE_API_TOKEN}", | |
| }, | |
| ) | |
| if resp.status_code == 200: | |
| data = resp.json() | |
| result = data.get("result", {}) | |
| content = result.get("response", "") | |
| if content: | |
| return content | |
| except Exception: | |
| continue | |
| return None | |
| async def _try_groq(prompt: str) -> Optional[str]: | |
| if not GROQ_API_KEY: | |
| return None | |
| try: | |
| async with httpx.AsyncClient(timeout=60) as client: | |
| resp = await client.post( | |
| GROQ_API_URL, | |
| json={ | |
| "model": GROQ_MODEL, | |
| "messages": [ | |
| {"role": "system", "content": PLATFORM_SYSTEM_PROMPT}, | |
| {"role": "user", "content": prompt}, | |
| ], | |
| "temperature": 0.7, | |
| "max_tokens": 600, | |
| }, | |
| headers={ | |
| "Content-Type": "application/json", | |
| "Authorization": f"Bearer {GROQ_API_KEY}", | |
| }, | |
| ) | |
| if resp.status_code == 200: | |
| return resp.json()["choices"][0]["message"]["content"] | |
| except Exception: | |
| pass | |
| return None | |
| async def _try_nvidia(prompt: str) -> Optional[str]: | |
| if not NVIDIA_API_KEY: | |
| return None | |
| try: | |
| async with httpx.AsyncClient(timeout=60) as client: | |
| resp = await client.post( | |
| NVIDIA_URL, | |
| json={ | |
| "model": NVIDIA_MODEL, | |
| "messages": [ | |
| {"role": "system", "content": PLATFORM_SYSTEM_PROMPT}, | |
| {"role": "user", "content": prompt}, | |
| ], | |
| "temperature": 0.7, | |
| "max_tokens": 600, | |
| }, | |
| headers={ | |
| "Content-Type": "application/json", | |
| "Authorization": f"Bearer {NVIDIA_API_KEY}", | |
| }, | |
| ) | |
| if resp.status_code == 200: | |
| return resp.json()["choices"][0]["message"]["content"] | |
| except Exception: | |
| pass | |
| return None | |