'commit'
This commit is contained in:
@@ -158,6 +158,12 @@
|
||||
min-height: 100px;
|
||||
}
|
||||
|
||||
.voice-options p {
|
||||
color: #666;
|
||||
text-align: center;
|
||||
padding: 20px;
|
||||
}
|
||||
|
||||
.voice-card {
|
||||
border: 1px solid #ddd;
|
||||
border-radius: 8px;
|
||||
@@ -165,12 +171,6 @@
|
||||
cursor: pointer;
|
||||
transition: all 0.3s;
|
||||
}
|
||||
|
||||
.voice-options p {
|
||||
color: #666;
|
||||
text-align: center;
|
||||
padding: 20px;
|
||||
}
|
||||
|
||||
.voice-card:hover {
|
||||
border-color: #3498db;
|
||||
@@ -242,7 +242,7 @@
|
||||
|
||||
<div class="form-group">
|
||||
<label for="text-input">输入文本</label>
|
||||
<textarea id="text-input" placeholder="请输入要转换为语音的文本..."></textarea>
|
||||
<textarea id="text-input" placeholder="请输入要转换为语音的文本...">海上升明月,天涯共此时。</textarea>
|
||||
</div>
|
||||
|
||||
<div class="form-group">
|
||||
@@ -321,105 +321,121 @@
|
||||
// 当前选中的音色
|
||||
let selectedVoiceType = null;
|
||||
|
||||
// API基础URL
|
||||
const apiBaseUrl = '/api/VideoRetalk';
|
||||
// API基础URL - 使用完整URL
|
||||
const apiBaseUrl = window.location.origin + '/api/tts';
|
||||
|
||||
// 显示错误信息
|
||||
function showError(message) {
|
||||
errorMessage.textContent = message;
|
||||
errorMessage.style.display = 'block';
|
||||
successMessage.style.display = 'none';
|
||||
}
|
||||
|
||||
// 显示成功信息
|
||||
function showSuccess(message) {
|
||||
successMessage.textContent = message;
|
||||
successMessage.style.display = 'block';
|
||||
errorMessage.style.display = 'none';
|
||||
}
|
||||
|
||||
// 隐藏所有消息
|
||||
function hideMessages() {
|
||||
errorMessage.style.display = 'none';
|
||||
successMessage.style.display = 'none';
|
||||
}
|
||||
|
||||
// 获取所有音色分类
|
||||
async function loadVoiceCategories() {
|
||||
try {
|
||||
const response = await fetch(`${apiBaseUrl}/voices/categories`);
|
||||
const data = await response.json();
|
||||
console.log('正在加载音色分类,API地址:', `${apiBaseUrl}/voice-categories`);
|
||||
const response = await fetch(`${apiBaseUrl}/voice-categories`);
|
||||
|
||||
if (data.success) {
|
||||
// 清空现有选项
|
||||
categorySelect.innerHTML = '<option value="">请选择音色分类</option>';
|
||||
|
||||
// 添加分类选项
|
||||
if (!response.ok) {
|
||||
throw new Error(`API请求失败: ${response.status} ${response.statusText}`);
|
||||
}
|
||||
|
||||
const data = await response.json();
|
||||
console.log('音色分类数据:', data);
|
||||
|
||||
// 清空下拉列表
|
||||
categorySelect.innerHTML = '<option value="">请选择音色分类</option>';
|
||||
|
||||
// 检查返回数据结构
|
||||
if (data.success && data.data && Array.isArray(data.data) && data.data.length > 0) {
|
||||
data.data.forEach(category => {
|
||||
const option = document.createElement('option');
|
||||
option.value = category;
|
||||
option.value = category; // 直接使用分类名称作为值
|
||||
option.textContent = category;
|
||||
categorySelect.appendChild(option);
|
||||
});
|
||||
} else {
|
||||
showError('获取音色分类失败: ' + data.message);
|
||||
showError('未能加载音色分类列表');
|
||||
console.error('音色分类数据格式不正确:', data);
|
||||
}
|
||||
|
||||
// 绑定分类变化事件(确保只绑定一次)
|
||||
if (!categorySelect.dataset.eventBound) {
|
||||
categorySelect.addEventListener('change', function() {
|
||||
if (this.value) {
|
||||
loadVoicesByCategory(this.value);
|
||||
} else {
|
||||
voiceOptions.innerHTML = '<p>请先选择音色分类</p>';
|
||||
selectedVoiceType = null;
|
||||
}
|
||||
});
|
||||
categorySelect.dataset.eventBound = 'true';
|
||||
}
|
||||
} catch (error) {
|
||||
showError('获取音色分类失败: ' + error.message);
|
||||
console.error('加载音色分类失败:', error);
|
||||
showError(`加载音色分类失败: ${error.message}`);
|
||||
}
|
||||
}
|
||||
|
||||
// 根据分类获取音色列表
|
||||
async function loadVoicesByCategory(category) {
|
||||
// 加载指定分类的音色
|
||||
async function loadVoicesByCategory(categoryId) {
|
||||
try {
|
||||
const response = await fetch(`${apiBaseUrl}/voices/by-category/${category}`);
|
||||
const data = await response.json();
|
||||
console.log('正在加载音色列表,分类:', categoryId);
|
||||
const response = await fetch(`${apiBaseUrl}/voices?category=${encodeURIComponent(categoryId)}`);
|
||||
|
||||
if (data.success) {
|
||||
// 清空现有音色选项
|
||||
voiceOptions.innerHTML = '';
|
||||
|
||||
// 添加数据类型检查
|
||||
if (typeof data.data !== 'object' || data.data === null) {
|
||||
showError('获取的音色列表格式不正确');
|
||||
return;
|
||||
}
|
||||
|
||||
// 将对象转换为数组格式 [{voice_type, name, description}, ...]
|
||||
const voicesArray = Object.entries(data.data).map(([voiceType, description]) => {
|
||||
// 从描述中提取名称和说明(假设格式为 "名称(说明)")
|
||||
const match = description.match(/^(.*?)\((.*?)\)$/);
|
||||
return {
|
||||
voice_type: voiceType,
|
||||
name: match ? match[1] : description,
|
||||
description: match ? match[2] : '无描述'
|
||||
};
|
||||
});
|
||||
|
||||
// 检查数组是否为空
|
||||
if (voicesArray.length === 0) {
|
||||
voiceOptions.innerHTML = '<p>该分类下没有可用音色</p>';
|
||||
return;
|
||||
}
|
||||
|
||||
// 添加音色卡片
|
||||
voicesArray.forEach(voice => {
|
||||
if (!response.ok) {
|
||||
throw new Error(`API请求失败: ${response.status} ${response.statusText}`);
|
||||
}
|
||||
|
||||
const data = await response.json();
|
||||
console.log('音色列表数据:', data);
|
||||
voiceOptions.innerHTML = '';
|
||||
|
||||
// 检查返回数据结构
|
||||
if (data.success && data.data && typeof data.data === 'object' && Object.keys(data.data).length > 0) {
|
||||
Object.entries(data.data).forEach(([voiceId, voiceName]) => {
|
||||
const voiceCard = document.createElement('div');
|
||||
voiceCard.className = 'voice-card';
|
||||
voiceCard.dataset.voiceType = voice.voice_type;
|
||||
voiceCard.dataset.voiceType = voiceId;
|
||||
|
||||
const voiceName = document.createElement('div');
|
||||
voiceName.className = 'voice-name';
|
||||
voiceName.textContent = voice.name;
|
||||
voiceCard.innerHTML = `
|
||||
<div class="voice-name">${voiceName}</div>
|
||||
`;
|
||||
|
||||
const voiceDescription = document.createElement('div');
|
||||
voiceDescription.className = 'voice-description';
|
||||
voiceDescription.textContent = voice.description || '暂无描述';
|
||||
|
||||
voiceCard.appendChild(voiceName);
|
||||
voiceCard.appendChild(voiceDescription);
|
||||
|
||||
// 添加点击事件
|
||||
voiceCard.addEventListener('click', function() {
|
||||
// 移除其他卡片的选中状态
|
||||
// 移除其他选中状态
|
||||
document.querySelectorAll('.voice-card').forEach(card => {
|
||||
card.classList.remove('selected');
|
||||
});
|
||||
|
||||
// 添加当前卡片的选中状态
|
||||
// 设置当前选中状态
|
||||
this.classList.add('selected');
|
||||
|
||||
// 保存选中的音色类型
|
||||
selectedVoiceType = this.dataset.voiceType;
|
||||
console.log('已选择音色:', selectedVoiceType);
|
||||
});
|
||||
|
||||
voiceOptions.appendChild(voiceCard);
|
||||
});
|
||||
} else {
|
||||
voiceOptions.innerHTML = '<p>获取音色列表失败: ' + data.message + '</p>';
|
||||
voiceOptions.innerHTML = '<p>该分类下没有可用音色</p>';
|
||||
console.error('音色列表数据格式不正确:', data);
|
||||
}
|
||||
} catch (error) {
|
||||
voiceOptions.innerHTML = '<p>获取音色列表失败: ' + error.message + '</p>';
|
||||
console.error('加载音色列表失败:', error);
|
||||
showError('加载音色列表失败,请重试');
|
||||
}
|
||||
}
|
||||
|
||||
@@ -456,8 +472,10 @@
|
||||
encoding: 'mp3'
|
||||
};
|
||||
|
||||
console.log('正在生成音频,请求数据:', requestData);
|
||||
|
||||
// 发送请求
|
||||
const response = await fetch(`${apiBaseUrl}/tts`, {
|
||||
const response = await fetch(`${apiBaseUrl}/generate`, {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json'
|
||||
@@ -466,6 +484,7 @@
|
||||
});
|
||||
|
||||
const data = await response.json();
|
||||
console.log('音频生成结果:', data);
|
||||
|
||||
if (data.success) {
|
||||
// 显示成功消息
|
||||
@@ -477,61 +496,50 @@
|
||||
|
||||
// 设置下载按钮
|
||||
downloadBtn.onclick = function() {
|
||||
const a = document.createElement('a');
|
||||
a.href = data.audio_url;
|
||||
a.download = 'tts_audio.mp3';
|
||||
document.body.appendChild(a);
|
||||
a.click();
|
||||
document.body.removeChild(a);
|
||||
if (!data.audio_url) {
|
||||
showError('没有找到音频文件');
|
||||
return;
|
||||
}
|
||||
|
||||
// 创建有意义的文件名 (使用文本前10个字符 + 时间戳)
|
||||
const textPreview = textInput.value.trim().substring(0, 10).replace(/[^a-zA-Z0-9]/g, '_');
|
||||
const timestamp = new Date().toISOString().replace(/[:.]/g, '-');
|
||||
const fileName = `tts_${textPreview}_${timestamp}.mp3`;
|
||||
|
||||
try {
|
||||
const a = document.createElement('a');
|
||||
a.href = data.audio_url;
|
||||
a.download = fileName;
|
||||
document.body.appendChild(a);
|
||||
a.click();
|
||||
document.body.removeChild(a);
|
||||
} catch (error) {
|
||||
console.error('下载失败:', error);
|
||||
showError('下载失败,请重试');
|
||||
}
|
||||
};
|
||||
} else {
|
||||
showError('语音生成失败: ' + data.message);
|
||||
emptyResult.style.display = 'block';
|
||||
showError(data.message || '语音生成失败');
|
||||
}
|
||||
} catch (error) {
|
||||
showError('语音生成失败: ' + error.message);
|
||||
emptyResult.style.display = 'block';
|
||||
console.error('生成语音失败:', error);
|
||||
showError('生成语音失败,请重试');
|
||||
} finally {
|
||||
// 隐藏加载状态
|
||||
// 恢复按钮状态
|
||||
loading.classList.remove('active');
|
||||
generateBtn.disabled = false;
|
||||
|
||||
// 如果没有音频结果,显示空状态
|
||||
if (audioResult.style.display === 'none') {
|
||||
emptyResult.style.display = 'block';
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 显示错误消息
|
||||
function showError(message) {
|
||||
errorMessage.textContent = message;
|
||||
errorMessage.style.display = 'block';
|
||||
successMessage.style.display = 'none';
|
||||
}
|
||||
|
||||
// 显示成功消息
|
||||
function showSuccess(message) {
|
||||
successMessage.textContent = message;
|
||||
successMessage.style.display = 'block';
|
||||
errorMessage.style.display = 'none';
|
||||
}
|
||||
|
||||
// 隐藏所有消息
|
||||
function hideMessages() {
|
||||
errorMessage.style.display = 'none';
|
||||
successMessage.style.display = 'none';
|
||||
}
|
||||
|
||||
// 事件监听器
|
||||
categorySelect.addEventListener('change', function() {
|
||||
const category = this.value;
|
||||
if (category) {
|
||||
loadVoicesByCategory(category);
|
||||
} else {
|
||||
voiceOptions.innerHTML = '<p>请先选择音色分类</p>';
|
||||
selectedVoiceType = null;
|
||||
}
|
||||
});
|
||||
|
||||
// 绑定生成按钮点击事件
|
||||
generateBtn.addEventListener('click', generateAudio);
|
||||
|
||||
// 初始化
|
||||
// 初始化加载音色分类
|
||||
loadVoiceCategories();
|
||||
});
|
||||
</script>
|
||||
|
Reference in New Issue
Block a user