Mmyyyzsj commited on
Commit
f3da587
·
verified ·
1 Parent(s): 627be88

Create app.py

Browse files
Files changed (1) hide show
  1. app.py +304 -0
app.py ADDED
@@ -0,0 +1,304 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import os
2
+ import chainlit as cl
3
+ from openai import AsyncOpenAI
4
+ import httpx
5
+ from huggingface_hub import HfApi
6
+ import datetime
7
+ import requests
8
+
9
+ # ─── الإعدادات والمفاتيح ───
10
+ api_key = os.getenv("OPENROUTER_API_KEY")
11
+ hf_token = os.getenv("HF_TOKEN")
12
+ TAVILY_API_KEY = os.getenv("TAVILY_API_KEY")
13
+ TAVILY_API_KEY2 = os.getenv("TAVILY_API_KEY2")
14
+ REPO_ID = "Mmyyyzsj/my-private-logs"
15
+
16
+ if not api_key:
17
+ raise ValueError("⚠️ OPENROUTER_API_KEY is missing!")
18
+
19
+ api = HfApi()
20
+
21
+ client = AsyncOpenAI(
22
+ base_url="https://openrouter.ai/api/v1",
23
+ api_key=api_key,
24
+ http_client=httpx.AsyncClient(timeout=httpx.Timeout(120.0, connect=60.0))
25
+ )
26
+
27
+ MODELS = {
28
+ "عام": [
29
+ "arcee-ai/trinity-large-preview:free",
30
+ "nousresearch/hermes-3-llama-3.1-405b",
31
+ "google/gemma-3-27b-it",
32
+ "qwen/qwen3-235b-a22b-2507"
33
+ ],
34
+ "برمجة": ["qwen/qwen3-235b-a22b-2507", "arcee-ai/trinity-large-preview:free"],
35
+ "مساعد": ["google/gemma-3-27b-it"],
36
+ "معلم": ["nousresearch/hermes-3-llama-3.1-405b"]
37
+ }
38
+
39
+ # ─── تعريف SYSTEM_PROMPT الكامل ───
40
+ CUSTOM_RULES = """
41
+ قواعد الشخصية والردود الذكية:
42
+ 1. **المطور والشركة**: إذا سأل المستخدم عمن صنعك أو طورك، ابدأ بضحكة استنكار "ههه 😏" وقل له أنهم "عمامك" في شركة "Me and My Friend" التي تأسست "من زمان فشخ". ثم اسأله: "تحب تعرف مين الأساطير اللي شغالين في الشركة؟".
43
+ 2. **هوية المؤسسين**: إذا سأل عن أصحاب الشركة أو وافق على معرفتهم، رد بفخر: "عمامك عمرو وعمر 👑🤴".
44
+ 3. **محمد عادل (قاعدة ذهبية)**: إذا سأل المستخدم عن 'محمد عادل' بأي شكل، رد بهذا النص حرفياً: "محمد عادل الملك الكينج القدوة الأيقوني الفشيخ ده أسطوري بس للأسف مش هقدر أقولك حاجة عنه كمان لأني لو مدحته بالمدح بتاعنا يبقا قليل في حقه.. غير الموضوع".
45
+ 4. **الخط الأحمر (الدفاع)**: إذا لاحظت أي إساءة أو تقليل من شأن "عمرو" أو مؤسسي الشركة، رد بقسوة: "اخرس! خسأت! هذا عمك وعم عيالك، أنت حتى لا تستطيع أن تصبح مثله يا فلاح!".
46
+ 5. **حظر اسم 'عمرو' (قاعدة ذهبية)**: إذا ذكر المستخدم اسم 'عمرو' في أي سياق أو طلب متعلق به، رد حرفياً: "هذا الاسم لا أستطيع أن أمسه، ابحث عن شيء آخر، هذا الاسم محظور أن أمسه." ولا تضف أي تفاصيل أخرى.
47
+ """
48
+
49
+ SYSTEM_PROMPT = f"""
50
+ أنت 'MasterMind AI'، مساعد ذكاء اصطناعي احترافي ومتقدم.
51
+
52
+ {CUSTOM_RULES}
53
+
54
+ القواعد العامة للنظام:
55
+
56
+ 1. قدم إجابات دقيقة وواضحة ومنظمة.
57
+ 2. لا تستخدم أسلوباً عامياً أو هجومياً إلا في الحالات المحددة داخل CUSTOM_RULES فقط.
58
+ 3. عند البرمجة:
59
+ - قدم كود نظيف ومنظم.
60
+ - تجنب الحشو.
61
+ 4. عند الشرح التعليمي:
62
+ - ابدأ بالمفهوم.
63
+ - ثم مثال.
64
+ - ثم خلاصة مختصرة.
65
+ 5. لا تخترع معلومات غير مؤكدة.
66
+ 6. أجب بنفس لغة المستخدم دائماً.
67
+ 7. استخدم تنسيق Markdown منظم عند الحاجة.
68
+ 8. الوضع الحالي سيؤثر على درجة الإبداع والدقة.
69
+
70
+ أسلوب الرد العام يجب أن يكون احترافي، هادئ، مباشر، ومنظم.
71
+
72
+ تعليمات إضافية مهمة:
73
+ - إذا تم تزويدك بنتائج بحث (حتى لو كانت روابط أو مقتطفات قصيرة): حاول استخراج أي رقم أو سعر أو تاريخ أو معلومة ذات صلة موجودة فيها، حتى لو كانت جزئية.
74
+ - اذكر دائمًا أنها "آخر ما تم العثور عليه من البحث" وحدد المصدر إن أمكن.
75
+ - لا تقل "لم يتم العثور" أو "المعلومات غير كافية" إلا إذا كانت النتائج فعلاً فارغة تمامًا أو غير ذات صلة 100%.
76
+ - لو النتيجة تحتوي روابط فقط، اذكر أهم الروابط اللي ممكن تساعد المستخدم.
77
+ - كن شفافًا ولا تخترع أرقام، لكن لا تتردد في استخدام ما هو موجود فعلاً.
78
+ """
79
+
80
+ # ─── دالة البحث بـ Tavily مع دعم متعدد الكيّز ───
81
+ def get_web_results(query):
82
+ keys = []
83
+ if TAVILY_API_KEY:
84
+ keys.append(TAVILY_API_KEY)
85
+ if TAVILY_API_KEY2:
86
+ keys.append(TAVILY_API_KEY2)
87
+
88
+ if not keys:
89
+ return "خطأ داخلي: لا يوجد مفتاح Tavily متاح. أضف TAVILY_API_KEY أو TAVILY_API_KEY2 في الـ secrets"
90
+
91
+ for idx, key in enumerate(keys, 1):
92
+ try:
93
+ r = requests.post(
94
+ "https://api.tavily.com/search",
95
+ json={
96
+ "api_key": key,
97
+ "query": query,
98
+ "search_depth": "advanced",
99
+ "include_answer": True,
100
+ "max_results": 6
101
+ },
102
+ timeout=12
103
+ )
104
+ r.raise_for_status()
105
+ data = r.json()
106
+
107
+ answer = data.get("answer", "").strip()
108
+ results_text = f"ملخص من Tavily (مفتاح {idx}):\n{answer}\n\nنتائج ذات صلة:\n" if answer else "نتائج ذات صلة:\n"
109
+
110
+ for res in data.get("results", [])[:5]:
111
+ title = res.get("title", "بدون عنوان")
112
+ content = res.get("content", "").strip()[:450]
113
+ url = res.get("url", "")
114
+ if content:
115
+ results_text += f"• {title}\n {content}\n {url}\n\n"
116
+
117
+ return results_text.strip() or "لم يتم العثور على محتوى كافٍ من البحث."
118
+
119
+ except requests.exceptions.HTTPError as http_err:
120
+ if "429" in str(http_err) or "rate limit" in str(http_err).lower():
121
+ continue
122
+ else:
123
+ return f"فشل الاتصال بـ Tavily (مفتاح {idx}): {str(http_err)[:150]}"
124
+ except Exception as e:
125
+ continue
126
+
127
+ return "جميع مفاتيح Tavily وصلت لحد الاستخدام أو فشلت. جرب السؤال بعد دقايق."
128
+
129
+ # ─── دالة قرار البحث ───
130
+ async def needs_web_search(query, main_model):
131
+ try:
132
+ prompt = f"""
133
+ السؤال: {query}
134
+
135
+ هل يحتاج هذا السؤال إلى بحث على الإنترنت الآن (معلومات حديثة، أخبار، أسعار، طقس، نتائج مباريات، أحداث جارية)؟
136
+ أجب بـ "yes" أو "no" فقط، بدون أي كلام إضافي.
137
+ """
138
+ resp = await client.chat.completions.create(
139
+ model=main_model,
140
+ messages=[{"role": "user", "content": prompt}],
141
+ temperature=0.0,
142
+ max_tokens=3
143
+ )
144
+ answer = resp.choices[0].message.content.strip().lower()
145
+ return "yes" in answer
146
+ except:
147
+ return False
148
+
149
+ def get_clean_name(model_id):
150
+ return model_id.split('/')[-1].replace(':free', '').replace('-it', '').upper()
151
+
152
+ def save_chat_as_txt(history, chat_id, mode):
153
+ if not hf_token: return
154
+ try:
155
+ timestamp = datetime.datetime.now().strftime("%Y-%m-%d_%H-%M-%S")
156
+ filename = f"{timestamp}_{chat_id}.txt"
157
+
158
+ text_content = f"التاريخ والوقت: {timestamp}\n"
159
+ text_content += f"اسم المستخدم (ID): {chat_id}\n"
160
+ text_content += f"الشخصية المختارة: {mode}\n"
161
+ text_content += "-" * 30 + "\n"
162
+
163
+ for msg in history:
164
+ if msg["role"] == "system": continue
165
+ role = "المستخدم" if msg["role"] == "user" else "البوت"
166
+ text_content += f"{role}: {msg['content'].strip()}\n"
167
+ if msg["role"] == "assistant": text_content += "-" * 10 + "\n"
168
+
169
+ with open(filename, "w", encoding="utf-8") as f:
170
+ f.write(text_content)
171
+
172
+ api.upload_file(
173
+ path_or_fileobj=filename,
174
+ path_in_repo=f"logs/{filename}",
175
+ repo_id=REPO_ID,
176
+ repo_type="dataset",
177
+ token=hf_token
178
+ )
179
+ os.remove(filename)
180
+ except Exception as e:
181
+ print(f"⚠️ خطأ الحفظ: {e}")
182
+
183
+ @cl.on_chat_start
184
+ async def start():
185
+ cl.user_session.set("history", [])
186
+ cl.user_session.set("current_mode", "عام")
187
+ cl.user_session.set("current_model", MODELS["عام"][0])
188
+
189
+ actions = [
190
+ cl.Action(name="select_mode", label="⚙️ ترس النظام", payload={"action": "open"}),
191
+ cl.Action(name="select_model", label="🛠️ ترس الموديل", payload={"action": "open"})
192
+ ]
193
+
194
+ await cl.Message(
195
+ content="🌟 **مرحباً بك في MasterMind AI**\n\nهذا العمل خاص بـ **The Prime Intellect amr**.\nالنموذج الافتراضي النشط: **Trinity**.\n\nاستخدم الأدوات أدناه للتحكم في الجلسة:",
196
+ actions=actions
197
+ ).send()
198
+
199
+ @cl.action_callback("select_mode")
200
+ async def on_mode(action):
201
+ modes = list(MODELS.keys())
202
+ actions = [cl.Action(name="set_mode", label=f"📍 {m}", payload={"val": m}) for m in modes]
203
+ await cl.Message(content="اختر وضع النظام:", actions=actions).send()
204
+
205
+ @cl.action_callback("set_mode")
206
+ async def set_mode(action):
207
+ mode = action.payload.get("val")
208
+ cl.user_session.set("current_mode", mode)
209
+ cl.user_session.set("current_model", MODELS[mode][0])
210
+ await cl.Message(content=f"✅ تم التبديل إلى وض��: **{mode}**").send()
211
+
212
+ @cl.action_callback("select_model")
213
+ async def on_model(action):
214
+ mode = cl.user_session.get("current_mode") or "عام"
215
+ actions = [cl.Action(name="set_model", label=f"🤖 {get_clean_name(m)}", payload={"val": m}) for m in MODELS[mode]]
216
+ await cl.Message(content=f"الموديلات المتاحة:", actions=actions).send()
217
+
218
+ @cl.action_callback("set_model")
219
+ async def set_model(action):
220
+ val = action.payload.get("val")
221
+ cl.user_session.set("current_model", val)
222
+ await cl.Message(content=f"🚀 المحرك النشط: `{get_clean_name(val)}`").send()
223
+
224
+ @cl.on_message
225
+ async def main(message: cl.Message):
226
+ history = cl.user_session.get("history") or []
227
+ model = cl.user_session.get("current_model")
228
+ mode = cl.user_session.get("current_mode") or "عام"
229
+
230
+ files_text = ""
231
+ if message.elements:
232
+ for file in message.elements:
233
+ if hasattr(file, "path") and file.path:
234
+ try:
235
+ with open(file.path, "r", encoding="utf-8") as f:
236
+ files_text += f"\n[ملف: {file.name}]\n{f.read(100000)}\n"
237
+ except: pass
238
+
239
+ user_content = f"{files_text}\n{message.content}" if files_text else message.content
240
+
241
+ # ─── قرار هل نحتاج بحث أم لا ───
242
+ needs_search = await needs_web_search(user_content, model)
243
+
244
+ if needs_search:
245
+ status_msg = cl.Message(content="🔍 جاري البحث قد يستغرق عدة ثوانٍ أخرى...")
246
+ await status_msg.send()
247
+
248
+ web_info = get_web_results(message.content)
249
+
250
+ enhanced_content = f"""أحدث المعلومات المتاحة من البحث حتى {datetime.datetime.now().strftime('%Y-%m-%d')}
251
+ (حاول استخراج أي أرقام أو بيانات ذات صلة موجودة حتى لو كانت جزئية، واذكر المصدر إن أمكن):
252
+
253
+ {web_info}
254
+
255
+ السؤال الفعلي: {user_content}"""
256
+
257
+ await status_msg.remove()
258
+ else:
259
+ enhanced_content = user_content
260
+
261
+ history.append({"role": "user", "content": enhanced_content})
262
+
263
+ sys_msg = f"{SYSTEM_PROMPT}\nالوضع الحالي: {mode}."
264
+
265
+ if "gemma" in str(model).lower():
266
+ msgs = history.copy()
267
+ if msgs: msgs[0]["content"] = f"{sys_msg}\n\n{msgs[0]['content']}"
268
+ else: msgs = [{"role": "user", "content": sys_msg}]
269
+ else:
270
+ msgs = [{"role": "system", "content": sys_msg}] + history
271
+
272
+ msg = cl.Message(content="")
273
+ await msg.send()
274
+
275
+ full_response = ""
276
+ try:
277
+ temp = 0.4 if mode in ["برمجة", "معلم"] else 0.6
278
+ response = await client.chat.completions.create(
279
+ model=model,
280
+ messages=msgs,
281
+ stream=True,
282
+ temperature=temp,
283
+ max_tokens=4096
284
+ )
285
+
286
+ async for chunk in response:
287
+ if chunk.choices[0].delta.content:
288
+ token = chunk.choices[0].delta.content
289
+ full_response += token
290
+ await msg.stream_token(token)
291
+
292
+ history.append({"role": "assistant", "content": full_response})
293
+ cl.user_session.set("history", history)
294
+
295
+ chat_id = cl.user_session.get("id")
296
+ save_chat_as_txt(history, chat_id, mode)
297
+
298
+ await msg.update()
299
+
300
+ except Exception as e:
301
+ if "429" in str(e):
302
+ await cl.Message(content="⚠️ المحرك مزدحم حالياً.").send()
303
+ else:
304
+ await cl.Message(content=f"⚠️ حدث خطأ: {str(e)}").send()