diff --git a/web/static/css/style.css b/web/static/css/style.css deleted file mode 100644 index 75485e0..0000000 --- a/web/static/css/style.css +++ /dev/null @@ -1,22 +0,0 @@ -/* - * 精简版样式表 - * 注意:此站点现在主要使用 Tailwind CSS。 - * 这个文件只包含基本的重置样式,以支持可能不使用 Tailwind 的旧页面。 - */ - -/* 基本样式重置 */ -* { - box-sizing: border-box; - margin: 0; - padding: 0; -} - -/* 针对旧浏览器的代码高亮支持 */ -pre { - overflow-x: auto; -} - -/* 确保音频播放器响应式适应 */ -audio { - width: 100%; -} \ No newline at end of file diff --git a/web/static/js/app.js b/web/static/js/app.js index e1fb737..a2fb051 100644 --- a/web/static/js/app.js +++ b/web/static/js/app.js @@ -1,3 +1,29 @@ +function saveApiKey() { + console.log('Save API Key function called directly'); + + 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', 'warning'); + apiKeyInput.focus(); + } + } +} + document.addEventListener('DOMContentLoaded', function() { // 获取DOM元素 const textInput = document.getElementById('text'); @@ -17,6 +43,13 @@ document.addEventListener('DOMContentLoaded', function() { 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('api-key-save'); + const apiKeyClearButton = document.getElementById('api-key-clear'); + const apiKeyTestButton = document.getElementById('api-key-test'); + const togglePasswordButton = document.getElementById('toggle-password'); + const apiKeyHelp = document.getElementById('api-key-help'); // 保存最后一个音频URL let lastAudioUrl = ''; @@ -27,6 +60,7 @@ document.addEventListener('DOMContentLoaded', function() { initVoicesList(); initEventListeners(); loadApiKeyFromLocalStorage(); // 加载API Key + updateApiKeyVisibility(); // 更新API Key区域的可见性 // 更新字符计数 textInput.addEventListener('input', function() { @@ -61,6 +95,16 @@ document.addEventListener('DOMContentLoaded', function() { // 清空并重建选项 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 = {}; @@ -96,7 +140,7 @@ document.addEventListener('DOMContentLoaded', function() { updateStyleOptions(); } catch (error) { console.error('获取语音列表失败:', error); - voiceSelect.innerHTML = ''; + voiceSelect.innerHTML = ''; } } @@ -164,7 +208,7 @@ document.addEventListener('DOMContentLoaded', function() { copyHttpTtsLinkButton.addEventListener('click', function() { const text = textInput.value.trim(); if (!text) { - alert('请输入要转换的文本'); + showCustomAlert('请输入要转换的文本', 'warning'); return; } @@ -185,121 +229,128 @@ document.addEventListener('DOMContentLoaded', function() { copyToClipboard(httpTtsLink); }); - // 保存API Key按钮点击事件 - const saveApiKeyButton = document.createElement('button'); - saveApiKeyButton.textContent = '保存'; - saveApiKeyButton.className = 'flex-1 h-10 px-4 bg-gray-100 text-gray-700 border border-gray-300 rounded-md hover:bg-gray-200 transition-colors focus:outline-none focus:ring-2 focus:ring-gray-400 focus:ring-opacity-50 flex items-center justify-center'; - saveApiKeyButton.innerHTML = ` - - - - 保存 - `; - saveApiKeyButton.addEventListener('click', function() { - saveApiKeyToLocalStorage(apiKeyInput.value); + // 显示/隐藏API Key区域的按钮事件 + if (toggleApiKeyButton) { + toggleApiKeyButton.addEventListener('click', function() { + console.log('API Key button clicked'); // 添加调试日志 + if (apiKeyGroup) { + apiKeyGroup.classList.toggle('hidden'); - // 显示保存成功提示 - const successNotice = document.createElement('div'); - successNotice.className = 'fixed top-4 right-4 bg-green-100 border border-green-400 text-green-700 px-4 py-3 rounded shadow-md flex items-center z-50'; - successNotice.innerHTML = ` - - - - API Key 已保存 - `; - document.body.appendChild(successNotice); + // 如果是显示操作,聚焦到输入框 + if (!apiKeyGroup.classList.contains('hidden') && apiKeyInput) { + apiKeyInput.focus(); + } + } + }); + } - // 2秒后自动移除提示 - setTimeout(() => { - document.body.removeChild(successNotice); - }, 2000); - }); - apiKeyButtons.appendChild(saveApiKeyButton); + // API Key显示/隐藏功能 + if (togglePasswordButton) { + togglePasswordButton.addEventListener('click', function() { + const type = apiKeyInput.getAttribute('type') === 'password' ? 'text' : 'password'; + apiKeyInput.setAttribute('type', type); - // 清除API Key按钮点击事件 - const clearApiKeyButton = document.createElement('button'); - clearApiKeyButton.textContent = '清除'; - clearApiKeyButton.className = 'flex-1 h-10 px-4 bg-gray-100 text-gray-700 border border-gray-300 rounded-md hover:bg-gray-200 transition-colors focus:outline-none focus:ring-2 focus:ring-gray-400 focus:ring-opacity-50 flex items-center justify-center'; - clearApiKeyButton.innerHTML = ` - - - - 清除 - `; - clearApiKeyButton.addEventListener('click', function() { - localStorage.removeItem('apiKey'); - apiKeyInput.value = ''; + // 更新图标 + if (type === 'password') { + this.innerHTML = ` + + + `; + } else { + this.innerHTML = ` + + `; + } + }); + } - // 显示清除成功提示 - const successNotice = document.createElement('div'); - successNotice.className = 'fixed top-4 right-4 bg-blue-100 border border-blue-400 text-blue-700 px-4 py-3 rounded shadow-md flex items-center z-50'; - successNotice.innerHTML = ` - - - - API Key 已清除 - `; - document.body.appendChild(successNotice); + // 删除原有的保存API Key事件绑定,使用HTML onclick - // 2秒后自动移除提示 - setTimeout(() => { - document.body.removeChild(successNotice); - }, 2000); - }); - apiKeyButtons.appendChild(clearApiKeyButton); - } + // 按Enter键保存API Key + if (apiKeyInput) { + apiKeyInput.addEventListener('keydown', function(event) { + if (event.key === 'Enter') { + event.preventDefault(); + saveApiKey(); // 直接调用全局保存函数 + } + }); + } - // 增加密码显示/隐藏功能 - const togglePasswordButton = document.getElementById('toggle-password'); - if (togglePasswordButton) { - togglePasswordButton.addEventListener('click', function() { - const apiKeyInput = document.getElementById('api-key'); - const type = apiKeyInput.getAttribute('type') === 'password' ? 'text' : 'password'; - apiKeyInput.setAttribute('type', type); + // 清除API Key + if (apiKeyClearButton) { + apiKeyClearButton.addEventListener('click', function() { + apiKeyInput.value = ''; + localStorage.removeItem('apiKey'); + showNotification('API Key 已清除', 'success'); - // 更新图标 - if (type === 'password') { - this.innerHTML = ` - - - `; - } else { - this.innerHTML = ` - - `; - } - }); - } + // 更新状态提示 + apiKeyStatus.textContent = ''; + apiKeyStatus.className = 'api-key-status hidden'; + }); + } - // 复制内容到剪贴板的通用函数 - function copyToClipboard(text) { - navigator.clipboard.writeText(text).then(() => { - alert('链接已复制到剪贴板'); - }).catch(err => { - console.error('复制失败:', err); - // 兼容处理 - const textArea = document.createElement('textarea'); - textArea.value = text; - document.body.appendChild(textArea); - textArea.focus(); - textArea.select(); + // 测试API Key + if (apiKeyTestButton) { + apiKeyTestButton.addEventListener('click', function() { + const apiKey = apiKeyInput.value.trim(); + if (!apiKey) { + showNotification('请先输入API Key再测试', 'warning'); + apiKeyInput.focus(); + return; + } - try { - document.execCommand('copy'); - alert('链接已复制到剪贴板'); - } catch (err) { - console.error('复制失败:', err); - } + apiKeyTestButton.disabled = true; + apiKeyTestButton.innerHTML = ' 测试中...'; - document.body.removeChild(textArea); - }); + // 使用一个简单请求测试API Key + fetch(`${config.basePath}/voices?limit=1`, { + headers: { + 'Authorization': `Bearer ${apiKey}` + } + }) + .then(response => { + if (response.ok) { + apiKeyStatus.textContent = 'API Key 有效'; + apiKeyStatus.className = 'api-key-status valid'; + apiKeyStatus.classList.remove('hidden'); + showNotification('API Key 验证成功', 'success'); + } else { + apiKeyStatus.textContent = 'API Key 无效'; + apiKeyStatus.className = 'api-key-status invalid'; + apiKeyStatus.classList.remove('hidden'); + showNotification('API Key 验证失败', 'error'); + } + }) + .catch(error => { + console.error('测试API Key时出错:', error); + apiKeyStatus.textContent = '测试失败,请检查网络'; + apiKeyStatus.className = 'api-key-status invalid'; + apiKeyStatus.classList.remove('hidden'); + showNotification('无法完成API Key测试', 'error'); + }) + .finally(() => { + apiKeyTestButton.disabled = false; + apiKeyTestButton.innerHTML = ' 测试'; + }); + }); + } + + // 显示帮助信息 + if (apiKeyHelp) { + apiKeyHelp.addEventListener('click', function() { + showNotification('Azure API Key 可在Microsoft Azure门户中的认知服务资源下找到。更多信息请访问帮助文档。', 'info', 8000); + }); + } + + // 增强音频播放器 + enhanceAudioPlayer(); } // 生成语音 async function generateSpeech() { const text = textInput.value.trim(); if (!text) { - alert('请输入要转换的文本'); + showCustomAlert('请输入要转换的文本', 'warning'); return; } @@ -335,8 +386,8 @@ document.addEventListener('DOMContentLoaded', function() { if (response.status === 401) { // 显示API Key输入框 - apiKeyGroup.style.display = 'flex'; - alert('请输入有效的API Key以继续操作'); + apiKeyGroup.classList.remove('hidden'); + showCustomAlert('请输入有效的API Key以继续操作', 'error'); throw new Error('需要API Key授权'); } @@ -360,7 +411,7 @@ document.addEventListener('DOMContentLoaded', function() { } catch (error) { console.error('生成语音失败:', error); if (error.message !== '需要API Key授权') { - alert('生成语音失败,请重试'); + showCustomAlert('生成语音失败,请重试', 'error'); } } finally { // 恢复按钮状态 @@ -371,18 +422,290 @@ document.addEventListener('DOMContentLoaded', function() { // 保存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) { + + if (apiKey && apiKeyInput) { apiKeyInput.value = apiKey; + + // 显示已保存状态 + if (apiKeyStatus) { + apiKeyStatus.textContent = 'API Key 已保存'; + apiKeyStatus.className = 'api-key-status valid'; + apiKeyStatus.classList.remove('hidden'); + } } } + + // 更新API Key区域的可见性 + function updateApiKeyVisibility() { + // 确保元素存在 + if (!apiKeyGroup) return; + + // 如果有保存的API Key,则默认隐藏API Key输入区域 + const apiKey = localStorage.getItem('apiKey'); + if (apiKey && apiKey.trim() !== '') { + apiKeyGroup.classList.add('hidden'); + } else { + apiKeyGroup.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) { + navigator.clipboard.writeText(text).then(() => { + showCustomAlert('链接已复制到剪贴板', 'success'); + }).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'); + } catch (err) { + console.error('复制失败:', err); + } + + document.body.removeChild(textArea); + }); + } + + // 添加通知函数 + 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); + } + + // 添加特定的初始化函数来确保API Key按钮功能正常 + function ensureApiKeyFunctionality() { + const saveApiKeyBtn = document.getElementById('api-key-save'); + if (saveApiKeyBtn && !saveApiKeyBtn.hasEventListener) { + saveApiKeyBtn.hasEventListener = true; + saveApiKeyBtn.addEventListener('click', function() { + console.log('Save API Key button clicked (fallback)'); + + const apiKeyInput = document.getElementById('api-key'); + if (apiKeyInput) { + const apiKey = apiKeyInput.value.trim(); + if (apiKey) { + localStorage.setItem('apiKey', apiKey); + showCustomAlert('API Key 已成功保存', 'success'); + + const apiKeyGroup = document.getElementById('api-key-group'); + if (apiKeyGroup) { + apiKeyGroup.classList.add('hidden'); + } + } else { + showCustomAlert('请输入有效的API Key', 'warning'); + apiKeyInput.focus(); + } + } + }); + } + } + + // 确保API Key功能在DOM加载后初始化 + setTimeout(ensureApiKeyFunctionality, 500); + + // 添加自定义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 = ` +
+ ${iconSvg} +
+
+ ${title ? `

${title}

` : ''} +

${message}

+
+ +
+ `; + + // 添加到容器 + container.appendChild(alert); + + // 添加关闭事件 + const closeBtn = alert.querySelector('.custom-alert-close'); + closeBtn.addEventListener('click', () => { + removeAlert(alert); + }); + + // 动画效果 + setTimeout(() => { + alert.classList.add('show'); + + // 进度条动画 + const progress = alert.querySelector('.custom-alert-progress::after'); + if (progress) { + progress.style.animation = `progress ${duration}ms linear forwards`; + } + }, 10); + + // 自动关闭 + const timeout = setTimeout(() => { + removeAlert(alert); + }, duration); + + // 清除函数 + function removeAlert(element) { + element.classList.remove('show'); + setTimeout(() => { + if (element.parentNode) { + element.parentNode.removeChild(element); + } + }, 300); + clearTimeout(timeout); + } + + // 返回alert对象,以便可以手动控制 + return { + element: alert, + close: () => removeAlert(alert) + }; +} + +// 替换全局的alert函数(可选,谨慎使用) +const originalAlert = window.alert; +window.alert = function(message) { + showCustomAlert(message, 'info'); +}; + +// 在页面加载后,添加一次性检查以确保按钮可点击 +window.addEventListener('load', function() { + console.log('Window loaded, checking save button...'); + const saveBtn = document.getElementById('api-key-save'); + if (saveBtn) { + console.log('Save button found, adding fallback click handler'); + saveBtn.onclick = saveApiKey; + } }); diff --git a/web/templates/api-doc.html b/web/templates/api-doc.html index 9b74f62..b362e04 100644 --- a/web/templates/api-doc.html +++ b/web/templates/api-doc.html @@ -1,5 +1,5 @@ - + @@ -7,161 +7,502 @@ + - -
-
-

TTS服务 API文档

-

快速、高质量的文本转语音API服务

- -
+ + +
+
+
+
+
+
+
+
+
+
+
-
-
-

API概述

-

TTS服务API提供了简单而强大的方式将文本转换为自然语音。我们支持多种语言和声音,并允许您调节语速、语调以适应不同场景需求。

-

基础URL: {{.BasePath}}

-

所有API请求均使用HTTP协议,返回标准HTTP状态码表示请求结果。

-
+
+
+
+

TTS服务 API文档

+

快速、高质量的文本转语音API服务

-
-

文本转语音 API

- -

端点

- GET {{.BasePath}}/tts - -

参数

-
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
参数类型必选描述
tstring要转换的文本(需要进行URL编码)
vstring语音名称,使用short_name格式,默认: {{.DefaultVoice}}。可通过/voices接口获取所有可用语音
rstring语速调整,范围: -100%到100%,默认: {{.DefaultRate}}。正值加快语速,负值减慢语速
pstring语调调整,范围: -100%到100%,默认: {{.DefaultPitch}}。正值提高语调,负值降低语调
ostring输出音频格式,默认: {{.DefaultFormat}}。详见下方支持的格式列表
sstring情感风格,可用值取决于所选语音的style_list属性。例如:"cheerful"、"sad"等
+ + +
-

示例请求

-
curl "{{.BasePath}}/tts?t=%E4%BD%A0%E5%A5%BD%EF%BC%8C%E4%B8%96%E7%95%8C&v=zh-CN-XiaoxiaoNeural&r=0%25&p=0%25"
+
+
+

API概述

+

TTS服务API提供了简单而强大的方式将文本转换为自然语音。我们支持多种语言和声音,并允许您调节语速、语调以适应不同场景需求。

+

基础URL: {{.BasePath}}

+

所有API请求均使用HTTP协议,返回标准HTTP状态码表示请求结果。

+
-

另一个示例(带情感风格)

-
curl "{{.BasePath}}/tts?t=%E4%BB%8A%E5%A4%A9%E5%A4%A9%E6%B0%94%E7%9C%9F%E5%A5%BD&v=zh-CN-XiaoxiaoNeural&s=cheerful"
+ +
+

文本转语音 API

-

响应

-

返回音频文件,内容类型取决于请求的输出格式。正常响应状态码为200。

+

端点

+ GET {{.BasePath}}/tts -

错误响应

-

如果请求参数有误或服务出现问题,将返回对应的HTTP错误码和错误消息。

-
- - - - - - - - - - - - - - - - - - - - - -
状态码描述
400参数错误或缺失必要参数
404请求的资源不存在
500服务器内部错误
-
-
+

参数

+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
参数类型必选描述
tstring要转换的文本(需要进行URL编码)
vstring语音名称,使用short_name格式,默认: {{.DefaultVoice}}。可通过/voices接口获取所有可用语音
rstring语速调整,范围: -100%到100%,默认: {{.DefaultRate}}。正值加快语速,负值减慢语速
pstring语调调整,范围: -100%到100%,默认: {{.DefaultPitch}}。正值提高语调,负值降低语调
ostring输出音频格式,默认: {{.DefaultFormat}}。详见下方支持的格式列表
sstring情感风格,可用值取决于所选语音的style_list属性。例如:"cheerful"、"sad"等
+
-
-

获取可用语音 API

+

示例请求

+
curl "{{.BasePath}}/tts?t=%E4%BD%A0%E5%A5%BD%EF%BC%8C%E4%B8%96%E7%95%8C&v=zh-CN-XiaoxiaoNeural&r=0%25&p=0%25"
-

端点

- GET {{.BasePath}}/voices +

另一个示例(带情感风格)

+
curl "{{.BasePath}}/tts?t=%E4%BB%8A%E5%A4%A9%E5%A4%A9%E6%B0%94%E7%9C%9F%E5%A5%BD&v=zh-CN-XiaoxiaoNeural&s=cheerful"
-

参数

-
- - - - - - - - - - - - - - - - - - - - - - - -
参数类型必选描述
localestring筛选特定语言的语音,例如:zh-CN(中文)、en-US(英文)
genderstring筛选特定性别的语音,可选值:Male(男性)、Female(女性)
-
+

响应

+

返回音频文件,内容类型取决于请求的输出格式。正常响应状态码为200。

-

示例请求

-
curl "{{.BasePath}}/voices?locale=zh-CN&gender=Female"
+

错误响应

+

如果请求参数有误或服务出现问题,将返回对应的HTTP错误码和错误消息。

+
+ + + + + + + + + + + + + + + + + + + + + +
状态码描述
400参数错误或缺失必要参数
404请求的资源不存在
500服务器内部错误
+
+
-

响应

-

返回JSON格式的可用语音列表:

-
[
+                
+

获取可用语音 API

+ +

端点

+ GET {{.BasePath}}/voices + +

参数

+
+ + + + + + + + + + + + + + + + + + + + + + + +
参数类型必选描述
localestring筛选特定语言的语音,例如:zh-CN(中文)、en-US(英文)
genderstring筛选特定性别的语音,可选值:Male(男性)、Female(女性)
+
+ +

示例请求

+
curl "{{.BasePath}}/voices?locale=zh-CN&gender=Female"
+ +

响应

+

返回JSON格式的可用语音列表:

+
[
   {
     "name": "Microsoft Server Speech Text to Speech Voice (zh-CN, XiaoxiaoNeural)",
     "display_name": "Xiaoxiao",
@@ -174,67 +515,67 @@
   },
   ...
 ]
-

响应字段说明:

-
    -
  • name:语音的完整名称
  • -
  • display_name:显示用名称(拉丁字符)
  • -
  • local_name:本地化名称
  • -
  • short_name:简短名称(用于API调用的v参数)
  • -
  • gender:性别(Male或Female)
  • -
  • locale:语言代码
  • -
  • locale_name:语言本地化名称
  • -
  • style_list:支持的情感风格列表(如有)
  • -
-
+

响应字段说明:

+
    +
  • name:语音的完整名称
  • +
  • display_name:显示用名称(拉丁字符)
  • +
  • local_name:本地化名称
  • +
  • short_name:简短名称(用于API调用的v参数)
  • +
  • gender:性别(Male或Female)
  • +
  • locale:语言代码
  • +
  • locale_name:语言本地化名称
  • +
  • style_list:支持的情感风格列表(如有)
  • +
+ -
-

兼容OpenAI接口 API

+
+

兼容OpenAI接口 API

-

语音合成

- POST {{.BasePath}}/v1/audio/speech +

语音合成

+ POST {{.BasePath}}/v1/audio/speech -

请求体 (JSON)

-
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
参数类型必选描述
modelstring当前仅支持值: "tts-1"
inputstring要转换的文本内容
voicestring声音名称,使用Microsoft语音格式,例如:ja-JP-KeitaNeural、zh-CN-XiaoxiaoNeural
speednumber语速调整,范围: 0.5到2.0,默认: 1.0
-
+

请求体 (JSON)

+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
参数类型必选描述
modelstring当前仅支持值: "tts-1"
inputstring要转换的文本内容
voicestring声音名称,使用Microsoft语音格式,例如:ja-JP-KeitaNeural、zh-CN-XiaoxiaoNeural
speednumber语速调整,范围: 0.5到2.0,默认: 1.0
+
-

示例请求

-
curl -X POST "{{.BasePath}}/v1/audio/speech" \
+                    

示例请求

+
curl -X POST "{{.BasePath}}/v1/audio/speech" \
   -H "Content-Type: application/json" \
   -d '{
     "model": "tts-1",
@@ -242,8 +583,8 @@
     "voice": "zh-CN-XiaoxiaoNeural"
   }'
-

另一个示例(带速度调整)

-
curl -X POST "{{.BasePath}}/v1/audio/speech" \
+                    

另一个示例(带速度调整)

+
curl -X POST "{{.BasePath}}/v1/audio/speech" \
   -H "Content-Type: application/json" \
   -d '{
     "model": "tts-1",
@@ -252,72 +593,145 @@
     "speed": 1.2
   }'
-

响应

-

返回音频文件,内容类型取决于请求的输出格式。正常响应状态码为200。

+

响应

+

返回音频文件,内容类型取决于请求的输出格式。正常响应状态码为200。

-

错误响应

-

如果请求有误,将返回JSON格式的错误信息:

-
{
+                    

错误响应

+

如果请求有误,将返回JSON格式的错误信息:

+
{
   "error": {
     "message": "错误信息描述",
     "type": "错误类型",
     "code": "错误代码"
   }
 }
-
+
-
-

支持的输出格式

-
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
格式名称描述
audio-16khz-32kbitrate-mono-mp3MP3格式,16kHz, 32kbps
audio-16khz-64kbitrate-mono-mp3MP3格式,16kHz, 64kbps
audio-16khz-128kbitrate-mono-mp3MP3格式,16kHz, 128kbps
audio-24khz-48kbitrate-mono-mp3MP3格式,24kHz, 48kbps
audio-24khz-96kbitrate-mono-mp3MP3格式,24kHz, 96kbps
audio-24khz-160kbitrate-mono-mp3MP3格式,24kHz, 160kbps
riff-16khz-16bit-mono-pcmWAV格式,16kHz
riff-24khz-16bit-mono-pcmWAV格式,24kHz
-
-
-
+
+

支持的输出格式

+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
格式名称描述
audio-16khz-32kbitrate-mono-mp3MP3格式,16kHz, 32kbps
audio-16khz-64kbitrate-mono-mp3MP3格式,16kHz, 64kbps
audio-16khz-128kbitrate-mono-mp3MP3格式,16kHz, 128kbps
audio-24khz-48kbitrate-mono-mp3MP3格式,24kHz, 48kbps
audio-24khz-96kbitrate-mono-mp3MP3格式,24kHz, 96kbps
audio-24khz-160kbitrate-mono-mp3MP3格式,24kHz, 160kbps
riff-16khz-16bit-mono-pcmWAV格式,16kHz
riff-24khz-16bit-mono-pcmWAV格式,24kHz
+
+
+
- + +
+ + diff --git a/web/templates/index.html b/web/templates/index.html index 110fab7..5d10bfd 100644 --- a/web/templates/index.html +++ b/web/templates/index.html @@ -1,5 +1,5 @@ - + @@ -7,47 +7,1077 @@ + + - -
+ + +
+
+
+
+ +
+

文本转语音 (TTS)

将文本转换为自然流畅的语音

- + + +
-
-
-
-
- -
- - +
+ +
+ +
+ + +

输入文本

- +
- 0/5000 + 0/5000
@@ -55,53 +1085,90 @@
+
- +
+ + (调节语音的快慢程度) +
- 0% + class="w-full mr-2 focus:outline-none rate-slider"> + 0%
- +
+ + (调节语音的高低音) +
- 0% + class="w-full mr-2 focus:outline-none pitch-slider"> + 0%
- +
-
+
- - + +
+ + \ No newline at end of file