/**
 * 语音克隆测试页面 JavaScript
 * 核心功能:语音样本采集 → 识别 → 克隆 → 对比
 */
// 全局变量
let loadingModal = null;
let mediaRecorder = null;
let audioChunks = [];
let uploadedAudioPath = null;
let recognizedText = "";
let sampleAudioUrl = null;
let recordedAudioBlob = null;
// 当前工作流程状态
let currentStep = 1;
// DOM加载完成后初始化
document.addEventListener('DOMContentLoaded', function() {
    initializeComponents();
    bindEvents();
    loadAvailableVoices();
    updateStepIndicators();
});
/**
 * 初始化组件
 */
function initializeComponents() {
    loadingModal = new bootstrap.Modal(document.getElementById('loadingModal'));
}
/**
 * 绑定事件
 */
function bindEvents() {
    // 连接测试
    document.getElementById('test-connection-btn').addEventListener('click', testConnection);
    
    // 语音样本采集
    document.getElementById('voice-sample-form').addEventListener('submit', uploadVoiceSample);
    document.getElementById('voice-sample-upload').addEventListener('change', handleFileSelect);
    document.getElementById('start-recording').addEventListener('click', startRecording);
    document.getElementById('stop-recording').addEventListener('click', stopRecording);
    
    // 语音克隆生成
    document.getElementById('clone-generation-form').addEventListener('submit', generateClonedVoice);
    document.getElementById('clone-random-seed').addEventListener('click', () => getRandomSeed('clone-seed'));
    
    // 高级功能
    document.getElementById('preset-voice-form').addEventListener('submit', generatePresetVoice);
    document.getElementById('natural-control-form').addEventListener('submit', generateNaturalControl);
    
    // 其他
    document.getElementById('clear-log').addEventListener('click', clearLog);
}
/**
 * 更新步骤指示器
 */
function updateStepIndicators() {
    for (let i = 1; i <= 4; i++) {
        const indicator = document.getElementById(`step-${i}-indicator`);
        indicator.classList.remove('active', 'completed');
        
        if (i < currentStep) {
            indicator.classList.add('completed');
        } else if (i === currentStep) {
            indicator.classList.add('active');
        }
    }
    
    // 更新连接线
    document.querySelectorAll('.step-line').forEach((line, index) => {
        if (index + 1 < currentStep) {
            line.classList.add('completed');
        } else {
            line.classList.remove('completed');
        }
    });
}
/**
 * 跳转到指定步骤
 */
function goToStep(step) {
    currentStep = step;
    updateStepIndicators();
    
    // 启用/禁用相应按钮
    if (step >= 3) {
        document.getElementById('generate-clone-btn').disabled = false;
    }
}
/**
 * 重置工作流程
 */
function resetWorkflow() {
    currentStep = 1;
    updateStepIndicators();
    
    // 清空数据
    uploadedAudioPath = null;
    recognizedText = "";
    sampleAudioUrl = null;
    recordedAudioBlob = null;
    
    // 重置界面
    document.getElementById('sample-player').style.display = 'none';
    document.getElementById('recognition-result').style.display = 'none';
    document.getElementById('recognition-waiting').style.display = 'block';
    document.getElementById('comparison-result').style.display = 'none';
    document.getElementById('comparison-waiting').style.display = 'block';
    
    // 重置按钮状态
    document.getElementById('upload-sample-btn').disabled = true;
    document.getElementById('generate-clone-btn').disabled = true;
    
    // 清空文件选择
    document.getElementById('voice-sample-upload').value = '';
    
    addLog('工作流程已重置,可以重新开始', 'info');
}
/**
 * 显示加载状态
 */
function showLoading(message = '正在处理中...', detail = '请稍候...') {
    document.getElementById('loading-message').textContent = message;
    document.getElementById('loading-detail').textContent = detail;
    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 = `[${timestamp}] ${message}`;
    
    logContainer.appendChild(logEntry);
    logContainer.scrollTop = logContainer.scrollHeight;
}
/**
 * 清空日志
 */
function clearLog() {
    const logContainer = document.getElementById('test-log');
    logContainer.innerHTML = '
操作记录将显示在这里...
';
}
/**
 * 显示错误信息
 */
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 = `
        
    `;
    
    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 = `
        
    `;
    
    document.body.appendChild(toast);
    const bsToast = new bootstrap.Toast(toast);
    bsToast.show();
    
    setTimeout(() => {
        if (toast.parentNode) {
            toast.parentNode.removeChild(toast);
        }
    }, 3000);
}
/**
 * 创建音频播放URL
 */
function createAudioUrl(audioPath) {
    const filename = audioPath.split('/').pop();
    return `/voice-test/download-audio/${filename}`;
}
/**
 * 测试连接
 */
async function testConnection() {
    const btn = document.getElementById('test-connection-btn');
    const statusDiv = document.getElementById('connection-status');
    
    btn.disabled = true;
    btn.innerHTML = '测试中...';
    statusDiv.innerHTML = '正在测试连接...';
    
    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 = `
                
                    连接成功
                    支持语音克隆、识别、自然控制
                
            `;
            addLog(`连接成功!核心功能可用:语音克隆、识别、自然控制`, 'success');
            
            // 更新音色列表
            if (result.available_voices) {
                updateVoiceOptions(result.available_voices);
            }
        } else {
            statusDiv.innerHTML = `
                
                    连接失败: ${result.message}
                
            `;
            addLog(`连接失败: ${result.message}`, 'error');
        }
        
    } catch (error) {
        statusDiv.innerHTML = `
            
                请求失败: ${error.message}
            
        `;
        addLog(`请求失败: ${error.message}`, 'error');
    } finally {
        btn.disabled = false;
        btn.innerHTML = '测试连接';
    }
}
/**
 * 加载可用音色
 */
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('获取随机种子失败');
    }
}
/**
 * 处理文件选择
 */
function handleFileSelect(e) {
    const file = e.target.files[0];
    if (file) {
        // 重置录音状态
        recordedAudioBlob = null;
        sampleAudioUrl = null;
        
        addLog(`选择了音频文件: ${file.name} (${(file.size/1024/1024).toFixed(2)} MB)`);
        document.getElementById('upload-sample-btn').disabled = false;
        document.getElementById('upload-sample-btn').innerHTML = '上传并识别语音';
    }
}
/**
 * 开始录音
 */
async function startRecording() {
    try {
        // 重置文件选择
        document.getElementById('voice-sample-upload').value = '';
        
        const stream = await navigator.mediaDevices.getUserMedia({ 
            audio: {
                sampleRate: 16000,  // 设置采样率为16kHz
                channelCount: 1,    // 单声道
                echoCancellation: true,
                noiseSuppression: true
            }
        });
        
        // 创建MediaRecorder,明确指定格式
        const options = {
            mimeType: 'audio/webm;codecs=opus'  // 使用webm格式
        };
        
        // 检查浏览器支持的格式
        if (!MediaRecorder.isTypeSupported(options.mimeType)) {
            if (MediaRecorder.isTypeSupported('audio/webm')) {
                options.mimeType = 'audio/webm';
            } else if (MediaRecorder.isTypeSupported('audio/wav')) {
                options.mimeType = 'audio/wav';
            } else {
                // 使用默认格式
                delete options.mimeType;
            }
        }
        
        mediaRecorder = new MediaRecorder(stream, options);
        audioChunks = [];
        
        mediaRecorder.ondataavailable = function(event) {
            if (event.data.size > 0) {
                audioChunks.push(event.data);
            }
        };
        
        mediaRecorder.onstop = function() {
            // 创建音频Blob
            recordedAudioBlob = new Blob(audioChunks, { 
                type: mediaRecorder.mimeType || 'audio/webm' 
            });
            
            const audioUrl = URL.createObjectURL(recordedAudioBlob);
            
            // 显示录音预览
            const sampleAudio = document.getElementById('sample-audio');
            const sampleSource = document.getElementById('sample-audio-source');
            sampleSource.src = audioUrl;
            sampleAudio.load();
            document.getElementById('sample-player').style.display = 'block';
            
            // 启用上传按钮
            document.getElementById('upload-sample-btn').disabled = false;
            document.getElementById('upload-sample-btn').innerHTML = '上传并识别语音';
            
            // 保存录音数据
            sampleAudioUrl = audioUrl;
            
            addLog(`录音完成,格式: ${mediaRecorder.mimeType || 'default'}, 大小: ${(recordedAudioBlob.size/1024).toFixed(1)} KB`, 'success');
        };
        
        mediaRecorder.start(100); // 每100ms收集一次数据
        
        // 更新UI
        document.getElementById('start-recording').disabled = true;
        document.getElementById('stop-recording').disabled = false;
        document.getElementById('recording-status').textContent = '正在录音...';
        document.getElementById('recording-status').className = 'text-danger';
        
        addLog('开始录音...', 'info');
        
    } catch (error) {
        addLog(`录音失败: ${error.message}`, 'error');
        showError('录音失败,请检查麦克风权限');
    }
}
/**
 * 停止录音
 */
function stopRecording() {
    if (mediaRecorder && mediaRecorder.state !== 'inactive') {
        mediaRecorder.stop();
        mediaRecorder.stream.getTracks().forEach(track => track.stop());
    }
    
    // 更新UI
    document.getElementById('start-recording').disabled = false;
    document.getElementById('stop-recording').disabled = true;
    document.getElementById('recording-status').textContent = '录音已完成';
    document.getElementById('recording-status').className = 'text-success';
    
    addLog('录音停止', 'info');
}
/**
 * 上传语音样本并进行识别
 */
async function uploadVoiceSample(e) {
    e.preventDefault();
    
    showLoading('正在上传和识别语音...', '包括格式转换和语音识别,请稍候');
    addLog('开始上传语音样本进行识别...');
    
    try {
        const fileInput = document.getElementById('voice-sample-upload');
        const file = fileInput.files[0];
        
        let formData = new FormData();
        
        if (file) {
            // 上传文件
            formData.append('audio', file);
            addLog(`上传文件: ${file.name}`);
        } else if (recordedAudioBlob) {
            // 上传录音 - 使用正确的文件名和类型
            const timestamp = new Date().toISOString().replace(/[:.]/g, '-');
            const filename = `recording_${timestamp}.webm`;
            formData.append('audio', recordedAudioBlob, filename);
            addLog(`上传录音: ${filename}, 大小: ${(recordedAudioBlob.size/1024).toFixed(1)} KB`);
        } else {
            showError('请选择音频文件或先录音');
            hideLoading();
            return;
        }
        
        // 上传并识别
        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;
            recognizedText = result.recognized_text || '';
            
            // 显示识别结果
            document.getElementById('recognized-text').value = recognizedText;
            document.getElementById('recognition-result').style.display = 'block';
            document.getElementById('recognition-waiting').style.display = 'none';
            
            // 更新步骤
            goToStep(2);
            setTimeout(() => goToStep(3), 1000);
            
            addLog(`语音识别成功: "${recognizedText}"`, 'success');
            addLog(`音频处理信息: ${result.file_info?.format || '已转换格式'}`, 'info');
            showSuccess('语音样本上传成功!AI已识别出内容');
            
            // 保存原始音频用于对比
            const originalAudio = document.getElementById('original-audio');
            const originalSource = document.getElementById('original-audio-source');
            originalSource.src = createAudioUrl(uploadedAudioPath);
            originalAudio.load();
            
        } else {
            addLog(`语音识别失败: ${result.message}`, 'error');
            showError(result.message);
        }
        
    } catch (error) {
        addLog(`上传出错: ${error.message}`, 'error');
        showError('上传失败,请检查网络连接');
    } finally {
        hideLoading();
    }
}
/**
 * 生成克隆语音
 */
async function generateClonedVoice(e) {
    e.preventDefault();
    
    if (!uploadedAudioPath) {
        showError('请先上传语音样本');
        return;
    }
    
    const text = document.getElementById('clone-text').value.trim();
    const seed = parseInt(document.getElementById('clone-seed').value);
    const referenceText = document.getElementById('recognized-text').value.trim();
    
    if (!text) {
        showError('请输入要合成的文本');
        return;
    }
    
    showLoading('正在克隆你的声音...', '这是最复杂的步骤,请耐心等待');
    addLog(`开始语音克隆 - 目标文本: "${text.substring(0, 20)}..."`);
    addLog(`使用音频文件: ${uploadedAudioPath}`);
    
    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 clonedAudio = document.getElementById('cloned-audio');
            const clonedSource = document.getElementById('cloned-audio-source');
            clonedSource.src = createAudioUrl(result.audio_url);
            clonedAudio.load();
            
            // 显示对比界面
            document.getElementById('comparison-result').style.display = 'block';
            document.getElementById('comparison-waiting').style.display = 'none';
            
            // 更新到最后步骤
            goToStep(4);
            
            addLog(`🎉 语音克隆成功!请对比原声和克隆效果`, 'success');
            showSuccess('语音克隆完成!请播放音频对比效果');
            
        } else {
            addLog(`语音克隆失败: ${result.message}`, 'error');
            showError(result.message || '语音克隆失败,请重试');
        }
        
    } catch (error) {
        addLog(`克隆出错: ${error.message}`, 'error');
        showError('克隆失败,请检查网络连接');
    } finally {
        hideLoading();
    }
}
/**
 * 预训练音色语音生成(高级功能)
 */
async function generatePresetVoice(e) {
    e.preventDefault();
    
    const text = document.getElementById('preset-text').value.trim();
    const voice = document.getElementById('preset-voice').value;
    
    if (!text) {
        showError('请输入要合成的文本');
        return;
    }
    
    showLoading('正在生成预训练音色语音...');
    
    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: 42,
                speed: 1.0
            })
        });
        
        const result = await response.json();
        
        if (result.success) {
            const audioSource = document.getElementById('preset-audio-source');
            const resultDiv = document.getElementById('preset-result');
            
            audioSource.src = createAudioUrl(result.audio_url);
            audioSource.parentElement.load();
            resultDiv.style.display = 'block';
            
            addLog(`预训练音色生成成功 - ${voice}`, 'success');
        } 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();
    
    if (!text) {
        showError('请输入要合成的文本');
        return;
    }
    
    if (!instruction) {
        showError('请输入语音指令');
        return;
    }
    
    showLoading('正在生成自然语言控制语音...');
    
    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: 42
            })
        });
        
        const result = await response.json();
        
        if (result.success) {
            const audioSource = document.getElementById('natural-audio-source');
            const resultDiv = document.getElementById('natural-result');
            
            audioSource.src = createAudioUrl(result.audio_url);
            audioSource.parentElement.load();
            resultDiv.style.display = 'block';
            
            addLog(`自然语言控制生成成功`, 'success');
        } else {
            addLog(`自然语言控制生成失败: ${result.message}`, 'error');
            showError(result.message);
        }
        
    } catch (error) {
        addLog(`生成出错: ${error.message}`, 'error');
        showError('生成失败,请检查网络连接');
    } finally {
        hideLoading();
    }
}