function saveApiKey() { const apiKeyInput = document.getElementById('api-key'); const apiKeyGroup = document.getElementById('api-key-group'); if (apiKeyInput) { const apiKey = apiKeyInput.value.trim(); if (apiKey) { // 保存到localStorage localStorage.setItem('apiKey', apiKey); // 显示成功消息 showCustomAlert('API Key 已成功保存', 'success'); // 隐藏API Key输入区域 if (apiKeyGroup) { apiKeyGroup.classList.add('hidden'); } } else { showCustomAlert('API Key 已清空', 'success'); localStorage.setItem('apiKey', '') // 隐藏API Key输入区域 if (apiKeyGroup) { apiKeyGroup.classList.add('hidden'); } } } } // 保存表单数据到localStorage function saveFormData() { const textInput = document.getElementById('text'); const voiceSelect = document.getElementById('voice'); const styleSelect = document.getElementById('style'); const rateInput = document.getElementById('rate'); const pitchInput = document.getElementById('pitch'); // 保存文本内容 if (textInput && textInput.value) { localStorage.setItem('ttsText', textInput.value); } // 保存语音选择 if (voiceSelect && voiceSelect.value) { localStorage.setItem('ttsVoice', voiceSelect.value); } // 保存风格选择 if (styleSelect && styleSelect.value) { localStorage.setItem('ttsStyle', styleSelect.value); } // 保存语速 if (rateInput && rateInput.value) { localStorage.setItem('ttsRate', rateInput.value); } // 保存语调 if (pitchInput && pitchInput.value) { localStorage.setItem('ttsPitch', pitchInput.value); } } // 从localStorage加载表单数据 function loadFormData() { const textInput = document.getElementById('text'); const voiceSelect = document.getElementById('voice'); const styleSelect = document.getElementById('style'); const rateInput = document.getElementById('rate'); const rateValue = document.getElementById('rateValue'); const pitchInput = document.getElementById('pitch'); const pitchValue = document.getElementById('pitchValue'); // 加载文本内容 const savedText = localStorage.getItem('ttsText'); if (savedText && textInput) { textInput.value = savedText; // 更新字符计数 if (document.getElementById('charCount')) { document.getElementById('charCount').textContent = savedText.length; } } // 加载语速 const savedRate = localStorage.getItem('ttsRate'); if (savedRate && rateInput) { rateInput.value = savedRate; if (rateValue) { rateValue.textContent = savedRate + '%'; } } // 加载语调 const savedPitch = localStorage.getItem('ttsPitch'); if (savedPitch && pitchInput) { pitchInput.value = savedPitch; if (pitchValue) { pitchValue.textContent = savedPitch + '%'; } } // 保存风格选择的值,以便在语音加载后使用 const savedStyle = localStorage.getItem('ttsStyle'); // 加载语音选择(在语音列表加载完成后处理) const savedVoice = localStorage.getItem('ttsVoice'); if (savedVoice && voiceSelect) { // 在initVoicesList完成后设置 const voiceLoadInterval = setInterval(() => { if (voiceSelect.options.length > 0 && voiceSelect.options[0].value !== "loading") { for (let i = 0; i < voiceSelect.options.length; i++) { if (voiceSelect.options[i].value === savedVoice) { voiceSelect.selectedIndex = i; // 触发change事件以更新风格选项 const event = new Event('change'); voiceSelect.dispatchEvent(event); // 在语音选择更新后,设置保存的风格 // 使用setTimeout确保风格选项已经更新 setTimeout(() => { if (savedStyle && styleSelect && styleSelect.options.length > 0) { for (let j = 0; j < styleSelect.options.length; j++) { if (styleSelect.options[j].value === savedStyle) { styleSelect.selectedIndex = j; break; } } } }, 100); break; } } clearInterval(voiceLoadInterval); } }, 100); } else { // 如果没有保存的语音选择,但有保存的风格,直接尝试设置风格 if (savedStyle && styleSelect) { const styleLoadInterval = setInterval(() => { if (styleSelect.options.length > 0) { for (let i = 0; i < styleSelect.options.length; i++) { if (styleSelect.options[i].value === savedStyle) { styleSelect.selectedIndex = i; break; } } clearInterval(styleLoadInterval); } }, 100); } } } document.addEventListener('DOMContentLoaded', function () { // 获取DOM元素 const textInput = document.getElementById('text'); const voiceSelect = document.getElementById('voice'); const styleSelect = document.getElementById('style'); const rateInput = document.getElementById('rate'); const rateValue = document.getElementById('rateValue'); const pitchInput = document.getElementById('pitch'); const pitchValue = document.getElementById('pitchValue'); const apiKeyInput = document.getElementById('api-key'); const apiKeyGroup = document.getElementById('api-key-group'); const speakButton = document.getElementById('speak'); const downloadButton = document.getElementById('download'); const copyLinkButton = document.getElementById('copyLink'); const copyHttpTtsLinkButton = document.getElementById('copyHttpTtsLink'); const copyIfreetimeLinkButton = document.getElementById('copyIfreetimeLink'); // 新增元素引用 const audioPlayer = document.getElementById('audioPlayer'); const resultSection = document.getElementById('resultSection'); const charCount = document.getElementById('charCount'); const toggleApiKeyButton = document.getElementById('toggle-api-key'); const apiKeyStatus = document.getElementById('api-key-status'); const apiKeySaveButton = document.getElementById('apvi-key-save'); const togglePasswordButton = document.getElementById('toggle-password'); // 保存最后一个音频URL let lastAudioUrl = ''; // 存储语音数据 let voicesData = []; // 初始化 initVoicesList(); initEventListeners(); loadApiKeyFromLocalStorage(); // 加载API Key loadFormData(); // 加载表单数据 // 更新字符计数 textInput.addEventListener('input', function () { charCount.textContent = this.value.length; // 保存文本内容 localStorage.setItem('ttsText', this.value); }); // 更新语速值显示 rateInput.addEventListener('input', function () { const value = this.value; rateValue.textContent = value + '%'; // 保存语速 localStorage.setItem('ttsRate', value); }); // 更新语调值显示 pitchInput.addEventListener('input', function () { const value = this.value; pitchValue.textContent = value + '%'; // 保存语调 localStorage.setItem('ttsPitch', value); }); // 语音选择变化时更新可用风格 voiceSelect.addEventListener('change', function () { updateStyleOptions(); // 保存语音选择 localStorage.setItem('ttsVoice', this.value); }); // 添加风格选择变化事件 styleSelect.addEventListener('change', function() { // 保存风格选择 localStorage.setItem('ttsStyle', this.value); }); // 获取可用语音列表 async function initVoicesList() { try { const response = await fetch(`${config.basePath}/voices`); if (!response.ok) throw new Error('获取语音列表失败'); voicesData = await response.json(); // 清空并重建选项 voiceSelect.innerHTML = ''; // 如果还在加载,使用带有动画的加载提示 if (voicesData.length === 0) { const option = document.createElement('option'); option.value = "loading"; option.textContent = "加载中"; option.className = "loading-text"; voiceSelect.appendChild(option); return; } // 按语言和名称分组 const voicesByLocale = {}; voicesData.forEach(voice => { if (!voicesByLocale[voice.locale]) { voicesByLocale[voice.locale] = []; } voicesByLocale[voice.locale].push(voice); }); // 创建选项组 for (const locale in voicesByLocale) { const optgroup = document.createElement('optgroup'); optgroup.label = voicesByLocale[locale][0].locale_name; voicesByLocale[locale].forEach(voice => { const option = document.createElement('option'); option.value = voice.short_name; option.textContent = `${voice.local_name || voice.display_name} (${voice.gender})`; // 如果是默认语音则选中 if (voice.short_name === config.defaultVoice) { option.selected = true; } optgroup.appendChild(option); }); voiceSelect.appendChild(optgroup); } // 初始化风格列表 updateStyleOptions(); } catch (error) { console.error('获取语音列表失败:', error); voiceSelect.innerHTML = ''; } } // 更新风格选项 function updateStyleOptions() { // 清空风格选择 styleSelect.innerHTML = ''; // 获取当前选中的语音 const selectedVoice = voiceSelect.value; const voiceData = voicesData.find(v => v.short_name === selectedVoice); if (!voiceData || !voiceData.style_list || voiceData.style_list.length === 0) { // 如果没有可用风格,添加默认选项 const option = document.createElement('option'); option.value = "general"; option.textContent = "普通"; styleSelect.appendChild(option); return; } // 添加清空选项 const emptyOption = document.createElement('option'); emptyOption.value = ""; emptyOption.textContent = "-- 无风格 --"; styleSelect.appendChild(emptyOption); // 添加可用风格选项 voiceData.style_list.forEach(style => { const option = document.createElement('option'); option.value = style; option.textContent = style; // 如果是默认风格则选中 if (style === config.defaultStyle || (!config.defaultStyle && style === "general")) { option.selected = true; } styleSelect.appendChild(option); }); // 在风格选项更新后,尝试恢复保存的风格设置 const savedStyle = localStorage.getItem('ttsStyle'); if (savedStyle) { for (let i = 0; i < styleSelect.options.length; i++) { if (styleSelect.options[i].value === savedStyle) { styleSelect.selectedIndex = i; break; } } } } // 初始化事件监听器 function initEventListeners() { // 转换按钮点击事件 speakButton.addEventListener('click', generateSpeech); // 下载按钮点击事件 downloadButton.addEventListener('click', function () { if (lastAudioUrl) { const a = document.createElement('a'); a.href = lastAudioUrl; a.download = 'speech.mp3'; document.body.appendChild(a); a.click(); document.body.removeChild(a); } }); // 复制链接按钮点击事件 copyLinkButton.addEventListener('click', function () { if (lastAudioUrl) { // 获取完整的URL,包括域名部分 const fullUrl = new URL(lastAudioUrl, window.location.origin).href; copyToClipboard(fullUrl); } }); // 复制HttpTTS链接按钮点击事件 copyHttpTtsLinkButton.addEventListener('click', function () { const text = "{{java.encodeURI(speakText)}}"; const voice = voiceSelect.value; const displayName = voiceSelect.options[voiceSelect.selectedIndex].text; const style = styleSelect.value; const rate = "{{speakSpeed*4}}" const pitch = pitchInput.value; const apiKey = apiKeyInput.value.trim(); // 构建HttpTTS链接 let httpTtsLink = `${window.location.origin}${config.basePath}/reader.json?&v=${voice}&r=${rate}&p=${pitch}&n=${displayName}`; // 只有当style不为空时才添加 if (style) { httpTtsLink += `&s=${style}`; } // 添加API Key参数(如果有) if (apiKey) { httpTtsLink += `&api_key=${apiKey}`; } window.open(httpTtsLink, '_blank') }); // 复制爱阅记链接按钮点击事件 copyIfreetimeLinkButton.addEventListener('click', function () { const text = "{{java.encodeURI(speakText)}}"; const voice = voiceSelect.value; const displayName = voiceSelect.options[voiceSelect.selectedIndex].text; const style = styleSelect.value; const rate = "{{speakSpeed*4}}" const pitch = pitchInput.value; const apiKey = apiKeyInput.value.trim(); // 构建爱阅记链接 let ifreetimeLink = `${window.location.origin}${config.basePath}/ifreetime.json?&v=${voice}&r=${rate}&p=${pitch}&n=${displayName}`; // 只有当style不为空时才添加 if (style) { ifreetimeLink += `&s=${style}`; } // 添加API Key参数(如果有) if (apiKey) { ifreetimeLink += `&api_key=${apiKey}`; } window.open(ifreetimeLink, '_blank') }); // 显示/隐藏API Key区域的按钮事件 if (toggleApiKeyButton) { toggleApiKeyButton.addEventListener('click', function () { if (apiKeyGroup) { apiKeyGroup.classList.toggle('hidden'); // 如果是显示操作,聚焦到输入框 if (!apiKeyGroup.classList.contains('hidden') && apiKeyInput) { apiKeyInput.focus(); } } }); } // API Key显示/隐藏功能 if (togglePasswordButton) { togglePasswordButton.addEventListener('click', function () { const type = apiKeyInput.getAttribute('type') === 'password' ? 'text' : 'password'; apiKeyInput.setAttribute('type', type); // 更新图标 if (type === 'password') { this.innerHTML = ``; } else { this.innerHTML = ``; } }); } // 按Enter键保存API Key if (apiKeyInput) { apiKeyInput.addEventListener('keydown', function (event) { if (event.key === 'Enter') { event.preventDefault(); saveApiKey(); // 直接调用全局保存函数 } }); } // 增强音频播放器 enhanceAudioPlayer(); } // 生成语音 async function generateSpeech() { const text = textInput.value.trim(); if (!text) { showCustomAlert('请输入要转换的文本', 'warning'); return; } const voice = voiceSelect.value; const style = styleSelect.value; const rate = rateInput.value; const pitch = pitchInput.value; const apiKey = apiKeyInput.value.trim(); // 保存表单数据 saveFormData(); // 禁用按钮,显示加载状态 speakButton.disabled = true; speakButton.textContent = '生成中...'; try { // 构建URL参数 const params = new URLSearchParams({ t: text, v: voice, r: rate, p: pitch }); // 只有当style不为空时才添加 if (style) { params.append('s', style); } // 添加API Key参数(如果有) if (apiKey) { params.append('api_key', apiKey); } const url = `${config.basePath}/tts?${params.toString()}`; // 使用fetch发送请求以便捕获HTTP状态码 const response = await fetch(url); if (response.status === 401) { // 显示API Key输入框 apiKeyGroup.classList.remove('hidden'); showCustomAlert('请输入有效的API Key以继续操作', 'error'); throw new Error('需要API Key授权'); } if (!response.ok) { throw new Error(`HTTP错误: ${response.status}`); } // 获取音频blob const blob = await response.blob(); const audioUrl = URL.createObjectURL(blob); // 更新音频播放器 audioPlayer.src = audioUrl; lastAudioUrl = url; // 保存原始URL用于下载和复制链接 // 显示结果区域 resultSection.classList.remove('hidden'); // 播放音频 audioPlayer.play(); } catch (error) { console.error('生成语音失败:', error); if (error.message !== '需要API Key授权') { showCustomAlert('生成语音失败,请重试', 'error'); } } finally { // 恢复按钮状态 speakButton.disabled = false; speakButton.textContent = '转换为语音'; } } // 保存API Key到localStorage function saveApiKeyToLocalStorage(apiKey) { console.log('Saving API Key to localStorage'); // 添加调试日志 if (apiKey) { localStorage.setItem('apiKey', apiKey); } else { localStorage.removeItem('apiKey'); // 如果清除了API Key,显示输入区域 if (apiKeyGroup) { apiKeyGroup.classList.remove('hidden'); } } } // 从localStorage加载API Key function loadApiKeyFromLocalStorage() { const apiKey = localStorage.getItem('apiKey'); if (apiKey && apiKeyInput) { apiKeyInput.value = apiKey; // 显示已保存状态 if (apiKeyStatus) { apiKeyStatus.textContent = 'API Key 已保存'; apiKeyStatus.className = 'api-key-status valid'; apiKeyStatus.classList.remove('hidden'); } } } // 自定义音频播放器增强 function enhanceAudioPlayer() { // 监听音频播放器出现在DOM中 const observer = new MutationObserver((mutations) => { mutations.forEach((mutation) => { if (mutation.addedNodes.length) { const audioPlayer = document.getElementById('audioPlayer'); if (audioPlayer && !audioPlayer.dataset.enhanced) { // 标记为已增强,避免重复处理 audioPlayer.dataset.enhanced = 'true'; // 为音频播放器添加额外样式 audioPlayer.addEventListener('play', () => { audioPlayer.classList.add('playing'); // 可以添加播放时的视觉反馈 resultSection.classList.add('active-playback'); }); audioPlayer.addEventListener('pause', () => { audioPlayer.classList.remove('playing'); resultSection.classList.remove('active-playback'); }); } } }); }); // 开始观察DOM变化 observer.observe(document.body, {childList: true, subtree: true}); } // 复制内容到剪贴板的通用函数 function copyToClipboard(text) { let success = false; navigator.clipboard.writeText(text).then(() => { showCustomAlert('链接已复制到剪贴板', 'success'); success = true; }).catch(err => { console.error('复制失败:', err); // 兼容处理 const textArea = document.createElement('textarea'); textArea.value = text; document.body.appendChild(textArea); textArea.focus(); textArea.select(); try { document.execCommand('copy'); showCustomAlert('链接已复制到剪贴板', 'success'); success = true; } catch (err) { console.error('复制失败:', err); shwoCustomAlert('复制失败', 'error'); success = false; } document.body.removeChild(textArea); }); return success; } // 添加通知函数 function showNotification(message, type = 'info', duration = 3000) { // 移除任何现有通知 const existingNotifications = document.querySelectorAll('.api-key-notification'); existingNotifications.forEach(notification => { document.body.removeChild(notification); }); // 创建通知元素 const notification = document.createElement('div'); notification.className = `api-key-notification ${type}`; // 添加图标 let icon = ''; switch (type) { case 'success': icon = ''; break; case 'error': icon = ''; break; case 'warning': icon = ''; break; default: icon = ''; } notification.innerHTML = `${icon}${message}`; // 添加到页面 document.body.appendChild(notification); // 显示动画 setTimeout(() => { notification.classList.add('show'); }, 10); // 设置自动关闭 setTimeout(() => { notification.classList.remove('show'); setTimeout(() => { if (notification.parentNode) { document.body.removeChild(notification); } }, 300); }, duration); } // 添加自定义alert函数到全局范围 window.showCustomAlert = showCustomAlert; }); // 自定义alert函数 function showCustomAlert(message, type = 'info', title = '', duration = 3000) { // 获取或创建通知容器 let container = document.getElementById('custom-alert-container'); if (!container) { container = document.createElement('div'); container.id = 'custom-alert-container'; container.className = 'custom-alert-container'; document.body.appendChild(container); } // 创建通知元素 const alert = document.createElement('div'); alert.className = `custom-alert ${type}`; // 根据类型设置图标 let iconSvg = ''; switch (type) { case 'success': iconSvg = ''; break; case 'error': iconSvg = ''; break; case 'warning': iconSvg = ''; break; default: // info iconSvg = ''; } // 构建通知内容 alert.innerHTML = `