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事件初始化完成');
+});