This commit is contained in:
Caikecheng
2025-10-13 19:53:00 +08:00
53 changed files with 1360 additions and 129620 deletions

View File

@@ -25,6 +25,12 @@ class ASEngineClient:
# 存储路由和对应的回调函数
self.routes: Dict[str, Dict[str, Callable]] = {}
self._on_connect_callback = None
self._on_connect_callback_entry=None
self._on_connect_callback_initData=None
# 注册基础事件处理函数
self._register_base_events()
@@ -36,7 +42,9 @@ class ASEngineClient:
self.connected = True
self.sid = self.sio.get_sid(namespace=self.namespace)
print(f"Connected to server. SID: {self.sid}")
if self._on_connect_callback is not None:
self._on_connect_callback(self._on_connect_callback_entry, self._on_connect_callback_initData)
# 断开连接事件
@self.sio.on('disconnect', namespace=self.namespace)
def on_disconnect():
@@ -55,6 +63,14 @@ class ASEngineClient:
def on_response(data):
print(f"Received response: {data}")
def register_on_connect_entry(self, callback: Callable, entry: Any, init_data: Any)->bool:
if not callable(callback):
return False
self._on_connect_callback = callback
self._on_connect_callback_entry=entry
self._on_connect_callback_initData=init_data
def register_route_with_entry(self, route: str,
callback: Optional[Callable] = None,
entry: Any = None,
@@ -76,6 +92,7 @@ class ASEngineClient:
@self.sio.on(route, namespace=self.namespace)
def on_route_message(data, _route=route):
if not self.connected: return
print(f"Received message on route '{_route}': {data}")
route_info = self.routes.get(_route)
if not route_info:
@@ -138,7 +155,8 @@ class ASEngineClient:
# 等待连接建立
start_time = time.time()
while not self.connected and (time.time() - start_time) < timeout:
time.sleep(0.1)
print(f"Connecting To {self.server_url}")
time.sleep(0.2)
return self.connected
except Exception as e:
print(f"Connection failed: {e}")
@@ -263,8 +281,9 @@ class ASEngineClient:
class HSAEngineClient(ASEngineClient):
def __init__(self, server_url: str, namespace: str, id:str):
def __init__(self, server_url: str, namespace: str, id:str, chatmanager):
super().__init__(server_url, namespace, id)
self.chatmanager = chatmanager
def loadmarkdown(self, fmd, fmdp, fsp):
self.send_text('markdown-in', fmd, self.id)
self.send_text('markdown-prompt-in', fmdp, self.id)

View File

@@ -2,6 +2,7 @@ import signal
import sys
import os
import time
import re
import uuid
current_directory = os.path.dirname(os.path.abspath(__file__))
sys.path.append(current_directory)
@@ -29,11 +30,25 @@ class ChatManager:
_procs: Dict[str, ProcRecord] = {}
_lock = threading.RLock()
def __init__(self):
self.chapter_chain_now = -1
self.ase_client = None
self.app = None
self.socketio = None
self.user_uuid = None
self.raw_markdown = None
self.raw_markdown_prompts = None
self.raw_score_prompts = None
self.material_id = None
self.chapter_name = None
self.lesson_name = None
self.chapter_chain = None
self.function_manager = FunctionManager()
self.bb = None
self.chat_historys = None
pass
def init(self,user_uuid,ase_client,raw_markdown,raw_markdown_prompts,raw_score_prompts,max_iter = 5, app=None, socketio=None):
self.chapter_chain_now = -1
def init(self,user_uuid,ase_client,raw_markdown,raw_markdown_prompts,raw_score_prompts,material_id,chapter_name, lesson_name, max_iter = 5, app=None, socketio=None):
self.ase_client = ase_client
self.app = app
self.socketio = socketio
@@ -41,9 +56,12 @@ class ChatManager:
self.raw_markdown = raw_markdown
self.raw_markdown_prompts = raw_markdown_prompts
self.raw_score_prompts = raw_score_prompts
self.material_id = material_id
self.chapter_name = chapter_name
self.lesson_name = lesson_name
self.chapter_chain = load_chapters(raw_markdown, raw_markdown_prompts, raw_score_prompts)
self.function_manager = FunctionManager()
self.bb = None
self.chat_historys = []
def disconnect(self, uuid):
try:
@@ -53,17 +71,29 @@ class ChatManager:
print("ERROR: disconnect",e)
print("disconnect success stop code-server success")
def load_code_in_markdown(self):
nowchapter_content = self.chapter_chain[self.chapter_chain_now].chapter
code_texts = extract_code_blocks(nowchapter_content)
for code_text in code_texts:
self.vscode_ws.emit('code-file', {'type':'create', 'data':code_text, 'chapter_title': self.chapter_chain[self.chapter_chain_now].title}, room=self.user_uuid ,namespace='/vscode')
def next_chapter(self):
assert self.chapter_chain_now + 1 < len(self.chapter_chain), "chapter_chain_now out of range"
self.chapter_chain_now += 1
self.bb.next_chapter()
self.focus_chapter(self.chapter_chain_now)
def focus_chapter(self, chapter_index):
assert chapter_index < len(self.chapter_chain), "chapter_index out of range"
self.chapter_chain_now = chapter_index
self.chapter_chain[chapter_index].Focus()
self.ase_client.loadmarkdown(self.chapter_chain[chapter_index].chapter, self.chapter_chain[chapter_index].chapter_prompt, self.chapter_chain[chapter_index].score_prompt)
def load_next_chapter(self):
print(f"now load next chapter markdown {self.chapter_chain_now}")
self.ase_client.loadmarkdown(self.chapter_chain[self.chapter_chain_now].chapter, self.chapter_chain[self.chapter_chain_now].chapter_prompt, self.chapter_chain[self.chapter_chain_now].score_prompt)
self.load_code_in_markdown()
def upload_bb(self):
self.ase_client.send_text('backboard-in', self.bb.get_info_prompt())
@@ -256,4 +286,21 @@ class ChatManager:
for k in keys:
self.stop(k)
def extract_code_blocks(text):
"""
从文本中提取所有用```标记的代码段(包括标记本身)
参数:
text: 包含代码段的原始文本
返回:
提取到的代码段列表,每个元素是一个完整的代码块(包括```标记)
"""
# 正则表达式匹配```开始和结束的代码块
# 考虑可能的语言指定(如```python
pattern = r'```.*?```'
# 使用DOTALL模式让.匹配包括换行符在内的所有字符
code_blocks = re.findall(pattern, text, re.DOTALL)
return code_blocks

View File

@@ -20,11 +20,11 @@ def realtime_response(config: dict, realtime_action: dict) -> None:
bb = backboard_manager.get_backboard(user_uuid)
if bb is None: return
assert isinstance(bb, Backboard)
# 统一记历史
bb.add_history(realtime_action)
print(f"backboard action {realtime_action}")
rtype = realtime_action.get("type")
if rtype == "workspaceFolders":
@@ -34,7 +34,7 @@ def realtime_response(config: dict, realtime_action: dict) -> None:
file_path = realtime_action.get("filePath")
bb.active_file_path = file_path
assert isinstance(file_path, str)
with open(os.path.join(bb.root_path, bb.active_file_content), "r", encoding="utf-8") as f:
with open(os.path.join(bb.root_path, bb.active_file_path), "r", encoding="utf-8") as f:
bb.active_file_content = f.read()
elif rtype == "paste":
@@ -42,16 +42,16 @@ def realtime_response(config: dict, realtime_action: dict) -> None:
assert isinstance(file_path, str)
bb.pasted_file_path = file_path
bb.pasted_content = realtime_action.get("content")
bb.active_file_path = file_path
with open(os.path.join(bb.root_path, bb.active_file_content), "r", encoding="utf-8") as f:
with open(os.path.join(bb.root_path, bb.active_file_path), "r", encoding="utf-8") as f:
bb.active_file_content = f.read()
elif rtype == "fileEdit":
file_path = realtime_action.get("filePath")
assert isinstance(file_path, str)
bb.active_file_path = file_path
with open(os.path.join(bb.root_path, bb.active_file_content), "r", encoding="utf-8") as f:
with open(os.path.join(bb.root_path, bb.active_file_path), "r", encoding="utf-8") as f:
bb.active_file_content = f.read()
action_without_config = realtime_action.copy()
action_without_config.pop('config', None) # 使用pop并设置默认值None避免键不存在时出错
bb.add_history(action_without_config)
chatmanager.upload_bb()

View File

@@ -18,14 +18,15 @@ class User:
self.head_icon = head_icon # URL of the user's head icon
self._id = _id
self.kwargs = kwargs
self.mongo = current_app.extensions["mongo"]
def save_chapter_memory(self, course_id, lesson_name, subchapter_title, mem_list, score, is_rebuttal):
'''
Save the memory of a chapter
into the mongo db
'''
try:
mongo = current_app.extensions["mongo"]
cp = mongo.db.course_process.find_one({"user_uuid": self.user_uuid, "material_id": course_id})
cp = self.mongo.db.course_process.find_one({"user_uuid": self.user_uuid, "material_id": course_id})
if not cp:
return
cp['course_last_study_date'] = datetime.now().strftime('%Y-%m-%d')
@@ -57,13 +58,12 @@ class User:
break
if not have_lesson:
cp['lesson_processs'].append(([], chapter_name, lesson_name))
mongo.db.course_process.update_one({"user_uuid": self.user_uuid, "material_id": course_id}, {"$set": cp})
self.mongo.db.course_process.update_one({"user_uuid": self.user_uuid, "material_id": course_id}, {"$set": cp})
except Exception as e:
print("=-=-=-=-=-=")
print(e)
def get_all_course_progress(self):
mongo = current_app.extensions["mongo"]
course_processes = mongo.db.course_process.find({"user_uuid": self.user_uuid})
course_processes = self.mongo.db.course_process.find({"user_uuid": self.user_uuid})
for cp in course_processes:
print(f"Course: {cp['material_id']}")
for lessons,chapter_name,lesson_name in cp["lesson_processs"]:
@@ -76,17 +76,56 @@ class User:
print(f" Rebuttal Score: {step['rebuttal_score']}")
def get_course_progress(self, course_id):
mongo = current_app.extensions["mongo"]
cp = mongo.db.course_process.find_one({"user_uuid": self.user_uuid, "material_id": course_id})
cp = self.mongo.db.course_process.find_one({"user_uuid": self.user_uuid, "material_id": course_id})
return cp
def set_course_progress(self, chat_historys, step_scores, material_id, chapter_name, lesson_name):
'''
chat_historys: ['', '', ...] 用户对话历史(包括函数调用,也就是右侧栏用户看到的内容)
step_scores: [10, 20, ...] 各个步骤的得分
'''
try:
cl = self.mongo.db.course_process
document = cl.find_one({
'user_uuid': self.user_uuid,
'material_id': material_id
})
if not document:
cl.insert_one({
'user_uuid': self.user_uuid,
'material_id': material_id,
'lesson_processs': [],
'course_put_in_date': datetime.now().strftime('%Y-%m-%d'),
'course_last_study_date': datetime.now().strftime('%Y-%m-%d'),
'course_total_study_time': 0
})
document = cl.find_one({
'user_uuid': self.user_uuid,
'material_id': material_id
})
lesson_processes = document.get('lesson_processs', [])
finded = False
for process in lesson_processes:
if process[1]==chapter_name and process[2]==lesson_name:
process[0] = chat_historys
if len(process)<=3:
process.append(step_scores)
else: process[3] = step_scores
finded = True
if not finded:
lesson_processes.append((chat_historys, chapter_name, lesson_name, step_scores))
cl.update_one({'user_uuid': self.user_uuid, 'material_id': material_id}, {'$set': {'lesson_processs': lesson_processes}})
except Exception as e:
print("=-=-=-=-=-=")
print(e)
def select_new_course(self, course_id, course:Material):
try:
if course_id in self.select_course:
return True
self.select_course.append(course_id)
mongo = current_app.extensions["mongo"]
mongo.db.course_process.insert_one({
self.mongo.db.course_process.insert_one({
'user_uuid': self.user_uuid,
'material_id': course_id,
'lesson_processs':[],
@@ -95,9 +134,8 @@ class User:
'course_total_study_time':0})
for chapter in course.chapters:
for lesson in chapter.lessons:
mongo.db.course_process.update_one({"user_uuid": self.user_uuid, "material_id": course_id}, {"$push": {"lesson_processs": ([], chapter.chapter_name, lesson.lesson_name)}})
self.select_course.append(course_id)
mongo.db.users.update_one({"user_uuid": self.user_uuid}, {"$push": {"select_course": course_id}})
self.mongo.db.course_process.update_one({"user_uuid": self.user_uuid, "material_id": course_id}, {"$push": {"lesson_processs": ([], chapter.chapter_name, lesson.lesson_name)}})
self.mongo.db.users.update_one({"user_uuid": self.user_uuid}, {"$push": {"select_course": course_id}})
except Exception as e:
print("=-=-=-=-=-=")
print(e)
@@ -131,31 +169,27 @@ class UserList:
def add_user(self, username, password, teacher=False):
"""添加新的用户登录信息"""
# 检查是否已经有该用户
mongo = current_app.extensions["mongo"]
if mongo.db.users.find_one({"username": username}) is None:
mongo.db.users.insert_one({"user_uuid": Binary.from_uuid(uuid.uuid4()), "username": username, "teacher": teacher, "password": password, "select_course": [], "head_icon": ""})
if current_app.extensions["mongo"].db.users.find_one({"username": username}) is None:
current_app.extensions["mongo"].db.users.insert_one({"user_uuid": Binary.from_uuid(uuid.uuid4()), "username": username, "teacher": teacher, "password": password, "select_course": [], "head_icon": ""})
else:
print(f"User with ID {username} already exists.")
def get_user_by_name(self, username):
"""根据用户ID获取用户信息"""
mongo = current_app.extensions["mongo"]
if username not in self.Users:
self.Users[username] = User(**mongo.db.users.find_one({"username": username}))
self.Users[username] = User(**current_app.extensions["mongo"].db.users.find_one({"username": username}))
return self.Users[username]
else:
return self.Users[username]
def has_user(self, username):
"""检查用户是否存在"""
mongo = current_app.extensions["mongo"]
return mongo.db.users.find_one({"username": username}) is not None
return current_app.extensions["mongo"].db.users.find_one({"username": username}) is not None
def get_user_is_teacher(self, username):
"""获取指定 user_id 的用户是否为教师"""
mongo = current_app.extensions["mongo"]
if mongo.db.users.find_one({"username": username}) is not None:
result = mongo.db.users.find_one({"username": username})
if current_app.extensions["mongo"].db.users.find_one({"username": username}) is not None:
result = current_app.extensions["mongo"].db.users.find_one({"username": username})
return result['teacher']
return None
@@ -169,11 +203,13 @@ class UserList:
def update_password(self, username, new_password):
"""更新用户的密码"""
mongo = current_app.extensions["mongo"]
assert mongo.db.users.find_one({"username": username}) is not None, f"User with ID {username} does not exist."
mongo.db.users.update_one({"username": username}, {"$set": {"password": new_password, "teacher": mongo.db.users.find_one({"username": username})['teacher']}})
def delete_user(self, username):
"""删除用户"""
mongo = current_app.extensions["mongo"]
assert mongo.db.users.find_one({"username": username}) is not None, f"User with ID {username} does not exist."
mongo.db.users.delete_one({"username": username})

View File

@@ -8,16 +8,14 @@ from ..function_tools import next_chapter
from ..services.backboard_service import realtime_response
from ..services.markdown_service import load_full_markdown_file
from ..extension_ase.ase_client import HSAEngineClient
from ..services.user_service import get_or_load_current_user
class VSCodeNamespace(Namespace):
def on_login(self,data):
ex = current_app.extensions
uuid2username = ex["uuid2username"]
backboard_manager = ex["backboard_manager"]
print("VSCode client connected")
print(data)
dataconfig = data['config']
user_uuid = dataconfig.get('user_uuid')
path = dataconfig.get('path')
@@ -25,10 +23,18 @@ class VSCodeNamespace(Namespace):
lesson_id = dataconfig.get('chapter_id')
print(f"User {user_uuid} connected with path: {path}")
join_room(user_uuid, namespace='/vscode')
if backboard_manager.get_backboard(user_uuid) == None:
backboard_manager.add_backboard(user_uuid,uuid2username[user_uuid], course_id, lesson_id, path)
chatmanager = ex['user_uuid2chatmanager'][user_uuid]
chatmanager.vscode_ws=self
def on_load_chapter(self, data):
dataconfig = data['config']
user_uuid = dataconfig.get('user_uuid')
ex = current_app.extensions
chatmanager = ex['user_uuid2chatmanager'][user_uuid]
chatmanager.load_code_in_markdown()
def on_message(self, data):
# print(f"Received from VSCode client: {data}")
dataconfig = data['config']
@@ -52,17 +58,44 @@ class AgentNamespace(Namespace):
chapter_name = data['chapter_name']
user_id = uuid2username[user_uuid]
session['user_uuid'] = user_uuid
join_room(user_uuid, namespace='/agent') # 将该用户加入以user_id为名的room
print(f'User connected with session user_uuid: {user_uuid}')
user_uuid2chatmanager = ex["user_uuid2chatmanager"]
user_uuid2ase_client = ex["user_uuid2ase_client"]
user_uuid2ase_client[user_uuid] = HSAEngineClient(current_app.config["ASE_ENGINE_URL"], current_app.config["ASE_ENGINE_NAMESPACE"],id=user_id)
user_uuid2ase_client[user_uuid].connect()
# if (user_uuid2ase_client[user_uuid] is not None and user_uuid2ase_client[user_uuid].connected):
# user_uuid2ase_client[user_uuid].disconnect()
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 = []
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)
chatmanager.init(user_uuid, user_uuid2ase_client[user_uuid], fmd, fmdp, fsp, course_id, chapter_name, lesson_name, max_iter=5, app=current_app, socketio=self)
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()
self.chatmanager = chatmanager
self.ase_client = user_uuid2ase_client[user_uuid]
user_uuid2ase_client[user_uuid].register_on_connect_entry(
callback=self.on_connect_to_ase,
entry=(self, user_uuid2ase_client[user_uuid]),
init_data={'room':user_uuid}
)
user_uuid2ase_client[user_uuid].register_route_with_entry(
route='dialog',
callback=self.receive_ase_dialog,
entry=self,
init_data={'room': user_uuid}
)
user_uuid2ase_client[user_uuid].register_route_with_entry(
route='dialog-hint',
callback=self.receive_ase_dialog_hint,
entry=self,
init_data={'room': user_uuid}
)
user_uuid2ase_client[user_uuid].register_route_with_entry(
route='voice_out',
callback=self.receive_ase_voice_out,
@@ -81,18 +114,18 @@ class AgentNamespace(Namespace):
entry=self,
init_data={'room': user_uuid}
)
fmd,fmdp,fsp = load_full_markdown_file(course_id, chapter_name, lesson_name)
chatmanager = user_uuid2chatmanager[user_uuid]
chatmanager.init(user_uuid, user_uuid2ase_client[user_uuid], fmd, fmdp, fsp, max_iter=5, app=current_app, socketio=self)
# 注册函数,以确保一些初始参数
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})
user_uuid2ase_client[user_uuid].register_route_with_entry(
route='chapter_score',
callback=self.receive_ase_chapter_score,
entry=(get_or_load_current_user(),chatmanager),
init_data={'room': user_uuid}
)
user_uuid2ase_client[user_uuid].connect()
join_room(user_uuid, namespace='/agent') # 将该用户加入以user_id为名的room
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.focus_chapter(0)
def on_language(self, language):
ex = current_app.extensions
@@ -108,13 +141,8 @@ class AgentNamespace(Namespace):
data = json.loads(data)
if data['type'] == 'text':
res = user_uuid2ase_client[uuid].send_text('dialog', data['data'])
chatmanager[uuid].chat_historys.append({'role': 'user', 'content': data['data']})
print(f"Send text to route 'dialog' success: {res}")
# print('=*='*20)
# print(res.content)
# reply = f"{res.content['speak']}"
# with current_app.app_context():
# emit('message',reply, room=uuid, namespace='/agent')
# emit('request_function',res.content['function'], room=uuid, namespace='/agent')
if data['type'] == 'function': # 在这里 前端会发送允许执行的function与参数
print("function_call", data['data'])
@@ -123,6 +151,7 @@ class AgentNamespace(Namespace):
d['data'] = res.to_dict()
print("function_call_res", d['data'])
emit(d['route'], d, room=uuid, namespace='/agent')
chatmanager[uuid].chat_historys.append({'role': 'user', 'content': data['data']})
user_uuid2ase_client[uuid].send_text(d['route'], data['data'])
def on_VoiceMessage(self, voice_data):
@@ -134,9 +163,20 @@ class AgentNamespace(Namespace):
res = user_uuid2ase_client[uuid].send_voice('voice_in', voice_data)
print(f"Send voicce to route 'voice_in' success: {res}")
def on_connect_to_ase(client_entry: HSAEngineClient, namespace_entry, init_data):
'''
此时刚刚建立好aseclient连接并向前端发送打开code-server-like指令。
'''
print(f"on_connect_to_ase {client_entry}, {namespace_entry}, {init_data}")
namespace_entry, ase_client = namespace_entry
namespace_entry.emit('open-iframe',"", room=init_data['room'], namespace='/agent')
ase_client.chatmanager.load_next_chapter()
ase_client.send_text("chapter-start", "")
namespace_entry.emit('message', "Multi-Agents服务已连接", room=init_data['room'], namespace='/agent')
def receive_ase_dialog(client_entry: HSAEngineClient, namespace_entry: Namespace, data, init_data):
print("receive_ase_dialog", data)
client_entry.chatmanager.chat_historys.append({'role': 'assistant', 'content': data['data']})
namespace_entry.emit('message', data['data'], room=init_data['room'], namespace='/agent')
#接受语音信息
@@ -152,6 +192,16 @@ class AgentNamespace(Namespace):
print("receive_ase_next_chapter", data)
namespace_entry.emit('request_function',data, room=init_data['room'], namespace='/agent') # data 是一个列表 /dict也支持
def receive_ase_chapter_score(client_entry: HSAEngineClient, user_and_manager, data, init_data):
print("load_next_chapter")
user, chatmanager = user_and_manager
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)
chatmanager.load_next_chapter()
def on_initiative(self,data): # 主动call
print("User active function call")
ex = current_app.extensions

View File

@@ -0,0 +1,133 @@
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>Document</title>
<!-- 你的其他样式 -->
<script src="https://cdn.jsdelivr.net/npm/mathjax@3/es5/tex-mml-chtml.js"></script>
<script>
MathJax.config = {
tex: {
inlineMath: [['$', '$'], ['\(', '\)']]
},
svg: {
fontCache: 'global'
}
};
</script>
</head>
<body>
<h1>效率的重要性与实践验证</h1>
<h2>效率</h2>
<h3>理论热身:算法选择的智慧</h3>
<h4>任务:分析与决策</h4>
<p>项目组目前有两套备选的交通信号灯同步算法,它们将在不同性能的服务器上运行:</p>
<ul>
<li>
<p><strong>方案A</strong>部署在超级服务器上10^9 次运算/秒采用的是一种较为简单的算法处理n个路口需要 <eq>2n^2</eq> 次计算。</p>
</li>
<li>
<p><strong>方案B</strong>部署在普通服务器上10^7 次运算/秒但采用的是一种更优化的算法处理n个路口需要 <eq>50nlog_2n</eq> 次计算。</p>
</li>
</ul>
<p>你的项目经理右侧的Agent希望你通过分析来判断哪个方案更具前景。请与TA对话逐一回答以下问题。</p>
<h4>思考题</h4>
<p>与右侧的Agent对话回答以下问题</p>
<ol>
<li>
<p><strong>概念回顾</strong>:首先,请向你的“经理”解释,根据课程所学,一个合格的“算法”应具备哪些基本特征?</p>
</li>
<li>
<p><strong>小规模测试</strong>对于一个包含100个路口的小型城区n=100计算并说明方案A和方案B分别需要多长时间完成计算在这种情况下你会推荐哪个方案</p>
</li>
<li>
<p><strong>大规模应用</strong>现在我们需要为一座拥有100万个路口的大都市n=1,000,000进行规划。再次计算并说明两个方案的耗时。你的推荐会改变吗为什么</p>
</li>
<li>
<p><strong>总结陈词</strong>:综合以上分析,向你的“经理”总结:为什么一个更优的算法设计,其重要性远超硬件性能的提升? 这验证了课程中提到的哪个核心观点?</p>
</li>
</ol>
<h3>编程实践:验证算法的真实性能</h3>
<h4>任务:编码与分析</h4>
<p>理论分析让你认识到了算法效率的重要性。现在,你需要通过编程来亲身感受不同交通状况对同一算法性能的巨大影响。我们将以“插入排序”为例,来处理三种典型的交通流量数据,这分别对应算法分析中的<strong>最好</strong><strong>最坏</strong><strong>平均</strong>情况。</p>
<h5>题目:模拟交通流量排序</h5>
<p>实现插入排序算法,并验证其在处理“畅通无阻”(数据有序)、“交通大堵塞”(数据逆序)和“随机车流”(数据随机)三种模式时的效率差异。</p>
<h5>代码框架</h5>
<p>在代码编辑区,完成 <code>insertion_sort(arr)</code> 函数的实现后运行完整代码并与Agent讨论结果。
<strong>请创建任意文件,将下面代码写入到编辑器中</strong></p>
<pre><code class="language-python">import random
import time
def insertion_sort(arr):
&quot;&quot;&quot;
实现插入排序算法
参数arr: 待排序的交通数据数组(整数代表车辆通行次序)
返回: 排序后的数组
&quot;&quot;&quot;
# TODO: 请在此处实现你的插入排序逻辑
def generate_traffic_data(n):
&quot;&quot;&quot;
生成模拟交通数据
参数n: 数据规模(路口数量或监控点数量)
返回: 三种不同交通状况的数据
&quot;&quot;&quot;
random_data = [random.randint(0, 10**6) for _ in range(n)]
# 模拟“畅通无阻”:交通流按次序进入,数据基本有序 (Best Case)
best_case_data = sorted(random_data)
# 模拟“交通大堵塞”:疏散时情况完全反转,数据逆序 (Worst Case)
worst_case_data = sorted(random_data, reverse=True)
# 模拟“随机车流”:正常但无规律的交通状况 (Average Case)
average_case_data = random_data
return best_case_data, worst_case_data, average_case_data
def measure_performance(func, data):
&quot;&quot;&quot;
测量算法性能
参数func: 排序函数
参数data: 交通数据
返回: 执行时间(毫秒)
&quot;&quot;&quot;
start_time = time.perf_counter_ns()
func(data.copy()) # 使用副本避免影响其他测试
end_time = time.perf_counter_ns()
return (end_time - start_time) / 10**6 # 转换为毫秒
#测试不同规模的路口网络
network_sizes = [1000, 5000, 10000]
print(&quot;交通数据处理算法性能测试:&quot;)
for size in network_sizes:
best, worst, avg = generate_traffic_data(size)
time_best = measure_performance(insertion_sort, best)
time_worst = measure_performance(insertion_sort, worst)
time_avg = measure_performance(insertion_sort, avg)
print(f&quot;网络规模 n={size}:&quot;)
print(f&quot; - 畅通无阻 (Best Case): {time_best:.2f} ms&quot;)
print(f&quot; - 交通大堵塞 (Worst Case): {time_worst:.2f} ms&quot;)
print(f&quot; - 随机车流 (Average Case): {time_avg:.2f} ms&quot;)
</code></pre>
<h4>分析与讨论</h4>
<p>完成编程并得到输出后请与右侧Agent讨论以下问题以检验你的理解</p>
<ol>
<li>
<p><strong>结果分析</strong>当网络规模从1000增加到10000时“交通大堵塞”最坏情况的处理时间增长了大约多少倍这更符合O(n)线性还是O(n2)(二次)的增长模式?</p>
</li>
<li>
<p><strong>原因探究</strong>:为什么“畅通无阻”(最好情况)的处理速度如此之快?它的时间复杂度是什么?请结合你的代码逻辑来解释。</p>
</li>
<li>
<p><strong>实践应用</strong>:根据你的实验结果,你认为插入排序是否适合用于需要快速响应大规模交通拥堵的实时预警系统?为什么?</p>
</li>
<li>
<p><strong>融会贯通</strong>:结合第一关的理论分析和第二关的编程实验,你对“算法是解决问题的核心”这句话有了怎样更深的理解?</p>
</li>
</ol>
</body>
</html>

View File

@@ -0,0 +1,116 @@
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>Document</title>
<!-- 你的其他样式 -->
<script src="https://cdn.jsdelivr.net/npm/mathjax@3/es5/tex-mml-chtml.js"></script>
<script>
MathJax.config = {
tex: {
inlineMath: [['$', '$'], ['\(', '\)']]
},
svg: {
fontCache: 'global'
}
};
</script>
</head>
<body>
<h1>分治策略进阶与主方法</h1>
<blockquote>
<p>Auto-generated at 2025-09-22 15:44</p>
</blockquote>
<h2>分治策略进阶与主方法</h2>
<h3>第一关:理论学习:主方法 (Master Theorem)</h3>
<h4>任务:掌握主方法</h4>
<h5>场景介绍</h5>
<p>在分析归并排序时,我们通过递推关系<eq>T(n)=2T(n/2)+Θ(n)</eq>得出了 O(nlogn) 的结论。但每次都画递归树很繁琐。主方法为形如 T(n)=aT(n/b)+f(n) 的递推式提供了一个“公式化”的求解捷径。</p>
<h5>思考题</h5>
<p>请与右侧的Agent你的项目经理对话学习并应用主方法</p>
<ol>
<li>
<p><strong>回顾与识别</strong>:对于归并排序的递推式 <eq>T(n)=2T(n/2)+Θ(n)</eq>,请指出主方法公式中对应的 <code>a</code><code>b</code><code>f(n)</code> 分别是什么?</p>
</li>
<li>
<p><strong>核心比较</strong>:主方法的核心是比较 <code>f(n)</code><eq>nlog_ba</eq> 的大小。请计算出归并排序中<eq>nlog_ba</eq> 的值,并判断它与 <code>f(n)</code> 的关系符合主方法的哪种情况Case</p>
</li>
<li>
<p><strong>实战演练</strong>:现在,运用你学到的主方法,求解以下几种在算法分析中常见的递推式,并说明你分别应用了主方法的哪种情况。</p>
<ul>
<li>a. <eq>T(n)=2T(n/4)+\sqrt n</eq></li>
<li>b.<eq>T(n)=7T(n/2)+n^2</eq> (这是Strassen矩阵乘法算法的复杂度模型)</li>
<li>c. <eq>T(n)=7T(n/3)+n^2</eq></li>
</ul>
</li>
</ol>
<h3>第二关:编程实践:最大子数组问题</h3>
<h4>任务:编码与分析</h4>
<h5>场景介绍</h5>
<p>我们的任务是分析每日交通流量的<strong>变化数组</strong>(正数代表流量增加,负数代表减少),并找到哪一个<strong>连续时段</strong>的流量总增量最大。这在算法上被称为“最大子数组问题” 6666。例如对于流量变化数组<code>[13, -3, -25, 20, -3, -16, -23, 18, 20, -7, 12, -5, -22, 15, -4, 7]</code>总增量最大的连续时段是从第8天到第11天总增量为 <code>18 + 20 - 7 + 12 = 43</code>
虽然这个问题可以通过O(n2) 的暴力法求解,但我们将使用更高效的分治策略。</p>
<h5>题目:最大交通流量增量</h5>
<p>你需要实现一个分治算法来解决最大子数组问题。同时,为了对比,你也会实现一个暴力求解算法。</p>
<h5>代码框架</h5>
<p>在下方代码编辑区,完成 <code>find_maximum_subarray</code>(分治法)和 <code>find_max_crossing_subarray</code> 以及 <code>find_maximum_subarray_brute</code>(暴力法)三个函数。</p>
<pre><code class="language-python">import time
import random
def find_maximum_subarray_brute(arr):
&quot;&quot;&quot;
使用暴力法求解最大子数组问题
返回: (最大和, 开始索引, 结束索引)
&quot;&quot;&quot;
#--- 本周任务:请在下方实现分治算法 ---
def find_max_crossing_subarray(arr, low, mid, high):
&quot;&quot;&quot;
找到跨越中点的最大子数组
返回: (最大和, 开始索引, 结束索引)
&quot;&quot;&quot;
# TODO: 实现寻找跨越中点的最大子数组的逻辑
# 提示: 从mid向左和向右分别扫描找到各自的最大和
def find_maximum_subarray(arr, low, high):
&quot;&quot;&quot;
使用分治法求解最大子数组问题
返回: (最大和, 开始索引, 结束索引)
&quot;&quot;&quot;
# TODO: 实现分治递归逻辑
# 提示: 递归基是当数组只有一个元素时
#--- 测试与对比部分 ---
traffic_changes = [13, -3, -25, 20, -3, -16, -23, 18, 20, -7, 12, -5, -22, 15, -4, 7]
print(f&quot;交通流量变化数据: {traffic_changes}&quot;)
#使用分治法
max_sum_dc, start_dc, end_dc = find_maximum_subarray(traffic_changes, 0, len(traffic_changes) - 1)
print(f&quot;分治法结果: 最大增量 = {max_sum_dc}, 时段 = Day {start_dc} to Day {end_dc}&quot;)
#使用暴力法验证
max_sum_brute, start_brute, end_brute = find_maximum_subarray_brute(traffic_changes)
print(f&quot;暴力法结果: 最大增量 = {max_sum_brute}, 时段 = Day {start_brute} to Day {end_brute}&quot;)
#性能测试
large_data = [random.randint(-50, 50) for _ in range(2000)]
start_time = time.perf_counter()
find_maximum_subarray(large_data, 0, len(large_data) - 1)
dc_time = (time.perf_counter() - start_time) * 1000
print(f&quot;\n在 n=2000 的数据集上,分治法耗时: {dc_time:.2f} ms&quot;)
start_time = time.perf_counter()
find_maximum_subarray_brute(large_data)
brute_time = (time.perf_counter() - start_time) * 1000
print(f&quot;在 n=2000 的数据集上,暴力法耗时: {brute_time:.2f} ms&quot;)</code></pre>
</body>
</html>

View File

@@ -7,7 +7,9 @@
display: flex;
flex-direction: column; /* 使子元素垂直排列 */
}
#leftSidebar {
padding-left: 3px;
}
body {
font-family: Arial, sans-serif;
display: flex;

View File

@@ -30,7 +30,8 @@ body {
border-radius: 15px;
box-shadow: 0 10px 30px rgba(0, 0, 0, 0.1);
width: 100%;
max-width: 500px;
min-width: 400px;
max-width: 600px;
text-align: center;
}

View File

@@ -0,0 +1,69 @@
.username-group { position: relative; }
.field-tip {
position: absolute;
top: 40%;
left: calc(60% + 10px); /* 紧挨输入框右侧 */
transform: translateY(-50%);
width: 260px;
background: #fff;
border: 1px solid #e5e7eb;
border-radius: 8px;
box-shadow: 0 8px 24px rgba(0,0,0,0.08);
padding: 10px 12px;
font-size: 12px;
line-height: 1.5;
color: #111827;
/* 初始隐藏 */
opacity: 0;
visibility: hidden;
pointer-events: none;
transition: opacity .15s ease, visibility .15s ease;
z-index: 10;
}
.field-tip::after { /* 小三角箭头 */
content: "";
position: absolute;
top: 50%;
left: -6px;
transform: translateY(-50%);
border-width: 6px;
border-style: solid;
border-color: transparent #e5e7eb transparent transparent;
}
.field-tip::before { /* 箭头内层白色 */
content: "";
position: absolute;
top: 50%;
left: -5px;
transform: translateY(-50%);
border-width: 5px;
border-style: solid;
border-color: transparent #fff transparent transparent;
z-index: 1;
}
.field-tip strong { display: block; margin-bottom: 6px; }
.field-tip ul { margin: 0; padding-left: 16px; }
.field-tip li { margin: 2px 0; color: #374151; }
.field-tip span { font-weight: 600; color: #111827; }
/* 显示状态 */
.field-tip.is-visible {
opacity: 1;
visibility: visible;
}
/* 小屏时把提示框放到输入框下方,避免遮挡 */
@media (max-width: 640px) {
.field-tip {
top: calc(60% + 8px);
left: 0;
transform: none;
width: min(92vw, 320px);
}
.field-tip::after,
.field-tip::before {
display: none;
}
}

View File

@@ -12,8 +12,10 @@ document.addEventListener('DOMContentLoaded', function () {
});
socket.on('connect', function () {
console.log('Connected to server');
socket.emit('login', JSON.stringify(data));
OpenIframe();
socket.emit('login',JSON.stringify(data));
});
socket.on('open-iframe', function(data) {
OpenIframe();
});
// 监听来自服务器的消息
socket.on('message', function (data) {
@@ -24,29 +26,18 @@ document.addEventListener('DOMContentLoaded', function () {
// 滚动到最新消息
document.getElementById('chatbox').scrollTop = document.getElementById('chatbox').scrollHeight;
});
//监听来自服务器的消息
/*socket.on('VoiceMessage', function(data) {
console.log("Received voice answer message:", data);
socket.on('message-hint', function(data){
console.log('get message-hint');
// 显示服务器的回复消息
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);
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('VoiceMessage', function (data) {
console.log("Received voice message I sent from server:", data);
//将语音转文字显示在输入框中
document.getElementById('messageInput').value = data;
});
socket.on('request_function', function (data) {
socket.on('request_function', function(data){
try {
console.log("request_function", data);
if (typeof data === 'string') {

View File

@@ -22,7 +22,6 @@ function show_user_data(user_data, course_brief_data_list){
}
}
function openCourseProgress(courseId) {
console.log(courseId)
const course = courseData[courseId];
if (!course) return;
fetch(`/dashboard/get_course_progress`, {
@@ -33,7 +32,7 @@ function openCourseProgress(courseId) {
body: JSON.stringify({course_id: courseId})
}).then(response => response.json())
.then(data => {
console.log(data)
// console.log(data)
/*
course_last_study_date: "2025-09-06"
course_put_in_date: "2025-09-06"
@@ -75,31 +74,36 @@ function openCourseProgress(courseId) {
const subChapterItem = document.createElement('li');//课时层级
subChapterItem.classList.add('sub-chapter-item');
subChapterItem.textContent = subChapter.lesson_name;
console.log('-------------------')
console.log(course)
// 查找学生进度
lesson_score_text_content = ""
for(let i = 0; i < course.lesson_processs.length; i++) {
if (course.lesson_processs[i][2] == subChapter.lesson_name && course.lesson_processs[i][1] == chapter.chapter_name) {//确定lesson_id对应正确
///////////////////////////////////////////
//这里之后要做lesson的各个step的进度和得分
////////////////////////////////////////////
console.log('-------------------')
console.log(course.lesson_processs[i][0])
lesson_chapters_progress = course.lesson_processs[i][0];//寻找对应的每一个章节的进度
for (let j=0; j<lesson_chapters_progress.length;j++){
chapter_progress = lesson_chapters_progress[j]
if(chapter_progress.title == subChapter){
subChapterItem.textContent += ("(已"+(chapter_progress.is_rebuttal?"申辩" : "完成")+" 得分"+ (chapter_progress.is_rebuttal?chapter_progress.rebuttal_score : chapter_progress.score) +"");
}
}
if (course.lesson_processs[i][0].length === 1) {
subChapterItem.classList.add('completed');
if (course.lesson_processs[i].length<=3){break;}
for (let j =0; j< course.lesson_processs[i][3].length;j++){
if (isObject(course.lesson_processs[i][3][j]))course.lesson_processs[i][3][j]=course.lesson_processs[i][3][j]['data']
lesson_score_text_content+=course.lesson_processs[i][3][j]+';'
}
// console.log('-------------------')
// subChapterItem.textContent +=
// console.log(course.lesson_processs[i][0])
// lesson_chapters_progress = course.lesson_processs[i][0];//寻找对应的每一个章节的进度
// for (let j=0; j<lesson_chapters_progress.length;j++){
// chapter_progress = lesson_chapters_progress[j]
// if(chapter_progress.title == subChapter){
// subChapterItem.textContent += ("(已"+(chapter_progress.is_rebuttal?"申辩" : "完成")+" 得分"+ (chapter_progress.is_rebuttal?chapter_progress.rebuttal_score : chapter_progress.score) +"");
// }
// }
// if (course.lesson_processs[i][0].length === 1) {
// subChapterItem.classList.add('completed');
// }
break;
}
}
if (lesson_score_text_content!=""){lesson_score_text_content = ' ('+lesson_score_text_content+')'}
subChapterItem.textContent+=lesson_score_text_content
// 添加点击事件跳转到学习页面
subChapterItem.addEventListener('click', () => {
const courseId = encodeURIComponent(course._id); // 对课程名称进行编码
@@ -136,3 +140,7 @@ function closeCourseProgress() {
// 关闭滑出卡片
document.getElementById('courseProgress').classList.remove('open');
}
function isObject(value) {
return Object.prototype.toString.call(value) === '[object Object]';
}

View File

@@ -8,7 +8,11 @@ document.addEventListener('DOMContentLoaded', function() {
const email = document.getElementById('email').value;
const password = document.getElementById('password').value;
const confirmPassword = document.getElementById('confirm-password').value;
const u = validateLinuxUsername(username);
if (!u.ok) {
alert(u.error);
return;
}
if (password !== confirmPassword) {
alert('密码和确认密码不一致');
return;
@@ -36,4 +40,36 @@ document.addEventListener('DOMContentLoaded', function() {
alert('注册失败');
});
});
});
});
// 校验 Linux 用户名
function validateLinuxUsername(name) {
// 基本字符规则:^[a-z_][a-z0-9_-]*[$]?$
const basic = /^[a-z_][a-z0-9_-]*[$]?$/;
if (typeof name !== 'string' || name.length === 0) {
return { ok: false, error: '用户名不能为空' };
}
// 长度限制(常见系统为 <= 32
if (name.length > 32) {
return { ok: false, error: '用户名长度不能超过 32 个字符' };
}
if (!basic.test(name)) {
return {
ok: false,
error:
'用户名需以小写字母或下划线开头,仅包含小写字母、数字、下划线、连字符,允许以 $ 结尾'
};
}
// 可选:避免纯数字用户名(多数系统不推荐)
if (/^[0-9]+$/.test(name)) {
return { ok: false, error: '用户名不能为纯数字' };
}
// 可选:避免使用保留名
const reserved = new Set(['root', 'daemon', 'bin', 'sys', 'sync', 'games', 'man']);
if (reserved.has(name)) {
return { ok: false, error: '该用户名为系统保留名,请更换' };
}
return { ok: true };
}

View File

@@ -0,0 +1,27 @@
(function () {
let usernameInput = document.getElementById('username');
if (!usernameInput){
usernameInput = document.getElementById('teacher-username');
}
const tip = document.getElementById('username-tip');
function showTip() {
tip.classList.add('is-visible');
tip.setAttribute('aria-hidden', 'false');
}
function hideTip() {
tip.classList.remove('is-visible');
tip.setAttribute('aria-hidden', 'true');
}
usernameInput.addEventListener('focus', showTip);
usernameInput.addEventListener('blur', hideTip);
// 当用户点击提示框自身时不影响(比如复制文本)
tip.addEventListener('mousedown', e => e.preventDefault());
// 可选:输入时也显示(防止因某些浏览器失焦导致闪烁)
usernameInput.addEventListener('input', () => {
if (document.activeElement === usernameInput) showTip();
});
})();

View File

@@ -8,7 +8,11 @@ document.addEventListener('DOMContentLoaded', function() {
const email = document.getElementById('teacher-email').value;
const password = document.getElementById('teacher-password').value;
const confirmPassword = document.getElementById('teacher-confirm-password').value;
const u = validateLinuxUsername(username);
if (!u.ok) {
alert(u.error);
return;
}
// 检查密码和确认密码是否一致
if (password !== confirmPassword) {
alert('密码和确认密码不一致');
@@ -39,3 +43,35 @@ document.addEventListener('DOMContentLoaded', function() {
});
});
});
// 校验 Linux 用户名
function validateLinuxUsername(name) {
// 基本字符规则:^[a-z_][a-z0-9_-]*[$]?$
const basic = /^[a-z_][a-z0-9_-]*[$]?$/;
if (typeof name !== 'string' || name.length === 0) {
return { ok: false, error: '用户名不能为空' };
}
// 长度限制(常见系统为 <= 32
if (name.length > 32) {
return { ok: false, error: '用户名长度不能超过 32 个字符' };
}
if (!basic.test(name)) {
return {
ok: false,
error:
'用户名需以小写字母或下划线开头,仅包含小写字母、数字、下划线、连字符,允许以 $ 结尾'
};
}
// 可选:避免纯数字用户名(多数系统不推荐)
if (/^[0-9]+$/.test(name)) {
return { ok: false, error: '用户名不能为纯数字' };
}
// 可选:避免使用保留名
const reserved = new Set(['root', 'daemon', 'bin', 'sys', 'sync', 'games', 'man']);
if (reserved.has(name)) {
return { ok: false, error: '该用户名为系统保留名,请更换' };
}
return { ok: true };
}

View File

@@ -0,0 +1,119 @@
<html>
<head>
<style>
img {
max-width: 100%;
height: auto;
}
</style>
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/github-markdown-css/5.2.0/github-markdown.min.css">
</head>
<body class="markdown-body">
<h1>效率的重要性与实践验证</h1>
<h2>效率</h2>
<h3>理论热身:算法选择的智慧</h3>
<h4>任务:分析与决策</h4>
<p>项目组目前有两套备选的交通信号灯同步算法,它们将在不同性能的服务器上运行:</p>
<ul>
<li>
<p><strong>方案A</strong>部署在超级服务器上10^9 次运算/秒采用的是一种较为简单的算法处理n个路口需要 $2n^2$ 次计算。</p>
</li>
<li>
<p><strong>方案B</strong>部署在普通服务器上10^7 次运算/秒但采用的是一种更优化的算法处理n个路口需要 $50nlog_2n$ 次计算。</p>
</li>
</ul>
<p>你的项目经理右侧的Agent希望你通过分析来判断哪个方案更具前景。请与TA对话逐一回答以下问题。</p>
<h4>思考题</h4>
<p>与右侧的Agent对话回答以下问题</p>
<ol>
<li>
<p><strong>概念回顾</strong>:首先,请向你的“经理”解释,根据课程所学,一个合格的“算法”应具备哪些基本特征? </p>
</li>
<li>
<p><strong>小规模测试</strong>对于一个包含100个路口的小型城区n=100计算并说明方案A和方案B分别需要多长时间完成计算在这种情况下你会推荐哪个方案</p>
</li>
<li>
<p><strong>大规模应用</strong>现在我们需要为一座拥有100万个路口的大都市n=1,000,000进行规划。再次计算并说明两个方案的耗时。你的推荐会改变吗为什么</p>
</li>
<li>
<p><strong>总结陈词</strong>:综合以上分析,向你的“经理”总结:为什么一个更优的算法设计,其重要性远超硬件性能的提升? 这验证了课程中提到的哪个核心观点?</p>
</li>
</ol>
<h3>编程实践:验证算法的真实性能</h3>
<h4>任务:编码与分析</h4>
<p>理论分析让你认识到了算法效率的重要性。现在,你需要通过编程来亲身感受不同交通状况对同一算法性能的巨大影响。我们将以“插入排序”为例,来处理三种典型的交通流量数据,这分别对应算法分析中的<strong>最好</strong><strong>最坏</strong><strong>平均</strong>情况。</p>
<h5>题目:模拟交通流量排序</h5>
<p>实现插入排序算法,并验证其在处理“畅通无阻”(数据有序)、“交通大堵塞”(数据逆序)和“随机车流”(数据随机)三种模式时的效率差异。</p>
<h5>代码框架</h5>
<p>在代码编辑区,完成 <code>insertion_sort(arr)</code> 函数的实现后运行完整代码并与Agent讨论结果。
<strong>请创建任意文件,将下面代码写入到编辑器中</strong>
```python
import random
import time</p>
<p>def insertion_sort(arr):
"""
实现插入排序算法
参数arr: 待排序的交通数据数组(整数代表车辆通行次序)
返回: 排序后的数组
"""
# TODO: 请在此处实现你的插入排序逻辑</p>
<p>def generate_traffic_data(n):
"""
生成模拟交通数据
参数n: 数据规模(路口数量或监控点数量)
返回: 三种不同交通状况的数据
"""
random_data = [random.randint(0, 10**6) for _ in range(n)]
# 模拟“畅通无阻”:交通流按次序进入,数据基本有序 (Best Case)
best_case_data = sorted(random_data)
# 模拟“交通大堵塞”:疏散时情况完全反转,数据逆序 (Worst Case)
worst_case_data = sorted(random_data, reverse=True)
# 模拟“随机车流”:正常但无规律的交通状况 (Average Case)
average_case_data = random_data
return best_case_data, worst_case_data, average_case_data</p>
<p>def measure_performance(func, data):
"""
测量算法性能
参数func: 排序函数
参数data: 交通数据
返回: 执行时间(毫秒)
"""
start_time = time.perf_counter_ns()
func(data.copy()) # 使用副本避免影响其他测试
end_time = time.perf_counter_ns()
return (end_time - start_time) / 10**6 # 转换为毫秒</p>
<h1>测试不同规模的路口网络</h1>
<p>network_sizes = [1000, 5000, 10000]
print("交通数据处理算法性能测试:")
for size in network_sizes:
best, worst, avg = generate_traffic_data(size)</p>
<pre><code>time_best = measure_performance(insertion_sort, best)
time_worst = measure_performance(insertion_sort, worst)
time_avg = measure_performance(insertion_sort, avg)
print(f"网络规模 n={size}:")
print(f" - 畅通无阻 (Best Case): {time_best:.2f} ms")
print(f" - 交通大堵塞 (Worst Case): {time_worst:.2f} ms")
print(f" - 随机车流 (Average Case): {time_avg:.2f} ms")
</code></pre>
<p>```</p>
<h4>分析与讨论</h4>
<p>完成编程并得到输出后请与右侧Agent讨论以下问题以检验你的理解</p>
<ol>
<li>
<p><strong>结果分析</strong>当网络规模从1000增加到10000时“交通大堵塞”最坏情况的处理时间增长了大约多少倍这更符合O(n)线性还是O(n2)(二次)的增长模式?</p>
</li>
<li>
<p><strong>原因探究</strong>:为什么“畅通无阻”(最好情况)的处理速度如此之快?它的时间复杂度是什么?请结合你的代码逻辑来解释。</p>
</li>
<li>
<p><strong>实践应用</strong>:根据你的实验结果,你认为插入排序是否适合用于需要快速响应大规模交通拥堵的实时预警系统?为什么?</p>
</li>
<li>
<p><strong>融会贯通</strong>:结合第一关的理论分析和第二关的编程实验,你对“算法是解决问题的核心”这句话有了怎样更深的理解?</p>
</li>
</ol>
</body>
</html>

View File

@@ -0,0 +1,126 @@
<html>
<head>
<style>
img {
max-width: 100%;
height: auto;
}
</style>
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/github-markdown-css/5.2.0/github-markdown.min.css">
</head>
<body class="markdown-body">
<h1>算法设计范式:分而治之与归并排序</h1>
<blockquote>
<p>Auto-generated at 2025-09-22 15:38</p>
</blockquote>
<h2>算法设计范式:分而治之与归并排序</h2>
<h3>第一关:理论探究:分而治之的力量</h3>
<p><strong>案例背景:智慧城市交通优化系统</strong></p>
<p>在上周的工作中你已经实现并分析了插入排序在交通数据处理中的性能。虽然它在数据量较小或基本有序的情况下表现不错但在应对大规模拥堵逆序数据其性能瓶颈非常明显。本周你的项目经理Agent将向你介绍一种更高效的算法设计思想——“分而治之”并要求你掌握其代表算法归并排序。</p>
<h4>任务:理解与分析</h4>
<h5>场景介绍</h5>
<p>你的经理告诉你归并排序Merge Sort是解决大规模排序问题的利器。在投入编码之前你必须先从理论上理解它为何如此高效。</p>
<h5>思考题</h5>
<p>请与右侧的Agent你的项目经理对话清晰地回答以下问题以证明你已理解核心思想</p>
<ol>
<li>
<p><strong>分治思想</strong>根据课程资料请解释什么是“分而治之”Divide-and-Conquer设计范式它包含哪三个基本步骤</p>
</li>
<li>
<p><strong>递推关系</strong>:归并排序的时间复杂度可以用递推式 T(n)=2T(n/2)+Θ(n) 来表示。请解释这个式子各部分的含义:</p>
<ul>
<li><code>2</code> 代表什么?</li>
<li><code>T(n/2)</code> 代表什么?</li>
<li><code>Θ(n)</code> 代表什么?</li>
</ul>
</li>
<li>
<p><strong>效率对比</strong>你的同事认为插入排序经过极致优化后处理n个数据的耗时为 $0.1n^2$;而归并排序由于递归和合并开销,耗时为 1000nlog_2n。在处理超大规模城市交通数据时即n趋于无穷大时哪个算法最终会胜出请从渐进增长的角度解释你的理由。</p>
</li>
<li>
<p><strong>增长排序</strong>:将下列函数的渐进增长率从低到高排序,并将它们划分到等价类(即 Θ 关系相同的函数)。这有助于你理解不同算法效率的所在层级。 </p>
<ul>
<li>$n^2$ (插入排序最坏情况)</li>
<li>nlogn (归并排序)</li>
<li>n (一种非常低效的蛮力算法)</li>
<li>$2^n$ (另一种指数级算法)</li>
<li>logn (类似二分查找的效率)</li>
</ul>
</li>
</ol>
<h3>第二关:编程实践:实现归并排序</h3>
<h4>任务:编码与对比</h4>
<h5>场景介绍</h5>
<p>现在你已经从理论上理解了归并排序的威力。是时候亲手实现它,并与上周的插入排序进行一次性能上的正面较量了!</p>
<h5>题目:实现归并排序并对比性能</h5>
<p>你需要实现归并排序的核心函数,并利用上周的测试框架来直观对比它与插入排序在处理不同交通数据时的表现。</p>
<h5>代码框架</h5>
<p>在下方代码编辑区,完成 <code>merge_sort(arr)</code><code>merge(left, right)</code> 两个函数的实现。
```python
import random
import time</p>
<h1>上周实现的插入排序(用于对比)</h1>
<p>def insertion_sort(arr):
for i in range(1, len(arr)):
key = arr[i]
j = i - 1
while j &gt;= 0 and arr[j] &gt; key:
arr[j + 1] = arr[j]
j -= 1
arr[j + 1] = key
return arr</p>
<h1>--- 本周任务:请在下方实现归并排序 ---</h1>
<p>def merge_sort(arr):
"""
实现归并排序算法
参数arr: 待排序的交通数据数组
返回: 一个新的、排序好的数组
"""
# TODO: 实现归并排序的递归逻辑
# 提示当数组元素个数小于等于1时递归终止</p>
<p>def merge(left, right):
"""
合并两个已排序的子数组
参数left: 左侧已排序数组
参数right: 右侧已排序数组
返回: 合并后的一个有序数组
"""</p>
<h1>--- 测试与对比部分 ---</h1>
<p>def measure_performance(sort_func, data):
start_time = time.perf_counter_ns()
sort_func(data.copy())
end_time = time.perf_counter_ns()
return (end_time - start_time) / 10**6</p>
<p>network_sizes = [1000, 5000, 10000, 50000]
print("算法性能对比测试:")
for size in network_sizes:
# 生成逆序数据,这是插入排序的最坏情况
worst_case_data = list(range(size, 0, -1))</p>
<pre><code>time_insertion = measure_performance(insertion_sort, worst_case_data)
time_merge = measure_performance(merge_sort, worst_case_data)
print(f"--- 网络规模 n={size} (最坏情况) ---")
print(f" - 插入排序: {time_insertion:.2f} ms")
print(f" - 归并排序: {time_merge:.2f} ms")
</code></pre>
<p>```</p>
<h5>分析与讨论</h5>
<p>完成编程并得到输出后请与右侧Agent讨论以下问题</p>
<ol>
<li>
<p><strong>性能验证</strong>:观察“交通大堵塞”(最坏情况)的测试结果,归并排序的性能表现与插入排序有何天壤之别?这是否验证了你在第一关的理论分析?</p>
</li>
<li>
<p><strong>性能稳定性</strong>:如果我们将测试数据换成“畅通无阻”(最好情况)或“随机车流”(平均情况),你认为归并排序的运行时间会发生巨大变化吗?为什么?</p>
</li>
<li>
<p><strong>空间成本</strong>:在实现 <code>merge</code> 函数时,你可能创建了一个新的数组 <code>merged</code> 来存放结果。这在算法分析中被称为“空间复杂度”。与在原数组上进行交换的插入排序相比,归并排序的这个特点是优点还是缺点?</p>
</li>
<li>
<p><strong>最终决策</strong>:作为项目工程师,你会选择哪种排序算法用于我们的大规模交通调度系统?请综合考虑<strong>时间效率</strong><strong>空间成本</strong>来陈述你的理由。</p>
</li>
</ol>
</body>
</html>

View File

@@ -21,6 +21,8 @@
<div class="course-info">
<h3 class="course-name">{{course.name}}</h3>
<p class="course-description">{{course.description}}</p>
</div>
</div>
{% endfor %}
</div>
</div>

View File

@@ -168,30 +168,36 @@
method: 'GET',
credentials: 'include' // 包含 Cookie确保 Flask 可以识别 session
})
.then(response => {
if (response.ok) return response.json();
throw new Error('Failed to fetch session');
})
.then(data => {
console.log('Session data:', data);
// 在成功获取 session 后创建并插入 iframe
const iframe = document.createElement('iframe');
const sessionInfo = data
iframe.src = '{{vscode_web_url}}/?workspace={{workspace_path}}&folder={{workspace_path}}';
iframe.style.width = '100%';
iframe.style.height = '99%';
// 将 iframe 插入到指定的 div 中
document.getElementById('vscodeWeb').appendChild(iframe);
})
.catch(error => {
console.error('Error fetching session:', error);
});
.then(response => {
if (response.ok) return response.json();
throw new Error('Failed to fetch session');
})
.then(data => {
console.log('Session data:', data);
})
.catch(error => {
console.error('Error fetching session:', error);
});
let lastIframe = null;
function OpenIframe(){
// 在成功获取 session 后创建并插入 iframe
const iframe = document.createElement('iframe');
const div=document.getElementById('vscodeWeb')
if (lastIframe){
lastIframe.remove();
}
lastIframe = iframe;
const sessionInfo = data
iframe.src = '{{vscode_web_url}}/?workspace={{workspace_path}}&folder={{workspace_path}}';
iframe.style.width = '100%';
iframe.style.height = '99%';
// 将 iframe 插入到指定的 div 中
div.appendChild(iframe);
}
@@ -237,6 +243,8 @@
});
</script>
<script src="/static/js/chatbox.js"></script>
</body>
</html>

View File

@@ -29,9 +29,14 @@
</div>
</main>
<footer>
<p>版权所有 &copy; 2024 “华实伴学君”——教学练评一体的虚拟编码助教</p>
</footer>
<footer>
<p>版权所有 &copy; 2024 “华实伴学君”——教学练评一体的虚拟编码助教</p>
<p>
<a href="https://beian.miit.gov.cn" target="_blank" rel="noopener noreferrer">
沪ICP备2025142149号
</a>
</p>
</footer>
</body>
<script>

View File

@@ -94,6 +94,7 @@
<a href="/">首页</a>
<a href="/dashboard">课程</a>
<a href="/grades">成绩</a>
{% endif %}
<div class="avatar dropdown">

View File

@@ -5,6 +5,7 @@
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>学生注册</title>
<link rel="stylesheet" href="/static/css/login.css">
<link rel="stylesheet" href="/static/css/register_field_tip.css">
<script src="/static/js/register.js"></script>
</head>
<body>
@@ -15,6 +16,17 @@
<div class="input-group">
<label for="username">用户名</label>
<input type="text" id="username" name="username" placeholder="请输入用户名" required>
<div class="field-tip" id="username-tip" role="tooltip" aria-hidden="true">
<strong>用户名规则</strong>
<ul>
<li><span>小写字母</span><span>下划线</span>开头</li>
<li>仅含<span>小写字母</span><span>数字</span><span>下划线 _</span><span>连字符 -</span></li>
<li>允许以 <span>$</span> 结尾(可选)</li>
<li>长度 ≤ 32 个字符</li>
<li>不可使用纯数字</li>
<li>避免保留名root / daemon 等</li>
</ul>
</div>
</div>
<div class="input-group">
<label for="email">邮箱</label>
@@ -32,5 +44,8 @@
</form>
</div>
</div>
<script src="/static/js/register_field_tip.js"></script>
</body>
</html>

View File

@@ -5,6 +5,7 @@
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>教师注册</title>
<link rel="stylesheet" href="/static/css/login.css">
<link rel="stylesheet" href="/static/css/register_field_tip.css">
<script src="/static/js/register_teacher.js"></script>
</head>
<body>
@@ -30,7 +31,20 @@
</div>
<button type="submit" class="login-btn">注册</button>
</form>
</div>
</div>
<div class="field-tip" id="username-tip" role="tooltip" aria-hidden="true">
<strong>用户名规则</strong>
<ul>
<li><span>小写字母</span><span>下划线</span>开头</li>
<li>仅含<span>小写字母</span><span>数字</span><span>下划线 _</span><span>连字符 -</span></li>
<li>允许以 <span>$</span> 结尾(可选)</li>
<li>长度 ≤ 32 个字符</li>
<li>不可使用纯数字</li>
<li>避免保留名root / daemon 等</li>
</ul>
</div>
</div>
<script src="/static/js/register_field_tip.js"></script>
</body>
</html>

View File

@@ -71,7 +71,11 @@ class Chapter():
def Focus(self):
self.focus = 1
def load_chapters(markdown, markdown_prompt, score_prompt):
'''
将教案蓝图化解为多个章节Chapter对象
'''
markdown_list = markdown.split("\n")
markdown_prompt_list = markdown_prompt.split("\n")
score_prompt_list = score_prompt.split("\n")

View File

@@ -3,8 +3,10 @@ from flask import Blueprint, jsonify, current_app, send_from_directory
from ..services.markdown_service import (
convert_markdown_to_html, wrap_with_styles,
save_html, copy_images, load_markdown_file
)
from markdown_it import MarkdownIt
from mdit_py_plugins import texmath
bp = Blueprint("markdown", __name__)
@@ -12,15 +14,17 @@ bp = Blueprint("markdown", __name__)
def convert_md(course_id, chapter_name, lesson_name):
print(f"convert_md: {course_id}, {chapter_name}, {lesson_name}")
md_file = load_markdown_file(course_id, chapter_name, lesson_name)
# 转换 markdown -> html
html = convert_markdown_to_html(md_file)
# 配置Markdown解析器,启用数学公式支持
md = MarkdownIt()
md.use(texmath.texmath_plugin) # 添加数学公式插件
# 转换markdown为html包含公式标记
html = md.render(md_file)
# 包装样式时添加MathJax支持用于在浏览器中渲染公式
html_with_styles = wrap_with_styles(html)
# 保存 HTML 文件到 static
html_output_path = os.path.join(current_app.config["STATIC_DIR"], f"{chapter_name}-{lesson_name}.html")
# 保存HTML文件到static
html_output_path = os.path.join(current_app.config["STATIC_DIR"], f"{course_id}-{chapter_name}-{lesson_name}.html")
save_html(html_with_styles, html_output_path)
return jsonify({"html_url": f"/static/{chapter_name}-{lesson_name}.html"})
return jsonify({"html_url": f"/static/{course_id}-{chapter_name}-{lesson_name}.html"})
# 提供静态文件访问
@bp.route("/static/<path:filename>")
@@ -28,3 +32,34 @@ def serve_static(filename):
return send_from_directory(current_app.config["STATIC_DIR"], filename)
def wrap_with_styles(html_content):
# 在HTML中添加MathJax支持
mathjax_script = """
<script src="https://cdn.jsdelivr.net/npm/mathjax@3/es5/tex-mml-chtml.js"></script>
<script>
MathJax.config = {
tex: {
inlineMath: [['$', '$'], ['\\(', '\\)']]
},
svg: {
fontCache: 'global'
}
};
</script>
"""
# 包装HTML内容和样式
return f"""
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>Document</title>
<!-- 你的其他样式 -->
{mathjax_script}
</head>
<body>
{html_content}
</body>
</html>
"""

View File

@@ -59,9 +59,10 @@ def desktop(user_uuid, course_id, chapter_name, lesson_name):
chatmanager = ChatManager()
print(f"now user uuid {user_uuid}")
current_app.extensions["user_uuid2chatmanager"][user_uuid] = chatmanager
code_server_port = 10000 + int(uuid.uuid4().int % 10000)
chatmanager.start_code_server(user_uuid, user_id, path_dir, code_server_port)
# chatmanager.start_code_server(user_uuid, user_id, path_dir, code_server_port)
@@ -119,7 +120,7 @@ def desktop(user_uuid, course_id, chapter_name, lesson_name):
)
# 确保文件的所有权归 user_id
subprocess.run(
["sudo", "chown", f"{user_id}:shared_group_{user_id}", config_path],
["sudo", "chown","-R", f"flask:shared_group_{user_id}", config_path],
check=True
)
subprocess.run(