From 864cb2a76ab9233fde3985a7d854b2b5b7d5dfde Mon Sep 17 00:00:00 2001 From: HuangHai <10402852@qq.com> Date: Thu, 28 Aug 2025 15:44:34 +0800 Subject: [PATCH] 'commit' --- dsLightRag/static/YunXiao/xueban.js | 492 ++++++++++++++++++++-------- 1 file changed, 356 insertions(+), 136 deletions(-) diff --git a/dsLightRag/static/YunXiao/xueban.js b/dsLightRag/static/YunXiao/xueban.js index a9671232..fc8e7c71 100644 --- a/dsLightRag/static/YunXiao/xueban.js +++ b/dsLightRag/static/YunXiao/xueban.js @@ -1,152 +1,372 @@ -// 学伴录音功能核心逻辑 -// 模型配置 +/** + * 学伴录音功能核心逻辑 + * 模块化组织:录音管理、ASR处理、音频播放、UI控制 + */ -// 录音相关变量 -let mediaRecorder; let audioChunks = []; let isRecording = false; -// 音频播放相关变量 -let audioElement = null; let isPlaying = false; - -// 获取URL参数 -function getUrlParam(name) { - const reg = new RegExp('(^|&)' + name + '=([^&]*)(&|$)'); - const r = window.location.search.substr(1).match(reg); - return r ? unescape(r[2]) : null; -} - -// 开始录音 -function startRecording() { - if (isRecording) return; - - console.log("尝试开始录音"); - navigator.mediaDevices.getUserMedia({ audio: true }) - .then(stream => { - mediaRecorder = new MediaRecorder(stream); - audioChunks = []; - - mediaRecorder.ondataavailable = event => { - if (event.data.size > 0) audioChunks.push(event.data); - }; - - mediaRecorder.onstop = () => { - const audioBlob = new Blob(audioChunks, { type: 'audio/wav' }); - console.log("录音完成,音频数据大小:", audioBlob.size); - uploadAudioToServer(audioBlob); - }; - - mediaRecorder.start(); - isRecording = true; - document.getElementById('recordingIndicator').style.display = 'flex'; - document.getElementById('startRecordBtn').style.display = 'none'; - document.getElementById('stopRecordBtn').style.display = 'flex'; - console.log("开始录音成功"); - - // 设置最长录音时间为60秒 - setTimeout(stopRecording, 60000); - }) - .catch(error => { - console.error("获取麦克风权限失败:", error); - alert("请授权麦克风权限以使用录音功能"); - }); -} - -// 停止录音 -function stopRecording() { - if (!isRecording || !mediaRecorder) return; - - mediaRecorder.stop(); - isRecording = false; - document.getElementById('recordingIndicator').style.display = 'none'; - document.getElementById('startRecordBtn').style.display = 'flex'; - document.getElementById('stopRecordBtn').style.display = 'none'; - console.log("停止录音"); - - if (mediaRecorder.stream) { - mediaRecorder.stream.getTracks().forEach(track => track.stop()); +// ==================== 全局状态管理 ==================== +const AudioState = { + recording: { + mediaRecorder: null, + audioChunks: [], + isRecording: false, + maxDuration: 60000 // 60秒 + }, + playback: { + audioElement: null, + isPlaying: false } -} +}; -// 上传音频到服务器 -function uploadAudioToServer(audioBlob) { - console.log("开始上传音频到服务器"); - document.getElementById('thinkingIndicator').style.display = 'flex'; +// ==================== 工具函数 ==================== +const Utils = { + // 获取URL参数 + getUrlParam(name) { + const reg = new RegExp('(^|&)' + name + '=([^&]*)(&|$)'); + const r = window.location.search.substr(1).match(reg); + return r ? unescape(r[2]) : null; + }, - const formData = new FormData(); - formData.append('file', audioBlob, 'recording.wav'); + // 格式化时间显示 + formatTime(seconds) { + const mins = Math.floor(seconds / 60).toString().padStart(2, '0'); + const secs = Math.floor(seconds % 60).toString().padStart(2, '0'); + return `${mins}:${secs}`; + } +}; - fetch('/api/xueban/upload-audio', { - method: 'POST', - body: formData - }) - .then(response => { - if (!response.ok) throw new Error('服务器响应错误'); - return response.json(); - }) - .then(data => { - console.log("处理结果:", data); - document.getElementById('thinkingIndicator').style.display = 'none'; - - if (data.success) { - showResults(data.data); - } else { - alert('音频处理失败: ' + data.message); +// ==================== UI控制器 ==================== +const UIController = { + // 显示/隐藏元素 + toggleElement(elementId, show) { + const element = document.getElementById(elementId); + if (element) { + element.style.display = show ? 'flex' : 'none'; } - }) - .catch(error => { - console.error("上传音频失败:", error); - document.getElementById('thinkingIndicator').style.display = 'none'; - alert('上传音频失败: ' + error.message); - }); -} + }, + + // 更新按钮状态 + updateRecordingButtons(isRecording) { + this.toggleElement('recordingIndicator', isRecording); + this.toggleElement('startRecordBtn', !isRecording); + this.toggleElement('stopRecordBtn', isRecording); + }, + + // 更新播放按钮图标 + updatePlayButton(isPlaying) { + const btn = document.getElementById('playAudioBtn'); + if (!btn) return; + + const playIcon = ''; + const pauseIcon = ''; + + btn.innerHTML = isPlaying ? pauseIcon : playIcon; + }, + + // 更新进度条 + updateProgress(progress) { + const progressBar = document.getElementById('progressBar'); + if (progressBar) { + progressBar.style.width = `${progress}%`; + } + }, + + // 更新时间显示 + updateTimeDisplay(currentTime, duration) { + const timeDisplay = document.getElementById('audioTime'); + if (timeDisplay) { + timeDisplay.textContent = `${Utils.formatTime(currentTime)} / ${Utils.formatTime(duration)}`; + } + } +}; -// 显示ASR识别结果和反馈 -function showResults(data) { - const resultContainer = document.getElementById('resultContainer'); - resultContainer.style.display = 'flex'; +// ==================== 录音管理模块 ==================== +const RecordingManager = { + // 初始化录音 + async initRecording() { + try { + const stream = await navigator.mediaDevices.getUserMedia({ audio: true }); + AudioState.recording.mediaRecorder = new MediaRecorder(stream); + AudioState.recording.audioChunks = []; + + // 设置录音数据收集回调 + AudioState.recording.mediaRecorder.ondataavailable = (event) => { + if (event.data.size > 0) { + AudioState.recording.audioChunks.push(event.data); + } + }; + + // 设置录音完成回调 + AudioState.recording.mediaRecorder.onstop = () => { + const audioBlob = new Blob(AudioState.recording.audioChunks, { type: 'audio/wav' }); + console.log('录音完成,音频数据大小:', audioBlob.size); + ASRProcessor.processAudio(audioBlob); + }; + + return true; + } catch (error) { + console.error('获取麦克风权限失败:', error); + alert('请授权麦克风权限以使用录音功能'); + return false; + } + }, + + // 开始录音 + async startRecording() { + if (AudioState.recording.isRecording) return; + + console.log('尝试开始录音'); + const initialized = await this.initRecording(); + + if (initialized && AudioState.recording.mediaRecorder) { + AudioState.recording.mediaRecorder.start(); + AudioState.recording.isRecording = true; + UIController.updateRecordingButtons(true); + console.log('开始录音成功'); + + // 设置最长录音时间 + setTimeout(() => this.stopRecording(), AudioState.recording.maxDuration); + } + }, + + // 停止录音 + stopRecording() { + if (!AudioState.recording.isRecording || !AudioState.recording.mediaRecorder) return; + + AudioState.recording.mediaRecorder.stop(); + AudioState.recording.isRecording = false; + UIController.updateRecordingButtons(false); + console.log('停止录音'); + + // 停止音频流 + if (AudioState.recording.mediaRecorder.stream) { + AudioState.recording.mediaRecorder.stream.getTracks().forEach(track => track.stop()); + } + } +}; - document.getElementById('asrResultText').textContent = data.asr_text || '未识别到内容'; - document.getElementById('feedbackResultText').textContent = data.feedback_text || '无反馈内容'; +// ==================== ASR处理模块 ==================== +const ASRProcessor = { + // 处理音频数据 + async processAudio(audioBlob) { + console.log('开始上传音频到服务器'); + UIController.toggleElement('thinkingIndicator', true); + + try { + const formData = new FormData(); + formData.append('file', audioBlob, 'recording.wav'); + + const response = await fetch('/api/xueban/upload-audio', { + method: 'POST', + body: formData + }); + + if (!response.ok) throw new Error('服务器响应错误'); + + const data = await response.json(); + console.log('处理结果:', data); + UIController.toggleElement('thinkingIndicator', false); + + if (data.success) { + ResultDisplay.showResults(data.data); + } else { + alert('音频处理失败: ' + data.message); + } + } catch (error) { + console.error('上传音频失败:', error); + UIController.toggleElement('thinkingIndicator', false); + alert('上传音频失败: ' + error.message); + } + } +}; - if (data.audio_url) { - if (audioElement) audioElement.pause(); - audioElement = new Audio(data.audio_url); - audioElement.onloadedmetadata = function() { - updateAudioTimeDisplay(); - try { audioElement.play(); isPlaying = true; updatePlayButton(); } - catch (e) { console.error("自动播放失败:", e); } +// ==================== 结果显示模块 ==================== +const ResultDisplay = { + // 显示ASR识别结果和反馈 + showResults(data) { + const resultContainer = document.getElementById('resultContainer'); + if (resultContainer) { + resultContainer.style.display = 'flex'; + } + + // 显示识别文本 + const asrTextElement = document.getElementById('asrResultText'); + if (asrTextElement) { + asrTextElement.textContent = data.asr_text || '未识别到内容'; + } + + // 显示反馈文本 + const feedbackTextElement = document.getElementById('feedbackResultText'); + if (feedbackTextElement) { + feedbackTextElement.textContent = data.feedback_text || '无反馈内容'; + } + + // 如果有音频URL,初始化音频播放器 + if (data.audio_url) { + AudioPlayer.initPlayer(data.audio_url); + } + } +}; + +// ==================== 音频播放模块 ==================== +const AudioPlayer = { + // 初始化音频播放器 + initPlayer(audioUrl) { + // 停止当前播放的音频 + if (AudioState.playback.audioElement) { + AudioState.playback.audioElement.pause(); + } + + // 创建新的音频元素 + AudioState.playback.audioElement = new Audio(audioUrl); + AudioState.playback.isPlaying = false; + + // 绑定音频事件 + AudioState.playback.audioElement.onloadedmetadata = () => { + this.updateTimeDisplay(); + this.play(); // 自动播放 }; - audioElement.ontimeupdate = function() { updateAudioProgress(); updateAudioTimeDisplay(); }; - audioElement.onended = function() { isPlaying = false; updatePlayButton(); }; - document.getElementById('playAudioBtn').onclick = togglePlayAudio; - document.getElementById('audioProgress').onclick = function(e) { - const rect = this.getBoundingClientRect(); - audioElement.currentTime = (e.clientX - rect.left) / rect.width * audioElement.duration; + + AudioState.playback.audioElement.ontimeupdate = () => { + this.updateProgress(); + this.updateTimeDisplay(); }; + + AudioState.playback.audioElement.onended = () => { + AudioState.playback.isPlaying = false; + UIController.updatePlayButton(false); + }; + + // 绑定播放按钮点击事件 + const playBtn = document.getElementById('playAudioBtn'); + if (playBtn) { + playBtn.onclick = () => this.togglePlay(); + } + + // 绑定进度条点击事件 + const progressContainer = document.getElementById('audioProgress'); + if (progressContainer) { + progressContainer.onclick = (e) => { + const rect = progressContainer.getBoundingClientRect(); + const clickPosition = (e.clientX - rect.left) / rect.width; + AudioState.playback.audioElement.currentTime = clickPosition * AudioState.playback.audioElement.duration; + }; + } + }, + + // 播放/暂停切换 + togglePlay() { + if (!AudioState.playback.audioElement) return; + + if (AudioState.playback.isPlaying) { + this.pause(); + } else { + this.play(); + } + }, + + // 播放 + play() { + if (!AudioState.playback.audioElement) return; + + try { + AudioState.playback.audioElement.play(); + AudioState.playback.isPlaying = true; + UIController.updatePlayButton(true); + } catch (e) { + console.error('播放失败:', e); + } + }, + + // 暂停 + pause() { + if (!AudioState.playback.audioElement) return; + + AudioState.playback.audioElement.pause(); + AudioState.playback.isPlaying = false; + UIController.updatePlayButton(false); + }, + + // 更新进度条 + updateProgress() { + if (!AudioState.playback.audioElement) return; + + const progress = (AudioState.playback.audioElement.currentTime / AudioState.playback.audioElement.duration) * 100; + UIController.updateProgress(progress); + }, + + // 更新时间显示 + updateTimeDisplay() { + if (!AudioState.playback.audioElement) return; + + const currentTime = AudioState.playback.audioElement.currentTime; + const duration = AudioState.playback.audioElement.duration; + UIController.updateTimeDisplay(currentTime, duration); + } +}; + +// ==================== 事件绑定 ==================== +const EventBinder = { + // 绑定所有事件 + bindEvents() { + // 绑定录音按钮事件 + const startBtn = document.getElementById('startRecordBtn'); + const stopBtn = document.getElementById('stopRecordBtn'); + + console.log('开始绑定事件,查找按钮元素...'); + console.log('开始录音按钮:', startBtn); + console.log('停止录音按钮:', stopBtn); + + if (startBtn) { + startBtn.onclick = () => { + console.log('点击开始录音按钮'); + RecordingManager.startRecording(); + }; + console.log('已绑定开始录音按钮事件'); + } else { + console.error('未找到开始录音按钮'); + } + + if (stopBtn) { + stopBtn.onclick = () => { + console.log('点击停止录音按钮'); + RecordingManager.stopRecording(); + }; + console.log('已绑定停止录音按钮事件'); + } else { + console.error('未找到停止录音按钮'); + } + } +}; + +// ==================== 初始化 ==================== +// 页面加载完成后初始化 +function initializeApp() { + console.log('开始初始化学伴录音功能...'); + + // 检查DOM是否已就绪 + if (document.readyState === 'loading') { + document.addEventListener('DOMContentLoaded', () => { + EventBinder.bindEvents(); + console.log('学伴录音功能初始化完成(DOMContentLoaded)'); + }); + } else { + // DOM已经加载完成,直接绑定事件 + EventBinder.bindEvents(); + console.log('学伴录音功能初始化完成(直接执行)'); } } -// 音频播放控制函数 -function togglePlayAudio() { - if (!audioElement) return; - isPlaying ? audioElement.pause() : audioElement.play(); - isPlaying = !isPlaying; - updatePlayButton(); -} +// 立即执行初始化 +initializeApp(); -function updatePlayButton() { - const btn = document.getElementById('playAudioBtn'); - btn.innerHTML = isPlaying ? - '' : - ''; -} +// 同时保留原有的DOMContentLoaded事件作为备用 +document.addEventListener('DOMContentLoaded', () => { + EventBinder.bindEvents(); + console.log('学伴录音功能备用初始化完成'); +}); -function updateAudioProgress() { - if (!audioElement) return; - const progress = (audioElement.currentTime / audioElement.duration) * 100; - document.getElementById('progressBar').style.width = `${progress}%`; -} - -function updateAudioTimeDisplay() { - if (!audioElement) return; - const format = s => `${Math.floor(s/60).toString().padStart(2,'0')}:${Math.floor(s%60).toString().padStart(2,'0')}`; - document.getElementById('audioTime').textContent = `${format(audioElement.currentTime)} / ${format(audioElement.duration)}`; -} +// 页面加载完成后也尝试绑定(确保万无一失) +window.addEventListener('load', () => { + EventBinder.bindEvents(); + console.log('学伴录音功能load事件初始化完成'); +});