Mark-Lasfar commited on
Commit
9aa52ab
·
1 Parent(s): 5e980fd

Update Model

Browse files
Files changed (2) hide show
  1. api/auth.py +51 -22
  2. static/js/chat.js +60 -60
api/auth.py CHANGED
@@ -1,4 +1,3 @@
1
- # api/auth.py
2
  from fastapi_users import FastAPIUsers
3
  from fastapi_users.authentication import CookieTransport, JWTStrategy, AuthenticationBackend
4
  from fastapi_users.db import SQLAlchemyUserDatabase
@@ -74,6 +73,11 @@ class UserManager(IntegerIDMixin, BaseUserManager[User, int]):
74
  ) -> UP:
75
  logger.info(f"Processing OAuth callback for {oauth_name} with account_id: {account_id}, email: {account_email}")
76
 
 
 
 
 
 
77
  oauth_account_dict = {
78
  "oauth_name": oauth_name,
79
  "access_token": access_token,
@@ -104,12 +108,17 @@ class UserManager(IntegerIDMixin, BaseUserManager[User, int]):
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
 
@@ -119,7 +128,7 @@ class UserManager(IntegerIDMixin, BaseUserManager[User, int]):
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,
@@ -128,14 +137,24 @@ class UserManager(IntegerIDMixin, BaseUserManager[User, int]):
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
@@ -147,15 +166,25 @@ class UserManager(IntegerIDMixin, BaseUserManager[User, int]):
147
  "is_verified": is_verified_by_default,
148
  }
149
  user = User(**user_dict)
150
- self.user_db.session.add(user)
151
- self.user_db.session.commit()
152
- self.user_db.session.refresh(user)
153
- logger.info(f"Created new user: {user.email}")
 
 
 
 
 
154
 
155
  oauth_account.user_id = user.id
156
- self.user_db.session.add(oauth_account)
157
- self.user_db.session.commit()
158
- logger.info(f"Linked OAuth account to new user: {user.email}")
 
 
 
 
 
159
  return await self.on_after_login(user, request)
160
 
161
  async def get_user_manager(user_db: SQLAlchemyUserDatabase = Depends(get_user_db)):
 
 
1
  from fastapi_users import FastAPIUsers
2
  from fastapi_users.authentication import CookieTransport, JWTStrategy, AuthenticationBackend
3
  from fastapi_users.db import SQLAlchemyUserDatabase
 
73
  ) -> UP:
74
  logger.info(f"Processing OAuth callback for {oauth_name} with account_id: {account_id}, email: {account_email}")
75
 
76
+ # Validate inputs
77
+ if not account_email or not account_id:
78
+ logger.error(f"Invalid OAuth callback data: email={account_email}, account_id={account_id}")
79
+ raise ValueError("Invalid OAuth callback data: email and account_id are required.")
80
+
81
  oauth_account_dict = {
82
  "oauth_name": oauth_name,
83
  "access_token": access_token,
 
108
  "is_verified": is_verified_by_default,
109
  }
110
  user = User(**user_dict)
111
+ try:
112
+ self.user_db.session.add(user)
113
+ self.user_db.session.commit()
114
+ self.user_db.session.refresh(user)
115
+ existing_oauth_account.user_id = user.id
116
+ self.user_db.session.commit()
117
+ logger.info(f"Created new user and linked to existing OAuth account: {user.email}")
118
+ except Exception as e:
119
+ self.user_db.session.rollback()
120
+ logger.error(f"Failed to create user for OAuth account {account_id}: {e}")
121
+ raise ValueError(f"Failed to create user: {e}")
122
  logger.info(f"Returning existing user: {user.email}")
123
  return await self.on_after_login(user, request)
124
 
 
128
  result = self.user_db.session.execute(statement)
129
  user = result.scalars().first()
130
  if user is None:
131
+ logger.info(f"No user found for email {account_email}. Creating new user.")
132
  # Create new user if not found
133
  user_dict = {
134
  "email": account_email,
 
137
  "is_verified": is_verified_by_default,
138
  }
139
  user = User(**user_dict)
140
+ try:
141
+ self.user_db.session.add(user)
142
+ self.user_db.session.commit()
143
+ self.user_db.session.refresh(user)
144
+ logger.info(f"Created new user for email: {user.email}")
145
+ except Exception as e:
146
+ self.user_db.session.rollback()
147
+ logger.error(f"Failed to create user for email {account_email}: {e}")
148
+ raise ValueError(f"Failed to create user: {e}")
149
  oauth_account.user_id = user.id
150
+ try:
151
+ self.user_db.session.add(oauth_account)
152
+ self.user_db.session.commit()
153
+ logger.info(f"Associated OAuth account with user: {user.email}")
154
+ except Exception as e:
155
+ self.user_db.session.rollback()
156
+ logger.error(f"Failed to associate OAuth account with user {user.email}: {e}")
157
+ raise ValueError(f"Failed to associate OAuth account: {e}")
158
  return await self.on_after_login(user, request)
159
 
160
  # Create new user
 
166
  "is_verified": is_verified_by_default,
167
  }
168
  user = User(**user_dict)
169
+ try:
170
+ self.user_db.session.add(user)
171
+ self.user_db.session.commit()
172
+ self.user_db.session.refresh(user)
173
+ logger.info(f"Created new user: {user.email}")
174
+ except Exception as e:
175
+ self.user_db.session.rollback()
176
+ logger.error(f"Failed to create user for email {account_email}: {e}")
177
+ raise ValueError(f"Failed to create user: {e}")
178
 
179
  oauth_account.user_id = user.id
180
+ try:
181
+ self.user_db.session.add(oauth_account)
182
+ self.user_db.session.commit()
183
+ logger.info(f"Linked OAuth account to new user: {user.email}")
184
+ except Exception as e:
185
+ self.user_db.session.rollback()
186
+ logger.error(f"Failed to link OAuth account to user {user.email}: {e}")
187
+ raise ValueError(f"Failed to link OAuth account: {e}")
188
  return await self.on_after_login(user, request)
189
 
190
  async def get_user_manager(user_db: SQLAlchemyUserDatabase = Depends(get_user_db)):
static/js/chat.js CHANGED
@@ -236,7 +236,7 @@ function startVoiceRecording() {
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,8 +259,8 @@ function stopVoiceRecording() {
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');
@@ -277,8 +277,8 @@ async function submitAudioMessage(formData) {
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,7 +291,7 @@ async function submitAudioMessage(formData) {
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();
@@ -316,22 +316,22 @@ async function sendRequest(endpoint, body, headers = {}) {
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
  }
@@ -361,13 +361,13 @@ function finalizeRequest() {
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,7 +381,7 @@ async function loadConversations() {
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 = '';
@@ -394,9 +394,9 @@ async function loadConversations() {
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,8 +408,8 @@ async function loadConversations() {
408
  });
409
  }
410
  } catch (error) {
411
- console.error('خطأ في تحميل المحادثات:', error);
412
- alert('فشل تحميل المحادثات. من فضلك، حاول مرة أخرى.');
413
  }
414
  }
415
 
@@ -421,11 +421,11 @@ async function loadConversation(conversationId) {
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,14 +434,14 @@ async function loadConversation(conversationId) {
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,7 +449,7 @@ async function deleteConversation(conversationId) {
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,8 +459,8 @@ async function deleteConversation(conversationId) {
459
  }
460
  await loadConversations();
461
  } catch (error) {
462
- console.error('خطأ في حذف المحادثة:', error);
463
- alert('فشل حذف المحادثة. من فضلك، حاول مرة أخرى.');
464
  }
465
  }
466
 
@@ -475,16 +475,16 @@ async function saveMessageToConversation(conversationId, role, content) {
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,14 +495,14 @@ async function createNewConversation() {
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,8 +516,8 @@ async function createNewConversation() {
516
  await loadConversations();
517
  toggleSidebar(false);
518
  } catch (error) {
519
- console.error('خطأ في إنشاء المحادثة:', error);
520
- alert('فشل إنشاء محادثة جديدة. من فضلك، حاول مرة أخرى.');
521
  }
522
  }
523
 
@@ -532,14 +532,14 @@ async function updateConversationTitle(conversationId, newTitle) {
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,7 +632,7 @@ async function submitMessage() {
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,7 +642,7 @@ async function submitMessage() {
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
  }
@@ -681,8 +681,8 @@ async function submitMessage() {
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,14 +695,14 @@ async function submitMessage() {
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,7 +715,7 @@ async function submitMessage() {
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,7 +724,7 @@ async function submitMessage() {
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,7 +737,7 @@ async function submitMessage() {
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();
@@ -749,7 +749,7 @@ async function submitMessage() {
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 });
@@ -800,7 +800,7 @@ function stopStream(forceCancel = false) {
800
  if (uiElements.settingsBtn) {
801
  uiElements.settingsBtn.addEventListener('click', () => {
802
  if (!checkAuth()) {
803
- alert('من فضلك، سجل الدخول للوصول إلى الإعدادات.');
804
  window.location.href = '/login';
805
  return;
806
  }
@@ -814,7 +814,7 @@ if (uiElements.settingsBtn) {
814
  localStorage.removeItem('token');
815
  window.location.href = '/login';
816
  }
817
- throw new Error('فشل جلب الإعدادات');
818
  }
819
  return res.json();
820
  })
@@ -846,8 +846,8 @@ if (uiElements.settingsBtn) {
846
  });
847
  })
848
  .catch(err => {
849
- console.error('خطأ في جلب الإعدادات:', err);
850
- alert('فشل تحميل الإعدادات. من فضلك، حاول مرة أخرى.');
851
  });
852
  });
853
  }
@@ -862,7 +862,7 @@ if (uiElements.settingsForm) {
862
  uiElements.settingsForm.addEventListener('submit', (e) => {
863
  e.preventDefault();
864
  if (!checkAuth()) {
865
- alert('من فضلك، سجل الدخول لحفظ الإعدادات.');
866
  window.location.href = '/login';
867
  return;
868
  }
@@ -882,18 +882,18 @@ if (uiElements.settingsForm) {
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,10 +906,10 @@ if (uiElements.historyToggle) {
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,8 +985,8 @@ if (uiElements.clearBtn) uiElements.clearBtn.addEventListener('click', clearAllM
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
  }
 
236
  mediaRecorder.addEventListener('dataavailable', event => audioChunks.push(event.data));
237
  }).catch(err => {
238
  console.error('Error accessing microphone:', err);
239
+ alert('Failed to access microphone. Please check permissions.');
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', 'Voice message');
263
+ conversationHistory.push({ role: 'user', content: 'Voice message' });
264
  sessionStorage.setItem('conversationHistory', JSON.stringify(conversationHistory));
265
  streamMsg = addMsg('assistant', '');
266
  const loadingEl = document.createElement('span');
 
277
  throw new Error(`Request failed with status ${response.status}`);
278
  }
279
  const data = await response.json();
280
+ if (!data.transcription) throw new Error('No transcription received from server');
281
+ const transcription = data.transcription || 'Error: No transcription generated.';
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 || 'Untitled Conversation';
295
  if (uiElements.conversationTitle) uiElements.conversationTitle.textContent = currentConversationTitle;
296
  history.pushState(null, '', `/chat/${currentConversationId}`);
297
  await loadConversations();
 
316
  if (!response.ok) {
317
  if (response.status === 403) {
318
  if (uiElements.messageLimitWarning) uiElements.messageLimitWarning.classList.remove('hidden');
319
+ throw new Error('Message limit reached. Please log in to continue.');
320
  }
321
  if (response.status === 401) {
322
  localStorage.removeItem('token');
323
  window.location.href = '/login';
324
+ throw new Error('Unauthorized. Please log in again.');
325
  }
326
  if (response.status === 503) {
327
+ throw new Error('Model not available. Please try another model.');
328
  }
329
+ throw new Error(`Request failed with status ${response.status}`);
330
  }
331
  return response;
332
  } catch (error) {
333
  if (error.name === 'AbortError') {
334
+ throw new Error('Request was aborted');
335
  }
336
  throw error;
337
  }
 
361
  function handleRequestError(error) {
362
  if (streamMsg) {
363
  streamMsg.querySelector('.loading')?.remove();
364
+ streamMsg.dataset.text = `Error: ${error.message || 'An error occurred during the request.'}`;
365
  renderMarkdown(streamMsg);
366
  streamMsg.dataset.done = '1';
367
  streamMsg = null;
368
  }
369
+ console.error('Request error:', error);
370
+ alert(`Error: ${error.message || 'An error occurred during the request.'}`);
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('Failed to load conversations');
385
  const conversations = await response.json();
386
  if (uiElements.conversationList) {
387
  uiElements.conversationList.innerHTML = '';
 
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 || 'Untitled Conversation'}</span>
398
  </div>
399
+ <button class="delete-conversation-btn text-red-400 hover:text-red-600 p-1" title="Delete Conversation" 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 loading conversations:', error);
412
+ alert('Failed to load conversations. Please try again.');
413
  }
414
  }
415
 
 
421
  });
422
  if (!response.ok) {
423
  if (response.status === 401) window.location.href = '/login';
424
+ throw new Error('Failed to load conversation');
425
  }
426
  const data = await response.json();
427
  currentConversationId = data.conversation_id;
428
+ currentConversationTitle = data.title || 'Untitled Conversation';
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 loading conversation:', error);
438
+ alert('Failed to load conversation. Please try again or log in.');
439
  }
440
  }
441
 
442
  // Delete conversation
443
  async function deleteConversation(conversationId) {
444
+ if (!confirm('Are you sure you want to delete this conversation?')) 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('Failed to delete conversation');
453
  }
454
  if (conversationId === currentConversationId) {
455
  clearAllMessages();
 
459
  }
460
  await loadConversations();
461
  } catch (error) {
462
+ console.error('Error deleting conversation:', error);
463
+ alert('Failed to delete conversation. Please try again.');
464
  }
465
  }
466
 
 
475
  },
476
  body: JSON.stringify({ role, content })
477
  });
478
+ if (!response.ok) throw new Error('Failed to save message');
479
  } catch (error) {
480
+ console.error('Error saving message:', error);
481
  }
482
  }
483
 
484
  // Create new conversation
485
  async function createNewConversation() {
486
  if (!checkAuth()) {
487
+ alert('Please log in to create a new conversation.');
488
  window.location.href = '/login';
489
  return;
490
  }
 
495
  'Content-Type': 'application/json',
496
  'Authorization': `Bearer ${checkAuth()}`
497
  },
498
+ body: JSON.stringify({ title: 'New Conversation' })
499
  });
500
  if (!response.ok) {
501
  if (response.status === 401) {
502
  localStorage.removeItem('token');
503
  window.location.href = '/login';
504
  }
505
+ throw new Error('Failed to create conversation');
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 creating conversation:', error);
520
+ alert('Failed to create new conversation. Please try again.');
521
  }
522
  }
523
 
 
532
  },
533
  body: JSON.stringify({ title: newTitle })
534
  });
535
+ if (!response.ok) throw new Error('Failed to update title');
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 updating title:', error);
542
+ alert('Failed to update conversation title.');
543
  }
544
  }
545
 
 
632
  if (file.type.startsWith('image/')) {
633
  endpoint = '/api/image-analysis';
634
  inputType = 'image';
635
+ message = 'Analyze this image';
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 = 'Transcribe this audio';
646
  formData = new FormData();
647
  formData.append('file', file);
648
  }
 
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('No transcription received from server');
685
+ const transcription = data.transcription || 'Error: No transcription generated.';
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 || 'Untitled Conversation';
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 || 'Error: No analysis generated.';
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 || 'Untitled Conversation';
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 || 'Error: No response generated.';
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 || 'Untitled Conversation';
741
  if (uiElements.conversationTitle) uiElements.conversationTitle.textContent = currentConversationTitle;
742
  history.pushState(null, '', `/chat/${currentConversationId}`);
743
  await loadConversations();
 
749
  while (true) {
750
  const { done, value } = await reader.read();
751
  if (done) {
752
+ if (!buffer.trim()) throw new Error('Empty response from server');
753
  break;
754
  }
755
  buffer += decoder.decode(value, { stream: true });
 
800
  if (uiElements.settingsBtn) {
801
  uiElements.settingsBtn.addEventListener('click', () => {
802
  if (!checkAuth()) {
803
+ alert('Please log in to access settings.');
804
  window.location.href = '/login';
805
  return;
806
  }
 
814
  localStorage.removeItem('token');
815
  window.location.href = '/login';
816
  }
817
+ throw new Error('Failed to fetch settings');
818
  }
819
  return res.json();
820
  })
 
846
  });
847
  })
848
  .catch(err => {
849
+ console.error('Error fetching settings:', err);
850
+ alert('Failed to load settings. Please try again.');
851
  });
852
  });
853
  }
 
862
  uiElements.settingsForm.addEventListener('submit', (e) => {
863
  e.preventDefault();
864
  if (!checkAuth()) {
865
+ alert('Please log in to save settings.');
866
  window.location.href = '/login';
867
  return;
868
  }
 
882
  localStorage.removeItem('token');
883
  window.location.href = '/login';
884
  }
885
+ throw new Error('Failed to update settings');
886
  }
887
  return res.json();
888
  })
889
  .then(() => {
890
+ alert('Settings updated successfully!');
891
  uiElements.settingsModal.classList.add('hidden');
892
  toggleSidebar(false);
893
  })
894
  .catch(err => {
895
+ console.error('Error updating settings:', err);
896
+ alert('Error updating settings: ' + 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>Show History`
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>Hide History`;
913
  }
914
  });
915
  }
 
985
 
986
  if (uiElements.conversationTitle) {
987
  uiElements.conversationTitle.addEventListener('click', () => {
988
+ if (!checkAuth()) return alert('Please log in to edit the conversation title.');
989
+ const newTitle = prompt('Enter new conversation title:', currentConversationTitle || '');
990
  if (newTitle && currentConversationId) {
991
  updateConversationTitle(currentConversationId, newTitle);
992
  }