Files
dsProject/dsLightRag/static/XunFei/audio_evaluation.html
2025-09-05 20:38:40 +08:00

345 lines
13 KiB
HTML
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.

<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>语音评测系统</title>
<style>
body {
font-family: Arial, sans-serif;
max-width: 800px;
margin: 0 auto;
padding: 20px;
background-color: #f5f5f5;
}
.container {
background: white;
padding: 30px;
border-radius: 10px;
box-shadow: 0 2px 10px rgba(0,0,0,0.1);
}
h1 {
text-align: center;
color: #333;
margin-bottom: 30px;
}
.form-group {
margin-bottom: 20px;
}
label {
display: block;
margin-bottom: 5px;
font-weight: bold;
color: #555;
}
select, input[type="text"] {
width: 100%;
padding: 10px;
border: 1px solid #ddd;
border-radius: 5px;
font-size: 16px;
}
.btn {
background-color: #007bff;
color: white;
padding: 12px 24px;
border: none;
border-radius: 5px;
cursor: pointer;
font-size: 16px;
margin-right: 10px;
}
.btn:disabled {
background-color: #ccc;
cursor: not-allowed;
}
.btn-record {
background-color: #dc3545;
}
.btn-stop {
background-color: #28a745;
}
.status {
margin-top: 20px;
padding: 10px;
border-radius: 5px;
background-color: #e9ecef;
}
.result {
margin-top: 20px;
padding: 15px;
border: 1px solid #dee2e6;
border-radius: 5px;
background-color: #f8f9fa;
}
.error {
color: #dc3545;
background-color: #f8d7da;
border-color: #f5c6cb;
}
.success {
color: #155724;
background-color: #d4edda;
border-color: #c3e6cb;
}
</style>
</head>
<body>
<div class="container">
<h1>语音评测系统</h1>
<div class="form-group">
<label for="language">选择评测语言:</label>
<select id="language">
<option value="chinese">中文</option>
<option value="english">英文</option>
</select>
</div>
<div class="form-group">
<label for="text">评测文本内容:</label>
<input type="text" id="text" placeholder="请输入要朗读的文本内容">
</div>
<div class="form-group">
<button id="recordBtn" class="btn btn-record">开始录音</button>
<button id="stopBtn" class="btn btn-stop" disabled>停止录音</button>
</div>
<div id="status" class="status">准备就绪</div>
<div id="result" class="result" style="display: none;">
<h3>评测结果</h3>
<div id="resultContent"></div>
</div>
</div>
<script>
let mediaRecorder;
let audioChunks = [];
let audioBlob;
// 获取DOM元素
const languageSelect = document.getElementById('language');
const textInput = document.getElementById('text');
const recordBtn = document.getElementById('recordBtn');
const stopBtn = document.getElementById('stopBtn');
// 删除提交按钮引用
const statusDiv = document.getElementById('status');
const resultDiv = document.getElementById('result');
const resultContent = document.getElementById('resultContent');
// 语言切换时自动填充示例文本
languageSelect.addEventListener('change', () => {
if (languageSelect.value === 'chinese') {
textInput.value = '窗前明月光,疑是地上霜。';
} else {
textInput.value = 'Nice to meet you.';
}
});
// 页面加载时自动填充中文示例
window.addEventListener('load', () => {
textInput.value = '窗前明月光,疑是地上霜。';
});
// 开始录音
recordBtn.addEventListener('click', async () => {
try {
statusDiv.textContent = '正在获取麦克风权限...';
statusDiv.className = 'status';
// ==== 插入WebSocket认证代码 ====
const wsProtocol = window.location.protocol === 'https:' ? 'wss:' : 'ws:';
const wsUrl = `${wsProtocol}//${window.location.host}/ws/audio-evaluation?token=${getAuthToken()}`;
const ws = new WebSocket(wsUrl);
// WebSocket事件处理
ws.onopen = () => {
console.log('WebSocket连接已建立');
statusDiv.textContent = 'WebSocket连接已建立准备录音...';
};
ws.onerror = (error) => {
console.error('WebSocket错误:', error);
statusDiv.textContent = 'WebSocket连接失败请刷新页面重试';
statusDiv.className = 'status error';
};
// ==== 插入结束 ====
// 使用更明确的提示并添加详细的错误处理
const stream = await navigator.mediaDevices.getUserMedia({ audio: true })
.catch(err => {
// 根据不同的错误类型提供更具体的提示
if (err.name === 'NotAllowedError') {
throw new Error('用户拒绝了麦克风访问权限。请在浏览器设置中允许麦克风访问,或刷新页面重试。');
} else if (err.name === 'NotFoundError') {
throw new Error('未检测到麦克风设备,请确保您的设备已正确连接麦克风。');
} else if (err.name === 'NotReadableError') {
throw new Error('麦克风设备正在被其他程序占用,请关闭其他可能使用麦克风的程序后重试。');
} else {
throw err;
}
});
mediaRecorder = new MediaRecorder(stream);
audioChunks = [];
mediaRecorder.ondataavailable = (event) => {
audioChunks.push(event.data);
};
mediaRecorder.onstop = () => {
audioBlob = new Blob(audioChunks, { type: 'audio/webm' });
statusDiv.textContent = '录音完成,正在自动提交评测...';
// 添加WebSocket关闭逻辑
if (ws) ws.close(1000, '录音已完成');
submitEvaluation();
};
// 添加录音最大时长限制60秒
setTimeout(() => {
if (mediaRecorder && mediaRecorder.state === 'recording') {
alert('已达到最大录音时长60秒');
mediaRecorder.stop();
mediaRecorder.stream.getTracks().forEach(track => track.stop());
recordBtn.disabled = false;
stopBtn.disabled = true;
}
}, 60000);
mediaRecorder.start();
statusDiv.textContent = '正在录音...最多60秒';
recordBtn.disabled = true;
stopBtn.disabled = false;
} catch (error) {
console.error('录音失败:', error);
// 使用更明显的错误提示方式
alert('录音失败: ' + error.message);
statusDiv.textContent = '录音失败: ' + error.message;
statusDiv.className = 'status error';
// 确保按钮状态正确重置
recordBtn.disabled = false;
stopBtn.disabled = true;
}
});
// 停止录音
stopBtn.addEventListener('click', () => {
if (mediaRecorder && mediaRecorder.state === 'recording') {
mediaRecorder.stop();
mediaRecorder.stream.getTracks().forEach(track => track.stop());
recordBtn.disabled = false;
stopBtn.disabled = true;
}
});
// 将提交评测逻辑提取为独立函数
async function submitEvaluation() {
if (!audioBlob) {
statusDiv.textContent = '请先完成录音';
statusDiv.className = 'status error';
return;
}
if (!textInput.value.trim()) {
statusDiv.textContent = '请输入评测文本内容';
statusDiv.className = 'status error';
return;
}
try {
statusDiv.textContent = '正在提交评测...';
statusDiv.className = 'status';
const formData = new FormData();
formData.append('audio_file', audioBlob, 'recording.webm');
formData.append('language', languageSelect.value);
formData.append('text', textInput.value.trim());
formData.append('group', 'adult');
formData.append('check_type', 'common');
formData.append('grade', 'middle');
const response = await fetch('/api/xunFei/evaluate-audio', {
method: 'POST',
body: formData
});
if (!response.ok) {
throw new Error(`HTTP error! status: ${response.status}`);
}
const result = await response.json();
if (result.status === 'success') {
displayResults(result.results);
statusDiv.textContent = '评测成功完成';
statusDiv.className = 'status success';
} else {
throw new Error(result.error_message || '评测失败');
}
} catch (error) {
console.error('评测失败:', error);
statusDiv.textContent = '评测失败: ' + error.message;
statusDiv.className = 'status error';
}
}
// 显示评测结果
function displayResults(results) {
resultDiv.style.display = 'block';
if (!results) {
resultContent.innerHTML = '<p>暂无评测结果</p>';
return;
}
let html = '<div class="result-summary">';
// 显示总分
if (results.total_score !== undefined) {
html += `<p><strong>总分:</strong> ${results.total_score.toFixed(4)} / 5.0</p>`;
}
// 显示各项分数
if (results.accuracy_score !== undefined) {
html += `<p><strong>准确度:</strong> ${results.accuracy_score.toFixed(4)}</p>`;
}
if (results.fluency_score !== undefined) {
html += `<p><strong>流利度:</strong> ${results.fluency_score.toFixed(4)}</p>`;
}
if (results.completeness_score !== undefined) {
html += `<p><strong>完整度:</strong> ${results.completeness_score.toFixed(4)}</p>`;
}
html += '</div>';
// 显示单词级评分(如果有)
if (results.words && results.words.length > 0) {
html += '<div class="word-scores"><h4>单词评分:</h4><ul>';
results.words.forEach(word => {
html += `<li>${word.content}: ${word.score.toFixed(4)}</li>`;
});
html += '</ul></div>';
}
resultContent.innerHTML = html;
}
// 在<script>标签内添加getAuthToken实现
function getAuthToken() {
// 实际项目中应从Cookie、localStorage或后端API获取
return 'your_auth_token_here'; // 临时占位,需替换为真实认证逻辑
}
// 页面卸载时关闭连接
window.addEventListener('beforeunload', () => {
if (ws) ws.close(1001, '页面即将关闭');
});
</script>
</body>
</html>