'commit'
This commit is contained in:
@@ -12,6 +12,9 @@ from urllib.parse import urlencode
|
||||
from wsgiref.handlers import format_date_time
|
||||
import websocket
|
||||
|
||||
from Config.Config import XF_APPID, XF_APISECRET, XF_APIKEY
|
||||
|
||||
|
||||
class XunFeiAudioEvaluator:
|
||||
"""讯飞语音评测类"""
|
||||
|
||||
@@ -251,9 +254,12 @@ class XunFeiAudioEvaluator:
|
||||
# 使用示例
|
||||
if __name__ == '__main__':
|
||||
# 配置参数
|
||||
appid = "5b83f8d6"
|
||||
api_secret = "604fa6cb9c5ab664a0d153fe0ccc6802"
|
||||
api_key = "5beb887923204000bfcb402046bb05a6"
|
||||
# appid = "5b83f8d6"
|
||||
# api_secret = "604fa6cb9c5ab664a0d153fe0ccc6802"
|
||||
# api_key = "5beb887923204000bfcb402046bb05a6"
|
||||
appid = XF_APPID
|
||||
api_secret = XF_APISECRET
|
||||
api_key = XF_APIKEY
|
||||
audio_file = "./1.mp3"
|
||||
|
||||
# 创建评测器实例
|
||||
|
Binary file not shown.
@@ -2,6 +2,7 @@ import logging
|
||||
import os
|
||||
import uuid
|
||||
import time
|
||||
import asyncio # 添加此行
|
||||
from fastapi import APIRouter, HTTPException, BackgroundTasks, Query, UploadFile, File, Form
|
||||
from pydantic import BaseModel
|
||||
from typing import Optional
|
||||
@@ -37,14 +38,15 @@ class AudioEvaluationResponse(BaseModel):
|
||||
# 科大讯飞API配置(需要根据实际情况配置)
|
||||
XUNFEI_CONFIG = {
|
||||
"appid": XF_APPID,
|
||||
"api_key": XF_APISECRET,
|
||||
"api_secret": XF_APIKEY
|
||||
# 修复参数名颠倒问题
|
||||
"api_key": XF_APIKEY,
|
||||
"api_secret": XF_APISECRET
|
||||
}
|
||||
|
||||
@router.post("/evaluate-audio", response_model=AudioEvaluationResponse)
|
||||
async def evaluate_audio(
|
||||
async def evaluate_audio( # 添加async关键字
|
||||
background_tasks: BackgroundTasks,
|
||||
language: str = Form("chinese", description="评测语言: chinese 或 english"),
|
||||
language: str = Form(..., description="语言类型: chinese/english"),
|
||||
text: str = Form(..., description="评测文本内容"),
|
||||
group: str = Form("adult", description="群体类型: adult, youth, pupil"),
|
||||
check_type: str = Form("common", description="检错严格程度: easy, common, hard"),
|
||||
@@ -58,6 +60,13 @@ async def evaluate_audio(
|
||||
if language not in ["chinese", "english"]:
|
||||
raise HTTPException(status_code=400, detail="language参数必须是'chinese'或'english'")
|
||||
|
||||
# 新增参数验证
|
||||
if check_type not in ["easy", "common", "hard"]:
|
||||
raise HTTPException(status_code=400, detail="check_type参数必须是'easy'、'common'或'hard'")
|
||||
|
||||
if grade not in ["junior", "middle", "senior"]:
|
||||
raise HTTPException(status_code=400, detail="grade参数必须是'junior'、'middle'或'senior'")
|
||||
|
||||
# 验证群体参数
|
||||
if group not in ["adult", "youth", "pupil"]:
|
||||
raise HTTPException(status_code=400, detail="group参数必须是'adult', 'youth'或'pupil'")
|
||||
@@ -71,6 +80,7 @@ async def evaluate_audio(
|
||||
# 创建评测器实例
|
||||
evaluator = XunFeiAudioEvaluator(
|
||||
appid=XUNFEI_CONFIG["appid"],
|
||||
# 与AudioEvaluator示例用法保持一致
|
||||
api_key=XUNFEI_CONFIG["api_key"],
|
||||
api_secret=XUNFEI_CONFIG["api_secret"],
|
||||
audio_file=temp_audio_path
|
||||
@@ -96,7 +106,11 @@ async def evaluate_audio(
|
||||
}
|
||||
|
||||
# 运行评测
|
||||
results, eval_time = evaluator.run_evaluation()
|
||||
from concurrent.futures import ThreadPoolExecutor
|
||||
executor = ThreadPoolExecutor(max_workers=4)
|
||||
results, eval_time = await asyncio.get_event_loop().run_in_executor(
|
||||
executor, evaluator.run_evaluation
|
||||
)
|
||||
|
||||
# 清理临时文件
|
||||
os.unlink(temp_audio_path)
|
||||
|
Binary file not shown.
@@ -4,85 +4,7 @@
|
||||
<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>
|
||||
<link rel="stylesheet" href="css/audio_evaluation.css">
|
||||
</head>
|
||||
<body>
|
||||
<div class="container">
|
||||
@@ -114,225 +36,6 @@
|
||||
</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`;
|
||||
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;
|
||||
}
|
||||
|
||||
// 页面卸载时关闭连接
|
||||
window.addEventListener('beforeunload', () => {
|
||||
if (ws) ws.close(1001, '页面即将关闭');
|
||||
});
|
||||
</script>
|
||||
<script src="js/audio_evaluation.js"></script>
|
||||
</body>
|
||||
</html>
|
||||
|
77
dsLightRag/static/XunFei/css/audio_evaluation.css
Normal file
77
dsLightRag/static/XunFei/css/audio_evaluation.css
Normal file
@@ -0,0 +1,77 @@
|
||||
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;
|
||||
}
|
190
dsLightRag/static/XunFei/js/audio_evaluation.js
Normal file
190
dsLightRag/static/XunFei/js/audio_evaluation.js
Normal file
@@ -0,0 +1,190 @@
|
||||
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 = '窗前明月光,疑是地上霜。';
|
||||
stopBtn.disabled = true;
|
||||
});
|
||||
|
||||
// 开始录音
|
||||
recordBtn.addEventListener('click', async () => {
|
||||
try {
|
||||
statusDiv.textContent = '正在获取麦克风权限...';
|
||||
statusDiv.className = 'status';
|
||||
|
||||
// 获取麦克风权限并处理可能的错误
|
||||
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 = '录音完成,正在自动提交评测...';
|
||||
submitEvaluation();
|
||||
// 停止所有音轨以释放麦克风
|
||||
stream.getTracks().forEach(track => track.stop());
|
||||
};
|
||||
|
||||
// 设置60秒自动停止录音
|
||||
const maxRecordingTime = 60000; // 60秒
|
||||
const timeoutId = setTimeout(() => {
|
||||
if (mediaRecorder && mediaRecorder.state === 'recording') {
|
||||
alert('已达到最大录音时长(60秒)');
|
||||
mediaRecorder.stop();
|
||||
recordBtn.disabled = false;
|
||||
stopBtn.disabled = true;
|
||||
}
|
||||
}, maxRecordingTime);
|
||||
|
||||
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();
|
||||
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状态码 ${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;
|
||||
}
|
Reference in New Issue
Block a user