Compare commits
6 Commits
ae9f7b84f4
...
code-serve
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
496c5be90a | ||
|
|
ec6be8233c | ||
|
|
d0331d9361 | ||
|
|
8e79380ba4 | ||
|
|
6acb1db093 | ||
|
|
af0ebda419 |
1
Html/.gitattributes
vendored
1
Html/.gitattributes
vendored
@@ -1 +0,0 @@
|
|||||||
Html/config.ini merge=ours
|
|
||||||
480
Html/app_back.py
Normal file
480
Html/app_back.py
Normal file
@@ -0,0 +1,480 @@
|
|||||||
|
from bootstrap import bootstrap_paths
|
||||||
|
bootstrap_paths()
|
||||||
|
from functools import wraps
|
||||||
|
from flask import Flask, redirect, session, request, jsonify, render_template, send_from_directory, url_for
|
||||||
|
from flask_cors import CORS
|
||||||
|
from flask_socketio import SocketIO, join_room, emit, Namespace
|
||||||
|
import markdown
|
||||||
|
import os
|
||||||
|
import uuid
|
||||||
|
import shutil
|
||||||
|
import sys
|
||||||
|
import json
|
||||||
|
from apps.auth.decorators import require_role
|
||||||
|
parent_dir = os.path.abspath(os.path.join(os.path.dirname(__file__), '..'))
|
||||||
|
sys.path.insert(0, parent_dir)
|
||||||
|
student_workspace_root_path = os.path.abspath(os.path.join(os.path.dirname(__file__), '../study'))
|
||||||
|
current_dir = os.path.abspath(os.path.join(os.path.dirname(__file__), '.'))
|
||||||
|
sys.path.insert(0, current_dir)
|
||||||
|
from AlgoriAgent.projects.algoriAgent.agent_manager import AgentManager
|
||||||
|
|
||||||
|
|
||||||
|
import configparser
|
||||||
|
|
||||||
|
from db.user_list import UserList
|
||||||
|
from db.user import User, load_user_from_json, create_user_json
|
||||||
|
from db.course_list import CourseList
|
||||||
|
from db.course import Course, load_course_from_json
|
||||||
|
GLOBAL_CONFIG = configparser.ConfigParser()
|
||||||
|
GLOBAL_CONFIG.read('config.ini')
|
||||||
|
VSCODE_WEB_URL = GLOBAL_CONFIG['VSCODE_WEB']['url']
|
||||||
|
USER_DATA_DIR = GLOBAL_CONFIG['USER_DATA']['dir']
|
||||||
|
COURSE_DATA_DIR = GLOBAL_CONFIG['COURSE_DATA']['dir']
|
||||||
|
|
||||||
|
app = Flask(__name__)
|
||||||
|
socketio = SocketIO(app, cors_allowed_origins="*",ping_timeout=60, ping_interval=5)
|
||||||
|
# socketio = SocketIO(app, cors_allowed_origins="http://localhost:9888") # 设置跨域支持
|
||||||
|
import logging
|
||||||
|
app.secret_key = 'cakebaker'
|
||||||
|
app.logger.setLevel(logging.DEBUG)
|
||||||
|
# 配置 CORS
|
||||||
|
CORS(app, resources={r"/*": {"origins": VSCODE_WEB_URL},}, supports_credentials=True)
|
||||||
|
|
||||||
|
|
||||||
|
userid_recorder = {} # user_id&path -> session['user_id']
|
||||||
|
|
||||||
|
'''
|
||||||
|
Backboard
|
||||||
|
'''
|
||||||
|
from backboardManager import BackBoardManager, Backboard
|
||||||
|
|
||||||
|
backboard_manager = BackBoardManager()
|
||||||
|
uuid2username = {}
|
||||||
|
username2uuid = {}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
# 定义命名空间:用于和 VSCode 插件交流
|
||||||
|
class VSCodeNamespace(Namespace):
|
||||||
|
def on_login(self,data):
|
||||||
|
print("VSCode client connected")
|
||||||
|
print(data)
|
||||||
|
dataconfig = data['config']
|
||||||
|
user_id = dataconfig.get('user_id')
|
||||||
|
path = dataconfig.get('path')
|
||||||
|
course_id = dataconfig.get('course_id')
|
||||||
|
lesson_id = dataconfig.get('chapter_id')
|
||||||
|
print(f"User {user_id} connected with path: {path}")
|
||||||
|
useruuid = username2uuid[user_id]
|
||||||
|
join_room(useruuid, namespace='/vscode')
|
||||||
|
if backboard_manager.get_backboard(useruuid) == None:
|
||||||
|
backboard_manager.add_backboard(useruuid,user_id, course_id, lesson_id, path)
|
||||||
|
|
||||||
|
|
||||||
|
def on_message(self, data):
|
||||||
|
print(f"Received from VSCode client: {data}")
|
||||||
|
dataconfig = data['config']
|
||||||
|
realtime_response(dataconfig, data)
|
||||||
|
# emit('response', {'message': 'Config data received and connection established'})
|
||||||
|
def on_disconnect(self, data):
|
||||||
|
print("VSCode client disconnected")
|
||||||
|
print("Disconnect reason:"+str(data))
|
||||||
|
|
||||||
|
|
||||||
|
def realtime_response(config, realtime_action):
|
||||||
|
user_id = config['user_id']
|
||||||
|
folder_path = config['path']
|
||||||
|
useruuid = username2uuid[user_id]
|
||||||
|
bb = backboard_manager.get_backboard(useruuid)
|
||||||
|
assert type(bb) == Backboard
|
||||||
|
|
||||||
|
bb.add_history(realtime_action)
|
||||||
|
|
||||||
|
if realtime_action['type'] == 'workspaceFolders':
|
||||||
|
bb.file_tree = realtime_action['fileTree']
|
||||||
|
|
||||||
|
if realtime_action['type'] == 'activeFile':
|
||||||
|
file_path = realtime_action['filePath']
|
||||||
|
assert type(file_path) == str
|
||||||
|
with open(f"{file_path}") as f:
|
||||||
|
bb.active_file_content = f.read()
|
||||||
|
|
||||||
|
if realtime_action['type'] == 'paste':
|
||||||
|
file_path = realtime_action['filePath']
|
||||||
|
assert type(file_path) == str
|
||||||
|
bb.pasted_file_path = file_path
|
||||||
|
bb.pasted_content = realtime_action['content']
|
||||||
|
bb.active_file_path = file_path
|
||||||
|
with open(f"{file_path}") as f:
|
||||||
|
bb.active_file_content = f.read()
|
||||||
|
|
||||||
|
|
||||||
|
if realtime_action['type'] == 'fileEdit':
|
||||||
|
file_path = realtime_action['filePath']
|
||||||
|
assert type(file_path) == str
|
||||||
|
bb.active_file_path = file_path
|
||||||
|
with open(f"{file_path}") as f:
|
||||||
|
bb.active_file_content = f.read()
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
print("config"+str(config))
|
||||||
|
print("realtime_action"+str(realtime_action))
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
'''
|
||||||
|
Agent and Chat
|
||||||
|
'''
|
||||||
|
agent_manager = AgentManager(app = app, socketio = socketio)
|
||||||
|
user_threads = {}
|
||||||
|
import threading
|
||||||
|
from concurrent.futures import ThreadPoolExecutor, as_completed
|
||||||
|
|
||||||
|
# 初始化线程池
|
||||||
|
executor = ThreadPoolExecutor(max_workers=10)
|
||||||
|
class AgentNamespace(Namespace):
|
||||||
|
def on_login(self, data):
|
||||||
|
data = json.loads(data)
|
||||||
|
user = data['username']
|
||||||
|
course_id = data['course_id']
|
||||||
|
chapter_id = data['chapter_id']
|
||||||
|
user_uuid = username2uuid[user]
|
||||||
|
session['user_id'] = user_uuid
|
||||||
|
print(f'User connected with session user_id: {user_uuid}')
|
||||||
|
# 从markdown、markdown_prompts、score_prompts中各读取数据folder.md并进行new_agent
|
||||||
|
with open(f'books/markdown/{course_id}/{chapter_id}.md','r',encoding='UTF-8') as fmd,\
|
||||||
|
open(f'books/markdown_prompts/{course_id}/{chapter_id}.md','r',encoding='UTF-8')as fmdp,\
|
||||||
|
open(f'books/score_prompts/{course_id}/{chapter_id}.md','r',encoding='UTF-8') as fsp:
|
||||||
|
markdown = fmd.read()
|
||||||
|
markdown_prompts = fmdp.read()
|
||||||
|
score_prompts = fsp.read()
|
||||||
|
user_uuid, agent = agent_manager.new_agent(course_id, chapter_id, markdown, markdown_prompts, score_prompts, id=user_uuid,
|
||||||
|
root_path=f'../../study/{user}/{course_id}/{chapter_id}')
|
||||||
|
print(user_uuid)
|
||||||
|
join_room(user_uuid, namespace='/agent') # 将该用户加入以user_id为名的room
|
||||||
|
backboard_manager.add_backboard(user_uuid, user, course_id, chapter_id, root_path=f'../../study/{user}/{course_id}/{chapter_id}')
|
||||||
|
|
||||||
|
def on_language(self, language):
|
||||||
|
id = session.get('user_id')
|
||||||
|
agent_manager.change_language(id, language)
|
||||||
|
|
||||||
|
def on_message(self, data):
|
||||||
|
print(f"Message from client: {data}")
|
||||||
|
id = session.get('user_id')
|
||||||
|
if (type(data)==str):
|
||||||
|
data = json.loads(data)
|
||||||
|
print(id)
|
||||||
|
if data['type'] == 'text':
|
||||||
|
res = agent_manager.invoke(id, data['data'], backboard_manager.get_backboard(id).get_info_prompt())
|
||||||
|
print('=*='*20)
|
||||||
|
print(res.content)
|
||||||
|
reply = f"{res.content['speak']}"
|
||||||
|
with app.app_context():
|
||||||
|
emit('message',reply, room=id, namespace='/agent')
|
||||||
|
emit('request_function',res.content['function'], room=id, namespace='/agent')
|
||||||
|
|
||||||
|
if data['type'] == 'function':
|
||||||
|
agent_manager.function_call(id, data['data'])
|
||||||
|
|
||||||
|
def on_initiative(self,data):
|
||||||
|
print("User active function call")
|
||||||
|
user_id = session['user_id']
|
||||||
|
if data['name'] == 'sample_judge':
|
||||||
|
agent_manager.sample_judge(user_id, backboard_manager.get_backboard(user_id))
|
||||||
|
if data['name'] == 'judge':
|
||||||
|
agent_manager.judge(user_id, backboard_manager.get_backboard(user_id))
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
def on_disconnect(self,data):
|
||||||
|
print("VSCode client disconnected")
|
||||||
|
print("Disconnect reason:"+str(data))
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
'''
|
||||||
|
Markdown to HTML
|
||||||
|
'''
|
||||||
|
# 配置文件路径
|
||||||
|
MARKDOWN_DIR = 'books/markdown' # 存放 markdown 文件的文件夹
|
||||||
|
STATIC_DIR = 'static' # 存放生成的 HTML 和图片资源的文件夹
|
||||||
|
IMAGE_DIR = 'image' # 图片资源相对于 markdown 的位置
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
@app.route('/<course_id>-<filename>-markdown', methods=['GET'])
|
||||||
|
def convert_md(course_id, filename):
|
||||||
|
md_file_path = os.path.join(MARKDOWN_DIR,course_id, f'{filename}.md')
|
||||||
|
|
||||||
|
if not os.path.exists(md_file_path):
|
||||||
|
return jsonify({'error': 'Markdown file not found'}), 404
|
||||||
|
|
||||||
|
# 读取 markdown 文件内容
|
||||||
|
with open(md_file_path, 'r', encoding='utf-8') as file:
|
||||||
|
md_content = file.read()
|
||||||
|
|
||||||
|
# 将 markdown 转换为 HTML
|
||||||
|
html_content = markdown.markdown(md_content)
|
||||||
|
# 插入 CSS 样式,限制图片最大宽度为 100%,高度自动调整
|
||||||
|
html_with_styles = f"""
|
||||||
|
<html>
|
||||||
|
<head>
|
||||||
|
<style>
|
||||||
|
img {{
|
||||||
|
max-width: 100%;
|
||||||
|
height: auto;
|
||||||
|
}}
|
||||||
|
</style>
|
||||||
|
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/github-markdown-css/5.2.0/github-markdown.min.css">
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
{html_content}
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
|
"""
|
||||||
|
# 保存 HTML 文件到 static 文件夹
|
||||||
|
html_output_path = os.path.join(STATIC_DIR, f'{filename}.html')
|
||||||
|
with open(html_output_path, 'w', encoding='utf-8') as html_file:
|
||||||
|
html_file.write(html_with_styles)
|
||||||
|
|
||||||
|
# 处理图片资源,将 image/xxx 文件夹拷贝到 static/image/xxx
|
||||||
|
image_source_dir = os.path.join(MARKDOWN_DIR,course_id, IMAGE_DIR, filename)
|
||||||
|
image_target_dir = os.path.join(STATIC_DIR, IMAGE_DIR, filename)
|
||||||
|
print(f"Copying image resources from {image_source_dir} to {image_target_dir}")
|
||||||
|
if os.path.exists(image_source_dir):
|
||||||
|
# 确保 static/image/xxx 目录存在
|
||||||
|
if not os.path.exists(image_target_dir):
|
||||||
|
os.makedirs(image_target_dir)
|
||||||
|
|
||||||
|
# 拷贝图片资源
|
||||||
|
for image_file in os.listdir(image_source_dir):
|
||||||
|
full_image_file_path = os.path.join(image_source_dir, image_file)
|
||||||
|
if os.path.isfile(full_image_file_path):
|
||||||
|
shutil.copy(full_image_file_path, image_target_dir)
|
||||||
|
|
||||||
|
return jsonify({'html_url': f'/static/{filename}.html'})
|
||||||
|
|
||||||
|
# 静态文件(HTML 和图片资源)的访问
|
||||||
|
@app.route('/static/<path:filename>')
|
||||||
|
def serve_static(filename):
|
||||||
|
return send_from_directory(STATIC_DIR, filename)
|
||||||
|
|
||||||
|
|
||||||
|
'''
|
||||||
|
Vscode
|
||||||
|
'''
|
||||||
|
@app.route('/desktop/<user_id>/<course_id>/<chapter_id>')
|
||||||
|
def desktop(user_id, course_id, chapter_id):
|
||||||
|
if 'user_id' not in session:
|
||||||
|
# return redirect(url_for('login'))
|
||||||
|
session['user_id'] = 'user_' + str(uuid.uuid4())
|
||||||
|
print("user "+ user_id + "uuid is"+ session['user_id'])
|
||||||
|
username2uuid[user_id] = session['user_id']
|
||||||
|
uuid2username[session['user_id']] = user_id
|
||||||
|
userid_recorder[user_id+'&'+course_id] = session['user_id']
|
||||||
|
|
||||||
|
# 在学习目录下创建一个名为 user_id_path 的文件夹
|
||||||
|
path_dir = os.path.join(student_workspace_root_path, user_id, course_id,chapter_id)
|
||||||
|
os.makedirs(path_dir, exist_ok=True)
|
||||||
|
# 在此文件夹内部创建一个.config文件,并写入 user_id=user_id\n path=path
|
||||||
|
config_path = os.path.join(path_dir, '.config')
|
||||||
|
if GLOBAL_CONFIG['VSCODE_WEB_PATH']['is_wsl']: # 如果是在 WSL 中运行,则将路径转换为 WSL 路径
|
||||||
|
path_dir = path_dir.replace('\\', '/')
|
||||||
|
path_dir = path_dir.replace(GLOBAL_CONFIG['VSCODE_WEB_PATH']['windows_path'] , GLOBAL_CONFIG['VSCODE_WEB_PATH']['wsl_path'])
|
||||||
|
with open(config_path, 'w', encoding='utf-8') as f:
|
||||||
|
tmpd = {'user_id': user_id, 'course_id': course_id, 'chapter_id': chapter_id, 'path': path_dir}
|
||||||
|
json.dump(tmpd, f)
|
||||||
|
print('---------------------')
|
||||||
|
print(config_path)
|
||||||
|
return render_template('desktop.html', vscode_web_url=VSCODE_WEB_URL, user_id=user_id, course_id=course_id, chapter_id=chapter_id, workspace_path=path_dir)
|
||||||
|
|
||||||
|
@app.route('/desktop_nouser/<course_id>/<chapter_id>')
|
||||||
|
def desktop_nouser(course_id, chapter_id):
|
||||||
|
if 'user_id' not in session:
|
||||||
|
return redirect(url_for('login'))
|
||||||
|
user_id = session['user_id']
|
||||||
|
username = uuid2username[user_id]
|
||||||
|
return redirect(url_for('desktop', user_id=username, course_id=course_id, chapter_id=chapter_id))
|
||||||
|
|
||||||
|
@app.route('/vscode_data', methods=['POST'])
|
||||||
|
def vscode_data():
|
||||||
|
data = request.json
|
||||||
|
config = data['config']
|
||||||
|
realtime_response(config,data)
|
||||||
|
print(f"Received data from VSCode: {data}")
|
||||||
|
return jsonify({"status": "success", "received": data})
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
'''
|
||||||
|
Login
|
||||||
|
|
||||||
|
'''
|
||||||
|
users_list = UserList()
|
||||||
|
course_list = CourseList()
|
||||||
|
|
||||||
|
|
||||||
|
@app.route('/register')
|
||||||
|
def register():
|
||||||
|
return render_template('register.html')
|
||||||
|
|
||||||
|
@app.route('/register_teacher')
|
||||||
|
def register_teacher():
|
||||||
|
return render_template('register_teacher.html')
|
||||||
|
|
||||||
|
@app.route('/register_teacher_post', methods=['POST'])
|
||||||
|
def register_teacher_post():
|
||||||
|
data = request.get_json()
|
||||||
|
username = data.get('username')
|
||||||
|
password = data.get('password')
|
||||||
|
if users_list.has_user(username) is not None:
|
||||||
|
users_list.add_user(username, password, teacher=True)
|
||||||
|
create_user_json(username, USER_DATA_DIR)
|
||||||
|
return jsonify({'success': True, 'message': '注册成功'})
|
||||||
|
else: return jsonify({'success': False, 'message': '用户已存在,请更换用户名'})
|
||||||
|
|
||||||
|
@app.route('/register_post', methods=['POST'])
|
||||||
|
def register_post():
|
||||||
|
data = request.get_json()
|
||||||
|
username = data.get('username')
|
||||||
|
password = data.get('password')
|
||||||
|
if users_list.has_user(username) is not None:
|
||||||
|
users_list.add_user(username, password)
|
||||||
|
create_user_json(username, USER_DATA_DIR)
|
||||||
|
return jsonify({'success': True, 'message': '注册成功'})
|
||||||
|
else: return jsonify({'success': False, 'message': '用户已存在,请更换用户名'})
|
||||||
|
|
||||||
|
|
||||||
|
@app.route('/login')
|
||||||
|
def login():
|
||||||
|
return render_template('login.html')
|
||||||
|
if 'user_id' not in session:
|
||||||
|
return render_template('login.html')
|
||||||
|
else:
|
||||||
|
return redirect(url_for('/'))
|
||||||
|
|
||||||
|
@app.route('/login_post', methods=['POST'])
|
||||||
|
def login_post():
|
||||||
|
# 获取请求的 JSON 数据
|
||||||
|
data = request.get_json()
|
||||||
|
|
||||||
|
username = data.get('username')
|
||||||
|
password = data.get('password')
|
||||||
|
|
||||||
|
pswd = users_list.get_user_pswd(username)
|
||||||
|
|
||||||
|
if pswd is None:
|
||||||
|
return jsonify({'success': False, 'message': '用户名或密码错误'})
|
||||||
|
if pswd != password:
|
||||||
|
return jsonify({'success': False, 'message': '用户名或密码错误'})
|
||||||
|
|
||||||
|
session['user_id'] = 'user_' + str(uuid.uuid4())
|
||||||
|
print("user "+ username + "uuid is"+ session['user_id'])
|
||||||
|
username2uuid[username] = session['user_id']
|
||||||
|
uuid2username[session['user_id']] = username
|
||||||
|
return jsonify({'success': True, 'message': '登录成功'})
|
||||||
|
|
||||||
|
@app.route('/login_teacher_post', methods=['POST'])
|
||||||
|
def login_teacher_post():
|
||||||
|
data = request.get_json()
|
||||||
|
username = data.get('username')
|
||||||
|
password = data.get('password')
|
||||||
|
pswd = users_list.get_user_pswd(username)
|
||||||
|
is_teacher = users_list.get_user_is_teacher(username)
|
||||||
|
if is_teacher is None or is_teacher == False:
|
||||||
|
return jsonify({'success': False, 'message': '用户名不存在或非教师账号'})
|
||||||
|
if pswd is None:
|
||||||
|
return jsonify({'success': False, 'message': '用户名或密码错误'})
|
||||||
|
if pswd != password:
|
||||||
|
return jsonify({'success': False, 'message': '用户名或密码错误'})
|
||||||
|
session['user_id'] = 'user_' + str(uuid.uuid4())
|
||||||
|
print("user "+ username + "uuid is"+ session['user_id'])
|
||||||
|
username2uuid[username] = session['user_id']
|
||||||
|
uuid2username[session['user_id']] = username
|
||||||
|
return jsonify({'success': True, 'message': '登录成功'})
|
||||||
|
|
||||||
|
@app.route('/teacherboard')
|
||||||
|
@require_role(roles="teacher")
|
||||||
|
def teacherboard():
|
||||||
|
return render_template('teacherboard.html')
|
||||||
|
|
||||||
|
|
||||||
|
@app.route('/logout')
|
||||||
|
def logout():
|
||||||
|
session.pop('user_id', None)
|
||||||
|
return redirect(url_for('login'))
|
||||||
|
|
||||||
|
@app.route('/get_session')
|
||||||
|
def get_session():
|
||||||
|
user_session = session.get('user_id', 'default_session')
|
||||||
|
return jsonify({"session": user_session})
|
||||||
|
|
||||||
|
|
||||||
|
'''
|
||||||
|
DashBoard
|
||||||
|
'''
|
||||||
|
user_id2UserClass = {}
|
||||||
|
@app.route('/dashboard')
|
||||||
|
@require_role
|
||||||
|
def dashboard():
|
||||||
|
user_id = session['user_id']
|
||||||
|
username = uuid2username[user_id]
|
||||||
|
if (user_id not in user_id2UserClass):
|
||||||
|
user_id2UserClass[user_id] = load_user_from_json(username, user_data_dir = USER_DATA_DIR)
|
||||||
|
user_course_data = []
|
||||||
|
for course_id in user_id2UserClass[user_id].select_course:
|
||||||
|
course_brief_info = course_list.get_course_brief_info(course_id, load_course_from_json(course_id, course_data_dir = COURSE_DATA_DIR))
|
||||||
|
user_course_data.append(course_brief_info)
|
||||||
|
return render_template('dashboard.html',user_data = user_id2UserClass[user_id].to_json_without_dialog(), user_course_data = json.dumps(user_course_data))
|
||||||
|
|
||||||
|
'''
|
||||||
|
Course
|
||||||
|
'''
|
||||||
|
|
||||||
|
@app.route('/course/<course_id>')
|
||||||
|
@require_role
|
||||||
|
def course(course_id):
|
||||||
|
c = load_course_from_json(course_id, course_data_dir = COURSE_DATA_DIR)
|
||||||
|
return render_template('course.html', course_id=course_id, course_data=c)
|
||||||
|
|
||||||
|
@app.route('/select_course', methods=['POST'])
|
||||||
|
@require_role
|
||||||
|
def select_course():
|
||||||
|
user = user_id2UserClass[session['user_id']]
|
||||||
|
data = request.get_json()
|
||||||
|
course_id = data.get('course_id')
|
||||||
|
if course_id:
|
||||||
|
user.select_new_course(course_id, load_course_from_json(course_id, COURSE_DATA_DIR))
|
||||||
|
return jsonify({'success': True, 'message': '课程选择成功'})
|
||||||
|
|
||||||
|
|
||||||
|
@app.route('/')
|
||||||
|
@require_role
|
||||||
|
def home_index():
|
||||||
|
selected_courses=[]
|
||||||
|
if ('user_id' in session):
|
||||||
|
if (session['user_id'] not in user_id2UserClass): return redirect(url_for('login'))
|
||||||
|
user = user_id2UserClass[session['user_id']]
|
||||||
|
for course_id in user.select_course:
|
||||||
|
selected_courses.append(course_id)
|
||||||
|
|
||||||
|
return render_template('index.html', courses_data = course_list, selected_courses =selected_courses)
|
||||||
|
|
||||||
|
# 一些app辅助函数,主要提供给Agent与数据库的交互能力
|
||||||
|
|
||||||
|
class MyFunction:
|
||||||
|
def save_chapter_memory(self, id, course_id, lesson_id, subchapter_title, mem_list, score, is_rebuttal):
|
||||||
|
print("-=-=-=-=-=-=-=-")
|
||||||
|
print(id, course_id, lesson_id, subchapter_title, mem_list, score, is_rebuttal)
|
||||||
|
username = uuid2username[id]
|
||||||
|
u = load_user_from_json(username, user_data_dir = USER_DATA_DIR)
|
||||||
|
u.save_chapter_memory(course_id, lesson_id, subchapter_title, mem_list, score, is_rebuttal)
|
||||||
|
|
||||||
|
app.my_function = MyFunction()
|
||||||
|
if __name__ == '__main__':
|
||||||
|
socketio.on_namespace(VSCodeNamespace('/vscode'))
|
||||||
|
socketio.on_namespace(AgentNamespace('/agent'))
|
||||||
|
socketio.run(app, host='0.0.0.0', port=5551, debug=False,allow_unsafe_werkzeug=True)
|
||||||
|
|
||||||
@@ -120,6 +120,7 @@ class ChatManager:
|
|||||||
|
|
||||||
|
|
||||||
def next_chapter(self):
|
def next_chapter(self):
|
||||||
|
print(f"now next chapter {self.chapter_chain_now} and len {len(self.chapter_chain)}")
|
||||||
assert self.chapter_chain_now + 1 < len(self.chapter_chain), "chapter_chain_now out of range"
|
assert self.chapter_chain_now + 1 < len(self.chapter_chain), "chapter_chain_now out of range"
|
||||||
self.chapter_chain_now += 1
|
self.chapter_chain_now += 1
|
||||||
self.bb.next_chapter()
|
self.bb.next_chapter()
|
||||||
|
|||||||
@@ -36,6 +36,7 @@ def save_learning_progress(chatmanager, user_id: str) -> bool:
|
|||||||
"chat_historys": chatmanager.chat_historys,
|
"chat_historys": chatmanager.chat_historys,
|
||||||
"scores": chatmanager.scores,
|
"scores": chatmanager.scores,
|
||||||
"multiagents_dialogs": his_data,
|
"multiagents_dialogs": his_data,
|
||||||
|
"chapter_chain_length": len(chatmanager.chapter_chain),
|
||||||
"updated_at": datetime.now()
|
"updated_at": datetime.now()
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -146,6 +147,7 @@ def load_learning_progress(user_id: str, material_id: str, chapter_name: str, le
|
|||||||
"chat_historys": [],
|
"chat_historys": [],
|
||||||
"scores": [],
|
"scores": [],
|
||||||
"chapter_chain_now": None,
|
"chapter_chain_now": None,
|
||||||
|
"chapter_chain_length": 0,
|
||||||
"updated_at": datetime.now().isoformat()
|
"updated_at": datetime.now().isoformat()
|
||||||
},
|
},
|
||||||
"message": "未找到学习记录,返回默认值"
|
"message": "未找到学习记录,返回默认值"
|
||||||
@@ -164,6 +166,7 @@ def load_learning_progress(user_id: str, material_id: str, chapter_name: str, le
|
|||||||
"chat_historys": [],
|
"chat_historys": [],
|
||||||
"scores": [],
|
"scores": [],
|
||||||
"chapter_chain_now": None,
|
"chapter_chain_now": None,
|
||||||
|
"chapter_chain_length": 0,
|
||||||
"updated_at": datetime.now().isoformat()
|
"updated_at": datetime.now().isoformat()
|
||||||
},
|
},
|
||||||
"message": error_msg
|
"message": error_msg
|
||||||
|
|||||||
6
Html/apps/static/cdnback/css/editor/editor.main.css
Normal file
6
Html/apps/static/cdnback/css/editor/editor.main.css
Normal file
File diff suppressed because one or more lines are too long
218
Html/apps/static/cdnback/css/xterm.css
Normal file
218
Html/apps/static/cdnback/css/xterm.css
Normal file
@@ -0,0 +1,218 @@
|
|||||||
|
/**
|
||||||
|
* Copyright (c) 2014 The xterm.js authors. All rights reserved.
|
||||||
|
* Copyright (c) 2012-2013, Christopher Jeffrey (MIT License)
|
||||||
|
* https://github.com/chjj/term.js
|
||||||
|
* @license MIT
|
||||||
|
*
|
||||||
|
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
|
* of this software and associated documentation files (the "Software"), to deal
|
||||||
|
* in the Software without restriction, including without limitation the rights
|
||||||
|
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||||
|
* copies of the Software, and to permit persons to whom the Software is
|
||||||
|
* furnished to do so, subject to the following conditions:
|
||||||
|
*
|
||||||
|
* The above copyright notice and this permission notice shall be included in
|
||||||
|
* all copies or substantial portions of the Software.
|
||||||
|
*
|
||||||
|
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||||
|
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||||
|
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||||
|
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||||
|
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||||
|
* THE SOFTWARE.
|
||||||
|
*
|
||||||
|
* Originally forked from (with the author's permission):
|
||||||
|
* Fabrice Bellard's javascript vt100 for jslinux:
|
||||||
|
* http://bellard.org/jslinux/
|
||||||
|
* Copyright (c) 2011 Fabrice Bellard
|
||||||
|
* The original design remains. The terminal itself
|
||||||
|
* has been extended to include xterm CSI codes, among
|
||||||
|
* other features.
|
||||||
|
*/
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Default styles for xterm.js
|
||||||
|
*/
|
||||||
|
|
||||||
|
.xterm {
|
||||||
|
cursor: text;
|
||||||
|
position: relative;
|
||||||
|
user-select: none;
|
||||||
|
-ms-user-select: none;
|
||||||
|
-webkit-user-select: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.xterm.focus,
|
||||||
|
.xterm:focus {
|
||||||
|
outline: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.xterm .xterm-helpers {
|
||||||
|
position: absolute;
|
||||||
|
top: 0;
|
||||||
|
/**
|
||||||
|
* The z-index of the helpers must be higher than the canvases in order for
|
||||||
|
* IMEs to appear on top.
|
||||||
|
*/
|
||||||
|
z-index: 5;
|
||||||
|
}
|
||||||
|
|
||||||
|
.xterm .xterm-helper-textarea {
|
||||||
|
padding: 0;
|
||||||
|
border: 0;
|
||||||
|
margin: 0;
|
||||||
|
/* Move textarea out of the screen to the far left, so that the cursor is not visible */
|
||||||
|
position: absolute;
|
||||||
|
opacity: 0;
|
||||||
|
left: -9999em;
|
||||||
|
top: 0;
|
||||||
|
width: 0;
|
||||||
|
height: 0;
|
||||||
|
z-index: -5;
|
||||||
|
/** Prevent wrapping so the IME appears against the textarea at the correct position */
|
||||||
|
white-space: nowrap;
|
||||||
|
overflow: hidden;
|
||||||
|
resize: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.xterm .composition-view {
|
||||||
|
/* TODO: Composition position got messed up somewhere */
|
||||||
|
background: #000;
|
||||||
|
color: #FFF;
|
||||||
|
display: none;
|
||||||
|
position: absolute;
|
||||||
|
white-space: nowrap;
|
||||||
|
z-index: 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
.xterm .composition-view.active {
|
||||||
|
display: block;
|
||||||
|
}
|
||||||
|
|
||||||
|
.xterm .xterm-viewport {
|
||||||
|
/* On OS X this is required in order for the scroll bar to appear fully opaque */
|
||||||
|
background-color: #000;
|
||||||
|
overflow-y: scroll;
|
||||||
|
cursor: default;
|
||||||
|
position: absolute;
|
||||||
|
right: 0;
|
||||||
|
left: 0;
|
||||||
|
top: 0;
|
||||||
|
bottom: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.xterm .xterm-screen {
|
||||||
|
position: relative;
|
||||||
|
}
|
||||||
|
|
||||||
|
.xterm .xterm-screen canvas {
|
||||||
|
position: absolute;
|
||||||
|
left: 0;
|
||||||
|
top: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.xterm .xterm-scroll-area {
|
||||||
|
visibility: hidden;
|
||||||
|
}
|
||||||
|
|
||||||
|
.xterm-char-measure-element {
|
||||||
|
display: inline-block;
|
||||||
|
visibility: hidden;
|
||||||
|
position: absolute;
|
||||||
|
top: 0;
|
||||||
|
left: -9999em;
|
||||||
|
line-height: normal;
|
||||||
|
}
|
||||||
|
|
||||||
|
.xterm.enable-mouse-events {
|
||||||
|
/* When mouse events are enabled (eg. tmux), revert to the standard pointer cursor */
|
||||||
|
cursor: default;
|
||||||
|
}
|
||||||
|
|
||||||
|
.xterm.xterm-cursor-pointer,
|
||||||
|
.xterm .xterm-cursor-pointer {
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
|
||||||
|
.xterm.column-select.focus {
|
||||||
|
/* Column selection mode */
|
||||||
|
cursor: crosshair;
|
||||||
|
}
|
||||||
|
|
||||||
|
.xterm .xterm-accessibility:not(.debug),
|
||||||
|
.xterm .xterm-message {
|
||||||
|
position: absolute;
|
||||||
|
left: 0;
|
||||||
|
top: 0;
|
||||||
|
bottom: 0;
|
||||||
|
right: 0;
|
||||||
|
z-index: 10;
|
||||||
|
color: transparent;
|
||||||
|
pointer-events: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.xterm .xterm-accessibility-tree:not(.debug) *::selection {
|
||||||
|
color: transparent;
|
||||||
|
}
|
||||||
|
|
||||||
|
.xterm .xterm-accessibility-tree {
|
||||||
|
user-select: text;
|
||||||
|
white-space: pre;
|
||||||
|
}
|
||||||
|
|
||||||
|
.xterm .live-region {
|
||||||
|
position: absolute;
|
||||||
|
left: -9999px;
|
||||||
|
width: 1px;
|
||||||
|
height: 1px;
|
||||||
|
overflow: hidden;
|
||||||
|
}
|
||||||
|
|
||||||
|
.xterm-dim {
|
||||||
|
/* Dim should not apply to background, so the opacity of the foreground color is applied
|
||||||
|
* explicitly in the generated class and reset to 1 here */
|
||||||
|
opacity: 1 !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.xterm-underline-1 { text-decoration: underline; }
|
||||||
|
.xterm-underline-2 { text-decoration: double underline; }
|
||||||
|
.xterm-underline-3 { text-decoration: wavy underline; }
|
||||||
|
.xterm-underline-4 { text-decoration: dotted underline; }
|
||||||
|
.xterm-underline-5 { text-decoration: dashed underline; }
|
||||||
|
|
||||||
|
.xterm-overline {
|
||||||
|
text-decoration: overline;
|
||||||
|
}
|
||||||
|
|
||||||
|
.xterm-overline.xterm-underline-1 { text-decoration: overline underline; }
|
||||||
|
.xterm-overline.xterm-underline-2 { text-decoration: overline double underline; }
|
||||||
|
.xterm-overline.xterm-underline-3 { text-decoration: overline wavy underline; }
|
||||||
|
.xterm-overline.xterm-underline-4 { text-decoration: overline dotted underline; }
|
||||||
|
.xterm-overline.xterm-underline-5 { text-decoration: overline dashed underline; }
|
||||||
|
|
||||||
|
.xterm-strikethrough {
|
||||||
|
text-decoration: line-through;
|
||||||
|
}
|
||||||
|
|
||||||
|
.xterm-screen .xterm-decoration-container .xterm-decoration {
|
||||||
|
z-index: 6;
|
||||||
|
position: absolute;
|
||||||
|
}
|
||||||
|
|
||||||
|
.xterm-screen .xterm-decoration-container .xterm-decoration.xterm-decoration-top-layer {
|
||||||
|
z-index: 7;
|
||||||
|
}
|
||||||
|
|
||||||
|
.xterm-decoration-overview-ruler {
|
||||||
|
z-index: 8;
|
||||||
|
position: absolute;
|
||||||
|
top: 0;
|
||||||
|
right: 0;
|
||||||
|
pointer-events: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.xterm-decoration-top {
|
||||||
|
z-index: 2;
|
||||||
|
position: relative;
|
||||||
|
}
|
||||||
51
Html/apps/static/cdnback/js/addons/fit/fit.js
Normal file
51
Html/apps/static/cdnback/js/addons/fit/fit.js
Normal file
@@ -0,0 +1,51 @@
|
|||||||
|
(function(f){if(typeof exports==="object"&&typeof module!=="undefined"){module.exports=f()}else if(typeof define==="function"&&define.amd){define([],f)}else{var g;if(typeof window!=="undefined"){g=window}else if(typeof global!=="undefined"){g=global}else if(typeof self!=="undefined"){g=self}else{g=this}g.fit = f()}})(function(){var define,module,exports;return (function(){function r(e,n,t){function o(i,f){if(!n[i]){if(!e[i]){var c="function"==typeof require&&require;if(!f&&c)return c(i,!0);if(u)return u(i,!0);var a=new Error("Cannot find module '"+i+"'");throw a.code="MODULE_NOT_FOUND",a}var p=n[i]={exports:{}};e[i][0].call(p.exports,function(r){var n=e[i][1][r];return o(n||r)},p,p.exports,r,e,n,t)}return n[i].exports}for(var u="function"==typeof require&&require,i=0;i<t.length;i++)o(t[i]);return o}return r})()({1:[function(require,module,exports){
|
||||||
|
"use strict";
|
||||||
|
Object.defineProperty(exports, "__esModule", { value: true });
|
||||||
|
function proposeGeometry(term) {
|
||||||
|
if (!term.element.parentElement) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
var parentElementStyle = window.getComputedStyle(term.element.parentElement);
|
||||||
|
var parentElementHeight = parseInt(parentElementStyle.getPropertyValue('height'));
|
||||||
|
var parentElementWidth = Math.max(0, parseInt(parentElementStyle.getPropertyValue('width')));
|
||||||
|
var elementStyle = window.getComputedStyle(term.element);
|
||||||
|
var elementPadding = {
|
||||||
|
top: parseInt(elementStyle.getPropertyValue('padding-top')),
|
||||||
|
bottom: parseInt(elementStyle.getPropertyValue('padding-bottom')),
|
||||||
|
right: parseInt(elementStyle.getPropertyValue('padding-right')),
|
||||||
|
left: parseInt(elementStyle.getPropertyValue('padding-left'))
|
||||||
|
};
|
||||||
|
var elementPaddingVer = elementPadding.top + elementPadding.bottom;
|
||||||
|
var elementPaddingHor = elementPadding.right + elementPadding.left;
|
||||||
|
var availableHeight = parentElementHeight - elementPaddingVer;
|
||||||
|
var availableWidth = parentElementWidth - elementPaddingHor - term._core.viewport.scrollBarWidth;
|
||||||
|
var geometry = {
|
||||||
|
cols: Math.floor(availableWidth / term._core.renderer.dimensions.actualCellWidth),
|
||||||
|
rows: Math.floor(availableHeight / term._core.renderer.dimensions.actualCellHeight)
|
||||||
|
};
|
||||||
|
return geometry;
|
||||||
|
}
|
||||||
|
exports.proposeGeometry = proposeGeometry;
|
||||||
|
function fit(term) {
|
||||||
|
var geometry = proposeGeometry(term);
|
||||||
|
if (geometry) {
|
||||||
|
if (term.rows !== geometry.rows || term.cols !== geometry.cols) {
|
||||||
|
term._core.renderer.clear();
|
||||||
|
term.resize(geometry.cols, geometry.rows);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
exports.fit = fit;
|
||||||
|
function apply(terminalConstructor) {
|
||||||
|
terminalConstructor.prototype.proposeGeometry = function () {
|
||||||
|
return proposeGeometry(this);
|
||||||
|
};
|
||||||
|
terminalConstructor.prototype.fit = function () {
|
||||||
|
fit(this);
|
||||||
|
};
|
||||||
|
}
|
||||||
|
exports.apply = apply;
|
||||||
|
|
||||||
|
},{}]},{},[1])(1)
|
||||||
|
});
|
||||||
|
//# sourceMappingURL=fit.js.map
|
||||||
27
Html/apps/static/cdnback/js/addons/fullscreen/fullscreen.js
Normal file
27
Html/apps/static/cdnback/js/addons/fullscreen/fullscreen.js
Normal file
@@ -0,0 +1,27 @@
|
|||||||
|
(function(f){if(typeof exports==="object"&&typeof module!=="undefined"){module.exports=f()}else if(typeof define==="function"&&define.amd){define([],f)}else{var g;if(typeof window!=="undefined"){g=window}else if(typeof global!=="undefined"){g=global}else if(typeof self!=="undefined"){g=self}else{g=this}g.fullscreen = f()}})(function(){var define,module,exports;return (function(){function r(e,n,t){function o(i,f){if(!n[i]){if(!e[i]){var c="function"==typeof require&&require;if(!f&&c)return c(i,!0);if(u)return u(i,!0);var a=new Error("Cannot find module '"+i+"'");throw a.code="MODULE_NOT_FOUND",a}var p=n[i]={exports:{}};e[i][0].call(p.exports,function(r){var n=e[i][1][r];return o(n||r)},p,p.exports,r,e,n,t)}return n[i].exports}for(var u="function"==typeof require&&require,i=0;i<t.length;i++)o(t[i]);return o}return r})()({1:[function(require,module,exports){
|
||||||
|
"use strict";
|
||||||
|
Object.defineProperty(exports, "__esModule", { value: true });
|
||||||
|
function toggleFullScreen(term, fullscreen) {
|
||||||
|
var fn;
|
||||||
|
if (typeof fullscreen === 'undefined') {
|
||||||
|
fn = (term.element.classList.contains('fullscreen')) ? 'remove' : 'add';
|
||||||
|
}
|
||||||
|
else if (!fullscreen) {
|
||||||
|
fn = 'remove';
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
fn = 'add';
|
||||||
|
}
|
||||||
|
term.element.classList[fn]('fullscreen');
|
||||||
|
}
|
||||||
|
exports.toggleFullScreen = toggleFullScreen;
|
||||||
|
function apply(terminalConstructor) {
|
||||||
|
terminalConstructor.prototype.toggleFullScreen = function (fullscreen) {
|
||||||
|
toggleFullScreen(this, fullscreen);
|
||||||
|
};
|
||||||
|
}
|
||||||
|
exports.apply = apply;
|
||||||
|
|
||||||
|
},{}]},{},[1])(1)
|
||||||
|
});
|
||||||
|
//# sourceMappingURL=fullscreen.js.map
|
||||||
126
Html/apps/static/cdnback/js/addons/search/search.js
Normal file
126
Html/apps/static/cdnback/js/addons/search/search.js
Normal file
@@ -0,0 +1,126 @@
|
|||||||
|
(function(f){if(typeof exports==="object"&&typeof module!=="undefined"){module.exports=f()}else if(typeof define==="function"&&define.amd){define([],f)}else{var g;if(typeof window!=="undefined"){g=window}else if(typeof global!=="undefined"){g=global}else if(typeof self!=="undefined"){g=self}else{g=this}g.search = f()}})(function(){var define,module,exports;return (function(){function r(e,n,t){function o(i,f){if(!n[i]){if(!e[i]){var c="function"==typeof require&&require;if(!f&&c)return c(i,!0);if(u)return u(i,!0);var a=new Error("Cannot find module '"+i+"'");throw a.code="MODULE_NOT_FOUND",a}var p=n[i]={exports:{}};e[i][0].call(p.exports,function(r){var n=e[i][1][r];return o(n||r)},p,p.exports,r,e,n,t)}return n[i].exports}for(var u="function"==typeof require&&require,i=0;i<t.length;i++)o(t[i]);return o}return r})()({1:[function(require,module,exports){
|
||||||
|
"use strict";
|
||||||
|
Object.defineProperty(exports, "__esModule", { value: true });
|
||||||
|
var SearchHelper = (function () {
|
||||||
|
function SearchHelper(_terminal) {
|
||||||
|
this._terminal = _terminal;
|
||||||
|
}
|
||||||
|
SearchHelper.prototype.findNext = function (term) {
|
||||||
|
if (!term || term.length === 0) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
var result;
|
||||||
|
var startRow = this._terminal._core.buffer.ydisp;
|
||||||
|
if (this._terminal._core.selectionManager.selectionEnd) {
|
||||||
|
startRow = this._terminal._core.selectionManager.selectionEnd[1];
|
||||||
|
}
|
||||||
|
for (var y = startRow + 1; y < this._terminal._core.buffer.ybase + this._terminal.rows; y++) {
|
||||||
|
result = this._findInLine(term, y);
|
||||||
|
if (result) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (!result) {
|
||||||
|
for (var y = 0; y < startRow; y++) {
|
||||||
|
result = this._findInLine(term, y);
|
||||||
|
if (result) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return this._selectResult(result);
|
||||||
|
};
|
||||||
|
SearchHelper.prototype.findPrevious = function (term) {
|
||||||
|
if (!term || term.length === 0) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
var result;
|
||||||
|
var startRow = this._terminal._core.buffer.ydisp;
|
||||||
|
if (this._terminal._core.selectionManager.selectionStart) {
|
||||||
|
startRow = this._terminal._core.selectionManager.selectionStart[1];
|
||||||
|
}
|
||||||
|
for (var y = startRow - 1; y >= 0; y--) {
|
||||||
|
result = this._findInLine(term, y);
|
||||||
|
if (result) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (!result) {
|
||||||
|
for (var y = this._terminal._core.buffer.ybase + this._terminal.rows - 1; y > startRow; y--) {
|
||||||
|
result = this._findInLine(term, y);
|
||||||
|
if (result) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return this._selectResult(result);
|
||||||
|
};
|
||||||
|
SearchHelper.prototype._findInLine = function (term, y) {
|
||||||
|
var lowerStringLine = this._terminal._core.buffer.translateBufferLineToString(y, true).toLowerCase();
|
||||||
|
var lowerTerm = term.toLowerCase();
|
||||||
|
var searchIndex = lowerStringLine.indexOf(lowerTerm);
|
||||||
|
if (searchIndex >= 0) {
|
||||||
|
var line = this._terminal._core.buffer.lines.get(y);
|
||||||
|
for (var i = 0; i < searchIndex; i++) {
|
||||||
|
var charData = line[i];
|
||||||
|
var char = charData[1];
|
||||||
|
if (char.length > 1) {
|
||||||
|
searchIndex -= char.length - 1;
|
||||||
|
}
|
||||||
|
var charWidth = charData[2];
|
||||||
|
if (charWidth === 0) {
|
||||||
|
searchIndex++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return {
|
||||||
|
term: term,
|
||||||
|
col: searchIndex,
|
||||||
|
row: y
|
||||||
|
};
|
||||||
|
}
|
||||||
|
};
|
||||||
|
SearchHelper.prototype._selectResult = function (result) {
|
||||||
|
if (!result) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
this._terminal._core.selectionManager.setSelection(result.col, result.row, result.term.length);
|
||||||
|
this._terminal.scrollLines(result.row - this._terminal._core.buffer.ydisp);
|
||||||
|
return true;
|
||||||
|
};
|
||||||
|
return SearchHelper;
|
||||||
|
}());
|
||||||
|
exports.SearchHelper = SearchHelper;
|
||||||
|
|
||||||
|
},{}],2:[function(require,module,exports){
|
||||||
|
"use strict";
|
||||||
|
Object.defineProperty(exports, "__esModule", { value: true });
|
||||||
|
var SearchHelper_1 = require("./SearchHelper");
|
||||||
|
function findNext(terminal, term) {
|
||||||
|
var addonTerminal = terminal;
|
||||||
|
if (!addonTerminal.__searchHelper) {
|
||||||
|
addonTerminal.__searchHelper = new SearchHelper_1.SearchHelper(addonTerminal);
|
||||||
|
}
|
||||||
|
return addonTerminal.__searchHelper.findNext(term);
|
||||||
|
}
|
||||||
|
exports.findNext = findNext;
|
||||||
|
function findPrevious(terminal, term) {
|
||||||
|
var addonTerminal = terminal;
|
||||||
|
if (!addonTerminal.__searchHelper) {
|
||||||
|
addonTerminal.__searchHelper = new SearchHelper_1.SearchHelper(addonTerminal);
|
||||||
|
}
|
||||||
|
return addonTerminal.__searchHelper.findPrevious(term);
|
||||||
|
}
|
||||||
|
exports.findPrevious = findPrevious;
|
||||||
|
function apply(terminalConstructor) {
|
||||||
|
terminalConstructor.prototype.findNext = function (term) {
|
||||||
|
return findNext(this, term);
|
||||||
|
};
|
||||||
|
terminalConstructor.prototype.findPrevious = function (term) {
|
||||||
|
return findPrevious(this, term);
|
||||||
|
};
|
||||||
|
}
|
||||||
|
exports.apply = apply;
|
||||||
|
|
||||||
|
},{"./SearchHelper":1}]},{},[2])(2)
|
||||||
|
});
|
||||||
|
//# sourceMappingURL=search.js.map
|
||||||
41
Html/apps/static/cdnback/js/addons/webLinks/webLinks.js
Normal file
41
Html/apps/static/cdnback/js/addons/webLinks/webLinks.js
Normal file
@@ -0,0 +1,41 @@
|
|||||||
|
(function(f){if(typeof exports==="object"&&typeof module!=="undefined"){module.exports=f()}else if(typeof define==="function"&&define.amd){define([],f)}else{var g;if(typeof window!=="undefined"){g=window}else if(typeof global!=="undefined"){g=global}else if(typeof self!=="undefined"){g=self}else{g=this}g.webLinks = f()}})(function(){var define,module,exports;return (function(){function r(e,n,t){function o(i,f){if(!n[i]){if(!e[i]){var c="function"==typeof require&&require;if(!f&&c)return c(i,!0);if(u)return u(i,!0);var a=new Error("Cannot find module '"+i+"'");throw a.code="MODULE_NOT_FOUND",a}var p=n[i]={exports:{}};e[i][0].call(p.exports,function(r){var n=e[i][1][r];return o(n||r)},p,p.exports,r,e,n,t)}return n[i].exports}for(var u="function"==typeof require&&require,i=0;i<t.length;i++)o(t[i]);return o}return r})()({1:[function(require,module,exports){
|
||||||
|
"use strict";
|
||||||
|
Object.defineProperty(exports, "__esModule", { value: true });
|
||||||
|
var protocolClause = '(https?:\\/\\/)';
|
||||||
|
var domainCharacterSet = '[\\da-z\\.-]+';
|
||||||
|
var negatedDomainCharacterSet = '[^\\da-z\\.-]+';
|
||||||
|
var domainBodyClause = '(' + domainCharacterSet + ')';
|
||||||
|
var tldClause = '([a-z\\.]{2,6})';
|
||||||
|
var ipClause = '((\\d{1,3}\\.){3}\\d{1,3})';
|
||||||
|
var localHostClause = '(localhost)';
|
||||||
|
var portClause = '(:\\d{1,5})';
|
||||||
|
var hostClause = '((' + domainBodyClause + '\\.' + tldClause + ')|' + ipClause + '|' + localHostClause + ')' + portClause + '?';
|
||||||
|
var pathClause = '(\\/[\\/\\w\\.\\-%~]*)*';
|
||||||
|
var queryStringHashFragmentCharacterSet = '[0-9\\w\\[\\]\\(\\)\\/\\?\\!#@$%&\'*+,:;~\\=\\.\\-]*';
|
||||||
|
var queryStringClause = '(\\?' + queryStringHashFragmentCharacterSet + ')?';
|
||||||
|
var hashFragmentClause = '(#' + queryStringHashFragmentCharacterSet + ')?';
|
||||||
|
var negatedPathCharacterSet = '[^\\/\\w\\.\\-%]+';
|
||||||
|
var bodyClause = hostClause + pathClause + queryStringClause + hashFragmentClause;
|
||||||
|
var start = '(?:^|' + negatedDomainCharacterSet + ')(';
|
||||||
|
var end = ')($|' + negatedPathCharacterSet + ')';
|
||||||
|
var strictUrlRegex = new RegExp(start + protocolClause + bodyClause + end);
|
||||||
|
function handleLink(event, uri) {
|
||||||
|
window.open(uri, '_blank');
|
||||||
|
}
|
||||||
|
function webLinksInit(term, handler, options) {
|
||||||
|
if (handler === void 0) { handler = handleLink; }
|
||||||
|
if (options === void 0) { options = {}; }
|
||||||
|
options.matchIndex = 1;
|
||||||
|
term.registerLinkMatcher(strictUrlRegex, handler, options);
|
||||||
|
}
|
||||||
|
exports.webLinksInit = webLinksInit;
|
||||||
|
function apply(terminalConstructor) {
|
||||||
|
terminalConstructor.prototype.webLinksInit = function (handler, options) {
|
||||||
|
webLinksInit(this, handler, options);
|
||||||
|
};
|
||||||
|
}
|
||||||
|
exports.apply = apply;
|
||||||
|
|
||||||
|
},{}]},{},[1])(1)
|
||||||
|
});
|
||||||
|
//# sourceMappingURL=webLinks.js.map
|
||||||
File diff suppressed because one or more lines are too long
8588
Html/apps/static/cdnback/js/xterm.js
Normal file
8588
Html/apps/static/cdnback/js/xterm.js
Normal file
File diff suppressed because it is too large
Load Diff
@@ -1,4 +1,3 @@
|
|||||||
|
|
||||||
.add-step-btn, .add-phase-btn {
|
.add-step-btn, .add-phase-btn {
|
||||||
background-color: #4CAF50;
|
background-color: #4CAF50;
|
||||||
color: white;
|
color: white;
|
||||||
|
|||||||
@@ -3,13 +3,28 @@
|
|||||||
background: linear-gradient(135deg, var(--primary-blue), var(--accent-blue));
|
background: linear-gradient(135deg, var(--primary-blue), var(--accent-blue));
|
||||||
box-shadow: 0 2px 10px rgba(0, 0, 0, 0.1);
|
box-shadow: 0 2px 10px rgba(0, 0, 0, 0.1);
|
||||||
padding: 0.8rem 1rem;
|
padding: 0.8rem 1rem;
|
||||||
|
display: flex;
|
||||||
|
justify-content: space-between;
|
||||||
|
align-items: center;
|
||||||
|
flex-wrap: wrap;
|
||||||
}
|
}
|
||||||
|
|
||||||
.navbar .logo h1 {
|
.navbar .logo h1 {
|
||||||
color: rgb(245, 245, 245);
|
color: rgb(245, 245, 245);
|
||||||
|
font-size: 18px;
|
||||||
font-size: 24px;
|
|
||||||
font-weight: bold;
|
font-weight: bold;
|
||||||
|
margin: 0;
|
||||||
|
white-space: nowrap;
|
||||||
|
overflow: hidden;
|
||||||
|
text-overflow: ellipsis;
|
||||||
|
max-width: 300px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.navbar-links {
|
||||||
|
display: flex;
|
||||||
|
justify-content: center;
|
||||||
|
flex: 1;
|
||||||
|
margin: 0 20px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.navbar-links a {
|
.navbar-links a {
|
||||||
@@ -70,3 +85,40 @@
|
|||||||
.dropdown:hover .dropdown-content {
|
.dropdown:hover .dropdown-content {
|
||||||
display: block;
|
display: block;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* 响应式设计 */
|
||||||
|
@media (max-width: 768px) {
|
||||||
|
.navbar {
|
||||||
|
flex-direction: column;
|
||||||
|
gap: 1rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.navbar-links {
|
||||||
|
margin: 0;
|
||||||
|
flex-wrap: wrap;
|
||||||
|
justify-content: center;
|
||||||
|
gap: 0.5rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.navbar-links a {
|
||||||
|
margin: 0 0.75rem;
|
||||||
|
font-size: 16px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.navbar .logo h1 {
|
||||||
|
font-size: 16px;
|
||||||
|
max-width: 250px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@media (max-width: 480px) {
|
||||||
|
.navbar .logo h1 {
|
||||||
|
font-size: 14px;
|
||||||
|
max-width: 200px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.navbar-links a {
|
||||||
|
margin: 0 0.5rem;
|
||||||
|
font-size: 14px;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -61,16 +61,26 @@
|
|||||||
</head>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
<div class="confirm-container">
|
<div class="confirm-container">
|
||||||
<h1>加载历史数据确认</h1>
|
{% if is_course_finished %}
|
||||||
<p>您即将进入学习环境,是否需要加载您之前的对话历史记录和学习进度数据?</p>
|
<h1>重新学习确认</h1>
|
||||||
<div class="btn-group">
|
<p>您已经完成了该课程的学习,是否要重新开始学习?</p>
|
||||||
<a href="{{ url_for('vscode.desktop', user_uuid=user_uuid, course_id=course_id, chapter_name=chapter_name, lesson_name=lesson_name, load_history='true') }}" class="btn btn-primary">
|
<div class="btn-group">
|
||||||
是,加载历史数据
|
<a href="{{ url_for('vscode.desktop', user_uuid=user_uuid, course_id=course_id, chapter_name=chapter_name, lesson_name=lesson_name, load_history='false') }}" class="btn btn-primary">
|
||||||
</a>
|
重新开始学习
|
||||||
<a href="{{ url_for('vscode.desktop', user_uuid=user_uuid, course_id=course_id, chapter_name=chapter_name, lesson_name=lesson_name, load_history='false') }}" class="btn btn-secondary">
|
</a>
|
||||||
否,重新开始
|
</div>
|
||||||
</a>
|
{% else %}
|
||||||
</div>
|
<h1>加载历史数据确认</h1>
|
||||||
|
<p>您即将进入学习环境,是否需要加载您之前的对话历史记录和学习进度数据?</p>
|
||||||
|
<div class="btn-group">
|
||||||
|
<a href="{{ url_for('vscode.desktop', user_uuid=user_uuid, course_id=course_id, chapter_name=chapter_name, lesson_name=lesson_name, load_history='true') }}" class="btn btn-primary">
|
||||||
|
是,加载历史数据
|
||||||
|
</a>
|
||||||
|
<a href="{{ url_for('vscode.desktop', user_uuid=user_uuid, course_id=course_id, chapter_name=chapter_name, lesson_name=lesson_name, load_history='false') }}" class="btn btn-secondary">
|
||||||
|
否,重新开始
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
{% endif %}
|
||||||
</div>
|
</div>
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
||||||
@@ -1,7 +1,6 @@
|
|||||||
<link rel="stylesheet" href="/static/css/navbar.css">
|
|
||||||
|
|
||||||
<link rel="stylesheet" href="/static/cdnback/all.min.css">
|
<link rel="stylesheet" href="/static/cdnback/all.min.css">
|
||||||
<link rel="stylesheet" href="/static/css/dashboard.css">
|
<link rel="stylesheet" href="/static/css/dashboard.css">
|
||||||
|
<link rel="stylesheet" href="/static/css/navbar.css">
|
||||||
<header class="navbar">
|
<header class="navbar">
|
||||||
<div class="logo">
|
<div class="logo">
|
||||||
<h1>华实学伴:教学练评一体的虚拟助教</h1>
|
<h1>华实学伴:教学练评一体的虚拟助教</h1>
|
||||||
|
|||||||
@@ -6,6 +6,7 @@ from ..services.backboard_service import realtime_response
|
|||||||
from ..extension_ase.ase_client.manager import ChatManager
|
from ..extension_ase.ase_client.manager import ChatManager
|
||||||
import subprocess
|
import subprocess
|
||||||
from ..auth.decorators import require_role
|
from ..auth.decorators import require_role
|
||||||
|
from ..services.course_service import load_learning_progress
|
||||||
|
|
||||||
bp = Blueprint("vscode", __name__)
|
bp = Blueprint("vscode", __name__)
|
||||||
|
|
||||||
@@ -149,13 +150,33 @@ def desktop(user_uuid, course_id, chapter_name, lesson_name):
|
|||||||
def desktop_nouser(course_id, chapter_name, lesson_name):
|
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"]
|
||||||
|
uuid2username = current_app.extensions["uuid2username"]
|
||||||
|
user_id = uuid2username[user_uuid]
|
||||||
|
|
||||||
|
# 检查是否有学习记录
|
||||||
|
progress_result = load_learning_progress(user_id, course_id, chapter_name, lesson_name)
|
||||||
|
|
||||||
|
if not progress_result["exists"]:
|
||||||
|
# 没有学习记录,直接跳转到学习页面,不显示确认加载历史的页面
|
||||||
|
return redirect(url_for("vscode.desktop", user_uuid=user_uuid, course_id=course_id, chapter_name=chapter_name, lesson_name=lesson_name, load_history="false"))
|
||||||
|
|
||||||
|
# 获取学习记录数据
|
||||||
|
progress_data = progress_result["data"]
|
||||||
|
chapter_chain_now = progress_data.get("chapter_chain_now", -1)
|
||||||
|
chapter_chain_length = progress_data.get("chapter_chain_length", 0)
|
||||||
|
|
||||||
|
# 检查是否已经学完所有章节
|
||||||
|
is_course_finished = chapter_chain_now >= chapter_chain_length - 1
|
||||||
|
|
||||||
return render_template(
|
return render_template(
|
||||||
"confirm_load_history.html",
|
"confirm_load_history.html",
|
||||||
user_uuid=user_uuid,
|
user_uuid=user_uuid,
|
||||||
course_id=course_id,
|
course_id=course_id,
|
||||||
chapter_name=chapter_name,
|
chapter_name=chapter_name,
|
||||||
lesson_name=lesson_name
|
lesson_name=lesson_name,
|
||||||
|
is_course_finished=is_course_finished
|
||||||
)
|
)
|
||||||
|
|
||||||
@bp.route("/vscode_data", methods=["POST"])
|
@bp.route("/vscode_data", methods=["POST"])
|
||||||
|
|||||||
@@ -39,35 +39,56 @@ def read_and_forward_pty_output(fd=None, pid=None, room_id=None,namespace=None):
|
|||||||
"""
|
"""
|
||||||
max_read_bytes = 1024 * 20
|
max_read_bytes = 1024 * 20
|
||||||
timeout=0.1
|
timeout=0.1
|
||||||
while True:
|
try:
|
||||||
socketio.sleep(timeout)
|
while True:
|
||||||
timeout=min(timeout*2, 0.4)
|
socketio.sleep(timeout)
|
||||||
# using flask default web server, or uwsgi production web server
|
timeout=min(timeout*2, 0.4)
|
||||||
# when the child process is terminated, it will not disappear from linux process list
|
# using flask default web server, or uwsgi production web server
|
||||||
# and keep staying as a zombie process until the parent exits.
|
# when the child process is terminated, it will not disappear from linux process list
|
||||||
try:
|
# and keep staying as a zombie process until the parent exits.
|
||||||
child_process = psutil.Process(pid)
|
try:
|
||||||
except psutil.NoSuchProcess as err:
|
child_process = psutil.Process(pid)
|
||||||
return
|
except psutil.NoSuchProcess as err:
|
||||||
if child_process.status() not in ('running', 'sleeping'):
|
# Process already terminated, clean up any zombie
|
||||||
return
|
|
||||||
if fd:
|
|
||||||
timeout_sec = 0
|
|
||||||
(data_ready, _, _) = select.select([fd], [], [], timeout_sec)
|
|
||||||
if data_ready:
|
|
||||||
# output = os.read(fd, max_read_bytes).decode('ascii')
|
|
||||||
timeout=0.1
|
|
||||||
try:
|
try:
|
||||||
output = os.read(fd, max_read_bytes).decode()
|
os.waitpid(pid, os.WNOHANG)
|
||||||
except Exception as err:
|
except Exception:
|
||||||
output = """
|
pass
|
||||||
***AQUI WEB TERM ERR***
|
return
|
||||||
{}
|
if child_process.status() not in ('running', 'sleeping'):
|
||||||
***********************
|
# Process is terminated or in other state, clean up
|
||||||
""".format(err)
|
try:
|
||||||
# the key for different visitor to get different terminal (instead of mixing up)
|
child_process.wait(timeout=1)
|
||||||
# is to let the background task push pty response to each one's own (default) ROOM!
|
except Exception:
|
||||||
namespace.emit("pty_output", {"output": output}, room=room_id)
|
try:
|
||||||
|
os.waitpid(pid, os.WNOHANG)
|
||||||
|
except Exception:
|
||||||
|
pass
|
||||||
|
return
|
||||||
|
if fd:
|
||||||
|
timeout_sec = 0
|
||||||
|
(data_ready, _, _) = select.select([fd], [], [], timeout_sec)
|
||||||
|
if data_ready:
|
||||||
|
# output = os.read(fd, max_read_bytes).decode('ascii')
|
||||||
|
timeout=0.1
|
||||||
|
try:
|
||||||
|
output = os.read(fd, max_read_bytes).decode()
|
||||||
|
except Exception as err:
|
||||||
|
output = """
|
||||||
|
***AQUI WEB TERM ERR***
|
||||||
|
{}
|
||||||
|
***********************
|
||||||
|
""".format(err)
|
||||||
|
# the key for different visitor to get different terminal (instead of mixing up)
|
||||||
|
# is to let the background task push pty response to each one's own (default) ROOM!
|
||||||
|
namespace.emit("pty_output", {"output": output}, room=room_id)
|
||||||
|
finally:
|
||||||
|
# Clean up file descriptor if it's open
|
||||||
|
if fd:
|
||||||
|
try:
|
||||||
|
os.close(fd)
|
||||||
|
except Exception:
|
||||||
|
pass
|
||||||
|
|
||||||
class VSCLikeNameSpace(Namespace):
|
class VSCLikeNameSpace(Namespace):
|
||||||
def on_connect(self):
|
def on_connect(self):
|
||||||
@@ -156,15 +177,40 @@ class VSCLikeNameSpace(Namespace):
|
|||||||
set_winsize(fd, data["rows"], data["cols"])
|
set_winsize(fd, data["rows"], data["cols"])
|
||||||
|
|
||||||
def on_disconnect(self):
|
def on_disconnect(self):
|
||||||
try:
|
child_pid = session.get('terminal_config', {}).get('child_pid')
|
||||||
child_process = psutil.Process(session.get('terminal_config', {}).get('child_pid'))
|
if child_pid:
|
||||||
except psutil.NoSuchProcess as err:
|
try:
|
||||||
disconnect()
|
child_process = psutil.Process(child_pid)
|
||||||
session['terminal_config'] = TERM_INIT_CONFIG
|
if child_process.status() in ('running', 'sleeping'):
|
||||||
return
|
# if visitor just close the browser tab then left alone the pty here
|
||||||
if child_process.status() in ('running', 'sleeping'):
|
# it should be terminated by the parent process after
|
||||||
# if visitor just close the browser tab then left alone the pty here
|
child_process.terminate()
|
||||||
# it should be terminated by the parent process after
|
# Wait for the process to terminate and collect its exit status
|
||||||
child_process.terminate()
|
child_process.wait(timeout=2)
|
||||||
current_app.logger.debug('user left the pty alone, terminated')
|
current_app.logger.debug('user left the pty alone, terminated and waited')
|
||||||
|
except psutil.NoSuchProcess as err:
|
||||||
|
# Process already terminated, try to wait anyway to clean up any zombie
|
||||||
|
try:
|
||||||
|
os.waitpid(child_pid, os.WNOHANG)
|
||||||
|
except Exception:
|
||||||
|
pass
|
||||||
|
except psutil.TimeoutExpired:
|
||||||
|
# If process didn't terminate in time, kill it forcefully
|
||||||
|
child_process.kill()
|
||||||
|
try:
|
||||||
|
child_process.wait(timeout=1)
|
||||||
|
except Exception:
|
||||||
|
pass
|
||||||
|
except Exception as err:
|
||||||
|
current_app.logger.error(f'Error terminating process: {err}')
|
||||||
|
finally:
|
||||||
|
# Close the file descriptor if it's open
|
||||||
|
fd = session.get('terminal_config', {}).get('fd')
|
||||||
|
if fd:
|
||||||
|
try:
|
||||||
|
os.close(fd)
|
||||||
|
except Exception:
|
||||||
|
pass
|
||||||
|
# Reset session config
|
||||||
|
session['terminal_config'] = TERM_INIT_CONFIG
|
||||||
current_app.logger.debug('Client disconnected')
|
current_app.logger.debug('Client disconnected')
|
||||||
@@ -4,17 +4,17 @@
|
|||||||
<meta charset="UTF-8">
|
<meta charset="UTF-8">
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||||
<title>Cloud IDE</title>
|
<title>Cloud IDE</title>
|
||||||
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/monaco-editor/0.34.0/min/vs/editor/editor.main.css">
|
<link rel="stylesheet" href="static/cdnback/css/editor/editor.main.css">
|
||||||
<script src="https://cdnjs.cloudflare.com/ajax/libs/socket.io/4.5.1/socket.io.min.js"></script>
|
<script src="static/cdnback/socket.io.min.js"></script>
|
||||||
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/5.15.4/css/all.min.css">
|
<link rel="stylesheet" href="static/cdnback/all.min.css">
|
||||||
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/xterm/5.5.0/xterm.css" />
|
<link rel="stylesheet" href="static/cdnback/css/xterm.css" />
|
||||||
<!-- <script src="https://cdnjs.cloudflare.com/ajax/libs/xterm/5.5.0/xterm.js"></script> -->
|
<!-- <script src="https://cdnjs.cloudflare.com/ajax/libs/xterm/5.5.0/xterm.js"></script> -->
|
||||||
<script src="https://unpkg.com/xterm@3.6.0/dist/xterm.js"></script>
|
<script src="static/cdnback/js/xterm.js"></script>
|
||||||
<script src="https://unpkg.com/xterm@3.6.0/dist/addons/fit/fit.js"></script>
|
<script src="static/cdnback/js/addons/fit/fit.js"></script>
|
||||||
<script src="https://unpkg.com/xterm@3.6.0/dist/addons/webLinks/webLinks.js"></script>
|
<script src="static/cdnback/js/addons/webLinks/webLinks.js"></script>
|
||||||
<script src="https://unpkg.com/xterm@3.6.0/dist/addons/fullscreen/fullscreen.js"></script>
|
<script src="static/cdnback/js/addons/fullscreen/fullscreen.js"></script>
|
||||||
<script src="https://unpkg.com/xterm@3.6.0/dist/addons/search/search.js"></script>
|
<script src="static/cdnback/js/addons/search/search.js"></script>
|
||||||
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/5.15.4/css/all.min.css">
|
<link rel="stylesheet" href="static/cdnback/all.min.css">
|
||||||
|
|
||||||
<link rel="stylesheet" href="/vsc-like/static/css/index.css">
|
<link rel="stylesheet" href="/vsc-like/static/css/index.css">
|
||||||
<link rel="stylesheet" href="/vsc-like/static/css/notification.css">
|
<link rel="stylesheet" href="/vsc-like/static/css/notification.css">
|
||||||
@@ -61,7 +61,7 @@
|
|||||||
</div>
|
</div>
|
||||||
<div id="notificationsContainer"></div>
|
<div id="notificationsContainer"></div>
|
||||||
|
|
||||||
<script src="https://cdnjs.cloudflare.com/ajax/libs/monaco-editor/0.34.0/min/vs/loader.js"></script>
|
<script src="static/cdnback/js/monaco-editor/0.34.0/min/vs/loader.js"></script>
|
||||||
<script src="/vsc-like/static/js/code-like-extension.js"></script>
|
<script src="/vsc-like/static/js/code-like-extension.js"></script>
|
||||||
<script src="/vsc-like/static/js/file.js"></script>
|
<script src="/vsc-like/static/js/file.js"></script>
|
||||||
<script src="/vsc-like/static/js/terminal.js"></script>
|
<script src="/vsc-like/static/js/terminal.js"></script>
|
||||||
|
|||||||
Reference in New Issue
Block a user