学生端选课进课闭环
This commit is contained in:
@@ -20,7 +20,7 @@ class Config:
|
|||||||
SOCKETIO_PING_TIMEOUT = 60
|
SOCKETIO_PING_TIMEOUT = 60
|
||||||
SOCKETIO_PING_INTERVAL = 5
|
SOCKETIO_PING_INTERVAL = 5
|
||||||
MARKDOWN_DIR = os.path.join(BASE_DIR, "books", "markdown")
|
MARKDOWN_DIR = os.path.join(BASE_DIR, "books", "markdown")
|
||||||
STATIC_DIR = os.path.join(BASE_DIR, "static")
|
STATIC_DIR = os.path.join(BASE_DIR, "apps", "static")
|
||||||
IMAGE_DIR = "image"
|
IMAGE_DIR = "image"
|
||||||
|
|
||||||
# 你的学生工作区根目录(如无就放到项目 data 目录)
|
# 你的学生工作区根目录(如无就放到项目 data 目录)
|
||||||
|
|||||||
@@ -2,8 +2,8 @@ import os, sys
|
|||||||
from flask_sqlalchemy import SQLAlchemy
|
from flask_sqlalchemy import SQLAlchemy
|
||||||
from flask_socketio import SocketIO
|
from flask_socketio import SocketIO
|
||||||
from flask_cors import CORS
|
from flask_cors import CORS
|
||||||
from db.user_list import UserList
|
|
||||||
from db.course_list import CourseList
|
from db.course_list import CourseList
|
||||||
|
from .services.user_db_service import UserList
|
||||||
from .services.my_function import MyFunction
|
from .services.my_function import MyFunction
|
||||||
from flask_pymongo import PyMongo
|
from flask_pymongo import PyMongo
|
||||||
from qcloud_cos import CosConfig
|
from qcloud_cos import CosConfig
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
from flask import current_app
|
from flask import current_app
|
||||||
|
import requests
|
||||||
|
|
||||||
def upload_file(rb_file, file_name):
|
def upload_file(rb_file, file_name):
|
||||||
if file_name is not None and "/" in file_name:
|
if file_name is not None and "/" in file_name:
|
||||||
@@ -21,3 +22,21 @@ def delete_file(file_name):
|
|||||||
Key=file_name,
|
Key=file_name,
|
||||||
)
|
)
|
||||||
return True
|
return True
|
||||||
|
|
||||||
|
def get_text_file(file_name):
|
||||||
|
response = requests.get(file_name)
|
||||||
|
|
||||||
|
if response.status_code == 200:
|
||||||
|
print(file_name)
|
||||||
|
# print(f"get_text_file: {response.text}")
|
||||||
|
|
||||||
|
return response.text
|
||||||
|
else:
|
||||||
|
return None
|
||||||
|
|
||||||
|
return response.text
|
||||||
|
# if file_name is not None and "/" in file_name:
|
||||||
|
# file_name = file_name.split("/")[-1]
|
||||||
|
# cos_client = current_app.extensions["cos_client"]
|
||||||
|
# print(f"get_text_file: {file_name}")
|
||||||
|
# return cos_client.get_object(Bucket=current_app.config["TENCENT_COS"]["bucket"], Key=file_name)["Body"].read()
|
||||||
@@ -10,21 +10,43 @@ from ..services.cos_service import upload_file, delete_file
|
|||||||
|
|
||||||
from ..models.material import Material
|
from ..models.material import Material
|
||||||
|
|
||||||
# 假设你已有这两个 loader
|
|
||||||
from db.course import load_course_from_json
|
|
||||||
|
|
||||||
def load_course(course_id: str):
|
def get_new_edit_materials(num: int)->List[Material]:
|
||||||
return load_course_from_json(
|
mongo = current_app.extensions["mongo"]
|
||||||
course_id, course_data_dir=current_app.config["COURSE_DATA_DIR"]
|
materials = mongo.db.materials.find().sort("updated_at", -1).limit(num)
|
||||||
)
|
returnCode = []
|
||||||
|
for material in materials:
|
||||||
|
returnCode.append(Material(**material))
|
||||||
|
returnCode[-1]._id = str(material['_id'])
|
||||||
|
return returnCode
|
||||||
|
|
||||||
|
def user_selected_course(user_obj):
|
||||||
|
briefs = []
|
||||||
|
for cid in getattr(user_obj, "select_course", []):
|
||||||
|
course_obj = load_material(cid)
|
||||||
|
brief={}
|
||||||
|
brief["_id"] = cid
|
||||||
|
brief["name"] = course_obj.name
|
||||||
|
brief["description"] = course_obj.description
|
||||||
|
brief["image_url"] = course_obj.image_url
|
||||||
|
brief["created_at"] = course_obj.created_at.strftime("%Y-%m-%d %H:%M:%S")
|
||||||
|
brief["updated_at"] = course_obj.updated_at.strftime("%Y-%m-%d %H:%M:%S")
|
||||||
|
brief["chapters"] = [chapter.model_dump() for chapter in course_obj.chapters]
|
||||||
|
briefs.append(brief)
|
||||||
|
return briefs
|
||||||
|
|
||||||
def user_selected_course_briefs(user_obj):
|
def user_selected_course_briefs(user_obj):
|
||||||
"""根据用户对象的选课列表,拼装课程简要信息数组"""
|
"""根据用户对象的选课列表,拼装课程简要信息数组"""
|
||||||
course_list = current_app.extensions["course_list"]
|
|
||||||
briefs = []
|
briefs = []
|
||||||
for cid in getattr(user_obj, "select_course", []):
|
for cid in getattr(user_obj, "select_course", []):
|
||||||
course_obj = load_course(cid)
|
course_obj = load_material(cid)
|
||||||
brief = course_list.get_course_brief_info(cid, course_obj)
|
brief={}
|
||||||
|
brief["_id"] = cid
|
||||||
|
brief["name"] = course_obj.name
|
||||||
|
brief["description"] = course_obj.description
|
||||||
|
brief["image_url"] = course_obj.image_url
|
||||||
|
brief["created_at"] = course_obj.created_at.strftime("%Y-%m-%d %H:%M:%S")
|
||||||
|
brief["updated_at"] = course_obj.updated_at.strftime("%Y-%m-%d %H:%M:%S")
|
||||||
briefs.append(brief)
|
briefs.append(brief)
|
||||||
return briefs
|
return briefs
|
||||||
|
|
||||||
@@ -45,6 +67,7 @@ def create_material(teacher_id, material_name, description, chapters, image_url)
|
|||||||
def load_material(material_id):
|
def load_material(material_id):
|
||||||
mongo = current_app.extensions["mongo"]
|
mongo = current_app.extensions["mongo"]
|
||||||
material = mongo.db.materials.find_one({"_id": ObjectId(material_id)})
|
material = mongo.db.materials.find_one({"_id": ObjectId(material_id)})
|
||||||
|
material['_id'] = material_id
|
||||||
return Material(**material)
|
return Material(**material)
|
||||||
|
|
||||||
def update_material(material_id, chapters):
|
def update_material(material_id, chapters):
|
||||||
|
|||||||
@@ -1,10 +1,35 @@
|
|||||||
import os, shutil, markdown
|
import os, shutil, markdown
|
||||||
|
from flask import current_app
|
||||||
|
from ..services.course_service import load_material
|
||||||
|
from ..services.cos_service import get_text_file
|
||||||
|
|
||||||
def convert_markdown_to_html(md_file_path: str) -> str:
|
def load_full_markdown_file(course_id, chapter_name, lesson_name):
|
||||||
"""读取 markdown 并转成 HTML 片段"""
|
material = load_material(course_id)
|
||||||
with open(md_file_path, 'r', encoding='utf-8') as f:
|
for chapter in material.chapters:
|
||||||
md_content = f.read()
|
if chapter.chapter_name == chapter_name:
|
||||||
return markdown.markdown(md_content)
|
for lesson in chapter.lessons:
|
||||||
|
if lesson.lesson_name == lesson_name:
|
||||||
|
return get_text_file(lesson.markdown_lesson_file_link), get_text_file(lesson.markdown_prompt_file_name), get_text_file(lesson.markdown_score_prompt_file_link)
|
||||||
|
return None, None, None
|
||||||
|
|
||||||
|
def load_markdown_file(course_id, chapter_name, lesson_name):
|
||||||
|
material = load_material(course_id)
|
||||||
|
file_link = None
|
||||||
|
for chapter in material.chapters:
|
||||||
|
if chapter.chapter_name == chapter_name:
|
||||||
|
for lesson in chapter.lessons:
|
||||||
|
if lesson.lesson_name == lesson_name:
|
||||||
|
file_link = lesson.markdown_lesson_file_link
|
||||||
|
break
|
||||||
|
break
|
||||||
|
if file_link is None:
|
||||||
|
return None
|
||||||
|
print(f"load_markdown_file: {file_link}")
|
||||||
|
return get_text_file(file_link)
|
||||||
|
|
||||||
|
def convert_markdown_to_html(md_file: str) -> str:
|
||||||
|
"""将 markdown 文件转成 HTML 片段"""
|
||||||
|
return markdown.markdown(md_file)
|
||||||
|
|
||||||
def wrap_with_styles(html_content: str) -> str:
|
def wrap_with_styles(html_content: str) -> str:
|
||||||
"""加上 CSS 样式和外层 HTML"""
|
"""加上 CSS 样式和外层 HTML"""
|
||||||
|
|||||||
@@ -1,7 +1,6 @@
|
|||||||
# myapp/services/memory_service.py
|
# myapp/services/memory_service.py
|
||||||
import os, json
|
import os, json
|
||||||
from flask import current_app
|
from flask import current_app
|
||||||
from db.user import load_user_from_json # 按你项目实际替换
|
|
||||||
|
|
||||||
def save_chapter_memory_by_uuid(
|
def save_chapter_memory_by_uuid(
|
||||||
user_uuid: str,
|
user_uuid: str,
|
||||||
@@ -14,11 +13,13 @@ def save_chapter_memory_by_uuid(
|
|||||||
):
|
):
|
||||||
uuid2username = current_app.extensions["uuid2username"]
|
uuid2username = current_app.extensions["uuid2username"]
|
||||||
user_map = current_app.extensions["user_uuid2UserClass"] # 可复用缓存
|
user_map = current_app.extensions["user_uuid2UserClass"] # 可复用缓存
|
||||||
user_data_dir = current_app.config["USER_DATA_DIR"]
|
users_list = current_app.extensions["users_list"]
|
||||||
|
|
||||||
username = uuid2username[user_uuid]
|
username = uuid2username[user_uuid]
|
||||||
# 若内存里已有用户对象就直接用;否则从 JSON 载入
|
# 若内存里已有用户对象就直接用;否则从 JSON 载入
|
||||||
user_obj = user_map.get(user_uuid) or load_user_from_json(username, user_data_dir=user_data_dir)
|
user_obj = user_map.get(user_uuid) or users_list.get_user_by_name(
|
||||||
|
username
|
||||||
|
)
|
||||||
user_map[user_uuid] = user_obj
|
user_map[user_uuid] = user_obj
|
||||||
|
|
||||||
user_obj.save_chapter_memory(
|
user_obj.save_chapter_memory(
|
||||||
|
|||||||
185
Html/apps/services/user_db_service.py
Normal file
185
Html/apps/services/user_db_service.py
Normal file
@@ -0,0 +1,185 @@
|
|||||||
|
from flask import current_app
|
||||||
|
from bson.binary import Binary
|
||||||
|
import uuid
|
||||||
|
import os
|
||||||
|
# Example usage:
|
||||||
|
import json
|
||||||
|
from datetime import datetime
|
||||||
|
import copy
|
||||||
|
from ..models.material import Material
|
||||||
|
|
||||||
|
class User:
|
||||||
|
def __init__(self, user_uuid, username, teacher, password, select_course, head_icon, _id, **kwargs):
|
||||||
|
self.user_uuid = user_uuid # User UUID
|
||||||
|
self.username = username
|
||||||
|
self.teacher = teacher
|
||||||
|
self.password = password
|
||||||
|
self.select_course = select_course # List of selected courses
|
||||||
|
self.head_icon = head_icon # URL of the user's head icon
|
||||||
|
self._id = _id
|
||||||
|
self.kwargs = kwargs
|
||||||
|
def save_chapter_memory(self, course_id, lesson_name, subchapter_title, mem_list, score, is_rebuttal):
|
||||||
|
'''
|
||||||
|
Save the memory of a chapter
|
||||||
|
into the mongo db
|
||||||
|
'''
|
||||||
|
try:
|
||||||
|
mongo = current_app.extensions["mongo"]
|
||||||
|
cp = mongo.db.course_process.find_one({"user_uuid": self.user_uuid, "material_id": course_id})
|
||||||
|
if not cp:
|
||||||
|
return
|
||||||
|
cp['course_last_study_date'] = datetime.now().strftime('%Y-%m-%d')
|
||||||
|
have_lesson = False
|
||||||
|
for _ in range(len(cp['lesson_processs'])):
|
||||||
|
lesson_process, cname, lname = cp['lesson_processs'][_]
|
||||||
|
if lname == lesson_name:
|
||||||
|
have_lesson = True
|
||||||
|
saveed = False
|
||||||
|
for i in range(len(lesson_process)):
|
||||||
|
step = lesson_process[i]
|
||||||
|
if step['title'] == subchapter_title:
|
||||||
|
step['dialog'] = mem_list
|
||||||
|
if is_rebuttal:
|
||||||
|
step['rebuttal_score'] = score
|
||||||
|
else:
|
||||||
|
step['score'] = score
|
||||||
|
step['is_rebuttal'] = is_rebuttal
|
||||||
|
saveed = True
|
||||||
|
break
|
||||||
|
if not saveed:
|
||||||
|
cp['lesson_processs'].append({
|
||||||
|
'title': subchapter_title,
|
||||||
|
'dialog': mem_list,
|
||||||
|
'score': score,
|
||||||
|
'is_rebuttal': is_rebuttal,
|
||||||
|
'rebuttal_score': score if is_rebuttal else 0,
|
||||||
|
})
|
||||||
|
break
|
||||||
|
if not have_lesson:
|
||||||
|
cp['lesson_processs'].append(([], chapter_name, lesson_name))
|
||||||
|
mongo.db.course_process.update_one({"user_uuid": self.user_uuid, "material_id": course_id}, {"$set": cp})
|
||||||
|
except Exception as e:
|
||||||
|
print("=-=-=-=-=-=")
|
||||||
|
print(e)
|
||||||
|
def get_all_course_progress(self):
|
||||||
|
mongo = current_app.extensions["mongo"]
|
||||||
|
course_processes = mongo.db.course_process.find({"user_uuid": self.user_uuid})
|
||||||
|
for cp in course_processes:
|
||||||
|
print(f"Course: {cp['material_id']}")
|
||||||
|
for lessons,chapter_name,lesson_name in cp["lesson_processs"]:
|
||||||
|
print(f" Lesson ID: {lesson_name}")
|
||||||
|
for step in lessons:
|
||||||
|
print(f" Chapter Dialog: {step['dialog']}")
|
||||||
|
print(f" Chapter Score: {step['score']}")
|
||||||
|
print(f" Rebuttal: {'Yes' if step['is_rebuttal'] else 'No'}")
|
||||||
|
if step['is_rebuttal']:
|
||||||
|
print(f" Rebuttal Score: {step['rebuttal_score']}")
|
||||||
|
|
||||||
|
def get_course_progress(self, course_id):
|
||||||
|
mongo = current_app.extensions["mongo"]
|
||||||
|
cp = mongo.db.course_process.find_one({"user_uuid": self.user_uuid, "material_id": course_id})
|
||||||
|
return cp
|
||||||
|
|
||||||
|
def select_new_course(self, course_id, course:Material):
|
||||||
|
try:
|
||||||
|
if course_id in self.select_course:
|
||||||
|
return True
|
||||||
|
self.select_course.append(course_id)
|
||||||
|
mongo = current_app.extensions["mongo"]
|
||||||
|
mongo.db.course_process.insert_one({
|
||||||
|
'user_uuid': self.user_uuid,
|
||||||
|
'material_id': course_id,
|
||||||
|
'lesson_processs':[],
|
||||||
|
'course_put_in_date':datetime.now().strftime('%Y-%m-%d'),
|
||||||
|
'course_last_study_date':datetime.now().strftime('%Y-%m-%d'),
|
||||||
|
'course_total_study_time':0})
|
||||||
|
for chapter in course.chapters:
|
||||||
|
for lesson in chapter.lessons:
|
||||||
|
mongo.db.course_process.update_one({"user_uuid": self.user_uuid, "material_id": course_id}, {"$push": {"lesson_processs": ([], chapter.chapter_name, lesson.lesson_name)}})
|
||||||
|
self.select_course.append(course_id)
|
||||||
|
mongo.db.users.update_one({"user_uuid": self.user_uuid}, {"$push": {"select_course": course_id}})
|
||||||
|
except Exception as e:
|
||||||
|
print("=-=-=-=-=-=")
|
||||||
|
print(e)
|
||||||
|
return False
|
||||||
|
return True
|
||||||
|
def save_to_db(self):
|
||||||
|
current_app.extensions["mongo"].db.users.update_one({"user_uuid": self.user_uuid}, {"$set": self.__json__()})
|
||||||
|
|
||||||
|
|
||||||
|
def __json__(self):
|
||||||
|
return {
|
||||||
|
'username': self.username,
|
||||||
|
'teacher': self.teacher,
|
||||||
|
'select_course': self.select_course,
|
||||||
|
'head_icon': self.head_icon,
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
def to_json(self):
|
||||||
|
return json.dumps(self.__json__(), indent=4)
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
class UserList:
|
||||||
|
|
||||||
|
def __init__(self):
|
||||||
|
"""从mongo中获取用户信息"""
|
||||||
|
self.Users = {}
|
||||||
|
|
||||||
|
def add_user(self, username, password, teacher=False):
|
||||||
|
"""添加新的用户登录信息"""
|
||||||
|
# 检查是否已经有该用户
|
||||||
|
mongo = current_app.extensions["mongo"]
|
||||||
|
if mongo.db.users.find_one({"username": username}) is None:
|
||||||
|
mongo.db.users.insert_one({"user_uuid": Binary.from_uuid(uuid.uuid4()), "username": username, "teacher": teacher, "password": password, "select_course": [], "head_icon": ""})
|
||||||
|
else:
|
||||||
|
print(f"User with ID {username} already exists.")
|
||||||
|
|
||||||
|
def get_user_by_name(self, username):
|
||||||
|
"""根据用户ID获取用户信息"""
|
||||||
|
mongo = current_app.extensions["mongo"]
|
||||||
|
if username not in self.Users:
|
||||||
|
self.Users[username] = User(**mongo.db.users.find_one({"username": username}))
|
||||||
|
return self.Users[username]
|
||||||
|
else:
|
||||||
|
return self.Users[username]
|
||||||
|
|
||||||
|
def has_user(self, username):
|
||||||
|
"""检查用户是否存在"""
|
||||||
|
mongo = current_app.extensions["mongo"]
|
||||||
|
return mongo.db.users.find_one({"username": username}) is not None
|
||||||
|
|
||||||
|
def get_user_is_teacher(self, username):
|
||||||
|
"""获取指定 user_id 的用户是否为教师"""
|
||||||
|
mongo = current_app.extensions["mongo"]
|
||||||
|
if mongo.db.users.find_one({"username": username}) is not None:
|
||||||
|
result = mongo.db.users.find_one({"username": username})
|
||||||
|
return result['teacher']
|
||||||
|
return None
|
||||||
|
|
||||||
|
def get_user_pswd(self, username):
|
||||||
|
"""获取指定 user_id 的用户信息"""
|
||||||
|
mongo = current_app.extensions["mongo"]
|
||||||
|
if mongo.db.users.find_one({"username": username}) is not None:
|
||||||
|
result = mongo.db.users.find_one({"username": username})
|
||||||
|
return result['password']
|
||||||
|
return None
|
||||||
|
|
||||||
|
def update_password(self, username, new_password):
|
||||||
|
"""更新用户的密码"""
|
||||||
|
assert mongo.db.users.find_one({"username": username}) is not None, f"User with ID {username} does not exist."
|
||||||
|
mongo.db.users.update_one({"username": username}, {"$set": {"password": new_password, "teacher": mongo.db.users.find_one({"username": username})['teacher']}})
|
||||||
|
|
||||||
|
def delete_user(self, username):
|
||||||
|
"""删除用户"""
|
||||||
|
assert mongo.db.users.find_one({"username": username}) is not None, f"User with ID {username} does not exist."
|
||||||
|
mongo.db.users.delete_one({"username": username})
|
||||||
|
|
||||||
|
def get_all_users(self):
|
||||||
|
"""获取所有用户的信息"""
|
||||||
|
mongo = current_app.extensions["mongo"]
|
||||||
|
return list(mongo.db.users.find())
|
||||||
|
|
||||||
|
|
||||||
@@ -3,8 +3,6 @@ import os
|
|||||||
from typing import Tuple
|
from typing import Tuple
|
||||||
from flask import current_app, session
|
from flask import current_app, session
|
||||||
|
|
||||||
# 假设你已有这两个 loader
|
|
||||||
from db.user import load_user_from_json
|
|
||||||
|
|
||||||
def _username_from_session() -> str:
|
def _username_from_session() -> str:
|
||||||
"""通过 session['user_id'] -> username"""
|
"""通过 session['user_id'] -> username"""
|
||||||
@@ -28,8 +26,9 @@ def get_or_load_current_user():
|
|||||||
|
|
||||||
user_map = current_app.extensions["user_uuid2UserClass"]
|
user_map = current_app.extensions["user_uuid2UserClass"]
|
||||||
if user_uuid not in user_map:
|
if user_uuid not in user_map:
|
||||||
user_map[user_uuid] = load_user_from_json(
|
users_list = current_app.extensions["users_list"]
|
||||||
username, user_data_dir=current_app.config["USER_DATA_DIR"]
|
user_map[user_uuid] = users_list.get_user_by_name(
|
||||||
|
username
|
||||||
)
|
)
|
||||||
return user_map[user_uuid]
|
return user_map[user_uuid]
|
||||||
|
|
||||||
|
|||||||
@@ -4,6 +4,8 @@ from flask import current_app, request, session
|
|||||||
from flask_socketio import Namespace, join_room, leave_room, emit
|
from flask_socketio import Namespace, join_room, leave_room, emit
|
||||||
from ..services.memory_service import save_chapter_memory_by_uuid
|
from ..services.memory_service import save_chapter_memory_by_uuid
|
||||||
from ..services.backboard_service import realtime_response
|
from ..services.backboard_service import realtime_response
|
||||||
|
from ..services.markdown_service import load_full_markdown_file
|
||||||
|
|
||||||
class VSCodeNamespace(Namespace):
|
class VSCodeNamespace(Namespace):
|
||||||
def on_login(self,data):
|
def on_login(self,data):
|
||||||
ex = current_app.extensions
|
ex = current_app.extensions
|
||||||
@@ -41,22 +43,20 @@ class AgentNamespace(Namespace):
|
|||||||
data = json.loads(data)
|
data = json.loads(data)
|
||||||
user_uuid = data['user_uuid']
|
user_uuid = data['user_uuid']
|
||||||
course_id = data['course_id']
|
course_id = data['course_id']
|
||||||
chapter_id = data['chapter_id']
|
lesson_name = data['lesson_name']
|
||||||
|
chapter_name = data['chapter_name']
|
||||||
user_id = uuid2username[user_uuid]
|
user_id = uuid2username[user_uuid]
|
||||||
session['user_uuid'] = user_uuid
|
session['user_uuid'] = user_uuid
|
||||||
print(f'User connected with session user_uuid: {user_uuid}')
|
print(f'User connected with session user_uuid: {user_uuid}')
|
||||||
|
|
||||||
|
fmd,fmdp,fsp = load_full_markdown_file(course_id, chapter_name, lesson_name)
|
||||||
# 从markdown、markdown_prompts、score_prompts中各读取数据folder.md并进行new_agent
|
# 从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,\
|
user_uuid, agent = agent_manager.new_agent(course_id, lesson_name, fmd, fmdp, fsp, id=user_uuid,
|
||||||
open(f'books/score_prompts/{course_id}/{chapter_id}.md','r',encoding='UTF-8') as fsp:
|
root_path=f'../../study/{user_id}/{course_id}/{chapter_name}/{lesson_name}')
|
||||||
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_id}/{course_id}/{chapter_id}')
|
|
||||||
print(user_uuid)
|
print(user_uuid)
|
||||||
join_room(user_uuid, namespace='/agent') # 将该用户加入以user_id为名的room
|
join_room(user_uuid, namespace='/agent') # 将该用户加入以user_id为名的room
|
||||||
backboard_manager.add_backboard(user_uuid, user_id, course_id, chapter_id, root_path=f'../../study/{user_id}/{course_id}/{chapter_id}')
|
backboard_manager.add_backboard(user_uuid, user_id, course_id, lesson_name, root_path=f'../../study/{user_id}/{course_id}/{chapter_name}/{lesson_name}')
|
||||||
|
|
||||||
def on_language(self, language):
|
def on_language(self, language):
|
||||||
ex = current_app.extensions
|
ex = current_app.extensions
|
||||||
|
|||||||
@@ -1,9 +1,11 @@
|
|||||||
|
|
||||||
|
|
||||||
/* 左右两侧的页面内容 */
|
/* 左右两侧的页面内容 */
|
||||||
.sidebar {
|
.sidebar {
|
||||||
background-color: #f0f0f0;
|
background-color: #f0f0f0;
|
||||||
padding: 20px;
|
|
||||||
height: 100%;
|
height: 100%;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column; /* 使子元素垂直排列 */
|
||||||
}
|
}
|
||||||
|
|
||||||
body {
|
body {
|
||||||
@@ -16,7 +18,8 @@
|
|||||||
.chatbox {
|
.chatbox {
|
||||||
flex-grow: 1;
|
flex-grow: 1;
|
||||||
padding: 20px;
|
padding: 20px;
|
||||||
height: 75%;
|
width: 100%;
|
||||||
|
flex: 1;
|
||||||
overflow-y: auto;
|
overflow-y: auto;
|
||||||
border: 1px solid #ccc;
|
border: 1px solid #ccc;
|
||||||
background-color: #f9f9f9;
|
background-color: #f9f9f9;
|
||||||
@@ -69,13 +72,6 @@
|
|||||||
height: 100%;
|
height: 100%;
|
||||||
position: relative;
|
position: relative;
|
||||||
}
|
}
|
||||||
/* 中间的拖拽条 */
|
|
||||||
.resizer {
|
|
||||||
width: 5px;
|
|
||||||
background-color: #ccc;
|
|
||||||
cursor: ew-resize;
|
|
||||||
position: relative;
|
|
||||||
}
|
|
||||||
|
|
||||||
.chatbox-header {
|
.chatbox-header {
|
||||||
padding: 10px;
|
padding: 10px;
|
||||||
@@ -110,19 +106,16 @@
|
|||||||
padding: 5px 10px;
|
padding: 5px 10px;
|
||||||
}
|
}
|
||||||
#leftSidebar, .vscode-web, #rightSidebar {
|
#leftSidebar, .vscode-web, #rightSidebar {
|
||||||
height: 100vh;
|
height: 100%;
|
||||||
overflow: auto;
|
overflow: auto;
|
||||||
}
|
}
|
||||||
|
|
||||||
#dragbar, #dragbar2 {
|
|
||||||
background-color: #ccc;
|
|
||||||
cursor: col-resize;
|
|
||||||
}
|
|
||||||
/* 主容器,使用 grid 布局 */
|
/* 主容器,使用 grid 布局 */
|
||||||
#maxcontainer {
|
#maxcontainer {
|
||||||
display: grid;
|
display: grid;
|
||||||
grid-template-columns: 1fr 5px 3fr 5px 1fr; /* 默认宽度比例 */
|
grid-template-columns: 1fr 5px 3fr 5px 1fr; /* 默认宽度比例 */
|
||||||
height: 100vh;
|
height: 100%;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* 各区域基础样式 */
|
/* 各区域基础样式 */
|
||||||
@@ -133,22 +126,24 @@
|
|||||||
.vscode-web {
|
.vscode-web {
|
||||||
width: 100%;
|
width: 100%;
|
||||||
height: 100%;
|
height: 100%;
|
||||||
border: none;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
.resizer {
|
.resizer {
|
||||||
background-color: #ccc;
|
background-color: #ccc;
|
||||||
cursor: col-resize;
|
cursor: col-resize;
|
||||||
width: 5px; /* 设置拖动条宽度 */
|
width: 5px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
.gutter {
|
.gutter {
|
||||||
background-color: #ccc; /* 分隔条颜色 */
|
background-color: #ccc; /* 分隔条颜色 */
|
||||||
cursor: col-resize;
|
cursor: col-resize;
|
||||||
}#container {
|
}#container {
|
||||||
display: grid;
|
display: grid;
|
||||||
grid-template-columns: 1fr 5px 3fr 5px 1fr; /* 初始比例 */
|
grid-template-columns: 1fr 5px 3fr 5px 1fr; /* 初始比例 */
|
||||||
height: 100vh;
|
height: 100%;
|
||||||
}
|
}
|
||||||
|
|
||||||
#leftSidebar, #vscodeWeb, #rightSidebar {
|
#leftSidebar, #vscodeWeb, #rightSidebar {
|
||||||
@@ -174,13 +169,18 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
.progress-title {
|
.progress-title {
|
||||||
height: 60px; /* 每个进度节点的高度 */
|
height: 60px;
|
||||||
text-align: center;
|
text-align: center;
|
||||||
line-height: 60px; /* 垂直居中 */
|
line-height: 60px;
|
||||||
margin: 2px; /* 水平方向上的间隔 */
|
margin-right: 2px;
|
||||||
border: 1px solid #ddd;
|
border: 1px solid #ddd;
|
||||||
transition: background-color 0.3s ease;
|
transition: background-color 0.3s ease;
|
||||||
position: relative;
|
position: relative;
|
||||||
|
|
||||||
|
/* 新增以下属性实现文字溢出省略 */
|
||||||
|
white-space: nowrap; /* 防止文字换行 */
|
||||||
|
overflow: hidden; /* 隐藏溢出内容 */
|
||||||
|
text-overflow: ellipsis; /* 溢出部分显示为省略号 */
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@@ -296,3 +296,41 @@
|
|||||||
.toggle-button .icon {
|
.toggle-button .icon {
|
||||||
font-size: 20px;
|
font-size: 20px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/* 设置滚动条的宽度、颜色等 */
|
||||||
|
-webkit-scrollbar {
|
||||||
|
width: 8px; /* 初始滚动条宽度 */
|
||||||
|
height: 8px; /* 水平滚动条宽度 */
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 设置滚动条轨道的颜色 */
|
||||||
|
-webkit-scrollbar-track {
|
||||||
|
background: #f1f1f1;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 设置滚动条滑块的颜色 */
|
||||||
|
-webkit-scrollbar-thumb {
|
||||||
|
background: #888;
|
||||||
|
border-radius: 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 当鼠标悬停在滚动条上时,改变其宽度和颜色 */
|
||||||
|
-webkit-scrollbar:hover {
|
||||||
|
width: 12px; /* 鼠标悬停时变粗 */
|
||||||
|
height: 12px;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 滚动条滑块的颜色变化 */
|
||||||
|
-webkit-scrollbar-thumb:hover {
|
||||||
|
background: #555; /* 鼠标悬停时变暗 */
|
||||||
|
}
|
||||||
|
|||||||
@@ -1,56 +1,58 @@
|
|||||||
// dashboard.js
|
// dashboard.js
|
||||||
let courseData;
|
let courseData;
|
||||||
function show_user_data(user_data, course_brief_data_list){
|
function show_user_data(user_data, course_brief_data_list){
|
||||||
courseData = user_data.course_process_dict
|
courseData = {}
|
||||||
|
for (let i=0;i<course_brief_data_list.length;i++){
|
||||||
|
courseData[course_brief_data_list[i]._id] = {}
|
||||||
|
}
|
||||||
for (let i=0;i<course_brief_data_list.length;i++){
|
for (let i=0;i<course_brief_data_list.length;i++){
|
||||||
course_brief_data = course_brief_data_list[i]
|
course_brief_data = course_brief_data_list[i]
|
||||||
console.log(course_brief_data)
|
console.log(course_brief_data)
|
||||||
courseData[course_brief_data.course_id].course_id = course_brief_data.course_id;
|
courseData[course_brief_data._id]._id = course_brief_data._id;
|
||||||
courseData[course_brief_data.course_id].title = course_brief_data.course_name;
|
courseData[course_brief_data._id].title = course_brief_data.name;
|
||||||
courseData[course_brief_data.course_id].lessons = course_brief_data.lessons;
|
courseData[course_brief_data._id].chapters = course_brief_data.chapters;
|
||||||
courseData[course_brief_data.course_id].course_img_path = course_brief_data.course_img_path;
|
courseData[course_brief_data._id].image_url = course_brief_data.image_url;
|
||||||
courseData[course_brief_data.course_id].course_description = course_brief_data.course_description;
|
courseData[course_brief_data._id].description = course_brief_data.description;
|
||||||
courseData[course_brief_data.course_id].course_create_date = course_brief_data.course_create_date;
|
courseData[course_brief_data._id].created_at = course_brief_data.created_at;
|
||||||
courseData[course_brief_data.course_id].course_update_data = course_brief_data.course_update_data;
|
courseData[course_brief_data._id].updated_at = course_brief_data.updated_at;
|
||||||
}
|
}
|
||||||
for (var course in courseData){
|
for (var course in courseData){
|
||||||
courseData[course].chapters = courseData[course].lessons
|
courseData[course].chapters = courseData[course].chapters
|
||||||
console.log(courseData[course])
|
console.log(courseData[course])
|
||||||
}
|
}
|
||||||
|
|
||||||
let courseCardsContainer = document.getElementById('course-cards');
|
|
||||||
courseCardsContainer.innerHTML = "";
|
|
||||||
for (var course in courseData){
|
|
||||||
course_id = course;
|
|
||||||
course = courseData[course];
|
|
||||||
let courseCard = document.createElement('div');
|
|
||||||
|
|
||||||
courseCard.className = 'course-card';
|
|
||||||
courseCard.innerHTML = `
|
|
||||||
<img src="${course.course_img_path}" alt="课程封面", class="course-image">
|
|
||||||
<div class="course-info">
|
|
||||||
<h3>${course.title}</h3>
|
|
||||||
<p>${course.course_description}</p>
|
|
||||||
</div>
|
|
||||||
<button class="select-button" >查看目录</button>
|
|
||||||
`;
|
|
||||||
courseCardsContainer.appendChild(courseCard);
|
|
||||||
courseCard.data = course_id
|
|
||||||
courseCard.addEventListener('click',() => {
|
|
||||||
openCourseProgress(courseCard.data);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
function openCourseProgress(courseId) {
|
function openCourseProgress(courseId) {
|
||||||
console.log(courseId)
|
console.log(courseId)
|
||||||
const course = courseData[courseId];
|
const course = courseData[courseId];
|
||||||
if (!course) return;
|
if (!course) return;
|
||||||
|
fetch(`/dashboard/get_course_progress`, {
|
||||||
|
method: 'POST',
|
||||||
|
headers: {
|
||||||
|
'Content-Type': 'application/json'
|
||||||
|
},
|
||||||
|
body: JSON.stringify({course_id: courseId})
|
||||||
|
}).then(response => response.json())
|
||||||
|
.then(data => {
|
||||||
|
console.log(data)
|
||||||
|
/*
|
||||||
|
course_last_study_date: "2025-09-06"
|
||||||
|
course_put_in_date: "2025-09-06"
|
||||||
|
course_total_study_time:0
|
||||||
|
lesson_processs:Array(1):
|
||||||
|
(3) [Array(0), '基础算法', '二分']
|
||||||
|
material_id:"68bacdfadf5aeae0912f7f18"
|
||||||
|
user_uuid: $binary: {base64: 'r4hz5J1JSWe1hKJqXnAgwQ==', subType: '04'}
|
||||||
|
|
||||||
// 更新右侧滑出卡片的内容
|
*/
|
||||||
|
course.course_last_study_date = data.data.course_last_study_date;
|
||||||
|
course.course_put_in_date = data.data.course_put_in_date;
|
||||||
|
course.course_total_study_time = data.data.course_total_study_time;
|
||||||
|
course.lesson_processs = data.data.lesson_processs;
|
||||||
|
course.material_id = data.data.material_id;
|
||||||
|
course.user_uuid = data.data.user_uuid;
|
||||||
document.getElementById('course-title').textContent = course.title;
|
document.getElementById('course-title').textContent = course.title;
|
||||||
///////////////////////// course.progress;还没有计算
|
///////////////////////// course.progress;还没有计算
|
||||||
document.getElementById('progress').textContent = course.progress;
|
document.getElementById('progress').textContent = course.progress;
|
||||||
|
|
||||||
// 更新章节列表
|
// 更新章节列表
|
||||||
const chapterList = document.getElementById('chapter-list');
|
const chapterList = document.getElementById('chapter-list');
|
||||||
chapterList.innerHTML = ''; // 清空之前的章节
|
chapterList.innerHTML = ''; // 清空之前的章节
|
||||||
@@ -61,23 +63,27 @@ function openCourseProgress(courseId) {
|
|||||||
|
|
||||||
const chapterTitle = document.createElement('div');
|
const chapterTitle = document.createElement('div');
|
||||||
chapterTitle.classList.add('chapter-title');
|
chapterTitle.classList.add('chapter-title');
|
||||||
chapterTitle.textContent = chapter.lesson_id;
|
chapterTitle.textContent = chapter.chapter_name;
|
||||||
chapterItem.appendChild(chapterTitle);
|
chapterItem.appendChild(chapterTitle);
|
||||||
|
|
||||||
// 创建子章节
|
// 创建子章节
|
||||||
if (chapter.subChapters && chapter.subChapters.length > 0) {
|
if (chapter.lessons && chapter.lessons.length > 0) {
|
||||||
const subChapterList = document.createElement('ul');
|
const subChapterList = document.createElement('ul');
|
||||||
subChapterList.classList.add('sub-chapter-list');
|
subChapterList.classList.add('sub-chapter-list');
|
||||||
|
|
||||||
chapter.subChapters.forEach(subChapter => {
|
chapter.lessons.forEach(subChapter => {
|
||||||
const subChapterItem = document.createElement('li');
|
const subChapterItem = document.createElement('li');//课时层级
|
||||||
subChapterItem.classList.add('sub-chapter-item');
|
subChapterItem.classList.add('sub-chapter-item');
|
||||||
subChapterItem.textContent = subChapter;
|
subChapterItem.textContent = subChapter.lesson_name;
|
||||||
console.log('-------------------')
|
console.log('-------------------')
|
||||||
console.log(course)
|
console.log(course)
|
||||||
// 查找学生进度
|
// 查找学生进度
|
||||||
for(let i = 0; i < course.lesson_processs.length; i++) {
|
for(let i = 0; i < course.lesson_processs.length; i++) {
|
||||||
if (course.lesson_processs[i][1] == chapter.lesson_id) {//确定lesson_id对应正确
|
if (course.lesson_processs[i][2] == subChapter.lesson_name && course.lesson_processs[i][1] == chapter.chapter_name) {//确定lesson_id对应正确
|
||||||
|
///////////////////////////////////////////
|
||||||
|
//这里之后要做lesson的各个step的进度和得分
|
||||||
|
////////////////////////////////////////////
|
||||||
|
|
||||||
console.log('-------------------')
|
console.log('-------------------')
|
||||||
console.log(course.lesson_processs[i][0])
|
console.log(course.lesson_processs[i][0])
|
||||||
lesson_chapters_progress = course.lesson_processs[i][0];//寻找对应的每一个章节的进度
|
lesson_chapters_progress = course.lesson_processs[i][0];//寻找对应的每一个章节的进度
|
||||||
@@ -87,7 +93,7 @@ function openCourseProgress(courseId) {
|
|||||||
subChapterItem.textContent += ("(已"+(chapter_progress.is_rebuttal?"申辩" : "完成")+" 得分"+ (chapter_progress.is_rebuttal?chapter_progress.rebuttal_score : chapter_progress.score) +")");
|
subChapterItem.textContent += ("(已"+(chapter_progress.is_rebuttal?"申辩" : "完成")+" 得分"+ (chapter_progress.is_rebuttal?chapter_progress.rebuttal_score : chapter_progress.score) +")");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (course.lessons[i].progress === 1) {
|
if (course.lesson_processs[i][0].length === 1) {
|
||||||
subChapterItem.classList.add('completed');
|
subChapterItem.classList.add('completed');
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
@@ -96,8 +102,8 @@ function openCourseProgress(courseId) {
|
|||||||
|
|
||||||
// 添加点击事件跳转到学习页面
|
// 添加点击事件跳转到学习页面
|
||||||
subChapterItem.addEventListener('click', () => {
|
subChapterItem.addEventListener('click', () => {
|
||||||
const courseId = encodeURIComponent(course.course_id); // 对课程名称进行编码
|
const courseId = encodeURIComponent(course._id); // 对课程名称进行编码
|
||||||
const url = `/desktop_nouser/${courseId}/${chapter.lesson_id}`;
|
const url = `/desktop_nouser/${courseId}/${chapter.chapter_name}/${subChapter.lesson_name}`;
|
||||||
window.location.href = url; // 跳转到该学习页面
|
window.location.href = url; // 跳转到该学习页面
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -114,6 +120,13 @@ function openCourseProgress(courseId) {
|
|||||||
|
|
||||||
chapterList.appendChild(chapterItem);
|
chapterList.appendChild(chapterItem);
|
||||||
});
|
});
|
||||||
|
})
|
||||||
|
.catch(error => {
|
||||||
|
console.error('Error:', error);
|
||||||
|
});
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
// 打开滑出卡片
|
// 打开滑出卡片
|
||||||
document.getElementById('courseProgress').classList.add('open');
|
document.getElementById('courseProgress').classList.add('open');
|
||||||
|
|||||||
16
Html/apps/static/二分.html
Normal file
16
Html/apps/static/二分.html
Normal file
@@ -0,0 +1,16 @@
|
|||||||
|
|
||||||
|
<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">
|
||||||
|
<p>b'# \xe4\xba\x8c\xe5\x88\x86\n> Auto-generated at 2025-09-05 19:48\n\n## \xe9\x98\xb6\xe6\xae\xb5\xe4\xb8\x80\xef\xbc\x9a\xe7\xa4\xba\xe4\xbe\x8b\xe9\x98\xb6\xe6\xae\xb5\n### \xe5\xbc\x95\xe5\x85\xa5\n\n\xe4\xba\x8c\xe5\x88\x86\xe6\x98\xaf\xe4\xb8\x80\xe4\xb8\xaa\xe5\xbe\x88\xe7\xae\x80\xe5\x8d\x95\xe5\x9f\xba\xe7\xa1\x80\xef\xbc\x8c\xe4\xbd\x86\xe5\xbe\x88\xe9\x87\x8d\xe8\xa6\x81\xe7\x9a\x84\xe7\x9f\xa5\xe8\xaf\x86\xe7\x82\xb9\xef\xbc\x8c\xe4\xb8\xba\xe4\xbb\xa5\xe5\x90\x8e\xe8\xae\xb8\xe5\xa4\x9a\xe9\xab\x98\xe7\xba\xa7\xe7\x9a\x84\xe6\x95\xb0\xe6\x8d\xae\xe7\xbb\x93\xe6\x9e\x84\xe4\xb8\x8e\xe7\xae\x97\xe6\xb3\x95\xe9\x93\xba\xe5\x9e\xab\xe3\x80\x82\n\n\xe4\xb8\x8b\xe9\x9d\xa2\xe6\x98\xaf\xe4\xb8\x80\xe4\xb8\xaa\xe7\x94\xa8\xe4\xba\x8c\xe5\x88\x86\xe7\x9a\x84\xe7\xae\x80\xe5\x8d\x95\xe5\x9c\xba\xe6\x99\xaf\xef\xbc\x9a\n\n\xe5\x81\x87\xe8\xae\xbe\xe5\xb0\x8f\xe6\x98\x8e\xe4\xbb\x8e0\xe5\x88\xb01000\xe4\xb9\x8b\xe9\x97\xb4\xe9\x80\x89\xe6\x8b\xa9\xe4\xba\x86\xe4\xb8\x80\xe4\xb8\xaa\xe6\x95\xb0\xe5\xad\x97\xe4\xbd\x86\xe4\xb8\x8d\xe5\x91\x8a\xe8\xaf\x89\xe4\xbd\xa0\xef\xbc\x8c\xe4\xbd\xa0\xe5\x8f\xaf\xe4\xbb\xa5\xe4\xb8\x8d\xe6\x96\xad\xe7\x8c\x9c\xe6\xb5\x8b\xe8\xbf\x99\xe4\xb8\xaa\xe6\x95\xb0\xef\xbc\x8c\xe6\xaf\x8f\xe6\xac\xa1\xe7\x8c\x9c\xe6\xb5\x8b\xe5\xb0\x8f\xe6\x98\x8e\xe4\xbc\x9a\xe5\x91\x8a\xe7\x9f\xa5\xe4\xbd\xa0\xe7\x9a\x84\xe7\x8c\x9c\xe6\xb5\x8b\xe5\xbe\x97\xe8\xbf\x87\xe5\xa4\xa7\xe8\xbf\x98\xe6\x98\xaf\xe8\xbf\x87\xe5\xb0\x8f\xef\xbc\x8c\xe9\x97\xae\xe6\x9c\x80\xe5\xa4\x9a\xe5\x87\xa0\xe6\xac\xa1\xe5\xb0\xb1\xe4\xb8\x80\xe5\xae\x9a\xe8\x83\xbd\xe7\x8c\x9c\xe4\xb8\xad\xef\xbc\x9f\n\n\xe7\xad\x94\xe6\xa1\x88\xe6\x98\xaf\xe5\x88\xa9\xe7\x94\xa8\xe4\xba\x8c\xe5\x88\x86\xe6\x9f\xa5\xe6\x89\xbe\xe7\x9a\x84\xe5\x8e\x9f\xe7\x90\x86\xef\xbc\x8c\xe7\x8c\x9c\xe6\xb5\x8b11\xe6\xac\xa1\xe5\x8d\xb3\xe5\x8f\xaf\xe3\x80\x82\n\n1. \xe5\xaf\xb9\xe4\xba\x8e0\xe5\x88\xb01000\xe7\x9a\x84\xe7\xad\x94\xe6\xa1\x88\xe5\xa4\x87\xe9\x80\x89\xe5\x8c\xba\xef\xbc\x8c\xe7\x8c\x9c\xe6\xb5\x8b\xe4\xb8\xad\xe4\xbd\x8d\xe6\x95\xb0500\xef\xbc\x8c\xe5\x81\x87\xe8\xae\xbe\xe8\xbf\x87\xe5\xb0\x8f\xef\xbc\x8c\n2. \xe5\x88\x99\xe5\xaf\xb9\xe4\xba\x8e501\xe5\x88\xb01000\xe7\x9a\x84\xe7\xad\x94\xe6\xa1\x88\xe5\xa4\x87\xe9\x80\x89\xe5\x8c\xba\xef\xbc\x8c\xe7\x8c\x9c\xe6\xb5\x8b750\xef\xbc\x8c\xe5\x81\x87\xe8\xae\xbe\xe8\xbf\x87\xe5\xa4\xa7\n3. \xe5\x88\x99\xe5\xaf\xb9\xe4\xba\x8e501\xe5\x88\xb0749\xe7\x9a\x84\xe7\xad\x94\xe6\xa1\x88\xe5\xa4\x87\xe9\x80\x89\xe5\x8c\xba\xef\xbc\x8c\xe7\x8c\x9c\xe6\xb5\x8b625\xef\xbc\x8c\xe5\x81\x87\xe8\xae\xbe\xe8\xbf\x87\xe5\xb0\x8f\xef\xbc\x8c\n4. \xe5\x88\x99\xe5\xaf\xb9\xe4\xba\x8e626\xe5\x88\xb0749\xe5\x8c\xba\xe9\x97\xb4......\n5. \xef\xbc\x88688-749\xef\xbc\x89\n6. \xef\xbc\x88718-749\xef\xbc\x89\n7. \xef\xbc\x88734-749\xef\xbc\x89\n8. \xef\xbc\x88742-749\xef\xbc\x89\n9. \xef\xbc\x88746-749\xef\xbc\x89\n10. \xef\xbc\x88748-749\xef\xbc\x89\n11. \xef\xbc\x88749-749\xef\xbc\x89\n\n\xe5\x9c\xa8\xe6\x9c\x80\xe5\xb7\xae\xe7\x9a\x84\xe6\x83\x85\xe5\x86\xb5\xe4\xb8\x8b\xef\xbc\x8c\xe7\xac\xac11\xe6\xac\xa1\xe7\x9a\x84\xe7\xad\x94\xe6\xa1\x88\xe5\xa4\x87\xe9\x80\x89\xe5\x8c\xba\xe5\xb0\xb1\xe4\xb8\x80\xe5\xae\x9a\xe9\x95\xbf\xe5\xba\xa6\xe4\xb8\xba1\xe4\xba\x86\xef\xbc\x8c\xe4\xb9\x9f\xe5\xb0\xb1\xe6\x98\xaf\xe5\xbf\x85\xe7\x84\xb6\xe6\x98\xaf\xe7\xad\x94\xe6\xa1\x88\xe3\x80\x82\n\n\xe5\x9b\xa0\xe6\xad\xa4\xe5\xa6\x82\xe6\x9e\x9c\xe5\xba\x8f\xe5\x88\x97\xe6\x98\xaf\xe6\x9c\x89\xe5\xba\x8f\xe7\x9a\x84\xef\xbc\x8c\xe5\xb0\xb1\xe5\x8f\xaf\xe4\xbb'</p>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
|
|
||||||
56
Html/apps/static/基础算法-二分.html
Normal file
56
Html/apps/static/基础算法-二分.html
Normal file
@@ -0,0 +1,56 @@
|
|||||||
|
|
||||||
|
<html>
|
||||||
|
<head>
|
||||||
|
<style>
|
||||||
|
img {
|
||||||
|
max-width: 100%;
|
||||||
|
height: auto;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/github-markdown-css/5.2.0/github-markdown.min.css">
|
||||||
|
</head>
|
||||||
|
<body class="markdown-body">
|
||||||
|
<h1>二分</h1>
|
||||||
|
<blockquote>
|
||||||
|
<p>Auto-generated at 2025-09-05 19:48</p>
|
||||||
|
</blockquote>
|
||||||
|
<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<=n<=10^5)</p>
|
||||||
|
<p>第二行n个用空格分开的整数ai。(0<=ai<=10^8)</p>
|
||||||
|
<p>第三行一个整数q,表示询问的次数。(1<=q<=10^4)</p>
|
||||||
|
<p>后q行,每行一个整数b,表示询问的数。(0<=b<=10^8)</p>
|
||||||
|
<h5>输出</h5>
|
||||||
|
<p>q行,每行一个整数,对应每次询问的返回结果。</p>
|
||||||
|
<h5>提示:</h5>
|
||||||
|
<p>完成代码后,通知Agent进行评测。</p>
|
||||||
|
<p>如果你还不完全会这个算法,询问Agent获取提示并进行学习。</p>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
|
|
||||||
@@ -31,6 +31,13 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<!-- 可以继续添加课程卡片 -->
|
<!-- 可以继续添加课程卡片 -->
|
||||||
|
{% for course in user_selected_course %}
|
||||||
|
<div class="course-card"onclick="openCourseProgress('{{course._id}}')">
|
||||||
|
<img src="{{course.image_url}}" alt="课程封面" class="course-image">
|
||||||
|
<div class="course-info">
|
||||||
|
<h3 class="course-name">{{course.name}}</h3>
|
||||||
|
<p class="course-description">{{course.description}}</p>
|
||||||
|
{% endfor %}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@@ -52,13 +59,14 @@
|
|||||||
</div>
|
</div>
|
||||||
<script src="/static/js/dashboard.js"></script>
|
<script src="/static/js/dashboard.js"></script>
|
||||||
<script>
|
<script>
|
||||||
window.appData = {
|
|
||||||
user_data: JSON.parse(`{{user_data}}`.replace(/"/g, "\"")),
|
console.log(`{{user_data}}`.replaceAll("'", "\"").replaceAll("True", "true").replaceAll("False", "false"))
|
||||||
user_course_data: JSON.parse(`{{user_course_data}}`.replace(/"/g, "\""))
|
console.log(`{{user_course_data}}`.replaceAll(""", "\"").replaceAll("True", "true").replaceAll("False", "false"))
|
||||||
}
|
let user_data = JSON.parse(`{{user_data}}`.replaceAll("'", "\"").replaceAll("True", "true").replaceAll("False", "false"));
|
||||||
console.log(window.appData.user_data)
|
let user_course_data = JSON.parse(`{{user_course_data}}`.replaceAll(""", "\"").replaceAll("True", "true").replaceAll("False", "false"))
|
||||||
console.log(window.appData.user_course_data)
|
console.log(user_data)
|
||||||
show_user_data(window.appData.user_data, window.appData.user_course_data)
|
console.log(user_course_data)
|
||||||
|
show_user_data(user_data, user_course_data)
|
||||||
</script>
|
</script>
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
||||||
|
|||||||
@@ -17,10 +17,10 @@
|
|||||||
<script src="https://cdn.bootcdn.net/ajax/libs/jquery/3.7.1/jquery.min.js"></script>
|
<script src="https://cdn.bootcdn.net/ajax/libs/jquery/3.7.1/jquery.min.js"></script>
|
||||||
</head>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
<div class="maxcontainer" id="maxcontainer">
|
<div class="maxcontainer" id="maxcontainer" style="height: 100%;">
|
||||||
<!-- 左侧内容 -->
|
<!-- 左侧内容 -->
|
||||||
<div class="sidebar" id="leftSidebar" style="width: 100%;height: 100%;">
|
<div class="sidebar" id="leftSidebar" style="width: 100%;height: 100%;">
|
||||||
<div id="markdown-content"style="width: 100%;height: 95%;">
|
<div id="markdown-content"style="width: 100%;height: 93%;">
|
||||||
<!-- 动态加载的 HTML 会显示在这里 -->
|
<!-- 动态加载的 HTML 会显示在这里 -->
|
||||||
</div>
|
</div>
|
||||||
<div id="markdown-content-process"style="width: 100%;height: 5%;">
|
<div id="markdown-content-process"style="width: 100%;height: 5%;">
|
||||||
@@ -30,14 +30,14 @@
|
|||||||
<!-- 这里将显示鼠标悬停的详情 -->
|
<!-- 这里将显示鼠标悬停的详情 -->
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div id="dragbar"></div>
|
<div id="dragbar" class="resizer"></div>
|
||||||
|
|
||||||
<!-- 中间嵌入的 Vscode-web 窗口,带有 tkn 参数 -->
|
<!-- 中间嵌入的 Vscode-web 窗口,带有 tkn 参数 -->
|
||||||
<div class="vscode-web"id="vscodeWeb" >
|
<div class="vscode-web"id="vscodeWeb" >
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|
||||||
<div id="dragbar2"></div>
|
<div id="dragbar2" class="resizer"></div>
|
||||||
<!-- 右侧内容 -->
|
<!-- 右侧内容 -->
|
||||||
<div class="sidebar" id="rightSidebar"style="width: 100%;height: 100%;">
|
<div class="sidebar" id="rightSidebar"style="width: 100%;height: 100%;">
|
||||||
<div class="chatbox-header">
|
<div class="chatbox-header">
|
||||||
@@ -54,7 +54,8 @@
|
|||||||
</label>
|
</label>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="chatbox" id="chatbox" style="width: 100%;height: 80%;">
|
|
||||||
|
<div class="chatbox" id="chatbox" >
|
||||||
<!-- 消息会在这里显示 -->
|
<!-- 消息会在这里显示 -->
|
||||||
</div>
|
</div>
|
||||||
<div class="input-area">
|
<div class="input-area">
|
||||||
@@ -66,7 +67,8 @@
|
|||||||
window.appData = {
|
window.appData = {
|
||||||
user_uuid:"{{user_uuid}}",
|
user_uuid:"{{user_uuid}}",
|
||||||
course_id:"{{course_id}}",
|
course_id:"{{course_id}}",
|
||||||
chapter_id:"{{chapter_id}}",
|
lesson_name:"{{lesson_name}}",
|
||||||
|
chapter_name:"{{chapter_name}}",
|
||||||
folder:"{{workspace_path}}"
|
folder:"{{workspace_path}}"
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
@@ -112,12 +114,19 @@
|
|||||||
let isDraggingLeft = false;
|
let isDraggingLeft = false;
|
||||||
let isDraggingRight = false;
|
let isDraggingRight = false;
|
||||||
|
|
||||||
dragbar.addEventListener('mousedown', function() {
|
// 当鼠标按下时,禁用 iframe 的鼠标事件
|
||||||
|
dragbar.addEventListener('mousedown', function(e) {
|
||||||
isDraggingLeft = true;
|
isDraggingLeft = true;
|
||||||
|
document.querySelectorAll('iframe').forEach(function(iframe) {
|
||||||
|
iframe.style.pointerEvents = 'none'; // 禁止 iframe 捕获鼠标事件
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
dragbar2.addEventListener('mousedown', function() {
|
dragbar2.addEventListener('mousedown', function(e) {
|
||||||
isDraggingRight = true;
|
isDraggingRight = true;
|
||||||
|
document.querySelectorAll('iframe').forEach(function(iframe) {
|
||||||
|
iframe.style.pointerEvents = 'none'; // 禁止 iframe 捕获鼠标事件
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
document.addEventListener('mousemove', function(e) {
|
document.addEventListener('mousemove', function(e) {
|
||||||
@@ -133,6 +142,9 @@
|
|||||||
document.addEventListener('mouseup', function() {
|
document.addEventListener('mouseup', function() {
|
||||||
isDraggingLeft = false;
|
isDraggingLeft = false;
|
||||||
isDraggingRight = false;
|
isDraggingRight = false;
|
||||||
|
document.querySelectorAll('iframe').forEach(function(iframe) {
|
||||||
|
iframe.style.pointerEvents = 'auto'; // 恢复 iframe 的鼠标事件
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
||||||
@@ -156,7 +168,7 @@
|
|||||||
const sessionInfo = data
|
const sessionInfo = data
|
||||||
iframe.src = '{{vscode_web_url}}/?workspace={{workspace_path}}&folder={{workspace_path}}';
|
iframe.src = '{{vscode_web_url}}/?workspace={{workspace_path}}&folder={{workspace_path}}';
|
||||||
iframe.style.width = '100%';
|
iframe.style.width = '100%';
|
||||||
iframe.style.height = '100%';
|
iframe.style.height = '99%';
|
||||||
|
|
||||||
// 将 iframe 插入到指定的 div 中
|
// 将 iframe 插入到指定的 div 中
|
||||||
document.getElementById('vscodeWeb').appendChild(iframe);
|
document.getElementById('vscodeWeb').appendChild(iframe);
|
||||||
@@ -174,8 +186,8 @@
|
|||||||
|
|
||||||
|
|
||||||
// 动态加载 Markdown 文件
|
// 动态加载 Markdown 文件
|
||||||
function loadMarkdown(course_id, filename) {
|
function loadMarkdown(course_id, chapter_name, lesson_name) {
|
||||||
fetch(`/${course_id}-${filename}-markdown`)
|
fetch(`/${course_id}-${chapter_name}-${lesson_name}-markdown`)
|
||||||
.then(response => response.json())
|
.then(response => response.json())
|
||||||
.then(data => {
|
.then(data => {
|
||||||
if (data.html_url) {
|
if (data.html_url) {
|
||||||
@@ -194,7 +206,7 @@
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
document.addEventListener('DOMContentLoaded', function() {
|
document.addEventListener('DOMContentLoaded', function() {
|
||||||
loadMarkdown('{{course_id}}','{{chapter_id}}');
|
loadMarkdown('{{course_id}}','{{chapter_name}}','{{lesson_name}}');
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -6,21 +6,21 @@
|
|||||||
<title>课程选择主页</title>
|
<title>课程选择主页</title>
|
||||||
<link rel="stylesheet" href="/static/css/index.css">
|
<link rel="stylesheet" href="/static/css/index.css">
|
||||||
</head>
|
</head>
|
||||||
<body onload="init_index()" >
|
<body >
|
||||||
{% include 'navbar.html' %} <!-- 引入navbar.html -->
|
{% include 'navbar.html' %} <!-- 引入navbar.html -->
|
||||||
<main>
|
<main>
|
||||||
<div class="course-selection">
|
<div class="course-selection">
|
||||||
<h2>课程列表</h2>
|
<h2>课程列表</h2>
|
||||||
<div class="course-list">
|
<div class="course-list">
|
||||||
{% for course_id, course in courses_data.items() %}
|
{% for course in courses_data %}
|
||||||
<div class="course-card" onclick="show_course_details('{{ course_id }}')">
|
<div class="course-card" onclick="show_course_details('{{ course._id }}')">
|
||||||
<img src="{{ course.course_img_path }}" alt="{{ course.course_name }}">
|
<img src="{{ course.image_url }}" alt="{{ course.course_name }}">
|
||||||
<h3>{{ course.course_name }}</h3>
|
<h3>{{ course.name }}</h3>
|
||||||
<p>{{ course.course_description }}</p>
|
<p>{{ course.description }}</p>
|
||||||
{% if course_id in selected_courses %}
|
{% if course._id in selected_courses %}
|
||||||
<button class="selected-button">已选择</button>
|
<button class="selected-button">已选择</button>
|
||||||
{% else %}
|
{% else %}
|
||||||
<button class="select-button" data-course-id="{{course_id}}">选择课程</button>
|
<button class="select-button" data-course-id="{{course._id}}">选择课程</button>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
</div>
|
</div>
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
|
|||||||
@@ -99,7 +99,7 @@
|
|||||||
<div class="avatar dropdown">
|
<div class="avatar dropdown">
|
||||||
<img src="/static/image/algorithm/book_cover.png" alt="个人头像" class="avatar-img">
|
<img src="/static/image/algorithm/book_cover.png" alt="个人头像" class="avatar-img">
|
||||||
<div class="dropdown-content">
|
<div class="dropdown-content">
|
||||||
<a href="/switch-role" id="switch-role">切换到学生/教师</a>
|
<a href="/switch-role/{{'student' if role != 'teacher' else 'teacher'}}" id="switch-role">切换到学生/教师</a>
|
||||||
<a href="/logout" id="logout">注销</a>
|
<a href="/logout" id="logout">注销</a>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -43,6 +43,13 @@ def login_teacher_post():
|
|||||||
return jsonify({"success": ok, "message": msg})
|
return jsonify({"success": ok, "message": msg})
|
||||||
|
|
||||||
|
|
||||||
|
@bp.get("/switch-role/<role>")
|
||||||
|
@require_role
|
||||||
|
def switch_role(role):
|
||||||
|
if role == "teacher":
|
||||||
|
return redirect(url_for("main.dashboard"))
|
||||||
|
else:
|
||||||
|
return redirect(url_for("material.teacherboard"))
|
||||||
@bp.get("/logout")
|
@bp.get("/logout")
|
||||||
def logout():
|
def logout():
|
||||||
logout_user()
|
logout_user()
|
||||||
|
|||||||
@@ -1,12 +1,22 @@
|
|||||||
# myapp/views/dashboard.py
|
# myapp/views/dashboard.py
|
||||||
import json
|
import json
|
||||||
from flask import Blueprint, render_template, request, jsonify, redirect, url_for
|
from flask import Blueprint, render_template, request, jsonify, redirect, url_for, current_app, session
|
||||||
from ..auth.decorators import require_role
|
from ..auth.decorators import require_role
|
||||||
from ..services.user_service import get_or_load_current_user, add_course_for_current_user
|
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
|
from ..services.course_service import load_material, user_selected_course_briefs, get_new_edit_materials,user_selected_course
|
||||||
|
|
||||||
bp = Blueprint("main", __name__) # 根路径蓝图
|
bp = Blueprint("main", __name__) # 根路径蓝图
|
||||||
|
|
||||||
|
@bp.post("/dashboard/get_course_progress")
|
||||||
|
@require_role
|
||||||
|
def get_course_progress():
|
||||||
|
user_obj = get_or_load_current_user()
|
||||||
|
course_id = request.json.get("course_id")
|
||||||
|
|
||||||
|
if not course_id:
|
||||||
|
return jsonify({"success": False, "message": "缺少 course_id"}), 400
|
||||||
|
return jsonify({"success": True, "message": "获取课程进度成功", "data": user_obj.get_course_progress(course_id)}), 200
|
||||||
|
|
||||||
@bp.get("/dashboard")
|
@bp.get("/dashboard")
|
||||||
@require_role
|
@require_role
|
||||||
def dashboard():
|
def dashboard():
|
||||||
@@ -14,17 +24,18 @@ def dashboard():
|
|||||||
if user_obj is None:
|
if user_obj is None:
|
||||||
return redirect(url_for("auth.login"))
|
return redirect(url_for("auth.login"))
|
||||||
|
|
||||||
user_course_data = user_selected_course_briefs(user_obj)
|
user_course_data = user_selected_course(user_obj)
|
||||||
return render_template(
|
return render_template(
|
||||||
"dashboard.html",
|
"dashboard.html",
|
||||||
user_data=user_obj.to_json_without_dialog(),
|
user_data=user_obj.__json__(),
|
||||||
user_course_data=json.dumps(user_course_data, ensure_ascii=False),
|
user_course_data=json.dumps(user_course_data, ensure_ascii=False),
|
||||||
|
user_selected_course=user_course_data
|
||||||
)
|
)
|
||||||
|
|
||||||
@bp.get("/course/<course_id>")
|
@bp.get("/course/<course_id>")
|
||||||
@require_role
|
@require_role
|
||||||
def course(course_id):
|
def course(course_id):
|
||||||
c = load_course(course_id)
|
c = load_material(course_id)
|
||||||
return render_template("course.html", course_id=course_id, course_data=c)
|
return render_template("course.html", course_id=course_id, course_data=c)
|
||||||
|
|
||||||
@bp.post("/select_course")
|
@bp.post("/select_course")
|
||||||
@@ -35,8 +46,13 @@ def select_course():
|
|||||||
if not course_id:
|
if not course_id:
|
||||||
return jsonify({"success": False, "message": "缺少 course_id"}), 400
|
return jsonify({"success": False, "message": "缺少 course_id"}), 400
|
||||||
|
|
||||||
course_data = load_course(course_id)
|
# course_data = load_material(course_id)
|
||||||
ok = add_course_for_current_user(course_id, course_data)
|
user_list = current_app.extensions["users_list"]
|
||||||
|
uuid2username = current_app.extensions["uuid2username"]
|
||||||
|
username = uuid2username[session["user_uuid"]]
|
||||||
|
user_obj = user_list.get_user_by_name(username)
|
||||||
|
ok = user_obj.select_new_course(course_id, load_material(course_id))
|
||||||
|
|
||||||
if not ok:
|
if not ok:
|
||||||
return jsonify({"success": False, "message": "未登录或会话失效"}), 401
|
return jsonify({"success": False, "message": "未登录或会话失效"}), 401
|
||||||
return jsonify({"success": True, "message": "课程选择成功"})
|
return jsonify({"success": True, "message": "课程选择成功"})
|
||||||
@@ -49,10 +65,8 @@ def home_index():
|
|||||||
return redirect(url_for("auth.login"))
|
return redirect(url_for("auth.login"))
|
||||||
|
|
||||||
selected_courses = list(getattr(user_obj, "select_course", []))
|
selected_courses = list(getattr(user_obj, "select_course", []))
|
||||||
# 课程目录:依据你的 CourseList 暴露的接口进行传递(这里直接传对象,模板里用)
|
course_list = get_new_edit_materials(10)
|
||||||
from flask import current_app
|
print(course_list)
|
||||||
course_list = current_app.extensions["course_list"]
|
|
||||||
|
|
||||||
return render_template(
|
return render_template(
|
||||||
"index.html",
|
"index.html",
|
||||||
courses_data=course_list,
|
courses_data=course_list,
|
||||||
|
|||||||
@@ -2,32 +2,25 @@ import os
|
|||||||
from flask import Blueprint, jsonify, current_app, send_from_directory
|
from flask import Blueprint, jsonify, current_app, send_from_directory
|
||||||
from ..services.markdown_service import (
|
from ..services.markdown_service import (
|
||||||
convert_markdown_to_html, wrap_with_styles,
|
convert_markdown_to_html, wrap_with_styles,
|
||||||
save_html, copy_images
|
save_html, copy_images, load_markdown_file
|
||||||
|
|
||||||
)
|
)
|
||||||
|
|
||||||
bp = Blueprint("markdown", __name__)
|
bp = Blueprint("markdown", __name__)
|
||||||
|
|
||||||
@bp.route("/<course_id>-<filename>-markdown", methods=["GET"])
|
@bp.route("/<course_id>-<chapter_name>-<lesson_name>-markdown", methods=["GET"])
|
||||||
def convert_md(course_id, filename):
|
def convert_md(course_id, chapter_name, lesson_name):
|
||||||
md_file_path = os.path.join(current_app.config["MARKDOWN_DIR"], course_id, f"{filename}.md")
|
print(f"convert_md: {course_id}, {chapter_name}, {lesson_name}")
|
||||||
|
md_file = load_markdown_file(course_id, chapter_name, lesson_name)
|
||||||
if not os.path.exists(md_file_path):
|
|
||||||
return jsonify({"error": "Markdown file not found"}), 404
|
|
||||||
|
|
||||||
# 转换 markdown -> html
|
# 转换 markdown -> html
|
||||||
html = convert_markdown_to_html(md_file_path)
|
html = convert_markdown_to_html(md_file)
|
||||||
html_with_styles = wrap_with_styles(html)
|
html_with_styles = wrap_with_styles(html)
|
||||||
|
|
||||||
# 保存 HTML 文件到 static
|
# 保存 HTML 文件到 static
|
||||||
html_output_path = os.path.join(current_app.config["STATIC_DIR"], f"{filename}.html")
|
html_output_path = os.path.join(current_app.config["STATIC_DIR"], f"{chapter_name}-{lesson_name}.html")
|
||||||
save_html(html_with_styles, html_output_path)
|
save_html(html_with_styles, html_output_path)
|
||||||
|
|
||||||
# 拷贝图片资源
|
return jsonify({"html_url": f"/static/{chapter_name}-{lesson_name}.html"})
|
||||||
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>")
|
@bp.route("/static/<path:filename>")
|
||||||
|
|||||||
@@ -5,8 +5,8 @@ from ..services.backboard_service import realtime_response
|
|||||||
|
|
||||||
bp = Blueprint("vscode", __name__)
|
bp = Blueprint("vscode", __name__)
|
||||||
|
|
||||||
@bp.route("/desktop/<user_uuid>/<course_id>/<chapter_id>")
|
@bp.route("/desktop/<user_uuid>/<course_id>/<chapter_name>/<lesson_name>")
|
||||||
def desktop(user_uuid, course_id, chapter_id):
|
def desktop(user_uuid, course_id, chapter_name, lesson_name):
|
||||||
username2uuid = current_app.extensions["username2uuid"]
|
username2uuid = current_app.extensions["username2uuid"]
|
||||||
uuid2username = current_app.extensions["uuid2username"]
|
uuid2username = current_app.extensions["uuid2username"]
|
||||||
userid_recorder = current_app.extensions["userid_recorder"]
|
userid_recorder = current_app.extensions["userid_recorder"]
|
||||||
@@ -23,7 +23,7 @@ def desktop(user_uuid, course_id, chapter_id):
|
|||||||
|
|
||||||
# 按课程/章节创建工作目录
|
# 按课程/章节创建工作目录
|
||||||
base_root = current_app.config["STUDENT_WORKSPACE_ROOT"]
|
base_root = current_app.config["STUDENT_WORKSPACE_ROOT"]
|
||||||
path_dir = os.path.join(base_root, user_id, course_id, chapter_id)
|
path_dir = os.path.join(base_root, user_id, course_id, chapter_name)
|
||||||
os.makedirs(path_dir, exist_ok=True)
|
os.makedirs(path_dir, exist_ok=True)
|
||||||
|
|
||||||
# 写 .config(并按需要转换为 WSL 路径)
|
# 写 .config(并按需要转换为 WSL 路径)
|
||||||
@@ -37,7 +37,8 @@ def desktop(user_uuid, course_id, chapter_id):
|
|||||||
tmpd = {
|
tmpd = {
|
||||||
"user_uuid": session["user_uuid"],
|
"user_uuid": session["user_uuid"],
|
||||||
"course_id": course_id,
|
"course_id": course_id,
|
||||||
"chapter_id": chapter_id,
|
"chapter_name": chapter_name,
|
||||||
|
"lesson_name": lesson_name,
|
||||||
"path": path_for_vscode
|
"path": path_for_vscode
|
||||||
}
|
}
|
||||||
with open(config_path, "w", encoding="utf-8") as f:
|
with open(config_path, "w", encoding="utf-8") as f:
|
||||||
@@ -48,16 +49,16 @@ def desktop(user_uuid, course_id, chapter_id):
|
|||||||
return render_template(
|
return render_template(
|
||||||
"desktop.html",
|
"desktop.html",
|
||||||
vscode_web_url=current_app.config["VSCODE_WEB_URL"],
|
vscode_web_url=current_app.config["VSCODE_WEB_URL"],
|
||||||
user_uuid=session["user_uuid"], course_id=course_id, chapter_id=chapter_id,
|
user_uuid=session["user_uuid"], course_id=course_id, chapter_name=chapter_name, lesson_name=lesson_name,
|
||||||
workspace_path=path_for_vscode
|
workspace_path=path_for_vscode,
|
||||||
)
|
)
|
||||||
|
|
||||||
@bp.route("/desktop_nouser/<course_id>/<chapter_id>")
|
@bp.route("/desktop_nouser/<course_id>/<chapter_name>/<lesson_name>")
|
||||||
def desktop_nouser(course_id, chapter_id):
|
def desktop_nouser(course_id, chapter_name, lesson_name):
|
||||||
if "user_uuid" not in session:
|
if "user_uuid" not in session:
|
||||||
return redirect(url_for("auth.login")) # 如果你有 auth 蓝图
|
return redirect(url_for("auth.login")) # 如果你有 auth 蓝图
|
||||||
user_uuid = session["user_uuid"]
|
user_uuid = session["user_uuid"]
|
||||||
return redirect(url_for("vscode.desktop", user_uuid=user_uuid, course_id=course_id, chapter_id=chapter_id))
|
return redirect(url_for("vscode.desktop", user_uuid=user_uuid, course_id=course_id, chapter_name=chapter_name, lesson_name=lesson_name))
|
||||||
|
|
||||||
@bp.route("/vscode_data", methods=["POST"])
|
@bp.route("/vscode_data", methods=["POST"])
|
||||||
def vscode_data():
|
def vscode_data():
|
||||||
|
|||||||
@@ -25,6 +25,11 @@ class Course:
|
|||||||
def to_json(self):
|
def to_json(self):
|
||||||
return json.dumps(self, default=lambda o: o.__json__(), indent=4)
|
return json.dumps(self, default=lambda o: o.__json__(), indent=4)
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
def load_course_from_json(course_id, course_data_dir = None)-> Course:
|
def load_course_from_json(course_id, course_data_dir = None)-> Course:
|
||||||
try:
|
try:
|
||||||
COURSE_DATA_DIR = course_data_dir if course_data_dir else LOCAL_COURSE_DATA_DIR
|
COURSE_DATA_DIR = course_data_dir if course_data_dir else LOCAL_COURSE_DATA_DIR
|
||||||
|
|||||||
1
study/TCake/68bacdfadf5aeae0912f7f18/二分/.config
Normal file
1
study/TCake/68bacdfadf5aeae0912f7f18/二分/.config
Normal file
@@ -0,0 +1 @@
|
|||||||
|
{"user_uuid": "user_4b50781c-6988-4d67-9c74-fc529011468c", "course_id": "68bacdfadf5aeae0912f7f18", "chapter_id": "二分", "path": "/mnt/f/GIThubRepository/CodeAgent/code-agent/study/TCake/68bacdfadf5aeae0912f7f18/二分"}
|
||||||
1
study/TCake/68bacdfadf5aeae0912f7f18/基础算法/.config
Normal file
1
study/TCake/68bacdfadf5aeae0912f7f18/基础算法/.config
Normal file
@@ -0,0 +1 @@
|
|||||||
|
{"user_uuid": "user_ec2c74c2-9466-4ea1-99ae-15c2ea3f8c8e", "course_id": "68bacdfadf5aeae0912f7f18", "chapter_name": "基础算法", "lesson_name": "二分", "path": "/mnt/f/GIThubRepository/CodeAgent/code-agent/study/TCake/68bacdfadf5aeae0912f7f18/基础算法"}
|
||||||
Reference in New Issue
Block a user