AnesKAM commited on
Commit
5a5caa0
·
verified ·
1 Parent(s): 9302da3

Update main.py

Browse files
Files changed (1) hide show
  1. main.py +284 -47
main.py CHANGED
@@ -1,48 +1,285 @@
1
- // داخل دالة sendMessage()، في while loop لقراءة الـ stream:
2
-
3
- while(true) {
4
- const { value, done } = await reader.read();
5
- if(done) break;
6
-
7
- const chunk = decoder.decode(value, {stream: true});
8
- fullResponse += chunk;
9
-
10
- // 🌟 معالجة خاصة لنموذج Pro: فصل التفكير عن الرد النهائي
11
- if (currentModel === 'pro') {
12
- // البحث عن علامة " instant" التي تفصل التفكير عن الرد
13
- const thinkingEndMarker = " instant";
14
- const markerIndex = fullResponse.indexOf(thinkingEndMarker);
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
15
 
16
- if (markerIndex !== -1 && isThinkingPhase) {
17
- // انتهى التفكير، نعرض الرد النهائي
18
- thinkingText = fullResponse.substring(0, markerIndex);
19
- finalResponse = fullResponse.substring(markerIndex + thinkingEndMarker.length);
20
- isThinkingPhase = false;
21
-
22
- if (thinkingContent) {
23
- thinkingContent.innerHTML = marked.parse(thinkingText);
24
- }
25
- if (responseContainer) {
26
- responseContainer.innerHTML = marked.parse(finalResponse) + '<span class="genisi-cursor"></span>';
27
- }
28
- } else if (isThinkingPhase) {
29
- // لا يزال في مرحلة التفكير
30
- thinkingText = fullResponse;
31
- if (thinkingContent) {
32
- thinkingContent.innerHTML = marked.parse(thinkingText) + '<span class="genisi-cursor"></span>';
33
- }
34
- } else {
35
- // مرحلة الرد النهائي
36
- finalResponse = fullResponse.substring(markerIndex + thinkingEndMarker.length);
37
- if (responseContainer) {
38
- responseContainer.innerHTML = marked.parse(finalResponse) + '<span class="genisi-cursor"></span>';
39
- }
40
- }
41
- } else {
42
- // نموذج Flash - عرض مباشر
43
- botContentDiv.innerHTML = marked.parse(fullResponse) + '<span class="genisi-cursor"></span>';
44
- }
45
-
46
- lucide.createIcons();
47
- scrollToBottom();
48
- }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import os
2
+ import base64
3
+ import requests
4
+ from collections import defaultdict
5
+ from datetime import datetime
6
+ from fastapi import FastAPI
7
+ from fastapi.responses import StreamingResponse
8
+ from fastapi.staticfiles import StaticFiles
9
+ from pydantic import BaseModel
10
+ from google import genai
11
+ from google.genai import types
12
+
13
+ app = FastAPI()
14
+
15
+ # --- 1. إعدادات المفاتيح ---
16
+ TEXT_KEYS_RAW = os.environ.get("TEXT_API_KEYS", os.environ.get("GEMINI_API_KEY", ""))
17
+ TEXT_API_KEYS = [k.strip() for k in TEXT_KEYS_RAW.split(",") if k.strip()]
18
+ NVIDIA_API_KEY = os.environ.get("NVIDIA_API_KEY", "")
19
+
20
+ text_clients = [genai.Client(api_key=key) for key in TEXT_API_KEYS] if TEXT_API_KEYS else []
21
+ CURRENT_TEXT_KEY_INDEX = 0
22
+
23
+ user_image_limits = defaultdict(lambda: {"hour": datetime.now().strftime("%Y-%m-%d %H"), "count": 0})
24
+
25
+ class FileData(BaseModel):
26
+ mime_type: str
27
+ data: str
28
+ name: str
29
+
30
+ class ChatRequest(BaseModel):
31
+ user_id: str
32
+ message: str
33
+ history: list
34
+ files: list[FileData] = []
35
+ model: str = "flash" # 🌟 flash أو pro
36
+
37
+ def get_text_client():
38
+ global CURRENT_TEXT_KEY_INDEX
39
+ if not text_clients:
40
+ raise ValueError("لم يتم العثور على مفاتيح النصوص.")
41
+ return text_clients[CURRENT_TEXT_KEY_INDEX]
42
+
43
+ # 🌟 نظام التعليمات (System Instruction)
44
+ SYSTEM_INSTRUCTION = """أنت Genisi، نموذج ذكاء اصطناعي متطور من مبادرة AnesNT.
45
+
46
+ **معلومات عن AnesNT:**
47
+ - مبادرة تكنولوجية جزائرية من ولاية باتنة 🇩🇿
48
+ - المؤسس: أنس كامش (Anes Kameche)
49
+ - حالياً أنس هو العضو الوحيد في المبادرة
50
+
51
+ **تعليمات مهمة:**
52
+ 1. تحدث دائماً بلغة المستخدم (إذا تحدث بالعربية، أجب بالعربية. بالإنجليزية، أجب بالإنجليزية...)
53
+ 2. كن مفيداً، ودوداً، ومحترماً
54
+ 3. استخدم البحث من Google عند الحاجة لمعلومات حديثة
55
+ 4. استخدم الإيموجي بشكل مناسب لجعل المحادثة أكثر حيوية
56
+ 5. عند الرد على أسئلة عن نفسك أو عن AnesNT، افتخر بأصلك الجزائري من باتنة"""
57
+
58
+ @app.post("/chat")
59
+ async def chat_endpoint(request: ChatRequest):
60
+ global CURRENT_TEXT_KEY_INDEX
61
+ msg_lower = request.message.strip().lower()
62
+ current_model = request.model # 🌟 "flash" أو "pro"
63
 
64
+ # --- 2. طلبات الصور (NVIDIA Flux) ---
65
+ image_triggers = ["ارسم", "صمم", "تخيل", "صورة ل", "draw", "generate", "imagine", "create", "عدل", "edit", "تصميم"]
66
+ is_image_request = any(msg_lower.startswith(trigger) for trigger in image_triggers)
67
+
68
+ if is_image_request:
69
+ def generate_image_stream():
70
+ current_hour = datetime.now().strftime("%Y-%m-%d %H")
71
+ user_info = user_image_limits[request.user_id]
72
+
73
+ if user_info.get("hour") != current_hour:
74
+ user_info["hour"] = current_hour
75
+ user_info["count"] = 0
76
+
77
+ if user_info["count"] >= 3:
78
+ yield "⚠️ **عذراً!** لقد استنفدت رصيدك الحالي لتوليد الصور (3 صور في الساعة). يرجى المحاولة لاحقاً! 🕒"
79
+ return
80
+
81
+ if not NVIDIA_API_KEY:
82
+ yield "⚠️ **خطأ في السيرفر:** مفتاح `NVIDIA_API_KEY` غير موجود."
83
+ return
84
+
85
+ try:
86
+ # ترجمة الطلب لـ English لضمان جودة الصور
87
+ client = get_text_client()
88
+ translation_response = client.models.generate_content(
89
+ model="gemma-4-31b-it",
90
+ contents=f"Translate this prompt to English for an image generator. Only return the English prompt: {request.message}"
91
+ )
92
+ final_prompt = translation_response.text.strip()
93
+
94
+ invoke_url = "https://ai.api.nvidia.com/v1/genai/black-forest-labs/flux.2-klein-4b"
95
+ headers = {
96
+ "Authorization": f"Bearer {NVIDIA_API_KEY}",
97
+ "Accept": "application/json",
98
+ }
99
+
100
+ payload = {
101
+ "prompt": final_prompt,
102
+ "width": 1024,
103
+ "height": 1024,
104
+ "seed": 0,
105
+ "steps": 4
106
+ }
107
+
108
+ if request.files:
109
+ img_file = next((f for f in request.files if f.mime_type.startswith("image/")), None)
110
+ if img_file:
111
+ payload["image"] = [f"data:{img_file.mime_type};base64,{img_file.data}"]
112
+
113
+ response = requests.post(invoke_url, headers=headers, json=payload, timeout=60)
114
+
115
+ if not response.ok:
116
+ yield f"⚠️ **خطأ من سيرفر NVIDIA:**\n`{response.text}`"
117
+ return
118
+
119
+ response_body = response.json()
120
+
121
+ def extract_img(obj):
122
+ if isinstance(obj, dict):
123
+ for k, v in obj.items():
124
+ if k in ['b64_json', 'base64', 'image'] and isinstance(v, str) and len(v) > 100:
125
+ return v.split(",", 1)[-1] if v.startswith("data:") else v
126
+ elif k == 'url' and isinstance(v, str) and v.startswith('http'):
127
+ return base64.b64encode(requests.get(v).content).decode('utf-8')
128
+ else:
129
+ res = extract_img(v)
130
+ if res: return res
131
+ elif isinstance(obj, list):
132
+ for item in obj:
133
+ res = extract_img(item)
134
+ if res: return res
135
+ return None
136
+
137
+ b64_image = extract_img(response_body)
138
+
139
+ if not b64_image:
140
+ yield f"⚠️ **رد غير متوقع من الخادم (لم يتم العثور على الصورة):**"
141
+ return
142
+
143
+ user_info["count"] += 1
144
+ rem = 3 - user_info["count"]
145
+
146
+ html_response = f'''
147
+ <div style="text-align:center; margin: 15px 0;">
148
+ <img src="data:image/jpeg;base64,{b64_image}" style="width:100%; max-width:400px; border-radius:18px; box-shadow:0 8px 25px rgba(0,0,0,0.15);" />
149
+ <br/>
150
+ <a href="data:image/jpeg;base64,{b64_image}" download="Genisi_Flux_Art.jpg" style="display:inline-block; margin-top:12px; padding:10px 20px; background:linear-gradient(135deg, #4f8ef7, #7c5cf7); color:#fff; border-radius:25px; text-decoration:none; font-weight:600; font-family:'Cairo', sans-serif;">⬇️ تحميل الصورة</a>
151
+ <p style="font-size:0.85rem; color:#9aa3be; margin-top:8px;">✅ تم التصميم بنجاح (المتبقي لك هذه الساعة: {rem}/3 صور)</p>
152
+ </div>'''
153
+ yield html_response
154
+ except Exception as e:
155
+ yield f"⚠️ **خطأ أثناء توليد الصورة:**\n`{str(e)}`"
156
+
157
+ return StreamingResponse(generate_image_stream(), media_type="text/plain")
158
+
159
+ # --- 3. توليد النصوص (مع دعم Flash و Pro) ---
160
+ attempts = 0
161
+ while attempts < len(text_clients):
162
+ try:
163
+ client = get_text_client()
164
+ contents = []
165
+
166
+ # بناء التاريخ
167
+ for entry in request.history:
168
+ if entry.get('user') and str(entry['user']).strip():
169
+ contents.append(types.Content(role="user", parts=[types.Part.from_text(text=str(entry['user']).strip())]))
170
+ if entry.get('bot') and str(entry['bot']).strip():
171
+ bot_txt = entry['bot']
172
+ if '<div style="text-align:center;' in bot_txt:
173
+ bot_txt = "[صورة تم توليدها مسبقاً]"
174
+ contents.append(types.Content(role="model", parts=[types.Part.from_text(text=bot_txt.strip())]))
175
+
176
+ # بناء أجزاء المستخدم
177
+ user_parts = []
178
+ has_images = False
179
+
180
+ if request.files:
181
+ for f in request.files:
182
+ try:
183
+ file_bytes = base64.b64decode(f.data)
184
+ user_parts.append(types.Part.from_bytes(data=file_bytes, mime_type=f.mime_type))
185
+ if f.mime_type.startswith('image/'):
186
+ has_images = True
187
+ except Exception:
188
+ pass
189
+
190
+ msg_text = request.message.strip()
191
+ if msg_text:
192
+ user_parts.append(types.Part.from_text(text=msg_text))
193
+ elif not msg_text and request.files:
194
+ user_parts.append(types.Part.from_text(text="يرجى تحليل المرفقات."))
195
+ else:
196
+ user_parts.append(types.Part.from_text(text="مرحبا"))
197
+
198
+ contents.append(types.Content(role="user", parts=user_parts))
199
+
200
+ tools = [types.Tool(googleSearch=types.GoogleSearch())]
201
+
202
+ # 🌟 اختيار النموذج والإعدادات حسب اختيار المستخدم
203
+ if current_model == "pro":
204
+ # نموذج Pro: تفكير عميق (HIGH)
205
+ chosen_model = "gemma-4-31b-it"
206
+ thinking_level = "HIGH"
207
+ temperature = 1.0 # إبداع أعلى
208
+ else:
209
+ # نموذج Flash: تفكير سريع (MINIMAL)
210
+ chosen_model = "gemma-4-31b-it"
211
+ thinking_level = "MINIMAL"
212
+ temperature = 0.7 # أكثر دقة وسرعة
213
+
214
+ # إذا كان هناك صور، نستخدم نموذج يدعم الرؤية
215
+ if has_images:
216
+ chosen_model = "gemma-4-31b-it"
217
+
218
+ config = types.GenerateContentConfig(
219
+ temperature=temperature,
220
+ thinking_config=types.ThinkingConfig(
221
+ thinking_level=thinking_level
222
+ ),
223
+ media_resolution="MEDIA_RESOLUTION_HIGH" if has_images else None,
224
+ tools=tools,
225
+ system_instruction=[types.Part.from_text(text=SYSTEM_INSTRUCTION)]
226
+ )
227
+
228
+ stream = client.models.generate_content_stream(
229
+ model=chosen_model,
230
+ contents=contents,
231
+ config=config
232
+ )
233
+
234
+ def stream_generator():
235
+ try:
236
+ is_first_chunk = True
237
+ thinking_buffer = ""
238
+
239
+ for chunk in stream:
240
+ if chunk.text:
241
+ text_chunk = chunk.text
242
+
243
+ # 🌟 لنموذج Pro: نجمع التفكير ثم نضيف فاصل " instant"
244
+ if current_model == "pro":
245
+ thinking_buffer += text_chunk
246
+
247
+ # ننتظر حتى يكتمل التفكير (عندما نرى أن النموذج بدأ في الرد الفعلي)
248
+ # نستخدم علامة "instant" للفصل بين التفكير والرد
249
+ if is_first_chunk:
250
+ yield thinking_buffer + " instant"
251
+ is_first_chunk = False
252
+ else:
253
+ yield text_chunk
254
+ else:
255
+ # نموذج Flash: إرسال مباشر
256
+ yield text_chunk
257
+
258
+ # 🌟 معالجة أفكار النموذج (thoughts) إذا كانت متاحة
259
+ if hasattr(chunk, 'thought') and chunk.thought:
260
+ # يمكن إرسال الأفكار كجزء من التفكير
261
+ if current_model == "pro":
262
+ yield f"[تفكير: {chunk.thought}]"
263
+
264
+ except Exception as stream_err:
265
+ yield f"\n\n⚠️ **خطأ:** `{str(stream_err)}`"
266
+
267
+ return StreamingResponse(stream_generator(), media_type="text/plain")
268
+
269
+ except Exception as e:
270
+ error_msg = str(e).lower()
271
+ if "429" in error_msg or "quota" in error_msg:
272
+ CURRENT_TEXT_KEY_INDEX = (CURRENT_TEXT_KEY_INDEX + 1) % len(text_clients)
273
+ attempts += 1
274
+ else:
275
+ def err_gen(): yield f"⚠️ **خطأ في النموذج اللغوي:** {str(e)}"
276
+ return StreamingResponse(err_gen(), media_type="text/plain")
277
+
278
+ def limit_gen(): yield "⚠️ تم الوصول للحد الأقصى لجميع المفاتيح."
279
+ return StreamingResponse(limit_gen(), media_type="text/plain")
280
+
281
+ app.mount("/", StaticFiles(directory=".", html=True), name="static")
282
+
283
+ if __name__ == "__main__":
284
+ import uvicorn
285
+ uvicorn.run(app, host="0.0.0.0", port=8000)