@@ -0,0 +1,221 @@
|
||||
// DOM元素获取
|
||||
const apiKeyInput = document.getElementById('apiKey');
|
||||
const apiBaseInput = document.getElementById('apiBase');
|
||||
const textInput = document.getElementById('textInput');
|
||||
const charCount = document.getElementById('charCount');
|
||||
const modelSelect = document.getElementById('model');
|
||||
const voiceSelect = document.getElementById('voice');
|
||||
const generateBtn = document.getElementById('generateBtn');
|
||||
const playBtn = document.getElementById('playBtn');
|
||||
const downloadBtn = document.getElementById('downloadBtn');
|
||||
const statusDiv = document.getElementById('status');
|
||||
const audioPlayer = document.getElementById('audioPlayer');
|
||||
|
||||
// 当前生成的音频URL
|
||||
let currentAudioURL = null;
|
||||
|
||||
// 初始化函数
|
||||
function init() {
|
||||
// 加载本地存储的配置
|
||||
loadConfig();
|
||||
|
||||
// 绑定事件监听器
|
||||
bindEventListeners();
|
||||
|
||||
// 初始状态更新
|
||||
updateCharCount();
|
||||
}
|
||||
|
||||
// 加载本地存储的配置
|
||||
function loadConfig() {
|
||||
const savedAPIKey = localStorage.getItem('openai_api_key');
|
||||
const savedAPIBase = localStorage.getItem('openai_api_base');
|
||||
|
||||
if (savedAPIKey) {
|
||||
apiKeyInput.value = savedAPIKey;
|
||||
}
|
||||
|
||||
if (savedAPIBase) {
|
||||
apiBaseInput.value = savedAPIBase;
|
||||
}
|
||||
}
|
||||
|
||||
// 保存配置到本地存储
|
||||
function saveConfig() {
|
||||
localStorage.setItem('openai_api_key', apiKeyInput.value);
|
||||
localStorage.setItem('openai_api_base', apiBaseInput.value);
|
||||
}
|
||||
|
||||
// 绑定事件监听器
|
||||
function bindEventListeners() {
|
||||
// 文本输入事件 - 更新字符计数
|
||||
textInput.addEventListener('input', updateCharCount);
|
||||
|
||||
// API配置输入事件 - 保存到本地存储
|
||||
apiKeyInput.addEventListener('input', saveConfig);
|
||||
apiBaseInput.addEventListener('input', saveConfig);
|
||||
|
||||
// 生成语音按钮点击事件
|
||||
generateBtn.addEventListener('click', generateSpeech);
|
||||
|
||||
// 播放按钮点击事件
|
||||
playBtn.addEventListener('click', togglePlay);
|
||||
|
||||
// 下载按钮点击事件
|
||||
downloadBtn.addEventListener('click', downloadAudio);
|
||||
|
||||
// 音频播放结束事件
|
||||
audioPlayer.addEventListener('ended', onAudioEnded);
|
||||
}
|
||||
|
||||
// 更新字符计数
|
||||
function updateCharCount() {
|
||||
const count = textInput.value.length;
|
||||
charCount.textContent = count;
|
||||
}
|
||||
|
||||
// 生成语音
|
||||
async function generateSpeech() {
|
||||
// 验证输入
|
||||
if (!validateInput()) {
|
||||
return;
|
||||
}
|
||||
|
||||
// 保存配置
|
||||
saveConfig();
|
||||
|
||||
// 更新状态
|
||||
setStatus('loading', '正在生成语音...');
|
||||
disableControls();
|
||||
|
||||
try {
|
||||
// 构建请求数据
|
||||
const requestData = {
|
||||
api_key: apiKeyInput.value,
|
||||
text: textInput.value,
|
||||
voice: voiceSelect.value,
|
||||
model: modelSelect.value,
|
||||
api_base: apiBaseInput.value
|
||||
};
|
||||
|
||||
// 发送请求到后端API
|
||||
const response = await fetch('/api/tts', {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json'
|
||||
},
|
||||
body: JSON.stringify(requestData)
|
||||
});
|
||||
|
||||
if (!response.ok) {
|
||||
const errorData = await response.json().catch(() => ({
|
||||
error: `HTTP错误: ${response.status} ${response.statusText}`
|
||||
}));
|
||||
throw new Error(errorData.error || '生成语音失败');
|
||||
}
|
||||
|
||||
// 获取音频Blob
|
||||
const audioBlob = await response.blob();
|
||||
|
||||
// 创建音频URL
|
||||
if (currentAudioURL) {
|
||||
URL.revokeObjectURL(currentAudioURL);
|
||||
}
|
||||
currentAudioURL = URL.createObjectURL(audioBlob);
|
||||
|
||||
// 更新音频播放器
|
||||
audioPlayer.src = currentAudioURL;
|
||||
|
||||
// 更新状态
|
||||
setStatus('success', '语音生成成功!');
|
||||
enableControls();
|
||||
|
||||
} catch (error) {
|
||||
// 处理错误
|
||||
setStatus('error', `生成失败: ${error.message}`);
|
||||
disableControls();
|
||||
}
|
||||
}
|
||||
|
||||
// 验证输入
|
||||
function validateInput() {
|
||||
if (!apiKeyInput.value.trim()) {
|
||||
setStatus('error', '请输入API Key');
|
||||
apiKeyInput.focus();
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!textInput.value.trim()) {
|
||||
setStatus('error', '请输入要转换的文本');
|
||||
textInput.focus();
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
// 切换播放状态
|
||||
function togglePlay() {
|
||||
if (audioPlayer.paused) {
|
||||
audioPlayer.play();
|
||||
playBtn.textContent = '暂停';
|
||||
} else {
|
||||
audioPlayer.pause();
|
||||
playBtn.textContent = '播放';
|
||||
}
|
||||
}
|
||||
|
||||
// 音频播放结束
|
||||
function onAudioEnded() {
|
||||
playBtn.textContent = '播放';
|
||||
}
|
||||
|
||||
// 下载音频
|
||||
function downloadAudio() {
|
||||
if (!currentAudioURL) {
|
||||
return;
|
||||
}
|
||||
|
||||
// 创建下载链接
|
||||
const link = document.createElement('a');
|
||||
link.href = currentAudioURL;
|
||||
link.download = 'speech.mp3';
|
||||
|
||||
// 触发下载
|
||||
document.body.appendChild(link);
|
||||
link.click();
|
||||
document.body.removeChild(link);
|
||||
}
|
||||
|
||||
// 设置状态
|
||||
function setStatus(type, message) {
|
||||
statusDiv.className = `status ${type}`;
|
||||
statusDiv.textContent = message;
|
||||
|
||||
// 清除成功状态
|
||||
if (type === 'success') {
|
||||
setTimeout(() => {
|
||||
statusDiv.className = 'status';
|
||||
statusDiv.textContent = '';
|
||||
}, 3000);
|
||||
}
|
||||
}
|
||||
|
||||
// 禁用控件
|
||||
function disableControls() {
|
||||
generateBtn.disabled = true;
|
||||
playBtn.disabled = true;
|
||||
downloadBtn.disabled = true;
|
||||
generateBtn.classList.add('loading');
|
||||
}
|
||||
|
||||
// 启用控件
|
||||
function enableControls() {
|
||||
generateBtn.disabled = false;
|
||||
playBtn.disabled = false;
|
||||
downloadBtn.disabled = false;
|
||||
generateBtn.classList.remove('loading');
|
||||
}
|
||||
|
||||
// 页面加载完成后初始化
|
||||
document.addEventListener('DOMContentLoaded', init);
|
||||
Reference in New Issue
Block a user