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

Update main.py

Browse files
Files changed (1) hide show
  1. main.py +47 -225
main.py CHANGED
@@ -1,226 +1,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
-
36
- def get_text_client():
37
- global CURRENT_TEXT_KEY_INDEX
38
- if not text_clients:
39
- raise ValueError("لم يتم العثور على مفاتيح النصوص.")
40
- return text_clients[CURRENT_TEXT_KEY_INDEX]
41
-
42
- @app.post("/chat")
43
- async def chat_endpoint(request: ChatRequest):
44
- global CURRENT_TEXT_KEY_INDEX
45
- msg_lower = request.message.strip().lower()
46
 
47
- # --- 2. طلبات الصور (NVIDIA Flux) ---
48
- image_triggers = ["ارسم", "صمم", "تخيل", "صورة ل", "draw", "generate", "imagine", "create", "عدل", "edit", "تصميم"]
49
- is_image_request = any(msg_lower.startswith(trigger) for trigger in image_triggers)
50
-
51
- if is_image_request:
52
- def generate_image_stream():
53
- current_hour = datetime.now().strftime("%Y-%m-%d %H")
54
- user_info = user_image_limits[request.user_id]
55
-
56
- if user_info.get("hour") != current_hour:
57
- user_info["hour"] = current_hour
58
- user_info["count"] = 0
59
-
60
- if user_info["count"] >= 3:
61
- yield "⚠️ **عذراً!** لقد استنفدت رصيدك الحالي لتوليد الصور (3 صور في الساعة). يرجى المحاولة لاحقاً! 🕒"
62
- return
63
-
64
- if not NVIDIA_API_KEY:
65
- yield "⚠️ **خطأ في السيرفر:** مفتاح `NVIDIA_API_KEY` غير موجود."
66
- return
67
-
68
- try:
69
- # ترجمة الطلب لـ English لضمان جودة الصور
70
- client = get_text_client()
71
- translation_response = client.models.generate_content(
72
- model="gemma-4-31b-it",
73
- contents=f"Translate this prompt to English for an image generator. Only return the English prompt: {request.message}"
74
- )
75
- final_prompt = translation_response.text.strip()
76
-
77
- invoke_url = "https://ai.api.nvidia.com/v1/genai/black-forest-labs/flux.2-klein-4b"
78
- headers = {
79
- "Authorization": f"Bearer {NVIDIA_API_KEY}",
80
- "Accept": "application/json",
81
- }
82
-
83
- payload = {
84
- "prompt": final_prompt,
85
- "width": 1024,
86
- "height": 1024,
87
- "seed": 0,
88
- "steps": 4
89
- }
90
-
91
- if request.files:
92
- img_file = next((f for f in request.files if f.mime_type.startswith("image/")), None)
93
- if img_file:
94
- payload["image"] = [f"data:{img_file.mime_type};base64,{img_file.data}"]
95
-
96
- response = requests.post(invoke_url, headers=headers, json=payload, timeout=60)
97
-
98
- if not response.ok:
99
- yield f"⚠️ **خطأ من سيرفر NVIDIA:**\n`{response.text}`"
100
- return
101
-
102
- response_body = response.json()
103
-
104
- def extract_img(obj):
105
- if isinstance(obj, dict):
106
- for k, v in obj.items():
107
- if k in ['b64_json', 'base64', 'image'] and isinstance(v, str) and len(v) > 100:
108
- return v.split(",", 1)[-1] if v.startswith("data:") else v
109
- elif k == 'url' and isinstance(v, str) and v.startswith('http'):
110
- return base64.b64encode(requests.get(v).content).decode('utf-8')
111
- else:
112
- res = extract_img(v)
113
- if res: return res
114
- elif isinstance(obj, list):
115
- for item in obj:
116
- res = extract_img(item)
117
- if res: return res
118
- return None
119
-
120
- b64_image = extract_img(response_body)
121
-
122
- if not b64_image:
123
- yield f"⚠️ **رد غير متوقع من الخادم (لم يتم العثور على الصورة):**"
124
- return
125
-
126
- user_info["count"] += 1
127
- rem = 3 - user_info["count"]
128
-
129
- html_response = f'''
130
- <div style="text-align:center; margin: 15px 0;">
131
- <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);" />
132
- <br/>
133
- <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, var(--accent), var(--accent2)); color:#fff; border-radius:25px; text-decoration:none; font-weight:600; font-family:'Cairo', sans-serif;">⬇️ تحميل الصورة</a>
134
- <p style="font-size:0.85rem; color:var(--text3); margin-top:8px;">✅ تم التصميم بنجاح (المتبقي لك هذه الساعة: {rem}/3 صور)</p>
135
- </div>'''
136
- yield html_response
137
- except Exception as e:
138
- yield f"⚠️ **خطأ أثناء توليد الصورة:**\n`{str(e)}`"
139
-
140
- return StreamingResponse(generate_image_stream(), media_type="text/plain")
141
-
142
- # --- 3. توليد النصوص وتحليل الملفات لحظياً ⚡ ---
143
- attempts = 0
144
- while attempts < len(text_clients):
145
- try:
146
- client = get_text_client()
147
- contents = []
148
-
149
- for entry in request.history:
150
- if entry.get('user') and str(entry['user']).strip():
151
- contents.append(types.Content(role="user", parts=[types.Part.from_text(text=str(entry['user']).strip())]))
152
- if entry.get('bot') and str(entry['bot']).strip():
153
- bot_txt = entry['bot']
154
- if '<div style="text-align:center;' in bot_txt:
155
- bot_txt = "[صورة تم توليدها مسبقاً]"
156
- contents.append(types.Content(role="model", parts=[types.Part.from_text(text=bot_txt.strip())]))
157
-
158
- user_parts = []
159
- has_images = False
160
-
161
- if request.files:
162
- for f in request.files:
163
- try:
164
- file_bytes = base64.b64decode(f.data)
165
- user_parts.append(types.Part.from_bytes(data=file_bytes, mime_type=f.mime_type))
166
- if f.mime_type.startswith('image/'):
167
- has_images = True
168
- except Exception:
169
- pass
170
-
171
- msg_text = request.message.strip()
172
- if msg_text:
173
- user_parts.append(types.Part.from_text(text=msg_text))
174
- elif not msg_text and request.files:
175
- user_parts.append(types.Part.from_text(text="يرجى تحليل المرفقات."))
176
- else:
177
- user_parts.append(types.Part.from_text(text="مرحبا"))
178
-
179
- contents.append(types.Content(role="user", parts=user_parts))
180
-
181
- tools = [types.Tool(googleSearch=types.GoogleSearch())]
182
- system_instruction = """انت Genisi نموذج ذكاء اصطناعي متطور من قبل AnesNT
183
- AnesNT مبادرة جزائرية من ولاية باتنة صاحبها انس كامش باللاتينية Anes Kameche وهو حاليا العضو الوحيد في هذي المبادرة وهي مبادرة تكنولوجية
184
- كن مفيدا وصديقا للمستخدم تحدث باللغة التي تحدث بها المستخدم"""
185
-
186
- chosen_model = "gemma-4-31b-it" if has_images else "gemma-4-31b-it"
187
-
188
- # تم دمج thinking_config هنا
189
- config = types.GenerateContentConfig(
190
- temperature=0.7,
191
- thinking_config=types.ThinkingConfig(
192
- thinking_level="MINIMAL"
193
- ),
194
- tools=tools,
195
- system_instruction=[types.Part.from_text(text=system_instruction)]
196
- )
197
-
198
- stream = client.models.generate_content_stream(
199
- model=chosen_model,
200
- contents=contents,
201
- config=config
202
- )
203
-
204
- def stream_generator():
205
- try:
206
- for chunk in stream:
207
- if chunk.text:
208
- yield chunk.text
209
- except Exception as stream_err:
210
- yield f"\n\n⚠️ **خطأ:** `{str(stream_err)}`"
211
-
212
- return StreamingResponse(stream_generator(), media_type="text/plain")
213
-
214
- except Exception as e:
215
- error_msg = str(e).lower()
216
- if "429" in error_msg or "quota" in error_msg:
217
- CURRENT_TEXT_KEY_INDEX = (CURRENT_TEXT_KEY_INDEX + 1) % len(text_clients)
218
- attempts += 1
219
- else:
220
- def err_gen(): yield f"⚠️ **خطأ في النموذج اللغوي:** {str(e)}"
221
- return StreamingResponse(err_gen(), media_type="text/plain")
222
-
223
- def limit_gen(): yield "⚠️ تم الوصول للحد الأقصى لجميع المفاتيح."
224
- return StreamingResponse(limit_gen(), media_type="text/plain")
225
-
226
- app.mount("/", StaticFiles(directory=".", html=True), name="static")
 
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
+ }