Version 0.1.1 rise to flask app factory

This commit is contained in:
CakeCN
2025-08-27 19:39:38 +08:00
parent 8dba8688a7
commit 5c0187bc4b
55 changed files with 1708 additions and 613 deletions

View File

@@ -1,497 +1,20 @@
from functools import wraps # app.py 0.1.1 rise to factory mode
from flask import Flask, redirect, session, request, jsonify, render_template, send_from_directory, url_for from bootstrap import bootstrap_paths
from flask_cors import CORS bootstrap_paths()
from flask_socketio import SocketIO, join_room, emit, Namespace from apps import create_app
import markdown from apps.extensions import socketio
import os
import uuid # 工厂创建 app工厂里已经做了加载配置/注册蓝图/注册 namespaces
import shutil app = create_app()
import sys
import json if __name__ == "__main__":
parent_dir = os.path.abspath(os.path.join(os.path.dirname(__file__), '..')) # 仅本地开发用;生产不要用 werkzeug 自带的开发服务器
sys.path.insert(0, parent_dir) socketio.run(
student_workspace_root_path = os.path.abspath(os.path.join(os.path.dirname(__file__), '../study')) app,
host="0.0.0.0",
port=5551,
debug=True, # 开发期打开
allow_unsafe_werkzeug=True # 避免 dev server 的安全限制提示
)
current_dir = os.path.abspath(os.path.join(os.path.dirname(__file__), '.'))
sys.path.insert(0, current_dir)
from AlgoriAgent.projects.algoriAgent.agent_manager import AgentManager
import configparser
from db.user_list import UserList
from db.user import User, load_user_from_json, create_user_json
from db.course_list import CourseList
from db.course import Course, load_course_from_json
GLOBAL_CONFIG = configparser.ConfigParser()
GLOBAL_CONFIG.read('config.ini')
VSCODE_WEB_URL = GLOBAL_CONFIG['VSCODE_WEB']['url']
USER_DATA_DIR = GLOBAL_CONFIG['USER_DATA']['dir']
COURSE_DATA_DIR = GLOBAL_CONFIG['COURSE_DATA']['dir']
app = Flask(__name__)
socketio = SocketIO(app, cors_allowed_origins="*",ping_timeout=60, ping_interval=5)
# socketio = SocketIO(app, cors_allowed_origins="http://localhost:9888") # 设置跨域支持
import logging
app.secret_key = 'cakebaker'
app.logger.setLevel(logging.DEBUG)
# 配置 CORS
CORS(app, resources={r"/*": {"origins": VSCODE_WEB_URL},}, supports_credentials=True)
userid_recorder = {} # user_id&path -> session['user_id']
'''
Backboard
'''
from backboardManager import BackBoardManager, Backboard
backboard_manager = BackBoardManager()
uuid2username = {}
username2uuid = {}
# 定义命名空间:用于和 VSCode 插件交流
class VSCodeNamespace(Namespace):
def on_login(self,data):
print("VSCode client connected")
print(data)
dataconfig = data['config']
user_id = dataconfig.get('user_id')
path = dataconfig.get('path')
course_id = dataconfig.get('course_id')
lesson_id = dataconfig.get('chapter_id')
print(f"User {user_id} connected with path: {path}")
useruuid = username2uuid[user_id]
join_room(useruuid, namespace='/vscode')
if backboard_manager.get_backboard(useruuid) == None:
backboard_manager.add_backboard(useruuid,user_id, course_id, lesson_id, path)
def on_message(self, data):
print(f"Received from VSCode client: {data}")
dataconfig = data['config']
realtime_response(dataconfig, data)
# emit('response', {'message': 'Config data received and connection established'})
def on_disconnect(self, data):
print("VSCode client disconnected")
print("Disconnect reason:"+str(data))
def realtime_response(config, realtime_action):
user_id = config['user_id']
folder_path = config['path']
useruuid = username2uuid[user_id]
bb = backboard_manager.get_backboard(useruuid)
assert type(bb) == Backboard
bb.add_history(realtime_action)
if realtime_action['type'] == 'workspaceFolders':
bb.file_tree = realtime_action['fileTree']
if realtime_action['type'] == 'activeFile':
file_path = realtime_action['filePath']
assert type(file_path) == str
with open(f"{file_path}") as f:
bb.active_file_content = f.read()
if realtime_action['type'] == 'paste':
file_path = realtime_action['filePath']
assert type(file_path) == str
bb.pasted_file_path = file_path
bb.pasted_content = realtime_action['content']
bb.active_file_path = file_path
with open(f"{file_path}") as f:
bb.active_file_content = f.read()
if realtime_action['type'] == 'fileEdit':
file_path = realtime_action['filePath']
assert type(file_path) == str
bb.active_file_path = file_path
with open(f"{file_path}") as f:
bb.active_file_content = f.read()
print("config"+str(config))
print("realtime_action"+str(realtime_action))
pass
'''
Agent and Chat
'''
agent_manager = AgentManager(app = app, socketio = socketio)
user_threads = {}
import threading
from concurrent.futures import ThreadPoolExecutor, as_completed
# 初始化线程池
executor = ThreadPoolExecutor(max_workers=10)
class AgentNamespace(Namespace):
def on_login(self, data):
data = json.loads(data)
user = data['username']
course_id = data['course_id']
chapter_id = data['chapter_id']
user_uuid = username2uuid[user]
session['user_id'] = user_uuid
print(f'User connected with session user_id: {user_uuid}')
# 从markdown、markdown_prompts、score_prompts中各读取数据folder.md并进行new_agent
with open(f'books/markdown/{course_id}/{chapter_id}.md','r',encoding='UTF-8') as fmd,\
open(f'books/markdown_prompts/{course_id}/{chapter_id}.md','r',encoding='UTF-8')as fmdp,\
open(f'books/score_prompts/{course_id}/{chapter_id}.md','r',encoding='UTF-8') as fsp:
markdown = fmd.read()
markdown_prompts = fmdp.read()
score_prompts = fsp.read()
user_uuid, agent = agent_manager.new_agent(course_id, chapter_id, markdown, markdown_prompts, score_prompts, id=user_uuid,
root_path=f'../../study/{user}/{course_id}/{chapter_id}')
print(user_uuid)
join_room(user_uuid, namespace='/agent') # 将该用户加入以user_id为名的room
backboard_manager.add_backboard(user_uuid, user, course_id, chapter_id, root_path=f'../../study/{user}/{course_id}/{chapter_id}')
def on_language(self, language):
id = session.get('user_id')
agent_manager.change_language(id, language)
def on_message(self, data):
print(f"Message from client: {data}")
id = session.get('user_id')
if (type(data)==str):
data = json.loads(data)
print(id)
if data['type'] == 'text':
res = agent_manager.invoke(id, data['data'], backboard_manager.get_backboard(id).get_info_prompt())
print('=*='*20)
print(res.content)
reply = f"{res.content['speak']}"
with app.app_context():
emit('message',reply, room=id, namespace='/agent')
emit('request_function',res.content['function'], room=id, namespace='/agent')
if data['type'] == 'function':
agent_manager.function_call(id, data['data'])
def on_initiative(self,data):
print("User active function call")
user_id = session['user_id']
if data['name'] == 'sample_judge':
agent_manager.sample_judge(user_id, backboard_manager.get_backboard(user_id))
if data['name'] == 'judge':
agent_manager.judge(user_id, backboard_manager.get_backboard(user_id))
def on_disconnect(self,data):
print("VSCode client disconnected")
print("Disconnect reason:"+str(data))
'''
Markdown to HTML
'''
# 配置文件路径
MARKDOWN_DIR = 'books/markdown' # 存放 markdown 文件的文件夹
STATIC_DIR = 'static' # 存放生成的 HTML 和图片资源的文件夹
IMAGE_DIR = 'image' # 图片资源相对于 markdown 的位置
@app.route('/<course_id>-<filename>-markdown', methods=['GET'])
def convert_md(course_id, filename):
md_file_path = os.path.join(MARKDOWN_DIR,course_id, f'{filename}.md')
if not os.path.exists(md_file_path):
return jsonify({'error': 'Markdown file not found'}), 404
# 读取 markdown 文件内容
with open(md_file_path, 'r', encoding='utf-8') as file:
md_content = file.read()
# 将 markdown 转换为 HTML
html_content = markdown.markdown(md_content)
# 插入 CSS 样式,限制图片最大宽度为 100%,高度自动调整
html_with_styles = f"""
<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>
{html_content}
</body>
</html>
"""
# 保存 HTML 文件到 static 文件夹
html_output_path = os.path.join(STATIC_DIR, f'{filename}.html')
with open(html_output_path, 'w', encoding='utf-8') as html_file:
html_file.write(html_with_styles)
# 处理图片资源,将 image/xxx 文件夹拷贝到 static/image/xxx
image_source_dir = os.path.join(MARKDOWN_DIR,course_id, IMAGE_DIR, filename)
image_target_dir = os.path.join(STATIC_DIR, IMAGE_DIR, filename)
print(f"Copying image resources from {image_source_dir} to {image_target_dir}")
if os.path.exists(image_source_dir):
# 确保 static/image/xxx 目录存在
if not os.path.exists(image_target_dir):
os.makedirs(image_target_dir)
# 拷贝图片资源
for image_file in os.listdir(image_source_dir):
full_image_file_path = os.path.join(image_source_dir, image_file)
if os.path.isfile(full_image_file_path):
shutil.copy(full_image_file_path, image_target_dir)
return jsonify({'html_url': f'/static/{filename}.html'})
# 静态文件HTML 和图片资源)的访问
@app.route('/static/<path:filename>')
def serve_static(filename):
return send_from_directory(STATIC_DIR, filename)
'''
Vscode
'''
@app.route('/desktop/<user_id>/<course_id>/<chapter_id>')
def desktop(user_id, course_id, chapter_id):
if 'user_id' not in session:
# return redirect(url_for('login'))
session['user_id'] = 'user_' + str(uuid.uuid4())
print("user "+ user_id + "uuid is"+ session['user_id'])
username2uuid[user_id] = session['user_id']
uuid2username[session['user_id']] = user_id
userid_recorder[user_id+'&'+course_id] = session['user_id']
# 在学习目录下创建一个名为 user_id_path 的文件夹
path_dir = os.path.join(student_workspace_root_path, user_id, course_id,chapter_id)
os.makedirs(path_dir, exist_ok=True)
# 在此文件夹内部创建一个.config文件并写入 user_id=user_id\n path=path
config_path = os.path.join(path_dir, '.config')
if GLOBAL_CONFIG['VSCODE_WEB_PATH']['is_wsl']: # 如果是在 WSL 中运行,则将路径转换为 WSL 路径
path_dir = path_dir.replace('\\', '/')
path_dir = path_dir.replace(GLOBAL_CONFIG['VSCODE_WEB_PATH']['windows_path'] , GLOBAL_CONFIG['VSCODE_WEB_PATH']['wsl_path'])
with open(config_path, 'w', encoding='utf-8') as f:
tmpd = {'user_id': user_id, 'course_id': course_id, 'chapter_id': chapter_id, 'path': path_dir}
json.dump(tmpd, f)
print('---------------------')
print(config_path)
return render_template('desktop.html', vscode_web_url=VSCODE_WEB_URL, user_id=user_id, course_id=course_id, chapter_id=chapter_id, workspace_path=path_dir)
@app.route('/desktop_nouser/<course_id>/<chapter_id>')
def desktop_nouser(course_id, chapter_id):
if 'user_id' not in session:
return redirect(url_for('login'))
user_id = session['user_id']
username = uuid2username[user_id]
return redirect(url_for('desktop', user_id=username, course_id=course_id, chapter_id=chapter_id))
@app.route('/vscode_data', methods=['POST'])
def vscode_data():
data = request.json
config = data['config']
realtime_response(config,data)
print(f"Received data from VSCode: {data}")
return jsonify({"status": "success", "received": data})
@app.route('/logout')
def logout():
session.pop('user_id', None)
return redirect(url_for('login'))
@app.route('/get_session')
def get_session():
user_session = session.get('user_id', 'default_session')
return jsonify({"session": user_session})
'''
Login
'''
users_list = UserList()
course_list = CourseList()
def require_teacher(func):
@wraps(func)
def wrapper(*args, **kwargs):
if 'user_id' not in session or session['user_id'] not in uuid2username:
return redirect(url_for('login'))
user_id = session['user_id']
username = uuid2username[user_id]
if users_list.get_user_is_teacher(username) is None or users_list.get_user_is_teacher(username) == False:
return redirect(url_for('login'))
return func(*args, **kwargs)
return wrapper
def require_user(func):
@wraps(func)
def wrapper(*args, **kwargs):
if 'user_id' not in session or session['user_id'] not in uuid2username:
return redirect(url_for('login'))
return func(*args, **kwargs)
return wrapper
@app.route('/register')
def register():
return render_template('register.html')
@app.route('/register_teacher')
def register_teacher():
return render_template('register_teacher.html')
@app.route('/register_teacher_post', methods=['POST'])
def register_teacher_post():
data = request.get_json()
username = data.get('username')
password = data.get('password')
if users_list.has_user(username) is not None:
users_list.add_user(username, password, teacher=True)
create_user_json(username, USER_DATA_DIR)
return jsonify({'success': True, 'message': '注册成功'})
else: return jsonify({'success': False, 'message': '用户已存在,请更换用户名'})
@app.route('/register_post', methods=['POST'])
def register_post():
data = request.get_json()
username = data.get('username')
password = data.get('password')
if users_list.has_user(username) is not None:
users_list.add_user(username, password)
create_user_json(username, USER_DATA_DIR)
return jsonify({'success': True, 'message': '注册成功'})
else: return jsonify({'success': False, 'message': '用户已存在,请更换用户名'})
@app.route('/login')
def login():
return render_template('login.html')
if 'user_id' not in session:
return render_template('login.html')
else:
return redirect(url_for('/'))
@app.route('/login_post', methods=['POST'])
def login_post():
# 获取请求的 JSON 数据
data = request.get_json()
username = data.get('username')
password = data.get('password')
pswd = users_list.get_user_pswd(username)
if pswd is None:
return jsonify({'success': False, 'message': '用户名或密码错误'})
if pswd != password:
return jsonify({'success': False, 'message': '用户名或密码错误'})
session['user_id'] = 'user_' + str(uuid.uuid4())
print("user "+ username + "uuid is"+ session['user_id'])
username2uuid[username] = session['user_id']
uuid2username[session['user_id']] = username
return jsonify({'success': True, 'message': '登录成功'})
@app.route('/login_teacher_post', methods=['POST'])
def login_teacher_post():
data = request.get_json()
username = data.get('username')
password = data.get('password')
pswd = users_list.get_user_pswd(username)
is_teacher = users_list.get_user_is_teacher(username)
if is_teacher is None or is_teacher == False:
return jsonify({'success': False, 'message': '用户名不存在或非教师账号'})
if pswd is None:
return jsonify({'success': False, 'message': '用户名或密码错误'})
if pswd != password:
return jsonify({'success': False, 'message': '用户名或密码错误'})
session['user_id'] = 'user_' + str(uuid.uuid4())
print("user "+ username + "uuid is"+ session['user_id'])
username2uuid[username] = session['user_id']
uuid2username[session['user_id']] = username
return jsonify({'success': True, 'message': '登录成功'})
@app.route('/teacherboard')
@require_teacher
def teacherboard():
return render_template('teacherboard.html')
user_id2UserClass = {}
'''
DashBoard
'''
@app.route('/dashboard')
@require_user
def dashboard():
user_id = session['user_id']
username = uuid2username[user_id]
if (user_id not in user_id2UserClass):
user_id2UserClass[user_id] = load_user_from_json(username, user_data_dir = USER_DATA_DIR)
user_course_data = []
for course_id in user_id2UserClass[user_id].select_course:
course_brief_info = course_list.get_course_brief_info(course_id, load_course_from_json(course_id, course_data_dir = COURSE_DATA_DIR))
user_course_data.append(course_brief_info)
return render_template('dashboard.html',user_data = user_id2UserClass[user_id].to_json_without_dialog(), user_course_data = json.dumps(user_course_data))
'''
Course
'''
@app.route('/course/<course_id>')
@require_user
def course(course_id):
c = load_course_from_json(course_id, course_data_dir = COURSE_DATA_DIR)
return render_template('course.html', course_id=course_id, course_data=c)
@app.route('/select_course', methods=['POST'])
@require_user
def select_course():
user = user_id2UserClass[session['user_id']]
data = request.get_json()
course_id = data.get('course_id')
if course_id:
user.select_new_course(course_id, load_course_from_json(course_id, COURSE_DATA_DIR))
return jsonify({'success': True, 'message': '课程选择成功'})
@app.route('/')
@require_user
def home_index():
selected_courses=[]
if ('user_id' in session):
if (session['user_id'] not in user_id2UserClass): return redirect(url_for('login'))
user = user_id2UserClass[session['user_id']]
for course_id in user.select_course:
selected_courses.append(course_id)
return render_template('index.html', courses_data = course_list, selected_courses =selected_courses)
# 一些app辅助函数主要提供给Agent与数据库的交互能力
class MyFunction:
def save_chapter_memory(self, id, course_id, lesson_id, subchapter_title, mem_list, score, is_rebuttal):
print("-=-=-=-=-=-=-=-")
print(id, course_id, lesson_id, subchapter_title, mem_list, score, is_rebuttal)
username = uuid2username[id]
u = load_user_from_json(username, user_data_dir = USER_DATA_DIR)
u.save_chapter_memory(course_id, lesson_id, subchapter_title, mem_list, score, is_rebuttal)
app.my_function = MyFunction()
if __name__ == '__main__':
socketio.on_namespace(VSCodeNamespace('/vscode'))
socketio.on_namespace(AgentNamespace('/agent'))
socketio.run(app, host='0.0.0.0', port=5551, debug=False,allow_unsafe_werkzeug=True)

480
Html/app_back.py Normal file
View File

@@ -0,0 +1,480 @@
from bootstrap import bootstrap_paths
bootstrap_paths()
from functools import wraps
from flask import Flask, redirect, session, request, jsonify, render_template, send_from_directory, url_for
from flask_cors import CORS
from flask_socketio import SocketIO, join_room, emit, Namespace
import markdown
import os
import uuid
import shutil
import sys
import json
from apps.auth.decorators import require_role
parent_dir = os.path.abspath(os.path.join(os.path.dirname(__file__), '..'))
sys.path.insert(0, parent_dir)
student_workspace_root_path = os.path.abspath(os.path.join(os.path.dirname(__file__), '../study'))
current_dir = os.path.abspath(os.path.join(os.path.dirname(__file__), '.'))
sys.path.insert(0, current_dir)
from AlgoriAgent.projects.algoriAgent.agent_manager import AgentManager
import configparser
from db.user_list import UserList
from db.user import User, load_user_from_json, create_user_json
from db.course_list import CourseList
from db.course import Course, load_course_from_json
GLOBAL_CONFIG = configparser.ConfigParser()
GLOBAL_CONFIG.read('config.ini')
VSCODE_WEB_URL = GLOBAL_CONFIG['VSCODE_WEB']['url']
USER_DATA_DIR = GLOBAL_CONFIG['USER_DATA']['dir']
COURSE_DATA_DIR = GLOBAL_CONFIG['COURSE_DATA']['dir']
app = Flask(__name__)
socketio = SocketIO(app, cors_allowed_origins="*",ping_timeout=60, ping_interval=5)
# socketio = SocketIO(app, cors_allowed_origins="http://localhost:9888") # 设置跨域支持
import logging
app.secret_key = 'cakebaker'
app.logger.setLevel(logging.DEBUG)
# 配置 CORS
CORS(app, resources={r"/*": {"origins": VSCODE_WEB_URL},}, supports_credentials=True)
userid_recorder = {} # user_id&path -> session['user_id']
'''
Backboard
'''
from backboardManager import BackBoardManager, Backboard
backboard_manager = BackBoardManager()
uuid2username = {}
username2uuid = {}
# 定义命名空间:用于和 VSCode 插件交流
class VSCodeNamespace(Namespace):
def on_login(self,data):
print("VSCode client connected")
print(data)
dataconfig = data['config']
user_id = dataconfig.get('user_id')
path = dataconfig.get('path')
course_id = dataconfig.get('course_id')
lesson_id = dataconfig.get('chapter_id')
print(f"User {user_id} connected with path: {path}")
useruuid = username2uuid[user_id]
join_room(useruuid, namespace='/vscode')
if backboard_manager.get_backboard(useruuid) == None:
backboard_manager.add_backboard(useruuid,user_id, course_id, lesson_id, path)
def on_message(self, data):
print(f"Received from VSCode client: {data}")
dataconfig = data['config']
realtime_response(dataconfig, data)
# emit('response', {'message': 'Config data received and connection established'})
def on_disconnect(self, data):
print("VSCode client disconnected")
print("Disconnect reason:"+str(data))
def realtime_response(config, realtime_action):
user_id = config['user_id']
folder_path = config['path']
useruuid = username2uuid[user_id]
bb = backboard_manager.get_backboard(useruuid)
assert type(bb) == Backboard
bb.add_history(realtime_action)
if realtime_action['type'] == 'workspaceFolders':
bb.file_tree = realtime_action['fileTree']
if realtime_action['type'] == 'activeFile':
file_path = realtime_action['filePath']
assert type(file_path) == str
with open(f"{file_path}") as f:
bb.active_file_content = f.read()
if realtime_action['type'] == 'paste':
file_path = realtime_action['filePath']
assert type(file_path) == str
bb.pasted_file_path = file_path
bb.pasted_content = realtime_action['content']
bb.active_file_path = file_path
with open(f"{file_path}") as f:
bb.active_file_content = f.read()
if realtime_action['type'] == 'fileEdit':
file_path = realtime_action['filePath']
assert type(file_path) == str
bb.active_file_path = file_path
with open(f"{file_path}") as f:
bb.active_file_content = f.read()
print("config"+str(config))
print("realtime_action"+str(realtime_action))
pass
'''
Agent and Chat
'''
agent_manager = AgentManager(app = app, socketio = socketio)
user_threads = {}
import threading
from concurrent.futures import ThreadPoolExecutor, as_completed
# 初始化线程池
executor = ThreadPoolExecutor(max_workers=10)
class AgentNamespace(Namespace):
def on_login(self, data):
data = json.loads(data)
user = data['username']
course_id = data['course_id']
chapter_id = data['chapter_id']
user_uuid = username2uuid[user]
session['user_id'] = user_uuid
print(f'User connected with session user_id: {user_uuid}')
# 从markdown、markdown_prompts、score_prompts中各读取数据folder.md并进行new_agent
with open(f'books/markdown/{course_id}/{chapter_id}.md','r',encoding='UTF-8') as fmd,\
open(f'books/markdown_prompts/{course_id}/{chapter_id}.md','r',encoding='UTF-8')as fmdp,\
open(f'books/score_prompts/{course_id}/{chapter_id}.md','r',encoding='UTF-8') as fsp:
markdown = fmd.read()
markdown_prompts = fmdp.read()
score_prompts = fsp.read()
user_uuid, agent = agent_manager.new_agent(course_id, chapter_id, markdown, markdown_prompts, score_prompts, id=user_uuid,
root_path=f'../../study/{user}/{course_id}/{chapter_id}')
print(user_uuid)
join_room(user_uuid, namespace='/agent') # 将该用户加入以user_id为名的room
backboard_manager.add_backboard(user_uuid, user, course_id, chapter_id, root_path=f'../../study/{user}/{course_id}/{chapter_id}')
def on_language(self, language):
id = session.get('user_id')
agent_manager.change_language(id, language)
def on_message(self, data):
print(f"Message from client: {data}")
id = session.get('user_id')
if (type(data)==str):
data = json.loads(data)
print(id)
if data['type'] == 'text':
res = agent_manager.invoke(id, data['data'], backboard_manager.get_backboard(id).get_info_prompt())
print('=*='*20)
print(res.content)
reply = f"{res.content['speak']}"
with app.app_context():
emit('message',reply, room=id, namespace='/agent')
emit('request_function',res.content['function'], room=id, namespace='/agent')
if data['type'] == 'function':
agent_manager.function_call(id, data['data'])
def on_initiative(self,data):
print("User active function call")
user_id = session['user_id']
if data['name'] == 'sample_judge':
agent_manager.sample_judge(user_id, backboard_manager.get_backboard(user_id))
if data['name'] == 'judge':
agent_manager.judge(user_id, backboard_manager.get_backboard(user_id))
def on_disconnect(self,data):
print("VSCode client disconnected")
print("Disconnect reason:"+str(data))
'''
Markdown to HTML
'''
# 配置文件路径
MARKDOWN_DIR = 'books/markdown' # 存放 markdown 文件的文件夹
STATIC_DIR = 'static' # 存放生成的 HTML 和图片资源的文件夹
IMAGE_DIR = 'image' # 图片资源相对于 markdown 的位置
@app.route('/<course_id>-<filename>-markdown', methods=['GET'])
def convert_md(course_id, filename):
md_file_path = os.path.join(MARKDOWN_DIR,course_id, f'{filename}.md')
if not os.path.exists(md_file_path):
return jsonify({'error': 'Markdown file not found'}), 404
# 读取 markdown 文件内容
with open(md_file_path, 'r', encoding='utf-8') as file:
md_content = file.read()
# 将 markdown 转换为 HTML
html_content = markdown.markdown(md_content)
# 插入 CSS 样式,限制图片最大宽度为 100%,高度自动调整
html_with_styles = f"""
<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>
{html_content}
</body>
</html>
"""
# 保存 HTML 文件到 static 文件夹
html_output_path = os.path.join(STATIC_DIR, f'{filename}.html')
with open(html_output_path, 'w', encoding='utf-8') as html_file:
html_file.write(html_with_styles)
# 处理图片资源,将 image/xxx 文件夹拷贝到 static/image/xxx
image_source_dir = os.path.join(MARKDOWN_DIR,course_id, IMAGE_DIR, filename)
image_target_dir = os.path.join(STATIC_DIR, IMAGE_DIR, filename)
print(f"Copying image resources from {image_source_dir} to {image_target_dir}")
if os.path.exists(image_source_dir):
# 确保 static/image/xxx 目录存在
if not os.path.exists(image_target_dir):
os.makedirs(image_target_dir)
# 拷贝图片资源
for image_file in os.listdir(image_source_dir):
full_image_file_path = os.path.join(image_source_dir, image_file)
if os.path.isfile(full_image_file_path):
shutil.copy(full_image_file_path, image_target_dir)
return jsonify({'html_url': f'/static/{filename}.html'})
# 静态文件HTML 和图片资源)的访问
@app.route('/static/<path:filename>')
def serve_static(filename):
return send_from_directory(STATIC_DIR, filename)
'''
Vscode
'''
@app.route('/desktop/<user_id>/<course_id>/<chapter_id>')
def desktop(user_id, course_id, chapter_id):
if 'user_id' not in session:
# return redirect(url_for('login'))
session['user_id'] = 'user_' + str(uuid.uuid4())
print("user "+ user_id + "uuid is"+ session['user_id'])
username2uuid[user_id] = session['user_id']
uuid2username[session['user_id']] = user_id
userid_recorder[user_id+'&'+course_id] = session['user_id']
# 在学习目录下创建一个名为 user_id_path 的文件夹
path_dir = os.path.join(student_workspace_root_path, user_id, course_id,chapter_id)
os.makedirs(path_dir, exist_ok=True)
# 在此文件夹内部创建一个.config文件并写入 user_id=user_id\n path=path
config_path = os.path.join(path_dir, '.config')
if GLOBAL_CONFIG['VSCODE_WEB_PATH']['is_wsl']: # 如果是在 WSL 中运行,则将路径转换为 WSL 路径
path_dir = path_dir.replace('\\', '/')
path_dir = path_dir.replace(GLOBAL_CONFIG['VSCODE_WEB_PATH']['windows_path'] , GLOBAL_CONFIG['VSCODE_WEB_PATH']['wsl_path'])
with open(config_path, 'w', encoding='utf-8') as f:
tmpd = {'user_id': user_id, 'course_id': course_id, 'chapter_id': chapter_id, 'path': path_dir}
json.dump(tmpd, f)
print('---------------------')
print(config_path)
return render_template('desktop.html', vscode_web_url=VSCODE_WEB_URL, user_id=user_id, course_id=course_id, chapter_id=chapter_id, workspace_path=path_dir)
@app.route('/desktop_nouser/<course_id>/<chapter_id>')
def desktop_nouser(course_id, chapter_id):
if 'user_id' not in session:
return redirect(url_for('login'))
user_id = session['user_id']
username = uuid2username[user_id]
return redirect(url_for('desktop', user_id=username, course_id=course_id, chapter_id=chapter_id))
@app.route('/vscode_data', methods=['POST'])
def vscode_data():
data = request.json
config = data['config']
realtime_response(config,data)
print(f"Received data from VSCode: {data}")
return jsonify({"status": "success", "received": data})
'''
Login
'''
users_list = UserList()
course_list = CourseList()
@app.route('/register')
def register():
return render_template('register.html')
@app.route('/register_teacher')
def register_teacher():
return render_template('register_teacher.html')
@app.route('/register_teacher_post', methods=['POST'])
def register_teacher_post():
data = request.get_json()
username = data.get('username')
password = data.get('password')
if users_list.has_user(username) is not None:
users_list.add_user(username, password, teacher=True)
create_user_json(username, USER_DATA_DIR)
return jsonify({'success': True, 'message': '注册成功'})
else: return jsonify({'success': False, 'message': '用户已存在,请更换用户名'})
@app.route('/register_post', methods=['POST'])
def register_post():
data = request.get_json()
username = data.get('username')
password = data.get('password')
if users_list.has_user(username) is not None:
users_list.add_user(username, password)
create_user_json(username, USER_DATA_DIR)
return jsonify({'success': True, 'message': '注册成功'})
else: return jsonify({'success': False, 'message': '用户已存在,请更换用户名'})
@app.route('/login')
def login():
return render_template('login.html')
if 'user_id' not in session:
return render_template('login.html')
else:
return redirect(url_for('/'))
@app.route('/login_post', methods=['POST'])
def login_post():
# 获取请求的 JSON 数据
data = request.get_json()
username = data.get('username')
password = data.get('password')
pswd = users_list.get_user_pswd(username)
if pswd is None:
return jsonify({'success': False, 'message': '用户名或密码错误'})
if pswd != password:
return jsonify({'success': False, 'message': '用户名或密码错误'})
session['user_id'] = 'user_' + str(uuid.uuid4())
print("user "+ username + "uuid is"+ session['user_id'])
username2uuid[username] = session['user_id']
uuid2username[session['user_id']] = username
return jsonify({'success': True, 'message': '登录成功'})
@app.route('/login_teacher_post', methods=['POST'])
def login_teacher_post():
data = request.get_json()
username = data.get('username')
password = data.get('password')
pswd = users_list.get_user_pswd(username)
is_teacher = users_list.get_user_is_teacher(username)
if is_teacher is None or is_teacher == False:
return jsonify({'success': False, 'message': '用户名不存在或非教师账号'})
if pswd is None:
return jsonify({'success': False, 'message': '用户名或密码错误'})
if pswd != password:
return jsonify({'success': False, 'message': '用户名或密码错误'})
session['user_id'] = 'user_' + str(uuid.uuid4())
print("user "+ username + "uuid is"+ session['user_id'])
username2uuid[username] = session['user_id']
uuid2username[session['user_id']] = username
return jsonify({'success': True, 'message': '登录成功'})
@app.route('/teacherboard')
@require_role(roles="teacher")
def teacherboard():
return render_template('teacherboard.html')
@app.route('/logout')
def logout():
session.pop('user_id', None)
return redirect(url_for('login'))
@app.route('/get_session')
def get_session():
user_session = session.get('user_id', 'default_session')
return jsonify({"session": user_session})
'''
DashBoard
'''
user_id2UserClass = {}
@app.route('/dashboard')
@require_role
def dashboard():
user_id = session['user_id']
username = uuid2username[user_id]
if (user_id not in user_id2UserClass):
user_id2UserClass[user_id] = load_user_from_json(username, user_data_dir = USER_DATA_DIR)
user_course_data = []
for course_id in user_id2UserClass[user_id].select_course:
course_brief_info = course_list.get_course_brief_info(course_id, load_course_from_json(course_id, course_data_dir = COURSE_DATA_DIR))
user_course_data.append(course_brief_info)
return render_template('dashboard.html',user_data = user_id2UserClass[user_id].to_json_without_dialog(), user_course_data = json.dumps(user_course_data))
'''
Course
'''
@app.route('/course/<course_id>')
@require_role
def course(course_id):
c = load_course_from_json(course_id, course_data_dir = COURSE_DATA_DIR)
return render_template('course.html', course_id=course_id, course_data=c)
@app.route('/select_course', methods=['POST'])
@require_role
def select_course():
user = user_id2UserClass[session['user_id']]
data = request.get_json()
course_id = data.get('course_id')
if course_id:
user.select_new_course(course_id, load_course_from_json(course_id, COURSE_DATA_DIR))
return jsonify({'success': True, 'message': '课程选择成功'})
@app.route('/')
@require_role
def home_index():
selected_courses=[]
if ('user_id' in session):
if (session['user_id'] not in user_id2UserClass): return redirect(url_for('login'))
user = user_id2UserClass[session['user_id']]
for course_id in user.select_course:
selected_courses.append(course_id)
return render_template('index.html', courses_data = course_list, selected_courses =selected_courses)
# 一些app辅助函数主要提供给Agent与数据库的交互能力
class MyFunction:
def save_chapter_memory(self, id, course_id, lesson_id, subchapter_title, mem_list, score, is_rebuttal):
print("-=-=-=-=-=-=-=-")
print(id, course_id, lesson_id, subchapter_title, mem_list, score, is_rebuttal)
username = uuid2username[id]
u = load_user_from_json(username, user_data_dir = USER_DATA_DIR)
u.save_chapter_memory(course_id, lesson_id, subchapter_title, mem_list, score, is_rebuttal)
app.my_function = MyFunction()
if __name__ == '__main__':
socketio.on_namespace(VSCodeNamespace('/vscode'))
socketio.on_namespace(AgentNamespace('/agent'))
socketio.run(app, host='0.0.0.0', port=5551, debug=False,allow_unsafe_werkzeug=True)

34
Html/apps/__init__.py Normal file
View File

@@ -0,0 +1,34 @@
import logging
from flask import Flask
from .config import Config
from .extensions import init_extensions, register_namespaces
from .views import register_blueprints
def create_app(config_object: type = Config) -> Flask:
app = Flask(__name__, template_folder="templates", static_folder="static")
app.config.from_object(config_object)
# secret_key 从 config 里来
app.secret_key = app.config["SECRET_KEY"]
# logging
app.logger.setLevel(logging.DEBUG)
register_namespaces(app)
# 1) 初始化第三方扩展db、cache、jwt、cors 等)
init_extensions(app)
# 2) 注册蓝图(把子文件暴露的蓝图统一挂到 app 上)
register_blueprints(app)
# 3) 其他钩子/命令/错误处理
@app.route("/healthz")
def healthz():
return {"status": "ok"}
return app

View File

@@ -0,0 +1,98 @@
# myapp/auth/decorators.py
from functools import wraps
from typing import Callable, Optional, Union, Iterable
from flask import session, redirect, url_for, current_app
RoleType = Union[str, Iterable[str]]
RoleChecker = Callable[[str, RoleType], bool] # (username, required_roles) -> bool
def _get_deps():
"""从 app 注入点拿依赖,避免循环导入。"""
app = current_app
uuid2username = (
app.config.get("UUID2USERNAME")
or app.extensions.get("uuid2username")
)
users_list = (
app.config.get("USERS_LIST")
or app.extensions.get("users_list")
)
role_checker: Optional[RoleChecker] = (
app.config.get("ROLE_CHECKER")
or app.extensions.get("role_checker")
)
return uuid2username, users_list, role_checker
def require_role(
view=None,
*,
# 未登录时跳转到哪个 endpoint
login_endpoint: str = "auth.login",
# 角色要求None/空 -> 仅需登录;"teacher" 或 ["teacher", "admin"] -> 需具备其中之一
roles: Optional[RoleType] = None,
# 可选:自定义角色校验函数(优先级高于内置 users_list 适配)
checker: Optional[RoleChecker] = None,
):
"""
用法:
1) 仅需登录(等价于 require_user
@require_role
2) 需要 teacher 角色(等价于 require_teacher
@require_role(roles="teacher")
3) 需要多个角色之一:
@require_role(roles=["teacher", "admin"])
4) 自定义校验器(例如 RBAC/权限码):
@require_role(roles="teacher", checker=my_checker)
也支持指定登录端点:
@require_role(login_endpoint="login")
"""
def decorator(func):
@wraps(func)
def wrapper(*args, **kwargs):
uuid2username, users_list, injected_checker = _get_deps()
user_id = session.get("user_id")
if not user_id or uuid2username is None or user_id not in uuid2username:
return redirect(url_for(login_endpoint))
# 仅需登录
if not roles:
return func(*args, **kwargs)
username = uuid2username[user_id]
# 选择校验器优先级:参数 checker > app 注入的 role_checker > 内置 users_list 适配
effective_checker = checker or injected_checker
# 内置对 users_list 的向后兼容(你的现有接口)
if effective_checker is None:
def builtin_checker(u: str, required: RoleType) -> bool:
# 支持 teacher 场景(原先的 get_user_is_teacher
def has_teacher(u_: str) -> bool:
if users_list is None:
return False
getter = getattr(users_list, "get_user_is_teacher", None)
return bool(getter and getter(u_) is True)
def match_one(required_role: str) -> bool:
if required_role == "teacher":
return has_teacher(u)
# 你也可以在此扩展更多内置角色判断
# 比如 get_user_is_admin / get_user_roles 等
# 未知角色默认 False
return False
if isinstance(required, str):
return match_one(required)
return any(match_one(r) for r in required)
effective_checker = builtin_checker
ok = effective_checker(username, roles)
if not ok:
return redirect(url_for(login_endpoint))
return func(*args, **kwargs)
return wrapper
return decorator(view) if callable(view) else decorator

1
Html/apps/bootstrap.py Normal file
View File

@@ -0,0 +1 @@
# Html/apps/bootstrap.py

37
Html/apps/config.py Normal file
View File

@@ -0,0 +1,37 @@
# app/config.py
import configparser
import os
BASE_DIR = os.path.abspath(os.path.join(os.path.dirname(__file__),'..'))
GLOBAL_CONFIG = configparser.ConfigParser()
GLOBAL_CONFIG.read(os.getenv("APP_CONFIG_FILE", "config.ini"))
class Config:
SECRET_KEY = os.getenv("SECRET_KEY", "dev-secret")
SQLALCHEMY_DATABASE_URI = os.getenv("DATABASE_URL", "sqlite:///dev.db")
SQLALCHEMY_TRACK_MODIFICATIONS = False
SECRET_KEY = os.getenv("SECRET_KEY", "cakebaker") # 覆盖优先
VSCODE_WEB_URL = GLOBAL_CONFIG['VSCODE_WEB']['url']
USER_DATA_DIR = GLOBAL_CONFIG['USER_DATA']['dir']
COURSE_DATA_DIR = GLOBAL_CONFIG['COURSE_DATA']['dir']
# socketio / cors 配置也可以放这里
SOCKETIO_PING_TIMEOUT = 60
SOCKETIO_PING_INTERVAL = 5
MARKDOWN_DIR = os.path.join(BASE_DIR, "books", "markdown")
STATIC_DIR = os.path.join(BASE_DIR, "static")
IMAGE_DIR = "image"
# 你的学生工作区根目录(如无就放到项目 data 目录)
STUDENT_WORKSPACE_ROOT = os.getenv(
"STUDENT_WORKSPACE_ROOT",
os.path.join(BASE_DIR, "data", "workspaces")
)
# WSL 相关
VSCODE_WEB_PATH = {
"is_wsl": GLOBAL_CONFIG.getboolean("VSCODE_WEB_PATH", "is_wsl", fallback=False),
"windows_path": GLOBAL_CONFIG.get("VSCODE_WEB_PATH", "windows_path", fallback=""),
"wsl_path": GLOBAL_CONFIG.get("VSCODE_WEB_PATH", "wsl_path", fallback=""),
}

59
Html/apps/extensions.py Normal file
View File

@@ -0,0 +1,59 @@
import os, sys
from flask_sqlalchemy import SQLAlchemy
from flask_socketio import SocketIO
from flask_cors import CORS
from db.user_list import UserList
from db.course_list import CourseList
from .services.my_function import MyFunction
db = SQLAlchemy()
socketio = SocketIO(cors_allowed_origins="*") # 不直接传 app
cors = CORS()
# ===== 你的全局对象 =====
# Backboard
from backboardManager import BackBoardManager # 你已有的类
backboard_manager = BackBoardManager()
# 用户映射(全局内存结构)
userid_recorder = {} # 你代码里有使用
users_list = UserList()
uuid2username = {} # 你的映射,或换成数据库访问层
username2uuid = {}
course_list = CourseList()
user_id2UserClass = {}
from AlgoriAgent.projects.algoriAgent.agent_manager import AgentManager
agent_manager = AgentManager()
def init_extensions(app):
db.init_app(app)
# 第三方
socketio.init_app(
app,
cors_allowed_origins='*',#app.config["VSCODE_WEB_URL"],
ping_timeout=app.config["SOCKETIO_PING_TIMEOUT"],
ping_interval=app.config["SOCKETIO_PING_INTERVAL"],
)
cors.init_app(
app,
resources={r"/*": {"origins": app.config["VSCODE_WEB_URL"]}},
supports_credentials=True,
)
# 把自定义对象挂到 app.extensions供各处通过 current_app 访问
app.extensions["users_list"] = users_list
app.extensions["course_list"] = course_list
app.extensions["backboard_manager"] = backboard_manager
app.extensions["uuid2username"] = uuid2username
app.extensions["username2uuid"] = username2uuid
app.extensions["userid_recorder"] = userid_recorder
app.extensions["user_id2UserClass"] = user_id2UserClass
app.extensions["agent_manager"] = agent_manager
app.extensions["my_function"] = MyFunction()
def register_namespaces(app):
"""把所有 Socket.IO namespaces 注册到 socketio"""
# 延迟导入以避免循环
from .sockets.namespaces import VSCodeNamespace, AgentNamespace
socketio.on_namespace(VSCodeNamespace("/vscode"))
socketio.on_namespace(AgentNamespace("/agent"))

View File

Before

Width:  |  Height:  |  Size: 8.3 KiB

After

Width:  |  Height:  |  Size: 8.3 KiB

View File

@@ -0,0 +1,46 @@
# myapp/services/auth_service.py
import os, uuid, json
from flask import current_app, session
def create_user_json(username: str):
user_dir = current_app.config["USER_DATA_DIR"]
os.makedirs(user_dir, exist_ok=True)
path = os.path.join(user_dir, f"{username}.json")
if not os.path.exists(path):
with open(path, "w", encoding="utf-8") as f:
json.dump({"username": username}, f, ensure_ascii=False)
def register_user(username: str, password: str, *, teacher: bool=False):
users_list = current_app.extensions["users_list"]
# 你原逻辑似乎把 has_user 的判断反了:直觉上「不存在才允许新增」
exists = users_list.has_user(username)
if exists:
return False, "用户已存在,请更换用户名"
users_list.add_user(username, password, teacher=teacher)
create_user_json(username)
return True, "注册成功"
def login_user(username: str, password: str, *, require_teacher: bool=False):
users_list = current_app.extensions["users_list"]
pswd = users_list.get_user_pswd(username)
if pswd is None or pswd != password:
return False, "用户名或密码错误"
if require_teacher:
is_teacher = users_list.get_user_is_teacher(username)
if not is_teacher:
return False, "用户名不存在或非教师账号"
# 设置会话 + 关联映射
user_uuid = "user_" + str(uuid.uuid4())
session["user_id"] = user_uuid
username2uuid = current_app.extensions["username2uuid"]
uuid2username = current_app.extensions["uuid2username"]
username2uuid[username] = user_uuid
uuid2username[user_uuid] = username
return True, "登录成功"
def logout_user():
session.pop("user_id", None)

View File

@@ -0,0 +1,59 @@
# myapp/services/backboard_service.py
import os
from flask import current_app
from backboardManager import Backboard # 你已有的类
def _to_wsl_path_if_needed(path: str) -> str:
cfg = current_app.config["VSCODE_WEB_PATH"]
if cfg.get("is_wsl") and cfg.get("windows_path") and cfg.get("wsl_path"):
return path.replace("\\", "/").replace(cfg["windows_path"], cfg["wsl_path"])
return path
def realtime_response(config: dict, realtime_action: dict) -> None:
"""
业务:根据 VSCode 上报的动作,更新 Backboard黑板状态。
"""
# 依赖从 app.extensions 取,避免循环导入
username2uuid = current_app.extensions["username2uuid"]
backboard_manager = current_app.extensions["backboard_manager"]
user_id = config["user_id"]
folder_path = config["path"] # 如需 WSL 转换可在生成 config 时处理
useruuid = username2uuid[user_id]
bb = backboard_manager.get_backboard(useruuid)
assert isinstance(bb, Backboard)
# 统一记历史
bb.add_history(realtime_action)
rtype = realtime_action.get("type")
if rtype == "workspaceFolders":
bb.file_tree = realtime_action.get("fileTree")
elif rtype == "activeFile":
file_path = realtime_action.get("filePath")
assert isinstance(file_path, str)
with open(file_path, "r", encoding="utf-8") as f:
bb.active_file_content = f.read()
bb.active_file_path = file_path
elif rtype == "paste":
file_path = realtime_action.get("filePath")
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(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(file_path, "r", encoding="utf-8") as f:
bb.active_file_content = f.read()
# 如需调试输出,可在这里统一记录日志
current_app.logger.debug("vscode config: %s", config)
current_app.logger.debug("vscode action: %s", realtime_action)

View File

@@ -0,0 +1,20 @@
# myapp/services/course_service.py
from flask import current_app
# 假设你已有这两个 loader
from db.course import load_course_from_json
def load_course(course_id: str):
return load_course_from_json(
course_id, course_data_dir=current_app.config["COURSE_DATA_DIR"]
)
def user_selected_course_briefs(user_obj):
"""根据用户对象的选课列表,拼装课程简要信息数组"""
course_list = current_app.extensions["course_list"]
briefs = []
for cid in getattr(user_obj, "select_course", []):
course_obj = load_course(cid)
brief = course_list.get_course_brief_info(cid, course_obj)
briefs.append(brief)
return briefs

View File

@@ -0,0 +1,40 @@
import os, shutil, markdown
def convert_markdown_to_html(md_file_path: str) -> str:
"""读取 markdown 并转成 HTML 片段"""
with open(md_file_path, 'r', encoding='utf-8') as f:
md_content = f.read()
return markdown.markdown(md_content)
def wrap_with_styles(html_content: str) -> str:
"""加上 CSS 样式和外层 HTML"""
return f"""
<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">
{html_content}
</body>
</html>
"""
def save_html(html_str: str, output_path: str) -> None:
os.makedirs(os.path.dirname(output_path), exist_ok=True)
with open(output_path, "w", encoding="utf-8") as f:
f.write(html_str)
def copy_images(source_dir: str, target_dir: str) -> None:
if not os.path.exists(source_dir):
return
os.makedirs(target_dir, exist_ok=True)
for filename in os.listdir(source_dir):
src = os.path.join(source_dir, filename)
if os.path.isfile(src):
shutil.copy(src, target_dir)

View File

@@ -0,0 +1,26 @@
# myapp/services/memory_service.py
import os, json
from flask import current_app
from db.user import load_user_from_json # 按你项目实际替换
def save_chapter_memory_by_uuid(
user_uuid: str,
course_id: str,
lesson_id: str,
subchapter_title: str,
mem_list,
score,
is_rebuttal: bool,
):
uuid2username = current_app.extensions["uuid2username"]
user_map = current_app.extensions["user_id2UserClass"] # 可复用缓存
user_data_dir = current_app.config["USER_DATA_DIR"]
username = uuid2username[user_uuid]
# 若内存里已有用户对象就直接用;否则从 JSON 载入
user_obj = user_map.get(user_uuid) or load_user_from_json(username, user_data_dir=user_data_dir)
user_map[user_uuid] = user_obj
user_obj.save_chapter_memory(
course_id, lesson_id, subchapter_title, mem_list, score, is_rebuttal
)

View File

@@ -0,0 +1,10 @@
# myapp/services/my_function.py
from flask import current_app
from .memory_service import save_chapter_memory_by_uuid
class MyFunction:
def save_chapter_memory(self, user_uuid, course_id, lesson_id, subchapter_title, mem_list, score, is_rebuttal):
current_app.logger.debug("save_chapter_memory %s %s %s", user_uuid, course_id, lesson_id)
save_chapter_memory_by_uuid(
user_uuid, course_id, lesson_id, subchapter_title, mem_list, score, is_rebuttal
)

View File

@@ -0,0 +1,42 @@
# myapp/services/user_service.py
import os
from typing import Tuple
from flask import current_app, session
# 假设你已有这两个 loader
from db.user import load_user_from_json
def _username_from_session() -> str:
"""通过 session['user_id'] -> username"""
user_uuid = session.get("user_id")
if not user_uuid:
return None
uuid2username = current_app.extensions["uuid2username"]
return uuid2username.get(user_uuid)
def get_or_load_current_user():
"""
返回当前用户对象(如 user_id2UserClass 中没有则从 JSON 载入并缓存)
"""
user_uuid = session.get("user_id")
if not user_uuid:
return None
username = _username_from_session()
if not username:
return None
user_map = current_app.extensions["user_id2UserClass"]
if user_uuid not in user_map:
user_map[user_uuid] = load_user_from_json(
username, user_data_dir=current_app.config["USER_DATA_DIR"]
)
return user_map[user_uuid]
def add_course_for_current_user(course_id: str, course_data):
user_obj = get_or_load_current_user()
if user_obj is None:
return False
# 你的 UserClass 应该有 select_new_course 接口
user_obj.select_new_course(course_id, course_data)
return True

View File

@@ -0,0 +1,104 @@
# myapp/sockets/namespaces.py
import json
from flask import current_app, request, session
from flask_socketio import Namespace, join_room, leave_room, emit
from ..services.memory_service import save_chapter_memory_by_uuid
from ..services.backboard_service import realtime_response
class VSCodeNamespace(Namespace):
def on_login(self,data):
ex = current_app.extensions
username2uuid = ex["username2uuid"]
backboard_manager = ex["backboard_manager"]
print("VSCode client connected")
print(data)
dataconfig = data['config']
user_id = dataconfig.get('user_id')
path = dataconfig.get('path')
course_id = dataconfig.get('course_id')
lesson_id = dataconfig.get('chapter_id')
print(f"User {user_id} connected with path: {path}")
useruuid = username2uuid[user_id]
join_room(useruuid, namespace='/vscode')
if backboard_manager.get_backboard(useruuid) == None:
backboard_manager.add_backboard(useruuid,user_id, course_id, lesson_id, path)
def on_message(self, data):
print(f"Received from VSCode client: {data}")
dataconfig = data['config']
realtime_response(dataconfig, data)
# emit('response', {'message': 'Config data received and connection established'})
def on_disconnect(self, data):
print("VSCode client disconnected")
print("Disconnect reason:"+str(data))
class AgentNamespace(Namespace):
def on_login(self, data):
ex = current_app.extensions
username2uuid = ex["username2uuid"]
backboard_manager = ex["backboard_manager"]
agent_manager = ex["agent_manager"]
data = json.loads(data)
user = data['username']
course_id = data['course_id']
chapter_id = data['chapter_id']
user_uuid = username2uuid[user]
session['user_id'] = user_uuid
print(f'User connected with session user_id: {user_uuid}')
# 从markdown、markdown_prompts、score_prompts中各读取数据folder.md并进行new_agent
with open(f'books/markdown/{course_id}/{chapter_id}.md','r',encoding='UTF-8') as fmd,\
open(f'books/markdown_prompts/{course_id}/{chapter_id}.md','r',encoding='UTF-8')as fmdp,\
open(f'books/score_prompts/{course_id}/{chapter_id}.md','r',encoding='UTF-8') as fsp:
markdown = fmd.read()
markdown_prompts = fmdp.read()
score_prompts = fsp.read()
user_uuid, agent = agent_manager.new_agent(course_id, chapter_id, markdown, markdown_prompts, score_prompts, id=user_uuid,
root_path=f'../../study/{user}/{course_id}/{chapter_id}')
print(user_uuid)
join_room(user_uuid, namespace='/agent') # 将该用户加入以user_id为名的room
backboard_manager.add_backboard(user_uuid, user, course_id, chapter_id, root_path=f'../../study/{user}/{course_id}/{chapter_id}')
def on_language(self, language):
ex = current_app.extensions
agent_manager = ex["agent_manager"]
id = session.get('user_id')
agent_manager.change_language(id, language)
def on_message(self, data):
print(f"Message from client: {data}")
ex = current_app.extensions
agent_manager = ex["agent_manager"]
backboard_manager = ex["backboard_manager"]
id = session.get('user_id')
if (type(data)==str):
data = json.loads(data)
print(id)
if data['type'] == 'text':
res = agent_manager.invoke(id, data['data'], backboard_manager.get_backboard(id).get_info_prompt())
print('=*='*20)
print(res.content)
reply = f"{res.content['speak']}"
with current_app.app_context():
emit('message',reply, room=id, namespace='/agent')
emit('request_function',res.content['function'], room=id, namespace='/agent')
if data['type'] == 'function':
agent_manager.function_call(id, data['data'])
def on_initiative(self,data):
print("User active function call")
ex = current_app.extensions
agent_manager = ex["agent_manager"]
backboard_manager = ex["backboard_manager"]
user_id = session['user_id']
if data['name'] == 'sample_judge':
agent_manager.sample_judge(user_id, backboard_manager.get_backboard(user_id))
if data['name'] == 'judge':
agent_manager.judge(user_id, backboard_manager.get_backboard(user_id))
def on_disconnect(self,data):
print("VSCode client disconnected")
print("Disconnect reason:"+str(data))

View File

@@ -0,0 +1,53 @@
<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>
<h1>二分查找与二分答案</h1>
<h2>二分查找</h2>
<h3>引入</h3>
<p>二分是一个很简单基础,但很重要的知识点,为以后许多高级的数据结构与算法铺垫。</p>
<p>下面是一个用二分的简单场景:</p>
<p>假设小明从0到1000之间选择了一个数字但不告诉你你可以不断猜测这个数每次猜测小明会告知你的猜测得过大还是过小问最多几次就一定能猜中</p>
<p>答案是利用二分查找的原理猜测11次即可。</p>
<ol>
<li>对于0到1000的答案备选区猜测中位数500假设过小</li>
<li>则对于501到1000的答案备选区猜测750假设过大</li>
<li>则对于501到749的答案备选区猜测625假设过小</li>
<li>则对于626到749区间......</li>
<li>688-749</li>
<li>718-749</li>
<li>734-749</li>
<li>742-749</li>
<li>746-749</li>
<li>748-749</li>
<li>749-749</li>
</ol>
<p>在最差的情况下第11次的答案备选区就一定长度为1了也就是必然是答案。</p>
<p>因此如果序列是有序的,就可以通过二分查找快速定位所需要的数据。</p>
<h4>思考题询问Agent以学习计算方法或验证你的答案</h4>
<p>对于上面那个题目如果问题区间是1到4000最差情况下需要猜测几次</p>
<h3>练习:二分查找</h3>
<p>试试对于下面的题目,用代码实现一下二分查找。</p>
<h4>题目:有序数组寻址</h4>
<p>给出一个长度为n的有序数组从小到大有q次询问对于每次询问输出指定数在数组中的下标。如果不存在则输出-1。</p>
<h5>输入</h5>
<p>第一行一个整数n。(1&lt;=n&lt;=10^5)</p>
<p>第二行n个用空格分开的整数ai。(0&lt;=ai&lt;=10^8)</p>
<p>第三行一个整数q表示询问的次数。(1&lt;=q&lt;=10^4)</p>
<p>后q行每行一个整数b表示询问的数。(0&lt;=b&lt;=10^8)</p>
<h5>输出</h5>
<p>q行每行一个整数对应每次询问的返回结果。</p>
<h5>提示:</h5>
<p>完成代码后通知Agent进行评测。</p>
<p>如果你还不完全会这个算法询问Agent获取提示并进行学习。</p>
</body>
</html>

View File

@@ -84,6 +84,75 @@
.details-content { .details-content {
padding-right: 20px; padding-right: 20px;
} }
.lesson-item {
display: flex;
align-items: center;
justify-content: space-between; /* 让垃圾桶靠右 */
padding: 5px 0;
}
.lesson-text {
cursor: pointer; /* 鼠标悬停时变成手指 */
transition: color 0.3s;
flex: 1; /* 占据左侧空间 */
}
.lesson-text:hover {
color: #2575fc; /* 这里你可以设置悬停时的颜色 */
}
.icon-btn {
background: none;
border: none;
cursor: pointer;
margin-left: 8px;
color: #666;
font-size: 14px;
transition: color 0.2s;
}
.icon-btn:hover {
color: #2575fc; /* hover 高亮 */
}
.delete-btn {
color: #c0392b; /* 红色垃圾桶 */
}
.delete-btn:hover {
color: #e74c3c;
}
.new-chapter-input {
width: 200px;
padding: 8px;
margin-top: 10px;
margin-bottom: 10px;
font-size: 14px;
border-radius: 4px;
border: 1px solid #ddd;
}
.icon-btn {
background: none;
border: none;
cursor: pointer;
margin-left: 8px;
color: #666;
font-size: 14px;
transition: color 0.2s;
}
.icon-btn:hover {
color: #2575fc; /* hover 高亮 */
}
.delete-btn {
color: #c0392b; /* 红色垃圾桶 */
}
.delete-btn:hover {
color: #e74c3c;
}
.add-chapter-btn { .add-chapter-btn {
background-color: #2575fc; background-color: #2575fc;

View File

Before

Width:  |  Height:  |  Size: 19 KiB

After

Width:  |  Height:  |  Size: 19 KiB

View File

Before

Width:  |  Height:  |  Size: 912 KiB

After

Width:  |  Height:  |  Size: 912 KiB

View File

Before

Width:  |  Height:  |  Size: 119 KiB

After

Width:  |  Height:  |  Size: 119 KiB

View File

Before

Width:  |  Height:  |  Size: 1.9 MiB

After

Width:  |  Height:  |  Size: 1.9 MiB

View File

@@ -4,7 +4,12 @@ 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('/agent?username='+data.username+'&folder='+data.folder); socket = io('ws://localhost:5551/agent',{
query:{
username:data.username,
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));

View File

@@ -0,0 +1,189 @@
function lessonTemplate(lesson) {
return `
<li class="lesson-item">
<span class="lesson-text" onclick="editLesson('${lesson}')">${lesson}
<button class="icon-btn edit-btn" onclick="editLesson('${lesson}')">
<i class="fas fa-pencil-alt"></i>
</button>
</span>
<button class="icon-btn delete-btn" onclick="deleteLesson('${lesson}')">
<i class="fas fa-trash-alt"></i>
</button>
</li>
`;
}
function show_teacher_data(user_data, user_course_data) {
// 这里可以根据user_course_data生成课程卡片
const courseCardsContainer = document.getElementById('course-cards');
user_course_data.forEach(course => {
const courseCard = document.createElement('div');
courseCard.classList.add('course-card');
courseCard.onclick = function() { openCourseDetails(course.id); };
courseCard.innerHTML = `
<img src="${course.cover_image}" alt="课程封面" class="course-image">
<div class="course-info">
<h3 class="course-name">${course.name}</h3>
<p class="course-description">${course.description}</p>
</div>
`;
courseCardsContainer.appendChild(courseCard);
});
}
let isAddingChapter = false;
function showInputForNewChapter() {
if (isAddingChapter) return; // 防止重复点击
isAddingChapter = true; // 标记正在添加章节
// 找到新增章节按钮并隐藏
const addButton = document.querySelector('.add-chapter-btn');
addButton.style.display = 'none';
// 创建一个输入框
const input = document.createElement('input');
input.type = 'text';
input.placeholder = '请输入章节名称...';
input.className = 'new-chapter-input';
input.onblur = () => checkAndAddChapter(input);
// 将输入框插入到页面
const chapterList = document.getElementById('chapter-list');
chapterList.appendChild(input);
input.focus(); // 聚焦到输入框
}
function checkAndAddChapter(input) {
const chapterName = input.value.trim();
// 如果章节名称为空,显示提示并不添加
if (!chapterName) {
input.remove(); // 删除输入框
document.querySelector('.add-chapter-btn').style.display = 'block'; // 重新显示新增章节按钮
isAddingChapter = false;
return;
}
// 如果章节名称合法,添加到章节列表
const chapterList = document.getElementById('chapter-list');
const chapterItem = document.createElement('li');
chapterItem.innerHTML = `
<li class="lesson-item">
<strong>${chapterName}</strong> <button class="icon-btn delete-btn" onclick="deleteChapter(this)">
<i class="fas fa-trash-alt"></i>
</button>
</li>
<ul>
</ul>
<button class="add-lesson-btn" onclick="showInputForNewLesson('${chapterName}', this)">新增课时</button>
`;
chapterList.appendChild(chapterItem);
// 恢复新增按钮的显示
document.querySelector('.add-chapter-btn').style.display = 'block';
input.remove();
isAddingChapter = false;
}
function openCourseDetails(courseId) {
// 打开课程目录
const courseDetails = document.getElementById('courseDetails');
const courseTitle = document.getElementById('course-title');
courseTitle.textContent = "课程名称: " + courseId; // 假设这里显示课程ID实际情况应该是课程名称
courseDetails.classList.add('open');
// 获取章节数据并渲染
renderChapters(courseId);
}
function deleteChapter(deleteButton) {
// 删除章节
const chapterItem = deleteButton.closest('li');
chapterItem.remove();
}
function renderChapters(courseId) {
const chapterList = document.getElementById('chapter-list');
chapterList.innerHTML = ""; // 清空章节列表
// 假设从服务器获取课程章节数据
const chapters = [
{ title: "第一章:算法基础", lessons: ["算法简介", "算法设计方法"] },
{ title: "第二章:数据结构", lessons: ["数组", "链表"] }
];
chapters.forEach(chapter => {
const chapterItem = document.createElement('li');
// 渲染章节标题
let lessonsHtml = chapter.lessons.map(lesson => lessonTemplate(lesson)).join('');
chapterItem.innerHTML = `
<strong>${chapter.title}</strong>
<ul>
${lessonsHtml}
</ul>
<button class="add-lesson-btn" onclick="showInputForNewLesson('${chapter.title}', this)">新增课时</button>
`;
chapterList.appendChild(chapterItem);
});
}
function showInputForNewLesson(chapterTitle, button) {
// 防止重复点击
const addButton = button;
addButton.style.display = 'none'; // 隐藏按钮
// 创建一个输入框
const input = document.createElement('input');
input.type = 'text';
input.placeholder = '请输入课时名称...';
input.className = 'new-lesson-input';
input.onblur = () => checkAndAddLesson(input, chapterTitle, addButton); // 失去焦点时检查输入
// 将输入框插入到章节列表中
const chapterItem = addButton.closest('li'); // 获取到点击按钮的父元素
chapterItem.appendChild(input);
input.focus(); // 聚焦到输入框
}
function checkAndAddLesson(input, chapterTitle, addButton) {
const lessonName = input.value.trim();
// 如果课时名称为空,显示提示并不添加
if (!lessonName) {
input.remove(); // 删除输入框
addButton.style.display = 'block'; // 重新显示新增课时按钮
return;
}
// 如果课时名称合法,添加到课时列表
const chapterList = document.getElementById('chapter-list');
const chapterItem = Array.from(chapterList.children).find(item => item.querySelector('strong').textContent === chapterTitle);
const lessonsList = chapterItem.querySelector('ul');
const lessonItem = document.createElement('span');
lessonItem.innerHTML = lessonTemplate(lessonName);
lessonsList.appendChild(lessonItem); // 将新课时添加到章节下
input.remove(); // 删除输入框
addButton.style.display = 'block'; // 重新显示新增课时按钮
}
function deleteLesson(lesson) {
const lessonItems = document.querySelectorAll('.lesson-item');
lessonItems.forEach(item => {
if (item.querySelector('.lesson-text').textContent === lesson) {
item.remove(); // 删除对应的课时项
}
});
}
function editLesson(lesson) {
// 编辑课时的逻辑
alert("编辑课时: " + lesson);
}
function closeCourseDetails() {
document.getElementById('courseDetails').classList.remove('open');
}

View File

@@ -66,7 +66,8 @@
window.appData = { window.appData = {
username:"{{user_id}}", username:"{{user_id}}",
course_id:"{{course_id}}", course_id:"{{course_id}}",
chapter_id:"{{chapter_id}}" chapter_id:"{{chapter_id}}",
folder:"{{workspace_path}}"
} }
</script> </script>
</div> </div>

View File

@@ -7,6 +7,7 @@
<link rel="stylesheet" href="/static/css/teacherboard.css"> <link rel="stylesheet" href="/static/css/teacherboard.css">
<link rel="stylesheet" href="/static/css/dashboard.css"> <link rel="stylesheet" href="/static/css/dashboard.css">
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.4.0/css/all.min.css">
</head> </head>
<body> <body>
@@ -47,7 +48,7 @@
<ul id="chapter-list"> <ul id="chapter-list">
<!-- 章节列表会在点击课程时动态生成 --> <!-- 章节列表会在点击课程时动态生成 -->
</ul> </ul>
<button class="add-chapter-btn" onclick="addChapter()">新增章节</button> <button class="add-chapter-btn" onclick="showInputForNewChapter()">新增章节</button>
</div> </div>
</div> </div>

View File

@@ -0,0 +1,10 @@
from flask import Flask
from .markdown import bp as markdown_bp
from .vscode import bp as vscode_bp
from .auth import bp as auth_bp
from .dashboard import bp as main_bp
def register_blueprints(app: Flask):
app.register_blueprint(markdown_bp) # 默认就是挂在根路径
app.register_blueprint(vscode_bp)
app.register_blueprint(auth_bp)
app.register_blueprint(main_bp)

57
Html/apps/views/auth.py Normal file
View File

@@ -0,0 +1,57 @@
# myapp/views/auth.py
from flask import Blueprint, render_template, request, jsonify, redirect, url_for, session, current_app
from ..services.auth_service import register_user, login_user, logout_user
from ..auth.decorators import require_role
bp = Blueprint("auth", __name__)
@bp.get("/register")
def register():
return render_template("register.html")
@bp.get("/register_teacher")
def register_teacher():
return render_template("register_teacher.html")
@bp.post("/register_post")
def register_post():
data = request.get_json(force=True) or {}
ok, msg = register_user(data.get("username"), data.get("password"), teacher=False)
return jsonify({"success": ok, "message": msg})
@bp.post("/register_teacher_post")
def register_teacher_post():
data = request.get_json(force=True) or {}
ok, msg = register_user(data.get("username"), data.get("password"), teacher=True)
return jsonify({"success": ok, "message": msg})
@bp.get("/login")
def login():
# 你原来 return 后还有逻辑,已被覆盖;这里简化
return render_template("login.html")
@bp.post("/login_post")
def login_post():
data = request.get_json(force=True) or {}
ok, msg = login_user(data.get("username"), data.get("password"), require_teacher=False)
return jsonify({"success": ok, "message": msg})
@bp.post("/login_teacher_post")
def login_teacher_post():
data = request.get_json(force=True) or {}
ok, msg = login_user(data.get("username"), data.get("password"), require_teacher=True)
return jsonify({"success": ok, "message": msg})
@bp.get("/teacherboard")
@require_role(roles="teacher")
def teacherboard():
return render_template("teacherboard.html")
@bp.get("/logout")
def logout():
logout_user()
return redirect(url_for("auth.login"))
@bp.get("/get_session")
def get_session():
return jsonify({"session": session.get("user_id", "default_session")})

View File

@@ -0,0 +1,60 @@
# myapp/views/dashboard.py
import json
from flask import Blueprint, render_template, request, jsonify, redirect, url_for
from ..auth.decorators import require_role
from ..services.user_service import get_or_load_current_user, add_course_for_current_user
from ..services.course_service import load_course, user_selected_course_briefs
bp = Blueprint("main", __name__) # 根路径蓝图
@bp.get("/dashboard")
@require_role
def dashboard():
user_obj = get_or_load_current_user()
if user_obj is None:
return redirect(url_for("auth.login"))
user_course_data = user_selected_course_briefs(user_obj)
return render_template(
"dashboard.html",
user_data=user_obj.to_json_without_dialog(),
user_course_data=json.dumps(user_course_data, ensure_ascii=False),
)
@bp.get("/course/<course_id>")
@require_role
def course(course_id):
c = load_course(course_id)
return render_template("course.html", course_id=course_id, course_data=c)
@bp.post("/select_course")
@require_role
def select_course():
data = request.get_json(force=True) or {}
course_id = data.get("course_id")
if not course_id:
return jsonify({"success": False, "message": "缺少 course_id"}), 400
course_data = load_course(course_id)
ok = add_course_for_current_user(course_id, course_data)
if not ok:
return jsonify({"success": False, "message": "未登录或会话失效"}), 401
return jsonify({"success": True, "message": "课程选择成功"})
@bp.get("/")
@require_role
def home_index():
user_obj = get_or_load_current_user()
if user_obj is None:
return redirect(url_for("auth.login"))
selected_courses = list(getattr(user_obj, "select_course", []))
# 课程目录:依据你的 CourseList 暴露的接口进行传递(这里直接传对象,模板里用)
from flask import current_app
course_list = current_app.extensions["course_list"]
return render_template(
"index.html",
courses_data=course_list,
selected_courses=selected_courses
)

View File

@@ -0,0 +1,35 @@
import os
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
)
bp = Blueprint("markdown", __name__)
@bp.route("/<course_id>-<filename>-markdown", methods=["GET"])
def convert_md(course_id, filename):
md_file_path = os.path.join(current_app.config["MARKDOWN_DIR"], course_id, f"{filename}.md")
if not os.path.exists(md_file_path):
return jsonify({"error": "Markdown file not found"}), 404
# 转换 markdown -> html
html = convert_markdown_to_html(md_file_path)
html_with_styles = wrap_with_styles(html)
# 保存 HTML 文件到 static
html_output_path = os.path.join(current_app.config["STATIC_DIR"], f"{filename}.html")
save_html(html_with_styles, html_output_path)
# 拷贝图片资源
image_source = os.path.join(current_app.config["MARKDOWN_DIR"], course_id, current_app.config["IMAGE_DIR"], filename)
image_target = os.path.join(current_app.config["STATIC_DIR"], current_app.config["IMAGE_DIR"], filename)
copy_images(image_source, image_target)
return jsonify({"html_url": f"/static/{filename}.html"})
# 提供静态文件访问
@bp.route("/static/<path:filename>")
def serve_static(filename):
return send_from_directory(current_app.config["STATIC_DIR"], filename)

69
Html/apps/views/vscode.py Normal file
View File

@@ -0,0 +1,69 @@
# myapp/views/vscode.py
import os, uuid, json
from flask import Blueprint, current_app, session, redirect, url_for, render_template, request, jsonify
from ..services.backboard_service import realtime_response
bp = Blueprint("vscode", __name__)
@bp.route("/desktop/<user_id>/<course_id>/<chapter_id>")
def desktop(user_id, course_id, chapter_id):
# session 中放 uuid访客也能进则给一个
if "user_id" not in session:
session["user_id"] = "user_" + str(uuid.uuid4())
current_app.logger.debug("user %s uuid is %s", user_id, session["user_id"])
# 全局映射
username2uuid = current_app.extensions["username2uuid"]
uuid2username = current_app.extensions["uuid2username"]
userid_recorder = current_app.extensions["userid_recorder"]
username2uuid[user_id] = session["user_id"]
uuid2username[session["user_id"]] = user_id
userid_recorder[f"{user_id}&{course_id}"] = session["user_id"]
# 按课程/章节创建工作目录
base_root = current_app.config["STUDENT_WORKSPACE_ROOT"]
path_dir = os.path.join(base_root, user_id, course_id, chapter_id)
os.makedirs(path_dir, exist_ok=True)
# 写 .config并按需要转换为 WSL 路径)
cfg = current_app.config["VSCODE_WEB_PATH"]
path_for_vscode = path_dir
if cfg.get("is_wsl"):
path_for_vscode = path_for_vscode.replace("\\", "/") \
.replace(cfg.get("windows_path", ""), cfg.get("wsl_path", ""))
config_path = os.path.join(path_dir, ".config")
tmpd = {
"user_id": user_id,
"course_id": course_id,
"chapter_id": chapter_id,
"path": path_for_vscode
}
with open(config_path, "w", encoding="utf-8") as f:
json.dump(tmpd, f, ensure_ascii=False)
current_app.logger.debug("config file path: %s", config_path)
return render_template(
"desktop.html",
vscode_web_url=current_app.config["VSCODE_WEB_URL"],
user_id=user_id, course_id=course_id, chapter_id=chapter_id,
workspace_path=path_for_vscode
)
@bp.route("/desktop_nouser/<course_id>/<chapter_id>")
def desktop_nouser(course_id, chapter_id):
if "user_id" not in session:
return redirect(url_for("auth.login")) # 如果你有 auth 蓝图
user_uuid = session["user_id"]
username = current_app.extensions["uuid2username"].get(user_uuid)
return redirect(url_for("vscode.desktop", user_id=username, course_id=course_id, chapter_id=chapter_id))
@bp.route("/vscode_data", methods=["POST"])
def vscode_data():
data = request.get_json(force=True)
config = data.get("config") or {}
realtime_response(config, data) # 调用服务层逻辑
return jsonify({"status": "success", "received": data})

31
Html/bootstrap.py Normal file
View File

@@ -0,0 +1,31 @@
from pathlib import Path
import sys, os
def bootstrap_paths():
"""
'AlgoriAgent'(位于 repo_root/AlgoriAgent可被 import
并提供 STUDENT_WORKSPACE_ROOT 的默认值repo_root/study
适配当前工厂位置repo_root/Html/apps/__init__.py
"""
here = Path(__file__).resolve()
# repo_root = .../Html/apps/../../ => parents[2]
repo_root = here.parents[2]
# 兜底:如果目录结构变化,尝试向上搜索包含 AlgoriAgent 的目录作为 repo_root
if not (repo_root / "AlgoriAgent").exists():
for p in here.parents:
if (p / "AlgoriAgent").is_dir():
repo_root = p
break
# 确保仓库根在 sys.path这样 'AlgoriAgent' 顶层包可被发现)
repo_root_str = str(repo_root)
if repo_root_str not in sys.path:
sys.path.insert(0, repo_root_str)
# 可选:如果你还有其它顶层包,也可在此处继续插入
# 默认学习工作区目录(也可放到 config.ini / 环境变量覆盖)
os.environ.setdefault("STUDENT_WORKSPACE_ROOT", str(repo_root / "study"))
return repo_root # 方便调试/日志

View File

@@ -9,7 +9,7 @@
</style> </style>
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/github-markdown-css/5.2.0/github-markdown.min.css"> <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/github-markdown-css/5.2.0/github-markdown.min.css">
</head> </head>
<body> <body class="markdown-body">
<h1>二分查找与二分答案</h1> <h1>二分查找与二分答案</h1>
<h2>二分查找</h2> <h2>二分查找</h2>
<h3>引入</h3> <h3>引入</h3>

View File

@@ -1,70 +0,0 @@
function show_teacher_data(user_data, user_course_data) {
// 这里可以根据user_course_data生成课程卡片
const courseCardsContainer = document.getElementById('course-cards');
user_course_data.forEach(course => {
const courseCard = document.createElement('div');
courseCard.classList.add('course-card');
courseCard.onclick = function() { openCourseDetails(course.id); };
courseCard.innerHTML = `
<img src="${course.cover_image}" alt="课程封面" class="course-image">
<div class="course-info">
<h3 class="course-name">${course.name}</h3>
<p class="course-description">${course.description}</p>
</div>
`;
courseCardsContainer.appendChild(courseCard);
});
}
function openCourseDetails(courseId) {
// 打开课程目录
const courseDetails = document.getElementById('courseDetails');
const courseTitle = document.getElementById('course-title');
courseTitle.textContent = "课程名称: " + courseId; // 假设这里显示课程ID实际情况应该是课程名称
courseDetails.classList.add('open');
// 获取章节数据并渲染
renderChapters(courseId);
}
function closeCourseDetails() {
document.getElementById('courseDetails').classList.remove('open');
}
function renderChapters(courseId) {
const chapterList = document.getElementById('chapter-list');
chapterList.innerHTML = ""; // 清空章节列表
// 假设从服务器获取课程章节数据
const chapters = [
{ title: "第一章:算法基础", lessons: ["算法简介", "算法设计方法"] },
{ title: "第二章:数据结构", lessons: ["数组", "链表"] }
];
chapters.forEach(chapter => {
const chapterItem = document.createElement('li');
chapterItem.innerHTML = `
<strong>${chapter.title}</strong>
<ul>
${chapter.lessons.map(lesson => `<li>${lesson} <button onclick="editLesson('${lesson}')">编辑</button></li>`).join('')}
</ul>
<button onclick="addLesson('${chapter.title}')">新增课时</button>
`;
chapterList.appendChild(chapterItem);
});
}
function addChapter() {
// 新增章节的逻辑
alert("新增章节功能");
}
function addLesson(chapterTitle) {
// 新增课时的逻辑
alert("新增课时功能: " + chapterTitle);
}
function editLesson(lesson) {
// 编辑课时的逻辑
alert("编辑课时: " + lesson);
}

6
Html/wsgi.py Normal file
View File

@@ -0,0 +1,6 @@
# wsgi.py
from bootstrap import bootstrap_paths
bootstrap_paths()
from apps import create_app
# 提供一个可被 WSGI/进程管理器导入的 app 对象
app = create_app()