amauricunha commited on
Commit
daa0f70
·
verified ·
1 Parent(s): 6ccd25a

Update templates/index.html

Browse files
Files changed (1) hide show
  1. templates/index.html +166 -5
templates/index.html CHANGED
@@ -701,8 +701,39 @@
701
  }
702
 
703
  // --- FUNÇÕES DE ÁUDIO ---
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
704
  async function fetchAudioBlob(text) {
705
  const selectedTld = voiceSelector.value;
 
 
 
 
 
 
 
706
  const response = await fetch('/tts-proxy', {
707
  method: 'POST',
708
  headers: { 'Content-Type': 'application/json' },
@@ -712,7 +743,22 @@
712
  const err = await response.json();
713
  throw new Error(err.error || `HTTP error! status: ${response.status}`);
714
  }
715
- return await response.blob();
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
716
  }
717
 
718
  async function playAudio(text, event) {
@@ -742,14 +788,101 @@
742
  const charCount = document.getElementById('charCount');
743
  const playText = document.getElementById('playText');
744
  const playSpinner = document.getElementById('playSpinner');
 
 
 
745
  textEditor.addEventListener('input', () => {
746
  const len = textEditor.innerText.length;
 
 
 
747
  charCount.textContent = `${len}/10000`;
748
  playButton.disabled = len === 0;
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
749
  });
 
 
750
  textEditor.addEventListener('mouseup', () => {
751
  createCardButton.disabled = !window.getSelection().toString().trim();
 
752
  });
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
753
  async function handlePlay() {
754
  const text = textEditor.innerText.trim();
755
  if (!text) {
@@ -757,27 +890,55 @@
757
  return;
758
  }
759
 
760
- if (text.length > 5000) {
761
  showToast('Text is too long for audio generation. Please use shorter text.', 'warning');
762
  return;
763
  }
764
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
765
  setButtonLoading(playButton, true);
766
  playText.textContent = 'Generating...';
767
  playSpinner.classList.remove('hidden');
768
 
769
  try {
770
  const audioBlob = await fetchAudioBlob(text);
771
- const audioUrl = URL.createObjectURL(audioBlob);
772
- audioPlayer.src = audioUrl;
 
 
 
 
 
 
 
 
773
  audioPlayer.classList.remove('hidden');
774
 
 
775
  audioPlayer.onplay = () => showToast('Audio ready! Playing...', 'success', 1000);
776
- audioPlayer.onended = () => URL.revokeObjectURL(audioUrl);
 
 
 
777
  audioPlayer.onerror = () => showToast('Failed to play audio', 'error');
778
 
779
  await audioPlayer.play();
 
780
  } catch (error) {
 
781
  showToast('Failed to generate audio. Please check your connection and try again.', 'error');
782
  } finally {
783
  setButtonLoading(playButton, false);
 
701
  }
702
 
703
  // --- FUNÇÕES DE ÁUDIO ---
704
+
705
+ // Cache para armazenar áudios gerados
706
+ const audioCache = new Map();
707
+
708
+ // Função para gerar uma chave de cache baseada no texto e voice
709
+ function getAudioCacheKey(text, tld) {
710
+ return `${text.substring(0, 100)}_${tld}_${text.length}`;
711
+ }
712
+
713
+ // Função para limpar cache de áudio (útil quando muda o texto significativamente)
714
+ function clearAudioCache() {
715
+ audioCache.forEach(blob => {
716
+ if (blob && blob.url) {
717
+ URL.revokeObjectURL(blob.url);
718
+ }
719
+ });
720
+ audioCache.clear();
721
+ if (currentAudioUrl) {
722
+ URL.revokeObjectURL(currentAudioUrl);
723
+ currentAudioUrl = null;
724
+ }
725
+ console.log('Audio cache cleared');
726
+ }
727
+
728
  async function fetchAudioBlob(text) {
729
  const selectedTld = voiceSelector.value;
730
+ const cacheKey = getAudioCacheKey(text, selectedTld);
731
+
732
+ // Verificar se já temos esse áudio no cache
733
+ if (audioCache.has(cacheKey)) {
734
+ return audioCache.get(cacheKey);
735
+ }
736
+
737
  const response = await fetch('/tts-proxy', {
738
  method: 'POST',
739
  headers: { 'Content-Type': 'application/json' },
 
743
  const err = await response.json();
744
  throw new Error(err.error || `HTTP error! status: ${response.status}`);
745
  }
746
+
747
+ const blob = await response.blob();
748
+
749
+ // Armazenar no cache (limitar a 10 áudios para não usar muita memória)
750
+ if (audioCache.size >= 10) {
751
+ const firstKey = audioCache.keys().next().value;
752
+ // Revogar URL do áudio mais antigo
753
+ const oldBlob = audioCache.get(firstKey);
754
+ if (oldBlob && oldBlob.url) {
755
+ URL.revokeObjectURL(oldBlob.url);
756
+ }
757
+ audioCache.delete(firstKey);
758
+ }
759
+
760
+ audioCache.set(cacheKey, blob);
761
+ return blob;
762
  }
763
 
764
  async function playAudio(text, event) {
 
788
  const charCount = document.getElementById('charCount');
789
  const playText = document.getElementById('playText');
790
  const playSpinner = document.getElementById('playSpinner');
791
+
792
+ let lastTextForCache = '';
793
+
794
  textEditor.addEventListener('input', () => {
795
  const len = textEditor.innerText.length;
796
+ const currentText = textEditor.innerText.trim();
797
+
798
+ // Atualizar contador
799
  charCount.textContent = `${len}/10000`;
800
  playButton.disabled = len === 0;
801
+
802
+ // Limpar cache se o texto mudou significativamente (mais de 50 caracteres de diferença)
803
+ if (Math.abs(currentText.length - lastTextForCache.length) > 50 ||
804
+ (currentText.length > 100 && currentText.substring(0, 100) !== lastTextForCache.substring(0, 100))) {
805
+ if (currentAudioUrl) {
806
+ audioPlayer.src = '';
807
+ audioPlayer.classList.add('hidden');
808
+ }
809
+ lastTextForCache = currentText;
810
+ }
811
+
812
+ // Mudança de cor baseada no limite
813
+ if (len > 10000) {
814
+ charCount.classList.add('text-red-500');
815
+ charCount.classList.remove('text-gray-500');
816
+ } else if (len > 8000) {
817
+ charCount.classList.remove('text-red-500', 'text-gray-500');
818
+ charCount.classList.add('text-yellow-500');
819
+ } else {
820
+ charCount.classList.remove('text-red-500', 'text-yellow-500');
821
+ charCount.classList.add('text-gray-500');
822
+ }
823
  });
824
+
825
+ // Controle de seleção de texto para flashcards e áudio
826
  textEditor.addEventListener('mouseup', () => {
827
  createCardButton.disabled = !window.getSelection().toString().trim();
828
+ handleTextSelection();
829
  });
830
+
831
+ textEditor.addEventListener('keyup', () => {
832
+ handleTextSelection();
833
+ });
834
+
835
+ function handleTextSelection() {
836
+ const selectedText = window.getSelection().toString().trim();
837
+ const playSelectionBtn = document.getElementById('playSelectionBtn');
838
+
839
+ if (selectedText && selectedText.length > 0) {
840
+ if (!playSelectionBtn) {
841
+ // Criar botão de tocar seleção se não existir
842
+ const selectionBtn = document.createElement('button');
843
+ selectionBtn.id = 'playSelectionBtn';
844
+ selectionBtn.className = 'btn bg-blue-500 hover:bg-blue-600 text-white ml-2';
845
+ selectionBtn.innerHTML = '🎵 Play Selection';
846
+ selectionBtn.onclick = () => playSelectedText();
847
+
848
+ // Inserir próximo ao botão Play principal
849
+ playButton.parentNode.insertBefore(selectionBtn, playButton.nextSibling);
850
+ }
851
+ playSelectionBtn.style.display = 'inline-block';
852
+ playSelectionBtn.disabled = false;
853
+ } else if (playSelectionBtn) {
854
+ playSelectionBtn.style.display = 'none';
855
+ }
856
+ }
857
+
858
+ async function playSelectedText() {
859
+ const selectedText = window.getSelection().toString().trim();
860
+ if (!selectedText) {
861
+ showToast('Please select some text first', 'warning');
862
+ return;
863
+ }
864
+
865
+ if (selectedText.length > 1000) {
866
+ showToast('Selected text is too long. Please select less than 1000 characters.', 'warning');
867
+ return;
868
+ }
869
+
870
+ const selectionBtn = document.getElementById('playSelectionBtn');
871
+ setButtonLoading(selectionBtn, true, '🎵 Play Selection');
872
+
873
+ try {
874
+ await playAudio(selectedText);
875
+ showToast(`Playing selected text (${selectedText.length} chars)`, 'success', 2000);
876
+ } catch (error) {
877
+ showToast('Failed to play selected text', 'error');
878
+ } finally {
879
+ setButtonLoading(selectionBtn, false, '🎵 Play Selection');
880
+ }
881
+ }
882
+
883
+ // Variável para armazenar o URL atual do áudio
884
+ let currentAudioUrl = null;
885
+
886
  async function handlePlay() {
887
  const text = textEditor.innerText.trim();
888
  if (!text) {
 
890
  return;
891
  }
892
 
893
+ if (text.length > 10000) { // Aumentado de 5000 para 10000
894
  showToast('Text is too long for audio generation. Please use shorter text.', 'warning');
895
  return;
896
  }
897
 
898
+ const selectedTld = voiceSelector.value;
899
+ const cacheKey = getAudioCacheKey(text, selectedTld);
900
+
901
+ // Se já temos um áudio carregado para este texto, apenas reproduzir
902
+ if (currentAudioUrl && audioPlayer.src && getAudioCacheKey(audioPlayer.dataset.lastText || '', audioPlayer.dataset.lastTld || '') === cacheKey) {
903
+ try {
904
+ await audioPlayer.play();
905
+ showToast('Playing cached audio...', 'success', 1000);
906
+ return;
907
+ } catch (error) {
908
+ console.log('Cached audio failed, regenerating...');
909
+ }
910
+ }
911
+
912
  setButtonLoading(playButton, true);
913
  playText.textContent = 'Generating...';
914
  playSpinner.classList.remove('hidden');
915
 
916
  try {
917
  const audioBlob = await fetchAudioBlob(text);
918
+
919
+ // Limpar URL anterior se existir
920
+ if (currentAudioUrl) {
921
+ URL.revokeObjectURL(currentAudioUrl);
922
+ }
923
+
924
+ currentAudioUrl = URL.createObjectURL(audioBlob);
925
+ audioPlayer.src = currentAudioUrl;
926
+ audioPlayer.dataset.lastText = text;
927
+ audioPlayer.dataset.lastTld = selectedTld;
928
  audioPlayer.classList.remove('hidden');
929
 
930
+ // Remover listener anterior para evitar duplicatas
931
  audioPlayer.onplay = () => showToast('Audio ready! Playing...', 'success', 1000);
932
+ audioPlayer.onended = () => {
933
+ // NÃO revogar o URL aqui, manter para permitir replay
934
+ showToast('Audio finished', 'info', 1000);
935
+ };
936
  audioPlayer.onerror = () => showToast('Failed to play audio', 'error');
937
 
938
  await audioPlayer.play();
939
+ showToast(`Audio generated successfully! (${text.length} characters)`, 'success', 2000);
940
  } catch (error) {
941
+ console.error('Audio generation error:', error);
942
  showToast('Failed to generate audio. Please check your connection and try again.', 'error');
943
  } finally {
944
  setButtonLoading(playButton, false);