25/09/24 10:00~11:30 停机维护

This commit is contained in:
Cai
2025-09-24 18:12:45 +08:00
parent 07cf2363ab
commit 3ad36c29ed
13 changed files with 2997 additions and 224 deletions

2880
Html/.log

File diff suppressed because one or more lines are too long

View File

@@ -2,6 +2,7 @@ import signal
import sys
import os
import time
import re
import uuid
current_directory = os.path.dirname(os.path.abspath(__file__))
sys.path.append(current_directory)
@@ -70,6 +71,12 @@ class ChatManager:
print("ERROR: disconnect",e)
print("disconnect success stop code-server success")
def load_code_in_markdown(self):
nowchapter_content = self.chapter_chain[self.chapter_chain_now].chapter
code_texts = extract_code_blocks(nowchapter_content)
for code_text in code_texts:
self.vscode_ws.emit('code-file', {'type':'create', 'data':code_text, 'chapter_title': self.chapter_chain[self.chapter_chain_now].title}, room=self.user_uuid ,namespace='/vscode')
def next_chapter(self):
assert self.chapter_chain_now + 1 < len(self.chapter_chain), "chapter_chain_now out of range"
@@ -85,6 +92,7 @@ class ChatManager:
def load_next_chapter(self):
print(f"now load next chapter markdown {self.chapter_chain_now}")
self.ase_client.loadmarkdown(self.chapter_chain[self.chapter_chain_now].chapter, self.chapter_chain[self.chapter_chain_now].chapter_prompt, self.chapter_chain[self.chapter_chain_now].score_prompt)
self.load_code_in_markdown()
def upload_bb(self):
@@ -279,3 +287,20 @@ class ChatManager:
self.stop(k)
def extract_code_blocks(text):
"""
从文本中提取所有用```标记的代码段(包括标记本身)
参数:
text: 包含代码段的原始文本
返回:
提取到的代码段列表,每个元素是一个完整的代码块(包括```标记)
"""
# 正则表达式匹配```开始和结束的代码块
# 考虑可能的语言指定(如```python
pattern = r'```.*?```'
# 使用DOTALL模式让.匹配包括换行符在内的所有字符
code_blocks = re.findall(pattern, text, re.DOTALL)
return code_blocks

View File

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

View File

@@ -15,10 +15,7 @@ from ..services.user_service import get_or_load_current_user
class VSCodeNamespace(Namespace):
def on_login(self,data):
ex = current_app.extensions
uuid2username = ex["uuid2username"]
backboard_manager = ex["backboard_manager"]
print("VSCode client connected")
print(data)
dataconfig = data['config']
user_uuid = dataconfig.get('user_uuid')
path = dataconfig.get('path')
@@ -26,11 +23,18 @@ class VSCodeNamespace(Namespace):
lesson_id = dataconfig.get('chapter_id')
print(f"User {user_uuid} connected with path: {path}")
join_room(user_uuid, namespace='/vscode')
if backboard_manager.get_backboard(user_uuid) == None:
backboard_manager.add_backboard(user_uuid,uuid2username[user_uuid], course_id, lesson_id, path)
chatmanager = ex['user_uuid2chatmanager'][user_uuid]
chatmanager.vscode_ws=self
def on_load_chapter(self, data):
dataconfig = data['config']
user_uuid = dataconfig.get('user_uuid')
ex = current_app.extensions
chatmanager = ex['user_uuid2chatmanager'][user_uuid]
chatmanager.load_code_in_markdown()
def on_message(self, data):
# print(f"Received from VSCode client: {data}")
dataconfig = data['config']
@@ -139,6 +143,9 @@ class AgentNamespace(Namespace):
user_uuid2ase_client[uuid].send_text(d['route'], data['data'])
def on_connect_to_ase(client_entry: HSAEngineClient, namespace_entry, init_data):
'''
此时刚刚建立好aseclient连接并向前端发送打开code-server-like指令。
'''
print(f"on_connect_to_ase {client_entry}, {namespace_entry}, {init_data}")
namespace_entry, ase_client = namespace_entry
namespace_entry.emit('open-iframe',"", room=init_data['room'], namespace='/agent')

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -1,5 +1,20 @@
let vs_socket = null;
let debounceTimeout;
// 定义代码类型到文件后缀的映射
const typeToExtension = {
'python': 'py',
'cpp': 'cpp',
'javascript': 'js',
'java': 'java',
'html': 'html',
'css': 'css',
'php': 'php',
'ruby': 'rb',
'go': 'go',
'c': 'c',
'cs': 'cs'
// 可以根据需要添加更多映射
};
// 缓冲器函数(防抖)
function debounceSendToServer(data, delay) {
@@ -50,6 +65,7 @@ vs_socket.on('connect', () => {
// vscode.window.showInformationMessage('Connected to server');
console.log('Connected to server');
vs_socket.emit('login', { config: code_like_config });
vs_socket.emit('load_chapter',{config: code_like_config})
});
vs_socket.on('disconnect', () => {
// vscode.window.showInformationMessage('Disconnected from server');
@@ -60,4 +76,29 @@ vs_socket.on('terminal',(data)=>{
socket.emit("pty_input", {"input": data.data+'\r\n'}, function(response) {
});
})
vs_socket.on('code-file',(data)=>{
if (data.type=='create'){
let title = data.chapter_title
let content = data.data
const codeBlockRegex = /^```(\w+)\s([\s\S]*?)\s```$/;
const match = content.match(codeBlockRegex);
if (match && match.length >= 3) {
const codeType = match[1].toLowerCase();
const codeContent = match[2].trim();
// 根据代码类型获取后缀如果没有匹配的则使用txt
const fileExtension = typeToExtension[codeType] || 'txt';
console.log('代码类型:', codeType);
console.log('文件后缀:', fileExtension);
console.log('代码内容:', codeContent);
createAndWriteNewFile(title+'.'+fileExtension, codeContent)
// 这里可以使用解析出的结果进行后续操作
} else {
console.error('无效的代码格式');
// 处理格式错误的情况
}
}
})

View File

@@ -262,6 +262,12 @@ function loadFileContent(item) {
}
function createAndWriteNewFile(path, content){
fetch(`/vsc-like/create-file`, { method: 'POST', body: JSON.stringify({ path: path, parent: '.' }) , headers: { 'Content-Type': 'application/json' } })
.then(() => {
postSaveFile('./'+path, content)
});
}
// 新建文件
@@ -303,6 +309,12 @@ function createNewFolder(event) {
}
}
function postSaveFile(path, content){
fetch(`/vsc-like/save-file`, { method: 'POST', body: JSON.stringify({ path: path, content: content }) , headers: { 'Content-Type': 'application/json' } })
.then(() => loadFileTree());
}
// 保存文件
function saveFile(event) {
event.stopPropagation();
@@ -311,8 +323,7 @@ function saveFile(event) {
if (selectedItem.path==undefined){
selectedItem.path='.';
}
fetch(`/vsc-like/save-file`, { method: 'POST', body: JSON.stringify({ path: selectedItem.path+'/'+selectedItem.name, content: content }) , headers: { 'Content-Type': 'application/json' } })
.then(() => loadFileTree());
postSaveFile(selectedItem.path+'/'+selectedItem.name, content)
}