Mark-Lasfar commited on
Commit
5e980fd
·
1 Parent(s): 6ddb9fe

Update Model

Browse files
Files changed (5) hide show
  1. api/auth.py +33 -8
  2. api/endpoints.py +258 -96
  3. static/js/chat.js +107 -92
  4. utils/generation.py +33 -28
  5. utils/web_search.py +5 -1
api/auth.py CHANGED
@@ -95,8 +95,21 @@ class UserManager(IntegerIDMixin, BaseUserManager[User, int]):
95
  logger.info(f"Found existing OAuth account for {oauth_name}, account_id: {account_id}")
96
  user = existing_oauth_account.user
97
  if user is None:
98
- logger.error(f"No user associated with OAuth account {account_id}")
99
- raise ValueError("No user associated with this OAuth account")
 
 
 
 
 
 
 
 
 
 
 
 
 
100
  logger.info(f"Returning existing user: {user.email}")
101
  return await self.on_after_login(user, request)
102
 
@@ -105,13 +118,25 @@ class UserManager(IntegerIDMixin, BaseUserManager[User, int]):
105
  statement = select(User).where(User.email == account_email)
106
  result = self.user_db.session.execute(statement)
107
  user = result.scalars().first()
108
- if user is not None:
109
- logger.info(f"Found existing user by email: {user.email}")
110
- oauth_account.user_id = user.id
111
- self.user_db.session.add(oauth_account)
 
 
 
 
 
 
 
112
  self.user_db.session.commit()
113
- logger.info(f"Associated OAuth account with user: {user.email}")
114
- return await self.on_after_login(user, request)
 
 
 
 
 
115
 
116
  # Create new user
117
  logger.info(f"Creating new user for email: {account_email}")
 
95
  logger.info(f"Found existing OAuth account for {oauth_name}, account_id: {account_id}")
96
  user = existing_oauth_account.user
97
  if user is None:
98
+ logger.error(f"No user associated with OAuth account {account_id}. Creating new user.")
99
+ # Create new user if user is None
100
+ user_dict = {
101
+ "email": account_email,
102
+ "hashed_password": self.password_helper.hash("dummy_password"),
103
+ "is_active": True,
104
+ "is_verified": is_verified_by_default,
105
+ }
106
+ user = User(**user_dict)
107
+ self.user_db.session.add(user)
108
+ self.user_db.session.commit()
109
+ self.user_db.session.refresh(user)
110
+ existing_oauth_account.user_id = user.id
111
+ self.user_db.session.commit()
112
+ logger.info(f"Created new user and linked to existing OAuth account: {user.email}")
113
  logger.info(f"Returning existing user: {user.email}")
114
  return await self.on_after_login(user, request)
115
 
 
118
  statement = select(User).where(User.email == account_email)
119
  result = self.user_db.session.execute(statement)
120
  user = result.scalars().first()
121
+ if user is None:
122
+ logger.error(f"No user found for email {account_email}. Creating new user.")
123
+ # Create new user if not found
124
+ user_dict = {
125
+ "email": account_email,
126
+ "hashed_password": self.password_helper.hash("dummy_password"),
127
+ "is_active": True,
128
+ "is_verified": is_verified_by_default,
129
+ }
130
+ user = User(**user_dict)
131
+ self.user_db.session.add(user)
132
  self.user_db.session.commit()
133
+ self.user_db.session.refresh(user)
134
+ logger.info(f"Created new user for email: {user.email}")
135
+ oauth_account.user_id = user.id
136
+ self.user_db.session.add(oauth_account)
137
+ self.user_db.session.commit()
138
+ logger.info(f"Associated OAuth account with user: {user.email}")
139
+ return await self.on_after_login(user, request)
140
 
141
  # Create new user
142
  logger.info(f"Creating new user for email: {account_email}")
api/endpoints.py CHANGED
@@ -8,14 +8,14 @@ from api.models import QueryRequest, ConversationOut, ConversationCreate, UserUp
8
  from api.auth import current_active_user
9
  from api.database import get_db
10
  from sqlalchemy.orm import Session
11
- from utils.generation import request_generation, select_model
12
  from utils.web_search import web_search
13
  import io
14
  from openai import OpenAI
15
  from motor.motor_asyncio import AsyncIOMotorClient
16
  from datetime import datetime
17
  import logging
18
- from typing import List
19
 
20
  router = APIRouter()
21
  logger = logging.getLogger(__name__)
@@ -31,9 +31,9 @@ if not BACKUP_HF_TOKEN:
31
  logger.warning("BACKUP_HF_TOKEN is not set. Fallback to secondary model will not work if primary token fails.")
32
 
33
  ROUTER_API_URL = os.getenv("ROUTER_API_URL", "https://router.huggingface.co")
34
- API_ENDPOINT = os.getenv("API_ENDPOINT", "https://api-inference.huggingface.co")
35
  FALLBACK_API_ENDPOINT = os.getenv("FALLBACK_API_ENDPOINT", "https://api-inference.huggingface.co")
36
- MODEL_NAME = os.getenv("MODEL_NAME", "openai/gpt-oss-120b") # بدون :cerebras
37
  SECONDARY_MODEL_NAME = os.getenv("SECONDARY_MODEL_NAME", "mistralai/Mixtral-8x7B-Instruct-v0.1")
38
  TERTIARY_MODEL_NAME = os.getenv("TERTIARY_MODEL_NAME", "Qwen/Qwen2.5-0.5B-Instruct")
39
  CLIP_BASE_MODEL = os.getenv("CLIP_BASE_MODEL", "Salesforce/blip-image-captioning-large")
@@ -85,6 +85,16 @@ async def handle_session(request: Request):
85
  )
86
  return session_id
87
 
 
 
 
 
 
 
 
 
 
 
88
  @router.get("/api/settings")
89
  async def get_settings(user: User = Depends(current_active_user)):
90
  if not user:
@@ -166,13 +176,18 @@ async def chat_endpoint(
166
  # Use user's preferred model if set
167
  preferred_model = user.preferred_model if user else None
168
  model_name, api_endpoint = select_model(req.message, input_type="text", preferred_model=preferred_model)
169
- system_prompt = req.system_prompt
170
- if user and user.additional_info:
171
- system_prompt = f"{system_prompt}\nUser Profile: {user.additional_info}\nConversation Style: {user.conversation_style or 'default'}"
 
 
 
 
 
172
 
173
  stream = request_generation(
174
- api_key=HF_TOKEN,
175
- api_base=api_endpoint,
176
  message=req.message,
177
  system_prompt=system_prompt,
178
  model_name=model_name,
@@ -183,19 +198,39 @@ async def chat_endpoint(
183
  input_type="text",
184
  output_format=req.output_format
185
  )
 
186
  if req.output_format == "audio":
187
  audio_chunks = []
188
- for chunk in stream:
189
- if isinstance(chunk, bytes):
190
- audio_chunks.append(chunk)
191
- audio_data = b"".join(audio_chunks)
192
- return StreamingResponse(io.BytesIO(audio_data), media_type="audio/wav")
 
 
 
 
 
 
 
 
 
 
193
  response_chunks = []
194
- for chunk in stream:
195
- if isinstance(chunk, str):
196
- response_chunks.append(chunk)
197
- response = "".join(response_chunks)
198
- logger.info(f"Chat response: {response}")
 
 
 
 
 
 
 
 
 
199
 
200
  if user and conversation:
201
  assistant_msg = Message(role="assistant", content=response, conversation_id=conversation.id)
@@ -244,12 +279,19 @@ async def audio_transcription_endpoint(
244
  db.commit()
245
 
246
  model_name, api_endpoint = select_model("transcribe audio", input_type="audio")
 
 
 
 
 
 
 
247
  audio_data = await file.read()
248
  stream = request_generation(
249
- api_key=HF_TOKEN,
250
- api_base=api_endpoint,
251
  message="Transcribe audio",
252
- system_prompt="Transcribe the provided audio using Whisper.",
253
  model_name=model_name,
254
  temperature=0.7,
255
  max_new_tokens=2048,
@@ -258,11 +300,20 @@ async def audio_transcription_endpoint(
258
  output_format="text"
259
  )
260
  response_chunks = []
261
- for chunk in stream:
262
- if isinstance(chunk, str):
263
- response_chunks.append(chunk)
264
- response = "".join(response_chunks)
265
- logger.info(f"Audio transcription response: {response}")
 
 
 
 
 
 
 
 
 
266
 
267
  if user and conversation:
268
  assistant_msg = Message(role="assistant", content=response, conversation_id=conversation.id)
@@ -290,12 +341,22 @@ async def text_to_speech_endpoint(
290
  await handle_session(request)
291
 
292
  text = req.get("text", "")
 
 
 
293
  model_name, api_endpoint = select_model("text to speech", input_type="tts")
 
 
 
 
 
 
 
294
  stream = request_generation(
295
- api_key=HF_TOKEN,
296
- api_base=api_endpoint,
297
  message=text,
298
- system_prompt="Convert the provided text to speech using a text-to-speech model.",
299
  model_name=model_name,
300
  temperature=0.7,
301
  max_new_tokens=2048,
@@ -303,11 +364,20 @@ async def text_to_speech_endpoint(
303
  output_format="audio"
304
  )
305
  audio_chunks = []
306
- for chunk in stream:
307
- if isinstance(chunk, bytes):
308
- audio_chunks.append(chunk)
309
- audio_data = b"".join(audio_chunks)
310
- return StreamingResponse(io.BytesIO(audio_data), media_type="audio/wav")
 
 
 
 
 
 
 
 
 
311
 
312
  @router.post("/api/code")
313
  async def code_endpoint(
@@ -323,16 +393,27 @@ async def code_endpoint(
323
  task = req.get("task")
324
  code = req.get("code", "")
325
  output_format = req.get("output_format", "text")
 
 
 
326
  prompt = f"Generate code for task: {task} using {framework}. Existing code: {code}"
327
  preferred_model = user.preferred_model if user else None
328
  model_name, api_endpoint = select_model(prompt, input_type="text", preferred_model=preferred_model)
329
- system_prompt = "You are a coding expert. Provide detailed, well-commented code with examples and explanations."
330
- if user and user.additional_info:
331
- system_prompt = f"{system_prompt}\nUser Profile: {user.additional_info}\nConversation Style: {user.conversation_style or 'default'}"
 
 
 
 
 
 
 
 
332
 
333
  stream = request_generation(
334
- api_key=HF_TOKEN,
335
- api_base=api_endpoint,
336
  message=prompt,
337
  system_prompt=system_prompt,
338
  model_name=model_name,
@@ -343,17 +424,36 @@ async def code_endpoint(
343
  )
344
  if output_format == "audio":
345
  audio_chunks = []
346
- for chunk in stream:
347
- if isinstance(chunk, bytes):
348
- audio_chunks.append(chunk)
349
- audio_data = b"".join(audio_chunks)
350
- return StreamingResponse(io.BytesIO(audio_data), media_type="audio/wav")
 
 
 
 
 
 
 
 
 
 
351
  response_chunks = []
352
- for chunk in stream:
353
- if isinstance(chunk, str):
354
- response_chunks.append(chunk)
355
- response = "".join(response_chunks)
356
- return {"generated_code": response}
 
 
 
 
 
 
 
 
 
357
 
358
  @router.post("/api/analysis")
359
  async def analysis_endpoint(
@@ -367,15 +467,26 @@ async def analysis_endpoint(
367
 
368
  message = req.get("text", "")
369
  output_format = req.get("output_format", "text")
 
 
 
370
  preferred_model = user.preferred_model if user else None
371
  model_name, api_endpoint = select_model(message, input_type="text", preferred_model=preferred_model)
372
- system_prompt = "You are an expert analyst. Provide detailed analysis with step-by-step reasoning and examples."
373
- if user and user.additional_info:
374
- system_prompt = f"{system_prompt}\nUser Profile: {user.additional_info}\nConversation Style: {user.conversation_style or 'default'}"
 
 
 
 
 
 
 
 
375
 
376
  stream = request_generation(
377
- api_key=HF_TOKEN,
378
- api_base=api_endpoint,
379
  message=message,
380
  system_prompt=system_prompt,
381
  model_name=model_name,
@@ -386,17 +497,36 @@ async def analysis_endpoint(
386
  )
387
  if output_format == "audio":
388
  audio_chunks = []
389
- for chunk in stream:
390
- if isinstance(chunk, bytes):
391
- audio_chunks.append(chunk)
392
- audio_data = b"".join(audio_chunks)
393
- return StreamingResponse(io.BytesIO(audio_data), media_type="audio/wav")
 
 
 
 
 
 
 
 
 
 
394
  response_chunks = []
395
- for chunk in stream:
396
- if isinstance(chunk, str):
397
- response_chunks.append(chunk)
398
- response = "".join(response_chunks)
399
- return {"analysis": response}
 
 
 
 
 
 
 
 
 
400
 
401
  @router.post("/api/image-analysis")
402
  async def image_analysis_endpoint(
@@ -430,14 +560,22 @@ async def image_analysis_endpoint(
430
 
431
  preferred_model = user.preferred_model if user else None
432
  model_name, api_endpoint = select_model("analyze image", input_type="image", preferred_model=preferred_model)
 
 
 
 
 
 
 
433
  image_data = await file.read()
434
- system_prompt = "You are an expert in image analysis. Provide detailed descriptions or classifications based on the query."
435
- if user and user.additional_info:
436
- system_prompt = f"{system_prompt}\nUser Profile: {user.additional_info}\nConversation Style: {user.conversation_style or 'default'}"
 
437
 
438
  stream = request_generation(
439
- api_key=HF_TOKEN,
440
- api_base=api_endpoint,
441
  message="Analyze this image",
442
  system_prompt=system_prompt,
443
  model_name=model_name,
@@ -449,36 +587,59 @@ async def image_analysis_endpoint(
449
  )
450
  if output_format == "audio":
451
  audio_chunks = []
452
- for chunk in stream:
453
- if isinstance(chunk, bytes):
454
- audio_chunks.append(chunk)
455
- audio_data = b"".join(audio_chunks)
456
- return StreamingResponse(io.BytesIO(audio_data), media_type="audio/wav")
457
- response_chunks = []
458
- for chunk in stream:
459
- if isinstance(chunk, str):
460
- response_chunks.append(chunk)
461
- response = "".join(response_chunks)
462
-
463
- if user and conversation:
464
- assistant_msg = Message(role="assistant", content=response, conversation_id=conversation.id)
465
- db.add(assistant_msg)
466
- db.commit()
467
- conversation.updated_at = datetime.utcnow()
468
- db.commit()
469
- return {
470
- "image_analysis": response,
471
- "conversation_id": conversation.conversation_id,
472
- "conversation_url": f"https://mgzon-mgzon-app.hf.space/chat/{conversation.conversation_id}",
473
- "conversation_title": conversation.title
474
- }
475
 
476
- return {"image_analysis": response}
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
477
 
478
  @router.get("/api/test-model")
479
  async def test_model(model: str = MODEL_NAME, endpoint: str = ROUTER_API_URL):
480
  try:
481
- _, api_key, selected_endpoint = check_model_availability(model, HF_TOKEN)
 
 
 
 
482
  client = OpenAI(api_key=api_key, base_url=selected_endpoint, timeout=60.0)
483
  response = client.chat.completions.create(
484
  model=model,
@@ -487,7 +648,8 @@ async def test_model(model: str = MODEL_NAME, endpoint: str = ROUTER_API_URL):
487
  )
488
  return {"status": "success", "response": response.choices[0].message.content}
489
  except Exception as e:
490
- return {"status": "error", "message": str(e)}
 
491
 
492
  @router.post("/api/conversations", response_model=ConversationOut)
493
  async def create_conversation(
 
8
  from api.auth import current_active_user
9
  from api.database import get_db
10
  from sqlalchemy.orm import Session
11
+ from utils.generation import request_generation, select_model, check_model_availability
12
  from utils.web_search import web_search
13
  import io
14
  from openai import OpenAI
15
  from motor.motor_asyncio import AsyncIOMotorClient
16
  from datetime import datetime
17
  import logging
18
+ from typing import List, Optional
19
 
20
  router = APIRouter()
21
  logger = logging.getLogger(__name__)
 
31
  logger.warning("BACKUP_HF_TOKEN is not set. Fallback to secondary model will not work if primary token fails.")
32
 
33
  ROUTER_API_URL = os.getenv("ROUTER_API_URL", "https://router.huggingface.co")
34
+ API_ENDPOINT = os.getenv("API_ENDPOINT", "https://api.cerebras.ai/v1") # تغيير الافتراضي لـ Cerebras
35
  FALLBACK_API_ENDPOINT = os.getenv("FALLBACK_API_ENDPOINT", "https://api-inference.huggingface.co")
36
+ MODEL_NAME = os.getenv("MODEL_NAME", "openai/gpt-oss-120b") # النموذج الرئيسي
37
  SECONDARY_MODEL_NAME = os.getenv("SECONDARY_MODEL_NAME", "mistralai/Mixtral-8x7B-Instruct-v0.1")
38
  TERTIARY_MODEL_NAME = os.getenv("TERTIARY_MODEL_NAME", "Qwen/Qwen2.5-0.5B-Instruct")
39
  CLIP_BASE_MODEL = os.getenv("CLIP_BASE_MODEL", "Salesforce/blip-image-captioning-large")
 
85
  )
86
  return session_id
87
 
88
+ # Helper function to enhance system prompt for Arabic language
89
+ def enhance_system_prompt(system_prompt: str, message: str, user: Optional[User] = None) -> str:
90
+ enhanced_prompt = system_prompt
91
+ # Check if the message is in Arabic
92
+ if any(0x0600 <= ord(char) <= 0x06FF for char in message):
93
+ enhanced_prompt += "\nRespond in Arabic with clear, concise, and accurate information tailored to the user's query."
94
+ if user and user.additional_info:
95
+ enhanced_prompt += f"\nUser Profile: {user.additional_info}\nConversation Style: {user.conversation_style or 'default'}"
96
+ return enhanced_prompt
97
+
98
  @router.get("/api/settings")
99
  async def get_settings(user: User = Depends(current_active_user)):
100
  if not user:
 
176
  # Use user's preferred model if set
177
  preferred_model = user.preferred_model if user else None
178
  model_name, api_endpoint = select_model(req.message, input_type="text", preferred_model=preferred_model)
179
+
180
+ # Check model availability
181
+ is_available, api_key, selected_endpoint = check_model_availability(model_name, HF_TOKEN)
182
+ if not is_available:
183
+ logger.error(f"Model {model_name} is not available at {api_endpoint}")
184
+ raise HTTPException(status_code=503, detail=f"Model {model_name} is not available. Please try another model.")
185
+
186
+ system_prompt = enhance_system_prompt(req.system_prompt, req.message, user)
187
 
188
  stream = request_generation(
189
+ api_key=api_key,
190
+ api_base=selected_endpoint,
191
  message=req.message,
192
  system_prompt=system_prompt,
193
  model_name=model_name,
 
198
  input_type="text",
199
  output_format=req.output_format
200
  )
201
+
202
  if req.output_format == "audio":
203
  audio_chunks = []
204
+ try:
205
+ for chunk in stream:
206
+ if isinstance(chunk, bytes):
207
+ audio_chunks.append(chunk)
208
+ else:
209
+ logger.warning(f"Unexpected non-bytes chunk in audio stream: {chunk}")
210
+ if not audio_chunks:
211
+ logger.error("No audio data generated.")
212
+ raise HTTPException(status_code=500, detail="No audio data generated.")
213
+ audio_data = b"".join(audio_chunks)
214
+ return StreamingResponse(io.BytesIO(audio_data), media_type="audio/wav")
215
+ except Exception as e:
216
+ logger.error(f"Audio generation failed: {e}")
217
+ raise HTTPException(status_code=500, detail=f"Audio generation failed: {str(e)}")
218
+
219
  response_chunks = []
220
+ try:
221
+ for chunk in stream:
222
+ if isinstance(chunk, str):
223
+ response_chunks.append(chunk)
224
+ else:
225
+ logger.warning(f"Unexpected non-string chunk in text stream: {chunk}")
226
+ response = "".join(response_chunks)
227
+ if not response.strip():
228
+ logger.error("Empty response generated.")
229
+ raise HTTPException(status_code=500, detail="Empty response generated from model.")
230
+ logger.info(f"Chat response: {response[:100]}...") # Log first 100 chars
231
+ except Exception as e:
232
+ logger.error(f"Chat generation failed: {e}")
233
+ raise HTTPException(status_code=500, detail=f"Chat generation failed: {str(e)}")
234
 
235
  if user and conversation:
236
  assistant_msg = Message(role="assistant", content=response, conversation_id=conversation.id)
 
279
  db.commit()
280
 
281
  model_name, api_endpoint = select_model("transcribe audio", input_type="audio")
282
+
283
+ # Check model availability
284
+ is_available, api_key, selected_endpoint = check_model_availability(model_name, HF_TOKEN)
285
+ if not is_available:
286
+ logger.error(f"Model {model_name} is not available at {api_endpoint}")
287
+ raise HTTPException(status_code=503, detail=f"Model {model_name} is not available. Please try another model.")
288
+
289
  audio_data = await file.read()
290
  stream = request_generation(
291
+ api_key=api_key,
292
+ api_base=selected_endpoint,
293
  message="Transcribe audio",
294
+ system_prompt="Transcribe the provided audio using Whisper. Ensure accurate transcription in the detected language.",
295
  model_name=model_name,
296
  temperature=0.7,
297
  max_new_tokens=2048,
 
300
  output_format="text"
301
  )
302
  response_chunks = []
303
+ try:
304
+ for chunk in stream:
305
+ if isinstance(chunk, str):
306
+ response_chunks.append(chunk)
307
+ else:
308
+ logger.warning(f"Unexpected non-string chunk in transcription stream: {chunk}")
309
+ response = "".join(response_chunks)
310
+ if not response.strip():
311
+ logger.error("Empty transcription generated.")
312
+ raise HTTPException(status_code=500, detail="Empty transcription generated from model.")
313
+ logger.info(f"Audio transcription response: {response[:100]}...")
314
+ except Exception as e:
315
+ logger.error(f"Audio transcription failed: {e}")
316
+ raise HTTPException(status_code=500, detail=f"Audio transcription failed: {str(e)}")
317
 
318
  if user and conversation:
319
  assistant_msg = Message(role="assistant", content=response, conversation_id=conversation.id)
 
341
  await handle_session(request)
342
 
343
  text = req.get("text", "")
344
+ if not text.strip():
345
+ raise HTTPException(status_code=400, detail="Text input is required for text-to-speech.")
346
+
347
  model_name, api_endpoint = select_model("text to speech", input_type="tts")
348
+
349
+ # Check model availability
350
+ is_available, api_key, selected_endpoint = check_model_availability(model_name, HF_TOKEN)
351
+ if not is_available:
352
+ logger.error(f"Model {model_name} is not available at {api_endpoint}")
353
+ raise HTTPException(status_code=503, detail=f"Model {model_name} is not available. Please try another model.")
354
+
355
  stream = request_generation(
356
+ api_key=api_key,
357
+ api_base=selected_endpoint,
358
  message=text,
359
+ system_prompt="Convert the provided text to speech using a text-to-speech model. Ensure clear and natural pronunciation, especially for Arabic text.",
360
  model_name=model_name,
361
  temperature=0.7,
362
  max_new_tokens=2048,
 
364
  output_format="audio"
365
  )
366
  audio_chunks = []
367
+ try:
368
+ for chunk in stream:
369
+ if isinstance(chunk, bytes):
370
+ audio_chunks.append(chunk)
371
+ else:
372
+ logger.warning(f"Unexpected non-bytes chunk in TTS stream: {chunk}")
373
+ if not audio_chunks:
374
+ logger.error("No audio data generated for TTS.")
375
+ raise HTTPException(status_code=500, detail="No audio data generated for text-to-speech.")
376
+ audio_data = b"".join(audio_chunks)
377
+ return StreamingResponse(io.BytesIO(audio_data), media_type="audio/wav")
378
+ except Exception as e:
379
+ logger.error(f"Text-to-speech generation failed: {e}")
380
+ raise HTTPException(status_code=500, detail=f"Text-to-speech generation failed: {str(e)}")
381
 
382
  @router.post("/api/code")
383
  async def code_endpoint(
 
393
  task = req.get("task")
394
  code = req.get("code", "")
395
  output_format = req.get("output_format", "text")
396
+ if not task:
397
+ raise HTTPException(status_code=400, detail="Task description is required.")
398
+
399
  prompt = f"Generate code for task: {task} using {framework}. Existing code: {code}"
400
  preferred_model = user.preferred_model if user else None
401
  model_name, api_endpoint = select_model(prompt, input_type="text", preferred_model=preferred_model)
402
+
403
+ # Check model availability
404
+ is_available, api_key, selected_endpoint = check_model_availability(model_name, HF_TOKEN)
405
+ if not is_available:
406
+ logger.error(f"Model {model_name} is not available at {api_endpoint}")
407
+ raise HTTPException(status_code=503, detail=f"Model {model_name} is not available. Please try another model.")
408
+
409
+ system_prompt = enhance_system_prompt(
410
+ "You are a coding expert. Provide detailed, well-commented code with examples and explanations.",
411
+ prompt, user
412
+ )
413
 
414
  stream = request_generation(
415
+ api_key=api_key,
416
+ api_base=selected_endpoint,
417
  message=prompt,
418
  system_prompt=system_prompt,
419
  model_name=model_name,
 
424
  )
425
  if output_format == "audio":
426
  audio_chunks = []
427
+ try:
428
+ for chunk in stream:
429
+ if isinstance(chunk, bytes):
430
+ audio_chunks.append(chunk)
431
+ else:
432
+ logger.warning(f"Unexpected non-bytes chunk in code audio stream: {chunk}")
433
+ if not audio_chunks:
434
+ logger.error("No audio data generated for code.")
435
+ raise HTTPException(status_code=500, detail="No audio data generated for code.")
436
+ audio_data = b"".join(audio_chunks)
437
+ return StreamingResponse(io.BytesIO(audio_data), media_type="audio/wav")
438
+ except Exception as e:
439
+ logger.error(f"Code audio generation failed: {e}")
440
+ raise HTTPException(status_code=500, detail=f"Code audio generation failed: {str(e)}")
441
+
442
  response_chunks = []
443
+ try:
444
+ for chunk in stream:
445
+ if isinstance(chunk, str):
446
+ response_chunks.append(chunk)
447
+ else:
448
+ logger.warning(f"Unexpected non-string chunk in code stream: {chunk}")
449
+ response = "".join(response_chunks)
450
+ if not response.strip():
451
+ logger.error("Empty code response generated.")
452
+ raise HTTPException(status_code=500, detail="Empty code response generated from model.")
453
+ return {"generated_code": response}
454
+ except Exception as e:
455
+ logger.error(f"Code generation failed: {e}")
456
+ raise HTTPException(status_code=500, detail=f"Code generation failed: {str(e)}")
457
 
458
  @router.post("/api/analysis")
459
  async def analysis_endpoint(
 
467
 
468
  message = req.get("text", "")
469
  output_format = req.get("output_format", "text")
470
+ if not message.strip():
471
+ raise HTTPException(status_code=400, detail="Text input is required for analysis.")
472
+
473
  preferred_model = user.preferred_model if user else None
474
  model_name, api_endpoint = select_model(message, input_type="text", preferred_model=preferred_model)
475
+
476
+ # Check model availability
477
+ is_available, api_key, selected_endpoint = check_model_availability(model_name, HF_TOKEN)
478
+ if not is_available:
479
+ logger.error(f"Model {model_name} is not available at {api_endpoint}")
480
+ raise HTTPException(status_code=503, detail=f"Model {model_name} is not available. Please try another model.")
481
+
482
+ system_prompt = enhance_system_prompt(
483
+ "You are an expert analyst. Provide detailed analysis with step-by-step reasoning and examples.",
484
+ message, user
485
+ )
486
 
487
  stream = request_generation(
488
+ api_key=api_key,
489
+ api_base=selected_endpoint,
490
  message=message,
491
  system_prompt=system_prompt,
492
  model_name=model_name,
 
497
  )
498
  if output_format == "audio":
499
  audio_chunks = []
500
+ try:
501
+ for chunk in stream:
502
+ if isinstance(chunk, bytes):
503
+ audio_chunks.append(chunk)
504
+ else:
505
+ logger.warning(f"Unexpected non-bytes chunk in analysis audio stream: {chunk}")
506
+ if not audio_chunks:
507
+ logger.error("No audio data generated for analysis.")
508
+ raise HTTPException(status_code=500, detail="No audio data generated for analysis.")
509
+ audio_data = b"".join(audio_chunks)
510
+ return StreamingResponse(io.BytesIO(audio_data), media_type="audio/wav")
511
+ except Exception as e:
512
+ logger.error(f"Analysis audio generation failed: {e}")
513
+ raise HTTPException(status_code=500, detail=f"Analysis audio generation failed: {str(e)}")
514
+
515
  response_chunks = []
516
+ try:
517
+ for chunk in stream:
518
+ if isinstance(chunk, str):
519
+ response_chunks.append(chunk)
520
+ else:
521
+ logger.warning(f"Unexpected non-string chunk in analysis stream: {chunk}")
522
+ response = "".join(response_chunks)
523
+ if not response.strip():
524
+ logger.error("Empty analysis response generated.")
525
+ raise HTTPException(status_code=500, detail="Empty analysis response generated from model.")
526
+ return {"analysis": response}
527
+ except Exception as e:
528
+ logger.error(f"Analysis generation failed: {e}")
529
+ raise HTTPException(status_code=500, detail=f"Analysis generation failed: {str(e)}")
530
 
531
  @router.post("/api/image-analysis")
532
  async def image_analysis_endpoint(
 
560
 
561
  preferred_model = user.preferred_model if user else None
562
  model_name, api_endpoint = select_model("analyze image", input_type="image", preferred_model=preferred_model)
563
+
564
+ # Check model availability
565
+ is_available, api_key, selected_endpoint = check_model_availability(model_name, HF_TOKEN)
566
+ if not is_available:
567
+ logger.error(f"Model {model_name} is not available at {api_endpoint}")
568
+ raise HTTPException(status_code=503, detail=f"Model {model_name} is not available. Please try another model.")
569
+
570
  image_data = await file.read()
571
+ system_prompt = enhance_system_prompt(
572
+ "You are an expert in image analysis. Provide detailed descriptions or classifications based on the query.",
573
+ "Analyze this image", user
574
+ )
575
 
576
  stream = request_generation(
577
+ api_key=api_key,
578
+ api_base=selected_endpoint,
579
  message="Analyze this image",
580
  system_prompt=system_prompt,
581
  model_name=model_name,
 
587
  )
588
  if output_format == "audio":
589
  audio_chunks = []
590
+ try:
591
+ for chunk in stream:
592
+ if isinstance(chunk, bytes):
593
+ audio_chunks.append(chunk)
594
+ else:
595
+ logger.warning(f"Unexpected non-bytes chunk in image analysis audio stream: {chunk}")
596
+ if not audio_chunks:
597
+ logger.error("No audio data generated for image analysis.")
598
+ raise HTTPException(status_code=500, detail="No audio data generated for image analysis.")
599
+ audio_data = b"".join(audio_chunks)
600
+ return StreamingResponse(io.BytesIO(audio_data), media_type="audio/wav")
601
+ except Exception as e:
602
+ logger.error(f"Image analysis audio generation failed: {e}")
603
+ raise HTTPException(status_code=500, detail=f"Image analysis audio generation failed: {str(e)}")
 
 
 
 
 
 
 
 
 
604
 
605
+ response_chunks = []
606
+ try:
607
+ for chunk in stream:
608
+ if isinstance(chunk, str):
609
+ response_chunks.append(chunk)
610
+ else:
611
+ logger.warning(f"Unexpected non-string chunk in image analysis stream: {chunk}")
612
+ response = "".join(response_chunks)
613
+ if not response.strip():
614
+ logger.error("Empty image analysis response generated.")
615
+ raise HTTPException(status_code=500, detail="Empty image analysis response generated from model.")
616
+
617
+ if user and conversation:
618
+ assistant_msg = Message(role="assistant", content=response, conversation_id=conversation.id)
619
+ db.add(assistant_msg)
620
+ db.commit()
621
+ conversation.updated_at = datetime.utcnow()
622
+ db.commit()
623
+ return {
624
+ "image_analysis": response,
625
+ "conversation_id": conversation.conversation_id,
626
+ "conversation_url": f"https://mgzon-mgzon-app.hf.space/chat/{conversation.conversation_id}",
627
+ "conversation_title": conversation.title
628
+ }
629
+
630
+ return {"image_analysis": response}
631
+ except Exception as e:
632
+ logger.error(f"Image analysis failed: {e}")
633
+ raise HTTPException(status_code=500, detail=f"Image analysis failed: {str(e)}")
634
 
635
  @router.get("/api/test-model")
636
  async def test_model(model: str = MODEL_NAME, endpoint: str = ROUTER_API_URL):
637
  try:
638
+ is_available, api_key, selected_endpoint = check_model_availability(model, HF_TOKEN)
639
+ if not is_available:
640
+ logger.error(f"Model {model} is not available at {endpoint}")
641
+ raise HTTPException(status_code=503, detail=f"Model {model} is not available.")
642
+
643
  client = OpenAI(api_key=api_key, base_url=selected_endpoint, timeout=60.0)
644
  response = client.chat.completions.create(
645
  model=model,
 
648
  )
649
  return {"status": "success", "response": response.choices[0].message.content}
650
  except Exception as e:
651
+ logger.error(f"Test model failed: {e}")
652
+ raise HTTPException(status_code=500, detail=f"Test model failed: {str(e)}")
653
 
654
  @router.post("/api/conversations", response_model=ConversationOut)
655
  async def create_conversation(
static/js/chat.js CHANGED
@@ -57,6 +57,11 @@ function autoResizeTextarea() {
57
  }
58
  }
59
 
 
 
 
 
 
60
  // Initialize page
61
  document.addEventListener('DOMContentLoaded', async () => {
62
  AOS.init({
@@ -98,9 +103,10 @@ function updateSendButtonState() {
98
  }
99
  }
100
 
101
- // Render markdown content
102
  function renderMarkdown(el) {
103
  const raw = el.dataset.text || '';
 
104
  const html = marked.parse(raw, {
105
  gfm: true,
106
  breaks: true,
@@ -108,7 +114,7 @@ function renderMarkdown(el) {
108
  smartypants: false,
109
  headerIds: false,
110
  });
111
- el.innerHTML = `<div class="md-content">${html}</div>`;
112
  const wrapper = el.querySelector('.md-content');
113
  wrapper.querySelectorAll('table').forEach(t => {
114
  if (!t.parentNode.classList?.contains('table-wrapper')) {
@@ -148,7 +154,7 @@ function leaveChatView() {
148
  // Add chat bubble
149
  function addMsg(who, text) {
150
  const div = document.createElement('div');
151
- div.className = `bubble ${who === 'user' ? 'bubble-user' : 'bubble-assist'}`;
152
  div.dataset.text = text;
153
  renderMarkdown(div);
154
  if (uiElements.chatBox) {
@@ -230,7 +236,7 @@ function startVoiceRecording() {
230
  mediaRecorder.addEventListener('dataavailable', event => audioChunks.push(event.data));
231
  }).catch(err => {
232
  console.error('Error accessing microphone:', err);
233
- alert('Failed to access microphone. Please check permissions.');
234
  isRecording = false;
235
  if (uiElements.sendBtn) uiElements.sendBtn.classList.remove('recording');
236
  });
@@ -253,8 +259,8 @@ function stopVoiceRecording() {
253
  // Send audio message
254
  async function submitAudioMessage(formData) {
255
  enterChatView();
256
- addMsg('user', 'Voice message');
257
- conversationHistory.push({ role: 'user', content: 'Voice message' });
258
  sessionStorage.setItem('conversationHistory', JSON.stringify(conversationHistory));
259
  streamMsg = addMsg('assistant', '');
260
  const loadingEl = document.createElement('span');
@@ -267,9 +273,12 @@ async function submitAudioMessage(formData) {
267
 
268
  try {
269
  const response = await sendRequest('/api/audio-transcription', formData);
 
 
 
270
  const data = await response.json();
271
- if (!data.transcription) throw new Error('No transcription received from server');
272
- const transcription = data.transcription || 'Error: No transcription generated.';
273
  if (streamMsg) {
274
  streamMsg.dataset.text = transcription;
275
  renderMarkdown(streamMsg);
@@ -282,7 +291,7 @@ async function submitAudioMessage(formData) {
282
  }
283
  if (checkAuth() && data.conversation_id) {
284
  currentConversationId = data.conversation_id;
285
- currentConversationTitle = data.conversation_title || 'Untitled Conversation';
286
  if (uiElements.conversationTitle) uiElements.conversationTitle.textContent = currentConversationTitle;
287
  history.pushState(null, '', `/chat/${currentConversationId}`);
288
  await loadConversations();
@@ -297,12 +306,35 @@ async function submitAudioMessage(formData) {
297
  async function sendRequest(endpoint, body, headers = {}) {
298
  const token = checkAuth();
299
  if (token) headers['Authorization'] = `Bearer ${token}`;
300
- return await fetch(endpoint, {
301
- method: 'POST',
302
- body,
303
- headers,
304
- signal: abortController.signal,
305
- });
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
306
  }
307
 
308
  // Helper to update UI during request
@@ -329,12 +361,13 @@ function finalizeRequest() {
329
  function handleRequestError(error) {
330
  if (streamMsg) {
331
  streamMsg.querySelector('.loading')?.remove();
332
- streamMsg.dataset.text = `Error: ${error.message || 'An error occurred during the request.'}`;
333
  renderMarkdown(streamMsg);
334
  streamMsg.dataset.done = '1';
335
  streamMsg = null;
336
  }
337
- console.error('Request error:', error);
 
338
  isRequestActive = false;
339
  abortController = null;
340
  if (uiElements.sendBtn) uiElements.sendBtn.style.display = 'inline-flex';
@@ -348,7 +381,7 @@ async function loadConversations() {
348
  const response = await fetch('/api/conversations', {
349
  headers: { 'Authorization': `Bearer ${checkAuth()}` }
350
  });
351
- if (!response.ok) throw new Error('Failed to load conversations');
352
  const conversations = await response.json();
353
  if (uiElements.conversationList) {
354
  uiElements.conversationList.innerHTML = '';
@@ -357,13 +390,13 @@ async function loadConversations() {
357
  li.className = `flex items-center justify-between text-white hover:bg-gray-700 p-2 rounded cursor-pointer transition-colors ${conv.conversation_id === currentConversationId ? 'bg-gray-700' : ''}`;
358
  li.dataset.conversationId = conv.conversation_id;
359
  li.innerHTML = `
360
- <div class="flex items-center flex-1" data-conversation-id="${conv.conversation_id}">
361
  <svg class="w-5 h-5 mr-2" fill="none" stroke="currentColor" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg">
362
  <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M8 10h.01M12 10h.01M16 10h.01M9 16H5a2 2 0 01-2-2V6a2 2 0 012-2h14a2 2 0 012 2v8a2 2 0 01-2 2h-5l-5 5v-5z"></path>
363
  </svg>
364
- <span class="truncate flex-1">${conv.title || 'Untitled Conversation'}</span>
365
  </div>
366
- <button class="delete-conversation-btn text-red-400 hover:text-red-600 p-1" title="Delete Conversation" data-conversation-id="${conv.conversation_id}">
367
  <svg class="w-5 h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg">
368
  <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M19 7l-.867 12.142A2 2 0 0116.138 21H7.862a2 2 0 01-1.995-1.858L5 7m5-4h4M3 7h18"></path>
369
  </svg>
@@ -375,7 +408,8 @@ async function loadConversations() {
375
  });
376
  }
377
  } catch (error) {
378
- console.error('Error loading conversations:', error);
 
379
  }
380
  }
381
 
@@ -387,11 +421,11 @@ async function loadConversation(conversationId) {
387
  });
388
  if (!response.ok) {
389
  if (response.status === 401) window.location.href = '/login';
390
- throw new Error('Failed to load conversation');
391
  }
392
  const data = await response.json();
393
  currentConversationId = data.conversation_id;
394
- currentConversationTitle = data.title || 'Untitled Conversation';
395
  conversationHistory = data.messages.map(msg => ({ role: msg.role, content: msg.content }));
396
  if (uiElements.chatBox) uiElements.chatBox.innerHTML = '';
397
  conversationHistory.forEach(msg => addMsg(msg.role, msg.content));
@@ -400,14 +434,14 @@ async function loadConversation(conversationId) {
400
  history.pushState(null, '', `/chat/${currentConversationId}`);
401
  toggleSidebar(false);
402
  } catch (error) {
403
- console.error('Error loading conversation:', error);
404
- alert('Failed to load conversation. Please try again or log in.');
405
  }
406
  }
407
 
408
  // Delete conversation
409
  async function deleteConversation(conversationId) {
410
- if (!confirm('Are you sure you want to delete this conversation?')) return;
411
  try {
412
  const response = await fetch(`/api/conversations/${conversationId}`, {
413
  method: 'DELETE',
@@ -415,7 +449,7 @@ async function deleteConversation(conversationId) {
415
  });
416
  if (!response.ok) {
417
  if (response.status === 401) window.location.href = '/login';
418
- throw new Error('Failed to delete conversation');
419
  }
420
  if (conversationId === currentConversationId) {
421
  clearAllMessages();
@@ -425,8 +459,8 @@ async function deleteConversation(conversationId) {
425
  }
426
  await loadConversations();
427
  } catch (error) {
428
- console.error('Error deleting conversation:', error);
429
- alert('Failed to delete conversation. Please try again.');
430
  }
431
  }
432
 
@@ -441,16 +475,16 @@ async function saveMessageToConversation(conversationId, role, content) {
441
  },
442
  body: JSON.stringify({ role, content })
443
  });
444
- if (!response.ok) throw new Error('Failed to save message');
445
  } catch (error) {
446
- console.error('Error saving message:', error);
447
  }
448
  }
449
 
450
  // Create new conversation
451
  async function createNewConversation() {
452
  if (!checkAuth()) {
453
- alert('Please log in to create a new conversation.');
454
  window.location.href = '/login';
455
  return;
456
  }
@@ -461,14 +495,14 @@ async function createNewConversation() {
461
  'Content-Type': 'application/json',
462
  'Authorization': `Bearer ${checkAuth()}`
463
  },
464
- body: JSON.stringify({ title: 'New Conversation' })
465
  });
466
  if (!response.ok) {
467
  if (response.status === 401) {
468
  localStorage.removeItem('token');
469
  window.location.href = '/login';
470
  }
471
- throw new Error('Failed to create conversation');
472
  }
473
  const data = await response.json();
474
  currentConversationId = data.conversation_id;
@@ -482,8 +516,8 @@ async function createNewConversation() {
482
  await loadConversations();
483
  toggleSidebar(false);
484
  } catch (error) {
485
- console.error('Error creating conversation:', error);
486
- alert('Failed to create new conversation. Please try again.');
487
  }
488
  }
489
 
@@ -498,14 +532,14 @@ async function updateConversationTitle(conversationId, newTitle) {
498
  },
499
  body: JSON.stringify({ title: newTitle })
500
  });
501
- if (!response.ok) throw new Error('Failed to update title');
502
  const data = await response.json();
503
  currentConversationTitle = data.title;
504
  if (uiElements.conversationTitle) uiElements.conversationTitle.textContent = currentConversationTitle;
505
  await loadConversations();
506
  } catch (error) {
507
- console.error('Error updating title:', error);
508
- alert('Failed to update conversation title.');
509
  }
510
  }
511
 
@@ -598,7 +632,7 @@ async function submitMessage() {
598
  if (file.type.startsWith('image/')) {
599
  endpoint = '/api/image-analysis';
600
  inputType = 'image';
601
- message = 'Analyze this image';
602
  formData = new FormData();
603
  formData.append('file', file);
604
  formData.append('output_format', 'text');
@@ -608,14 +642,16 @@ async function submitMessage() {
608
  if (file.type.startsWith('audio/')) {
609
  endpoint = '/api/audio-transcription';
610
  inputType = 'audio';
611
- message = 'Transcribe this audio';
612
  formData = new FormData();
613
  formData.append('file', file);
614
  }
615
  } else if (message) {
616
  payload = {
617
  message,
618
- system_prompt: 'You are an expert assistant providing detailed, comprehensive, and well-structured responses.',
 
 
619
  history: conversationHistory,
620
  temperature: 0.7,
621
  max_new_tokens: 128000,
@@ -643,31 +679,10 @@ async function submitMessage() {
643
 
644
  try {
645
  const response = await sendRequest(endpoint, payload ? JSON.stringify(payload) : formData, headers);
646
- if (!response.ok) {
647
- if (response.status === 403) {
648
- if (uiElements.messageLimitWarning) uiElements.messageLimitWarning.classList.remove('hidden');
649
- if (uiElements.input) uiElements.input.disabled = true;
650
- if (streamMsg) streamMsg.querySelector('.loading')?.remove();
651
- streamMsg = null;
652
- isRequestActive = false;
653
- abortController = null;
654
- if (uiElements.sendBtn) uiElements.sendBtn.style.display = 'inline-flex';
655
- if (uiElements.stopBtn) uiElements.stopBtn.style.display = 'none';
656
- setTimeout(() => window.location.href = '/login', 3000);
657
- return;
658
- }
659
- if (response.status === 401) {
660
- localStorage.removeItem('token');
661
- window.location.href = '/login';
662
- return;
663
- }
664
- throw new Error(`Request failed with status ${response.status}`);
665
- }
666
-
667
  if (endpoint === '/api/audio-transcription') {
668
  const data = await response.json();
669
- if (!data.transcription) throw new Error('No transcription received from server');
670
- const transcription = data.transcription || 'Error: No transcription generated.';
671
  if (streamMsg) {
672
  streamMsg.dataset.text = transcription;
673
  renderMarkdown(streamMsg);
@@ -680,14 +695,14 @@ async function submitMessage() {
680
  }
681
  if (checkAuth() && data.conversation_id) {
682
  currentConversationId = data.conversation_id;
683
- currentConversationTitle = data.conversation_title || 'Untitled Conversation';
684
  if (uiElements.conversationTitle) uiElements.conversationTitle.textContent = currentConversationTitle;
685
  history.pushState(null, '', `/chat/${currentConversationId}`);
686
  await loadConversations();
687
  }
688
  } else if (endpoint === '/api/image-analysis') {
689
  const data = await response.json();
690
- const analysis = data.image_analysis || 'Error: No analysis generated.';
691
  if (streamMsg) {
692
  streamMsg.dataset.text = analysis;
693
  renderMarkdown(streamMsg);
@@ -700,7 +715,7 @@ async function submitMessage() {
700
  }
701
  if (checkAuth() && data.conversation_id) {
702
  currentConversationId = data.conversation_id;
703
- currentConversationTitle = data.conversation_title || 'Untitled Conversation';
704
  if (uiElements.conversationTitle) uiElements.conversationTitle.textContent = currentConversationTitle;
705
  history.pushState(null, '', `/chat/${currentConversationId}`);
706
  await loadConversations();
@@ -709,7 +724,7 @@ async function submitMessage() {
709
  const contentType = response.headers.get('Content-Type');
710
  if (contentType?.includes('application/json')) {
711
  const data = await response.json();
712
- const responseText = data.response || 'Error: No response generated.';
713
  if (streamMsg) {
714
  streamMsg.dataset.text = responseText;
715
  renderMarkdown(streamMsg);
@@ -722,7 +737,7 @@ async function submitMessage() {
722
  }
723
  if (checkAuth() && data.conversation_id) {
724
  currentConversationId = data.conversation_id;
725
- currentConversationTitle = data.conversation_title || 'Untitled Conversation';
726
  if (uiElements.conversationTitle) uiElements.conversationTitle.textContent = currentConversationTitle;
727
  history.pushState(null, '', `/chat/${currentConversationId}`);
728
  await loadConversations();
@@ -733,7 +748,10 @@ async function submitMessage() {
733
  let buffer = '';
734
  while (true) {
735
  const { done, value } = await reader.read();
736
- if (done) break;
 
 
 
737
  buffer += decoder.decode(value, { stream: true });
738
  if (streamMsg) {
739
  streamMsg.dataset.text = buffer;
@@ -782,12 +800,12 @@ function stopStream(forceCancel = false) {
782
  if (uiElements.settingsBtn) {
783
  uiElements.settingsBtn.addEventListener('click', () => {
784
  if (!checkAuth()) {
785
- alert('Please log in to access settings.');
786
  window.location.href = '/login';
787
  return;
788
  }
789
  uiElements.settingsModal.classList.remove('hidden');
790
- fetch('/api/settings', { // تعديل: استخدام /api/settings بدل /users/me
791
  headers: { 'Authorization': `Bearer ${checkAuth()}` }
792
  })
793
  .then(res => {
@@ -796,12 +814,11 @@ if (uiElements.settingsBtn) {
796
  localStorage.removeItem('token');
797
  window.location.href = '/login';
798
  }
799
- throw new Error('Failed to fetch settings');
800
  }
801
  return res.json();
802
  })
803
  .then(data => {
804
- // تعبئة حقول الـ form
805
  document.getElementById('display_name').value = data.user_settings.display_name || '';
806
  document.getElementById('preferred_model').value = data.user_settings.preferred_model || 'standard';
807
  document.getElementById('job_title').value = data.user_settings.job_title || '';
@@ -810,9 +827,8 @@ if (uiElements.settingsBtn) {
810
  document.getElementById('additional_info').value = data.user_settings.additional_info || '';
811
  document.getElementById('conversation_style').value = data.user_settings.conversation_style || 'default';
812
 
813
- // تعبئة خيارات preferred_model ديناميكيًا
814
  const modelSelect = document.getElementById('preferred_model');
815
- modelSelect.innerHTML = ''; // تفريغ الخيارات القديمة
816
  data.available_models.forEach(model => {
817
  const option = document.createElement('option');
818
  option.value = model.alias;
@@ -820,19 +836,18 @@ if (uiElements.settingsBtn) {
820
  modelSelect.appendChild(option);
821
  });
822
 
823
- // تعبئة خيارات conversation_style ديناميكيًا
824
  const styleSelect = document.getElementById('conversation_style');
825
- styleSelect.innerHTML = ''; // تفريغ الخيارات القديمة
826
  data.conversation_styles.forEach(style => {
827
  const option = document.createElement('option');
828
  option.value = style;
829
- option.textContent = style.charAt(0).toUpperCase() + style.slice(1); // تحويل الحرف الأول لكابيتال
830
  styleSelect.appendChild(option);
831
  });
832
  })
833
  .catch(err => {
834
- console.error('Error fetching settings:', err);
835
- alert('Failed to load settings. Please try again.');
836
  });
837
  });
838
  }
@@ -847,7 +862,7 @@ if (uiElements.settingsForm) {
847
  uiElements.settingsForm.addEventListener('submit', (e) => {
848
  e.preventDefault();
849
  if (!checkAuth()) {
850
- alert('Please log in to save settings.');
851
  window.location.href = '/login';
852
  return;
853
  }
@@ -867,18 +882,18 @@ if (uiElements.settingsForm) {
867
  localStorage.removeItem('token');
868
  window.location.href = '/login';
869
  }
870
- throw new Error('Failed to update settings');
871
  }
872
  return res.json();
873
  })
874
  .then(() => {
875
- alert('Settings updated successfully!');
876
  uiElements.settingsModal.classList.add('hidden');
877
  toggleSidebar(false);
878
  })
879
  .catch(err => {
880
- console.error('Error updating settings:', err);
881
- alert('Error updating settings: ' + err.message);
882
  });
883
  });
884
  }
@@ -891,10 +906,10 @@ if (uiElements.historyToggle) {
891
  uiElements.historyToggle.innerHTML = uiElements.conversationList.classList.contains('hidden')
892
  ? `<svg class="w-5 h-5 mr-2" fill="none" stroke="currentColor" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg">
893
  <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M12 8v4l3 3m6-3a9 9 0 11-18 0 9 9 0 0118 0z"></path>
894
- </svg>Show History`
895
  : `<svg class="w-5 h-5 mr-2" fill="none" stroke="currentColor" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg">
896
  <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M6 18L18 6M6 6l12 12"></path>
897
- </svg>Hide History`;
898
  }
899
  });
900
  }
@@ -970,8 +985,8 @@ if (uiElements.clearBtn) uiElements.clearBtn.addEventListener('click', clearAllM
970
 
971
  if (uiElements.conversationTitle) {
972
  uiElements.conversationTitle.addEventListener('click', () => {
973
- if (!checkAuth()) return alert('Please log in to edit the conversation title.');
974
- const newTitle = prompt('Enter new conversation title:', currentConversationTitle || '');
975
  if (newTitle && currentConversationId) {
976
  updateConversationTitle(currentConversationId, newTitle);
977
  }
 
57
  }
58
  }
59
 
60
+ // Detect Arabic text
61
+ function isArabicText(text) {
62
+ return /[\u0600-\u06FF]/.test(text);
63
+ }
64
+
65
  // Initialize page
66
  document.addEventListener('DOMContentLoaded', async () => {
67
  AOS.init({
 
103
  }
104
  }
105
 
106
+ // Render markdown content with RTL support
107
  function renderMarkdown(el) {
108
  const raw = el.dataset.text || '';
109
+ const isArabic = isArabicText(raw);
110
  const html = marked.parse(raw, {
111
  gfm: true,
112
  breaks: true,
 
114
  smartypants: false,
115
  headerIds: false,
116
  });
117
+ el.innerHTML = `<div class="md-content" style="direction: ${isArabic ? 'rtl' : 'ltr'}; text-align: ${isArabic ? 'right' : 'left'};">${html}</div>`;
118
  const wrapper = el.querySelector('.md-content');
119
  wrapper.querySelectorAll('table').forEach(t => {
120
  if (!t.parentNode.classList?.contains('table-wrapper')) {
 
154
  // Add chat bubble
155
  function addMsg(who, text) {
156
  const div = document.createElement('div');
157
+ div.className = `bubble ${who === 'user' ? 'bubble-user' : 'bubble-assist'} ${isArabicText(text) ? 'rtl' : ''}`;
158
  div.dataset.text = text;
159
  renderMarkdown(div);
160
  if (uiElements.chatBox) {
 
236
  mediaRecorder.addEventListener('dataavailable', event => audioChunks.push(event.data));
237
  }).catch(err => {
238
  console.error('Error accessing microphone:', err);
239
+ alert('فشل الوصول إلى الميكروفون. من فضلك، تحقق من الأذونات.');
240
  isRecording = false;
241
  if (uiElements.sendBtn) uiElements.sendBtn.classList.remove('recording');
242
  });
 
259
  // Send audio message
260
  async function submitAudioMessage(formData) {
261
  enterChatView();
262
+ addMsg('user', 'رسالة صوتية');
263
+ conversationHistory.push({ role: 'user', content: 'رسالة صوتية' });
264
  sessionStorage.setItem('conversationHistory', JSON.stringify(conversationHistory));
265
  streamMsg = addMsg('assistant', '');
266
  const loadingEl = document.createElement('span');
 
273
 
274
  try {
275
  const response = await sendRequest('/api/audio-transcription', formData);
276
+ if (!response.ok) {
277
+ throw new Error(`Request failed with status ${response.status}`);
278
+ }
279
  const data = await response.json();
280
+ if (!data.transcription) throw new Error('لم يتم استلام نص النسخ من الخادم');
281
+ const transcription = data.transcription || 'خطأ: لم يتم إنشاء نص النسخ.';
282
  if (streamMsg) {
283
  streamMsg.dataset.text = transcription;
284
  renderMarkdown(streamMsg);
 
291
  }
292
  if (checkAuth() && data.conversation_id) {
293
  currentConversationId = data.conversation_id;
294
+ currentConversationTitle = data.conversation_title || 'محادثة بدون عنوان';
295
  if (uiElements.conversationTitle) uiElements.conversationTitle.textContent = currentConversationTitle;
296
  history.pushState(null, '', `/chat/${currentConversationId}`);
297
  await loadConversations();
 
306
  async function sendRequest(endpoint, body, headers = {}) {
307
  const token = checkAuth();
308
  if (token) headers['Authorization'] = `Bearer ${token}`;
309
+ try {
310
+ const response = await fetch(endpoint, {
311
+ method: 'POST',
312
+ body,
313
+ headers,
314
+ signal: abortController?.signal,
315
+ });
316
+ if (!response.ok) {
317
+ if (response.status === 403) {
318
+ if (uiElements.messageLimitWarning) uiElements.messageLimitWarning.classList.remove('hidden');
319
+ throw new Error('تم الوصول إلى الحد الأقصى للرسائل. من فضلك، سجل الدخول للمتابعة.');
320
+ }
321
+ if (response.status === 401) {
322
+ localStorage.removeItem('token');
323
+ window.location.href = '/login';
324
+ throw new Error('غير مصرح. من فضلك، سجل الدخول مرة أخرى.');
325
+ }
326
+ if (response.status === 503) {
327
+ throw new Error('النموذج غير متاح حاليًا. من فضلك، حاول استخدام نموذج آخر.');
328
+ }
329
+ throw new Error(`فشل الطلب: ${response.status}`);
330
+ }
331
+ return response;
332
+ } catch (error) {
333
+ if (error.name === 'AbortError') {
334
+ throw new Error('تم إلغاء الطلب');
335
+ }
336
+ throw error;
337
+ }
338
  }
339
 
340
  // Helper to update UI during request
 
361
  function handleRequestError(error) {
362
  if (streamMsg) {
363
  streamMsg.querySelector('.loading')?.remove();
364
+ streamMsg.dataset.text = `خطأ: ${error.message || 'حدث خطأ أثناء الطلب.'}`;
365
  renderMarkdown(streamMsg);
366
  streamMsg.dataset.done = '1';
367
  streamMsg = null;
368
  }
369
+ console.error('خطأ في الطلب:', error);
370
+ alert(`خطأ: ${error.message || 'حدث خطأ أثناء الطلب.'}`);
371
  isRequestActive = false;
372
  abortController = null;
373
  if (uiElements.sendBtn) uiElements.sendBtn.style.display = 'inline-flex';
 
381
  const response = await fetch('/api/conversations', {
382
  headers: { 'Authorization': `Bearer ${checkAuth()}` }
383
  });
384
+ if (!response.ok) throw new Error('فشل تحميل المحادثات');
385
  const conversations = await response.json();
386
  if (uiElements.conversationList) {
387
  uiElements.conversationList.innerHTML = '';
 
390
  li.className = `flex items-center justify-between text-white hover:bg-gray-700 p-2 rounded cursor-pointer transition-colors ${conv.conversation_id === currentConversationId ? 'bg-gray-700' : ''}`;
391
  li.dataset.conversationId = conv.conversation_id;
392
  li.innerHTML = `
393
+ <div class="flex items-center flex-1" style="direction: ${isArabicText(conv.title) ? 'rtl' : 'ltr'};" data-conversation-id="${conv.conversation_id}">
394
  <svg class="w-5 h-5 mr-2" fill="none" stroke="currentColor" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg">
395
  <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M8 10h.01M12 10h.01M16 10h.01M9 16H5a2 2 0 01-2-2V6a2 2 0 012-2h14a2 2 0 012 2v8a2 2 0 01-2 2h-5l-5 5v-5z"></path>
396
  </svg>
397
+ <span class="truncate flex-1">${conv.title || 'محادثة بدون عنوان'}</span>
398
  </div>
399
+ <button class="delete-conversation-btn text-red-400 hover:text-red-600 p-1" title="حذف المحادثة" data-conversation-id="${conv.conversation_id}">
400
  <svg class="w-5 h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg">
401
  <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M19 7l-.867 12.142A2 2 0 0116.138 21H7.862a2 2 0 01-1.995-1.858L5 7m5-4h4M3 7h18"></path>
402
  </svg>
 
408
  });
409
  }
410
  } catch (error) {
411
+ console.error('خطأ في تحميل المحادثات:', error);
412
+ alert('فشل تحميل المحادثات. من فضلك، حاول مرة أخرى.');
413
  }
414
  }
415
 
 
421
  });
422
  if (!response.ok) {
423
  if (response.status === 401) window.location.href = '/login';
424
+ throw new Error('فشل تحميل المحادثة');
425
  }
426
  const data = await response.json();
427
  currentConversationId = data.conversation_id;
428
+ currentConversationTitle = data.title || 'محادثة بدون عنوان';
429
  conversationHistory = data.messages.map(msg => ({ role: msg.role, content: msg.content }));
430
  if (uiElements.chatBox) uiElements.chatBox.innerHTML = '';
431
  conversationHistory.forEach(msg => addMsg(msg.role, msg.content));
 
434
  history.pushState(null, '', `/chat/${currentConversationId}`);
435
  toggleSidebar(false);
436
  } catch (error) {
437
+ console.error('خطأ في تحميل المحادثة:', error);
438
+ alert('فشل تحميل المحادثة. من فضلك، حاول مرة أخرى أو سجل الدخول.');
439
  }
440
  }
441
 
442
  // Delete conversation
443
  async function deleteConversation(conversationId) {
444
+ if (!confirm('هل أنت متأكد من حذف هذه المحادثة؟')) return;
445
  try {
446
  const response = await fetch(`/api/conversations/${conversationId}`, {
447
  method: 'DELETE',
 
449
  });
450
  if (!response.ok) {
451
  if (response.status === 401) window.location.href = '/login';
452
+ throw new Error('فشل حذف المحادثة');
453
  }
454
  if (conversationId === currentConversationId) {
455
  clearAllMessages();
 
459
  }
460
  await loadConversations();
461
  } catch (error) {
462
+ console.error('خطأ في حذف المحادثة:', error);
463
+ alert('فشل حذف المحادثة. من فضلك، حاول مرة أخرى.');
464
  }
465
  }
466
 
 
475
  },
476
  body: JSON.stringify({ role, content })
477
  });
478
+ if (!response.ok) throw new Error('فشل حفظ الرسالة');
479
  } catch (error) {
480
+ console.error('خطأ في حفظ الرسالة:', error);
481
  }
482
  }
483
 
484
  // Create new conversation
485
  async function createNewConversation() {
486
  if (!checkAuth()) {
487
+ alert('من فضلك، سجل الدخول لإنشاء محادثة جديدة.');
488
  window.location.href = '/login';
489
  return;
490
  }
 
495
  'Content-Type': 'application/json',
496
  'Authorization': `Bearer ${checkAuth()}`
497
  },
498
+ body: JSON.stringify({ title: 'محادثة جديدة' })
499
  });
500
  if (!response.ok) {
501
  if (response.status === 401) {
502
  localStorage.removeItem('token');
503
  window.location.href = '/login';
504
  }
505
+ throw new Error('فشل إنشاء المحادثة');
506
  }
507
  const data = await response.json();
508
  currentConversationId = data.conversation_id;
 
516
  await loadConversations();
517
  toggleSidebar(false);
518
  } catch (error) {
519
+ console.error('خطأ في إنشاء المحادثة:', error);
520
+ alert('فشل إنشاء محادثة جديدة. من فضلك، حاول مرة أخرى.');
521
  }
522
  }
523
 
 
532
  },
533
  body: JSON.stringify({ title: newTitle })
534
  });
535
+ if (!response.ok) throw new Error('فشل تحديث العنوان');
536
  const data = await response.json();
537
  currentConversationTitle = data.title;
538
  if (uiElements.conversationTitle) uiElements.conversationTitle.textContent = currentConversationTitle;
539
  await loadConversations();
540
  } catch (error) {
541
+ console.error('خطأ في تحديث العنوان:', error);
542
+ alert('فشل تحديث عنوان المحادثة.');
543
  }
544
  }
545
 
 
632
  if (file.type.startsWith('image/')) {
633
  endpoint = '/api/image-analysis';
634
  inputType = 'image';
635
+ message = 'تحليل هذه الصورة';
636
  formData = new FormData();
637
  formData.append('file', file);
638
  formData.append('output_format', 'text');
 
642
  if (file.type.startsWith('audio/')) {
643
  endpoint = '/api/audio-transcription';
644
  inputType = 'audio';
645
+ message = 'نسخ هذا الصوت';
646
  formData = new FormData();
647
  formData.append('file', file);
648
  }
649
  } else if (message) {
650
  payload = {
651
  message,
652
+ system_prompt: isArabicText(message)
653
+ ? 'أنت مساعد ذكي تقدم إجابات مفصلة ومنظمة باللغة العربية، مع ضمان الدقة والوضوح.'
654
+ : 'You are an expert assistant providing detailed, comprehensive, and well-structured responses.',
655
  history: conversationHistory,
656
  temperature: 0.7,
657
  max_new_tokens: 128000,
 
679
 
680
  try {
681
  const response = await sendRequest(endpoint, payload ? JSON.stringify(payload) : formData, headers);
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
682
  if (endpoint === '/api/audio-transcription') {
683
  const data = await response.json();
684
+ if (!data.transcription) throw new Error('لم يتم استلام نص النسخ من الخادم');
685
+ const transcription = data.transcription || 'خطأ: لم يتم إنشاء نص النسخ.';
686
  if (streamMsg) {
687
  streamMsg.dataset.text = transcription;
688
  renderMarkdown(streamMsg);
 
695
  }
696
  if (checkAuth() && data.conversation_id) {
697
  currentConversationId = data.conversation_id;
698
+ currentConversationTitle = data.conversation_title || 'محادثة بدون عنوان';
699
  if (uiElements.conversationTitle) uiElements.conversationTitle.textContent = currentConversationTitle;
700
  history.pushState(null, '', `/chat/${currentConversationId}`);
701
  await loadConversations();
702
  }
703
  } else if (endpoint === '/api/image-analysis') {
704
  const data = await response.json();
705
+ const analysis = data.image_analysis || 'خطأ: لم يتم إنشاء تحليل.';
706
  if (streamMsg) {
707
  streamMsg.dataset.text = analysis;
708
  renderMarkdown(streamMsg);
 
715
  }
716
  if (checkAuth() && data.conversation_id) {
717
  currentConversationId = data.conversation_id;
718
+ currentConversationTitle = data.conversation_title || 'محادثة بدون عنوان';
719
  if (uiElements.conversationTitle) uiElements.conversationTitle.textContent = currentConversationTitle;
720
  history.pushState(null, '', `/chat/${currentConversationId}`);
721
  await loadConversations();
 
724
  const contentType = response.headers.get('Content-Type');
725
  if (contentType?.includes('application/json')) {
726
  const data = await response.json();
727
+ const responseText = data.response || 'خطأ: لم يتم إنشاء رد.';
728
  if (streamMsg) {
729
  streamMsg.dataset.text = responseText;
730
  renderMarkdown(streamMsg);
 
737
  }
738
  if (checkAuth() && data.conversation_id) {
739
  currentConversationId = data.conversation_id;
740
+ currentConversationTitle = data.conversation_title || 'محادثة بدون عنوان';
741
  if (uiElements.conversationTitle) uiElements.conversationTitle.textContent = currentConversationTitle;
742
  history.pushState(null, '', `/chat/${currentConversationId}`);
743
  await loadConversations();
 
748
  let buffer = '';
749
  while (true) {
750
  const { done, value } = await reader.read();
751
+ if (done) {
752
+ if (!buffer.trim()) throw new Error('الرد فارغ من الخادم');
753
+ break;
754
+ }
755
  buffer += decoder.decode(value, { stream: true });
756
  if (streamMsg) {
757
  streamMsg.dataset.text = buffer;
 
800
  if (uiElements.settingsBtn) {
801
  uiElements.settingsBtn.addEventListener('click', () => {
802
  if (!checkAuth()) {
803
+ alert('من فضلك، سجل الدخول للوصول إلى الإعدادات.');
804
  window.location.href = '/login';
805
  return;
806
  }
807
  uiElements.settingsModal.classList.remove('hidden');
808
+ fetch('/api/settings', {
809
  headers: { 'Authorization': `Bearer ${checkAuth()}` }
810
  })
811
  .then(res => {
 
814
  localStorage.removeItem('token');
815
  window.location.href = '/login';
816
  }
817
+ throw new Error('فشل جلب الإعدادات');
818
  }
819
  return res.json();
820
  })
821
  .then(data => {
 
822
  document.getElementById('display_name').value = data.user_settings.display_name || '';
823
  document.getElementById('preferred_model').value = data.user_settings.preferred_model || 'standard';
824
  document.getElementById('job_title').value = data.user_settings.job_title || '';
 
827
  document.getElementById('additional_info').value = data.user_settings.additional_info || '';
828
  document.getElementById('conversation_style').value = data.user_settings.conversation_style || 'default';
829
 
 
830
  const modelSelect = document.getElementById('preferred_model');
831
+ modelSelect.innerHTML = '';
832
  data.available_models.forEach(model => {
833
  const option = document.createElement('option');
834
  option.value = model.alias;
 
836
  modelSelect.appendChild(option);
837
  });
838
 
 
839
  const styleSelect = document.getElementById('conversation_style');
840
+ styleSelect.innerHTML = '';
841
  data.conversation_styles.forEach(style => {
842
  const option = document.createElement('option');
843
  option.value = style;
844
+ option.textContent = style.charAt(0).toUpperCase() + style.slice(1);
845
  styleSelect.appendChild(option);
846
  });
847
  })
848
  .catch(err => {
849
+ console.error('خطأ في جلب الإعدادات:', err);
850
+ alert('فشل تحميل الإعدادات. من فضلك، حاول مرة أخرى.');
851
  });
852
  });
853
  }
 
862
  uiElements.settingsForm.addEventListener('submit', (e) => {
863
  e.preventDefault();
864
  if (!checkAuth()) {
865
+ alert('من فضلك، سجل الدخول لحفظ الإعدادات.');
866
  window.location.href = '/login';
867
  return;
868
  }
 
882
  localStorage.removeItem('token');
883
  window.location.href = '/login';
884
  }
885
+ throw new Error('فشل تحديث الإعدادات');
886
  }
887
  return res.json();
888
  })
889
  .then(() => {
890
+ alert('تم تحديث الإعدادات بنجاح!');
891
  uiElements.settingsModal.classList.add('hidden');
892
  toggleSidebar(false);
893
  })
894
  .catch(err => {
895
+ console.error('خطأ في تحديث الإعدادات:', err);
896
+ alert('خطأ في تحديث الإعدادات: ' + err.message);
897
  });
898
  });
899
  }
 
906
  uiElements.historyToggle.innerHTML = uiElements.conversationList.classList.contains('hidden')
907
  ? `<svg class="w-5 h-5 mr-2" fill="none" stroke="currentColor" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg">
908
  <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M12 8v4l3 3m6-3a9 9 0 11-18 0 9 9 0 0118 0z"></path>
909
+ </svg>إظهار السجل`
910
  : `<svg class="w-5 h-5 mr-2" fill="none" stroke="currentColor" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg">
911
  <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M6 18L18 6M6 6l12 12"></path>
912
+ </svg>إخفاء السجل`;
913
  }
914
  });
915
  }
 
985
 
986
  if (uiElements.conversationTitle) {
987
  uiElements.conversationTitle.addEventListener('click', () => {
988
+ if (!checkAuth()) return alert('من فضلك، سجل الدخول لتعديل عنوان المحادثة.');
989
+ const newTitle = prompt('أدخل عنوان المحادثة الجديد:', currentConversationTitle || '');
990
  if (newTitle && currentConversationId) {
991
  updateConversationTitle(currentConversationId, newTitle);
992
  }
utils/generation.py CHANGED
@@ -25,10 +25,10 @@ cache = TTLCache(maxsize=int(os.getenv("QUEUE_SIZE", 100)), ttl=600)
25
 
26
  # تعريف LATEX_DELIMS
27
  LATEX_DELIMS = [
28
- {"left": "$$ ", "right": " $$", "display": True},
29
  {"left": "$", "right": "$", "display": False},
30
- {"left": "\$$ ", "right": "\ $$", "display": True},
31
- {"left": "\$$ ", "right": "\ $$", "display": False},
32
  ]
33
 
34
  # إعداد العميل لـ Hugging Face Router API
@@ -37,7 +37,7 @@ BACKUP_HF_TOKEN = os.getenv("BACKUP_HF_TOKEN")
37
  ROUTER_API_URL = os.getenv("ROUTER_API_URL", "https://router.huggingface.co")
38
  API_ENDPOINT = os.getenv("API_ENDPOINT", "https://api-inference.huggingface.co")
39
  FALLBACK_API_ENDPOINT = os.getenv("FALLBACK_API_ENDPOINT", "https://api-inference.huggingface.co")
40
- MODEL_NAME = os.getenv("MODEL_NAME", "openai/gpt-oss-120b") # بدون :cerebras
41
  SECONDARY_MODEL_NAME = os.getenv("SECONDARY_MODEL_NAME", "mistralai/Mixtral-8x7B-Instruct-v0.1")
42
  TERTIARY_MODEL_NAME = os.getenv("TERTIARY_MODEL_NAME", "Qwen/Qwen2.5-0.5B-Instruct")
43
  CLIP_BASE_MODEL = os.getenv("CLIP_BASE_MODEL", "Salesforce/blip-image-captioning-large")
@@ -45,9 +45,8 @@ CLIP_LARGE_MODEL = os.getenv("CLIP_LARGE_MODEL", "openai/clip-vit-large-patch14"
45
  ASR_MODEL = os.getenv("ASR_MODEL", "openai/whisper-large-v3")
46
  TTS_MODEL = os.getenv("TTS_MODEL", "facebook/mms-tts-ara")
47
 
48
- # Provider endpoints (based on Router API providers)
49
  PROVIDER_ENDPOINTS = {
50
-
51
  "fireworks-ai": "https://api.fireworks.ai/inference/v1",
52
  "nebius": "https://api.nebius.ai/v1",
53
  "novita": "https://api.novita.ai/v1",
@@ -78,7 +77,13 @@ def check_model_availability(model_name: str, api_key: str) -> tuple[bool, str,
78
  if response.status_code == 200:
79
  data = response.json().get("data", {})
80
  providers = data.get("providers", [])
81
- # Select the first available provider (e.g., 'cerebras')
 
 
 
 
 
 
82
  for provider in providers:
83
  if provider.get("status") == "live":
84
  provider_name = provider.get("provider")
@@ -410,7 +415,7 @@ def request_generation(
410
  except Exception as e:
411
  logger.exception(f"[Gateway] Streaming failed for model {model_name}: {e}")
412
  if selected_api_key != BACKUP_HF_TOKEN and BACKUP_HF_TOKEN:
413
- logger.warning(f"Retrying with backup token for model {model_name}")
414
  for chunk in request_generation(
415
  api_key=BACKUP_HF_TOKEN,
416
  api_base=selected_endpoint,
@@ -475,27 +480,27 @@ def request_generation(
475
  buffer = ""
476
  continue
477
 
478
- if chunk.choices[0].finish_reason in ("stop", "error", "length"):
479
- if buffer:
480
- cached_chunks.append(buffer)
481
- yield buffer
482
- buffer = ""
483
 
484
- if reasoning_started and not reasoning_closed:
485
- cached_chunks.append("assistantfinal")
486
- yield "assistantfinal"
487
- reasoning_closed = True
488
-
489
- if not saw_visible_output:
490
- cached_chunks.append("No visible output produced.")
491
- yield "No visible output produced."
492
- if chunk.choices[0].finish_reason == "error":
493
- cached_chunks.append(f"Error: Unknown error with fallback model {fallback_model}")
494
- yield f"Error: Unknown error with fallback model {fallback_model}"
495
- elif chunk.choices[0].finish_reason == "length":
496
- cached_chunks.append("Response truncated due to token limit. Please refine your query or request continuation.")
497
- yield "Response truncated due to token limit. Please refine your query or request continuation."
498
- break
499
 
500
  if buffer and output_format == "audio":
501
  try:
 
25
 
26
  # تعريف LATEX_DELIMS
27
  LATEX_DELIMS = [
28
+ {"left": "$$", "right": "$$", "display": True},
29
  {"left": "$", "right": "$", "display": False},
30
+ {"left": "\\[", "right": "\\]", "display": True},
31
+ {"left": "\\(", "right": "\\)", "display": False},
32
  ]
33
 
34
  # إعداد العميل لـ Hugging Face Router API
 
37
  ROUTER_API_URL = os.getenv("ROUTER_API_URL", "https://router.huggingface.co")
38
  API_ENDPOINT = os.getenv("API_ENDPOINT", "https://api-inference.huggingface.co")
39
  FALLBACK_API_ENDPOINT = os.getenv("FALLBACK_API_ENDPOINT", "https://api-inference.huggingface.co")
40
+ MODEL_NAME = os.getenv("MODEL_NAME", "openai/gpt-oss-120b") # النموذج الرئيسي
41
  SECONDARY_MODEL_NAME = os.getenv("SECONDARY_MODEL_NAME", "mistralai/Mixtral-8x7B-Instruct-v0.1")
42
  TERTIARY_MODEL_NAME = os.getenv("TERTIARY_MODEL_NAME", "Qwen/Qwen2.5-0.5B-Instruct")
43
  CLIP_BASE_MODEL = os.getenv("CLIP_BASE_MODEL", "Salesforce/blip-image-captioning-large")
 
45
  ASR_MODEL = os.getenv("ASR_MODEL", "openai/whisper-large-v3")
46
  TTS_MODEL = os.getenv("TTS_MODEL", "facebook/mms-tts-ara")
47
 
48
+ # Provider endpoints (بدون together)
49
  PROVIDER_ENDPOINTS = {
 
50
  "fireworks-ai": "https://api.fireworks.ai/inference/v1",
51
  "nebius": "https://api.nebius.ai/v1",
52
  "novita": "https://api.novita.ai/v1",
 
77
  if response.status_code == 200:
78
  data = response.json().get("data", {})
79
  providers = data.get("providers", [])
80
+ # Prefer "cerebras" if available
81
+ for provider in providers:
82
+ if provider.get("provider") == "cerebras" and provider.get("status") == "live":
83
+ endpoint = PROVIDER_ENDPOINTS.get("cerebras", API_ENDPOINT)
84
+ logger.info(f"Model {model_name} is available via preferred provider cerebras at {endpoint}")
85
+ return True, api_key, endpoint
86
+ # Fallback to first live provider if cerebras not available
87
  for provider in providers:
88
  if provider.get("status") == "live":
89
  provider_name = provider.get("provider")
 
415
  except Exception as e:
416
  logger.exception(f"[Gateway] Streaming failed for model {model_name}: {e}")
417
  if selected_api_key != BACKUP_HF_TOKEN and BACKUP_HF_TOKEN:
418
+ logger.warning(f"Retrying with backup token for {model_name}")
419
  for chunk in request_generation(
420
  api_key=BACKUP_HF_TOKEN,
421
  api_base=selected_endpoint,
 
480
  buffer = ""
481
  continue
482
 
483
+ if chunk.choices[0].finish_reason in ("stop", "error", "length"):
484
+ if buffer:
485
+ cached_chunks.append(buffer)
486
+ yield buffer
487
+ buffer = ""
488
 
489
+ if reasoning_started and not reasoning_closed:
490
+ cached_chunks.append("assistantfinal")
491
+ yield "assistantfinal"
492
+ reasoning_closed = True
493
+
494
+ if not saw_visible_output:
495
+ cached_chunks.append("No visible output produced.")
496
+ yield "No visible output produced."
497
+ if chunk.choices[0].finish_reason == "error":
498
+ cached_chunks.append(f"Error: Unknown error with fallback model {fallback_model}")
499
+ yield f"Error: Unknown error with fallback model {fallback_model}"
500
+ elif chunk.choices[0].finish_reason == "length":
501
+ cached_chunks.append("Response truncated due to token limit. Please refine your query or request continuation.")
502
+ yield "Response truncated due to token limit. Please refine your query or request continuation."
503
+ break
504
 
505
  if buffer and output_format == "audio":
506
  try:
utils/web_search.py CHANGED
@@ -1,7 +1,9 @@
 
1
  import os
2
  import requests
3
  from bs4 import BeautifulSoup
4
  import logging
 
5
 
6
  logger = logging.getLogger(__name__)
7
 
@@ -23,7 +25,8 @@ def web_search(query: str) -> str:
23
  snippet = item.get("snippet", "")
24
  link = item.get("link", "")
25
  try:
26
- page_response = requests.get(link, timeout=5)
 
27
  page_response.raise_for_status()
28
  soup = BeautifulSoup(page_response.text, "html.parser")
29
  paragraphs = soup.find_all("p")
@@ -36,3 +39,4 @@ def web_search(query: str) -> str:
36
  except Exception as e:
37
  logger.exception("Web search failed")
38
  return f"Web search error: {e}"
 
 
1
+ #web_search.py
2
  import os
3
  import requests
4
  from bs4 import BeautifulSoup
5
  import logging
6
+ import time # لإضافة التأخير
7
 
8
  logger = logging.getLogger(__name__)
9
 
 
25
  snippet = item.get("snippet", "")
26
  link = item.get("link", "")
27
  try:
28
+ time.sleep(2) # إضافة تأخير 2 ثواني بين كل طلب
29
+ page_response = requests.get(link, timeout=10)
30
  page_response.raise_for_status()
31
  soup = BeautifulSoup(page_response.text, "html.parser")
32
  paragraphs = soup.find_all("p")
 
39
  except Exception as e:
40
  logger.exception("Web search failed")
41
  return f"Web search error: {e}"
42
+