语音交互功能
This commit is contained in:
@@ -187,7 +187,6 @@ class ASEngineClient:
|
|||||||
try:
|
try:
|
||||||
# 编码语音数据为base64
|
# 编码语音数据为base64
|
||||||
# voice_data = 'data:image/jpeg;base64,' +base64.b64encode(voice_data).decode('utf-8')
|
# voice_data = 'data:image/jpeg;base64,' +base64.b64encode(voice_data).decode('utf-8')
|
||||||
|
|
||||||
# 发送数据
|
# 发送数据
|
||||||
self.sio.emit(
|
self.sio.emit(
|
||||||
route,
|
route,
|
||||||
@@ -270,4 +269,3 @@ class HSAEngineClient(ASEngineClient):
|
|||||||
self.send_text('markdown-in', fmd, self.id)
|
self.send_text('markdown-in', fmd, self.id)
|
||||||
self.send_text('markdown-prompt-in', fmdp, self.id)
|
self.send_text('markdown-prompt-in', fmdp, self.id)
|
||||||
self.send_text('score-prompt-in', fsp, self.id)
|
self.send_text('score-prompt-in', fsp, self.id)
|
||||||
|
|
||||||
|
|||||||
@@ -63,6 +63,12 @@ class AgentNamespace(Namespace):
|
|||||||
entry=self,
|
entry=self,
|
||||||
init_data={'room': user_uuid}
|
init_data={'room': user_uuid}
|
||||||
)
|
)
|
||||||
|
user_uuid2ase_client[user_uuid].register_route_with_entry(
|
||||||
|
route='voice_out',
|
||||||
|
callback=self.receive_ase_voice_out,
|
||||||
|
entry=self,
|
||||||
|
init_data={'room': user_uuid}
|
||||||
|
)
|
||||||
user_uuid2ase_client[user_uuid].register_route_with_entry(
|
user_uuid2ase_client[user_uuid].register_route_with_entry(
|
||||||
route='judge',
|
route='judge',
|
||||||
callback=self.receive_ase_judge,
|
callback=self.receive_ase_judge,
|
||||||
@@ -119,11 +125,25 @@ class AgentNamespace(Namespace):
|
|||||||
emit(d['route'], d, room=uuid, namespace='/agent')
|
emit(d['route'], d, room=uuid, namespace='/agent')
|
||||||
user_uuid2ase_client[uuid].send_text(d['route'], data['data'])
|
user_uuid2ase_client[uuid].send_text(d['route'], data['data'])
|
||||||
|
|
||||||
|
def on_VoiceMessage(self, voice_data):
|
||||||
|
print(f"Message from client: {voice_data}")
|
||||||
|
ex = current_app.extensions
|
||||||
|
chatmanager = ex["user_uuid2chatmanager"]
|
||||||
|
uuid = session.get('user_uuid')
|
||||||
|
user_uuid2ase_client = ex["user_uuid2ase_client"]
|
||||||
|
res = user_uuid2ase_client[uuid].send_voice('voice_in', voice_data)
|
||||||
|
print(f"Send voicce to route 'voice_in' success: {res}")
|
||||||
|
|
||||||
|
|
||||||
def receive_ase_dialog(client_entry: HSAEngineClient, namespace_entry: Namespace, data, init_data):
|
def receive_ase_dialog(client_entry: HSAEngineClient, namespace_entry: Namespace, data, init_data):
|
||||||
print("receive_ase_dialog", data)
|
print("receive_ase_dialog", data)
|
||||||
namespace_entry.emit('message', data['data'], room=init_data['room'], namespace='/agent')
|
namespace_entry.emit('message', data['data'], room=init_data['room'], namespace='/agent')
|
||||||
|
|
||||||
|
#接受语音信息
|
||||||
|
def receive_ase_voice_out(client_entry: HSAEngineClient, namespace_entry: Namespace, data, init_data):
|
||||||
|
print("receive_ase_voice_out", data)
|
||||||
|
namespace_entry.emit('VoiceMessage', data, room=init_data['room'], namespace='/agent')
|
||||||
|
|
||||||
def receive_ase_judge(client_entry: HSAEngineClient, namespace_entry: Namespace, data, init_data):
|
def receive_ase_judge(client_entry: HSAEngineClient, namespace_entry: Namespace, data, init_data):
|
||||||
print("receive_ase_judge", data)
|
print("receive_ase_judge", data)
|
||||||
namespace_entry.emit('request_function', data, room=init_data['room'], namespace='/agent') # data 是一个列表 /dict也支持
|
namespace_entry.emit('request_function', data, room=init_data['room'], namespace='/agent') # data 是一个列表 /dict也支持
|
||||||
|
|||||||
@@ -1,30 +1,52 @@
|
|||||||
|
|
||||||
var socket;
|
var socket;
|
||||||
let system_message_idx=0
|
let system_message_idx = 0
|
||||||
document.addEventListener('DOMContentLoaded', function() {
|
document.addEventListener('DOMContentLoaded', function () {
|
||||||
data = window.appData;
|
data = window.appData;
|
||||||
console.log(data);
|
console.log(data);
|
||||||
socket = io('wss://hsamooc.com/agent',{
|
socket = io('wss://hsamooc.com/agent', {
|
||||||
query:{
|
query: {
|
||||||
user_uuid:data.user_uuid,
|
user_uuid: data.user_uuid,
|
||||||
folder:data.folder
|
folder: data.folder
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
socket.on('connect', function() {
|
socket.on('connect', function () {
|
||||||
console.log('Connected to server');
|
console.log('Connected to server');
|
||||||
socket.emit('login',JSON.stringify(data));
|
socket.emit('login', JSON.stringify(data));
|
||||||
OpenIframe();
|
OpenIframe();
|
||||||
});
|
});
|
||||||
// 监听来自服务器的消息
|
// 监听来自服务器的消息
|
||||||
socket.on('message', function(data) {
|
socket.on('message', function (data) {
|
||||||
// 显示服务器的回复消息
|
// 显示服务器的回复消息
|
||||||
const serverMessage = document.createElement('div');
|
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').appendChild(serverMessage);
|
||||||
// 滚动到最新消息
|
// 滚动到最新消息
|
||||||
document.getElementById('chatbox').scrollTop = document.getElementById('chatbox').scrollHeight;
|
document.getElementById('chatbox').scrollTop = document.getElementById('chatbox').scrollHeight;
|
||||||
});
|
});
|
||||||
socket.on('request_function', function(data){
|
|
||||||
|
//监听来自服务器的消息
|
||||||
|
/*socket.on('VoiceMessage', function(data) {
|
||||||
|
console.log("Received voice answer message:", data);
|
||||||
|
// 显示服务器的回复消息
|
||||||
|
const VoiceMessage = document.createElement('div');
|
||||||
|
VoiceMessage.className = 'chat-message server-message';
|
||||||
|
VoiceMessage.innerHTML = `
|
||||||
|
<div><b>华实君:</b></div>
|
||||||
|
${marked.parse(data)}
|
||||||
|
`;
|
||||||
|
//插入与滚动
|
||||||
|
document.getElementById('chatbox').appendChild(VoiceMessage);
|
||||||
|
document.getElementById('chatbox').scrollTop = document.getElementById('chatbox').scrollHeight;
|
||||||
|
});*/
|
||||||
|
//监听来自服务器的语音识别消息
|
||||||
|
socket.on('VoiceMessage', function (data) {
|
||||||
|
console.log("Received voice message I sent from server:", data);
|
||||||
|
//将语音转文字显示在输入框中
|
||||||
|
document.getElementById('messageInput').value = data;
|
||||||
|
});
|
||||||
|
|
||||||
|
socket.on('request_function', function (data) {
|
||||||
try {
|
try {
|
||||||
console.log("request_function", data);
|
console.log("request_function", data);
|
||||||
if (typeof data === 'string') {
|
if (typeof data === 'string') {
|
||||||
@@ -43,18 +65,18 @@ document.addEventListener('DOMContentLoaded', function() {
|
|||||||
approveButton.innerHTML = '√'; // 使用勾选符号
|
approveButton.innerHTML = '√'; // 使用勾选符号
|
||||||
approveButton.data = element;
|
approveButton.data = element;
|
||||||
approveButton.className = 'approve-button';
|
approveButton.className = 'approve-button';
|
||||||
approveButton.onclick = function(event) {
|
approveButton.onclick = function (event) {
|
||||||
console.log(element);
|
console.log(element);
|
||||||
socket.emit('message', JSON.stringify({ type: 'function', data: element }));
|
socket.emit('message', JSON.stringify({ type: 'function', data: element }));
|
||||||
event.currentTarget.disabled = true;
|
event.currentTarget.disabled = true;
|
||||||
event.currentTarget.classList.add('disabled');
|
event.currentTarget.classList.add('disabled');
|
||||||
};
|
};
|
||||||
requestMessage.appendChild(description);
|
requestMessage.appendChild(description);
|
||||||
requestMessage.appendChild(approveButton);
|
requestMessage.appendChild(approveButton);
|
||||||
|
|
||||||
document.getElementById('chatbox').appendChild(requestMessage);
|
document.getElementById('chatbox').appendChild(requestMessage);
|
||||||
document.getElementById('chatbox').scrollTop = document.getElementById('chatbox').scrollHeight;
|
document.getElementById('chatbox').scrollTop = document.getElementById('chatbox').scrollHeight;
|
||||||
}
|
}
|
||||||
// for (let i = 0; i < data.length; i++) {
|
// for (let i = 0; i < data.length; i++) {
|
||||||
// const element = data[i]
|
// const element = data[i]
|
||||||
// const requestMessage = document.createElement('div');
|
// const requestMessage = document.createElement('div');
|
||||||
@@ -82,11 +104,11 @@ document.addEventListener('DOMContentLoaded', function() {
|
|||||||
console.log(error)
|
console.log(error)
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
socket.on('next_chapter', function(data){
|
socket.on('next_chapter', function (data) {
|
||||||
next_chapter(data);
|
next_chapter(data);
|
||||||
});
|
});
|
||||||
socket.on('chapter_score',function(data){
|
socket.on('chapter_score', function (data) {
|
||||||
if (typeof data === 'string'){
|
if (typeof data === 'string') {
|
||||||
data = JSON.parse(data);
|
data = JSON.parse(data);
|
||||||
}
|
}
|
||||||
console.log(data)
|
console.log(data)
|
||||||
@@ -96,7 +118,7 @@ document.addEventListener('DOMContentLoaded', function() {
|
|||||||
document.getElementById('chatbox').appendChild(scoreMessage);
|
document.getElementById('chatbox').appendChild(scoreMessage);
|
||||||
document.getElementById('chatbox').scrollTop = document.getElementById('chatbox').scrollHeight;
|
document.getElementById('chatbox').scrollTop = document.getElementById('chatbox').scrollHeight;
|
||||||
})
|
})
|
||||||
socket.on('system_message',function(data){
|
socket.on('system_message', function (data) {
|
||||||
console.log(data)
|
console.log(data)
|
||||||
let systemMessage = document.createElement('div');
|
let systemMessage = document.createElement('div');
|
||||||
systemMessage.className = 'chat-message server-message';
|
systemMessage.className = 'chat-message server-message';
|
||||||
@@ -107,11 +129,11 @@ document.addEventListener('DOMContentLoaded', function() {
|
|||||||
|
|
||||||
systemMessage.appendChild(closeButton);
|
systemMessage.appendChild(closeButton);
|
||||||
systemMessage.innerHTML += `<b>系统提示: </b> ${data}`;
|
systemMessage.innerHTML += `<b>系统提示: </b> ${data}`;
|
||||||
systemMessage.id = 'system_message_'+system_message_idx;
|
systemMessage.id = 'system_message_' + system_message_idx;
|
||||||
system_message_idx+=1
|
system_message_idx += 1
|
||||||
document.getElementById('chatbox').appendChild(systemMessage);
|
document.getElementById('chatbox').appendChild(systemMessage);
|
||||||
document.getElementById('chatbox').scrollTop = document.getElementById('chatbox').scrollHeight;
|
document.getElementById('chatbox').scrollTop = document.getElementById('chatbox').scrollHeight;
|
||||||
$('.close-button').on('click', function(event) {
|
$('.close-button').on('click', function (event) {
|
||||||
$(this).css({
|
$(this).css({
|
||||||
'background-color': 'gray',
|
'background-color': 'gray',
|
||||||
'color': 'white', // 可选:设置文字颜色为白色,以便更清晰地显示
|
'color': 'white', // 可选:设置文字颜色为白色,以便更清晰地显示
|
||||||
@@ -119,7 +141,7 @@ document.addEventListener('DOMContentLoaded', function() {
|
|||||||
});
|
});
|
||||||
$(this).parent().remove();
|
$(this).parent().remove();
|
||||||
});
|
});
|
||||||
setTimeout(function() {
|
setTimeout(function () {
|
||||||
$(systemMessage.id).remove();
|
$(systemMessage.id).remove();
|
||||||
}, 5000); // 5000 毫秒 = 5 秒
|
}, 5000); // 5000 毫秒 = 5 秒
|
||||||
})
|
})
|
||||||
@@ -130,7 +152,7 @@ document.addEventListener('DOMContentLoaded', function() {
|
|||||||
const fontSizeSlider = document.getElementById('fontSizeSlider');
|
const fontSizeSlider = document.getElementById('fontSizeSlider');
|
||||||
const chatbox = document.getElementById('chatbox');
|
const chatbox = document.getElementById('chatbox');
|
||||||
|
|
||||||
fontSizeSlider.addEventListener('input', function() {
|
fontSizeSlider.addEventListener('input', function () {
|
||||||
console.log(fontSizeSlider.value);
|
console.log(fontSizeSlider.value);
|
||||||
chatbox.style.fontSize = fontSizeSlider.value + 'px';
|
chatbox.style.fontSize = fontSizeSlider.value + 'px';
|
||||||
});
|
});
|
||||||
@@ -144,27 +166,27 @@ const chineseLabel = document.getElementById('label_for_zh');
|
|||||||
let language = 'zh'; // 默认语言
|
let language = 'zh'; // 默认语言
|
||||||
|
|
||||||
// 切换语言事件
|
// 切换语言事件
|
||||||
englishRadio.addEventListener('change', function() {
|
englishRadio.addEventListener('change', function () {
|
||||||
if (englishRadio.checked) {
|
if (englishRadio.checked) {
|
||||||
language = 'en';
|
language = 'en';
|
||||||
englishLabel.classList.add('active');
|
englishLabel.classList.add('active');
|
||||||
chineseLabel.classList.remove('active');
|
chineseLabel.classList.remove('active');
|
||||||
}
|
}
|
||||||
socket.emit('language',language);
|
socket.emit('language', language);
|
||||||
});
|
});
|
||||||
|
|
||||||
chineseRadio.addEventListener('change', function() {
|
chineseRadio.addEventListener('change', function () {
|
||||||
if (chineseRadio.checked) {
|
if (chineseRadio.checked) {
|
||||||
language = 'zh';
|
language = 'zh';
|
||||||
chineseLabel.classList.add('active');
|
chineseLabel.classList.add('active');
|
||||||
englishLabel.classList.remove('active');
|
englishLabel.classList.remove('active');
|
||||||
}
|
}
|
||||||
socket.emit('language',language);
|
socket.emit('language', language);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
||||||
function sendInitiativeFunctionCall(function_name){
|
function sendInitiativeFunctionCall(function_name) {
|
||||||
socket.emit('initiative', {'name': function_name});
|
socket.emit('initiative', { 'name': function_name });
|
||||||
}
|
}
|
||||||
// 发送消息
|
// 发送消息
|
||||||
function sendMessage() {
|
function sendMessage() {
|
||||||
@@ -180,7 +202,7 @@ function sendMessage() {
|
|||||||
document.getElementById('chatbox').appendChild(userMessage);
|
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 = '';
|
document.getElementById('messageInput').value = '';
|
||||||
@@ -189,6 +211,107 @@ function sendMessage() {
|
|||||||
document.getElementById('chatbox').scrollTop = document.getElementById('chatbox').scrollHeight;
|
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();
|
||||||
|
}
|
||||||
|
//发送语音
|
||||||
|
function sendVoiceMessage() {
|
||||||
|
p = pyaudio.PyAudio()
|
||||||
|
//变换录音中图标
|
||||||
|
document.getElementById('recordIcon').className = 'bi bi-record-circle';
|
||||||
|
//监听
|
||||||
|
document.addEventListener('click', handleMouseClick);
|
||||||
|
//打开麦克风流
|
||||||
|
voice_stream = p.open(format = FORMAT,
|
||||||
|
channels = CHANNELS,
|
||||||
|
rate = SAMPLE_RATE,
|
||||||
|
input = True,
|
||||||
|
frames_per_buffer = CHUNK_SIZE)
|
||||||
|
audio_chunks = []
|
||||||
|
|
||||||
|
recordingInterval = setInterval(() => {
|
||||||
|
if (!isRecording) {
|
||||||
|
clearInterval(recordingInterval);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
// 从麦克风读取音频数据
|
||||||
|
let audio_data = voice_stream.read(CHUNK_SIZE);
|
||||||
|
|
||||||
|
if (!audio_data || audio_data.length === 0) {
|
||||||
|
stopRecording();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 保留原始 PCM 二进制数据
|
||||||
|
audio_chunks.push(audio_data);
|
||||||
|
|
||||||
|
// 当积累到一定数量的块时,发送
|
||||||
|
if (audio_chunks.length >= 16) {
|
||||||
|
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 = [];
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Error reading audio data:', error);
|
||||||
|
stopRecording();
|
||||||
|
}
|
||||||
|
}, 10); // 每 10 毫秒检查一次录音状态
|
||||||
|
}
|
||||||
|
|
||||||
|
//停止录音
|
||||||
|
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', JSON.stringify({ data: merged_data, type: 'audio' }));
|
||||||
|
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 currentChapterIndex = -1;
|
let currentChapterIndex = -1;
|
||||||
function next_chapter(data) {
|
function next_chapter(data) {
|
||||||
// 获取所有的 h3 元素
|
// 获取所有的 h3 元素
|
||||||
@@ -261,7 +384,7 @@ function generateProgressBar(N, idx, titles) {
|
|||||||
titleBox.style.width = `${nodeWidth}%`;
|
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.innerHTML = `Step ${i + 1}: ${titles[i] || "No title"}`;// - Additional details here...`;
|
||||||
detailBox.style.display = 'block';
|
detailBox.style.display = 'block';
|
||||||
// 根据进度条位置显示详情
|
// 根据进度条位置显示详情
|
||||||
@@ -271,7 +394,7 @@ function generateProgressBar(N, idx, titles) {
|
|||||||
});
|
});
|
||||||
|
|
||||||
// 监听鼠标移出事件来隐藏详情
|
// 监听鼠标移出事件来隐藏详情
|
||||||
titleBox.addEventListener('mouseleave', function() {
|
titleBox.addEventListener('mouseleave', function () {
|
||||||
detailBox.style.display = 'none';
|
detailBox.style.display = 'none';
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
<!DOCTYPE html>
|
<!DOCTYPE html>
|
||||||
<html lang="en">
|
<html lang="en">
|
||||||
|
|
||||||
<head>
|
<head>
|
||||||
<meta charset="UTF-8">
|
<meta charset="UTF-8">
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||||
@@ -17,30 +18,32 @@
|
|||||||
<script src="https://unpkg.com/split.js/dist/split.min.js"></script>
|
<script src="https://unpkg.com/split.js/dist/split.min.js"></script>
|
||||||
<script src="https://cdn.bootcdn.net/ajax/libs/jquery/3.7.1/jquery.min.js"></script>
|
<script src="https://cdn.bootcdn.net/ajax/libs/jquery/3.7.1/jquery.min.js"></script>
|
||||||
</head>
|
</head>
|
||||||
|
|
||||||
<body>
|
<body>
|
||||||
<div class="maxcontainer" id="maxcontainer" style="height: 100%;">
|
<div class="maxcontainer" id="maxcontainer" style="height: 100%;">
|
||||||
<!-- 左侧内容 -->
|
<!-- 左侧内容 -->
|
||||||
<div class="sidebar" id="leftSidebar" style="width: 100%;height: 100%;">
|
<div class="sidebar" id="leftSidebar" style="width: 100%;height: 100%;">
|
||||||
<div id="markdown-content"style="width: 100%;height: 93%;">
|
<div id="markdown-content" style="width: 100%;height: 93%;">
|
||||||
<!-- 动态加载的 HTML 会显示在这里 -->
|
<!-- 动态加载的 HTML 会显示在这里 -->
|
||||||
</div>
|
</div>
|
||||||
<div id="markdown-content-process"style="width: 100%;height: 5%;">
|
<div id="markdown-content-process" style="width: 100%;height: 5%;">
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
<div id="progress-detail" style="position: absolute; top: 0; left: 0; display: none; padding: 10px; background-color: rgba(0, 0, 0, 0.7); color: white; border-radius: 5px;">
|
<div id="progress-detail"
|
||||||
|
style="position: absolute; top: 0; left: 0; display: none; padding: 10px; background-color: rgba(0, 0, 0, 0.7); color: white; border-radius: 5px;">
|
||||||
<!-- 这里将显示鼠标悬停的详情 -->
|
<!-- 这里将显示鼠标悬停的详情 -->
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div id="dragbar" class="resizer"></div>
|
<div id="dragbar" class="resizer"></div>
|
||||||
|
|
||||||
<!-- 中间嵌入的 Vscode-web 窗口,带有 tkn 参数 -->
|
<!-- 中间嵌入的 Vscode-web 窗口,带有 tkn 参数 -->
|
||||||
<div class="vscode-web"id="vscodeWeb" >
|
<div class="vscode-web" id="vscodeWeb">
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|
||||||
<div id="dragbar2" class="resizer"></div>
|
<div id="dragbar2" class="resizer"></div>
|
||||||
<!-- 右侧内容 -->
|
<!-- 右侧内容 -->
|
||||||
<div class="sidebar" id="rightSidebar"style="width: 100%;height: 100%;">
|
<div class="sidebar" id="rightSidebar" style="width: 100%;height: 100%;">
|
||||||
<div class="chatbox-header">
|
<div class="chatbox-header">
|
||||||
<div class="slider-container">
|
<div class="slider-container">
|
||||||
<label for="fontSizeSlider">Aa</label>
|
<label for="fontSizeSlider">Aa</label>
|
||||||
@@ -48,7 +51,7 @@
|
|||||||
</div>
|
</div>
|
||||||
<div class="language-toggle btn-group btn-group-toggle" data-toggle="buttons">
|
<div class="language-toggle btn-group btn-group-toggle" data-toggle="buttons">
|
||||||
<label class="btn btn-outline-secondary " id="label_for_en">
|
<label class="btn btn-outline-secondary " id="label_for_en">
|
||||||
<input type="radio" name="language" id="english" autocomplete="off" > En
|
<input type="radio" name="language" id="english" autocomplete="off"> En
|
||||||
</label>
|
</label>
|
||||||
<label class="btn btn-outline-secondary active" id="label_for_zh">
|
<label class="btn btn-outline-secondary active" id="label_for_zh">
|
||||||
<input type="radio" name="language" id="chinese" autocomplete="off" checked> Zn
|
<input type="radio" name="language" id="chinese" autocomplete="off" checked> Zn
|
||||||
@@ -56,21 +59,28 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="chatbox" id="chatbox" >
|
<div class="chatbox" id="chatbox">
|
||||||
<!-- 消息会在这里显示 -->
|
<!-- 消息会在这里显示 -->
|
||||||
</div>
|
</div>
|
||||||
<div class="input-area">
|
<div class="input-area">
|
||||||
<textarea id="messageInput" rows="2" class="form-control" placeholder="输入你的消息 (支持 Markdown 语法)"></textarea>
|
<textarea id="messageInput" rows="2" class="form-control"
|
||||||
<button class="btn btn-primary" onclick="sendMessage()">发送</button>
|
placeholder="输入你的消息 (支持 Markdown 语法)"></textarea>
|
||||||
|
<!--文本与语音的发送-->
|
||||||
|
<button class="btn btn-primary" onclick="sendMessage()">
|
||||||
|
<i class="bi bi-send"></i>
|
||||||
|
</button>
|
||||||
|
<button class="btn btn-secondary" onclick="sendAudioMessage()">
|
||||||
|
<i class="bi bi-mic" id = "recordIcon"></i>
|
||||||
|
</button>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<script type="text/javascript">
|
<script type="text/javascript">
|
||||||
window.appData = {
|
window.appData = {
|
||||||
user_uuid:"{{user_uuid}}",
|
user_uuid: "{{user_uuid}}",
|
||||||
course_id:"{{course_id}}",
|
course_id: "{{course_id}}",
|
||||||
lesson_name:"{{lesson_name}}",
|
lesson_name: "{{lesson_name}}",
|
||||||
chapter_name:"{{chapter_name}}",
|
chapter_name: "{{chapter_name}}",
|
||||||
folder:"{{workspace_path}}"
|
folder: "{{workspace_path}}"
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
</div>
|
</div>
|
||||||
@@ -117,21 +127,21 @@
|
|||||||
let isDraggingRight = false;
|
let isDraggingRight = false;
|
||||||
|
|
||||||
// 当鼠标按下时,禁用 iframe 的鼠标事件
|
// 当鼠标按下时,禁用 iframe 的鼠标事件
|
||||||
dragbar.addEventListener('mousedown', function(e) {
|
dragbar.addEventListener('mousedown', function (e) {
|
||||||
isDraggingLeft = true;
|
isDraggingLeft = true;
|
||||||
document.querySelectorAll('iframe').forEach(function(iframe) {
|
document.querySelectorAll('iframe').forEach(function (iframe) {
|
||||||
iframe.style.pointerEvents = 'none'; // 禁止 iframe 捕获鼠标事件
|
iframe.style.pointerEvents = 'none'; // 禁止 iframe 捕获鼠标事件
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
dragbar2.addEventListener('mousedown', function(e) {
|
dragbar2.addEventListener('mousedown', function (e) {
|
||||||
isDraggingRight = true;
|
isDraggingRight = true;
|
||||||
document.querySelectorAll('iframe').forEach(function(iframe) {
|
document.querySelectorAll('iframe').forEach(function (iframe) {
|
||||||
iframe.style.pointerEvents = 'none'; // 禁止 iframe 捕获鼠标事件
|
iframe.style.pointerEvents = 'none'; // 禁止 iframe 捕获鼠标事件
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
document.addEventListener('mousemove', function(e) {
|
document.addEventListener('mousemove', function (e) {
|
||||||
if (isDraggingLeft) {
|
if (isDraggingLeft) {
|
||||||
leftWidth = e.clientX; // 更新左侧栏宽度
|
leftWidth = e.clientX; // 更新左侧栏宽度
|
||||||
container.style.gridTemplateColumns = `${leftWidth}px 5px 2fr 5px ${rightWidth}px`;
|
container.style.gridTemplateColumns = `${leftWidth}px 5px 2fr 5px ${rightWidth}px`;
|
||||||
@@ -141,10 +151,10 @@
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
document.addEventListener('mouseup', function() {
|
document.addEventListener('mouseup', function () {
|
||||||
isDraggingLeft = false;
|
isDraggingLeft = false;
|
||||||
isDraggingRight = false;
|
isDraggingRight = false;
|
||||||
document.querySelectorAll('iframe').forEach(function(iframe) {
|
document.querySelectorAll('iframe').forEach(function (iframe) {
|
||||||
iframe.style.pointerEvents = 'auto'; // 恢复 iframe 的鼠标事件
|
iframe.style.pointerEvents = 'auto'; // 恢复 iframe 的鼠标事件
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
@@ -158,26 +168,26 @@
|
|||||||
method: 'GET',
|
method: 'GET',
|
||||||
credentials: 'include' // 包含 Cookie,确保 Flask 可以识别 session
|
credentials: 'include' // 包含 Cookie,确保 Flask 可以识别 session
|
||||||
})
|
})
|
||||||
.then(response => {
|
.then(response => {
|
||||||
if (response.ok) return response.json();
|
if (response.ok) return response.json();
|
||||||
throw new Error('Failed to fetch session');
|
throw new Error('Failed to fetch session');
|
||||||
})
|
})
|
||||||
.then(data => {
|
.then(data => {
|
||||||
console.log('Session data:', data);
|
console.log('Session data:', data);
|
||||||
|
|
||||||
// 在成功获取 session 后创建并插入 iframe
|
// 在成功获取 session 后创建并插入 iframe
|
||||||
const iframe = document.createElement('iframe');
|
const iframe = document.createElement('iframe');
|
||||||
const sessionInfo = data
|
const sessionInfo = data
|
||||||
iframe.src = '{{vscode_web_url}}/?workspace={{workspace_path}}&folder={{workspace_path}}';
|
iframe.src = '{{vscode_web_url}}/?workspace={{workspace_path}}&folder={{workspace_path}}';
|
||||||
iframe.style.width = '100%';
|
iframe.style.width = '100%';
|
||||||
iframe.style.height = '99%';
|
iframe.style.height = '99%';
|
||||||
|
|
||||||
// 将 iframe 插入到指定的 div 中
|
// 将 iframe 插入到指定的 div 中
|
||||||
document.getElementById('vscodeWeb').appendChild(iframe);
|
document.getElementById('vscodeWeb').appendChild(iframe);
|
||||||
})
|
})
|
||||||
.catch(error => {
|
.catch(error => {
|
||||||
console.error('Error fetching session:', error);
|
console.error('Error fetching session:', error);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
@@ -187,45 +197,46 @@
|
|||||||
|
|
||||||
|
|
||||||
|
|
||||||
// 动态加载 Markdown 文件
|
// 动态加载 Markdown 文件
|
||||||
function loadMarkdown(course_id, chapter_name, lesson_name) {
|
function loadMarkdown(course_id, chapter_name, lesson_name) {
|
||||||
fetch(`/${course_id}-${chapter_name}-${lesson_name}-markdown`)
|
fetch(`/${course_id}-${chapter_name}-${lesson_name}-markdown`)
|
||||||
.then(response => response.json())
|
.then(response => response.json())
|
||||||
.then(data => {
|
.then(data => {
|
||||||
if (data.html_url) {
|
if (data.html_url) {
|
||||||
document.getElementById('markdown-content').innerHTML = `<iframe id="markdown-content-iframe" src="${data.html_url}"style="width: 100%;height: 100%;"></iframe>`;
|
document.getElementById('markdown-content').innerHTML = `<iframe id="markdown-content-iframe" src="${data.html_url}"style="width: 100%;height: 100%;"></iframe>`;
|
||||||
document.getElementById('markdown-content-iframe').addEventListener('load', function() {
|
document.getElementById('markdown-content-iframe').addEventListener('load', function () {
|
||||||
// 在这里执行你想要的操作
|
// 在这里执行你想要的操作
|
||||||
console.log('Iframe has finished loading');
|
console.log('Iframe has finished loading');
|
||||||
next_chapter();
|
next_chapter();
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
console.error('Markdown file not found');
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.catch(error => {
|
||||||
|
console.error('Error loading markdown:', error);
|
||||||
});
|
});
|
||||||
} else {
|
}
|
||||||
console.error('Markdown file not found');
|
document.addEventListener('DOMContentLoaded', function () {
|
||||||
}
|
loadMarkdown('{{course_id}}', '{{chapter_name}}', '{{lesson_name}}');
|
||||||
})
|
|
||||||
.catch(error => {
|
|
||||||
console.error('Error loading markdown:', error);
|
|
||||||
});
|
});
|
||||||
}
|
|
||||||
document.addEventListener('DOMContentLoaded', function() {
|
|
||||||
loadMarkdown('{{course_id}}','{{chapter_name}}','{{lesson_name}}');
|
|
||||||
});
|
|
||||||
|
|
||||||
|
|
||||||
// 侧边工具栏切换
|
// 侧边工具栏切换
|
||||||
const sidebarTools = document.getElementById('sidebarTools');
|
const sidebarTools = document.getElementById('sidebarTools');
|
||||||
const toggleButton = document.getElementById('toggleButton');
|
const toggleButton = document.getElementById('toggleButton');
|
||||||
|
|
||||||
toggleButton.addEventListener('click', function() {
|
toggleButton.addEventListener('click', function () {
|
||||||
sidebarTools.classList.toggle('open');
|
sidebarTools.classList.toggle('open');
|
||||||
toggleButton.classList.toggle('open');
|
toggleButton.classList.toggle('open');
|
||||||
if (document.getElementById('toggleButton_icon').innerText === '❮'){
|
if (document.getElementById('toggleButton_icon').innerText === '❮') {
|
||||||
document.getElementById('toggleButton_icon').innerText = '❯';
|
document.getElementById('toggleButton_icon').innerText = '❯';
|
||||||
}else{
|
} else {
|
||||||
document.getElementById('toggleButton_icon').innerText = '❮';
|
document.getElementById('toggleButton_icon').innerText = '❮';
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
</script>
|
</script>
|
||||||
</body>
|
</body>
|
||||||
|
|
||||||
</html>
|
</html>
|
||||||
1
study/cake/68bacdfadf5aeae0912f7f18/基础算法/.config
Normal file
1
study/cake/68bacdfadf5aeae0912f7f18/基础算法/.config
Normal file
@@ -0,0 +1 @@
|
|||||||
|
{"user_uuid": "user_4e35d980-622b-49e0-ad29-3a4092274f9d", "course_id": "68bacdfadf5aeae0912f7f18", "chapter_name": "基础算法", "lesson_name": "二分", "path": "E:/agent/huashi/code-agent/study/cake/68bacdfadf5aeae0912f7f18/基础算法"}
|
||||||
Reference in New Issue
Block a user