Files
hsa/Html/apps/static/js/chatbox.js
2025-10-19 20:21:57 +08:00

562 lines
20 KiB
JavaScript
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.

var socket;
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.on('connect', function () {
console.log('Connected to server');
socket.emit('login',JSON.stringify(data));
});
socket.on('open-iframe', function(data) {
OpenIframe();
});
// 监听来自服务器的消息
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>`;
document.getElementById('chatbox').appendChild(serverMessage);
// 滚动到最新消息
document.getElementById('chatbox').scrollTop = document.getElementById('chatbox').scrollHeight;
});
socket.on('message-hint', function(data){
console.log('get message-hint');
// 显示服务器的回复消息
const serverMessage = document.createElement('div');
serverMessage.innerHTML = `<div id="hint-title"><b>提示</b></div><div id="hint-message" class="chat-message server-message"> ${data}</div>`;
document.getElementById('chatbox').appendChild(serverMessage);
// 滚动到最新消息
document.getElementById('chatbox').scrollTop = document.getElementById('chatbox').scrollHeight;
});
socket.on('request_function', function(data){
try {
console.log("request_function", data);
if (typeof data === 'string') {
data = JSON.parse(data);
}
if (typeof data === 'object') {
data = [data];
}
for (let i = 0; i < data.length; i++) {
const element = data[i];
const requestMessage = document.createElement('div');
requestMessage.className = 'chat-message server-message approve-message';
const description = document.createElement('div');
description.innerHTML = `<b>Function:</b> ${element.data.name}(${JSON.stringify(element.data.args)})`;
const approveButton = document.createElement('button');
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');
};
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');
// requestMessage.className = 'chat-message server-message';
// const approveButton = document.createElement('button');
// approveButton.innerText = '批准';
// approveButton.data = element;
// approveButton.style.width = '100%';
// approveButton.style.borderRadius = '4px';
// approveButton.onclick = function(event) {
// console.log(element)
// socket.emit('message', JSON.stringify({type:'function', data:element}));
// event.currentTarget.disabled = true;
// event.currentTarget.style.backgroundColor = 'gray';
// event.currentTarget.style.color = 'white'; // 可选:设置文字颜色为白色,以便更清晰地显示
// event.currentTarget.style.border = 'none'; // 可选:去掉边框
// };
// // 将按钮添加到消息气泡中
// requestMessage.innerHTML = `<b>Function:</b> ${element.name}(${JSON.stringify(element.arguments)})<br>`;
// requestMessage.appendChild(approveButton);
// document.getElementById('chatbox').appendChild(requestMessage);
// document.getElementById('chatbox').scrollTop = document.getElementById('chatbox').scrollHeight;
// }
} catch (error) {
console.log(error)
}
});
socket.on('next_chapter', function (data) {
next_chapter(data);
});
socket.on('chapter_score', function (data) {
if (typeof data === 'string') {
data = JSON.parse(data);
}
console.log(data)
const scoreMessage = document.createElement('div');
scoreMessage.className = 'chat-message server-message';
scoreMessage.innerHTML = `<b>Chapter ${data.chapter_id} Score:</b> ${JSON.stringify(data.data.content.speak)}`;
document.getElementById('chatbox').appendChild(scoreMessage);
document.getElementById('chatbox').scrollTop = document.getElementById('chatbox').scrollHeight;
})
socket.on('system_message', function (data) {
console.log(data)
let systemMessage = document.createElement('div');
systemMessage.className = 'chat-message server-message';
let closeButton = document.createElement('button');
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
document.getElementById('chatbox').appendChild(systemMessage);
document.getElementById('chatbox').scrollTop = document.getElementById('chatbox').scrollHeight;
$('.close-button').on('click', function (event) {
$(this).css({
'background-color': 'gray',
'color': 'white', // 可选:设置文字颜色为白色,以便更清晰地显示
'border': 'none' // 可选:去掉边框
});
$(this).parent().remove();
});
setTimeout(function () {
$(systemMessage.id).remove();
}, 5000); // 5000 毫秒 = 5 秒
})
});
// 控制文字大小的滑块
const fontSizeSlider = document.getElementById('fontSizeSlider');
const chatbox = document.getElementById('chatbox');
fontSizeSlider.addEventListener('input', function () {
console.log(fontSizeSlider.value);
chatbox.style.fontSize = fontSizeSlider.value + 'px';
});
// 语言切换按钮
const englishRadio = document.getElementById('english');
const chineseRadio = document.getElementById('chinese');
const englishLabel = document.getElementById('label_for_en');
const chineseLabel = document.getElementById('label_for_zh');
let language = 'zh'; // 默认语言
// 切换语言事件
englishRadio.addEventListener('change', function () {
if (englishRadio.checked) {
language = 'en';
englishLabel.classList.add('active');
chineseLabel.classList.remove('active');
}
socket.emit('language', language);
});
chineseRadio.addEventListener('change', function () {
if (chineseRadio.checked) {
language = 'zh';
chineseLabel.classList.add('active');
englishLabel.classList.remove('active');
}
socket.emit('language', language);
});
function sendInitiativeFunctionCall(function_name) {
socket.emit('initiative', { 'name': function_name });
}
// 发送消息
function sendMessage() {
const input = document.getElementById('messageInput').value;
if (input.trim() === '') return;
// 使用 marked.js 解析 markdown
const markdownHtml = marked.parse(input);
// 显示用户发送的消息
const userMessage = document.createElement('div');
userMessage.innerHTML = `<div class="chat-message user-message"><b>你:</b> ${markdownHtml}</div>`;
document.getElementById('chatbox').appendChild(userMessage);
// 发送消息到服务器
socket.emit('message', JSON.stringify({ data: input, type: 'text' }));
// 清空输入框
document.getElementById('messageInput').value = '';
// 滚动到最新消息
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 元素
var iframe = document.getElementById('markdown-content-iframe');
// 访问 iframe 的文档对象
var iframeDocument = iframe.contentDocument || iframe.contentWindow.document;
var titles = []
var h3Elements = iframeDocument.querySelectorAll('h3');
console.log("h3Elements next")
if (currentChapterIndex >= h3Elements.length) {
return; // 如果已经到了最后一个章节,则不进行任何操作
}
// 隐藏当前章节及其后的内容
for (let i = 0; i < h3Elements.length; i++) {
h3Elements[i].style.display = 'none';
titles.push(h3Elements[i].innerText)
let nextSibling = h3Elements[i].nextElementSibling;
while (nextSibling && nextSibling.tagName !== 'H3') {
nextSibling.style.display = 'none';
nextSibling = nextSibling.nextElementSibling;
}
}
// 显示下一个章节及其后的内容直到再下一个h3元素或结束
if (currentChapterIndex + 1 < h3Elements.length) {
h3Elements[currentChapterIndex + 1].style.display = 'block';
let nextSibling = h3Elements[currentChapterIndex + 1].nextElementSibling;
while (nextSibling && nextSibling.tagName !== 'H3') {
nextSibling.style.display = 'block';
nextSibling = nextSibling.nextElementSibling;
}
}
currentChapterIndex++;
generateProgressBar(h3Elements.length, currentChapterIndex, titles);
}
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'); // 已完成部分
} else {
titleBox.classList.add('white'); // 未完成部分
}
// 设置每个子框的标题
titleBox.innerHTML = titles[i] || `Step ${i + 1}`;
// 设置每个进度节点的宽度
titleBox.style.width = `${nodeWidth}%`;
// 监听鼠标移入事件来显示详情
titleBox.addEventListener('mouseenter', function () {
detailBox.innerHTML = `Step ${i + 1}: ${titles[i] || "No title"}`;// - Additional details here...`;
detailBox.style.display = 'block';
// 根据进度条位置显示详情
const rect = titleBox.getBoundingClientRect();
detailBox.style.top = `${rect.top + window.scrollY - 100}px`; // 微调位置
detailBox.style.left = `${rect.left + window.scrollX + rect.width / 2 - detailBox.offsetWidth / 2}px`;
});
// 监听鼠标移出事件来隐藏详情
titleBox.addEventListener('mouseleave', function () {
detailBox.style.display = 'none';
});
progressBox.appendChild(titleBox);
}
// 将生成的进度条添加到容器中
container.appendChild(progressBox);
}