Mark-Lasfar commited on
Commit
19bd628
·
1 Parent(s): cf6fa01
Files changed (1) hide show
  1. static/js/chat.js +110 -62
static/js/chat.js CHANGED
@@ -155,6 +155,21 @@ function renderMarkdown(el) {
155
  div.appendChild(t);
156
  }
157
  });
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
158
  wrapper.querySelectorAll('hr').forEach(h => h.classList.add('styled-hr'));
159
  Prism.highlightAllUnder(wrapper);
160
  if (uiElements.chatBox) {
@@ -226,21 +241,31 @@ function addMsg(who, text) {
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';
@@ -751,7 +776,7 @@ async function submitMessage() {
751
  return;
752
  }
753
 
754
- enterChatView();
755
 
756
  if (uiElements.fileInput?.files.length > 0) {
757
  const file = uiElements.fileInput.files[0];
@@ -793,57 +818,85 @@ async function submitMessage() {
793
  sessionStorage.setItem('conversationHistory', JSON.stringify(conversationHistory));
794
  }
795
  streamMsg = addMsg('assistant', '');
796
- const loadingEl = document.createElement('span');
797
- loadingEl.className = 'loading';
798
- streamMsg.appendChild(loadingEl);
 
799
  updateUIForRequest();
800
 
801
  isRequestActive = true;
802
  abortController = new AbortController();
803
- let attemptHistory = [];
804
 
805
  try {
806
  const response = await sendRequest(endpoint, payload ? JSON.stringify(payload) : formData, headers);
807
  let responseText = '';
808
- if (endpoint === '/api/audio-transcription' || endpoint === '/api/image-analysis') {
809
  const data = await response.json();
810
- responseText = data.transcription || data.image_analysis || 'Error: No response generated.';
 
 
 
 
 
 
 
 
 
 
811
  } else {
812
- const reader = response.body.getReader();
813
- const decoder = new TextDecoder();
814
- let buffer = '';
815
- streamMsg.dataset.text = '';
816
- streamMsg.querySelector('.loading')?.remove();
817
-
818
- while (true) {
819
- const { done, value } = await reader.read();
820
- if (done) {
821
- if (!buffer.trim()) throw new Error('Empty response from server');
822
- break;
823
  }
824
- const chunk = decoder.decode(value, { stream: true });
825
- buffer += chunk;
826
- console.log('Received chunk:', chunk);
827
-
828
- if (streamMsg) {
829
- streamMsg.dataset.text = buffer;
830
- currentAssistantText = buffer;
831
- renderMarkdown(streamMsg);
832
- if (uiElements.chatBox) {
833
- uiElements.chatBox.scrollTop = uiElements.chatBox.scrollHeight;
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
834
  }
835
- await new Promise(resolve => setTimeout(resolve, 20)); // تأخير لتأثير الكتابة
836
  }
 
837
  }
838
- responseText = buffer;
839
  }
840
 
 
 
 
841
  if (streamMsg) {
842
  streamMsg.dataset.text = responseText;
843
  renderMarkdown(streamMsg);
844
  streamMsg.dataset.done = '1';
845
  }
846
- attemptHistory.push({ role: 'assistant', content: responseText });
847
  if (!(await checkAuth())) {
848
  conversationHistory.push({ role: 'assistant', content: responseText });
849
  sessionStorage.setItem('conversationHistory', JSON.stringify(conversationHistory));
@@ -851,25 +904,20 @@ async function submitMessage() {
851
  finalizeRequest();
852
  } catch (error) {
853
  handleRequestError(error);
854
- attemptHistory.push({ role: 'assistant', content: `Error: ${error.message || 'An error occurred'}` });
855
- }
856
-
857
- // تخزين تاريخ المحاولات
858
- if (attemptHistory.length > 0) {
859
- attemptHistory.forEach((attempt, index) => {
860
- addAttemptHistory(attempt.role, attempt.content, index + 1);
861
- });
862
  }
863
  }
864
 
865
  let attemptCount = 0;
866
- function addAttemptHistory(who, text, attemptNum) {
 
 
867
  attemptCount++;
 
868
  const container = document.createElement('div');
869
  container.className = 'message-container';
870
  const div = document.createElement('div');
871
  div.className = `bubble ${who === 'user' ? 'bubble-user' : 'bubble-assist'} ${isArabicText(text) ? 'rtl' : ''}`;
872
- div.dataset.text = text;
873
  renderMarkdown(div);
874
 
875
  const historyActions = document.createElement('div');
@@ -879,12 +927,10 @@ function addAttemptHistory(who, text, attemptNum) {
879
  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>';
880
  prevBtn.title = 'Previous Attempt';
881
  prevBtn.onclick = () => {
882
- if (attemptNum > 1) {
883
- const prevAttempt = attemptHistory[attemptNum - 2];
884
- if (prevAttempt) {
885
- streamMsg.dataset.text = prevAttempt.content;
886
- renderMarkdown(streamMsg);
887
- }
888
  }
889
  };
890
  const nextBtn = document.createElement('button');
@@ -892,16 +938,14 @@ function addAttemptHistory(who, text, attemptNum) {
892
  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>';
893
  nextBtn.title = 'Next Attempt';
894
  nextBtn.onclick = () => {
895
- if (attemptNum < attemptHistory.length) {
896
- const nextAttempt = attemptHistory[attemptNum];
897
- if (nextAttempt) {
898
- streamMsg.dataset.text = nextAttempt.content;
899
- renderMarkdown(streamMsg);
900
- }
901
  }
902
  };
903
  historyActions.appendChild(prevBtn);
904
- historyActions.appendChild(document.createTextNode(`Attempt ${attemptNum}`));
905
  historyActions.appendChild(nextBtn);
906
 
907
  container.appendChild(div);
@@ -909,7 +953,11 @@ function addAttemptHistory(who, text, attemptNum) {
909
  if (uiElements.chatBox) {
910
  uiElements.chatBox.appendChild(container);
911
  uiElements.chatBox.scrollTop = uiElements.chatBox.scrollHeight;
 
 
 
912
  }
 
913
  }
914
 
915
  // Stop streaming
 
155
  div.appendChild(t);
156
  }
157
  });
158
+ // إضافة زر نسخ لكل بلوك كود
159
+ wrapper.querySelectorAll('pre').forEach(pre => {
160
+ const copyBtn = document.createElement('button');
161
+ copyBtn.className = 'copy-btn';
162
+ 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>';
163
+ copyBtn.title = 'Copy Code';
164
+ copyBtn.onclick = () => {
165
+ navigator.clipboard.writeText(pre.querySelector('code').innerText).then(() => {
166
+ copyBtn.textContent = 'Copied!';
167
+ setTimeout(() => { 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>'; }, 2000);
168
+ });
169
+ };
170
+ pre.appendChild(copyBtn);
171
+ });
172
+
173
  wrapper.querySelectorAll('hr').forEach(h => h.classList.add('styled-hr'));
174
  Prism.highlightAllUnder(wrapper);
175
  if (uiElements.chatBox) {
 
241
  // إضافة أيقونات التحكم
242
  const actions = document.createElement('div');
243
  actions.className = 'message-actions';
244
+
245
+ // زر نسخ الرد
246
+ const copyBtn = document.createElement('button');
247
+ copyBtn.className = 'action-btn';
248
+ 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>';
249
+ copyBtn.title = 'Copy Response';
250
+ copyBtn.onclick = () => {
251
+ navigator.clipboard.writeText(text).then(() => {
252
+ copyBtn.textContent = 'Copied!';
253
+ setTimeout(() => { 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>'; }, 2000);
254
+ });
255
+ };
256
+ actions.appendChild(copyBtn);
257
+
258
+ // زر إعادة المحاولة (للمساعد فقط)
259
  if (who === 'assistant') {
260
  const retryBtn = document.createElement('button');
261
  retryBtn.className = 'action-btn';
262
  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>';
263
  retryBtn.title = 'Retry';
264
+ retryBtn.onclick = () => submitMessage(); // إعادة إرسال السؤال
265
  actions.appendChild(retryBtn);
 
 
 
 
 
 
 
266
  }
267
+
268
+ // زر تعديل السؤال (للمستخدم فقط)
269
  if (who === 'user') {
270
  const editBtn = document.createElement('button');
271
  editBtn.className = 'action-btn';
 
776
  return;
777
  }
778
 
779
+ enterChatView(); // دايمًا إظهار المحادثة قبل الإرسال
780
 
781
  if (uiElements.fileInput?.files.length > 0) {
782
  const file = uiElements.fileInput.files[0];
 
818
  sessionStorage.setItem('conversationHistory', JSON.stringify(conversationHistory));
819
  }
820
  streamMsg = addMsg('assistant', '');
821
+ const thinkingEl = document.createElement('span');
822
+ thinkingEl.className = 'thinking';
823
+ thinkingEl.textContent = 'The model is thinking...';
824
+ streamMsg.appendChild(thinkingEl);
825
  updateUIForRequest();
826
 
827
  isRequestActive = true;
828
  abortController = new AbortController();
829
+ const startTime = Date.now();
830
 
831
  try {
832
  const response = await sendRequest(endpoint, payload ? JSON.stringify(payload) : formData, headers);
833
  let responseText = '';
834
+ if (endpoint === '/api/audio-transcription') {
835
  const data = await response.json();
836
+ if (!data.transcription) throw new Error('No transcription received from server');
837
+ responseText = data.transcription || 'Error: No transcription generated.';
838
+ streamMsg.dataset.text = responseText;
839
+ renderMarkdown(streamMsg);
840
+ streamMsg.dataset.done = '1';
841
+ } else if (endpoint === '/api/image-analysis') {
842
+ const data = await response.json();
843
+ responseText = data.image_analysis || 'Error: No analysis generated.';
844
+ streamMsg.dataset.text = responseText;
845
+ renderMarkdown(streamMsg);
846
+ streamMsg.dataset.done = '1';
847
  } else {
848
+ const contentType = response.headers.get('Content-Type');
849
+ if (contentType?.includes('application/json')) {
850
+ const data = await response.json();
851
+ responseText = data.response || 'Error: No response generated.';
852
+ if (data.conversation_id) {
853
+ currentConversationId = data.conversation_id;
854
+ currentConversationTitle = data.conversation_title || 'Untitled Conversation';
855
+ if (uiElements.conversationTitle) uiElements.conversationTitle.textContent = currentConversationTitle;
856
+ history.pushState(null, '', `/chat/${currentConversationId}`);
857
+ await loadConversations();
 
858
  }
859
+ } else {
860
+ const reader = response.body.getReader();
861
+ const decoder = new TextDecoder();
862
+ let buffer = '';
863
+ streamMsg.dataset.text = '';
864
+ streamMsg.querySelector('.thinking')?.remove();
865
+
866
+ while (true) {
867
+ const { done, value } = await reader.read();
868
+ if (done) {
869
+ if (!buffer.trim()) throw new Error('Empty response from server');
870
+ break;
871
+ }
872
+ const chunk = decoder.decode(value, { stream: true });
873
+ buffer += chunk;
874
+ console.log('Received chunk:', chunk);
875
+
876
+ if (streamMsg) {
877
+ streamMsg.dataset.text = buffer;
878
+ currentAssistantText = buffer;
879
+ renderMarkdown(streamMsg);
880
+ streamMsg.style.opacity = '1';
881
+ if (uiElements.chatBox) {
882
+ uiElements.chatBox.style.display = 'flex';
883
+ uiElements.chatBox.scrollTop = uiElements.chatBox.scrollHeight;
884
+ }
885
+ await new Promise(resolve => setTimeout(resolve, 30)); // تأخير 30ms لتأثير الكتابة الطبيعي
886
  }
 
887
  }
888
+ responseText = buffer;
889
  }
 
890
  }
891
 
892
+ const endTime = DateTime.now();
893
+ const thinkingTime = Math.round((endTime - startTime) / 1000); // حساب الوقت بالثواني
894
+ streamMsg.dataset.text += `\n\n*Processed in ${thinkingTime} seconds.*`;
895
  if (streamMsg) {
896
  streamMsg.dataset.text = responseText;
897
  renderMarkdown(streamMsg);
898
  streamMsg.dataset.done = '1';
899
  }
 
900
  if (!(await checkAuth())) {
901
  conversationHistory.push({ role: 'assistant', content: responseText });
902
  sessionStorage.setItem('conversationHistory', JSON.stringify(conversationHistory));
 
904
  finalizeRequest();
905
  } catch (error) {
906
  handleRequestError(error);
 
 
 
 
 
 
 
 
907
  }
908
  }
909
 
910
  let attemptCount = 0;
911
+ let attempts = []; // لتخزين الردود السابقة
912
+
913
+ function addAttemptHistory(who, text) {
914
  attemptCount++;
915
+ attempts.push(text); // تخزين الرد
916
  const container = document.createElement('div');
917
  container.className = 'message-container';
918
  const div = document.createElement('div');
919
  div.className = `bubble ${who === 'user' ? 'bubble-user' : 'bubble-assist'} ${isArabicText(text) ? 'rtl' : ''}`;
920
+ div.dataset.text = '';
921
  renderMarkdown(div);
922
 
923
  const historyActions = document.createElement('div');
 
927
  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>';
928
  prevBtn.title = 'Previous Attempt';
929
  prevBtn.onclick = () => {
930
+ if (attemptCount > 1) {
931
+ attemptCount--;
932
+ div.dataset.text = attempts[attemptCount - 1];
933
+ renderMarkdown(div);
 
 
934
  }
935
  };
936
  const nextBtn = document.createElement('button');
 
938
  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>';
939
  nextBtn.title = 'Next Attempt';
940
  nextBtn.onclick = () => {
941
+ if (attemptCount < attempts.length) {
942
+ attemptCount++;
943
+ div.dataset.text = attempts[attemptCount - 1];
944
+ renderMarkdown(div);
 
 
945
  }
946
  };
947
  historyActions.appendChild(prevBtn);
948
+ historyActions.appendChild(document.createTextNode(`Attempt ${attemptCount}`));
949
  historyActions.appendChild(nextBtn);
950
 
951
  container.appendChild(div);
 
953
  if (uiElements.chatBox) {
954
  uiElements.chatBox.appendChild(container);
955
  uiElements.chatBox.scrollTop = uiElements.chatBox.scrollHeight;
956
+ } else {
957
+ console.error('chatBox not found, appending to a fallback container');
958
+ document.body.appendChild(container);
959
  }
960
+ return div;
961
  }
962
 
963
  // Stop streaming