diff --git a/Html/apps/config.py b/Html/apps/config.py
index a06fb17..fe33684 100644
--- a/Html/apps/config.py
+++ b/Html/apps/config.py
@@ -48,4 +48,5 @@ class Config:
}
ASE_ENGINE_URL = GLOBAL_CONFIG['ASE_ENGINE']['url']
+ ASE_ENGINE_URL_TOKEN = GLOBAL_CONFIG['ASE_ENGINE']['url_token']
ASE_ENGINE_NAMESPACE = GLOBAL_CONFIG['ASE_ENGINE']['namespace']
\ No newline at end of file
diff --git a/Html/apps/services/course_service.py b/Html/apps/services/course_service.py
index 108b5ec..5780a56 100644
--- a/Html/apps/services/course_service.py
+++ b/Html/apps/services/course_service.py
@@ -1,6 +1,8 @@
# myapp/services/course_service.py
import re
-from typing import Dict, List
+import requests
+import json
+from typing import Dict, List, Optional
from flask import current_app
from urllib.parse import urlparse
from datetime import datetime
@@ -450,10 +452,10 @@ def _default_markdowns(lesson_name: str):
## 阶段一:示例阶段
### 步骤A
-请基于“步骤A”的教学目标与材料,生成指导性提示词(尽量结构化,给出评估标准和示例)。
+请基于"步骤A"的教学目标与材料,生成指导性提示词(尽量结构化,给出评估标准和示例)。
### 步骤B
-请基于“步骤B”的教学目标与材料,生成指导性提示词。
+请基于"步骤B"的教学目标与材料,生成指导性提示词。
"""
score_prompt_md = f"""# {lesson_name}
@@ -463,8 +465,134 @@ def _default_markdowns(lesson_name: str):
## 阶段一:示例阶段
### 步骤A
-对学员在“步骤A”的输出进行评分与文字反馈,分数区间 0-100,并输出 JSON:```{{"score": , "reasons": "", "advices": ""}}```
+对学员在"步骤A"的输出进行评分与文字反馈,分数区间 0-100,并输出 JSON:```{{"score": , "reasons": "", "advices": ""}}```
### 步骤B
-同上,对“步骤B”输出进行评分与反馈。
+同上,对"步骤B"输出进行评分与反馈。
"""
return lesson_md, prompt_md, score_prompt_md
+
+def save_learning_progress(user_uuid: str, chatmanager, user_id: str) -> bool:
+ """
+ 保存用户学习进度到MongoDB
+
+ 参数:
+ - user_uuid: 用户UUID
+ - chatmanager: ChatManager实例,包含学习进度数据
+ - user_id: 用户ID
+
+ 返回:
+ - bool: 保存是否成功
+ """
+ try:
+ # 获取MongoDB连接
+ mongo = current_app.extensions["mongo"]
+
+ # 获取MultiAgents框架中的对话列表
+ dialog_list = get_multiagents_dialog_list(user_id)
+
+ # 构建学习进度数据
+ progress_data = {
+ "user_uuid": user_uuid,
+ "user_id": user_id,
+ "material_id": chatmanager.material_id,
+ "chapter_name": chatmanager.chapter_name,
+ "lesson_name": chatmanager.lesson_name,
+ "chapter_chain_now": chatmanager.chapter_chain_now,
+ "chat_historys": chatmanager.chat_historys,
+ "scores": chatmanager.scores,
+ "multiagents_dialogs": dialog_list,
+ "updated_at": datetime.now()
+ }
+
+ # 保存到MongoDB,使用upsert操作(如果存在则更新,不存在则插入)
+ mongo.db.learning_progress.update_one(
+ {
+ "user_uuid": user_uuid,
+ "material_id": chatmanager.material_id,
+ "chapter_name": chatmanager.chapter_name,
+ "lesson_name": chatmanager.lesson_name
+ },
+ {"$set": progress_data},
+ upsert=True
+ )
+
+ current_app.logger.info(f"学习进度保存成功: user_uuid={user_uuid}, lesson={chatmanager.lesson_name}")
+ return True
+ except Exception as e:
+ current_app.logger.error(f"保存学习进度失败: {str(e)}")
+ return False
+
+def get_multiagents_dialog_list(user_id: str) -> List[Dict]:
+ """
+ 从MultiAgents框架获取用户对话列表
+
+ 参数:
+ - user_id: 用户ID
+
+ 返回:
+ - List[Dict]: 对话列表
+ """
+ try:
+ # 从配置中获取ASE引擎URL和token
+ config = current_app.config
+ url_token = config.get("ASE_ENGINE_URL_TOKEN", "")
+
+ # 构建请求URL
+ base_url = config.get("ASE_ENGINE_URL", "")
+ api_url = f"{base_url}/api/sync/download"
+
+ # 构建请求参数
+ payload = {
+ "namespace_url": url_token, # 使用配置中的token作为namespace_url
+ "user_id": user_id
+ }
+
+ # 发送POST请求
+ response = requests.post(
+ api_url,
+ json=payload,
+ headers={"Content-Type": "application/json"},
+ timeout=10
+ )
+
+ # 检查响应
+ if response.status_code == 200:
+ return response.json()
+ else:
+ current_app.logger.error(f"获取MultiAgents对话列表失败: HTTP {response.status_code}")
+ return []
+ except Exception as e:
+ current_app.logger.error(f"获取MultiAgents对话列表异常: {str(e)}")
+ return []
+
+def load_learning_progress(user_uuid: str, material_id: str, chapter_name: str, lesson_name: str) -> Optional[Dict]:
+ """
+ 加载用户学习进度
+
+ 参数:
+ - user_uuid: 用户UUID
+ - material_id: 课程ID
+ - chapter_name: 章节名
+ - lesson_name: 课时名
+
+ 返回:
+ - Optional[Dict]: 学习进度数据,如果不存在则返回None
+ """
+ try:
+ mongo = current_app.extensions["mongo"]
+ progress = mongo.db.learning_progress.find_one({
+ "user_uuid": user_uuid,
+ "material_id": material_id,
+ "chapter_name": chapter_name,
+ "lesson_name": lesson_name
+ })
+
+ if progress:
+ # 转换ObjectId为字符串
+ if "_id" in progress:
+ progress["_id"] = str(progress["_id"])
+ return progress
+ return None
+ except Exception as e:
+ current_app.logger.error(f"加载学习进度失败: {str(e)}")
+ return None
diff --git a/Html/apps/sockets/namespaces.py b/Html/apps/sockets/namespaces.py
index f31f128..2224ea7 100644
--- a/Html/apps/sockets/namespaces.py
+++ b/Html/apps/sockets/namespaces.py
@@ -7,7 +7,7 @@ from flask_socketio import Namespace, join_room, leave_room, emit
from ..function_tools import judge
from ..function_tools import next_chapter
from ..services.backboard_service import realtime_response
-from ..services.markdown_service import load_full_markdown_file
+from ..services.course_service import load_full_markdown_file, save_learning_progress
from ..extension_ase.ase_client import HSAEngineClient
from ..services.user_service import get_or_load_current_user
@@ -71,8 +71,32 @@ class AgentNamespace(Namespace):
chatmanager = user_uuid2chatmanager[user_uuid]
chatmanager.function_manager.add_function(judge, {'course_id': course_id, 'root_path': f'/home/{user_id}/{course_id}/{chapter_name}/{lesson_name}'})
chatmanager.function_manager.add_function(next_chapter, {'user_uuid': user_uuid})
- chatmanager.chat_historys = []
- chatmanager.scores = []
+
+ # 尝试加载用户学习进度
+ from ..services.course_service import load_learning_progress
+ progress = load_learning_progress(user_uuid, course_id, chapter_name, lesson_name)
+ need_skip_chapters = 0
+
+ if progress:
+ # 恢复学习进度数据
+ if 'scores' in progress:
+ # 根据scores的数量确定需要跳过的章节数
+ need_skip_chapters = len(progress['scores'])
+ chatmanager.scores = progress['scores']
+ print(f"恢复用户学习进度: {user_uuid}, 已完成 {need_skip_chapters} 个章节")
+
+ # 恢复聊天历史
+ if 'chat_historys' in progress:
+ chatmanager.chat_historys = progress['chat_historys']
+ else:
+ chatmanager.chat_historys = []
+
+ # 恢复当前章节链
+ if 'chapter_chain_now' in progress:
+ chatmanager.chapter_chain_now = progress['chapter_chain_now']
+ else:
+ chatmanager.chat_historys = []
+ chatmanager.scores = []
fmd,fmdp,fsp = load_full_markdown_file(course_id, chapter_name, lesson_name)
user_uuid2ase_client[user_uuid] = HSAEngineClient(current_app.config["ASE_ENGINE_URL"], current_app.config["ASE_ENGINE_NAMESPACE"],id=user_id, chatmanager = chatmanager)
@@ -80,6 +104,10 @@ class AgentNamespace(Namespace):
backboard_manager.add_backboard(user_uuid, user_id, course_id, lesson_name, root_path=f'/home/{user_id}/{course_id}/{chapter_name}/{lesson_name}')
chatmanager.bb = backboard_manager.get_backboard(user_uuid)
chatmanager.next_chapter()
+
+ # 如果有保存的进度,需要跳过已经完成的章节
+ for _ in range(need_skip_chapters):
+ chatmanager.next_chapter()
self.chatmanager = chatmanager
self.ase_client = user_uuid2ase_client[user_uuid]
user_uuid2ase_client[user_uuid].register_on_connect_entry(
@@ -221,7 +249,7 @@ class AgentNamespace(Namespace):
if len(chatmanager.scores) >= chatmanager.chapter_chain_now:
chatmanager.scores[chatmanager.chapter_chain_now - 1] = data.get('data', None)
else: chatmanager.scores.append(data.get('data', None))
- user.set_course_progress(chatmanager.chat_historys, chatmanager.scores, chatmanager.material_id, chatmanager.chapter_name, chatmanager.lesson_name)
+ user.set_course_progress(chatmanager.chat_historys, chatmanager.scores, chatmanager.course_id, chatmanager.chapter_name, chatmanager.lesson_name)
chatmanager.load_next_chapter()
time.sleep(0.5)
chatmanager.ase_client.send_text("chapter-start", "")
@@ -241,10 +269,32 @@ class AgentNamespace(Namespace):
def on_disconnect(self,data):
+ print(f'AgentNamespace client disconnected, session user_uuid: {session.get("user_uuid", "unknown")}')
ex = current_app.extensions
uuid = session.get('user_uuid')
- user_uuid2chatmanager = ex["user_uuid2chatmanager"]
- user_uuid2chatmanager[uuid].disconnect(uuid)
+ user_uuid2chatmanager = ex.get("user_uuid2chatmanager", {})
+
+ # 保存学习进度到MongoDB
+ try:
+ # 获取用户ID
+ uuid2username = ex.get("uuid2username", {})
+ user_id = uuid2username.get(uuid, "unknown")
+
+ # 调用保存学习进度功能
+ chatmanager = user_uuid2chatmanager.get(uuid)
+ if chatmanager:
+ save_result = save_learning_progress(uuid, chatmanager, user_id)
+ print(f"保存学习进度结果: {save_result}, user_uuid={uuid}")
+ except Exception as e:
+ print(f"保存学习进度时发生异常: {str(e)}")
+
+ # 执行原有的断开连接逻辑
+ if uuid in user_uuid2chatmanager:
+ user_uuid2chatmanager[uuid].disconnect(uuid)
+
+ # 离开房间
+ if uuid:
+ leave_room(uuid, namespace='/agent')
print("VSCode client disconnected")
print("Disconnect reason:"+str(data))
\ No newline at end of file
diff --git a/Html/apps/static/js/chatbox.js b/Html/apps/static/js/chatbox.js
index 8c6dc34..7ba5155 100644
--- a/Html/apps/static/js/chatbox.js
+++ b/Html/apps/static/js/chatbox.js
@@ -105,29 +105,7 @@ document.addEventListener('DOMContentLoaded', function () {
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 = `Function: ${element.name}(${JSON.stringify(element.arguments)})
`;
- // requestMessage.appendChild(approveButton);
- // document.getElementById('chatbox').appendChild(requestMessage);
- // document.getElementById('chatbox').scrollTop = document.getElementById('chatbox').scrollHeight;
- // }
+
} catch (error) {
console.log(error)
}
@@ -250,124 +228,9 @@ 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;
-// }
-// }
+
+
//语音这一块//
diff --git a/Html/config.ini b/Html/config.ini
index 5ad9972..8efda60 100644
--- a/Html/config.ini
+++ b/Html/config.ini
@@ -29,3 +29,4 @@ region = ap-guangzhou
[ASE_ENGINE]
url = https://asengine.net
namespace = /socket.io/7100eebf7a909e1ee3f4e06766f512f0358ecb6ac51d5434c17d31dd70890dc1
+url_token = 7100eebf7a909e1ee3f4e06766f512f0358ecb6ac51d5434c17d31dd70890dc1