feat: improve API key validation and enhance error handling in TTS service
This commit is contained in:
@@ -95,7 +95,7 @@ func (c *Client) getEndpoint(ctx context.Context) (map[string]interface{}, error
|
||||
// 更新缓存
|
||||
c.endpointMu.Lock()
|
||||
c.endpoint = endpoint
|
||||
c.endpointExpiry = time.Now().Add(30 * time.Minute) // 令牌有效期通常是1小时,提前刷新
|
||||
c.endpointExpiry = time.Now().Add(5 * time.Minute)
|
||||
c.endpointMu.Unlock()
|
||||
|
||||
return endpoint, nil
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
const encoder = new TextEncoder();
|
||||
let expiredAt = null;
|
||||
let endpoint = null;
|
||||
const API_KEY = 'your-secret-api-key'; // 添加 API 密钥常量,可以修改为你想要的值
|
||||
|
||||
// 定义需要保留的 SSML 标签模式
|
||||
const preserveTags = [
|
||||
@@ -73,7 +72,15 @@ async function handleRequest(request) {
|
||||
|
||||
// 验证 API 密钥
|
||||
if (!validateApiKey(apiKey)) {
|
||||
return new Response('Unauthorized: Invalid API key', { status: 401 });
|
||||
// 改进 401 错误响应,提供更友好的错误信息
|
||||
return new Response(JSON.stringify({
|
||||
error: 'Unauthorized',
|
||||
message: '无效的 API 密钥,请确保您提供了正确的密钥。',
|
||||
status: 401
|
||||
}), {
|
||||
status: 401,
|
||||
headers: { 'Content-Type': 'application/json; charset=utf-8' }
|
||||
});
|
||||
}
|
||||
|
||||
const text = requestUrl.searchParams.get('t') || '';
|
||||
@@ -160,7 +167,7 @@ async function handleRequest(request) {
|
||||
</head>
|
||||
<body class="bg-gray-50 text-gray-800">
|
||||
<!-- 导航栏 -->
|
||||
<nav class="bg-ms-blue shadow-lg">
|
||||
<nav class="bg-ms-blue shadow-2xl">
|
||||
<div class="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8">
|
||||
<div class="flex justify-between h-16">
|
||||
<div class="flex items-center">
|
||||
@@ -174,146 +181,148 @@ async function handleRequest(request) {
|
||||
</nav>
|
||||
|
||||
<!-- 主内容 -->
|
||||
<main class="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8 py-8">
|
||||
<!-- 主要功能区 -->
|
||||
<div class="flex flex-col lg:flex-row gap-8">
|
||||
<!-- 左边栏:语音转换 -->
|
||||
<div class="lg:w-2/3">
|
||||
<div class="bg-white overflow-hidden shadow rounded-lg divide-y divide-gray-200">
|
||||
<div class="px-4 py-5 sm:px-6">
|
||||
<h2 class="text-lg font-medium text-gray-900">在线文本转语音</h2>
|
||||
<p class="mt-1 text-sm text-gray-500">输入文本并选择语音进行转换</p>
|
||||
</div>
|
||||
<div class="px-4 py-5 sm:p-6">
|
||||
<form id="ttsForm" class="space-y-6">
|
||||
<div>
|
||||
<label for="apiKey" class="block text-sm font-medium text-gray-700">API Key</label>
|
||||
<input type="text" id="apiKey" name="apiKey" required
|
||||
class="mt-1 block w-full shadow-sm sm:text-sm border-gray-300 rounded-md focus:ring-ms-blue focus:border-ms-blue"
|
||||
placeholder="输入API Key" />
|
||||
</div>
|
||||
<main class="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8 py-12">
|
||||
<div class="border-b border-gray-200">
|
||||
<nav class="-mb-px flex space-x-8 justify-center" aria-label="Tabs">
|
||||
<button class="tab-link py-4 px-1 text-ms-blue border-b-2 border-ms-blue hover:text-ms-dark-blue" data-tab="tab1">在线文本转语音</button>
|
||||
<button class="tab-link py-4 px-1 text-gray-500 hover:text-ms-dark-blue" data-tab="tab2">API文档</button>
|
||||
<button class="tab-link py-4 px-1 text-gray-500 hover:text-ms-dark-blue" data-tab="tab3">关于服务</button>
|
||||
</nav>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<label for="text" class="block text-sm font-medium text-gray-700">输入文本</label>
|
||||
<textarea id="text" name="text" rows="4" required
|
||||
class="mt-1 block w-full shadow-sm sm:text-sm border-gray-300 rounded-md focus:ring-ms-blue focus:border-ms-blue"
|
||||
placeholder="请输入要转换的文本"></textarea>
|
||||
</div>
|
||||
|
||||
<div class="grid grid-cols-1 md:grid-cols-2 gap-6">
|
||||
<div>
|
||||
<div class="flex justify-between mb-1">
|
||||
<label for="languageFilter" class="block text-sm font-medium text-gray-700">语言</label>
|
||||
</div>
|
||||
<select id="languageFilter" name="languageFilter"
|
||||
class="mt-1 block w-full py-2 px-3 border border-gray-300 bg-white rounded-md shadow-sm focus:outline-none focus:ring-ms-blue focus:border-ms-blue sm:text-sm"
|
||||
onchange="filterVoicesByLanguage()">
|
||||
<option value="zh">中文 (Chinese)</option>
|
||||
<option value="all">所有语言</option>
|
||||
<option value="en">英文 (English)</option>
|
||||
<option value="ja">日文 (Japanese)</option>
|
||||
<option value="ko">韩文 (Korean)</option>
|
||||
<option value="fr">法语 (French)</option>
|
||||
<option value="de">德语 (German)</option>
|
||||
<option value="es">西班牙语 (Spanish)</option>
|
||||
<option value="ru">俄语 (Russian)</option>
|
||||
</select>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<div class="flex justify-between mb-1">
|
||||
<label for="voice" class="block text-sm font-medium text-gray-700">选择语音</label>
|
||||
</div>
|
||||
<select id="voice" name="voice"
|
||||
class="mt-1 block w-full py-2 px-3 border border-gray-300 bg-white rounded-md shadow-sm focus:outline-none focus:ring-ms-blue focus:border-ms-blue sm:text-sm">
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="grid grid-cols-1 md:grid-cols-2 gap-6">
|
||||
<div>
|
||||
<label for="rate" class="block text-sm font-medium text-gray-700">语速调整</label>
|
||||
<div class="flex items-center mt-2">
|
||||
<span id="rateValue" class="w-8 text-sm text-gray-500">0</span>
|
||||
<input type="range" id="rate" name="rate" min="-100" max="100" value="0"
|
||||
class="mt-1 block w-full" oninput="document.getElementById('rateValue').textContent=this.value" />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<label for="pitch" class="block text-sm font-medium text-gray-700">音调调整</label>
|
||||
<div class="flex items-center mt-2">
|
||||
<span id="pitchValue" class="w-8 text-sm text-gray-500">0</span>
|
||||
<input type="range" id="pitch" name="pitch" min="-100" max="100" value="0"
|
||||
class="mt-1 block w-full" oninput="document.getElementById('pitchValue').textContent=this.value" />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="flex flex-col sm:flex-row gap-3">
|
||||
<button type="submit"
|
||||
class="inline-flex justify-center py-2 px-4 border border-transparent shadow-sm text-sm font-medium rounded-md text-white bg-ms-blue hover:bg-ms-dark-blue focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-ms-blue">
|
||||
生成语音
|
||||
</button>
|
||||
<button type="button" id="downloadBtn" style="display:none;"
|
||||
class="inline-flex justify-center py-2 px-4 border border-transparent shadow-sm text-sm font-medium rounded-md text-white bg-green-600 hover:bg-green-700 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-green-500">
|
||||
下载音频
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<div id="voiceLoadError" role="alert" class="mt-4 rounded-md bg-red-50 p-4" style="display: none;">
|
||||
<div class="flex">
|
||||
<div class="flex-shrink-0">
|
||||
<svg class="h-5 w-5 text-red-400" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="currentColor" aria-hidden="true">
|
||||
<path fill-rule="evenodd" d="M10 18a8 8 0 100-16 8 8 0 000 16zM8.707 7.293a1 1 0 00-1.414 1.414L8.586 10l-1.293 1.293a1 1 0 101.414 1.414L10 11.414l1.293-1.293a1 1 0 001.414-1.414L11.414 10l1.293-1.293a1 1 0 00-1.414-1.414L10 8.586 8.707 7.293z" clip-rule="evenodd" />
|
||||
</svg>
|
||||
</div>
|
||||
<div class="ml-3">
|
||||
<h3 class="text-sm font-medium text-red-800">无法加载语音列表</h3>
|
||||
<div class="mt-2 text-sm text-red-700">
|
||||
<p>显示默认语音列表。请检查网络连接或稍后再试。</p>
|
||||
<div id="tab1" class="tab-content mt-6">
|
||||
<!-- 在线文本转语音表单内容 -->
|
||||
<div class="flex flex-col lg:flex-row gap-8">
|
||||
<!-- 左边栏:语音转换 -->
|
||||
<div class="lg:w-3/4 mx-auto">
|
||||
<div class="bg-white overflow-hidden shadow rounded-lg divide-y divide-gray-200">
|
||||
<div class="px-4 py-5 sm:px-6">
|
||||
<h2 class="text-lg font-medium text-gray-900">在线文本转语音</h2>
|
||||
<p class="mt-1 text-sm text-gray-500">输入文本并选择语音进行转换</p>
|
||||
</div>
|
||||
<div class="px-4 py-5 sm:p-6">
|
||||
<form id="ttsForm" class="space-y-6">
|
||||
<!-- 添加错误提示区域 -->
|
||||
<div id="apiErrorAlert" class="rounded-md bg-red-50 p-4" style="display: none;">
|
||||
<div class="flex">
|
||||
<div class="flex-shrink-0">
|
||||
<svg class="h-5 w-5 text-red-400" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="currentColor">
|
||||
<path fill-rule="evenodd" d="M10 18a8 8 0 100-16 8 8 000 16zM8.707 7.293a1 1 00-1.414 1.414L8.586 10l-1.293 1.293a1 1 101.414 1.414L10 11.414l1.293-1.293a1 1 001.414-1.414L11.414 10l1.293-1.293a1 1 00-1.414-1.414L10 8.586 8.707 7.293z" clip-rule="evenodd" />
|
||||
</svg>
|
||||
</div>
|
||||
<div class="ml-3">
|
||||
<h3 class="text-sm font-medium text-red-800" id="apiErrorTitle">错误</h3>
|
||||
<div class="mt-2 text-sm text-red-700">
|
||||
<p id="apiErrorMessage"></p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
|
||||
<div id="audioContainer" class="mt-6 rounded-md bg-gray-50 p-4 border border-gray-200" style="display: none;">
|
||||
<audio id="audioPlayer" controls class="w-full"></audio>
|
||||
<div>
|
||||
<label for="apiKey" class="block text-sm font-medium text-gray-700">API Key</label>
|
||||
<input type="text" id="apiKey" name="apiKey" required
|
||||
class="mt-1 block w-full shadow-sm sm:text-sm border-gray-300 rounded-md focus:ring-ms-blue focus:border-ms-blue"
|
||||
placeholder="输入API Key" />
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<label for="text" class="block text-sm font-medium text-gray-700">输入文本</label>
|
||||
<textarea id="text" name="text" rows="4" required
|
||||
class="mt-1 block w-full shadow-sm sm:text-sm border-gray-300 rounded-md focus:ring-ms-blue focus:border-ms-blue"
|
||||
placeholder="请输入要转换的文本"></textarea>
|
||||
</div>
|
||||
|
||||
<div class="grid grid-cols-1 md:grid-cols-2 gap-6">
|
||||
<div>
|
||||
<div class="flex justify-between mb-1">
|
||||
<label for="languageFilter" class="block text-sm font-medium text-gray-700">语言</label>
|
||||
</div>
|
||||
<select id="languageFilter" name="languageFilter"
|
||||
class="mt-1 block w-full py-2 px-3 border border-gray-300 bg-white rounded-md shadow-sm focus:outline-none focus:ring-ms-blue focus:border-ms-blue sm:text-sm"
|
||||
onchange="filterVoicesByLanguage()">
|
||||
<option value="zh">中文 (Chinese)</option>
|
||||
<option value="all">所有语言</option>
|
||||
<option value="en">英文 (English)</option>
|
||||
<option value="ja">日文 (Japanese)</option>
|
||||
<option value="ko">韩文 (Korean)</option>
|
||||
<option value="fr">法语 (French)</option>
|
||||
<option value="de">德语 (German)</option>
|
||||
<option value="es">西班牙语 (Spanish)</option>
|
||||
<option value="ru">俄语 (Russian)</option>
|
||||
</select>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<div class="flex justify-between mb-1">
|
||||
<label for="voice" class="block text-sm font-medium text-gray-700">选择语音</label>
|
||||
</div>
|
||||
<select id="voice" name="voice"
|
||||
class="mt-1 block w-full py-2 px-3 border border-gray-300 bg-white rounded-md shadow-sm focus:outline-none focus:ring-ms-blue focus:border-ms-blue sm:text-sm">
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="grid grid-cols-1 md:grid-cols-2 gap-6">
|
||||
<div>
|
||||
<label for="rate" class="block text-sm font-medium text-gray-700">语速调整</label>
|
||||
<div class="flex items-center mt-2">
|
||||
<span id="rateValue" class="w-8 text-sm text-gray-500">0</span>
|
||||
<input type="range" id="rate" name="rate" min="-100" max="100" value="0"
|
||||
class="mt-1 block w-full" oninput="document.getElementById('rateValue').textContent=this.value" />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<label for="pitch" class="block text-sm font-medium text-gray-700">音调调整</label>
|
||||
<div class="flex items-center mt-2">
|
||||
<span id="pitchValue" class="w-8 text-sm text-gray-500">0</span>
|
||||
<input type="range" id="pitch" name="pitch" min="-100" max="100" value="0"
|
||||
class="mt-1 block w-full" oninput="document.getElementById('pitchValue').textContent=this.value" />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="flex flex-col sm:flex-row gap-3">
|
||||
<button type="submit"
|
||||
class="inline-flex justify-center py-2 px-4 border border-transparent shadow-sm text-sm font-medium rounded-md text-white bg-ms-blue hover:bg-ms-dark-blue focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-ms-blue">
|
||||
生成语音
|
||||
</button>
|
||||
<button type="button" id="downloadBtn" style="display:none;"
|
||||
class="inline-flex justify-center py-2 px-4 border border-transparent shadow-sm text-sm font-medium rounded-md text-white bg-green-600 hover:bg-green-700 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-green-500">
|
||||
下载音频
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<div id="voiceLoadError" role="alert" class="mt-4 rounded-md bg-red-50 p-4" style="display: none;">
|
||||
<div class="flex">
|
||||
<div class="flex-shrink-0">
|
||||
<svg class="h-5 w-5 text-red-400" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="currentColor" aria-hidden="true">
|
||||
<path fill-rule="evenodd" d="M10 18a8 8 0 100-16 8 8 000 16zM8.707 7.293a1 1 00-1.414 1.414L8.586 10l-1.293 1.293a1 1 101.414 1.414L10 11.414l1.293-1.293a1 1 001.414-1.414L11.414 10l1.293-1.293a1 1 00-1.414-1.414L10 8.586 8.707 7.293z" clip-rule="evenodd" />
|
||||
</svg>
|
||||
</div>
|
||||
<div class="ml-3">
|
||||
<h3 class="text-sm font-medium text-red-800">无法加载语音列表</h3>
|
||||
<div class="mt-2 text-sm text-red-700">
|
||||
<p>显示默认语音列表。请检查网络连接或稍后再试。</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
|
||||
<div id="audioContainer" class="mt-6 rounded-md bg-gray-50 p-4 border border-gray-200" style="display: none;">
|
||||
<audio id="audioPlayer" controls class="w-full"></audio>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 右边栏:API文档 -->
|
||||
<div class="lg:w-1/3 space-y-6">
|
||||
<!-- 关于卡片 -->
|
||||
<div class="bg-white overflow-hidden shadow rounded-lg">
|
||||
<div class="px-4 py-5 sm:p-6">
|
||||
<h3 class="text-lg leading-6 font-medium text-gray-900">关于服务</h3>
|
||||
<div class="mt-2 max-w-xl text-sm text-gray-500">
|
||||
<p>
|
||||
Microsoft TTS API 是一个高质量的文本转语音服务,支持多种语言和声音。
|
||||
通过简单的 API 调用,可以将文本转换为自然流畅的语音。
|
||||
</p>
|
||||
</div>
|
||||
<div class="mt-3 bg-blue-50 p-3 rounded-md">
|
||||
<div class="flex">
|
||||
<div class="flex-shrink-0">
|
||||
<svg class="h-5 w-5 text-blue-500" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="currentColor">
|
||||
<path fill-rule="evenodd" d="M18 10a8 8 0 11-16 0 8 8 0 0116 0zm-7-4a1 1 0 11-2 0 1 1 0 012 0zM9 9a1 1 0 000 2v3a1 1 0 001 1h1a1 1 0 100-2v-3a1 1 0 00-1-1H9z" clip-rule="evenodd" />
|
||||
</svg>
|
||||
</div>
|
||||
<div class="ml-3 flex-1 md:flex md:justify-between">
|
||||
<p class="text-sm text-blue-700">
|
||||
支持 SSML 标签和 OpenAI 兼容接口
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div id="tab2" class="tab-content mt-6" style="display: none;">
|
||||
<!-- API文档部分 -->
|
||||
<div class="w-full lg:w-2/3 mx-auto space-y-6">
|
||||
<!-- API 文档链接 -->
|
||||
<div class="bg-white overflow-hidden shadow rounded-lg divide-y divide-gray-200">
|
||||
<div class="px-4 py-5 sm:px-6">
|
||||
@@ -366,7 +375,7 @@ curl ${baseUrl}/v1/audio/speech \\
|
||||
<div class="flex">
|
||||
<div class="flex-shrink-0">
|
||||
<svg class="h-5 w-5 text-amber-500" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="currentColor">
|
||||
<path fill-rule="evenodd" d="M8.257 3.099c.765-1.36 2.722-1.36 3.486 0l5.58 9.92c.75 1.334-.213 2.98-1.742 2.98H4.42c-1.53 0-2.493-1.646-1.743-2.98l5.58-9.92zM11 13a1 1 0 11-2 0 1 1 0 012 0zm-1-8a1 1 0 00-1 1v3a1 1 0 002 0V6a1 1 0 00-1-1z" clip-rule="evenodd" />
|
||||
<path fill-rule="evenodd" d="M8.257 3.099c.765-1.36 2.722-1.36 3.486 0l5.58 9.92c.75 1.334-.213 2.98-1.742 2.98H4.42c-1.53 0-2.493-1.646-1.743-2.98l5.58-9.92zM11 13a1 1 11-2 0 1 1 12 0zm-1-8a1 1 00-1 1v3a1 1 001 1h1a1 1 100-2v-3a1 1 00-1-1H9z" clip-rule="evenodd" />
|
||||
</svg>
|
||||
</div>
|
||||
<div class="ml-3">
|
||||
@@ -384,6 +393,38 @@ curl ${baseUrl}/v1/audio/speech \\
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div id="tab3" class="tab-content mt-6" style="display: none;">
|
||||
<!-- 关于服务部分 -->
|
||||
<div class="w-full lg:w-2/3 mx-auto space-y-6">
|
||||
<!-- 关于卡片 -->
|
||||
<div class="bg-white overflow-hidden shadow rounded-lg">
|
||||
<div class="px-4 py-5 sm:p-6">
|
||||
<h3 class="text-lg leading-6 font-medium text-gray-900">关于服务</h3>
|
||||
<div class="mt-2 max-w-xl text-sm text-gray-500">
|
||||
<p>
|
||||
Microsoft TTS API 是一个高质量的文本转语音服务,支持多种语言和声音。
|
||||
通过简单的 API 调用,可以将文本转换为自然流畅的语音。
|
||||
</p>
|
||||
</div>
|
||||
<div class="mt-3 bg-blue-50 p-3 rounded-md">
|
||||
<div class="flex">
|
||||
<div class="flex-shrink-0">
|
||||
<svg class="h-5 w-5 text-blue-500" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="currentColor">
|
||||
<path fill-rule="evenodd" d="M18 10a8 8 0 11-16 0 8 8 0116 0zm-7-4a1 1 11-2 0 1 1 12 0zm-1-8a1 1 00-1 1v3a1 1 001 1h1a1 1 100-2v-3a1 1 00-1-1H9z" clip-rule="evenodd" />
|
||||
</svg>
|
||||
</div>
|
||||
<div class="ml-3 flex-1 md:flex md:justify-between">
|
||||
<p class="text-sm text-blue-700">
|
||||
支持 SSML 标签和 OpenAI 兼容接口
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</main>
|
||||
|
||||
<!-- 页脚 -->
|
||||
@@ -401,6 +442,9 @@ curl ${baseUrl}/v1/audio/speech \\
|
||||
document.getElementById('ttsForm').addEventListener('submit', async function(e) {
|
||||
e.preventDefault();
|
||||
|
||||
// 隐藏先前的错误信息
|
||||
document.getElementById('apiErrorAlert').style.display = 'none';
|
||||
|
||||
const apiKey = document.getElementById('apiKey').value;
|
||||
const text = encodeURIComponent(document.getElementById('text').value);
|
||||
const voice = document.getElementById('voice').value;
|
||||
@@ -408,18 +452,36 @@ curl ${baseUrl}/v1/audio/speech \\
|
||||
const pitch = document.getElementById('pitch').value;
|
||||
|
||||
if (!text) {
|
||||
alert('请输入要转换的文本');
|
||||
showError('请输入要转换的文本', '文本内容不能为空');
|
||||
return;
|
||||
}
|
||||
|
||||
if (!apiKey) {
|
||||
alert('请输入API Key');
|
||||
showError('请输入API Key', 'API密钥不能为空');
|
||||
return;
|
||||
}
|
||||
|
||||
const url = \`${baseUrl}/tts?api_key=\${apiKey}&t=\${text}&v=\${voice}&r=\${rate}&p=\${pitch}\`;
|
||||
|
||||
try {
|
||||
const response = await fetch(url);
|
||||
|
||||
if (!response.ok) {
|
||||
// 处理错误响应
|
||||
if (response.status === 401) {
|
||||
try {
|
||||
const errorData = await response.json();
|
||||
showError('认证失败', errorData.message || '无效的API密钥,请确保您提供了正确的密钥');
|
||||
} catch (e) {
|
||||
showError('认证失败', '无效的API密钥,请确保您提供了正确的密钥');
|
||||
}
|
||||
return;
|
||||
} else {
|
||||
showError('请求失败');
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
const audioPlayer = document.getElementById('audioPlayer');
|
||||
audioPlayer.src = url;
|
||||
audioPlayer.play();
|
||||
@@ -432,10 +494,21 @@ curl ${baseUrl}/v1/audio/speech \\
|
||||
window.location.href = downloadUrl;
|
||||
};
|
||||
} catch (error) {
|
||||
alert('生成音频失败: ' + error.message);
|
||||
showError('生成音频失败', error.message);
|
||||
}
|
||||
});
|
||||
|
||||
// 显示错误信息的函数
|
||||
function showError(title, message) {
|
||||
const errorAlert = document.getElementById('apiErrorAlert');
|
||||
document.getElementById('apiErrorTitle').textContent = title;
|
||||
document.getElementById('apiErrorMessage').textContent = message;
|
||||
errorAlert.style.display = 'block';
|
||||
|
||||
// 滚动到错误信息
|
||||
errorAlert.scrollIntoView({ behavior: 'smooth', block: 'center' });
|
||||
}
|
||||
|
||||
// 按语言筛选语音
|
||||
function filterVoicesByLanguage() {
|
||||
const languageFilter = document.getElementById('languageFilter').value;
|
||||
@@ -593,10 +666,31 @@ curl ${baseUrl}/v1/audio/speech \\
|
||||
|
||||
// 页面加载完成后加载语音列表
|
||||
window.onload = loadVoices;
|
||||
|
||||
document.addEventListener('DOMContentLoaded', function() {
|
||||
const tabLinks = document.querySelectorAll('.tab-link');
|
||||
const tabContents = document.querySelectorAll('.tab-content');
|
||||
|
||||
tabLinks.forEach(link => {
|
||||
link.addEventListener('click', () => {
|
||||
tabLinks.forEach(l => {
|
||||
l.classList.remove('text-ms-blue', 'border-ms-blue');
|
||||
l.classList.add('text-gray-500');
|
||||
});
|
||||
link.classList.add('text-ms-blue', 'border-ms-blue');
|
||||
link.classList.remove('text-gray-500');
|
||||
|
||||
const targetId = link.getAttribute('data-tab');
|
||||
tabContents.forEach(tc => {
|
||||
tc.style.display = (tc.id === targetId) ? '' : 'none';
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
`, { status: 200, headers: { 'Content-Type': 'text/html; charset=utf-8' }});
|
||||
`, { status: 200, headers: { 'Content-Type': 'text/html; charset=utf-8'}});
|
||||
}
|
||||
|
||||
addEventListener('fetch', event => {
|
||||
@@ -708,7 +802,9 @@ async function bytesToBase64(bytes) {
|
||||
|
||||
// API 密钥验证函数
|
||||
function validateApiKey(apiKey) {
|
||||
return apiKey === API_KEY;
|
||||
// 从环境变量获取 API 密钥并进行验证
|
||||
const expectedApiKey = API_KEY || '';
|
||||
return apiKey === expectedApiKey;
|
||||
}
|
||||
|
||||
async function getVoice(text, voiceName = 'zh-CN-XiaoxiaoMultilingualNeural', rate = 0, pitch = 0, outputFormat='audio-24khz-48kbitrate-mono-mp3', download=false) {
|
||||
|
||||
@@ -2,4 +2,8 @@ name = "wstrans"
|
||||
main = "src/index.js"
|
||||
compatibility_date = "2024-04-15"
|
||||
workers_dev = true
|
||||
node_compat = true
|
||||
compatibility_flags = [ "nodejs_compat" ]
|
||||
|
||||
|
||||
[vars]
|
||||
API_KEY = "zuoban"
|
||||
Reference in New Issue
Block a user