AnesKAM commited on
Commit
5f5701e
·
verified ·
1 Parent(s): 628022f

Update main.py

Browse files
Files changed (1) hide show
  1. main.py +250 -198
main.py CHANGED
@@ -12,7 +12,9 @@ from google.genai import types
12
 
13
  app = FastAPI()
14
 
15
- # --- إعدادات المفاتيح ---
 
 
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", "")
@@ -22,6 +24,9 @@ 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
@@ -32,15 +37,11 @@ class ChatRequest(BaseModel):
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
- # --- تعليمات النظام ---
44
  SYSTEM_INSTRUCTION = """أنت Genisi، نموذج ذكاء اصطناعي متطور من مبادرة AnesNT.
45
 
46
  **معلومات عن AnesNT:**
@@ -54,221 +55,265 @@ SYSTEM_INSTRUCTION = """أنت Genisi، نموذج ذكاء اصطناعي مت
54
  3. استخدم البحث من Google عند الحاجة.
55
  4. استخدم الإيموجي بشكل مناسب."""
56
 
57
- @app.post("/chat")
58
- async def chat_endpoint(request: ChatRequest):
 
 
 
59
  global CURRENT_TEXT_KEY_INDEX
60
- msg_lower = request.message.strip().lower()
61
- current_model = request.model
 
 
 
 
 
 
62
 
63
- # --- طلبات الصور (NVIDIA Flux) ---
64
- image_triggers = ["ارسم", "صمم", "تخيل", "صورة ل", "draw", "generate", "imagine", "create", "عدل", "edit", "تصميم"]
65
- is_image_request = any(msg_lower.startswith(trigger) for trigger in image_triggers)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
66
 
67
- if is_image_request:
68
- def generate_image_stream():
69
- current_hour = datetime.now().strftime("%Y-%m-%d %H")
70
- user_info = user_image_limits[request.user_id]
71
-
72
- if user_info.get("hour") != current_hour:
73
- user_info["hour"] = current_hour
74
- user_info["count"] = 0
75
 
76
- if user_info["count"] >= 3:
77
- yield "⚠️ **عذراً!** لقد استنفدت رصيدك الحالي لتوليد الصور (3 صور في الساعة). يرجى المحاولة لاحقاً! 🕒"
78
- return
79
-
80
- if not NVIDIA_API_KEY:
81
- yield "⚠️ **خطأ في السيرفر:** مفتاح `NVIDIA_API_KEY` غير موجود."
82
- return
83
-
84
- try:
85
- client = get_text_client()
86
- translation_response = client.models.generate_content(
87
- model="gemma-4-31b-it",
88
- contents=f"Translate this prompt to English for an image generator. Only return the English prompt: {request.message}"
89
- )
90
- final_prompt = translation_response.text.strip()
 
 
 
 
 
 
 
 
91
 
92
- invoke_url = "https://ai.api.nvidia.com/v1/genai/black-forest-labs/flux.2-klein-4b"
93
- headers = {
94
- "Authorization": f"Bearer {NVIDIA_API_KEY}",
95
- "Accept": "application/json",
96
- }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
97
 
98
- payload = {
99
- "prompt": final_prompt,
100
- "width": 1024,
101
- "height": 1024,
102
- "seed": 0,
103
- "steps": 4
104
- }
105
 
106
- if request.files:
107
- img_file = next((f for f in request.files if f.mime_type.startswith("image/")), None)
108
- if img_file:
109
- payload["image"] = [f"data:{img_file.mime_type};base64,{img_file.data}"]
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
110
 
111
- response = requests.post(invoke_url, headers=headers, json=payload, timeout=60)
112
-
113
- if not response.ok:
114
- yield f"⚠️ **خطأ من سيرفر NVIDIA:**\n`{response.text}`"
115
- return
 
 
 
 
 
 
 
116
 
117
- response_body = response.json()
118
-
119
- def extract_img(obj):
120
- if isinstance(obj, dict):
121
- for k, v in obj.items():
122
- if k in ['b64_json', 'base64', 'image'] and isinstance(v, str) and len(v) > 100:
123
- return v.split(",", 1)[-1] if v.startswith("data:") else v
124
- elif k == 'url' and isinstance(v, str) and v.startswith('http'):
125
- return base64.b64encode(requests.get(v).content).decode('utf-8')
126
- else:
127
- res = extract_img(v)
128
- if res: return res
129
- elif isinstance(obj, list):
130
- for item in obj:
131
- res = extract_img(item)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
132
  if res: return res
133
- return None
134
-
135
- b64_image = extract_img(response_body)
 
 
136
 
137
- if not b64_image:
138
- yield f"⚠️ **رد غير متوقع من الخادم (لم يتم العثور على الصورة):**"
139
- return
 
 
140
 
141
- user_info["count"] += 1
142
- rem = 3 - user_info["count"]
143
-
144
- html_response = f'''
145
  <div style="text-align:center; margin: 15px 0;">
146
  <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);" />
147
  <br/>
148
  <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>
149
  <p style="font-size:0.85rem; color:#9aa3be; margin-top:8px;">✅ تم التصميم بنجاح (المتبقي لك هذه الساعة: {rem}/3 صور)</p>
150
  </div>'''
151
- yield html_response
152
- except Exception as e:
153
- yield f"⚠️ **خطأ أثناء توليد الصورة:**\n`{str(e)}`"
154
-
155
- return StreamingResponse(generate_image_stream(), media_type="text/plain")
 
 
 
 
 
 
 
 
 
 
 
 
 
 
156
 
157
- # --- توليد النصوص ---
158
  attempts = 0
159
  while attempts < len(text_clients):
160
  try:
161
  client = get_text_client()
162
- contents = []
163
-
164
- # بناء التاريخ
165
- for entry in request.history:
166
- if entry.get('user') and str(entry['user']).strip():
167
- contents.append(types.Content(role="user", parts=[types.Part.from_text(text=str(entry['user']).strip())]))
168
- if entry.get('bot') and str(entry['bot']).strip():
169
- bot_txt = entry['bot']
170
- if '<div style="text-align:center;' in bot_txt:
171
- bot_txt = "[صورة تم توليدها مسبقاً]"
172
- contents.append(types.Content(role="model", parts=[types.Part.from_text(text=bot_txt.strip())]))
173
-
174
- # أجزاء المستخدم
175
- user_parts = []
176
- has_images = False
177
 
178
- if request.files:
179
- for f in request.files:
180
- try:
181
- file_bytes = base64.b64decode(f.data)
182
- user_parts.append(types.Part.from_bytes(data=file_bytes, mime_type=f.mime_type))
183
- if f.mime_type.startswith('image/'):
184
- has_images = True
185
- except Exception:
186
- pass
187
-
188
- msg_text = request.message.strip()
189
- if msg_text:
190
- user_parts.append(types.Part.from_text(text=msg_text))
191
- elif not msg_text and request.files:
192
- user_parts.append(types.Part.from_text(text="يرجى تحليل المرفقات."))
193
- else:
194
- user_parts.append(types.Part.from_text(text="مرحبا"))
195
-
196
- contents.append(types.Content(role="user", parts=user_parts))
197
 
 
 
198
  tools = [types.Tool(googleSearch=types.GoogleSearch())]
199
- chosen_model = "gemma-4-31b-it"
200
-
201
- # --- 🌟 منطق Pro: تفكير منفصل ثم رد نهائي مع علامة " instant" ---
202
- if current_model == "pro":
203
- # المرحلة 1: توليد التفكير (بدون streaming)
204
- thinking_config = types.GenerateContentConfig(
205
- temperature=1.0,
206
- thinking_config=types.ThinkingConfig(thinking_level="HIGH"),
207
- tools=tools,
208
- system_instruction=[types.Part.from_text(text=SYSTEM_INSTRUCTION)]
209
- )
210
- thinking_response = client.models.generate_content(
211
- model=chosen_model,
212
- contents=contents,
213
- config=thinking_config
214
- )
215
- thinking_text = thinking_response.text.strip()
216
-
217
- # المرحلة 2: توليد الرد النهائي (مع streaming)
218
- # نضيف التفكير كجزء من السياق عشان النموذج يعرف إنو خلاص فكر
219
- final_prompt = f"[لقد فكرت مسبقاً بما يلي]\n{thinking_text}\n\n[الآن قدم ردك النهائي المباشر بناءً على هذا التفكير]"
220
- # نضيفه كجزء من محتوى المستخدم الأخير (أو نضيف Content جديد)
221
- final_contents = contents.copy()
222
- final_contents.append(types.Content(role="user", parts=[types.Part.from_text(text=final_prompt)]))
223
-
224
- final_config = types.GenerateContentConfig(
225
- temperature=0.7,
226
- thinking_config=types.ThinkingConfig(thinking_level="MINIMAL"), # بدون تفكير إضافي
227
- tools=tools,
228
- system_instruction=[types.Part.from_text(text=SYSTEM_INSTRUCTION)]
229
- )
230
-
231
- stream = client.models.generate_content_stream(
232
- model=chosen_model,
233
- contents=final_contents,
234
- config=final_config
235
- )
236
-
237
- def stream_generator():
238
- # أولاً نرسل التفكير + علامة الفصل
239
- yield thinking_text + " instant"
240
- # ثم نرسل الرد النهائي
241
- try:
242
- for chunk in stream:
243
- if chunk.text:
244
- yield chunk.text
245
- except Exception as e:
246
- yield f"\n\n⚠️ **خطأ:** `{str(e)}`"
247
-
248
- return StreamingResponse(stream_generator(), media_type="text/plain")
249
 
250
- # --- 🌟 منطق Flash: عادي مباشر ---
 
 
251
  else:
252
- config = types.GenerateContentConfig(
253
- temperature=0.7,
254
- thinking_config=types.ThinkingConfig(thinking_level="MINIMAL"),
255
- tools=tools,
256
- system_instruction=[types.Part.from_text(text=SYSTEM_INSTRUCTION)]
257
- )
258
- stream = client.models.generate_content_stream(
259
- model=chosen_model,
260
- contents=contents,
261
- config=config
262
- )
263
- def stream_generator():
264
- try:
265
- for chunk in stream:
266
- if chunk.text:
267
- yield chunk.text
268
- except Exception as e:
269
- yield f"\n\n⚠️ **خطأ:** `{str(e)}`"
270
- return StreamingResponse(stream_generator(), media_type="text/plain")
271
-
272
  except Exception as e:
273
  error_msg = str(e).lower()
274
  if "429" in error_msg or "quota" in error_msg:
@@ -277,8 +322,15 @@ async def chat_endpoint(request: ChatRequest):
277
  else:
278
  def err_gen(): yield f"⚠️ **خطأ في النموذج:** {str(e)}"
279
  return StreamingResponse(err_gen(), media_type="text/plain")
280
-
281
  def limit_gen(): yield "⚠️ تم الوصول للحد الأقصى لجميع المفاتيح."
282
  return StreamingResponse(limit_gen(), media_type="text/plain")
283
 
284
- app.mount("/", StaticFiles(directory=".", html=True), name="static")
 
 
 
 
 
 
 
 
12
 
13
  app = FastAPI()
14
 
15
+ # ============================================================
16
+ # إعدادات المفاتيح
17
+ # ============================================================
18
  TEXT_KEYS_RAW = os.environ.get("TEXT_API_KEYS", os.environ.get("GEMINI_API_KEY", ""))
19
  TEXT_API_KEYS = [k.strip() for k in TEXT_KEYS_RAW.split(",") if k.strip()]
20
  NVIDIA_API_KEY = os.environ.get("NVIDIA_API_KEY", "")
 
24
 
25
  user_image_limits = defaultdict(lambda: {"hour": datetime.now().strftime("%Y-%m-%d %H"), "count": 0})
26
 
27
+ # ============================================================
28
+ # النماذج
29
+ # ============================================================
30
  class FileData(BaseModel):
31
  mime_type: str
32
  data: str
 
37
  message: str
38
  history: list
39
  files: list[FileData] = []
40
+ model: str = "flash"
41
 
42
+ # ============================================================
43
+ # تعليمات النظام
44
+ # ============================================================
 
 
 
 
45
  SYSTEM_INSTRUCTION = """أنت Genisi، نموذج ذكاء اصطناعي متطور من مبادرة AnesNT.
46
 
47
  **معلومات عن AnesNT:**
 
55
  3. استخدم البحث من Google عند الحاجة.
56
  4. استخدم الإيموجي بشكل مناسب."""
57
 
58
+ # ============================================================
59
+ # دوال مساعدة
60
+ # ============================================================
61
+ def get_text_client():
62
+ """الحصول على العميل الحالي مع تدوير المفاتيح عند الحاجة"""
63
  global CURRENT_TEXT_KEY_INDEX
64
+ if not text_clients:
65
+ raise ValueError("لم يتم العثور على مفاتيح النصوص.")
66
+ return text_clients[CURRENT_TEXT_KEY_INDEX]
67
+
68
+ def build_contents(history: list, message: str, files: list) -> tuple:
69
+ """بناء محتوى المحادثة للـ API"""
70
+ contents = []
71
+ has_images = False
72
 
73
+ # بناء التاريخ
74
+ for entry in history:
75
+ if entry.get('user') and str(entry['user']).strip():
76
+ contents.append(types.Content(role="user", parts=[types.Part.from_text(text=str(entry['user']).strip())]))
77
+ if entry.get('bot') and str(entry['bot']).strip():
78
+ bot_txt = entry['bot']
79
+ if '<div style="text-align:center;' in bot_txt:
80
+ bot_txt = "[صورة تم توليدها مسبقاً]"
81
+ contents.append(types.Content(role="model", parts=[types.Part.from_text(text=bot_txt.strip())]))
82
+
83
+ # بناء أجزاء المستخدم
84
+ user_parts = []
85
+ if files:
86
+ for f in files:
87
+ try:
88
+ file_bytes = base64.b64decode(f.data)
89
+ user_parts.append(types.Part.from_bytes(data=file_bytes, mime_type=f.mime_type))
90
+ if f.mime_type.startswith('image/'):
91
+ has_images = True
92
+ except Exception:
93
+ pass
94
+
95
+ msg_text = message.strip()
96
+ if msg_text:
97
+ user_parts.append(types.Part.from_text(text=msg_text))
98
+ elif not msg_text and files:
99
+ user_parts.append(types.Part.from_text(text="يرجى تحليل المرفقات."))
100
+ else:
101
+ user_parts.append(types.Part.from_text(text="مرحبا"))
102
+
103
+ contents.append(types.Content(role="user", parts=user_parts))
104
+ return contents, has_images
105
 
106
+ def get_base_config(temperature: float, thinking_level: str, tools: list):
107
+ """الحصول على إعدادات النموذج الأساسية"""
108
+ return types.GenerateContentConfig(
109
+ temperature=temperature,
110
+ thinking_config=types.ThinkingConfig(thinking_level=thinking_level),
111
+ tools=tools,
112
+ system_instruction=[types.Part.from_text(text=SYSTEM_INSTRUCTION)]
113
+ )
114
 
115
+ # ============================================================
116
+ # 🌟 دالة مستقلة لنموذج FLASH (سريع ومباشر)
117
+ # ============================================================
118
+ async def handle_flash_model(request: ChatRequest, client, contents: list, tools: list):
119
+ """نموذج Flash: استجابة سريعة ومباشرة"""
120
+ chosen_model = "gemma-4-31b-it"
121
+ config = get_base_config(temperature=0.7, thinking_level="MINIMAL", tools=tools)
122
+
123
+ stream = client.models.generate_content_stream(
124
+ model=chosen_model,
125
+ contents=contents,
126
+ config=config
127
+ )
128
+
129
+ def stream_generator():
130
+ try:
131
+ for chunk in stream:
132
+ if chunk.text:
133
+ yield chunk.text
134
+ except Exception as e:
135
+ yield f"\n\n⚠️ **خطأ:** `{str(e)}`"
136
+
137
+ return StreamingResponse(stream_generator(), media_type="text/plain")
138
 
139
+ # ============================================================
140
+ # 🌟 دالة مستقلة لنموذج PRO (تفكير عميق + رد نهائي)
141
+ # ============================================================
142
+ async def handle_pro_model(request: ChatRequest, client, contents: list, tools: list):
143
+ """نموذج Pro: تفكير منفصل داخل حاوية، ثم رد نهائي خارجها"""
144
+ chosen_model = "gemma-4-31b-it"
145
+
146
+ # ---------- المرحلة 1: توليد التفكير (بدون Streaming) ----------
147
+ thinking_config = get_base_config(temperature=1.0, thinking_level="HIGH", tools=tools)
148
+
149
+ thinking_response = client.models.generate_content(
150
+ model=chosen_model,
151
+ contents=contents,
152
+ config=thinking_config
153
+ )
154
+ thinking_text = thinking_response.text.strip() if thinking_response.text else ""
155
+
156
+ # ---------- المرحلة 2: توليد الرد النهائي (مع Streaming) ----------
157
+ # نضيف التفكير كجزء من السياق
158
+ final_prompt = f"""[لقد قمت بعملية تفكير عميق حول سؤال المستخدم، وهذا ما توصلت إليه:]
159
 
160
+ {thinking_text}
 
 
 
 
 
 
161
 
162
+ [الآن، بناءً على هذا التفكير، قدم رداً نهائياً مباشراً ومفيداً للمستخدم. لا تذكر أنك فكرت، فقط قدم الرد النهائي.]"""
163
+
164
+ final_contents = contents.copy()
165
+ final_contents.append(types.Content(role="user", parts=[types.Part.from_text(text=final_prompt)]))
166
+
167
+ final_config = get_base_config(temperature=0.7, thinking_level="MINIMAL", tools=tools)
168
+
169
+ stream = client.models.generate_content_stream(
170
+ model=chosen_model,
171
+ contents=final_contents,
172
+ config=final_config
173
+ )
174
+
175
+ def stream_generator():
176
+ # نرسل التفكير + علامة الفصل أولاً
177
+ yield thinking_text + " instant"
178
+
179
+ # ثم نرسل الرد النهائي
180
+ try:
181
+ for chunk in stream:
182
+ if chunk.text:
183
+ yield chunk.text
184
+ except Exception as e:
185
+ yield f"\n\n⚠️ **خطأ:** `{str(e)}`"
186
+
187
+ return StreamingResponse(stream_generator(), media_type="text/plain")
188
 
189
+ # ============================================================
190
+ # 🌟 دالة مستقلة لتوليد الصور
191
+ # ============================================================
192
+ async def handle_image_request(request: ChatRequest, client):
193
+ """معالجة طلبات توليد الصور باستخدام NVIDIA Flux"""
194
+ def generate_image_stream():
195
+ current_hour = datetime.now().strftime("%Y-%m-%d %H")
196
+ user_info = user_image_limits[request.user_id]
197
+
198
+ if user_info.get("hour") != current_hour:
199
+ user_info["hour"] = current_hour
200
+ user_info["count"] = 0
201
 
202
+ if user_info["count"] >= 3:
203
+ yield "⚠️ **عذراً!** لقد استنفدت رصيدك الحالي لتوليد الصور (3 صور في الساعة). يرجى المحاولة لاحقاً! 🕒"
204
+ return
205
+
206
+ if not NVIDIA_API_KEY:
207
+ yield "⚠️ **خطأ في السيرفر:** مفتاح `NVIDIA_API_KEY` غير موجود."
208
+ return
209
+
210
+ try:
211
+ # ترجمة الطلب للإنجليزية
212
+ translation_response = client.models.generate_content(
213
+ model="gemma-4-31b-it",
214
+ contents=f"Translate this prompt to English for an image generator. Only return the English prompt: {request.message}"
215
+ )
216
+ final_prompt = translation_response.text.strip()
217
+
218
+ invoke_url = "https://ai.api.nvidia.com/v1/genai/black-forest-labs/flux.2-klein-4b"
219
+ headers = {
220
+ "Authorization": f"Bearer {NVIDIA_API_KEY}",
221
+ "Accept": "application/json",
222
+ }
223
+
224
+ payload = {
225
+ "prompt": final_prompt,
226
+ "width": 1024,
227
+ "height": 1024,
228
+ "seed": 0,
229
+ "steps": 4
230
+ }
231
+
232
+ if request.files:
233
+ img_file = next((f for f in request.files if f.mime_type.startswith("image/")), None)
234
+ if img_file:
235
+ payload["image"] = [f"data:{img_file.mime_type};base64,{img_file.data}"]
236
+
237
+ response = requests.post(invoke_url, headers=headers, json=payload, timeout=60)
238
+
239
+ if not response.ok:
240
+ yield f"⚠️ **خطأ من سيرفر NVIDIA:**\n`{response.text}`"
241
+ return
242
+
243
+ response_body = response.json()
244
+
245
+ def extract_img(obj):
246
+ if isinstance(obj, dict):
247
+ for k, v in obj.items():
248
+ if k in ['b64_json', 'base64', 'image'] and isinstance(v, str) and len(v) > 100:
249
+ return v.split(",", 1)[-1] if v.startswith("data:") else v
250
+ elif k == 'url' and isinstance(v, str) and v.startswith('http'):
251
+ return base64.b64encode(requests.get(v).content).decode('utf-8')
252
+ else:
253
+ res = extract_img(v)
254
  if res: return res
255
+ elif isinstance(obj, list):
256
+ for item in obj:
257
+ res = extract_img(item)
258
+ if res: return res
259
+ return None
260
 
261
+ b64_image = extract_img(response_body)
262
+
263
+ if not b64_image:
264
+ yield f"⚠️ **رد غير متوقع من الخادم (لم يتم العثور على الصورة):**"
265
+ return
266
 
267
+ user_info["count"] += 1
268
+ rem = 3 - user_info["count"]
269
+
270
+ html_response = f'''
271
  <div style="text-align:center; margin: 15px 0;">
272
  <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);" />
273
  <br/>
274
  <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>
275
  <p style="font-size:0.85rem; color:#9aa3be; margin-top:8px;">✅ تم التصميم بنجاح (المتبقي لك هذه الساعة: {rem}/3 صور)</p>
276
  </div>'''
277
+ yield html_response
278
+ except Exception as e:
279
+ yield f"⚠️ **خطأ أثناء توليد الصورة:**\n`{str(e)}`"
280
+
281
+ return StreamingResponse(generate_image_stream(), media_type="text/plain")
282
+
283
+ # ============================================================
284
+ # نقطة النهاية الرئيسية
285
+ # ============================================================
286
+ @app.post("/chat")
287
+ async def chat_endpoint(request: ChatRequest):
288
+ global CURRENT_TEXT_KEY_INDEX
289
+
290
+ msg_lower = request.message.strip().lower()
291
+ current_model = request.model
292
+
293
+ # التحقق من طلبات الصور
294
+ image_triggers = ["ارسم", "صمم", "تخيل", "صورة ل", "draw", "generate", "imagine", "create", "عدل", "edit", "تصميم"]
295
+ is_image_request = any(msg_lower.startswith(trigger) for trigger in image_triggers)
296
 
297
+ # محاولة تنفيذ الطلب مع تدوير المفاتيح
298
  attempts = 0
299
  while attempts < len(text_clients):
300
  try:
301
  client = get_text_client()
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
302
 
303
+ # --- طلب صورة ---
304
+ if is_image_request:
305
+ return await handle_image_request(request, client)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
306
 
307
+ # --- طلب نصي ---
308
+ contents, _ = build_contents(request.history, request.message, request.files)
309
  tools = [types.Tool(googleSearch=types.GoogleSearch())]
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
310
 
311
+ # 🌟 توجيه إلى الدالة المناسبة حسب النموذج
312
+ if current_model == "pro":
313
+ return await handle_pro_model(request, client, contents, tools)
314
  else:
315
+ return await handle_flash_model(request, client, contents, tools)
316
+
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
317
  except Exception as e:
318
  error_msg = str(e).lower()
319
  if "429" in error_msg or "quota" in error_msg:
 
322
  else:
323
  def err_gen(): yield f"⚠️ **خطأ في النموذج:** {str(e)}"
324
  return StreamingResponse(err_gen(), media_type="text/plain")
325
+
326
  def limit_gen(): yield "⚠️ تم الوصول للحد الأقصى لجميع المفاتيح."
327
  return StreamingResponse(limit_gen(), media_type="text/plain")
328
 
329
+ # ============================================================
330
+ # تقديم الملفات الثابتة
331
+ # ============================================================
332
+ app.mount("/", StaticFiles(directory=".", html=True), name="static")
333
+
334
+ if __name__ == "__main__":
335
+ import uvicorn
336
+ uvicorn.run(app, host="0.0.0.0", port=8000)