Merge branch 'feature/voice_send'

This commit is contained in:
Cai
2025-10-19 20:30:51 +08:00
15 changed files with 674 additions and 219 deletions

View File

@@ -1,16 +1,16 @@
var socket;
let system_message_idx=0
document.addEventListener('DOMContentLoaded', function() {
let system_message_idx = 0
document.addEventListener('DOMContentLoaded', function () {
data = window.appData;
console.log(data);
socket = io('wss://hsamooc.com/agent',{
query:{
user_uuid:data.user_uuid,
folder:data.folder
socket = io('wss://hsamooc.com/agent', {
query: {
user_uuid: data.user_uuid,
folder: data.folder
}
});
socket.on('connect', function() {
socket.on('connect', function () {
console.log('Connected to server');
socket.emit('login',JSON.stringify(data));
});
@@ -18,10 +18,15 @@ document.addEventListener('DOMContentLoaded', function() {
OpenIframe();
});
// 监听来自服务器的消息
socket.on('message', function(data) {
socket.on('voiceMessage', function (data) {
console.log(data)
const input = document.createElement('messageInput');
input.data = data['data'];
});
socket.on('message', function (data) {
// 显示服务器的回复消息
const serverMessage = document.createElement('div');
serverMessage.innerHTML = `<div><b>华实君</b></div><div class="chat-message server-message"> ${marked.parse(data)}</div>`;
serverMessage.innerHTML = `<div><b>华实君:</b></div><div class="chat-message server-message"> ${marked.parse(data)}</div>`;
document.getElementById('chatbox').appendChild(serverMessage);
// 滚动到最新消息
document.getElementById('chatbox').scrollTop = document.getElementById('chatbox').scrollHeight;
@@ -56,18 +61,18 @@ document.addEventListener('DOMContentLoaded', function() {
approveButton.innerHTML = '√'; // 使用勾选符号
approveButton.data = element;
approveButton.className = 'approve-button';
approveButton.onclick = function(event) {
console.log(element);
socket.emit('message', JSON.stringify({ type: 'function', data: element }));
event.currentTarget.disabled = true;
event.currentTarget.classList.add('disabled');
approveButton.onclick = function (event) {
console.log(element);
socket.emit('message', JSON.stringify({ type: 'function', data: element }));
event.currentTarget.disabled = true;
event.currentTarget.classList.add('disabled');
};
requestMessage.appendChild(description);
requestMessage.appendChild(approveButton);
document.getElementById('chatbox').appendChild(requestMessage);
document.getElementById('chatbox').scrollTop = document.getElementById('chatbox').scrollHeight;
}
}
// for (let i = 0; i < data.length; i++) {
// const element = data[i]
// const requestMessage = document.createElement('div');
@@ -95,11 +100,11 @@ document.addEventListener('DOMContentLoaded', function() {
console.log(error)
}
});
socket.on('next_chapter', function(data){
socket.on('next_chapter', function (data) {
next_chapter(data);
});
socket.on('chapter_score',function(data){
if (typeof data === 'string'){
socket.on('chapter_score', function (data) {
if (typeof data === 'string') {
data = JSON.parse(data);
}
console.log(data)
@@ -109,7 +114,7 @@ document.addEventListener('DOMContentLoaded', function() {
document.getElementById('chatbox').appendChild(scoreMessage);
document.getElementById('chatbox').scrollTop = document.getElementById('chatbox').scrollHeight;
})
socket.on('system_message',function(data){
socket.on('system_message', function (data) {
console.log(data)
let systemMessage = document.createElement('div');
systemMessage.className = 'chat-message server-message';
@@ -117,14 +122,14 @@ document.addEventListener('DOMContentLoaded', function() {
closeButton.className = 'close-button'; // 可以根据需要添加样式类
closeButton.innerHTML = '×'; // 关闭按钮的文本内容
closeButton.style.marginRight = '10px'; // 可选:设置关闭按钮的右边距
systemMessage.appendChild(closeButton);
systemMessage.innerHTML += `<b>系统提示: </b> ${data}`;
systemMessage.id = 'system_message_'+system_message_idx;
system_message_idx+=1
systemMessage.id = 'system_message_' + system_message_idx;
system_message_idx += 1
document.getElementById('chatbox').appendChild(systemMessage);
document.getElementById('chatbox').scrollTop = document.getElementById('chatbox').scrollHeight;
$('.close-button').on('click', function(event) {
$('.close-button').on('click', function (event) {
$(this).css({
'background-color': 'gray',
'color': 'white', // 可选:设置文字颜色为白色,以便更清晰地显示
@@ -132,7 +137,7 @@ document.addEventListener('DOMContentLoaded', function() {
});
$(this).parent().remove();
});
setTimeout(function() {
setTimeout(function () {
$(systemMessage.id).remove();
}, 5000); // 5000 毫秒 = 5 秒
})
@@ -154,7 +159,7 @@ document.addEventListener('DOMContentLoaded', function() {
const fontSizeSlider = document.getElementById('fontSizeSlider');
const chatbox = document.getElementById('chatbox');
fontSizeSlider.addEventListener('input', function() {
fontSizeSlider.addEventListener('input', function () {
console.log(fontSizeSlider.value);
chatbox.style.fontSize = fontSizeSlider.value + 'px';
});
@@ -168,27 +173,27 @@ const chineseLabel = document.getElementById('label_for_zh');
let language = 'zh'; // 默认语言
// 切换语言事件
englishRadio.addEventListener('change', function() {
englishRadio.addEventListener('change', function () {
if (englishRadio.checked) {
language = 'en';
englishLabel.classList.add('active');
chineseLabel.classList.remove('active');
}
socket.emit('language',language);
socket.emit('language', language);
});
chineseRadio.addEventListener('change', function() {
chineseRadio.addEventListener('change', function () {
if (chineseRadio.checked) {
language = 'zh';
chineseLabel.classList.add('active');
englishLabel.classList.remove('active');
}
socket.emit('language',language);
socket.emit('language', language);
});
function sendInitiativeFunctionCall(function_name){
socket.emit('initiative', {'name': function_name});
function sendInitiativeFunctionCall(function_name) {
socket.emit('initiative', { 'name': function_name });
}
// 发送消息
function sendMessage() {
@@ -204,7 +209,7 @@ function sendMessage() {
document.getElementById('chatbox').appendChild(userMessage);
// 发送消息到服务器
socket.emit('message', JSON.stringify({data: input, type:'text'}));
socket.emit('message', JSON.stringify({ data: input, type: 'text' }));
// 清空输入框
document.getElementById('messageInput').value = '';
@@ -213,6 +218,267 @@ function sendMessage() {
document.getElementById('chatbox').scrollTop = document.getElementById('chatbox').scrollHeight;
}
// let voice_stream = null;
// let audio_chunks = [];
// let isRecording = false;
// let recordingInterval = null;
// //鼠标点击终止事件
// function handleMouseClick(event) {
// event.stopPropagation();
// stopRecording();
// }
// //发送语音
// const startAudioRecording = async () => {
// try {
// const stream = await navigator.mediaDevices.getUserMedia({
// audio: {
// sampleRate: 16000,
// channelCount: 1,
// echoCancellation: true,
// noiseSuppression: true
// }
// });
// const mediaRecorder = new MediaRecorder(stream);
// const audioContext = new AudioContext({
// sampleRate: 16000
// });
// // Load the AudioWorklet
// await audioContext.audioWorklet.addModule('/static/js/audio-processor.js');
// const source = audioContext.createMediaStreamSource(stream);
// const workletNode = new AudioWorkletNode(audioContext, 'audio-processor');
// let audioChunks = [];
// workletNode.port.onmessage = (e) => {
// audioChunks.push(new Float32Array(e.data));
// if (audioChunks.length >= 16) {
// // 合并所有音频块
// const totalLength = audioChunks.reduce((acc, chunk) => acc + chunk.length, 0);
// const mergedData = new Float32Array(totalLength);
// let offset = 0;
// for (const chunk of audioChunks) {
// mergedData.set(chunk, offset);
// offset += chunk.length;
// }
// const audioMessage = {
// id: window.data.user_uuid,
// type: 'audio',
// data: mergedData,
// sampleRate: 16000,
// timestamp: Date.now()
// };
// socket.emit('voice', audioMessage);
// console.log("voice sended")
// audioChunks = [];
// }
// };
// source.connect(workletNode);
// workletNode.connect(audioContext.destination);
// mediaRecorder.value = {
// stop: () => {
// source.disconnect();
// workletNode.disconnect();
// stream.getTracks().forEach(track => track.stop());
// audioContext.close();
// }
// }
// isRecording.value = true;
// } catch (error) {
// console.error('Error starting audio recording:', error);
// }
// };
// function sendVoiceMessage() {
// startAudioRecording()
// //变换录音中图标
// document.getElementById('recordIcon').className = 'bi bi-record-circle';
// document.addEventListener('click', handleMouseClick);
// }
// //停止录音
// function stopRecording() {
// if (!isRecording) return;
// isRecording = false;
// //取消监听
// document.removeEventListener('click', handleMouseClick);
// //取消定时器
// if (recordingInterval) {
// clearInterval(recordingInterval);
// recordingInterval = null;
// }
// //变换图标
// document.getElementById('recordIcon').className = 'bi bi-mic';
// //发送剩下的数据
// if (audio_chunks.length > 0) {
// const merged_data = new Uint8Array(audio_chunks.reduce((acc, chunk) => acc + chunk.length, 0));
// let offset = 0;
// for (const chunk of audio_chunks) {
// merged_data.set(new Uint8Array(chunk), offset);
// offset += chunk.length;
// }
// socket.emit('VoiceMessage', merged_data, { binary: true });
// audio_chunks = [];
// }
// overlay.style.display = 'none';
// //关闭音频流
// if (voice_stream) {
// voice_stream.stop_stream();
// voice_stream.close();
// voice_stream = null;
// }
// if (p) {
// p.terminate();
// p = null;
// }
// }
//语音这一块//
let mediaRecorderInstance = null;
let audioContextInstance = null;
let workletNodeInstance = null;
let sourceInstance = null;
let streamInstance = null;
let isRecording = false;
//发送语音
const startAudioRecording = async () => {
try {
const stream = await navigator.mediaDevices.getUserMedia({
audio: {
sampleRate: 16000,
channelCount: 1,
echoCancellation: true,
noiseSuppression: true
}
});
// 保存实例到全局变量
streamInstance = stream;
const mediaRecorder = new MediaRecorder(stream);
mediaRecorderInstance = mediaRecorder;
const audioContext = new AudioContext({
sampleRate: 16000
});
audioContextInstance = audioContext;
// Load the AudioWorklet
await audioContext.audioWorklet.addModule('/static/js/audio-processor.js');
const source = audioContext.createMediaStreamSource(stream);
sourceInstance = source;
const workletNode = new AudioWorkletNode(audioContext, 'audio-processor');
workletNodeInstance = workletNode;
audioChunks = [];
workletNode.port.onmessage = (e) => {
if (!isRecording) return;
audioChunks.push(new Float32Array(e.data));
if (audioChunks.length >= 16) {
// 合并所有音频块
const totalLength = audioChunks.reduce((acc, chunk) => acc + chunk.length, 0);
const mergedData = new Float32Array(totalLength);
let offset = 0;
for (const chunk of audioChunks) {
mergedData.set(chunk, offset);
offset += chunk.length;
}
const audioMessage = {
id: window.data.user_uuid,
type: 'audio',
data: mergedData,
sampleRate: 16000,
timestamp: Date.now()
};
socket.emit('voice', audioMessage);
console.log("voice sended");
audioChunks = [];
}
};
source.connect(workletNode);
workletNode.connect(audioContext.destination);
isRecording = true;
} catch (error) {
console.error('Error starting audio recording:', error);
// 发生错误时清理资源
stopRecording();
}
};
//停止录音
function stopRecording() {
if (!isRecording) return;
// 停止所有音频处理
if (workletNodeInstance) {
workletNodeInstance.disconnect();
workletNodeInstance = null;
}
if (sourceInstance) {
sourceInstance.disconnect();
sourceInstance = null;
}
if (audioContextInstance && audioContextInstance.state !== 'closed') {
audioContextInstance.close();
audioContextInstance = null;
}
// 停止媒体流
if (streamInstance) {
streamInstance.getTracks().forEach(track => {
track.stop();
});
streamInstance = null;
}
// 重置录音状态
isRecording = false;
mediaRecorderInstance = null;
audioChunks = [];
// 恢复麦克风图标
const recordIcon = document.getElementById('recordIcon');
if (recordIcon) {
recordIcon.className = 'bi bi-mic';
}
// 移除点击事件监听器
document.removeEventListener('click', handleMouseClick);
}
//鼠标点击终止事件
function handleMouseClick(event) {
event.stopPropagation();
stopRecording();
}
function sendVoiceMessage() {
if (isRecording) {
stopRecording();
return;
}
startAudioRecording();
// 变换录音中图标
const recordIcon = document.getElementById('recordIcon');
if (recordIcon) {
recordIcon.className = 'bi bi-record-circle';
}
// 添加点击事件监听器
document.addEventListener('click', handleMouseClick);
}
//页面卸载时停止录音
window.addEventListener('beforeunload', () => {
if (isRecording) {
stopRecording();
}
});
let currentChapterIndex = -1;
function next_chapter(data) {
// 获取所有的 h3 元素
@@ -255,22 +521,22 @@ function next_chapter(data) {
function generateProgressBar(N, idx, titles) {
const container = document.getElementById('markdown-content-process');
container.innerHTML = ''; // 清空内容
// 创建进度条的外框
const progressBox = document.createElement('div');
progressBox.className = 'progress-box';
// 获取详细信息容器
const detailBox = document.getElementById('progress-detail');
// 根据N值动态调整每个进度节点的宽度
const nodeWidth = 100 / N; // 每个节点的宽度为 100% / N
// 根据N值生成子框并设置颜色
for (let i = 0; i < N; i++) {
const titleBox = document.createElement('div');
titleBox.className = 'progress-title';
// 设置进度条的颜色
if (i < idx) {
titleBox.classList.add('green'); // 已完成部分
@@ -285,7 +551,7 @@ function generateProgressBar(N, idx, titles) {
titleBox.style.width = `${nodeWidth}%`;
// 监听鼠标移入事件来显示详情
titleBox.addEventListener('mouseenter', function() {
titleBox.addEventListener('mouseenter', function () {
detailBox.innerHTML = `Step ${i + 1}: ${titles[i] || "No title"}`;// - Additional details here...`;
detailBox.style.display = 'block';
// 根据进度条位置显示详情
@@ -295,7 +561,7 @@ function generateProgressBar(N, idx, titles) {
});
// 监听鼠标移出事件来隐藏详情
titleBox.addEventListener('mouseleave', function() {
titleBox.addEventListener('mouseleave', function () {
detailBox.style.display = 'none';
});