File size: 11,843 Bytes
338517d
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
dfd40ba
 
338517d
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
d436343
 
 
 
 
 
 
338517d
 
 
 
 
 
d436343
 
 
338517d
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
dfd40ba
338517d
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
"""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