feat: enhance API documentation and UI with Tailwind CSS integration, add API Key management features
This commit is contained in:
@@ -1,3 +1,9 @@
|
||||
/*
|
||||
* 精简版样式表
|
||||
* 注意:此站点现在主要使用 Tailwind CSS。
|
||||
* 这个文件只包含基本的重置样式,以支持可能不使用 Tailwind 的旧页面。
|
||||
*/
|
||||
|
||||
/* 基本样式重置 */
|
||||
* {
|
||||
box-sizing: border-box;
|
||||
@@ -5,319 +11,12 @@
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
body {
|
||||
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Helvetica, Arial, sans-serif;
|
||||
line-height: 1.6;
|
||||
color: #333;
|
||||
background-color: #f5f7fa;
|
||||
padding: 20px;
|
||||
}
|
||||
|
||||
/* 容器 */
|
||||
.container {
|
||||
max-width: 1000px;
|
||||
margin: 0 auto;
|
||||
}
|
||||
|
||||
/* 页眉 */
|
||||
header {
|
||||
text-align: center;
|
||||
margin-bottom: 30px;
|
||||
padding: 20px;
|
||||
}
|
||||
|
||||
header h1 {
|
||||
font-size: 2.5rem;
|
||||
margin-bottom: 10px;
|
||||
color: #2c3e50;
|
||||
}
|
||||
|
||||
header p {
|
||||
font-size: 1.2rem;
|
||||
color: #7f8c8d;
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
|
||||
/* 导航 */
|
||||
nav {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
margin-top: 20px;
|
||||
}
|
||||
|
||||
nav a {
|
||||
text-decoration: none;
|
||||
color: #3498db;
|
||||
margin: 0 15px;
|
||||
padding: 5px 10px;
|
||||
border-radius: 5px;
|
||||
transition: all 0.3s ease;
|
||||
}
|
||||
|
||||
nav a:hover {
|
||||
background-color: #3498db;
|
||||
color: #fff;
|
||||
}
|
||||
|
||||
nav a.active {
|
||||
background-color: #3498db;
|
||||
color: #fff;
|
||||
}
|
||||
|
||||
/* 卡片 */
|
||||
.card {
|
||||
background-color: #fff;
|
||||
border-radius: 10px;
|
||||
box-shadow: 0 4px 6px rgba(0, 0, 0, 0.1);
|
||||
padding: 25px;
|
||||
margin-bottom: 25px;
|
||||
}
|
||||
|
||||
/* 标题 */
|
||||
h2 {
|
||||
color: #2c3e50;
|
||||
margin-bottom: 20px;
|
||||
border-bottom: 1px solid #ecf0f1;
|
||||
padding-bottom: 10px;
|
||||
}
|
||||
|
||||
h3 {
|
||||
color: #3498db;
|
||||
margin: 20px 0 10px;
|
||||
}
|
||||
|
||||
/* 输入区域 */
|
||||
.input-group {
|
||||
position: relative;
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
|
||||
textarea {
|
||||
width: 100%;
|
||||
padding: 15px;
|
||||
border: 1px solid #ddd;
|
||||
border-radius: 5px;
|
||||
resize: none;
|
||||
font-size: 1rem;
|
||||
font-family: inherit;
|
||||
}
|
||||
|
||||
textarea:focus {
|
||||
outline: none;
|
||||
border-color: #3498db;
|
||||
box-shadow: 0 0 0 2px rgba(52, 152, 219, 0.2);
|
||||
}
|
||||
|
||||
.char-counter {
|
||||
position: absolute;
|
||||
bottom: 10px;
|
||||
right: 10px;
|
||||
font-size: 0.8rem;
|
||||
color: #7f8c8d;
|
||||
}
|
||||
|
||||
/* API Key 输入框样式 */
|
||||
#api-key-group {
|
||||
margin-bottom: 15px;
|
||||
background-color: #f8f9fa;
|
||||
padding: 12px 15px;
|
||||
border-radius: 6px;
|
||||
border-left: 4px solid #3498db;
|
||||
}
|
||||
|
||||
.api-key-container {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.api-key-container label {
|
||||
width: 80px; /* 增加标签宽度 */
|
||||
flex-shrink: 0; /* 防止标签被压缩 */
|
||||
margin-bottom: 0; /* 覆盖默认的底部边距 */
|
||||
}
|
||||
|
||||
#api-key {
|
||||
width: 100%;
|
||||
padding: 8px 10px;
|
||||
border: 1px solid #ddd;
|
||||
border-radius: 5px;
|
||||
font-family: inherit;
|
||||
font-size: 1rem;
|
||||
}
|
||||
|
||||
#api-key:focus {
|
||||
outline: none;
|
||||
border-color: #3498db;
|
||||
box-shadow: 0 0 0 2px rgba(52, 152, 219, 0.2);
|
||||
}
|
||||
|
||||
#api-key::placeholder {
|
||||
color: #aaa;
|
||||
font-size: 0.9rem;
|
||||
}
|
||||
|
||||
/* 设置区域 */
|
||||
.settings {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(auto-fit, minmax(250px, 1fr));
|
||||
gap: 20px;
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
|
||||
.setting-group {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
label {
|
||||
margin-bottom: 5px;
|
||||
font-weight: bold;
|
||||
color: #2c3e50;
|
||||
}
|
||||
|
||||
select, input[type="range"] {
|
||||
padding: 8px;
|
||||
border: 1px solid #ddd;
|
||||
border-radius: 5px;
|
||||
background-color: #fff;
|
||||
}
|
||||
|
||||
select:focus {
|
||||
outline: none;
|
||||
border-color: #3498db;
|
||||
}
|
||||
|
||||
/* 按钮 */
|
||||
.actions {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
margin-top: 20px;
|
||||
}
|
||||
|
||||
button {
|
||||
padding: 10px 20px;
|
||||
border: none;
|
||||
border-radius: 5px;
|
||||
cursor: pointer;
|
||||
font-size: 1rem;
|
||||
transition: all 0.3s ease;
|
||||
}
|
||||
|
||||
.primary-button {
|
||||
background-color: #3498db;
|
||||
color: #fff;
|
||||
}
|
||||
|
||||
.primary-button:hover {
|
||||
background-color: #2980b9;
|
||||
}
|
||||
|
||||
.secondary-button {
|
||||
background-color: #ecf0f1;
|
||||
color: #2c3e50;
|
||||
margin: 0 5px;
|
||||
}
|
||||
|
||||
.secondary-button:hover {
|
||||
background-color: #bdc3c7;
|
||||
}
|
||||
|
||||
/* 音频播放器 */
|
||||
.audio-player {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
/* 针对旧浏览器的代码高亮支持 */
|
||||
pre {
|
||||
overflow-x: auto;
|
||||
}
|
||||
|
||||
/* 确保音频播放器响应式适应 */
|
||||
audio {
|
||||
width: 100%;
|
||||
margin-bottom: 15px;
|
||||
}
|
||||
|
||||
.audio-controls {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
/* 表格 */
|
||||
table {
|
||||
width: 100%;
|
||||
border-collapse: collapse;
|
||||
margin: 20px 0;
|
||||
}
|
||||
|
||||
th, td {
|
||||
padding: 12px 15px;
|
||||
text-align: left;
|
||||
border-bottom: 1px solid #ddd;
|
||||
}
|
||||
|
||||
th {
|
||||
background-color: #f8f9fa;
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
/* 代码 */
|
||||
code, pre {
|
||||
font-family: SFMono-Regular, Menlo, Monaco, Consolas, monospace;
|
||||
background-color: #f8f9fa;
|
||||
border-radius: 3px;
|
||||
padding: 2px 5px;
|
||||
font-size: 0.9rem;
|
||||
}
|
||||
|
||||
pre {
|
||||
padding: 15px;
|
||||
overflow-x: auto;
|
||||
margin: 15px 0;
|
||||
}
|
||||
|
||||
pre code {
|
||||
padding: 0;
|
||||
background-color: transparent;
|
||||
}
|
||||
|
||||
/* 页脚 */
|
||||
footer {
|
||||
text-align: center;
|
||||
margin-top: 40px;
|
||||
padding: 20px;
|
||||
color: #7f8c8d;
|
||||
font-size: 0.9rem;
|
||||
}
|
||||
|
||||
footer a {
|
||||
color: #3498db;
|
||||
text-decoration: none;
|
||||
}
|
||||
|
||||
footer a:hover {
|
||||
text-decoration: underline;
|
||||
}
|
||||
|
||||
/* 响应式调整 */
|
||||
@media (max-width: 768px) {
|
||||
.settings {
|
||||
grid-template-columns: 1fr;
|
||||
}
|
||||
|
||||
header h1 {
|
||||
font-size: 2rem;
|
||||
}
|
||||
|
||||
.card {
|
||||
padding: 15px;
|
||||
}
|
||||
}
|
||||
|
||||
.settings-row {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
justify-content: space-between;
|
||||
gap: 20px;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.half-width {
|
||||
width: 48%;
|
||||
}
|
||||
@@ -9,6 +9,7 @@ document.addEventListener('DOMContentLoaded', function() {
|
||||
const pitchValue = document.getElementById('pitchValue');
|
||||
const apiKeyInput = document.getElementById('api-key');
|
||||
const apiKeyGroup = document.getElementById('api-key-group');
|
||||
const apiKeyButtons = document.getElementById('api-key-buttons');
|
||||
const speakButton = document.getElementById('speak');
|
||||
const downloadButton = document.getElementById('download');
|
||||
const copyLinkButton = document.getElementById('copyLink');
|
||||
@@ -16,33 +17,34 @@ document.addEventListener('DOMContentLoaded', function() {
|
||||
const audioPlayer = document.getElementById('audioPlayer');
|
||||
const resultSection = document.getElementById('resultSection');
|
||||
const charCount = document.getElementById('charCount');
|
||||
|
||||
|
||||
// 保存最后一个音频URL
|
||||
let lastAudioUrl = '';
|
||||
// 存储语音数据
|
||||
let voicesData = [];
|
||||
|
||||
|
||||
// 初始化
|
||||
initVoicesList();
|
||||
initEventListeners();
|
||||
|
||||
loadApiKeyFromLocalStorage(); // 加载API Key
|
||||
|
||||
// 更新字符计数
|
||||
textInput.addEventListener('input', function() {
|
||||
charCount.textContent = this.value.length;
|
||||
});
|
||||
|
||||
|
||||
// 更新语速值显示
|
||||
rateInput.addEventListener('input', function() {
|
||||
const value = this.value;
|
||||
rateValue.textContent = value + '%';
|
||||
});
|
||||
|
||||
|
||||
// 更新语调值显示
|
||||
pitchInput.addEventListener('input', function() {
|
||||
const value = this.value;
|
||||
pitchValue.textContent = value + '%';
|
||||
});
|
||||
|
||||
|
||||
// 语音选择变化时更新可用风格
|
||||
voiceSelect.addEventListener('change', function() {
|
||||
updateStyleOptions();
|
||||
@@ -53,40 +55,40 @@ document.addEventListener('DOMContentLoaded', function() {
|
||||
try {
|
||||
const response = await fetch(`${config.basePath}/voices`);
|
||||
if (!response.ok) throw new Error('获取语音列表失败');
|
||||
|
||||
|
||||
voicesData = await response.json();
|
||||
|
||||
|
||||
// 清空并重建选项
|
||||
voiceSelect.innerHTML = '';
|
||||
|
||||
|
||||
// 按语言和名称分组
|
||||
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);
|
||||
}
|
||||
|
||||
@@ -97,7 +99,7 @@ document.addEventListener('DOMContentLoaded', function() {
|
||||
voiceSelect.innerHTML = '<option value="">无法加载语音列表</option>';
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// 更新风格选项
|
||||
function updateStyleOptions() {
|
||||
// 清空风格选择
|
||||
@@ -119,8 +121,8 @@ document.addEventListener('DOMContentLoaded', function() {
|
||||
// 添加可用风格选项
|
||||
voiceData.style_list.forEach(style => {
|
||||
const option = document.createElement('option');
|
||||
option.value = style
|
||||
option.textContent = style
|
||||
option.value = style;
|
||||
option.textContent = style;
|
||||
|
||||
// 如果是默认风格则选中
|
||||
if (style === config.defaultStyle ||
|
||||
@@ -136,7 +138,7 @@ document.addEventListener('DOMContentLoaded', function() {
|
||||
function initEventListeners() {
|
||||
// 转换按钮点击事件
|
||||
speakButton.addEventListener('click', generateSpeech);
|
||||
|
||||
|
||||
// 下载按钮点击事件
|
||||
downloadButton.addEventListener('click', function() {
|
||||
if (lastAudioUrl) {
|
||||
@@ -148,7 +150,7 @@ document.addEventListener('DOMContentLoaded', function() {
|
||||
document.body.removeChild(a);
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
// 复制链接按钮点击事件
|
||||
copyLinkButton.addEventListener('click', function() {
|
||||
if (lastAudioUrl) {
|
||||
@@ -173,7 +175,7 @@ document.addEventListener('DOMContentLoaded', function() {
|
||||
const apiKey = apiKeyInput.value.trim();
|
||||
|
||||
// 构建HttpTTS链接
|
||||
let httpTtsLink = `${window.location.origin}${config.basePath}/tts?t={{java.encodeURI(speakText)}}&v=${voice}&r={{speakSpeed*4}}&p=${pitch}&s=${style}`;
|
||||
let httpTtsLink = `${window.location.origin}${config.basePath}/tts?t=${encodeURIComponent(text)}&v=${voice}&r=${rate}&p=${pitch}&s=${style}`;
|
||||
|
||||
// 添加API Key参数(如果有)
|
||||
if (apiKey) {
|
||||
@@ -182,8 +184,93 @@ 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 = `
|
||||
<svg xmlns="http://www.w3.org/2000/svg" class="h-4 w-4 mr-1 text-gray-600" fill="none" viewBox="0 0 24 24" stroke="currentColor">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M8 7H5a2 2 0 00-2 2v9a2 2 0 002 2h14a2 2 0 002-2V9a2 2 0 00-2-2h-3m-1 4l-3 3m0 0l-3-3m3 3V4" />
|
||||
</svg>
|
||||
保存
|
||||
`;
|
||||
saveApiKeyButton.addEventListener('click', function() {
|
||||
saveApiKeyToLocalStorage(apiKeyInput.value);
|
||||
|
||||
// 显示保存成功提示
|
||||
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 = `
|
||||
<svg class="h-5 w-5 mr-2" fill="none" stroke="currentColor" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M5 13l4 4L19 7"></path>
|
||||
</svg>
|
||||
API Key 已保存
|
||||
`;
|
||||
document.body.appendChild(successNotice);
|
||||
|
||||
// 2秒后自动移除提示
|
||||
setTimeout(() => {
|
||||
document.body.removeChild(successNotice);
|
||||
}, 2000);
|
||||
});
|
||||
apiKeyButtons.appendChild(saveApiKeyButton);
|
||||
|
||||
// 清除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 = `
|
||||
<svg xmlns="http://www.w3.org/2000/svg" class="h-4 w-4 mr-1 text-gray-600" fill="none" viewBox="0 0 24 24" stroke="currentColor">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M19 7l-.867 12.142A2 2 0 0116.138 21H7.862a2 2 0 01-1.995-1.858L5 7m5 4v6m4-6v6m1-10V4a1 1 0 00-1-1h-4a1 1 0 00-1 1v3M4 7h16" />
|
||||
</svg>
|
||||
清除
|
||||
`;
|
||||
clearApiKeyButton.addEventListener('click', function() {
|
||||
localStorage.removeItem('apiKey');
|
||||
apiKeyInput.value = '';
|
||||
|
||||
// 显示清除成功提示
|
||||
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 = `
|
||||
<svg class="h-5 w-5 mr-2" fill="none" stroke="currentColor" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M5 13l4 4L19 7"></path>
|
||||
</svg>
|
||||
API Key 已清除
|
||||
`;
|
||||
document.body.appendChild(successNotice);
|
||||
|
||||
// 2秒后自动移除提示
|
||||
setTimeout(() => {
|
||||
document.body.removeChild(successNotice);
|
||||
}, 2000);
|
||||
});
|
||||
apiKeyButtons.appendChild(clearApiKeyButton);
|
||||
}
|
||||
|
||||
|
||||
// 增加密码显示/隐藏功能
|
||||
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);
|
||||
|
||||
// 更新图标
|
||||
if (type === 'password') {
|
||||
this.innerHTML = `<svg xmlns="http://www.w3.org/2000/svg" class="h-5 w-5" fill="none" viewBox="0 0 24 24" stroke="currentColor">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M15 12a3 3 0 11-6 0 3 3 0 016 0z" />
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M2.458 12C3.732 7.943 7.523 5 12 5c4.478 0 8.268 2.943 9.542 7-1.274 4.057-5.064 7-9.542 7-4.477 0-8.268-2.943-9.542-7z" />
|
||||
</svg>`;
|
||||
} else {
|
||||
this.innerHTML = `<svg xmlns="http://www.w3.org/2000/svg" class="h-5 w-5" fill="none" viewBox="0 0 24 24" stroke="currentColor">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M13.875 18.825A10.05 10.05 0 0112 19c-4.478 0-8.268-2.943-9.543-7a9.97 9.97 0 011.563-3.029m5.858.908a3 3 0 114.243 4.243M9.878 9.878l4.242 4.242M9.88 9.88l-3.29-3.29m7.532 7.532l3.29 3.29M3 3l18 18" />
|
||||
</svg>`;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// 复制内容到剪贴板的通用函数
|
||||
function copyToClipboard(text) {
|
||||
navigator.clipboard.writeText(text).then(() => {
|
||||
@@ -215,17 +302,17 @@ document.addEventListener('DOMContentLoaded', function() {
|
||||
alert('请输入要转换的文本');
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
const voice = voiceSelect.value;
|
||||
const style = styleSelect.value;
|
||||
const rate = rateInput.value;
|
||||
const pitch = pitchInput.value;
|
||||
const apiKey = apiKeyInput.value.trim();
|
||||
|
||||
|
||||
// 禁用按钮,显示加载状态
|
||||
speakButton.disabled = true;
|
||||
speakButton.textContent = '生成中...';
|
||||
|
||||
|
||||
try {
|
||||
// 构建URL参数
|
||||
const params = new URLSearchParams({
|
||||
@@ -235,14 +322,14 @@ document.addEventListener('DOMContentLoaded', function() {
|
||||
r: rate,
|
||||
p: pitch
|
||||
});
|
||||
|
||||
|
||||
// 添加API Key参数(如果有)
|
||||
if (apiKey) {
|
||||
params.append('api_key', apiKey);
|
||||
}
|
||||
|
||||
const url = `${config.basePath}/tts?${params.toString()}`;
|
||||
|
||||
|
||||
// 使用fetch发送请求以便捕获HTTP状态码
|
||||
const response = await fetch(url);
|
||||
|
||||
@@ -264,10 +351,10 @@ document.addEventListener('DOMContentLoaded', function() {
|
||||
// 更新音频播放器
|
||||
audioPlayer.src = audioUrl;
|
||||
lastAudioUrl = url; // 保存原始URL用于下载和复制链接
|
||||
|
||||
|
||||
// 显示结果区域
|
||||
resultSection.style.display = 'block';
|
||||
|
||||
resultSection.classList.remove('hidden');
|
||||
|
||||
// 播放音频
|
||||
audioPlayer.play();
|
||||
} catch (error) {
|
||||
@@ -281,4 +368,21 @@ document.addEventListener('DOMContentLoaded', function() {
|
||||
speakButton.textContent = '转换为语音';
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
// 保存API Key到localStorage
|
||||
function saveApiKeyToLocalStorage(apiKey) {
|
||||
if (apiKey) {
|
||||
localStorage.setItem('apiKey', apiKey);
|
||||
} else {
|
||||
localStorage.removeItem('apiKey');
|
||||
}
|
||||
}
|
||||
|
||||
// 从localStorage加载API Key
|
||||
function loadApiKeyFromLocalStorage() {
|
||||
const apiKey = localStorage.getItem('apiKey');
|
||||
if (apiKey) {
|
||||
apiKeyInput.value = apiKey;
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
@@ -4,156 +4,164 @@
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>API文档 - TTS服务</title>
|
||||
<link rel="stylesheet" href="{{.BasePath}}/static/css/style.css">
|
||||
<script src="https://cdn.tailwindcss.com"></script>
|
||||
<link rel="icon" type="image/svg+xml" href="{{.BasePath}}/static/icons/favicon.svg">
|
||||
<meta name="description" content="TTS服务API文档">
|
||||
</head>
|
||||
<body>
|
||||
<div class="container">
|
||||
<header>
|
||||
<h1>TTS服务 API文档</h1>
|
||||
<p>快速、高质量的文本转语音API服务</p>
|
||||
<nav>
|
||||
<a href="{{.BasePath}}/">主页</a>
|
||||
<a href="{{.BasePath}}/api-doc" class="active">API文档</a>
|
||||
<body class="text-gray-800 p-4 font-sans">
|
||||
<div class="max-w-4xl mx-auto">
|
||||
<header class="text-center mb-8 py-4">
|
||||
<h1 class="text-4xl font-bold mb-2 text-gray-800">TTS服务 API文档</h1>
|
||||
<p class="text-xl text-gray-600 mb-4">快速、高质量的文本转语音API服务</p>
|
||||
<nav class="flex justify-center mt-4">
|
||||
<a href="{{.BasePath}}/" class="mx-2 px-3 py-1 rounded text-blue-500 hover:bg-blue-500 hover:text-white transition-colors">主页</a>
|
||||
<a href="{{.BasePath}}/api-doc" class="mx-2 px-3 py-1 rounded bg-blue-500 text-white">API文档</a>
|
||||
</nav>
|
||||
</header>
|
||||
|
||||
<main>
|
||||
<section class="card">
|
||||
<h2>API概述</h2>
|
||||
<p>TTS服务API提供了简单而强大的方式将文本转换为自然语音。我们支持多种语言和声音,并允许您调节语速、语调以适应不同场景需求。</p>
|
||||
<p>基础URL: <code>{{.BasePath}}</code></p>
|
||||
<p>所有API请求均使用HTTP协议,返回标准HTTP状态码表示请求结果。</p>
|
||||
<section class="bg-white rounded-lg shadow-md p-6 mb-6">
|
||||
<h2 class="text-2xl font-bold mb-4 pb-2 border-b border-gray-200 text-gray-800">API概述</h2>
|
||||
<p class="mb-3">TTS服务API提供了简单而强大的方式将文本转换为自然语音。我们支持多种语言和声音,并允许您调节语速、语调以适应不同场景需求。</p>
|
||||
<p class="mb-3">基础URL: <code class="bg-gray-100 px-2 py-1 rounded text-sm font-mono">{{.BasePath}}</code></p>
|
||||
<p class="mb-3">所有API请求均使用HTTP协议,返回标准HTTP状态码表示请求结果。</p>
|
||||
</section>
|
||||
|
||||
<section class="card">
|
||||
<h2>文本转语音 API</h2>
|
||||
<h3>端点</h3>
|
||||
<code>GET {{.BasePath}}/tts</code>
|
||||
<section class="bg-white rounded-lg shadow-md p-6 mb-6">
|
||||
<h2 class="text-2xl font-bold mb-4 pb-2 border-b border-gray-200 text-gray-800">文本转语音 API</h2>
|
||||
|
||||
<h3>参数</h3>
|
||||
<table>
|
||||
<thead>
|
||||
<tr>
|
||||
<th>参数</th>
|
||||
<th>类型</th>
|
||||
<th>必选</th>
|
||||
<th>描述</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr>
|
||||
<td><code>t</code></td>
|
||||
<td>string</td>
|
||||
<td>是</td>
|
||||
<td>要转换的文本(需要进行URL编码)</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><code>v</code></td>
|
||||
<td>string</td>
|
||||
<td>否</td>
|
||||
<td>语音名称,使用short_name格式,默认: {{.DefaultVoice}}。可通过/voices接口获取所有可用语音</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><code>r</code></td>
|
||||
<td>string</td>
|
||||
<td>否</td>
|
||||
<td>语速调整,范围: -100%到100%,默认: {{.DefaultRate}}。正值加快语速,负值减慢语速</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><code>p</code></td>
|
||||
<td>string</td>
|
||||
<td>否</td>
|
||||
<td>语调调整,范围: -100%到100%,默认: {{.DefaultPitch}}。正值提高语调,负值降低语调</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><code>o</code></td>
|
||||
<td>string</td>
|
||||
<td>否</td>
|
||||
<td>输出音频格式,默认: {{.DefaultFormat}}。详见下方支持的格式列表</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><code>s</code></td>
|
||||
<td>string</td>
|
||||
<td>否</td>
|
||||
<td>情感风格,可用值取决于所选语音的style_list属性。例如:"cheerful"、"sad"等</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
<h3 class="text-xl font-semibold text-blue-600 mt-6 mb-2">端点</h3>
|
||||
<code class="block bg-gray-100 p-3 rounded text-sm font-mono mb-4">GET {{.BasePath}}/tts</code>
|
||||
|
||||
<h3>示例请求</h3>
|
||||
<pre><code>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"</code></pre>
|
||||
<h3 class="text-xl font-semibold text-blue-600 mt-6 mb-2">参数</h3>
|
||||
<div class="overflow-x-auto">
|
||||
<table class="min-w-full bg-white mb-6">
|
||||
<thead>
|
||||
<tr>
|
||||
<th class="py-3 px-4 bg-gray-100 font-semibold text-sm text-left border-b">参数</th>
|
||||
<th class="py-3 px-4 bg-gray-100 font-semibold text-sm text-left border-b">类型</th>
|
||||
<th class="py-3 px-4 bg-gray-100 font-semibold text-sm text-left border-b">必选</th>
|
||||
<th class="py-3 px-4 bg-gray-100 font-semibold text-sm text-left border-b">描述</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr>
|
||||
<td class="py-2 px-4 border-b"><code class="bg-gray-100 px-1 rounded text-sm font-mono">t</code></td>
|
||||
<td class="py-2 px-4 border-b">string</td>
|
||||
<td class="py-2 px-4 border-b">是</td>
|
||||
<td class="py-2 px-4 border-b">要转换的文本(需要进行URL编码)</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td class="py-2 px-4 border-b"><code class="bg-gray-100 px-1 rounded text-sm font-mono">v</code></td>
|
||||
<td class="py-2 px-4 border-b">string</td>
|
||||
<td class="py-2 px-4 border-b">否</td>
|
||||
<td class="py-2 px-4 border-b">语音名称,使用short_name格式,默认: {{.DefaultVoice}}。可通过/voices接口获取所有可用语音</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td class="py-2 px-4 border-b"><code class="bg-gray-100 px-1 rounded text-sm font-mono">r</code></td>
|
||||
<td class="py-2 px-4 border-b">string</td>
|
||||
<td class="py-2 px-4 border-b">否</td>
|
||||
<td class="py-2 px-4 border-b">语速调整,范围: -100%到100%,默认: {{.DefaultRate}}。正值加快语速,负值减慢语速</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td class="py-2 px-4 border-b"><code class="bg-gray-100 px-1 rounded text-sm font-mono">p</code></td>
|
||||
<td class="py-2 px-4 border-b">string</td>
|
||||
<td class="py-2 px-4 border-b">否</td>
|
||||
<td class="py-2 px-4 border-b">语调调整,范围: -100%到100%,默认: {{.DefaultPitch}}。正值提高语调,负值降低语调</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td class="py-2 px-4 border-b"><code class="bg-gray-100 px-1 rounded text-sm font-mono">o</code></td>
|
||||
<td class="py-2 px-4 border-b">string</td>
|
||||
<td class="py-2 px-4 border-b">否</td>
|
||||
<td class="py-2 px-4 border-b">输出音频格式,默认: {{.DefaultFormat}}。详见下方支持的格式列表</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td class="py-2 px-4 border-b"><code class="bg-gray-100 px-1 rounded text-sm font-mono">s</code></td>
|
||||
<td class="py-2 px-4 border-b">string</td>
|
||||
<td class="py-2 px-4 border-b">否</td>
|
||||
<td class="py-2 px-4 border-b">情感风格,可用值取决于所选语音的style_list属性。例如:"cheerful"、"sad"等</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
|
||||
<h3>另一个示例(带情感风格)</h3>
|
||||
<pre><code>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"</code></pre>
|
||||
<h3 class="text-xl font-semibold text-blue-600 mt-6 mb-2">示例请求</h3>
|
||||
<pre class="bg-gray-800 text-gray-100 p-4 rounded overflow-x-auto mb-4"><code>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"</code></pre>
|
||||
|
||||
<h3>响应</h3>
|
||||
<p>返回音频文件,内容类型取决于请求的输出格式。正常响应状态码为200。</p>
|
||||
<h3 class="text-xl font-semibold text-blue-600 mt-6 mb-2">另一个示例(带情感风格)</h3>
|
||||
<pre class="bg-gray-800 text-gray-100 p-4 rounded overflow-x-auto mb-4"><code>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"</code></pre>
|
||||
|
||||
<h3>错误响应</h3>
|
||||
<p>如果请求参数有误或服务出现问题,将返回对应的HTTP错误码和错误消息。</p>
|
||||
<table>
|
||||
<thead>
|
||||
<tr>
|
||||
<th>状态码</th>
|
||||
<th>描述</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr>
|
||||
<td>400</td>
|
||||
<td>参数错误或缺失必要参数</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>404</td>
|
||||
<td>请求的资源不存在</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>500</td>
|
||||
<td>服务器内部错误</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
<h3 class="text-xl font-semibold text-blue-600 mt-6 mb-2">响应</h3>
|
||||
<p class="mb-3">返回音频文件,内容类型取决于请求的输出格式。正常响应状态码为200。</p>
|
||||
|
||||
<h3 class="text-xl font-semibold text-blue-600 mt-6 mb-2">错误响应</h3>
|
||||
<p class="mb-3">如果请求参数有误或服务出现问题,将返回对应的HTTP错误码和错误消息。</p>
|
||||
<div class="overflow-x-auto">
|
||||
<table class="min-w-full bg-white mb-6">
|
||||
<thead>
|
||||
<tr>
|
||||
<th class="py-3 px-4 bg-gray-100 font-semibold text-sm text-left border-b">状态码</th>
|
||||
<th class="py-3 px-4 bg-gray-100 font-semibold text-sm text-left border-b">描述</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr>
|
||||
<td class="py-2 px-4 border-b">400</td>
|
||||
<td class="py-2 px-4 border-b">参数错误或缺失必要参数</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td class="py-2 px-4 border-b">404</td>
|
||||
<td class="py-2 px-4 border-b">请求的资源不存在</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td class="py-2 px-4 border-b">500</td>
|
||||
<td class="py-2 px-4 border-b">服务器内部错误</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section class="card">
|
||||
<h2>获取可用语音 API</h2>
|
||||
<h3>端点</h3>
|
||||
<code>GET {{.BasePath}}/voices</code>
|
||||
<section class="bg-white rounded-lg shadow-md p-6 mb-6">
|
||||
<h2 class="text-2xl font-bold mb-4 pb-2 border-b border-gray-200 text-gray-800">获取可用语音 API</h2>
|
||||
|
||||
<h3>参数</h3>
|
||||
<table>
|
||||
<thead>
|
||||
<tr>
|
||||
<th>参数</th>
|
||||
<th>类型</th>
|
||||
<th>必选</th>
|
||||
<th>描述</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr>
|
||||
<td><code>locale</code></td>
|
||||
<td>string</td>
|
||||
<td>否</td>
|
||||
<td>筛选特定语言的语音,例如:zh-CN(中文)、en-US(英文)</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><code>gender</code></td>
|
||||
<td>string</td>
|
||||
<td>否</td>
|
||||
<td>筛选特定性别的语音,可选值:Male(男性)、Female(女性)</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
<h3 class="text-xl font-semibold text-blue-600 mt-6 mb-2">端点</h3>
|
||||
<code class="block bg-gray-100 p-3 rounded text-sm font-mono mb-4">GET {{.BasePath}}/voices</code>
|
||||
|
||||
<h3>示例请求</h3>
|
||||
<pre><code>curl "{{.BasePath}}/voices?locale=zh-CN&gender=Female"</code></pre>
|
||||
<h3 class="text-xl font-semibold text-blue-600 mt-6 mb-2">参数</h3>
|
||||
<div class="overflow-x-auto">
|
||||
<table class="min-w-full bg-white mb-6">
|
||||
<thead>
|
||||
<tr>
|
||||
<th class="py-3 px-4 bg-gray-100 font-semibold text-sm text-left border-b">参数</th>
|
||||
<th class="py-3 px-4 bg-gray-100 font-semibold text-sm text-left border-b">类型</th>
|
||||
<th class="py-3 px-4 bg-gray-100 font-semibold text-sm text-left border-b">必选</th>
|
||||
<th class="py-3 px-4 bg-gray-100 font-semibold text-sm text-left border-b">描述</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr>
|
||||
<td class="py-2 px-4 border-b"><code class="bg-gray-100 px-1 rounded text-sm font-mono">locale</code></td>
|
||||
<td class="py-2 px-4 border-b">string</td>
|
||||
<td class="py-2 px-4 border-b">否</td>
|
||||
<td class="py-2 px-4 border-b">筛选特定语言的语音,例如:zh-CN(中文)、en-US(英文)</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td class="py-2 px-4 border-b"><code class="bg-gray-100 px-1 rounded text-sm font-mono">gender</code></td>
|
||||
<td class="py-2 px-4 border-b">string</td>
|
||||
<td class="py-2 px-4 border-b">否</td>
|
||||
<td class="py-2 px-4 border-b">筛选特定性别的语音,可选值:Male(男性)、Female(女性)</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
|
||||
<h3>响应</h3>
|
||||
<p>返回JSON格式的可用语音列表:</p>
|
||||
<pre><code>[
|
||||
<h3 class="text-xl font-semibold text-blue-600 mt-6 mb-2">示例请求</h3>
|
||||
<pre class="bg-gray-800 text-gray-100 p-4 rounded overflow-x-auto mb-4"><code>curl "{{.BasePath}}/voices?locale=zh-CN&gender=Female"</code></pre>
|
||||
|
||||
<h3 class="text-xl font-semibold text-blue-600 mt-6 mb-2">响应</h3>
|
||||
<p class="mb-3">返回JSON格式的可用语音列表:</p>
|
||||
<pre class="bg-gray-800 text-gray-100 p-4 rounded overflow-x-auto mb-4"><code>[
|
||||
{
|
||||
"name": "Microsoft Server Speech Text to Speech Voice (zh-CN, XiaoxiaoNeural)",
|
||||
"display_name": "Xiaoxiao",
|
||||
@@ -166,64 +174,67 @@
|
||||
},
|
||||
...
|
||||
]</code></pre>
|
||||
<p>响应字段说明:</p>
|
||||
<ul>
|
||||
<li><strong>name</strong>:语音的完整名称</li>
|
||||
<li><strong>display_name</strong>:显示用名称(拉丁字符)</li>
|
||||
<li><strong>local_name</strong>:本地化名称</li>
|
||||
<li><strong>short_name</strong>:简短名称(用于API调用的v参数)</li>
|
||||
<li><strong>gender</strong>:性别(Male或Female)</li>
|
||||
<li><strong>locale</strong>:语言代码</li>
|
||||
<li><strong>locale_name</strong>:语言本地化名称</li>
|
||||
<li><strong>style_list</strong>:支持的情感风格列表(如有)</li>
|
||||
<p class="mb-3">响应字段说明:</p>
|
||||
<ul class="list-disc pl-5 mb-4 space-y-1">
|
||||
<li><span class="font-semibold">name</span>:语音的完整名称</li>
|
||||
<li><span class="font-semibold">display_name</span>:显示用名称(拉丁字符)</li>
|
||||
<li><span class="font-semibold">local_name</span>:本地化名称</li>
|
||||
<li><span class="font-semibold">short_name</span>:简短名称(用于API调用的v参数)</li>
|
||||
<li><span class="font-semibold">gender</span>:性别(Male或Female)</li>
|
||||
<li><span class="font-semibold">locale</span>:语言代码</li>
|
||||
<li><span class="font-semibold">locale_name</span>:语言本地化名称</li>
|
||||
<li><span class="font-semibold">style_list</span>:支持的情感风格列表(如有)</li>
|
||||
</ul>
|
||||
</section>
|
||||
|
||||
<section class="card">
|
||||
<h2>兼容OpenAI接口 API</h2>
|
||||
<h3>语音合成</h3>
|
||||
<code>POST {{.BasePath}}/v1/audio/speech</code>
|
||||
<section class="bg-white rounded-lg shadow-md p-6 mb-6">
|
||||
<h2 class="text-2xl font-bold mb-4 pb-2 border-b border-gray-200 text-gray-800">兼容OpenAI接口 API</h2>
|
||||
|
||||
<h3>请求体 (JSON)</h3>
|
||||
<table>
|
||||
<thead>
|
||||
<tr>
|
||||
<th>参数</th>
|
||||
<th>类型</th>
|
||||
<th>必选</th>
|
||||
<th>描述</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr>
|
||||
<td><code>model</code></td>
|
||||
<td>string</td>
|
||||
<td>是</td>
|
||||
<td>当前仅支持值: "tts-1"</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><code>input</code></td>
|
||||
<td>string</td>
|
||||
<td>是</td>
|
||||
<td>要转换的文本内容</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><code>voice</code></td>
|
||||
<td>string</td>
|
||||
<td>是</td>
|
||||
<td>声音名称,使用Microsoft语音格式,例如:ja-JP-KeitaNeural、zh-CN-XiaoxiaoNeural</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><code>speed</code></td>
|
||||
<td>number</td>
|
||||
<td>否</td>
|
||||
<td>语速调整,范围: 0.5到2.0,默认: 1.0</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
<h3 class="text-xl font-semibold text-blue-600 mt-6 mb-2">语音合成</h3>
|
||||
<code class="block bg-gray-100 p-3 rounded text-sm font-mono mb-4">POST {{.BasePath}}/v1/audio/speech</code>
|
||||
|
||||
<h3>示例请求</h3>
|
||||
<pre><code>curl -X POST "{{.BasePath}}/v1/audio/speech" \
|
||||
<h3 class="text-xl font-semibold text-blue-600 mt-6 mb-2">请求体 (JSON)</h3>
|
||||
<div class="overflow-x-auto">
|
||||
<table class="min-w-full bg-white mb-6">
|
||||
<thead>
|
||||
<tr>
|
||||
<th class="py-3 px-4 bg-gray-100 font-semibold text-sm text-left border-b">参数</th>
|
||||
<th class="py-3 px-4 bg-gray-100 font-semibold text-sm text-left border-b">类型</th>
|
||||
<th class="py-3 px-4 bg-gray-100 font-semibold text-sm text-left border-b">必选</th>
|
||||
<th class="py-3 px-4 bg-gray-100 font-semibold text-sm text-left border-b">描述</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr>
|
||||
<td class="py-2 px-4 border-b"><code class="bg-gray-100 px-1 rounded text-sm font-mono">model</code></td>
|
||||
<td class="py-2 px-4 border-b">string</td>
|
||||
<td class="py-2 px-4 border-b">是</td>
|
||||
<td class="py-2 px-4 border-b">当前仅支持值: "tts-1"</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td class="py-2 px-4 border-b"><code class="bg-gray-100 px-1 rounded text-sm font-mono">input</code></td>
|
||||
<td class="py-2 px-4 border-b">string</td>
|
||||
<td class="py-2 px-4 border-b">是</td>
|
||||
<td class="py-2 px-4 border-b">要转换的文本内容</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td class="py-2 px-4 border-b"><code class="bg-gray-100 px-1 rounded text-sm font-mono">voice</code></td>
|
||||
<td class="py-2 px-4 border-b">string</td>
|
||||
<td class="py-2 px-4 border-b">是</td>
|
||||
<td class="py-2 px-4 border-b">声音名称,使用Microsoft语音格式,例如:ja-JP-KeitaNeural、zh-CN-XiaoxiaoNeural</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td class="py-2 px-4 border-b"><code class="bg-gray-100 px-1 rounded text-sm font-mono">speed</code></td>
|
||||
<td class="py-2 px-4 border-b">number</td>
|
||||
<td class="py-2 px-4 border-b">否</td>
|
||||
<td class="py-2 px-4 border-b">语速调整,范围: 0.5到2.0,默认: 1.0</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
|
||||
<h3 class="text-xl font-semibold text-blue-600 mt-6 mb-2">示例请求</h3>
|
||||
<pre class="bg-gray-800 text-gray-100 p-4 rounded overflow-x-auto mb-4"><code>curl -X POST "{{.BasePath}}/v1/audio/speech" \
|
||||
-H "Content-Type: application/json" \
|
||||
-d '{
|
||||
"model": "tts-1",
|
||||
@@ -231,8 +242,8 @@
|
||||
"voice": "zh-CN-XiaoxiaoNeural"
|
||||
}'</code></pre>
|
||||
|
||||
<h3>另一个示例(带速度调整)</h3>
|
||||
<pre><code>curl -X POST "{{.BasePath}}/v1/audio/speech" \
|
||||
<h3 class="text-xl font-semibold text-blue-600 mt-6 mb-2">另一个示例(带速度调整)</h3>
|
||||
<pre class="bg-gray-800 text-gray-100 p-4 rounded overflow-x-auto mb-4"><code>curl -X POST "{{.BasePath}}/v1/audio/speech" \
|
||||
-H "Content-Type: application/json" \
|
||||
-d '{
|
||||
"model": "tts-1",
|
||||
@@ -241,12 +252,12 @@
|
||||
"speed": 1.2
|
||||
}'</code></pre>
|
||||
|
||||
<h3>响应</h3>
|
||||
<p>返回音频文件,内容类型取决于请求的输出格式。正常响应状态码为200。</p>
|
||||
<h3 class="text-xl font-semibold text-blue-600 mt-6 mb-2">响应</h3>
|
||||
<p class="mb-3">返回音频文件,内容类型取决于请求的输出格式。正常响应状态码为200。</p>
|
||||
|
||||
<h3>错误响应</h3>
|
||||
<p>如果请求有误,将返回JSON格式的错误信息:</p>
|
||||
<pre><code>{
|
||||
<h3 class="text-xl font-semibold text-blue-600 mt-6 mb-2">错误响应</h3>
|
||||
<p class="mb-3">如果请求有误,将返回JSON格式的错误信息:</p>
|
||||
<pre class="bg-gray-800 text-gray-100 p-4 rounded overflow-x-auto mb-4"><code>{
|
||||
"error": {
|
||||
"message": "错误信息描述",
|
||||
"type": "错误类型",
|
||||
@@ -255,57 +266,58 @@
|
||||
}</code></pre>
|
||||
</section>
|
||||
|
||||
<section class="card">
|
||||
<h2>支持的输出格式</h2>
|
||||
<table>
|
||||
<thead>
|
||||
<tr>
|
||||
<th>格式名称</th>
|
||||
<th>描述</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr>
|
||||
<td><code>audio-16khz-32kbitrate-mono-mp3</code></td>
|
||||
<td>MP3格式,16kHz, 32kbps</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><code>audio-16khz-64kbitrate-mono-mp3</code></td>
|
||||
<td>MP3格式,16kHz, 64kbps</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><code>audio-16khz-128kbitrate-mono-mp3</code></td>
|
||||
<td>MP3格式,16kHz, 128kbps</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><code>audio-24khz-48kbitrate-mono-mp3</code></td>
|
||||
<td>MP3格式,24kHz, 48kbps</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><code>audio-24khz-96kbitrate-mono-mp3</code></td>
|
||||
<td>MP3格式,24kHz, 96kbps</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><code>audio-24khz-160kbitrate-mono-mp3</code></td>
|
||||
<td>MP3格式,24kHz, 160kbps</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><code>riff-16khz-16bit-mono-pcm</code></td>
|
||||
<td>WAV格式,16kHz</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><code>riff-24khz-16bit-mono-pcm</code></td>
|
||||
<td>WAV格式,24kHz</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
<section class="bg-white rounded-lg shadow-md p-6 mb-6">
|
||||
<h2 class="text-2xl font-bold mb-4 pb-2 border-b border-gray-200 text-gray-800">支持的输出格式</h2>
|
||||
<div class="overflow-x-auto">
|
||||
<table class="min-w-full bg-white mb-6">
|
||||
<thead>
|
||||
<tr>
|
||||
<th class="py-3 px-4 bg-gray-100 font-semibold text-sm text-left border-b">格式名称</th>
|
||||
<th class="py-3 px-4 bg-gray-100 font-semibold text-sm text-left border-b">描述</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr>
|
||||
<td class="py-2 px-4 border-b"><code class="bg-gray-100 px-1 rounded text-sm font-mono">audio-16khz-32kbitrate-mono-mp3</code></td>
|
||||
<td class="py-2 px-4 border-b">MP3格式,16kHz, 32kbps</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td class="py-2 px-4 border-b"><code class="bg-gray-100 px-1 rounded text-sm font-mono">audio-16khz-64kbitrate-mono-mp3</code></td>
|
||||
<td class="py-2 px-4 border-b">MP3格式,16kHz, 64kbps</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td class="py-2 px-4 border-b"><code class="bg-gray-100 px-1 rounded text-sm font-mono">audio-16khz-128kbitrate-mono-mp3</code></td>
|
||||
<td class="py-2 px-4 border-b">MP3格式,16kHz, 128kbps</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td class="py-2 px-4 border-b"><code class="bg-gray-100 px-1 rounded text-sm font-mono">audio-24khz-48kbitrate-mono-mp3</code></td>
|
||||
<td class="py-2 px-4 border-b">MP3格式,24kHz, 48kbps</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td class="py-2 px-4 border-b"><code class="bg-gray-100 px-1 rounded text-sm font-mono">audio-24khz-96kbitrate-mono-mp3</code></td>
|
||||
<td class="py-2 px-4 border-b">MP3格式,24kHz, 96kbps</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td class="py-2 px-4 border-b"><code class="bg-gray-100 px-1 rounded text-sm font-mono">audio-24khz-160kbitrate-mono-mp3</code></td>
|
||||
<td class="py-2 px-4 border-b">MP3格式,24kHz, 160kbps</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td class="py-2 px-4 border-b"><code class="bg-gray-100 px-1 rounded text-sm font-mono">riff-16khz-16bit-mono-pcm</code></td>
|
||||
<td class="py-2 px-4 border-b">WAV格式,16kHz</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td class="py-2 px-4 border-b"><code class="bg-gray-100 px-1 rounded text-sm font-mono">riff-24khz-16bit-mono-pcm</code></td>
|
||||
<td class="py-2 px-4 border-b">WAV格式,24kHz</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</section>
|
||||
</main>
|
||||
|
||||
<footer>
|
||||
<p>© 2025 TTS服务 | <a href="{{.BasePath}}/">返回主页</a></p>
|
||||
<footer class="text-center mt-10 py-4 text-gray-600 text-sm">
|
||||
<p>© 2025 TTS服务 | <a href="{{.BasePath}}/" class="text-blue-500 hover:underline">返回主页</a></p>
|
||||
</footer>
|
||||
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
|
||||
@@ -4,84 +4,111 @@
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>文本转语音 - TTS服务</title>
|
||||
<link rel="stylesheet" href="{{.BasePath}}/static/css/style.css">
|
||||
<script src="https://cdn.tailwindcss.com"></script>
|
||||
<link rel="icon" type="image/svg+xml" href="{{.BasePath}}/static/icons/favicon.svg">
|
||||
<meta name="description" content="基于Microsoft Azure语音服务的在线文本转语音工具">
|
||||
</head>
|
||||
<body>
|
||||
<div class="container">
|
||||
<header>
|
||||
<h1>文本转语音 (TTS)</h1>
|
||||
<p>将文本转换为自然流畅的语音</p>
|
||||
<nav>
|
||||
<a href="{{.BasePath}}/" class="active">主页</a>
|
||||
<a href="{{.BasePath}}/api-doc">API文档</a>
|
||||
<body class="bg-gray-50 text-gray-800 p-4 font-sans">
|
||||
<div class="max-w-4xl mx-auto">
|
||||
<header class="text-center mb-8 py-4">
|
||||
<h1 class="text-4xl font-bold mb-2 text-gray-800">文本转语音 (TTS)</h1>
|
||||
<p class="text-xl text-gray-600 mb-4">将文本转换为自然流畅的语音</p>
|
||||
<nav class="flex justify-center mt-4">
|
||||
<a href="{{.BasePath}}/" class="mx-2 px-3 py-1 rounded bg-blue-500 text-white">主页</a>
|
||||
<a href="{{.BasePath}}/api-doc" class="mx-2 px-3 py-1 rounded text-blue-500 hover:bg-blue-500 hover:text-white transition-colors">API文档</a>
|
||||
</nav>
|
||||
</header>
|
||||
|
||||
<main>
|
||||
<section class="card">
|
||||
<h2>输入文本</h2>
|
||||
<div class="input-group">
|
||||
<textarea id="text" placeholder="输入要转换的文本..." rows="6" maxlength="5000"></textarea>
|
||||
<div class="char-counter"><span id="charCount">0</span>/5000</div>
|
||||
<section class="bg-white rounded-lg shadow-md p-6 mb-6">
|
||||
<div id="api-key-group" class="mb-6 border-b border-gray-200 pb-4">
|
||||
<div class="flex flex-col md:flex-row md:items-end gap-3">
|
||||
<div class="flex-grow">
|
||||
<label for="api-key" class="block mb-1 font-semibold text-gray-700">API Key:</label>
|
||||
<div class="relative">
|
||||
<input type="password" id="api-key" placeholder="请输入有效的API Key以继续操作"
|
||||
class="w-full px-3 py-2 pr-10 border border-gray-300 rounded-md focus:outline-none focus:ring-2 focus:ring-blue-500 focus:border-blue-500">
|
||||
<button id="toggle-password" class="absolute inset-y-0 right-0 px-3 flex items-center text-gray-600 hover:text-blue-500 focus:outline-none">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" class="h-5 w-5" fill="none" viewBox="0 0 24 24" stroke="currentColor">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M15 12a3 3 0 11-6 0 3 3 0 016 0z" />
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M2.458 12C3.732 7.943 7.523 5 12 5c4.478 0 8.268 2.943 9.542 7-1.274 4.057-5.064 7-9.542 7-4.477 0-8.268-2.943-9.542-7z" />
|
||||
</svg>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
<div class="flex flex-wrap md:flex-nowrap gap-2" id="api-key-buttons">
|
||||
<!-- 按钮将通过JS添加 -->
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div id="api-key-group" style="display: none;" class="api-key-container">
|
||||
<label for="api-key">API Key:</label>
|
||||
<input type="password" id="api-key" placeholder="请输入有效的API Key以继续操作">
|
||||
<h2 class="text-xl font-bold mb-4 pb-2 border-b border-gray-200 text-gray-800">输入文本</h2>
|
||||
<div class="relative mb-4">
|
||||
<textarea id="text" placeholder="输入要转换的文本..." rows="6" maxlength="5000"
|
||||
class="w-full p-3 border border-gray-300 rounded-md resize-none focus:outline-none focus:ring-2 focus:ring-blue-500 focus:border-blue-500"></textarea>
|
||||
<div class="absolute bottom-2 right-2 text-sm text-gray-500">
|
||||
<span id="charCount">0</span>/5000
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="settings">
|
||||
<div class="setting-group">
|
||||
<label for="voice">语音:</label>
|
||||
<select id="voice">
|
||||
<div class="grid grid-cols-1 md:grid-cols-2 gap-4 mb-4">
|
||||
<div class="flex flex-col">
|
||||
<label for="voice" class="mb-1 font-semibold text-gray-700">语音:</label>
|
||||
<select id="voice"
|
||||
class="p-2 border border-gray-300 rounded-md bg-white focus:outline-none focus:ring-2 focus:ring-blue-500 focus:border-blue-500">
|
||||
<option value="loading">加载中...</option>
|
||||
</select>
|
||||
</div>
|
||||
|
||||
<div class="setting-group">
|
||||
<label for="style">风格:</label>
|
||||
<select id="style">
|
||||
<div class="flex flex-col">
|
||||
<label for="style" class="mb-1 font-semibold text-gray-700">风格:</label>
|
||||
<select id="style"
|
||||
class="p-2 border border-gray-300 rounded-md bg-white focus:outline-none focus:ring-2 focus:ring-blue-500 focus:border-blue-500">
|
||||
<option value="loading">加载中...</option>
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="settings-row">
|
||||
<div class="setting-group half-width">
|
||||
<label for="rate">语速:</label>
|
||||
<input type="range" id="rate" min="-100" max="100" value="0">
|
||||
<span id="rateValue">0%</span>
|
||||
<div class="flex flex-wrap -mx-2">
|
||||
<div class="w-full md:w-1/2 px-2 mb-4">
|
||||
<label for="rate" class="mb-1 font-semibold text-gray-700">语速:</label>
|
||||
<div class="flex items-center">
|
||||
<input type="range" id="rate" min="-100" max="100" value="0"
|
||||
class="w-full mr-2 focus:outline-none">
|
||||
<span id="rateValue" class="text-sm text-gray-600 w-10">0%</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="setting-group half-width">
|
||||
<label for="pitch">语调:</label>
|
||||
<input type="range" id="pitch" min="-100" max="100" value="0">
|
||||
<span id="pitchValue">0%</span>
|
||||
<div class="w-full md:w-1/2 px-2 mb-4">
|
||||
<label for="pitch" class="mb-1 font-semibold text-gray-700">语调:</label>
|
||||
<div class="flex items-center">
|
||||
<input type="range" id="pitch" min="-100" max="100" value="0"
|
||||
class="w-full mr-2 focus:outline-none">
|
||||
<span id="pitchValue" class="text-sm text-gray-600 w-10">0%</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="actions">
|
||||
<button id="speak" class="primary-button">转换为语音</button>
|
||||
<div class="flex justify-center mt-6">
|
||||
<button id="speak" class="px-6 py-2 bg-blue-500 text-white rounded-md hover:bg-blue-600 transition-colors focus:outline-none focus:ring-2 focus:ring-blue-500 focus:ring-opacity-50">转换为语音</button>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section class="card" id="resultSection" style="display:none;">
|
||||
<h2>语音输出</h2>
|
||||
<div class="audio-player">
|
||||
<audio id="audioPlayer" controls></audio>
|
||||
<div class="audio-controls">
|
||||
<button id="download" class="secondary-button">下载音频</button>
|
||||
<button id="copyLink" class="secondary-button">复制链接</button>
|
||||
<button id="copyHttpTtsLink" class="secondary-button">复制HttpTTS链接</button>
|
||||
<section id="resultSection" class="bg-white rounded-lg shadow-md p-6 mb-6 hidden">
|
||||
<h2 class="text-xl font-bold mb-4 pb-2 border-b border-gray-200 text-gray-800">语音输出</h2>
|
||||
<div class="flex flex-col items-center">
|
||||
<audio id="audioPlayer" controls class="w-full mb-4"></audio>
|
||||
<div class="flex flex-wrap justify-center">
|
||||
<button id="download" class="m-1 px-4 py-2 bg-gray-200 text-gray-700 rounded-md hover:bg-gray-300 transition-colors focus:outline-none focus:ring-2 focus:ring-gray-500 focus:ring-opacity-50">下载音频</button>
|
||||
<button id="copyLink" class="m-1 px-4 py-2 bg-gray-200 text-gray-700 rounded-md hover:bg-gray-300 transition-colors focus:outline-none focus:ring-2 focus:ring-gray-500 focus:ring-opacity-50">复制链接</button>
|
||||
<button id="copyHttpTtsLink" class="m-1 px-4 py-2 bg-gray-200 text-gray-700 rounded-md hover:bg-gray-300 transition-colors focus:outline-none focus:ring-2 focus:ring-gray-500 focus:ring-opacity-50">复制HttpTTS链接</button>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
</main>
|
||||
|
||||
<footer>
|
||||
<p>© 2025 TTS服务 | <a href="{{.BasePath}}/api-doc">API文档</a></p>
|
||||
<footer class="text-center mt-10 py-4 text-gray-600 text-sm">
|
||||
<p>© 2025 TTS服务 | <a href="{{.BasePath}}/api-doc" class="text-blue-500 hover:underline">API文档</a></p>
|
||||
</footer>
|
||||
</div>
|
||||
|
||||
|
||||
Reference in New Issue
Block a user