feat(tts): integrate autonomous Piper neural TTS with zero-latency pre-warming for words and context
This commit is contained in:
@@ -3,7 +3,7 @@
|
||||
// =======================================================================
|
||||
|
||||
import { api } from './api.js';
|
||||
import { escapeHtml, escapeRegex, slugify, showToast } from './ui.js';
|
||||
import { escapeHtml, escapeRegex, slugify, showToast, playAudio } from './ui.js';
|
||||
import { getAllCards, fetchCards } from './dictionary.js';
|
||||
|
||||
// Reader State & Appearance Settings
|
||||
@@ -1454,7 +1454,11 @@ export async function openWordPopup(term, contextSentence, targetElement, paragr
|
||||
if (popupTranslation) popupTranslation.textContent = trans || 'Перевод не найден';
|
||||
if (popupLoading) popupLoading.classList.add('hidden');
|
||||
if (popupContent) popupContent.classList.remove('hidden');
|
||||
if (popupAudioBtn) popupAudioBtn.classList.add('hidden');
|
||||
if (popupAudioBtn) {
|
||||
popupAudioBtn.classList.remove('hidden');
|
||||
popupAudioBtn.style.display = 'inline-flex';
|
||||
}
|
||||
popupCurrentData.audioUrl = data.audio_url || '';
|
||||
|
||||
const inLib = Boolean(data.in_library || existingCard);
|
||||
const libCard = data.library_card || existingCard;
|
||||
@@ -2096,6 +2100,9 @@ export function initReader() {
|
||||
window.addEventListener('scroll', schedulePopupUpdate, { passive: true, capture: true });
|
||||
window.addEventListener('resize', schedulePopupUpdate, { passive: true });
|
||||
|
||||
const popupAudioBtn = document.getElementById('popup-audio-btn');
|
||||
const popupAudioSentenceBtn = document.getElementById('popup-audio-sentence-btn');
|
||||
const popupAudioRuBtn = document.getElementById('popup-audio-ru-btn');
|
||||
const popupTranslateSentenceBtn = document.getElementById('popup-translate-sentence-btn');
|
||||
const popupUseContextMeaningBtn = document.getElementById('popup-use-context-meaning-btn');
|
||||
const popupSentenceLoading = document.getElementById('popup-sentence-loading');
|
||||
@@ -2105,6 +2112,36 @@ export function initReader() {
|
||||
const popupContextMeaningVal = document.getElementById('popup-context-meaning-val');
|
||||
const popupAiSourceBadge = document.getElementById('popup-ai-source-badge');
|
||||
|
||||
if (popupAudioBtn) {
|
||||
popupAudioBtn.addEventListener('click', (e) => {
|
||||
e.stopPropagation();
|
||||
if (!popupCurrentData || !popupCurrentData.term) return;
|
||||
const term = popupCurrentData.term;
|
||||
const url = popupCurrentData.audioUrl || `/api/tts?text=${encodeURIComponent(term)}&lang=en`;
|
||||
playAudio(url);
|
||||
});
|
||||
}
|
||||
|
||||
if (popupAudioSentenceBtn) {
|
||||
popupAudioSentenceBtn.addEventListener('click', (e) => {
|
||||
e.stopPropagation();
|
||||
if (!popupCurrentData || !popupCurrentData.contextSentence) return;
|
||||
const sentence = popupCurrentData.contextSentence;
|
||||
const url = `/api/tts?text=${encodeURIComponent(sentence)}&lang=en`;
|
||||
playAudio(url);
|
||||
});
|
||||
}
|
||||
|
||||
if (popupAudioRuBtn) {
|
||||
popupAudioRuBtn.addEventListener('click', (e) => {
|
||||
e.stopPropagation();
|
||||
const ruText = (popupSentenceRu && popupSentenceRu.textContent || '').trim();
|
||||
if (!ruText || ruText.startsWith('(') || ruText.startsWith('Ошибка')) return;
|
||||
const url = `/api/tts?text=${encodeURIComponent(ruText)}&lang=ru`;
|
||||
playAudio(url);
|
||||
});
|
||||
}
|
||||
|
||||
if (popupTranslateSentenceBtn) {
|
||||
popupTranslateSentenceBtn.addEventListener('click', async (e) => {
|
||||
e.stopPropagation();
|
||||
|
||||
Reference in New Issue
Block a user