diff --git a/dsLightRag/Routes/TeachingModel/api/__pycache__/ThemeController.cpython-310.pyc b/dsLightRag/Routes/TeachingModel/api/__pycache__/ThemeController.cpython-310.pyc index 4ccc318c..81c7b635 100644 Binary files a/dsLightRag/Routes/TeachingModel/api/__pycache__/ThemeController.cpython-310.pyc and b/dsLightRag/Routes/TeachingModel/api/__pycache__/ThemeController.cpython-310.pyc differ diff --git a/dsLightRag/Routes/TeachingModel/tasks/__pycache__/BackgroundTasks.cpython-310.pyc b/dsLightRag/Routes/TeachingModel/tasks/__pycache__/BackgroundTasks.cpython-310.pyc index 6a5846b5..0b2c9b86 100644 Binary files a/dsLightRag/Routes/TeachingModel/tasks/__pycache__/BackgroundTasks.cpython-310.pyc and b/dsLightRag/Routes/TeachingModel/tasks/__pycache__/BackgroundTasks.cpython-310.pyc differ diff --git a/dsLightRag/Routes/XueBanRoute.py b/dsLightRag/Routes/XueBanRoute.py index 4ff1ffc0..bdacd486 100644 --- a/dsLightRag/Routes/XueBanRoute.py +++ b/dsLightRag/Routes/XueBanRoute.py @@ -17,6 +17,8 @@ logger = logging.getLogger(__name__) from Util.XueBanUtil import get_xueban_response_async from Util.ASRClient import ASRClient from Util.ObsUtil import ObsUploader +# 新增导入TTSService +from Util.TTSService import TTSService @router.post("/xueban/upload-audio") @@ -49,11 +51,36 @@ async def upload_audio(file: UploadFile = File(...)): os.remove(temp_file_path) logger.info(f"临时文件已删除: {temp_file_path}") - # 返回识别结果 + # 使用大模型生成反馈 + logger.info(f"使用大模型生成反馈,输入文本: {asr_result['text']}") + response_generator = get_xueban_response_async(asr_result['text'], stream=False) + feedback_text = "" + async for chunk in response_generator: + feedback_text += chunk + logger.info(f"大模型反馈生成完成: {feedback_text}") + + # 使用TTS生成语音 + tts_service = TTSService() + tts_temp_file = os.path.join(tempfile.gettempdir(), f"tts_{timestamp}.mp3") + success = tts_service.synthesize(feedback_text, output_file=tts_temp_file) + if not success: + raise Exception("TTS语音合成失败") + logger.info(f"TTS语音合成成功,文件保存至: {tts_temp_file}") + + # 上传TTS音频文件到OBS + tts_audio_url = upload_file_to_obs(tts_temp_file) + os.remove(tts_temp_file) # 删除临时TTS文件 + logger.info(f"TTS文件已上传至OBS: {tts_audio_url}") + + # 返回结果,包含ASR文本和TTS音频URL return JSONResponse(content={ "success": True, - "message": "音频识别成功", - "data": asr_result + "message": "音频处理和语音反馈生成成功", + "data": { + "asr_text": asr_result['text'], + "feedback_text": feedback_text, + "audio_url": tts_audio_url + } }) except Exception as e: diff --git a/dsLightRag/Routes/__pycache__/XueBanRoute.cpython-310.pyc b/dsLightRag/Routes/__pycache__/XueBanRoute.cpython-310.pyc index 28969d32..d7fe8fcf 100644 Binary files a/dsLightRag/Routes/__pycache__/XueBanRoute.cpython-310.pyc and b/dsLightRag/Routes/__pycache__/XueBanRoute.cpython-310.pyc differ diff --git a/dsLightRag/Util/ObsUtil.py b/dsLightRag/Util/ObsUtil.py index 1396cec5..c3608d3d 100644 --- a/dsLightRag/Util/ObsUtil.py +++ b/dsLightRag/Util/ObsUtil.py @@ -2,8 +2,6 @@ import traceback import logging import base64 # 添加base64模块导入 -import requests # 添加requests模块导入 - from obs import ObsClient from obs import PutObjectHeader diff --git a/dsLightRag/Util/__pycache__/ASRClient.cpython-310.pyc b/dsLightRag/Util/__pycache__/ASRClient.cpython-310.pyc new file mode 100644 index 00000000..9652ad7f Binary files /dev/null and b/dsLightRag/Util/__pycache__/ASRClient.cpython-310.pyc differ diff --git a/dsLightRag/Util/__pycache__/TTSService.cpython-310.pyc b/dsLightRag/Util/__pycache__/TTSService.cpython-310.pyc new file mode 100644 index 00000000..edf9329b Binary files /dev/null and b/dsLightRag/Util/__pycache__/TTSService.cpython-310.pyc differ diff --git a/dsLightRag/Util/__pycache__/XueBanUtil.cpython-310.pyc b/dsLightRag/Util/__pycache__/XueBanUtil.cpython-310.pyc index 90aa8829..f385ec8f 100644 Binary files a/dsLightRag/Util/__pycache__/XueBanUtil.cpython-310.pyc and b/dsLightRag/Util/__pycache__/XueBanUtil.cpython-310.pyc differ diff --git a/dsLightRag/static/XueBan.html b/dsLightRag/static/XueBan.html index 9052cf3c..4b35f14d 100644 --- a/dsLightRag/static/XueBan.html +++ b/dsLightRag/static/XueBan.html @@ -49,6 +49,63 @@ border-radius: 50%; margin-right: 8px; animation: pulse 1.5s infinite; } @keyframes pulse { 0% { opacity: 1; } 50% { opacity: 0.5; } 100% { opacity: 1; } } + + /* 添加思考中动画样式 */ + .thinking-indicator { + position: fixed; bottom: 20px; right: 120px; z-index: 1000; + padding: 10px 15px; background-color: rgba(0, 123, 255, 0.9); + color: white; border-radius: 20px; box-shadow: 0 2px 10px rgba(0, 0, 0, 0.2); + display: none; align-items: center; + } + .thinking-dots { + display: flex; gap: 4px; margin-right: 8px; + } + .thinking-dot { + width: 8px; height: 8px; background-color: white; + border-radius: 50%; + } + .thinking-dot:nth-child(1) { animation: pulse 1.5s infinite 0s; } + .thinking-dot:nth-child(2) { animation: pulse 1.5s infinite 0.3s; } + .thinking-dot:nth-child(3) { animation: pulse 1.5s infinite 0.6s; } + + /* 新增样式 */ + .result-container { + position: fixed; bottom: 80px; left: 20px; z-index: 1000; + padding: 15px; background-color: rgba(255, 255, 255, 0.95); + border-radius: 8px; box-shadow: 0 4px 15px rgba(0, 0, 0, 0.15); + max-width: 400px; width: 90%; + display: none; flex-direction: column; gap: 10px; + } + .result-header { + font-weight: bold; color: #495057; margin-bottom: 5px; + display: flex; align-items: center; gap: 5px; + } + .result-header.asr { color: #007bff; } + .result-header.feedback { color: #28a745; } + .result-text { + color: #333; line-height: 1.5; max-height: 200px; overflow-y: auto; + padding-right: 5px; word-break: break-all; + } + .audio-player-container { + margin-top: 10px; display: flex; align-items: center; gap: 10px; + } + .play-button { + background-color: #28a745; color: white; border: none; + border-radius: 50%; width: 40px; height: 40px; + cursor: pointer; display: flex; align-items: center; justify-content: center; + display: flex; /* 始终显示播放按钮 */ + } + .play-button:hover { background-color: #218838; } + .audio-progress { + flex-grow: 1; height: 6px; background-color: #e9ecef; border-radius: 3px; + overflow: hidden; cursor: pointer; + } + .progress-bar { + height: 100%; background-color: #28a745; width: 0%; + } + .audio-time { + font-size: 12px; color: #6c757d; + } @@ -66,10 +123,56 @@ 正在录音... + +
+
+
+
+
+
+ 学伴正在思考中... +
+
- - + + +
+ + +
+
+
+ + + + + + 你讲的话 +
+
+
+
+ +
+
+
+ +
+
+
+
00:00 / 00:00
+
@@ -83,6 +186,8 @@ // 录音相关变量 let mediaRecorder; let audioChunks = []; let isRecording = false; + // 音频播放相关变量 + let audioElement = null; let isPlaying = false; // 获取URL参数 function getUrlParam(name) { @@ -149,6 +254,9 @@ // 上传音频到服务器 function uploadAudioToServer(audioBlob) { console.log("开始上传音频到服务器"); + // 显示思考中动画 + document.getElementById('thinkingIndicator').style.display = 'flex'; + const formData = new FormData(); formData.append('file', audioBlob, 'recording.wav'); @@ -163,45 +271,137 @@ return response.json(); }) .then(data => { - console.log("ASR识别结果:", data); + console.log("处理结果:", data); + // 隐藏思考中动画 + document.getElementById('thinkingIndicator').style.display = 'none'; + if (data.success) { - showAsrResult(data.data.text); + showResults(data.data); } else { - alert('音频识别失败: ' + data.message); + alert('音频处理失败: ' + data.message); } }) .catch(error => { console.error("上传音频失败:", error); + // 隐藏思考中动画 + document.getElementById('thinkingIndicator').style.display = 'none'; + alert('上传音频失败: ' + error.message); }); } - // 显示ASR识别结果 - function showAsrResult(text) { - // 检查是否已存在结果显示元素 - let resultElement = document.getElementById('asrResult'); - if (!resultElement) { - resultElement = document.createElement('div'); - resultElement.id = 'asrResult'; - resultElement.style.position = 'fixed'; - resultElement.style.bottom = '80px'; - resultElement.style.left = '20px'; - resultElement.style.zIndex = '1000'; - resultElement.style.padding = '10px 15px'; - resultElement.style.backgroundColor = 'rgba(0, 123, 255, 0.9)'; - resultElement.style.color = 'white'; - resultElement.style.borderRadius = '5px'; - resultElement.style.boxShadow = '0 2px 10px rgba(0, 0, 0, 0.2)'; - document.body.appendChild(resultElement); + // 显示ASR识别结果和反馈 + function showResults(data) { + // 更新结果显示容器 + const resultContainer = document.getElementById('resultContainer'); + resultContainer.style.display = 'flex'; + + // 显示ASR结果 + document.getElementById('asrResultText').textContent = data.asr_text || '未识别到内容'; + + // 显示反馈文本 + document.getElementById('feedbackResultText').textContent = data.feedback_text || '无反馈内容'; + + // 准备音频播放 + if (data.audio_url) { + if (audioElement) { + audioElement.pause(); + audioElement = null; + } + + audioElement = new Audio(data.audio_url); + audioElement.onloadedmetadata = function() { + updateAudioTimeDisplay(); + // 音频加载完成后自动播放 + try { + audioElement.play(); + isPlaying = true; + updatePlayButton(); + } catch (e) { + console.error("自动播放失败:", e); + } + // 无论自动播放是否成功,都显示播放按钮 + document.getElementById('playAudioBtn').style.display = 'flex'; + }; + + audioElement.ontimeupdate = function() { + updateAudioProgress(); + updateAudioTimeDisplay(); + }; + + audioElement.onended = function() { + isPlaying = false; + updatePlayButton(); + }; + + // 绑定播放按钮事件 + document.getElementById('playAudioBtn').onclick = togglePlayAudio; + + // 绑定进度条点击事件 + document.getElementById('audioProgress').onclick = function(e) { + if (!audioElement) return; + + const progressBar = document.getElementById('audioProgress'); + const rect = progressBar.getBoundingClientRect(); + const clickPosition = (e.clientX - rect.left) / rect.width; + audioElement.currentTime = clickPosition * audioElement.duration; + }; } + } - resultElement.textContent = '识别结果: ' + text; - resultElement.style.display = 'block'; + // 切换音频播放/暂停 + function togglePlayAudio() { + if (!audioElement) return; - // 5秒后自动隐藏 - setTimeout(() => { - resultElement.style.display = 'none'; - }, 5000); + if (isPlaying) { + audioElement.pause(); + } else { + audioElement.play(); + } + isPlaying = !isPlaying; + updatePlayButton(); + } + + // 更新播放按钮状态 + function updatePlayButton() { + const playButton = document.getElementById('playAudioBtn'); + if (isPlaying) { + playButton.innerHTML = ` + + + + `; + } else { + playButton.innerHTML = ` + + + + `; + } + } + + // 更新音频进度条 + function updateAudioProgress() { + if (!audioElement || !audioElement.duration) return; + + const progress = (audioElement.currentTime / audioElement.duration) * 100; + document.getElementById('progressBar').style.width = `${progress}%`; + } + + // 更新音频时间显示 + function updateAudioTimeDisplay() { + if (!audioElement || !audioElement.duration) return; + + const currentTime = formatTime(audioElement.currentTime); + const duration = formatTime(audioElement.duration); + document.getElementById('audioTime').textContent = `${currentTime} / ${duration}`; + } + + // 格式化时间为 MM:SS + function formatTime(seconds) { + const mins = Math.floor(seconds / 60); + const secs = Math.floor(seconds % 60); + return `${mins.toString().padStart(2, '0')}:${secs.toString().padStart(2, '0')}`; } // 初始化看板娘 - 简化为Sample.html的工作版本