Athspi commited on
Commit
4b26a3a
·
verified ·
1 Parent(s): 889b89d

Update static/index.html

Browse files
Files changed (1) hide show
  1. static/index.html +137 -61
static/index.html CHANGED
@@ -693,31 +693,31 @@
693
  const createBubbles = () => {
694
  const bubblesContainer = document.querySelector('.bubbles');
695
  const bubbleCount = 10;
696
-
697
  for (let i = 0; i < bubbleCount; i++) {
698
  const bubble = document.createElement('div');
699
  bubble.classList.add('bubble');
700
-
701
  // Random sizes
702
  const size = Math.random() * 60 + 20;
703
  bubble.style.width = `${size}px`;
704
  bubble.style.height = `${size}px`;
705
-
706
  // Random positions
707
  bubble.style.left = `${Math.random() * 100}%`;
708
  bubble.style.top = `${Math.random() * 100}%`;
709
-
710
  // Random animation delay and duration
711
  const animationDuration = Math.random() * 10 + 5;
712
  const animationDelay = Math.random() * 5;
713
-
714
  bubble.style.animationDuration = `${animationDuration}s`;
715
  bubble.style.animationDelay = `${animationDelay}s`;
716
-
717
  bubblesContainer.appendChild(bubble);
718
  }
719
  };
720
-
721
  // DOM Elements
722
  const dropzone = document.getElementById('dropzone');
723
  const fileInput = document.getElementById('audioFileInput');
@@ -737,7 +737,7 @@
737
  const toast = document.getElementById('toast');
738
  const audioVisualization = document.getElementById('audioVisualization');
739
  const visualizerBars = document.getElementById('visualizerBars');
740
-
741
  // Variables
742
  let audioFile = null;
743
  let mediaRecorder = null;
@@ -746,19 +746,19 @@
746
  let audioContext = null;
747
  let analyser = null;
748
  let visualizationInterval = null;
749
-
750
  // Create visualizer bars
751
  const createVisualizerBars = () => {
752
  visualizerBars.innerHTML = '';
753
  const barCount = 50;
754
-
755
  for (let i = 0; i < barCount; i++) {
756
  const bar = document.createElement('div');
757
  bar.classList.add('visualizer-bar');
758
  visualizerBars.appendChild(bar);
759
  }
760
  };
761
-
762
  // Initialize audio context
763
  const initAudioContext = () => {
764
  if (!audioContext) {
@@ -767,14 +767,14 @@
767
  analyser.fftSize = 256;
768
  }
769
  };
770
-
771
  // Update visualization
772
  const updateVisualization = (dataArray) => {
773
  const bars = visualizerBars.querySelectorAll('.visualizer-bar');
774
  const bufferLength = analyser.frequencyBinCount;
775
-
776
  analyser.getByteFrequencyData(dataArray);
777
-
778
  for (let i = 0; i < bars.length; i++) {
779
  const index = Math.floor(i * (bufferLength / bars.length));
780
  const value = dataArray[index] / 255;
@@ -782,66 +782,66 @@
782
  bars[i].style.height = `${height}%`;
783
  }
784
  };
785
-
786
  // Start visualization
787
  const startVisualization = (stream) => {
788
  initAudioContext();
789
-
790
  const source = audioContext.createMediaStreamSource(stream);
791
  source.connect(analyser);
792
-
793
  const dataArray = new Uint8Array(analyser.frequencyBinCount);
794
-
795
  createVisualizerBars();
796
  audioVisualization.classList.remove('hidden');
797
-
798
  visualizationInterval = setInterval(() => {
799
  updateVisualization(dataArray);
800
  }, 50);
801
  };
802
-
803
  // Stop visualization
804
  const stopVisualization = () => {
805
  if (visualizationInterval) {
806
  clearInterval(visualizationInterval);
807
  visualizationInterval = null;
808
  }
809
-
810
  audioVisualization.classList.add('hidden');
811
  };
812
-
813
  // Initialize
814
  const init = () => {
815
  createBubbles();
816
-
817
  // Drag and drop functionality
818
  ['dragenter', 'dragover', 'dragleave', 'drop'].forEach(eventName => {
819
  dropzone.addEventListener(eventName, preventDefaults, false);
820
  });
821
-
822
  function preventDefaults(e) {
823
  e.preventDefault();
824
  e.stopPropagation();
825
  }
826
-
827
  ['dragenter', 'dragover'].forEach(eventName => {
828
  dropzone.addEventListener(eventName, highlight, false);
829
  });
830
-
831
  ['dragleave', 'drop'].forEach(eventName => {
832
  dropzone.addEventListener(eventName, unhighlight, false);
833
  });
834
-
835
  function highlight() {
836
  dropzone.classList.add('dragover');
837
  }
838
-
839
  function unhighlight() {
840
  dropzone.classList.remove('dragover');
841
  }
842
-
843
  dropzone.addEventListener('drop', handleDrop, false);
844
-
845
  function handleDrop(e) {
846
  const dt = e.dataTransfer;
847
  const files = dt.files;
@@ -859,14 +859,33 @@
859
 
860
  // Handle file selection
861
  const handleFile = (file) => {
862
- if (file.type.startsWith('audio/')) {
863
- audioFile = file;
864
- fileInfo.textContent = `Selected file: ${file.name}`;
865
- fileInfo.classList.remove('hidden');
866
- translateBtn.disabled = false;
867
- } else {
868
- alert('Please upload a valid audio file.');
 
 
 
 
869
  }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
870
  };
871
 
872
  // Record button click handler
@@ -902,41 +921,68 @@
902
  mediaRecorder.onstop = () => {
903
  const audioBlob = new Blob(audioChunks, { type: 'audio/wav' });
904
  audioFile = new File([audioBlob], 'recording.wav', { type: 'audio/wav' });
905
- fileInfo.textContent = `Recording saved: ${audioFile.name}`;
906
  fileInfo.classList.remove('hidden');
907
  translateBtn.disabled = false;
908
  stopVisualization();
909
  };
910
  } catch (err) {
911
  console.error('Error starting recording:', err);
912
- alert('Error starting recording. Please ensure you have a microphone connected and permissions granted.');
 
 
 
 
 
 
 
 
 
 
 
 
 
 
913
  }
914
  };
915
 
 
916
  // Stop recording
917
  const stopRecording = () => {
918
- mediaRecorder.stop();
919
- isRecording = false;
920
- recordBtn.classList.remove('recording');
921
- recordBtn.innerHTML = `
922
- <svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
923
- <circle cx="12" cy="12" r="10"></circle>
924
- <circle cx="12" cy="12" r="3"></circle>
925
- </svg>
926
- Start Recording
927
- `;
928
- audioChunks = [];
 
 
929
  };
930
 
 
931
  // Translate button click handler
932
  translateBtn.addEventListener('click', async () => {
933
- if (!audioFile || !languageSelect.value) {
934
- alert('Please select an audio file and a target language.');
 
 
 
 
935
  return;
936
  }
937
 
938
  loadingSection.style.display = 'flex';
939
  outputContainer.style.display = 'none';
 
 
 
 
 
940
 
941
  const formData = new FormData();
942
  formData.append('audio', audioFile);
@@ -944,7 +990,7 @@
944
 
945
  try {
946
  const response = await axios.post('/translate', formData);
947
-
948
  if (response.data.error) {
949
  throw new Error(response.data.error);
950
  }
@@ -958,10 +1004,27 @@
958
 
959
  loadingSection.style.display = 'none';
960
  outputContainer.style.display = 'block';
961
- } catch (err) {
962
- console.error('Error:', err);
963
- showToast(err.message || 'An error occurred');
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
964
  loadingSection.style.display = 'none';
 
965
  }
966
  });
967
 
@@ -969,6 +1032,9 @@
969
  copyOriginal.addEventListener('click', () => {
970
  navigator.clipboard.writeText(originalText.textContent).then(() => {
971
  showToast('Original text copied to clipboard!');
 
 
 
972
  });
973
  });
974
 
@@ -976,15 +1042,22 @@
976
  copyTranslated.addEventListener('click', () => {
977
  navigator.clipboard.writeText(translatedText.textContent).then(() => {
978
  showToast('Translated text copied to clipboard!');
 
 
 
979
  });
980
  });
981
 
982
  // Download translated audio
983
  downloadTranslated.addEventListener('click', () => {
984
- const link = document.createElement('a');
985
- link.href = translatedAudio.src;
986
- link.download = `translated_audio.${translatedAudio.src.split('.').pop()}`;
987
- link.click();
 
 
 
 
988
  });
989
 
990
  // Show toast message
@@ -1000,8 +1073,11 @@
1000
  fetch('/languages')
1001
  .then(response => response.json())
1002
  .then(languages => {
1003
- languageSelect.innerHTML = '<option value="" disabled selected>Select language</option>' +
1004
  languages.map(lang => `<option value="${lang}">${lang}</option>`).join('');
 
 
 
1005
  });
1006
  };
1007
 
 
693
  const createBubbles = () => {
694
  const bubblesContainer = document.querySelector('.bubbles');
695
  const bubbleCount = 10;
696
+
697
  for (let i = 0; i < bubbleCount; i++) {
698
  const bubble = document.createElement('div');
699
  bubble.classList.add('bubble');
700
+
701
  // Random sizes
702
  const size = Math.random() * 60 + 20;
703
  bubble.style.width = `${size}px`;
704
  bubble.style.height = `${size}px`;
705
+
706
  // Random positions
707
  bubble.style.left = `${Math.random() * 100}%`;
708
  bubble.style.top = `${Math.random() * 100}%`;
709
+
710
  // Random animation delay and duration
711
  const animationDuration = Math.random() * 10 + 5;
712
  const animationDelay = Math.random() * 5;
713
+
714
  bubble.style.animationDuration = `${animationDuration}s`;
715
  bubble.style.animationDelay = `${animationDelay}s`;
716
+
717
  bubblesContainer.appendChild(bubble);
718
  }
719
  };
720
+
721
  // DOM Elements
722
  const dropzone = document.getElementById('dropzone');
723
  const fileInput = document.getElementById('audioFileInput');
 
737
  const toast = document.getElementById('toast');
738
  const audioVisualization = document.getElementById('audioVisualization');
739
  const visualizerBars = document.getElementById('visualizerBars');
740
+
741
  // Variables
742
  let audioFile = null;
743
  let mediaRecorder = null;
 
746
  let audioContext = null;
747
  let analyser = null;
748
  let visualizationInterval = null;
749
+
750
  // Create visualizer bars
751
  const createVisualizerBars = () => {
752
  visualizerBars.innerHTML = '';
753
  const barCount = 50;
754
+
755
  for (let i = 0; i < barCount; i++) {
756
  const bar = document.createElement('div');
757
  bar.classList.add('visualizer-bar');
758
  visualizerBars.appendChild(bar);
759
  }
760
  };
761
+
762
  // Initialize audio context
763
  const initAudioContext = () => {
764
  if (!audioContext) {
 
767
  analyser.fftSize = 256;
768
  }
769
  };
770
+
771
  // Update visualization
772
  const updateVisualization = (dataArray) => {
773
  const bars = visualizerBars.querySelectorAll('.visualizer-bar');
774
  const bufferLength = analyser.frequencyBinCount;
775
+
776
  analyser.getByteFrequencyData(dataArray);
777
+
778
  for (let i = 0; i < bars.length; i++) {
779
  const index = Math.floor(i * (bufferLength / bars.length));
780
  const value = dataArray[index] / 255;
 
782
  bars[i].style.height = `${height}%`;
783
  }
784
  };
785
+
786
  // Start visualization
787
  const startVisualization = (stream) => {
788
  initAudioContext();
789
+
790
  const source = audioContext.createMediaStreamSource(stream);
791
  source.connect(analyser);
792
+
793
  const dataArray = new Uint8Array(analyser.frequencyBinCount);
794
+
795
  createVisualizerBars();
796
  audioVisualization.classList.remove('hidden');
797
+
798
  visualizationInterval = setInterval(() => {
799
  updateVisualization(dataArray);
800
  }, 50);
801
  };
802
+
803
  // Stop visualization
804
  const stopVisualization = () => {
805
  if (visualizationInterval) {
806
  clearInterval(visualizationInterval);
807
  visualizationInterval = null;
808
  }
809
+
810
  audioVisualization.classList.add('hidden');
811
  };
812
+
813
  // Initialize
814
  const init = () => {
815
  createBubbles();
816
+
817
  // Drag and drop functionality
818
  ['dragenter', 'dragover', 'dragleave', 'drop'].forEach(eventName => {
819
  dropzone.addEventListener(eventName, preventDefaults, false);
820
  });
821
+
822
  function preventDefaults(e) {
823
  e.preventDefault();
824
  e.stopPropagation();
825
  }
826
+
827
  ['dragenter', 'dragover'].forEach(eventName => {
828
  dropzone.addEventListener(eventName, highlight, false);
829
  });
830
+
831
  ['dragleave', 'drop'].forEach(eventName => {
832
  dropzone.addEventListener(eventName, unhighlight, false);
833
  });
834
+
835
  function highlight() {
836
  dropzone.classList.add('dragover');
837
  }
838
+
839
  function unhighlight() {
840
  dropzone.classList.remove('dragover');
841
  }
842
+
843
  dropzone.addEventListener('drop', handleDrop, false);
844
+
845
  function handleDrop(e) {
846
  const dt = e.dataTransfer;
847
  const files = dt.files;
 
859
 
860
  // Handle file selection
861
  const handleFile = (file) => {
862
+ const allowedAudioTypes = ['audio/mpeg', 'audio/ogg', 'audio/wav', 'audio/webm', 'audio/x-wav', 'audio/mp3']; // Add more if needed
863
+ const maxFileSizeMB = 30;
864
+ const maxFileSize = maxFileSizeMB * 1024 * 1024; // 30MB in bytes
865
+
866
+ if (!allowedAudioTypes.includes(file.type)) {
867
+ showToast('Error: Invalid audio format. Please upload MP3, OGG, WAV, or WebM.');
868
+ fileInput.value = ''; // Reset file input
869
+ audioFile = null;
870
+ fileInfo.classList.add('hidden');
871
+ translateBtn.disabled = true;
872
+ return;
873
  }
874
+
875
+ if (file.size > maxFileSize) {
876
+ showToast(`Error: File size exceeds ${maxFileSizeMB}MB limit.`);
877
+ fileInput.value = ''; // Reset file input
878
+ audioFile = null;
879
+ fileInfo.classList.add('hidden');
880
+ translateBtn.disabled = true;
881
+ return;
882
+ }
883
+
884
+
885
+ audioFile = file;
886
+ fileInfo.textContent = `Selected file: ${file.name} (${(file.size / (1024 * 1024)).toFixed(2)} MB)`;
887
+ fileInfo.classList.remove('hidden');
888
+ translateBtn.disabled = false;
889
  };
890
 
891
  // Record button click handler
 
921
  mediaRecorder.onstop = () => {
922
  const audioBlob = new Blob(audioChunks, { type: 'audio/wav' });
923
  audioFile = new File([audioBlob], 'recording.wav', { type: 'audio/wav' });
924
+ fileInfo.textContent = `Recording saved: ${audioFile.name} (${(audioFile.size / (1024 * 1024)).toFixed(2)} MB)`;
925
  fileInfo.classList.remove('hidden');
926
  translateBtn.disabled = false;
927
  stopVisualization();
928
  };
929
  } catch (err) {
930
  console.error('Error starting recording:', err);
931
+ if (err.name === 'NotAllowedError' || err.name === 'PermissionDeniedError') {
932
+ showToast('Error: Microphone access denied. Please check your browser permissions.');
933
+ } else {
934
+ showToast('Error starting recording. Please ensure you have a microphone connected and permissions granted.');
935
+ }
936
+ stopVisualization(); // Ensure visualization stops even on error
937
+ recordBtn.classList.remove('recording'); // Remove recording class in case of error
938
+ recordBtn.innerHTML = `
939
+ <svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
940
+ <circle cx="12" cy="12" r="10"></circle>
941
+ <circle cx="12" cy="12" r="3"></circle>
942
+ </svg>
943
+ Start Recording
944
+ `;
945
+ isRecording = false; // Reset recording state in case of error
946
  }
947
  };
948
 
949
+
950
  // Stop recording
951
  const stopRecording = () => {
952
+ if (mediaRecorder && mediaRecorder.state === 'recording') { // Check if mediaRecorder is initialized and recording
953
+ mediaRecorder.stop();
954
+ isRecording = false;
955
+ recordBtn.classList.remove('recording');
956
+ recordBtn.innerHTML = `
957
+ <svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
958
+ <circle cx="12" cy="12" r="10"></circle>
959
+ <circle cx="12" cy="12" r="3"></circle>
960
+ </svg>
961
+ Start Recording
962
+ `;
963
+ audioChunks = [];
964
+ }
965
  };
966
 
967
+
968
  // Translate button click handler
969
  translateBtn.addEventListener('click', async () => {
970
+ if (!audioFile) {
971
+ showToast('Please select or record an audio file.');
972
+ return;
973
+ }
974
+ if (!languageSelect.value) {
975
+ showToast('Please select a target language.');
976
  return;
977
  }
978
 
979
  loadingSection.style.display = 'flex';
980
  outputContainer.style.display = 'none';
981
+ originalText.textContent = ''; // Clear previous text
982
+ translatedText.textContent = ''; // Clear previous text
983
+ originalAudio.src = ''; // Clear previous audio
984
+ translatedAudio.src = ''; // Clear previous audio
985
+
986
 
987
  const formData = new FormData();
988
  formData.append('audio', audioFile);
 
990
 
991
  try {
992
  const response = await axios.post('/translate', formData);
993
+
994
  if (response.data.error) {
995
  throw new Error(response.data.error);
996
  }
 
1004
 
1005
  loadingSection.style.display = 'none';
1006
  outputContainer.style.display = 'block';
1007
+ } catch (error) {
1008
+ console.error('Translation error:', error);
1009
+ let errorMessage = 'An error occurred during translation.';
1010
+ if (error.response) {
1011
+ // The request was made and the server responded with a status code
1012
+ errorMessage = `Translation failed: ${error.response.data.message || error.response.statusText} (Status ${error.response.status})`;
1013
+ console.error("Server response data:", error.response.data);
1014
+ console.error("Server response status:", error.response.status);
1015
+
1016
+ } else if (error.request) {
1017
+ // The request was made but no response was received
1018
+ errorMessage = 'Translation failed: No response from server. Please check your network connection.';
1019
+ console.error("No response received:", error.request);
1020
+ } else {
1021
+ // Something happened in setting up the request that triggered an Error
1022
+ errorMessage = `Translation failed: ${error.message}`;
1023
+ console.error("Error during request setup:", error.message);
1024
+ }
1025
+ showToast(errorMessage);
1026
  loadingSection.style.display = 'none';
1027
+ outputContainer.style.display = 'none'; // Keep output container hidden on error
1028
  }
1029
  });
1030
 
 
1032
  copyOriginal.addEventListener('click', () => {
1033
  navigator.clipboard.writeText(originalText.textContent).then(() => {
1034
  showToast('Original text copied to clipboard!');
1035
+ }).catch(err => {
1036
+ console.error("Clipboard copy error", err);
1037
+ showToast('Failed to copy original text to clipboard.');
1038
  });
1039
  });
1040
 
 
1042
  copyTranslated.addEventListener('click', () => {
1043
  navigator.clipboard.writeText(translatedText.textContent).then(() => {
1044
  showToast('Translated text copied to clipboard!');
1045
+ }).catch(err => {
1046
+ console.error("Clipboard copy error", err);
1047
+ showToast('Failed to copy translated text to clipboard.');
1048
  });
1049
  });
1050
 
1051
  // Download translated audio
1052
  downloadTranslated.addEventListener('click', () => {
1053
+ if (translatedAudio.src) {
1054
+ const link = document.createElement('a');
1055
+ link.href = translatedAudio.src;
1056
+ link.download = `translated_audio.mp3`; // force mp3 download for broader compatibility, adjust as needed
1057
+ link.click();
1058
+ } else {
1059
+ showToast('No translated audio available to download.');
1060
+ }
1061
  });
1062
 
1063
  // Show toast message
 
1073
  fetch('/languages')
1074
  .then(response => response.json())
1075
  .then(languages => {
1076
+ languageSelect.innerHTML = '<option value="" disabled selected>Select language</option>' +
1077
  languages.map(lang => `<option value="${lang}">${lang}</option>`).join('');
1078
+ }).catch(error => {
1079
+ console.error("Failed to load languages:", error);
1080
+ showToast("Failed to load languages. Please check your connection.");
1081
  });
1082
  };
1083