Happy_language/app/static/js/voice_test.js
superlishunqin 28b23647e6 test-api
2025-09-15 00:34:51 +08:00

502 lines
15 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

/**
* CosyVoice API 测试页面 JavaScript
*/
// 全局变量
let uploadedAudioPath = null;
let loadingModal = null;
// DOM加载完成后初始化
document.addEventListener('DOMContentLoaded', function() {
initializeComponents();
bindEvents();
loadAvailableVoices();
});
/**
* 初始化组件
*/
function initializeComponents() {
loadingModal = new bootstrap.Modal(document.getElementById('loadingModal'));
// 语速滑块显示
const speedSlider = document.getElementById('preset-speed');
const speedValue = document.getElementById('preset-speed-value');
speedSlider.addEventListener('input', function() {
speedValue.textContent = this.value;
});
}
/**
* 绑定事件
*/
function bindEvents() {
// 连接测试
document.getElementById('test-connection-btn').addEventListener('click', testConnection);
// 预训练音色测试
document.getElementById('preset-voice-form').addEventListener('submit', generatePresetVoice);
document.getElementById('preset-random-seed').addEventListener('click', () => getRandomSeed('preset-seed'));
// 自然语言控制测试
document.getElementById('natural-control-form').addEventListener('submit', generateNaturalControl);
document.getElementById('natural-random-seed').addEventListener('click', () => getRandomSeed('natural-seed'));
// 语音克隆测试
document.getElementById('audio-upload-form').addEventListener('submit', uploadReferenceAudio);
document.getElementById('voice-clone-form').addEventListener('submit', generateVoiceClone);
document.getElementById('clone-random-seed').addEventListener('click', () => getRandomSeed('clone-seed'));
// 清空日志
document.getElementById('clear-log').addEventListener('click', clearLog);
}
/**
* 显示加载状态
*/
function showLoading(message = '正在处理中...') {
document.getElementById('loading-message').textContent = message;
loadingModal.show();
}
/**
* 隐藏加载状态
*/
function hideLoading() {
loadingModal.hide();
}
/**
* 添加日志
*/
function addLog(message, type = 'info') {
const logContainer = document.getElementById('test-log');
const timestamp = new Date().toLocaleTimeString();
const logEntry = document.createElement('div');
const colors = {
'info': 'text-primary',
'success': 'text-success',
'error': 'text-danger',
'warning': 'text-warning'
};
logEntry.className = `mb-2 ${colors[type] || 'text-primary'}`;
logEntry.innerHTML = `<small>[${timestamp}]</small> ${message}`;
logContainer.appendChild(logEntry);
logContainer.scrollTop = logContainer.scrollHeight;
}
/**
* 清空日志
*/
function clearLog() {
const logContainer = document.getElementById('test-log');
logContainer.innerHTML = '<p class="text-muted">测试记录将显示在这里...</p>';
}
/**
* 显示错误信息
*/
function showError(message) {
const toast = document.createElement('div');
toast.className = 'toast align-items-center text-white bg-danger border-0 position-fixed top-0 end-0 m-3';
toast.style.zIndex = '9999';
toast.innerHTML = `
<div class="d-flex">
<div class="toast-body">
<i class="fas fa-exclamation-circle me-2"></i>${message}
</div>
<button type="button" class="btn-close btn-close-white me-2 m-auto" data-bs-dismiss="toast"></button>
</div>
`;
document.body.appendChild(toast);
const bsToast = new bootstrap.Toast(toast);
bsToast.show();
// 自动移除
setTimeout(() => {
if (toast.parentNode) {
toast.parentNode.removeChild(toast);
}
}, 5000);
}
/**
* 显示成功信息
*/
function showSuccess(message) {
const toast = document.createElement('div');
toast.className = 'toast align-items-center text-white bg-success border-0 position-fixed top-0 end-0 m-3';
toast.style.zIndex = '9999';
toast.innerHTML = `
<div class="d-flex">
<div class="toast-body">
<i class="fas fa-check-circle me-2"></i>${message}
</div>
<button type="button" class="btn-close btn-close-white me-2 m-auto" data-bs-dismiss="toast"></button>
</div>
`;
document.body.appendChild(toast);
const bsToast = new bootstrap.Toast(toast);
bsToast.show();
// 自动移除
setTimeout(() => {
if (toast.parentNode) {
toast.parentNode.removeChild(toast);
}
}, 3000);
}
/**
* 测试连接
*/
async function testConnection() {
const btn = document.getElementById('test-connection-btn');
const statusDiv = document.getElementById('connection-status');
btn.disabled = true;
btn.innerHTML = '<i class="fas fa-spinner fa-spin me-2"></i>测试中...';
statusDiv.innerHTML = '<span class="text-info">正在测试连接...</span>';
addLog('开始测试CosyVoice服务连接...');
try {
const response = await fetch('/voice-test/api/voice-test/connection', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
}
});
const result = await response.json();
if (result.success) {
statusDiv.innerHTML = `
<span class="text-success">
<i class="fas fa-check-circle me-2"></i>连接成功
<small class="text-muted">${result.api_url}</small>
</span>
`;
addLog(`连接成功!可用音色数量: ${result.available_voices ? result.available_voices.length : 0}`, 'success');
// 更新音色列表
if (result.available_voices) {
updateVoiceOptions(result.available_voices);
}
} else {
statusDiv.innerHTML = `
<span class="text-danger">
<i class="fas fa-times-circle me-2"></i>连接失败: ${result.message}
</span>
`;
addLog(`连接失败: ${result.message}`, 'error');
}
} catch (error) {
statusDiv.innerHTML = `
<span class="text-danger">
<i class="fas fa-times-circle me-2"></i>请求失败: ${error.message}
</span>
`;
addLog(`请求失败: ${error.message}`, 'error');
} finally {
btn.disabled = false;
btn.innerHTML = '<i class="fas fa-wifi me-2"></i>测试连接';
}
}
/**
* 加载可用音色
*/
async function loadAvailableVoices() {
try {
const response = await fetch('/voice-test/api/voice-test/voices');
const result = await response.json();
if (result.success) {
updateVoiceOptions(result.voices);
}
} catch (error) {
console.error('加载音色失败:', error);
}
}
/**
* 更新音色选项
*/
function updateVoiceOptions(voices) {
const voiceSelect = document.getElementById('preset-voice');
voiceSelect.innerHTML = '';
voices.forEach(voice => {
const option = document.createElement('option');
option.value = voice;
option.textContent = voice;
voiceSelect.appendChild(option);
});
}
/**
* 获取随机种子
*/
async function getRandomSeed(inputId) {
try {
const response = await fetch('/voice-test/api/voice-test/random-seed');
const result = await response.json();
if (result.success) {
document.getElementById(inputId).value = result.seed;
addLog(`生成随机种子: ${result.seed}`);
}
} catch (error) {
showError('获取随机种子失败');
}
}
/**
* 预训练音色语音生成
*/
async function generatePresetVoice(e) {
e.preventDefault();
const text = document.getElementById('preset-text').value.trim();
const voice = document.getElementById('preset-voice').value;
const seed = parseInt(document.getElementById('preset-seed').value);
const speed = parseFloat(document.getElementById('preset-speed').value);
if (!text) {
showError('请输入要合成的文本');
return;
}
showLoading('正在生成语音...');
addLog(`开始预训练音色生成 - 音色: ${voice}, 种子: ${seed}, 语速: ${speed}x`);
try {
const response = await fetch('/voice-test/api/voice-test/generate/preset', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify({
text: text,
voice: voice,
seed: seed,
speed: speed
})
});
const result = await response.json();
if (result.success) {
// 显示音频播放器
const audioSource = document.getElementById('preset-audio-source');
const resultDiv = document.getElementById('preset-result');
audioSource.src = result.audio_url;
audioSource.parentElement.load();
resultDiv.style.display = 'block';
addLog(`预训练音色生成成功!音频地址: ${result.audio_url}`, 'success');
showSuccess('语音生成成功!');
} else {
addLog(`预训练音色生成失败: ${result.message}`, 'error');
showError(result.message);
}
} catch (error) {
addLog(`预训练音色生成出错: ${error.message}`, 'error');
showError('生成失败,请检查网络连接');
} finally {
hideLoading();
}
}
/**
* 自然语言控制语音生成
*/
async function generateNaturalControl(e) {
e.preventDefault();
const text = document.getElementById('natural-text').value.trim();
const instruction = document.getElementById('natural-instruction').value.trim();
const seed = parseInt(document.getElementById('natural-seed').value);
if (!text) {
showError('请输入要合成的文本');
return;
}
if (!instruction) {
showError('请输入语音指令');
return;
}
showLoading('正在生成语音...');
addLog(`开始自然语言控制生成 - 指令: ${instruction}, 种子: ${seed}`);
try {
const response = await fetch('/voice-test/api/voice-test/generate/natural', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify({
text: text,
instruction: instruction,
seed: seed
})
});
const result = await response.json();
if (result.success) {
// 显示音频播放器
const audioSource = document.getElementById('natural-audio-source');
const resultDiv = document.getElementById('natural-result');
audioSource.src = result.audio_url;
audioSource.parentElement.load();
resultDiv.style.display = 'block';
addLog(`自然语言控制生成成功!音频地址: ${result.audio_url}`, 'success');
showSuccess('语音生成成功!');
} else {
addLog(`自然语言控制生成失败: ${result.message}`, 'error');
showError(result.message);
}
} catch (error) {
addLog(`自然语言控制生成出错: ${error.message}`, 'error');
showError('生成失败,请检查网络连接');
} finally {
hideLoading();
}
}
/**
* 上传参考音频
*/
async function uploadReferenceAudio(e) {
e.preventDefault();
const fileInput = document.getElementById('reference-audio');
const file = fileInput.files[0];
if (!file) {
showError('请选择音频文件');
return;
}
showLoading('正在上传并识别音频...');
addLog(`开始上传音频文件: ${file.name} (${(file.size/1024/1024).toFixed(2)} MB)`);
const formData = new FormData();
formData.append('audio', file);
try {
const response = await fetch('/voice-test/api/voice-test/upload-audio', {
method: 'POST',
body: formData
});
const result = await response.json();
if (result.success) {
// 保存音频路径
uploadedAudioPath = result.file_path;
// 显示识别结果
const resultDiv = document.getElementById('upload-result');
const recognizedText = document.getElementById('recognized-text');
recognizedText.value = result.recognized_text || '';
resultDiv.style.display = 'block';
// 启用克隆按钮
const cloneBtn = document.querySelector('#voice-clone-form button[type="submit"]');
cloneBtn.disabled = false;
addLog(`音频上传成功!识别文本: ${result.recognized_text || '(无内容)'}`, 'success');
showSuccess('音频上传成功!');
} else {
addLog(`音频上传失败: ${result.message}`, 'error');
showError(result.message);
}
} catch (error) {
addLog(`音频上传出错: ${error.message}`, 'error');
showError('上传失败,请检查网络连接');
} finally {
hideLoading();
}
}
/**
* 语音克隆生成
*/
async function generateVoiceClone(e) {
e.preventDefault();
if (!uploadedAudioPath) {
showError('请先上传参考音频');
return;
}
const text = document.getElementById('clone-text').value.trim();
const referenceText = document.getElementById('recognized-text').value.trim();
const seed = parseInt(document.getElementById('clone-seed').value);
if (!text) {
showError('请输入要合成的文本');
return;
}
showLoading('正在进行语音克隆...');
addLog(`开始语音克隆 - 种子: ${seed}`);
try {
const response = await fetch('/voice-test/api/voice-test/generate/clone', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify({
text: text,
reference_audio_path: uploadedAudioPath,
reference_text: referenceText,
seed: seed
})
});
const result = await response.json();
if (result.success) {
// 显示音频播放器
const audioSource = document.getElementById('clone-audio-source');
const resultDiv = document.getElementById('clone-result');
audioSource.src = result.audio_url;
audioSource.parentElement.load();
resultDiv.style.display = 'block';
addLog(`语音克隆成功!音频地址: ${result.audio_url}`, 'success');
showSuccess('语音克隆成功!');
} else {
addLog(`语音克隆失败: ${result.message}`, 'error');
showError(result.message);
}
} catch (error) {
addLog(`语音克隆出错: ${error.message}`, 'error');
showError('克隆失败,请检查网络连接');
} finally {
hideLoading();
}
}