2 Commits

Author SHA1 Message Date
CakeCN
ede89cef53 fix load_full_markdown_file import bug 2025-11-18 22:15:45 +08:00
CakeCN
0fd548edcd save_load_learned 2025-11-17 23:14:55 +08:00
5 changed files with 195 additions and 151 deletions

View File

@@ -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']

View File

@@ -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": <int>, "reasons": "<string>", "advices": "<string>"}}```
对学员在"步骤A"的输出进行评分与文字反馈,分数区间 0-100并输出 JSON```{{"score": <int>, "reasons": "<string>", "advices": "<string>"}}```
### 步骤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

View File

@@ -7,7 +7,8 @@ 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.markdown_service import load_full_markdown_file
from ..services.course_service import save_learning_progress
from ..extension_ase.ase_client import HSAEngineClient
from ..services.user_service import get_or_load_current_user
@@ -71,8 +72,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 +105,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 +250,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 +270,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))

View File

@@ -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 = `<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)
}
@@ -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;
// }
// }
//语音这一块//

View File

@@ -29,3 +29,4 @@ region = ap-guangzhou
[ASE_ENGINE]
url = https://asengine.net
namespace = /socket.io/7100eebf7a909e1ee3f4e06766f512f0358ecb6ac51d5434c17d31dd70890dc1
url_token = 7100eebf7a909e1ee3f4e06766f512f0358ecb6ac51d5434c17d31dd70890dc1