'commit'
This commit is contained in:
@@ -179,287 +179,298 @@
|
||||
有权限问题的话,请点击<a href="https://dsideal.obs.cn-north-1.myhuaweicloud.com/HuangHai/11%E3%80%81%E5%AD%A6%E4%BC%B4Chrome%E5%BD%95%E9%9F%B3%E9%85%8D%E7%BD%AE%E4%BF%AE%E6%94%B9.pdf">这里</a>
|
||||
</div>
|
||||
<script src="https://l2dwidget.js.org/lib/L2Dwidget.min.js"></script>
|
||||
<script>
|
||||
// 模型配置 - 使用与Sample.html相同的CDN链接
|
||||
const models = {
|
||||
shizuku: { jsonPath: "https://unpkg.com/live2d-widget-model-shizuku@1.0.5/assets/shizuku.model.json", name: "小智" },
|
||||
koharu: { jsonPath: "https://unpkg.com/live2d-widget-model-koharu@1.0.5/assets/koharu.model.json", name: "小荷" },
|
||||
wanko: { jsonPath: "https://unpkg.com/live2d-widget-model-wanko@1.0.5/assets/wanko.model.json", name: "汪喵" }
|
||||
};
|
||||
<!-- 移除内联看板娘代码,引用独立JS文件 -->
|
||||
<script src="YunXiao/live2d_widget.js"></script>
|
||||
|
||||
// 录音相关变量
|
||||
let mediaRecorder; let audioChunks = []; let isRecording = false;
|
||||
// 音频播放相关变量
|
||||
let audioElement = null; let isPlaying = false;
|
||||
<!-- 保留模型选择器HTML -->
|
||||
<div class="model-selector">
|
||||
<label for="model-select">选择学伴:</label>
|
||||
<select id="model-select">
|
||||
<option value="shizuku">小智</option>
|
||||
<option value="koharu">小荷</option>
|
||||
<option value="wanko">汪喵</option>
|
||||
</select>
|
||||
</div>
|
||||
|
||||
// 获取URL参数
|
||||
function getUrlParam(name) {
|
||||
const reg = new RegExp('(^|&)' + name + '=([^&]*)(&|$)');
|
||||
const r = window.location.search.substr(1).match(reg);
|
||||
return r ? unescape(r[2]) : null;
|
||||
}
|
||||
// 模型配置 - 使用与Sample.html相同的CDN链接
|
||||
const models = {
|
||||
shizuku: { jsonPath: "https://unpkg.com/live2d-widget-model-shizuku@1.0.5/assets/shizuku.model.json", name: "小智" },
|
||||
koharu: { jsonPath: "https://unpkg.com/live2d-widget-model-koharu@1.0.5/assets/koharu.model.json", name: "小荷" },
|
||||
wanko: { jsonPath: "https://unpkg.com/live2d-widget-model-wanko@1.0.5/assets/wanko.model.json", name: "汪喵" }
|
||||
};
|
||||
|
||||
// 开始录音
|
||||
function startRecording() {
|
||||
if (isRecording) return;
|
||||
// 录音相关变量
|
||||
let mediaRecorder; let audioChunks = []; let isRecording = false;
|
||||
// 音频播放相关变量
|
||||
let audioElement = null; let isPlaying = false;
|
||||
|
||||
console.log("尝试开始录音");
|
||||
navigator.mediaDevices.getUserMedia({ audio: true })
|
||||
.then(stream => {
|
||||
mediaRecorder = new MediaRecorder(stream);
|
||||
audioChunks = [];
|
||||
// 获取URL参数
|
||||
function getUrlParam(name) {
|
||||
const reg = new RegExp('(^|&)' + name + '=([^&]*)(&|$)');
|
||||
const r = window.location.search.substr(1).match(reg);
|
||||
return r ? unescape(r[2]) : null;
|
||||
}
|
||||
|
||||
mediaRecorder.ondataavailable = event => {
|
||||
if (event.data.size > 0) audioChunks.push(event.data);
|
||||
};
|
||||
// 开始录音
|
||||
function startRecording() {
|
||||
if (isRecording) return;
|
||||
|
||||
mediaRecorder.onstop = () => {
|
||||
const audioBlob = new Blob(audioChunks, { type: 'audio/wav' });
|
||||
console.log("录音完成,音频数据大小:", audioBlob.size);
|
||||
const audioUrl = URL.createObjectURL(audioBlob);
|
||||
console.log("录音URL:", audioUrl);
|
||||
// 这里可以调用ASR服务
|
||||
uploadAudioToServer(audioBlob);
|
||||
};
|
||||
console.log("尝试开始录音");
|
||||
navigator.mediaDevices.getUserMedia({ audio: true })
|
||||
.then(stream => {
|
||||
mediaRecorder = new MediaRecorder(stream);
|
||||
audioChunks = [];
|
||||
|
||||
mediaRecorder.start();
|
||||
isRecording = true;
|
||||
document.getElementById('recordingIndicator').style.display = 'flex';
|
||||
document.getElementById('startRecordBtn').style.display = 'none';
|
||||
document.getElementById('stopRecordBtn').style.display = 'flex';
|
||||
console.log("开始录音成功");
|
||||
mediaRecorder.ondataavailable = event => {
|
||||
if (event.data.size > 0) audioChunks.push(event.data);
|
||||
};
|
||||
|
||||
// 设置最长录音时间为60秒
|
||||
setTimeout(stopRecording, 60000);
|
||||
})
|
||||
.catch(error => {
|
||||
console.error("获取麦克风权限失败:", error);
|
||||
alert("请授权麦克风权限以使用录音功能");
|
||||
});
|
||||
}
|
||||
mediaRecorder.onstop = () => {
|
||||
const audioBlob = new Blob(audioChunks, { type: 'audio/wav' });
|
||||
console.log("录音完成,音频数据大小:", audioBlob.size);
|
||||
const audioUrl = URL.createObjectURL(audioBlob);
|
||||
console.log("录音URL:", audioUrl);
|
||||
// 这里可以调用ASR服务
|
||||
uploadAudioToServer(audioBlob);
|
||||
};
|
||||
|
||||
// 停止录音
|
||||
function stopRecording() {
|
||||
if (!isRecording || !mediaRecorder) return;
|
||||
mediaRecorder.start();
|
||||
isRecording = true;
|
||||
document.getElementById('recordingIndicator').style.display = 'flex';
|
||||
document.getElementById('startRecordBtn').style.display = 'none';
|
||||
document.getElementById('stopRecordBtn').style.display = 'flex';
|
||||
console.log("开始录音成功");
|
||||
|
||||
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());
|
||||
}
|
||||
}
|
||||
|
||||
// 上传音频到服务器
|
||||
function uploadAudioToServer(audioBlob) {
|
||||
console.log("开始上传音频到服务器");
|
||||
// 显示思考中动画
|
||||
document.getElementById('thinkingIndicator').style.display = 'flex';
|
||||
|
||||
const formData = new FormData();
|
||||
formData.append('file', audioBlob, 'recording.wav');
|
||||
|
||||
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);
|
||||
}
|
||||
// 设置最长录音时间为60秒
|
||||
setTimeout(stopRecording, 60000);
|
||||
})
|
||||
.catch(error => {
|
||||
console.error("上传音频失败:", error);
|
||||
// 隐藏思考中动画
|
||||
document.getElementById('thinkingIndicator').style.display = 'none';
|
||||
|
||||
alert('上传音频失败: ' + error.message);
|
||||
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());
|
||||
}
|
||||
}
|
||||
|
||||
// 显示ASR识别结果和反馈
|
||||
function showResults(data) {
|
||||
// 更新结果显示容器
|
||||
const resultContainer = document.getElementById('resultContainer');
|
||||
resultContainer.style.display = 'flex';
|
||||
// 上传音频到服务器
|
||||
function uploadAudioToServer(audioBlob) {
|
||||
console.log("开始上传音频到服务器");
|
||||
// 显示思考中动画
|
||||
document.getElementById('thinkingIndicator').style.display = 'flex';
|
||||
|
||||
// 显示ASR结果
|
||||
document.getElementById('asrResultText').textContent = data.asr_text || '未识别到内容';
|
||||
const formData = new FormData();
|
||||
formData.append('file', audioBlob, 'recording.wav');
|
||||
|
||||
// 显示反馈文本
|
||||
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;
|
||||
};
|
||||
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';
|
||||
|
||||
// 切换音频播放/暂停
|
||||
function togglePlayAudio() {
|
||||
if (!audioElement) return;
|
||||
if (data.success) {
|
||||
showResults(data.data);
|
||||
} else {
|
||||
alert('音频处理失败: ' + data.message);
|
||||
}
|
||||
})
|
||||
.catch(error => {
|
||||
console.error("上传音频失败:", error);
|
||||
// 隐藏思考中动画
|
||||
document.getElementById('thinkingIndicator').style.display = 'none';
|
||||
|
||||
if (isPlaying) {
|
||||
alert('上传音频失败: ' + error.message);
|
||||
});
|
||||
}
|
||||
|
||||
// 显示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();
|
||||
} else {
|
||||
audioElement.play();
|
||||
audioElement = null;
|
||||
}
|
||||
isPlaying = !isPlaying;
|
||||
updatePlayButton();
|
||||
|
||||
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;
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
// 更新播放按钮状态
|
||||
function updatePlayButton() {
|
||||
const playButton = document.getElementById('playAudioBtn');
|
||||
if (isPlaying) {
|
||||
playButton.innerHTML = `
|
||||
<svg width="20" height="20" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M6 19H10V5H6V19ZM14 19H18V5H14V19Z" fill="white"/>
|
||||
</svg>
|
||||
`;
|
||||
} else {
|
||||
playButton.innerHTML = `
|
||||
<svg width="20" height="20" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M8 5V19L19 12L8 5Z" fill="white"/>
|
||||
</svg>
|
||||
`;
|
||||
}
|
||||
// 切换音频播放/暂停
|
||||
function togglePlayAudio() {
|
||||
if (!audioElement) return;
|
||||
|
||||
if (isPlaying) {
|
||||
audioElement.pause();
|
||||
} else {
|
||||
audioElement.play();
|
||||
}
|
||||
isPlaying = !isPlaying;
|
||||
updatePlayButton();
|
||||
}
|
||||
|
||||
// 更新音频进度条
|
||||
function updateAudioProgress() {
|
||||
if (!audioElement || !audioElement.duration) return;
|
||||
|
||||
const progress = (audioElement.currentTime / audioElement.duration) * 100;
|
||||
document.getElementById('progressBar').style.width = `${progress}%`;
|
||||
// 更新播放按钮状态
|
||||
function updatePlayButton() {
|
||||
const playButton = document.getElementById('playAudioBtn');
|
||||
if (isPlaying) {
|
||||
playButton.innerHTML = `
|
||||
<svg width="20" height="20" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M6 19H10V5H6V19ZM14 19H18V5H14V19Z" fill="white"/>
|
||||
</svg>
|
||||
`;
|
||||
} else {
|
||||
playButton.innerHTML = `
|
||||
<svg width="20" height="20" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M8 5V19L19 12L8 5Z" fill="white"/>
|
||||
</svg>
|
||||
`;
|
||||
}
|
||||
}
|
||||
|
||||
// 更新音频时间显示
|
||||
function updateAudioTimeDisplay() {
|
||||
if (!audioElement || !audioElement.duration) return;
|
||||
// 更新音频进度条
|
||||
function updateAudioProgress() {
|
||||
if (!audioElement || !audioElement.duration) return;
|
||||
|
||||
const currentTime = formatTime(audioElement.currentTime);
|
||||
const duration = formatTime(audioElement.duration);
|
||||
document.getElementById('audioTime').textContent = `${currentTime} / ${duration}`;
|
||||
}
|
||||
const progress = (audioElement.currentTime / audioElement.duration) * 100;
|
||||
document.getElementById('progressBar').style.width = `${progress}%`;
|
||||
}
|
||||
|
||||
// 格式化时间为 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')}`;
|
||||
}
|
||||
// 更新音频时间显示
|
||||
function updateAudioTimeDisplay() {
|
||||
if (!audioElement || !audioElement.duration) return;
|
||||
|
||||
// 初始化看板娘 - 简化为Sample.html的工作版本
|
||||
function initL2Dwidget() {
|
||||
const modelId = getUrlParam('id') || 'koharu';
|
||||
const model = models[modelId] || models.koharu;
|
||||
const currentTime = formatTime(audioElement.currentTime);
|
||||
const duration = formatTime(audioElement.duration);
|
||||
document.getElementById('audioTime').textContent = `${currentTime} / ${duration}`;
|
||||
}
|
||||
|
||||
document.getElementById('model-select').value = modelId;
|
||||
console.log('加载模型:', model.name, model.jsonPath);
|
||||
// 格式化时间为 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相同的配置
|
||||
L2Dwidget.init({
|
||||
"model": { "jsonPath": model.jsonPath, "scale": 1 },
|
||||
"display": {
|
||||
"position": "right",
|
||||
"width": 150,
|
||||
"height": 300,
|
||||
"hOffset": 0, // 重置水平偏移
|
||||
"vOffset": -20 // 重置垂直偏移
|
||||
},
|
||||
"mobile": { "show": true, "scale": 0.5 },
|
||||
"react": { "opacityDefault": 0.8, "opacityOnHover": 1 },
|
||||
"dialog": { "enable": true, "script": {
|
||||
'tap body': `你好啊,我是${model.name}。`,
|
||||
'tap face': '有什么问题或者烦心事都可以和我聊聊~'
|
||||
}}
|
||||
// 初始化看板娘 - 简化为Sample.html的工作版本
|
||||
function initL2Dwidget() {
|
||||
const modelId = getUrlParam('id') || 'koharu';
|
||||
const model = models[modelId] || models.koharu;
|
||||
|
||||
document.getElementById('model-select').value = modelId;
|
||||
console.log('加载模型:', model.name, model.jsonPath);
|
||||
|
||||
// 初始化模型 - 与Sample.html相同的配置
|
||||
L2Dwidget.init({
|
||||
"model": { "jsonPath": model.jsonPath, "scale": 1 },
|
||||
"display": {
|
||||
"position": "right",
|
||||
"width": 150,
|
||||
"height": 300,
|
||||
"hOffset": 0, // 重置水平偏移
|
||||
"vOffset": -20 // 重置垂直偏移
|
||||
},
|
||||
"mobile": { "show": true, "scale": 0.5 },
|
||||
"react": { "opacityDefault": 0.8, "opacityOnHover": 1 },
|
||||
"dialog": { "enable": true, "script": {
|
||||
'tap body': `你好啊,我是${model.name}。`,
|
||||
'tap face': '有什么问题或者烦心事都可以和我聊聊~'
|
||||
}}
|
||||
});
|
||||
}
|
||||
|
||||
// 页面加载完成后初始化
|
||||
window.onload = function() {
|
||||
// 直接初始化看板娘,不添加额外延迟
|
||||
initL2Dwidget();
|
||||
|
||||
// 监听下拉框变化(使用独立JS暴露的接口)
|
||||
document.getElementById('model-select').addEventListener('change', function() {
|
||||
window.switchL2DModel(this.value);
|
||||
});
|
||||
|
||||
// 绑定录音按钮事件
|
||||
document.getElementById('startRecordBtn').addEventListener('click', startRecording);
|
||||
document.getElementById('stopRecordBtn').addEventListener('click', stopRecording);
|
||||
|
||||
// 页面加载时请求麦克风权限
|
||||
navigator.mediaDevices.getUserMedia({ audio: true })
|
||||
.then(stream => {
|
||||
console.log("麦克风权限已授予");
|
||||
// 立即停止流,只获取权限
|
||||
stream.getTracks().forEach(track => track.stop());
|
||||
})
|
||||
.catch(error => {
|
||||
console.error("获取麦克风权限失败:", error);
|
||||
alert("请授权麦克风权限以使用录音功能");
|
||||
});
|
||||
}
|
||||
|
||||
// 页面加载完成后初始化
|
||||
window.onload = function() {
|
||||
// 直接初始化看板娘,不添加额外延迟
|
||||
initL2Dwidget();
|
||||
|
||||
// 监听下拉框变化
|
||||
document.getElementById('model-select').addEventListener('change', function() {
|
||||
window.location.search = '?id=' + this.value;
|
||||
});
|
||||
|
||||
// 绑定录音按钮事件
|
||||
document.getElementById('startRecordBtn').addEventListener('click', startRecording);
|
||||
document.getElementById('stopRecordBtn').addEventListener('click', stopRecording);
|
||||
|
||||
// 页面加载时请求麦克风权限
|
||||
navigator.mediaDevices.getUserMedia({ audio: true })
|
||||
.then(stream => {
|
||||
console.log("麦克风权限已授予");
|
||||
// 立即停止流,只获取权限
|
||||
stream.getTracks().forEach(track => track.stop());
|
||||
})
|
||||
.catch(error => {
|
||||
console.error("获取麦克风权限失败:", error);
|
||||
alert("请授权麦克风权限以使用录音功能");
|
||||
});
|
||||
};
|
||||
</script>
|
||||
};
|
||||
</body>
|
||||
</html>
|
@@ -1,51 +1,31 @@
|
||||
// 看板娘初始化
|
||||
L2Dwidget.init({
|
||||
"model": {
|
||||
"jsonPath": "https://unpkg.com/live2d-widget-model-shizuku@1.0.5/assets/shizuku.model.json",
|
||||
"scale": 1
|
||||
},
|
||||
"display": {
|
||||
"position": "right",
|
||||
"width": 150,
|
||||
"height": 300,
|
||||
"hOffset": 0,
|
||||
"vOffset": -20
|
||||
},
|
||||
"mobile": {
|
||||
"show": true,
|
||||
"scale": 0.5
|
||||
},
|
||||
"react": {
|
||||
"opacityDefault": 0.8,
|
||||
"opacityOnHover": 0.2
|
||||
// 看板娘模型配置(基于原始代码提取)
|
||||
const models = {
|
||||
shizuku: { jsonPath: "https://unpkg.com/live2d-widget-model-shizuku@1.0.5/assets/shizuku.model.json", name: "小智" },
|
||||
koharu: { jsonPath: "https://unpkg.com/live2d-widget-model-koharu@1.0.5/assets/koharu.model.json", name: "小荷" },
|
||||
wanko: { jsonPath: "https://unpkg.com/live2d-widget-model-wanko@1.0.5/assets/wanko.model.json", name: "汪喵" }
|
||||
};
|
||||
|
||||
// 获取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 initL2Dwidget() {
|
||||
const modelId = getUrlParam('id') || 'koharu';
|
||||
const model = models[modelId] || models.koharu;
|
||||
|
||||
// 设置下拉框选中状态
|
||||
if (document.getElementById('model-select')) {
|
||||
document.getElementById('model-select').value = modelId;
|
||||
}
|
||||
});
|
||||
console.log('加载模型:', model.name, model.jsonPath);
|
||||
|
||||
// 模型选择器事件监听
|
||||
document.getElementById('model-select').addEventListener('change', function() {
|
||||
const selectedModel = this.value;
|
||||
let modelPath;
|
||||
|
||||
switch(selectedModel) {
|
||||
case 'shizuku':
|
||||
modelPath = "https://unpkg.com/live2d-widget-model-shizuku@1.0.5/assets/shizuku.model.json";
|
||||
break;
|
||||
case 'koharu':
|
||||
modelPath = "https://unpkg.com/live2d-widget-model-koharu@1.0.5/assets/koharu.model.json";
|
||||
break;
|
||||
case 'wanko':
|
||||
modelPath = "https://unpkg.com/live2d-widget-model-wanko@1.0.5/assets/wanko.model.json";
|
||||
break;
|
||||
default:
|
||||
modelPath = "https://unpkg.com/live2d-widget-model-shizuku@1.0.5/assets/shizuku.model.json";
|
||||
}
|
||||
|
||||
// 重新初始化看板娘
|
||||
// 初始化L2Dwidget(保留原始配置)
|
||||
L2Dwidget.init({
|
||||
"model": {
|
||||
"jsonPath": modelPath,
|
||||
"scale": 1
|
||||
},
|
||||
"model": { "jsonPath": model.jsonPath, "scale": 1 },
|
||||
"display": {
|
||||
"position": "right",
|
||||
"width": 150,
|
||||
@@ -53,13 +33,19 @@ document.getElementById('model-select').addEventListener('change', function() {
|
||||
"hOffset": 0,
|
||||
"vOffset": -20
|
||||
},
|
||||
"mobile": {
|
||||
"show": true,
|
||||
"scale": 0.5
|
||||
},
|
||||
"react": {
|
||||
"opacityDefault": 0.8,
|
||||
"opacityOnHover": 0.2
|
||||
}
|
||||
"mobile": { "show": true, "scale": 0.5 },
|
||||
"react": { "opacityDefault": 0.8, "opacityOnHover": 1 },
|
||||
"dialog": { "enable": true, "script": {
|
||||
'tap body': `你好啊,我是${model.name}。`,
|
||||
'tap face': '有什么问题或者烦心事都可以和我聊聊~'
|
||||
}}
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
// 页面加载完成后初始化看板娘
|
||||
window.addEventListener('load', initL2Dwidget);
|
||||
|
||||
// 暴露模型切换功能接口
|
||||
window.switchL2DModel = function(modelId) {
|
||||
window.location.search = '?id=' + modelId;
|
||||
};
|
@@ -113,8 +113,8 @@ h1 {
|
||||
}
|
||||
|
||||
.option {
|
||||
margin-bottom: 12px;
|
||||
padding: 12px 15px;
|
||||
margin-bottom: 15px; /* 从12px增加到15px */
|
||||
padding: 15px 18px; /* 从12px 15px增加到15px 18px */
|
||||
border-radius: 8px;
|
||||
border: 2px solid #e0e0e0;
|
||||
cursor: pointer;
|
||||
@@ -124,8 +124,9 @@ h1 {
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.option input[type="radio"] {
|
||||
margin-right: 10px;
|
||||
.option input[type="radio"],
|
||||
.option input[type="checkbox"] {
|
||||
margin-right: 20px; /* 增加间距到20px */
|
||||
cursor: pointer;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
@@ -7,108 +7,113 @@
|
||||
<link rel="stylesheet" href="physics_quiz.css">
|
||||
</head>
|
||||
<body>
|
||||
<div class="container">
|
||||
<header>
|
||||
<h1>物理知识测验 - 万有引力定律</h1>
|
||||
<div class="difficulty-indicator difficulty-medium">当前难度:中等</div>
|
||||
<div class="quiz-info">
|
||||
共15道题,分三个难度级别。完成当前难度后将根据您的表现推荐下一难度。
|
||||
</div>
|
||||
</header>
|
||||
|
||||
<div class="question-section">
|
||||
<!-- 试题将通过JavaScript动态渲染 -->
|
||||
<div class="container">
|
||||
<header>
|
||||
<h1>物理知识测验 - 万有引力定律</h1>
|
||||
<div class="difficulty-indicator difficulty-medium">当前难度:中等</div>
|
||||
<div class="quiz-info">
|
||||
共15道题,分三个难度级别。完成当前难度后将根据您的表现推荐下一难度。
|
||||
</div>
|
||||
</header>
|
||||
|
||||
<div class="navigation-section" id="navigation">
|
||||
<h3 id="navigation-message"></h3>
|
||||
<button type="button" id="next-btn">开始答题</button>
|
||||
</div>
|
||||
|
||||
<div class="submit-section">
|
||||
<button type="button" id="submit-btn">提交答案</button>
|
||||
<div id="result">
|
||||
<div class="result-header">测验结果</div>
|
||||
<div class="score">您的得分:<span id="score-value">0</span>/100分</div>
|
||||
<div class="correct-answers">正确题数:<span id="correct-count">0</span>道</div>
|
||||
<div class="incorrect-answers">错误题数:<span id="incorrect-count">0</span>道</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="question-section">
|
||||
<!-- 试题将通过JavaScript动态渲染 -->
|
||||
</div>
|
||||
|
||||
|
||||
<!-- 学伴功能区域 -->
|
||||
<div class="model-selector">
|
||||
<label for="model-select">选择学伴:</label>
|
||||
<select id="model-select">
|
||||
<option value="shizuku">小智</option>
|
||||
<option value="koharu">小荷</option>
|
||||
<option value="wanko">汪喵</option>
|
||||
</select>
|
||||
<div class="navigation-section" id="navigation">
|
||||
<h3 id="navigation-message"></h3>
|
||||
<button type="button" id="next-btn">开始答题</button>
|
||||
</div>
|
||||
|
||||
<div class="recording-indicator" id="recordingIndicator">
|
||||
<div class="recording-dot"></div>
|
||||
<span>正在录音...</span>
|
||||
</div>
|
||||
|
||||
<div class="thinking-indicator" id="thinkingIndicator">
|
||||
<div class="thinking-dots">
|
||||
<div class="thinking-dot"></div>
|
||||
<div class="thinking-dot"></div>
|
||||
<div class="thinking-dot"></div>
|
||||
</div>
|
||||
<span>学伴正在思考中...</span>
|
||||
</div>
|
||||
|
||||
<div class="recording-controls" style="position: fixed; right:37px; bottom: 230px; z-index: 998;">
|
||||
<button class="record-button" id="startRecordBtn" style="font-size: 14px;">帮我讲题</button>
|
||||
<button class="stop-button" id="stopRecordBtn" style="font-size: 14px;">停止讲话</button>
|
||||
</div>
|
||||
|
||||
<div class="result-container" id="resultContainer">
|
||||
<div>
|
||||
<div class="result-header asr">
|
||||
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M12 1H4C2.89 1 2 1.9 2 3V13C2 14.1 2.89 15 4 15H12C13.11 15 14 14.1 14 13V3C14 1.9 13.11 1 12 1ZM12 13H4V3H12V13Z" fill="#007bff"/>
|
||||
<path d="M8 4C6.34 4 5 5.34 5 7C5 8.66 6.34 10 8 10C9.66 10 11 8.66 11 7C11 5.34 9.66 4 8 4ZM8 8.5C7.17 8.5 6.5 7.83 6.5 7C6.5 6.17 7.17 5.5 8 5.5C8.83 5.5 9.5 6.17 9.5 7C9.5 7.83 8.83 8.5 8 8.5Z" fill="#007bff"/>
|
||||
<path d="M8 11C5.79 11 4 12.79 4 15V16H12V15C12 12.79 10.21 11 8 11ZM8 13C9.1 13 10 13.9 10 15H6C6 13.9 6.9 13 8 13Z" fill="#007bff"/>
|
||||
</svg>
|
||||
你讲的话
|
||||
</div>
|
||||
<div class="result-text" id="asrResultText"></div>
|
||||
</div>
|
||||
<div>
|
||||
<div class="result-header feedback">
|
||||
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M1 4C1 2.9 1.9 2 3 2H13C14.1 2 15 2.9 15 4V11C15 12.1 14.1 13 13 13H9L5 16V13H3C1.9 13 1 12.1 1 11V4Z" fill="#28a745"/>
|
||||
<path d="M8 8H6V5H8V8ZM11 8H9V5H11V8ZM11 11H6V9H11V11Z" fill="#28a745"/>
|
||||
</svg>
|
||||
学伴回复
|
||||
</div>
|
||||
<div class="result-text" id="feedbackResultText"></div>
|
||||
</div>
|
||||
<div class="audio-player-container">
|
||||
<button class="play-button" id="playAudioBtn">
|
||||
<svg width="20" height="20" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M8 5V19L19 12L8 5Z" fill="white"/>
|
||||
</svg>
|
||||
</button>
|
||||
<div class="audio-progress" id="audioProgress">
|
||||
<div class="progress-bar" id="progressBar"></div>
|
||||
</div>
|
||||
<div class="audio-time" id="audioTime">00:00 / 00:00</div>
|
||||
<div class="submit-section">
|
||||
<button type="button" id="submit-btn">提交答案</button>
|
||||
<div id="result">
|
||||
<div class="result-header">测验结果</div>
|
||||
<div class="score">您的得分:<span id="score-value">0</span>/100分</div>
|
||||
<div class="correct-answers">正确题数:<span id="correct-count">0</span>道</div>
|
||||
<div class="incorrect-answers">错误题数:<span id="incorrect-count">0</span>道</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
<!-- 学伴功能区域 -->
|
||||
<div class="model-selector">
|
||||
<label for="model-select">选择学伴:</label>
|
||||
<select id="model-select">
|
||||
<option value="shizuku">小智</option>
|
||||
<option value="koharu">小荷</option>
|
||||
<option value="wanko">汪喵</option>
|
||||
</select>
|
||||
</div>
|
||||
|
||||
<div class="recording-indicator" id="recordingIndicator">
|
||||
<div class="recording-dot"></div>
|
||||
<span>正在录音...</span>
|
||||
</div>
|
||||
|
||||
<div class="thinking-indicator" id="thinkingIndicator">
|
||||
<div class="thinking-dots">
|
||||
<div class="thinking-dot"></div>
|
||||
<div class="thinking-dot"></div>
|
||||
<div class="thinking-dot"></div>
|
||||
</div>
|
||||
<span>学伴正在思考中...</span>
|
||||
</div>
|
||||
|
||||
<div class="recording-controls" style="position: fixed; right:37px; bottom: 230px; z-index: 998;">
|
||||
<button class="record-button" id="startRecordBtn" style="font-size: 14px;">帮我讲题</button>
|
||||
<button class="stop-button" id="stopRecordBtn" style="font-size: 14px;">停止讲话</button>
|
||||
</div>
|
||||
|
||||
<div class="result-container" id="resultContainer">
|
||||
<div>
|
||||
<a href="https://dsideal.obs.cn-north-1.myhuaweicloud.com/HuangHai/11%E3%80%81%E5%AD%A6%E4%BC%B4Chrome%E5%BD%95%E9%9F%B3%E9%85%8D%E7%BD%AE%E4%BF%AE%E6%94%B9.pdf">有权限问题?</a>
|
||||
<div class="result-header asr">
|
||||
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M12 1H4C2.89 1 2 1.9 2 3V13C2 14.1 2.89 15 4 15H12C13.11 15 14 14.1 14 13V3C14 1.9 13.11 1 12 1ZM12 13H4V3H12V13Z"
|
||||
fill="#007bff"/>
|
||||
<path d="M8 4C6.34 4 5 5.34 5 7C5 8.66 6.34 10 8 10C9.66 10 11 8.66 11 7C11 5.34 9.66 4 8 4ZM8 8.5C7.17 8.5 6.5 7.83 6.5 7C6.5 6.17 7.17 5.5 8 5.5C8.83 5.5 9.5 6.17 9.5 7C9.5 7.83 8.83 8.5 8 8.5Z"
|
||||
fill="#007bff"/>
|
||||
<path d="M8 11C5.79 11 4 12.79 4 15V16H12V15C12 12.79 10.21 11 8 11ZM8 13C9.1 13 10 13.9 10 15H6C6 13.9 6.9 13 8 13Z"
|
||||
fill="#007bff"/>
|
||||
</svg>
|
||||
你讲的话
|
||||
</div>
|
||||
<div class="result-text" id="asrResultText"></div>
|
||||
</div>
|
||||
<div>
|
||||
<div class="result-header feedback">
|
||||
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M1 4C1 2.9 1.9 2 3 2H13C14.1 2 15 2.9 15 4V11C15 12.1 14.1 13 13 13H9L5 16V13H3C1.9 13 1 12.1 1 11V4Z"
|
||||
fill="#28a745"/>
|
||||
<path d="M8 8H6V5H8V8ZM11 8H9V5H11V8ZM11 11H6V9H11V11Z" fill="#28a745"/>
|
||||
</svg>
|
||||
学伴回复
|
||||
</div>
|
||||
<div class="result-text" id="feedbackResultText"></div>
|
||||
</div>
|
||||
<div class="audio-player-container">
|
||||
<button class="play-button" id="playAudioBtn">
|
||||
<svg width="20" height="20" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M8 5V19L19 12L8 5Z" fill="white"/>
|
||||
</svg>
|
||||
</button>
|
||||
<div class="audio-progress" id="audioProgress">
|
||||
<div class="progress-bar" id="progressBar"></div>
|
||||
</div>
|
||||
<div class="audio-time" id="audioTime">00:00 / 00:00</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 引入看板娘相关脚本 -->
|
||||
<script src="https://l2dwidget.js.org/lib/L2Dwidget.min.js"></script>
|
||||
<script src="live2d_widget.js"></script>
|
||||
<!-- 引入试题数据和渲染逻辑 -->
|
||||
<script src="physics_quiz.js"></script>
|
||||
<div>
|
||||
<a href="https://dsideal.obs.cn-north-1.myhuaweicloud.com/HuangHai/11%E3%80%81%E5%AD%A6%E4%BC%B4Chrome%E5%BD%95%E9%9F%B3%E9%85%8D%E7%BD%AE%E4%BF%AE%E6%94%B9.pdf">有权限问题?</a>
|
||||
</div>
|
||||
|
||||
<!-- 引入看板娘相关脚本 -->
|
||||
<script src="https://l2dwidget.js.org/lib/L2Dwidget.min.js"></script>
|
||||
<script src="live2d_widget.js"></script> <!-- 先加载基础配置 -->
|
||||
<script src="xueban.js"></script> <!-- 后加载扩展功能 -->
|
||||
<!-- 引入试题数据和渲染逻辑 -->
|
||||
<script src="physics_quiz.js"></script>
|
||||
</body>
|
||||
</html>
|
@@ -18,8 +18,8 @@ const correctAnswers = {
|
||||
hq1: 'C',
|
||||
hq2: 'A',
|
||||
hq3: 'C',
|
||||
hq4: 'A',
|
||||
hq5: 'B'
|
||||
hq4: ['C', 'D'], // 多选题,正确答案是CD
|
||||
hq5: ['A', 'C'] // 多选题,正确答案是AC
|
||||
}
|
||||
};
|
||||
|
||||
@@ -47,10 +47,10 @@ const quizQuestions = [
|
||||
points: 20,
|
||||
text: '地球质量为M,半径为R,引力常量为G。一颗质量为m的人造地球卫星在距离地面高度为h的轨道上做匀速圆周运动,则( )',
|
||||
options: [
|
||||
{ id: 'mq2-a', label: 'A', text: '卫星的线速度大小为<img src="https://dsideal.obs.cn-north-1.myhuaweicloud.com/HuangHai/WToM/images/e2ef2d4a91a3dfc8f7e227c45dcf5dc0\/media/\image1.png" alt="公式">' },
|
||||
{ id: 'mq2-b', label: 'B', text: '卫星的角速度大小为<img src="https://dsideal.obs.cn-north-1.myhuaweicloud.com/HuangHai/WToM/images/e2ef2d4a91a3dfc8f7e227c45dcf5dc0\/media/\image2.png" alt="公式">' },
|
||||
{ id: 'mq2-c', label: 'C', text: '卫星的向心加速度大小为<img src="https://dsideal.obs.cn-north-1.myhuaweicloud.com/HuangHai/WToM/images/e2ef2d4a91a3dfc8f7e227c45dcf5dc0\/media/\image3.png" alt="公式">' },
|
||||
{ id: 'mq2-d', label: 'D', text: '卫星的周期为<img src="https://dsideal.obs.cn-north-1.myhuaweicloud.com/HuangHai/WToM/images/e2ef2d4a91a3dfc8f7e227c45dcf5dc0\/media/\image4.png" alt="公式">' }
|
||||
{ id: 'mq2-a', label: 'A', text: '卫星的线速度大小为<img src="https://dsideal.obs.cn-north-1.myhuaweicloud.com/HuangHai/WToM/images/e2ef2d4a91a3dfc8f7e227c45dcf5dc0/media/image1.png" alt="公式">' },
|
||||
{ id: 'mq2-b', label: 'B', text: '卫星的角速度大小为<img src="https://dsideal.obs.cn-north-1.myhuaweicloud.com/HuangHai/WToM/images/e2ef2d4a91a3dfc8f7e227c45dcf5dc0/media/image2.png" alt="公式">' },
|
||||
{ id: 'mq2-c', label: 'C', text: '卫星的向心加速度大小为<img src="https://dsideal.obs.cn-north-1.myhuaweicloud.com/HuangHai/WToM/images/e2ef2d4a91a3dfc8f7e227c45dcf5dc0/media/image3.png" alt="公式">' },
|
||||
{ id: 'mq2-d', label: 'D', text: '卫星的周期为<img src="https://dsideal.obs.cn-north-1.myhuaweicloud.com/HuangHai/WToM/images/e2ef2d4a91a3dfc8f7e227c45dcf5dc0/media/image4.png" alt="公式">' }
|
||||
],
|
||||
explanation: '正确答案:C<br>解析:A.卫星的线速度大小为v=√(GM/(R+h)),故A错误;B.卫星的角速度大小为ω=√(GM/(R+h)³),故B错误;C.卫星的向心加速度大小为a=GM/(R+h)²,故C正确;D.卫星的周期为T=2π√((R+h)³/GM),故D错误。故答案为:C。'
|
||||
},
|
||||
@@ -87,11 +87,11 @@ const quizQuestions = [
|
||||
difficulty: 'medium',
|
||||
number: 5,
|
||||
points: 20,
|
||||
text: '我国首颗超百Gbps容量高通量地球静止轨道通信卫星中星26号卫星,于北京时间2023年2月23日在西昌卫星发射中心成功发射,该卫星主要用于为固定端及车、船、机载终端提供高速宽带接入服务。如图,某时刻中星26与椭圆轨道侦察卫星恰好位于C、D两点,两星轨道相交于A、B两点,C、D连线过地心,D点为远地点,两卫星运行周期都为T。下列说法正确的是( )<br><br><img src="https://dsideal.obs.cn-north-1.myhuaweicloud.com/HuangHai/WToM/images/760c54b8e6491d7c3ac566d6299818a4\/media/\image1.png" alt="轨道图">',
|
||||
text: '我国首颗超百Gbps容量高通量地球静止轨道通信卫星中星26号卫星,于北京时间2023年2月23日在西昌卫星发射中心成功发射,该卫星主要用于为固定端及车、船、机载终端提供高速宽带接入服务。如图,某时刻中星26与椭圆轨道侦察卫星恰好位于C、D两点,两星轨道相交于A、B两点,C、D连线过地心,D点为远地点,两卫星运行周期都为T。下列说法正确的是( )<br><br><img src="https://dsideal.obs.cn-north-1.myhuaweicloud.com/HuangHai/WToM/images/760c54b8e6491d7c3ac566d6299818a4/media/image1.png" alt="轨道图">',
|
||||
options: [
|
||||
{ id: 'mq5-a', label: 'A', text: '中星26与侦察卫星可能在A点或B点相遇' },
|
||||
{ id: 'mq5-b', label: 'B', text: '侦查卫星从D点运动到A点过程中机械能增大' },
|
||||
{ id: 'mq5-c', label: 'C', text: '中星26在C点线速度<img src="https://dsideal.obs.cn-north-1.myhuaweicloud.com/HuangHai/WToM/images/760c54b8e6491d7c3ac566d6299818a4\/media/\image2.png" alt="公式">与侦察卫星在D点线速度<img src="https://dsideal.obs.cn-north-1.myhuaweicloud.com/HuangHai/WToM/images/760c54b8e6491d7c3ac566d6299818a4\/media/\image3.png" alt="公式">相等' },
|
||||
{ id: 'mq5-c', label: 'C', text: '中星26在C点线速度<img src="https://dsideal.obs.cn-north-1.myhuaweicloud.com/HuangHai/WToM/images/760c54b8e6491d7c3ac566d6299818a4/media/image2.png" alt="公式">与侦察卫星在D点线速度<img src="https://dsideal.obs.cn-north-1.myhuaweicloud.com/HuangHai/WToM/images/760c54b8e6491d7c3ac566d6299818a4/media/image3.png" alt="公式">相等' },
|
||||
{ id: 'mq5-d', label: 'D', text: '相等时间内中星26与地球的连线扫过的面积大于侦察卫星与地球的连线扫过的面积' }
|
||||
],
|
||||
explanation: '正确答案:D<br>解析:A.中星26与侦察卫星周期相同,并且当中星26在下半周运动时,卫星在上半周运动,故不可能相遇,故A错误;B.侦查卫星在D到A点过程中只有引力做功故机械能不变,故B错误;C.开普勒第二定律可知,在近地点速度大于远地点速度故中星26在C点线速度大于侦察卫星在D点线速度,故C错误;D.中星26与侦察卫星的周期相同,由开普勒第三定律,中星26轨道半径等于侦察卫星的半长轴,运动一个周期中星26是一个圆,而侦察卫星是一个椭圆,由于圆的面积大于椭圆的面积,故相等时间内中星26与地球的连线扫过的面积大于侦察卫星与地球的连线扫过的面积,故D正确;故答案为:D。'
|
||||
@@ -259,7 +259,7 @@ const incorrectCount = document.getElementById('incorrect-count');
|
||||
const difficultyIndicator = document.querySelector('.difficulty-indicator');
|
||||
const questionSection = document.querySelector('.question-section');
|
||||
|
||||
// 获取难度名称 - 移到全局作用域
|
||||
// 获取难度名称
|
||||
function getDifficultyName(difficulty) {
|
||||
const names = {
|
||||
easy: '简单',
|
||||
@@ -273,199 +273,183 @@ function getDifficultyName(difficulty) {
|
||||
document.addEventListener('DOMContentLoaded', function() {
|
||||
// 渲染题目
|
||||
renderQuestions();
|
||||
// 修改录音按钮事件绑定代码
|
||||
|
||||
// 录音按钮事件绑定
|
||||
const startRecordBtn = document.getElementById('startRecordBtn');
|
||||
const stopRecordBtn = document.getElementById('stopRecordBtn');
|
||||
const recordingIndicator = document.getElementById('recordingIndicator');
|
||||
|
||||
// 隐藏停止讲话按钮,因为我们不需要它
|
||||
if (stopRecordBtn) {
|
||||
stopRecordBtn.style.display = 'none';
|
||||
}
|
||||
|
||||
if (startRecordBtn && stopRecordBtn && recordingIndicator) {
|
||||
// 点击"帮我讲题"按钮
|
||||
startRecordBtn.addEventListener('click', function() {
|
||||
// 显示录音指示器
|
||||
recordingIndicator.style.display = 'flex';
|
||||
// 隐藏"帮我讲题"按钮,显示"停止讲话"按钮
|
||||
startRecordBtn.style.display = 'none';
|
||||
stopRecordBtn.style.display = 'flex';
|
||||
// 这里添加实际录音功能逻辑
|
||||
console.log('开始录音...');
|
||||
// 示例:调用录音API或相关函数
|
||||
// startRecording();
|
||||
});
|
||||
startRecordBtn.addEventListener('click', function() {
|
||||
recordingIndicator.style.display = 'flex';
|
||||
startRecordBtn.style.display = 'none';
|
||||
stopRecordBtn.style.display = 'flex';
|
||||
console.log('开始录音...');
|
||||
});
|
||||
|
||||
// 点击"停止讲话"按钮
|
||||
stopRecordBtn.addEventListener('click', function() {
|
||||
// 隐藏录音指示器
|
||||
recordingIndicator.style.display = 'none';
|
||||
// 隐藏"停止讲话"按钮,显示"帮我讲题"按钮
|
||||
stopRecordBtn.style.display = 'none';
|
||||
startRecordBtn.style.display = 'flex';
|
||||
// 这里添加停止录音功能逻辑
|
||||
console.log('停止录音...');
|
||||
// 示例:停止录音并处理音频
|
||||
// stopRecording();
|
||||
});
|
||||
stopRecordBtn.addEventListener('click', function() {
|
||||
recordingIndicator.style.display = 'none';
|
||||
stopRecordBtn.style.display = 'none';
|
||||
startRecordBtn.style.display = 'flex';
|
||||
console.log('停止录音...');
|
||||
});
|
||||
} else {
|
||||
console.error('录音相关DOM元素未找到');
|
||||
console.error('录音相关DOM元素未找到');
|
||||
}
|
||||
|
||||
// 提交当前难度的答案
|
||||
// 修改提交按钮的事件处理逻辑
|
||||
// 提交按钮事件
|
||||
submitBtn.addEventListener('click', function() {
|
||||
const currentAnswers = correctAnswers[quizState.currentDifficulty];
|
||||
const totalQuestions = Object.keys(currentAnswers).length;
|
||||
let score = 0;
|
||||
let correct = 0;
|
||||
let incorrect = 0;
|
||||
const currentAnswers = correctAnswers[quizState.currentDifficulty];
|
||||
const totalQuestions = Object.keys(currentAnswers).length;
|
||||
let score = 0;
|
||||
let correct = 0;
|
||||
let incorrect = 0;
|
||||
|
||||
// 检查所有问题的答案
|
||||
for (const [questionName, correctOptions] of Object.entries(currentAnswers)) {
|
||||
// 获取用户选择的所有选项
|
||||
const selectedOptions = Array.from(document.querySelectorAll(`input[name="${questionName}"]:checked`))
|
||||
.map(checkbox => checkbox.value);
|
||||
// 检查所有问题的答案
|
||||
for (const [questionName, correctOptions] of Object.entries(currentAnswers)) {
|
||||
// 获取用户选择的所有选项
|
||||
const selectedOptions = Array.from(document.querySelectorAll(`input[name="${questionName}"]:checked`))
|
||||
.map(checkbox => checkbox.value);
|
||||
|
||||
const questionNumber = questionName.substring(2); // 从mq1提取1
|
||||
const explanationElement = document.querySelector(`[data-difficulty="${quizState.currentDifficulty}"][data-question="${questionNumber}"] .question-explanation`);
|
||||
const questionNumber = questionName.substring(2); // 从mq1提取1
|
||||
const explanationElement = document.querySelector(`[data-difficulty="${quizState.currentDifficulty}"][data-question="${questionNumber}"] .question-explanation`);
|
||||
|
||||
// 显示解析
|
||||
explanationElement.style.display = 'block';
|
||||
// 显示解析
|
||||
explanationElement.style.display = 'block';
|
||||
|
||||
if (selectedOptions.length > 0) {
|
||||
// 标记用户选择的选项
|
||||
let allCorrect = true;
|
||||
if (selectedOptions.length > 0) {
|
||||
// 标记用户选择的选项
|
||||
let allCorrect = true;
|
||||
|
||||
// 标记用户选择的每个选项
|
||||
document.querySelectorAll(`input[name="${questionName}"]:checked`).forEach(checkbox => {
|
||||
const userAnswer = checkbox.value;
|
||||
if (correctOptions.includes(userAnswer)) {
|
||||
checkbox.closest('.option').style.backgroundColor = '#e8f5e9';
|
||||
checkbox.closest('.option').style.borderColor = '#4caf50';
|
||||
} else {
|
||||
checkbox.closest('.option').style.backgroundColor = '#ffebee';
|
||||
checkbox.closest('.option').style.borderColor = '#c62828';
|
||||
allCorrect = false;
|
||||
}
|
||||
// 标记用户选择的每个选项
|
||||
document.querySelectorAll(`input[name="${questionName}"]:checked`).forEach(checkbox => {
|
||||
const userAnswer = checkbox.value;
|
||||
if (correctOptions.includes(userAnswer)) {
|
||||
checkbox.closest('.option').style.backgroundColor = '#e8f5e9';
|
||||
checkbox.closest('.option').style.borderColor = '#4caf50';
|
||||
} else {
|
||||
checkbox.closest('.option').style.backgroundColor = '#ffebee';
|
||||
checkbox.closest('.option').style.borderColor = '#c62828';
|
||||
allCorrect = false;
|
||||
}
|
||||
});
|
||||
|
||||
// 标记用户未选择但正确的选项
|
||||
document.querySelectorAll(`input[name="${questionName}"]`).forEach(checkbox => {
|
||||
if (correctOptions.includes(checkbox.value) && !checkbox.checked) {
|
||||
checkbox.closest('.option').style.backgroundColor = '#e3f2fd';
|
||||
checkbox.closest('.option').style.borderColor = '#2196f3';
|
||||
allCorrect = false;
|
||||
}
|
||||
});
|
||||
|
||||
// 判断是否全对
|
||||
if (allCorrect && selectedOptions.length === correctOptions.length) {
|
||||
score += 20; // 每题20分
|
||||
correct++;
|
||||
} else {
|
||||
incorrect++;
|
||||
}
|
||||
} else {
|
||||
// 未答题
|
||||
incorrect++;
|
||||
const questionElement = document.querySelector(`[data-difficulty="${quizState.currentDifficulty}"][data-question="${questionNumber}"]`);
|
||||
questionElement.style.border = '2px solid #ff9800';
|
||||
|
||||
// 显示所有正确答案
|
||||
document.querySelectorAll(`input[name="${questionName}"]`).forEach(checkbox => {
|
||||
if (correctOptions.includes(checkbox.value)) {
|
||||
checkbox.closest('.option').style.backgroundColor = '#e3f2fd';
|
||||
checkbox.closest('.option').style.borderColor = '#2196f3';
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
// 保存当前难度得分
|
||||
quizState.scores[quizState.currentDifficulty] = {
|
||||
score: score,
|
||||
correct: correct,
|
||||
incorrect: incorrect
|
||||
};
|
||||
quizState.completedDifficulties.push(quizState.currentDifficulty);
|
||||
|
||||
// 显示当前难度结果
|
||||
scoreValue.textContent = score;
|
||||
correctCount.textContent = correct;
|
||||
incorrectCount.textContent = incorrect;
|
||||
result.style.display = 'block';
|
||||
|
||||
// 隐藏提交按钮
|
||||
submitBtn.style.display = 'none';
|
||||
|
||||
// 根据当前难度和得分决定下一步
|
||||
if (quizState.currentDifficulty === 'medium') {
|
||||
if (correct < 3) {
|
||||
// 中等难度答对少于3题,推荐简单难度
|
||||
showNavigation('您在中等难度题目中表现不佳,建议先完成简单难度题目来巩固基础。', 'easy');
|
||||
} else {
|
||||
// 中等难度答对3题及以上,推荐高级难度
|
||||
showNavigation('恭喜您完成中等难度题目!接下来挑战高级难度题目吧。', 'hard');
|
||||
}
|
||||
} else {
|
||||
// 简单或高级难度完成后,显示最终结果
|
||||
showFinalResult();
|
||||
}
|
||||
});
|
||||
|
||||
// 标记用户未选择但正确的选项
|
||||
document.querySelectorAll(`input[name="${questionName}"]`).forEach(checkbox => {
|
||||
if (correctOptions.includes(checkbox.value) && !checkbox.checked) {
|
||||
checkbox.closest('.option').style.backgroundColor = '#e3f2fd';
|
||||
checkbox.closest('.option').style.borderColor = '#2196f3';
|
||||
allCorrect = false;
|
||||
}
|
||||
});
|
||||
|
||||
// 判断是否全对
|
||||
if (allCorrect && selectedOptions.length === correctOptions.length) {
|
||||
score += 20; // 每题20分
|
||||
correct++;
|
||||
} else {
|
||||
incorrect++;
|
||||
}
|
||||
} else {
|
||||
// 未答题
|
||||
incorrect++;
|
||||
const questionElement = document.querySelector(`[data-difficulty="${quizState.currentDifficulty}"][data-question="${questionNumber}"]`);
|
||||
questionElement.style.border = '2px solid #ff9800';
|
||||
|
||||
// 显示所有正确答案
|
||||
document.querySelectorAll(`input[name="${questionName}"]`).forEach(checkbox => {
|
||||
if (correctOptions.includes(checkbox.value)) {
|
||||
checkbox.closest('.option').style.backgroundColor = '#e3f2fd';
|
||||
checkbox.closest('.option').style.borderColor = '#2196f3';
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
// 保存当前难度得分
|
||||
quizState.scores[quizState.currentDifficulty] = {
|
||||
score: score,
|
||||
correct: correct,
|
||||
incorrect: incorrect
|
||||
};
|
||||
quizState.completedDifficulties.push(quizState.currentDifficulty);
|
||||
|
||||
// 显示当前难度结果
|
||||
scoreValue.textContent = score;
|
||||
correctCount.textContent = correct;
|
||||
incorrectCount.textContent = incorrect;
|
||||
result.style.display = 'block';
|
||||
|
||||
// 隐藏提交按钮
|
||||
submitBtn.style.display = 'none';
|
||||
|
||||
// 根据当前难度和得分决定下一步
|
||||
if (quizState.currentDifficulty === 'medium') {
|
||||
if (correct < 3) {
|
||||
// 中等难度答对少于3题,推荐简单难度
|
||||
showNavigation('您在中等难度题目中表现不佳,建议先完成简单难度题目来巩固基础。', 'easy');
|
||||
} else {
|
||||
// 中等难度答对3题及以上,推荐高级难度
|
||||
showNavigation('恭喜您完成中等难度题目!接下来挑战高级难度题目吧。', 'hard');
|
||||
}
|
||||
} else {
|
||||
// 简单或高级难度完成后,显示最终结果
|
||||
showFinalResult();
|
||||
}
|
||||
});
|
||||
|
||||
// 显示导航信息
|
||||
function showNavigation(message, nextDifficulty) {
|
||||
navigationMessage.textContent = message;
|
||||
navigationSection.dataset.nextDifficulty = nextDifficulty;
|
||||
navigationSection.style.display = 'block';
|
||||
}
|
||||
|
||||
// 开始下一组题目
|
||||
nextBtn.addEventListener('click', function() {
|
||||
const nextDifficulty = navigationSection.dataset.nextDifficulty;
|
||||
if (!nextDifficulty) return;
|
||||
const nextDifficulty = navigationSection.dataset.nextDifficulty;
|
||||
if (!nextDifficulty) return;
|
||||
|
||||
// 更新当前难度
|
||||
quizState.currentDifficulty = nextDifficulty;
|
||||
// 更新当前难度
|
||||
quizState.currentDifficulty = nextDifficulty;
|
||||
|
||||
// 隐藏导航和结果
|
||||
navigationSection.style.display = 'none';
|
||||
result.style.display = 'none';
|
||||
// 隐藏导航和结果
|
||||
navigationSection.style.display = 'none';
|
||||
result.style.display = 'none';
|
||||
|
||||
// 显示提交按钮
|
||||
submitBtn.style.display = 'inline-block';
|
||||
// 显示提交按钮
|
||||
submitBtn.style.display = 'inline-block';
|
||||
|
||||
// 更新难度指示器
|
||||
difficultyIndicator.textContent = `当前难度:${getDifficultyName(nextDifficulty)}`;
|
||||
difficultyIndicator.className = `difficulty-indicator difficulty-${nextDifficulty}`;
|
||||
// 更新难度指示器
|
||||
difficultyIndicator.textContent = `当前难度:${getDifficultyName(nextDifficulty)}`;
|
||||
difficultyIndicator.className = `difficulty-indicator difficulty-${nextDifficulty}`;
|
||||
|
||||
// 隐藏所有题目
|
||||
document.querySelectorAll('.question').forEach(question => {
|
||||
question.classList.add('hidden');
|
||||
// 隐藏所有题目
|
||||
document.querySelectorAll('.question').forEach(question => {
|
||||
question.classList.add('hidden');
|
||||
});
|
||||
|
||||
// 显示下一组题目
|
||||
document.querySelectorAll(`[data-difficulty="${nextDifficulty}"]`).forEach(question => {
|
||||
question.classList.remove('hidden');
|
||||
});
|
||||
|
||||
// 滚动到页面顶部
|
||||
window.scrollTo(0, 0);
|
||||
});
|
||||
});
|
||||
|
||||
// 显示下一组题目
|
||||
document.querySelectorAll(`[data-difficulty="${nextDifficulty}"]`).forEach(question => {
|
||||
question.classList.remove('hidden');
|
||||
});
|
||||
// 显示导航信息
|
||||
function showNavigation(message, nextDifficulty) {
|
||||
navigationMessage.textContent = message;
|
||||
navigationSection.dataset.nextDifficulty = nextDifficulty;
|
||||
navigationSection.style.display = 'block';
|
||||
}
|
||||
|
||||
// 滚动到页面顶部
|
||||
window.scrollTo(0, 0);
|
||||
});
|
||||
|
||||
// 显示最终结果
|
||||
function showFinalResult() {
|
||||
// 显示最终结果
|
||||
function showFinalResult() {
|
||||
// 计算总分
|
||||
let totalScore = 0;
|
||||
let totalCorrect = 0;
|
||||
let totalIncorrect = 0;
|
||||
|
||||
for (const difficulty of quizState.completedDifficulties) {
|
||||
totalScore += quizState.scores[difficulty].score;
|
||||
totalCorrect += quizState.scores[difficulty].correct;
|
||||
totalIncorrect += quizState.scores[difficulty].incorrect;
|
||||
totalScore += quizState.scores[difficulty].score;
|
||||
totalCorrect += quizState.scores[difficulty].correct;
|
||||
totalIncorrect += quizState.scores[difficulty].incorrect;
|
||||
}
|
||||
|
||||
// 更新结果显示
|
||||
@@ -478,8 +462,7 @@ document.addEventListener('DOMContentLoaded', function() {
|
||||
navigationMessage.textContent = '恭喜您完成所有推荐题目!';
|
||||
navigationSection.style.display = 'block';
|
||||
document.getElementById('next-btn').style.display = 'none';
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// 渲染题目函数
|
||||
function renderQuestions() {
|
||||
|
152
dsLightRag/static/YunXiao/xueban.js
Normal file
152
dsLightRag/static/YunXiao/xueban.js
Normal file
@@ -0,0 +1,152 @@
|
||||
// 学伴录音功能核心逻辑
|
||||
// 模型配置
|
||||
|
||||
// 录音相关变量
|
||||
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());
|
||||
}
|
||||
}
|
||||
|
||||
// 上传音频到服务器
|
||||
function uploadAudioToServer(audioBlob) {
|
||||
console.log("开始上传音频到服务器");
|
||||
document.getElementById('thinkingIndicator').style.display = 'flex';
|
||||
|
||||
const formData = new FormData();
|
||||
formData.append('file', audioBlob, 'recording.wav');
|
||||
|
||||
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);
|
||||
}
|
||||
})
|
||||
.catch(error => {
|
||||
console.error("上传音频失败:", error);
|
||||
document.getElementById('thinkingIndicator').style.display = 'none';
|
||||
alert('上传音频失败: ' + error.message);
|
||||
});
|
||||
}
|
||||
|
||||
// 显示ASR识别结果和反馈
|
||||
function showResults(data) {
|
||||
const resultContainer = document.getElementById('resultContainer');
|
||||
resultContainer.style.display = 'flex';
|
||||
|
||||
document.getElementById('asrResultText').textContent = data.asr_text || '未识别到内容';
|
||||
document.getElementById('feedbackResultText').textContent = data.feedback_text || '无反馈内容';
|
||||
|
||||
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); }
|
||||
};
|
||||
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;
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
// 音频播放控制函数
|
||||
function togglePlayAudio() {
|
||||
if (!audioElement) return;
|
||||
isPlaying ? audioElement.pause() : audioElement.play();
|
||||
isPlaying = !isPlaying;
|
||||
updatePlayButton();
|
||||
}
|
||||
|
||||
function updatePlayButton() {
|
||||
const btn = document.getElementById('playAudioBtn');
|
||||
btn.innerHTML = isPlaying ?
|
||||
'<svg width="20" height="20" viewBox="0 0 24 24" fill="none"><path d="M6 19H10V5H6V19ZM14 19H18V5H14V19Z" fill="white"/></svg>' :
|
||||
'<svg width="20" height="20" viewBox="0 0 24 24" fill="none"><path d="M8 5V19L19 12L8 5Z" fill="white"/></svg>';
|
||||
}
|
||||
|
||||
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)}`;
|
||||
}
|
Reference in New Issue
Block a user