')
+@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)
+
diff --git a/Html/apps/__init__.py b/Html/apps/__init__.py
new file mode 100644
index 0000000..00ed968
--- /dev/null
+++ b/Html/apps/__init__.py
@@ -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
diff --git a/Html/apps/auth/decorators.py b/Html/apps/auth/decorators.py
new file mode 100644
index 0000000..85e85e8
--- /dev/null
+++ b/Html/apps/auth/decorators.py
@@ -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
diff --git a/Html/apps/bootstrap.py b/Html/apps/bootstrap.py
new file mode 100644
index 0000000..17dd0e5
--- /dev/null
+++ b/Html/apps/bootstrap.py
@@ -0,0 +1 @@
+# Html/apps/bootstrap.py
diff --git a/Html/apps/config.py b/Html/apps/config.py
new file mode 100644
index 0000000..bb285e1
--- /dev/null
+++ b/Html/apps/config.py
@@ -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=""),
+ }
diff --git a/Html/apps/extensions.py b/Html/apps/extensions.py
new file mode 100644
index 0000000..a98cfdc
--- /dev/null
+++ b/Html/apps/extensions.py
@@ -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"))
\ No newline at end of file
diff --git a/Html/image/readme/1728381936172.png b/Html/apps/image/readme/1728381936172.png
similarity index 100%
rename from Html/image/readme/1728381936172.png
rename to Html/apps/image/readme/1728381936172.png
diff --git a/Html/apps/services/auth_service.py b/Html/apps/services/auth_service.py
new file mode 100644
index 0000000..823d4dd
--- /dev/null
+++ b/Html/apps/services/auth_service.py
@@ -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)
diff --git a/Html/apps/services/backboard_service.py b/Html/apps/services/backboard_service.py
new file mode 100644
index 0000000..8e758cf
--- /dev/null
+++ b/Html/apps/services/backboard_service.py
@@ -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)
diff --git a/Html/apps/services/course_service.py b/Html/apps/services/course_service.py
new file mode 100644
index 0000000..fb4a179
--- /dev/null
+++ b/Html/apps/services/course_service.py
@@ -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
diff --git a/Html/apps/services/markdown_service.py b/Html/apps/services/markdown_service.py
new file mode 100644
index 0000000..11bb982
--- /dev/null
+++ b/Html/apps/services/markdown_service.py
@@ -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_content}
+
+
+ """
+
+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)
diff --git a/Html/apps/services/memory_service.py b/Html/apps/services/memory_service.py
new file mode 100644
index 0000000..7a652ed
--- /dev/null
+++ b/Html/apps/services/memory_service.py
@@ -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
+ )
diff --git a/Html/apps/services/my_function.py b/Html/apps/services/my_function.py
new file mode 100644
index 0000000..0e0fb53
--- /dev/null
+++ b/Html/apps/services/my_function.py
@@ -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
+ )
diff --git a/Html/apps/services/user_service.py b/Html/apps/services/user_service.py
new file mode 100644
index 0000000..736e8e2
--- /dev/null
+++ b/Html/apps/services/user_service.py
@@ -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
diff --git a/Html/apps/sockets/namespaces.py b/Html/apps/sockets/namespaces.py
new file mode 100644
index 0000000..f1d27fa
--- /dev/null
+++ b/Html/apps/sockets/namespaces.py
@@ -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))
\ No newline at end of file
diff --git a/Html/apps/static/binary_search.html b/Html/apps/static/binary_search.html
new file mode 100644
index 0000000..4f70959
--- /dev/null
+++ b/Html/apps/static/binary_search.html
@@ -0,0 +1,53 @@
+
+
+
+
+
+
+
+ 二分查找与二分答案
+二分查找
+引入
+二分是一个很简单基础,但很重要的知识点,为以后许多高级的数据结构与算法铺垫。
+下面是一个用二分的简单场景:
+假设小明从0到1000之间选择了一个数字但不告诉你,你可以不断猜测这个数,每次猜测小明会告知你的猜测得过大还是过小,问最多几次就一定能猜中?
+答案是利用二分查找的原理,猜测11次即可。
+
+- 对于0到1000的答案备选区,猜测中位数500,假设过小,
+- 则对于501到1000的答案备选区,猜测750,假设过大
+- 则对于501到749的答案备选区,猜测625,假设过小,
+- 则对于626到749区间......
+- (688-749)
+- (718-749)
+- (734-749)
+- (742-749)
+- (746-749)
+- (748-749)
+- (749-749)
+
+在最差的情况下,第11次的答案备选区就一定长度为1了,也就是必然是答案。
+因此如果序列是有序的,就可以通过二分查找快速定位所需要的数据。
+思考题(询问Agent以学习计算方法,或验证你的答案)
+对于上面那个题目,如果问题区间是1到4000,最差情况下需要猜测几次?
+练习:二分查找
+试试对于下面的题目,用代码实现一下二分查找。
+题目:有序数组寻址
+给出一个长度为n的有序数组(从小到大),有q次询问,对于每次询问,输出指定数在数组中的下标。如果不存在则输出-1。
+输入
+第一行一个整数n。(1<=n<=10^5)
+第二行n个用空格分开的整数ai。(0<=ai<=10^8)
+第三行一个整数q,表示询问的次数。(1<=q<=10^4)
+后q行,每行一个整数b,表示询问的数。(0<=b<=10^8)
+输出
+q行,每行一个整数,对应每次询问的返回结果。
+提示:
+完成代码后,通知Agent进行评测。
+如果你还不完全会这个算法,询问Agent获取提示并进行学习。
+
+
+
\ No newline at end of file
diff --git a/Html/static/css/course.css b/Html/apps/static/css/course.css
similarity index 100%
rename from Html/static/css/course.css
rename to Html/apps/static/css/course.css
diff --git a/Html/static/css/dashboard.css b/Html/apps/static/css/dashboard.css
similarity index 100%
rename from Html/static/css/dashboard.css
rename to Html/apps/static/css/dashboard.css
diff --git a/Html/static/css/desktop.css b/Html/apps/static/css/desktop.css
similarity index 100%
rename from Html/static/css/desktop.css
rename to Html/apps/static/css/desktop.css
diff --git a/Html/static/css/index.css b/Html/apps/static/css/index.css
similarity index 100%
rename from Html/static/css/index.css
rename to Html/apps/static/css/index.css
diff --git a/Html/static/css/login.css b/Html/apps/static/css/login.css
similarity index 100%
rename from Html/static/css/login.css
rename to Html/apps/static/css/login.css
diff --git a/Html/static/css/teacherboard.css b/Html/apps/static/css/teacherboard.css
similarity index 65%
rename from Html/static/css/teacherboard.css
rename to Html/apps/static/css/teacherboard.css
index 2d3559c..c375397 100644
--- a/Html/static/css/teacherboard.css
+++ b/Html/apps/static/css/teacherboard.css
@@ -84,6 +84,75 @@
.details-content {
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 {
background-color: #2575fc;
diff --git a/Html/static/example.html b/Html/apps/static/example.html
similarity index 100%
rename from Html/static/example.html
rename to Html/apps/static/example.html
diff --git a/Html/static/image/CS101/CS101.png b/Html/apps/static/image/CS101/CS101.png
similarity index 100%
rename from Html/static/image/CS101/CS101.png
rename to Html/apps/static/image/CS101/CS101.png
diff --git a/Html/static/image/algorithm/book_cover.png b/Html/apps/static/image/algorithm/book_cover.png
similarity index 100%
rename from Html/static/image/algorithm/book_cover.png
rename to Html/apps/static/image/algorithm/book_cover.png
diff --git a/Html/static/image/data-structure/book_cover-data-structure.png b/Html/apps/static/image/data-structure/book_cover-data-structure.png
similarity index 100%
rename from Html/static/image/data-structure/book_cover-data-structure.png
rename to Html/apps/static/image/data-structure/book_cover-data-structure.png
diff --git a/Html/static/image/example/p1.png b/Html/apps/static/image/example/p1.png
similarity index 100%
rename from Html/static/image/example/p1.png
rename to Html/apps/static/image/example/p1.png
diff --git a/Html/static/js/chatbox.js b/Html/apps/static/js/chatbox.js
similarity index 98%
rename from Html/static/js/chatbox.js
rename to Html/apps/static/js/chatbox.js
index 35612c4..5da49a5 100644
--- a/Html/static/js/chatbox.js
+++ b/Html/apps/static/js/chatbox.js
@@ -4,7 +4,12 @@ let system_message_idx=0
document.addEventListener('DOMContentLoaded', function() {
data = window.appData;
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() {
console.log('Connected to server');
socket.emit('login',JSON.stringify(data));
diff --git a/Html/static/js/dashboard.js b/Html/apps/static/js/dashboard.js
similarity index 100%
rename from Html/static/js/dashboard.js
rename to Html/apps/static/js/dashboard.js
diff --git a/Html/static/js/index.js b/Html/apps/static/js/index.js
similarity index 100%
rename from Html/static/js/index.js
rename to Html/apps/static/js/index.js
diff --git a/Html/static/js/login.js b/Html/apps/static/js/login.js
similarity index 100%
rename from Html/static/js/login.js
rename to Html/apps/static/js/login.js
diff --git a/Html/static/js/register.js b/Html/apps/static/js/register.js
similarity index 100%
rename from Html/static/js/register.js
rename to Html/apps/static/js/register.js
diff --git a/Html/static/js/register_teacher.js b/Html/apps/static/js/register_teacher.js
similarity index 100%
rename from Html/static/js/register_teacher.js
rename to Html/apps/static/js/register_teacher.js
diff --git a/Html/apps/static/js/teacherboard.js b/Html/apps/static/js/teacherboard.js
new file mode 100644
index 0000000..bca619b
--- /dev/null
+++ b/Html/apps/static/js/teacherboard.js
@@ -0,0 +1,189 @@
+function lessonTemplate(lesson) {
+ return `
+
+ ${lesson}
+
+
+
+
+ `;
+}
+
+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 = `
+
+
+
${course.name}
+
${course.description}
+
+ `;
+ 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 = `
+
+ ${chapterName}
+
+
+
+
+ `;
+ 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 = `
+ ${chapter.title}
+
+
+ `;
+
+
+ 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');
+}
diff --git a/Html/static/twice_split.html b/Html/apps/static/twice_split.html
similarity index 100%
rename from Html/static/twice_split.html
rename to Html/apps/static/twice_split.html
diff --git a/Html/templates/course.html b/Html/apps/templates/course.html
similarity index 100%
rename from Html/templates/course.html
rename to Html/apps/templates/course.html
diff --git a/Html/templates/dashboard.html b/Html/apps/templates/dashboard.html
similarity index 100%
rename from Html/templates/dashboard.html
rename to Html/apps/templates/dashboard.html
diff --git a/Html/templates/desktop.html b/Html/apps/templates/desktop.html
similarity index 98%
rename from Html/templates/desktop.html
rename to Html/apps/templates/desktop.html
index bb87bd1..8633354 100644
--- a/Html/templates/desktop.html
+++ b/Html/apps/templates/desktop.html
@@ -66,7 +66,8 @@
window.appData = {
username:"{{user_id}}",
course_id:"{{course_id}}",
- chapter_id:"{{chapter_id}}"
+ chapter_id:"{{chapter_id}}",
+ folder:"{{workspace_path}}"
}
diff --git a/Html/templates/index.html b/Html/apps/templates/index.html
similarity index 97%
rename from Html/templates/index.html
rename to Html/apps/templates/index.html
index 865194a..034cac8 100644
--- a/Html/templates/index.html
+++ b/Html/apps/templates/index.html
@@ -1,44 +1,44 @@
-
-
-
-
-
- 课程选择主页
-
-
-
- {% include 'navbar.html' %}
-
-
-
课程列表
-
- {% for course_id, course in courses_data.items() %}
-
-

-
{{ course.course_name }}
-
{{ course.course_description }}
- {% if course_id in selected_courses %}
-
- {% else %}
-
- {% endif %}
-
- {% endfor %}
-
-
-
-
-
-
-
-
-
-
-
+
+
+
+
+
+ 课程选择主页
+
+
+
+ {% include 'navbar.html' %}
+
+
+
课程列表
+
+ {% for course_id, course in courses_data.items() %}
+
+

+
{{ course.course_name }}
+
{{ course.course_description }}
+ {% if course_id in selected_courses %}
+
+ {% else %}
+
+ {% endif %}
+
+ {% endfor %}
+
+
+
+
+
+
+
+
+
+
+
diff --git a/Html/templates/login.html b/Html/apps/templates/login.html
similarity index 100%
rename from Html/templates/login.html
rename to Html/apps/templates/login.html
diff --git a/Html/templates/navbar.html b/Html/apps/templates/navbar.html
similarity index 100%
rename from Html/templates/navbar.html
rename to Html/apps/templates/navbar.html
diff --git a/Html/templates/register.html b/Html/apps/templates/register.html
similarity index 100%
rename from Html/templates/register.html
rename to Html/apps/templates/register.html
diff --git a/Html/templates/register_teacher.html b/Html/apps/templates/register_teacher.html
similarity index 100%
rename from Html/templates/register_teacher.html
rename to Html/apps/templates/register_teacher.html
diff --git a/Html/templates/saveindex.html b/Html/apps/templates/saveindex.html
similarity index 100%
rename from Html/templates/saveindex.html
rename to Html/apps/templates/saveindex.html
diff --git a/Html/templates/teacherboard.html b/Html/apps/templates/teacherboard.html
similarity index 94%
rename from Html/templates/teacherboard.html
rename to Html/apps/templates/teacherboard.html
index 48a4d58..06c2696 100644
--- a/Html/templates/teacherboard.html
+++ b/Html/apps/templates/teacherboard.html
@@ -7,6 +7,7 @@
+
@@ -47,7 +48,7 @@
-
+
diff --git a/Html/apps/views/__init__.py b/Html/apps/views/__init__.py
new file mode 100644
index 0000000..f9abcc0
--- /dev/null
+++ b/Html/apps/views/__init__.py
@@ -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)
\ No newline at end of file
diff --git a/Html/apps/views/auth.py b/Html/apps/views/auth.py
new file mode 100644
index 0000000..65f6fc2
--- /dev/null
+++ b/Html/apps/views/auth.py
@@ -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")})
diff --git a/Html/apps/views/dashboard.py b/Html/apps/views/dashboard.py
new file mode 100644
index 0000000..9a6d118
--- /dev/null
+++ b/Html/apps/views/dashboard.py
@@ -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/")
+@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
+ )
diff --git a/Html/apps/views/markdown.py b/Html/apps/views/markdown.py
new file mode 100644
index 0000000..4dafe03
--- /dev/null
+++ b/Html/apps/views/markdown.py
@@ -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("/--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/")
+def serve_static(filename):
+ return send_from_directory(current_app.config["STATIC_DIR"], filename)
diff --git a/Html/apps/views/vscode.py b/Html/apps/views/vscode.py
new file mode 100644
index 0000000..1aafa24
--- /dev/null
+++ b/Html/apps/views/vscode.py
@@ -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///")
+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//")
+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})
diff --git a/Html/bootstrap.py b/Html/bootstrap.py
new file mode 100644
index 0000000..bcb15ce
--- /dev/null
+++ b/Html/bootstrap.py
@@ -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 # 方便调试/日志
\ No newline at end of file
diff --git a/Html/static/binary_search.html b/Html/static/binary_search.html
index 4f70959..ad467d0 100644
--- a/Html/static/binary_search.html
+++ b/Html/static/binary_search.html
@@ -9,7 +9,7 @@
-
+
二分查找与二分答案
二分查找
引入
diff --git a/Html/static/js/teacherboard.js b/Html/static/js/teacherboard.js
deleted file mode 100644
index 9c86c7f..0000000
--- a/Html/static/js/teacherboard.js
+++ /dev/null
@@ -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 = `
-
-
-
${course.name}
-
${course.description}
-
- `;
- 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 = `
- ${chapter.title}
-
- ${chapter.lessons.map(lesson => `- ${lesson}
`).join('')}
-
-
- `;
- chapterList.appendChild(chapterItem);
- });
-}
-
-function addChapter() {
- // 新增章节的逻辑
- alert("新增章节功能");
-}
-
-function addLesson(chapterTitle) {
- // 新增课时的逻辑
- alert("新增课时功能: " + chapterTitle);
-}
-
-function editLesson(lesson) {
- // 编辑课时的逻辑
- alert("编辑课时: " + lesson);
-}
diff --git a/Html/wsgi.py b/Html/wsgi.py
new file mode 100644
index 0000000..bbcbe5f
--- /dev/null
+++ b/Html/wsgi.py
@@ -0,0 +1,6 @@
+# wsgi.py
+from bootstrap import bootstrap_paths
+bootstrap_paths()
+from apps import create_app
+# 提供一个可被 WSGI/进程管理器导入的 app 对象
+app = create_app()