Mark-Lasfar commited on
Commit
bd1eb98
·
1 Parent(s): 8879157
Files changed (2) hide show
  1. static/css/chat/bubble.css +35 -4
  2. static/js/chat.js +113 -46
static/css/chat/bubble.css CHANGED
@@ -4,7 +4,7 @@
4
  */
5
 
6
  .bubble {
7
- display: block !important; /* إجبار العرض لتجنب التعارض مع hidden */
8
  align-self: flex-start;
9
  background: #1f1f1f;
10
  color: #e8e8e8;
@@ -14,8 +14,8 @@
14
  font-family: 'Inter', sans-serif;
15
  font-size: 0.95rem;
16
  position: relative;
17
- opacity: 1 !important; /* إزالة أي خفاء افتراضي */
18
- animation: bubbleAppear 0.3s cubic-bezier(0.4, 0, 0.2, 1) forwards; /* الاحتفاظ بالأنيميشن لكن بدون تأخير */
19
  margin: 0.75rem 0;
20
  overflow-wrap: break-word;
21
  word-break: break-word;
@@ -26,7 +26,8 @@
26
  box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
27
  border: 1px solid rgba(255, 255, 255, 0.05);
28
  transition: all 0.3s ease;
29
- min-height: 20px; /* ضمان ارتفاع أدنى للظهور */
 
30
  }
31
 
32
  .bubble:hover {
@@ -58,4 +59,34 @@
58
  background: transparent;
59
  border: none;
60
  box-shadow: none;
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
61
  }
 
4
  */
5
 
6
  .bubble {
7
+ display: block !important;
8
  align-self: flex-start;
9
  background: #1f1f1f;
10
  color: #e8e8e8;
 
14
  font-family: 'Inter', sans-serif;
15
  font-size: 0.95rem;
16
  position: relative;
17
+ opacity: 1 !important;
18
+ animation: bubbleAppear 0.3s cubic-bezier(0.4, 0, 0.2, 1) forwards;
19
  margin: 0.75rem 0;
20
  overflow-wrap: break-word;
21
  word-break: break-word;
 
26
  box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
27
  border: 1px solid rgba(255, 255, 255, 0.05);
28
  transition: all 0.3s ease;
29
+ min-height: 20px;
30
+ user-select: text; /* للسماح بتحديد النصوص */
31
  }
32
 
33
  .bubble:hover {
 
59
  background: transparent;
60
  border: none;
61
  box-shadow: none;
62
+ }
63
+
64
+ .message-container {
65
+ margin: 1rem 0;
66
+ border-bottom: 1px solid rgba(255, 255, 255, 0.1);
67
+ }
68
+
69
+ .message-actions {
70
+ display: flex;
71
+ gap: 0.5rem;
72
+ margin-top: 0.5rem;
73
+ opacity: 0;
74
+ transition: opacity 0.3s ease;
75
+ }
76
+
77
+ .message-container:hover .message-actions {
78
+ opacity: 1;
79
+ }
80
+
81
+ .action-btn {
82
+ background: none;
83
+ border: none;
84
+ cursor: pointer;
85
+ color: #3b82f6;
86
+ font-size: 0.9rem;
87
+ padding: 0.2rem 0.5rem;
88
+ }
89
+
90
+ .action-btn:hover {
91
+ color: #60a5fa;
92
  }
static/js/chat.js CHANGED
@@ -214,18 +214,58 @@ function leaveChatView() {
214
 
215
  // Add chat bubble
216
  function addMsg(who, text) {
 
 
217
  const div = document.createElement('div');
218
  div.className = `bubble ${who === 'user' ? 'bubble-user' : 'bubble-assist'} ${isArabicText(text) ? 'rtl' : ''}`;
219
  div.dataset.text = text;
220
  console.log('Adding message:', { who, text });
221
  renderMarkdown(div);
222
  div.style.display = 'block';
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
223
  if (uiElements.chatBox) {
224
- uiElements.chatBox.appendChild(div);
225
  uiElements.chatBox.scrollTop = uiElements.chatBox.scrollHeight;
226
  } else {
227
  console.error('chatBox not found, appending to a fallback container');
228
- document.body.appendChild(div); // فال باك إذا فشل العثور
229
  }
230
  return div;
231
  }
@@ -711,7 +751,7 @@ async function submitMessage() {
711
  return;
712
  }
713
 
714
- enterChatView(); // دايمًا إظهار المحادثة قبل الإرسال
715
 
716
  if (uiElements.fileInput?.files.length > 0) {
717
  const file = uiElements.fileInput.files[0];
@@ -764,55 +804,37 @@ async function submitMessage() {
764
  try {
765
  const response = await sendRequest(endpoint, payload ? JSON.stringify(payload) : formData, headers);
766
  let responseText = '';
767
- if (endpoint === '/api/audio-transcription') {
768
- const data = await response.json();
769
- if (!data.transcription) throw new Error('No transcription received from server');
770
- responseText = data.transcription || 'Error: No transcription generated.';
771
- } else if (endpoint === '/api/image-analysis') {
772
  const data = await response.json();
773
- responseText = data.image_analysis || 'Error: No analysis generated.';
774
  } else {
775
- const contentType = response.headers.get('Content-Type');
776
- if (contentType?.includes('application/json')) {
777
- const data = await response.json();
778
- responseText = data.response || 'Error: No response generated.';
779
- if (data.conversation_id) {
780
- currentConversationId = data.conversation_id;
781
- currentConversationTitle = data.conversation_title || 'Untitled Conversation';
782
- if (uiElements.conversationTitle) uiElements.conversationTitle.textContent = currentConversationTitle;
783
- history.pushState(null, '', `/chat/${currentConversationId}`);
784
- await loadConversations();
 
785
  }
786
- } else {
787
- const reader = response.body.getReader();
788
- const decoder = new TextDecoder();
789
- let buffer = '';
790
- streamMsg.dataset.text = '';
791
- streamMsg.querySelector('.loading')?.remove();
792
-
793
- while (true) {
794
- const { done, value } = await reader.read();
795
- if (done) {
796
- if (!buffer.trim()) throw new Error('Empty response from server');
797
- break;
798
- }
799
- const chunk = decoder.decode(value, { stream: true });
800
- buffer += chunk;
801
- console.log('Received chunk:', chunk);
802
-
803
- if (streamMsg) {
804
- streamMsg.dataset.text = buffer;
805
- currentAssistantText = buffer;
806
- renderMarkdown(streamMsg);
807
- streamMsg.style.opacity = '1';
808
- if (uiElements.chatBox) {
809
- uiElements.chatBox.style.display = 'flex';
810
- uiElements.chatBox.scrollTop = uiElements.chatBox.scrollHeight;
811
- }
812
  }
 
813
  }
814
- responseText = buffer;
815
  }
 
816
  }
817
 
818
  if (streamMsg) {
@@ -824,12 +846,57 @@ async function submitMessage() {
824
  conversationHistory.push({ role: 'assistant', content: responseText });
825
  sessionStorage.setItem('conversationHistory', JSON.stringify(conversationHistory));
826
  }
 
 
 
 
 
 
 
827
  finalizeRequest();
828
  } catch (error) {
829
  handleRequestError(error);
830
  }
831
  }
832
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
833
  // Stop streaming
834
  function stopStream(forceCancel = false) {
835
  if (!isRequestActive && !isRecording) return;
 
214
 
215
  // Add chat bubble
216
  function addMsg(who, text) {
217
+ const container = document.createElement('div');
218
+ container.className = 'message-container';
219
  const div = document.createElement('div');
220
  div.className = `bubble ${who === 'user' ? 'bubble-user' : 'bubble-assist'} ${isArabicText(text) ? 'rtl' : ''}`;
221
  div.dataset.text = text;
222
  console.log('Adding message:', { who, text });
223
  renderMarkdown(div);
224
  div.style.display = 'block';
225
+
226
+ // إضافة أيقونات التحكم
227
+ const actions = document.createElement('div');
228
+ actions.className = 'message-actions';
229
+ if (who === 'assistant') {
230
+ const retryBtn = document.createElement('button');
231
+ retryBtn.className = 'action-btn';
232
+ retryBtn.innerHTML = '<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor"><path d="M4 4v5h.582m15.356 2A8.001 8.001 0 004.582 9m0 0H9m11 11v-5h-.581m0 0a8.003 8.003 0 01-15.357-2m15.357 2H15"></path></svg>';
233
+ retryBtn.title = 'Retry';
234
+ retryBtn.onclick = () => submitMessage();
235
+ actions.appendChild(retryBtn);
236
+
237
+ const copyBtn = document.createElement('button');
238
+ copyBtn.className = 'action-btn';
239
+ copyBtn.innerHTML = '<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor"><path d="M8 16H6a2 2 0 01-2-2V6a2 2 0 012-2h8a2 2 0 012 2v2m-6 12h8a2 2 0 002-2v-8a2 2 0 00-2-2h-8a2 2 0 00-2 2v8a2 2 0 002 2z"></path></svg>';
240
+ copyBtn.title = 'Copy Response';
241
+ copyBtn.onclick = () => navigator.clipboard.writeText(text);
242
+ actions.appendChild(copyBtn);
243
+ }
244
+ if (who === 'user') {
245
+ const editBtn = document.createElement('button');
246
+ editBtn.className = 'action-btn';
247
+ editBtn.innerHTML = '<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor"><path d="M11 4H4a2 2 0 00-2 2v14a2 2 0 002 2h14a2 2 0 002-2v-7M18.5 2.5a2.121 2.121 0 013 3L12 15l-4 1 1-4 9.5-9.5z"></path></svg>';
248
+ editBtn.title = 'Edit Question';
249
+ editBtn.onclick = () => {
250
+ const newText = prompt('Edit your question:', text);
251
+ if (newText) {
252
+ div.dataset.text = newText;
253
+ renderMarkdown(div);
254
+ conversationHistory = conversationHistory.map(msg => msg.role === 'user' && msg.content === text ? { role: 'user', content: newText } : msg);
255
+ sessionStorage.setItem('conversationHistory', JSON.stringify(conversationHistory));
256
+ }
257
+ };
258
+ actions.appendChild(editBtn);
259
+ }
260
+
261
+ container.appendChild(div);
262
+ container.appendChild(actions);
263
  if (uiElements.chatBox) {
264
+ uiElements.chatBox.appendChild(container);
265
  uiElements.chatBox.scrollTop = uiElements.chatBox.scrollHeight;
266
  } else {
267
  console.error('chatBox not found, appending to a fallback container');
268
+ document.body.appendChild(container);
269
  }
270
  return div;
271
  }
 
751
  return;
752
  }
753
 
754
+ enterChatView();
755
 
756
  if (uiElements.fileInput?.files.length > 0) {
757
  const file = uiElements.fileInput.files[0];
 
804
  try {
805
  const response = await sendRequest(endpoint, payload ? JSON.stringify(payload) : formData, headers);
806
  let responseText = '';
807
+ if (endpoint === '/api/audio-transcription' || endpoint === '/api/image-analysis') {
 
 
 
 
808
  const data = await response.json();
809
+ responseText = data.transcription || data.image_analysis || 'Error: No response generated.';
810
  } else {
811
+ const reader = response.body.getReader();
812
+ const decoder = new TextDecoder();
813
+ let buffer = '';
814
+ streamMsg.dataset.text = '';
815
+ streamMsg.querySelector('.loading')?.remove();
816
+
817
+ while (true) {
818
+ const { done, value } = await reader.read();
819
+ if (done) {
820
+ if (!buffer.trim()) throw new Error('Empty response from server');
821
+ break;
822
  }
823
+ const chunk = decoder.decode(value, { stream: true });
824
+ buffer += chunk;
825
+ console.log('Received chunk:', chunk);
826
+
827
+ if (streamMsg) {
828
+ streamMsg.dataset.text = buffer;
829
+ currentAssistantText = buffer;
830
+ renderMarkdown(streamMsg);
831
+ if (uiElements.chatBox) {
832
+ uiElements.chatBox.scrollTop = uiElements.chatBox.scrollHeight;
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
833
  }
834
+ await new Promise(resolve => setTimeout(resolve, 20)); // تأخير لتأثير الكتابة
835
  }
 
836
  }
837
+ responseText = buffer;
838
  }
839
 
840
  if (streamMsg) {
 
846
  conversationHistory.push({ role: 'assistant', content: responseText });
847
  sessionStorage.setItem('conversationHistory', JSON.stringify(conversationHistory));
848
  }
849
+ if (checkAuth() && response.headers.get('X-Conversation-ID')) {
850
+ currentConversationId = response.headers.get('X-Conversation-ID');
851
+ currentConversationTitle = response.headers.get('X-Conversation-Title') || 'Untitled Conversation';
852
+ if (uiElements.conversationTitle) uiElements.conversationTitle.textContent = currentConversationTitle;
853
+ history.pushState(null, '', `/chat/${currentConversationId}`);
854
+ await loadConversations();
855
+ }
856
  finalizeRequest();
857
  } catch (error) {
858
  handleRequestError(error);
859
  }
860
  }
861
 
862
+ let attemptCount = 0;
863
+ function addAttemptHistory(who, text) {
864
+ attemptCount++;
865
+ const container = document.createElement('div');
866
+ container.className = 'message-container';
867
+ const div = document.createElement('div');
868
+ div.className = `bubble ${who === 'user' ? 'bubble-user' : 'bubble-assist'} ${isArabicText(text) ? 'rtl' : ''}`;
869
+ div.dataset.text = text;
870
+ renderMarkdown(div);
871
+
872
+ const historyActions = document.createElement('div');
873
+ historyActions.className = 'message-actions';
874
+ const prevBtn = document.createElement('button');
875
+ prevBtn.className = 'action-btn';
876
+ prevBtn.innerHTML = '<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor"><path d="M15 19l-7-7 7-7"></path></svg>';
877
+ prevBtn.title = 'Previous Attempt';
878
+ prevBtn.onclick = () => {
879
+ // تنفيذ الرجوع لمحاولة سابقة (يحتاج تطوير إضافي)
880
+ };
881
+ const nextBtn = document.createElement('button');
882
+ nextBtn.className = 'action-btn';
883
+ nextBtn.innerHTML = '<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor"><path d="M9 5l7 7-7 7"></path></svg>';
884
+ nextBtn.title = 'Next Attempt';
885
+ nextBtn.onclick = () => {
886
+ // تنفيذ الذهاب لمحاولة لاحقة (يحتاج تطوير إضافي)
887
+ };
888
+ historyActions.appendChild(prevBtn);
889
+ historyActions.appendChild(document.createTextNode(`Attempt ${attemptCount}`));
890
+ historyActions.appendChild(nextBtn);
891
+
892
+ container.appendChild(div);
893
+ container.appendChild(historyActions);
894
+ if (uiElements.chatBox) {
895
+ uiElements.chatBox.appendChild(container);
896
+ uiElements.chatBox.scrollTop = uiElements.chatBox.scrollHeight;
897
+ }
898
+ }
899
+
900
  // Stop streaming
901
  function stopStream(forceCancel = false) {
902
  if (!isRequestActive && !isRecording) return;