25/09/24 10:00~11:30 停机维护
This commit is contained in:
@@ -2,6 +2,7 @@ import signal
|
|||||||
import sys
|
import sys
|
||||||
import os
|
import os
|
||||||
import time
|
import time
|
||||||
|
import re
|
||||||
import uuid
|
import uuid
|
||||||
current_directory = os.path.dirname(os.path.abspath(__file__))
|
current_directory = os.path.dirname(os.path.abspath(__file__))
|
||||||
sys.path.append(current_directory)
|
sys.path.append(current_directory)
|
||||||
@@ -70,6 +71,12 @@ class ChatManager:
|
|||||||
print("ERROR: disconnect",e)
|
print("ERROR: disconnect",e)
|
||||||
print("disconnect success stop code-server success")
|
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):
|
def next_chapter(self):
|
||||||
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"
|
||||||
@@ -85,6 +92,7 @@ class ChatManager:
|
|||||||
def load_next_chapter(self):
|
def load_next_chapter(self):
|
||||||
print(f"now load next chapter markdown {self.chapter_chain_now}")
|
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.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):
|
def upload_bb(self):
|
||||||
@@ -278,4 +286,21 @@ class ChatManager:
|
|||||||
for k in keys:
|
for k in keys:
|
||||||
self.stop(k)
|
self.stop(k)
|
||||||
|
|
||||||
|
|
||||||
|
def extract_code_blocks(text):
|
||||||
|
"""
|
||||||
|
从文本中提取所有用```标记的代码段(包括标记本身)
|
||||||
|
|
||||||
|
参数:
|
||||||
|
text: 包含代码段的原始文本
|
||||||
|
|
||||||
|
返回:
|
||||||
|
提取到的代码段列表,每个元素是一个完整的代码块(包括```标记)
|
||||||
|
"""
|
||||||
|
# 正则表达式匹配```开始和结束的代码块
|
||||||
|
# 考虑可能的语言指定(如```python)
|
||||||
|
pattern = r'```.*?```'
|
||||||
|
# 使用DOTALL模式让.匹配包括换行符在内的所有字符
|
||||||
|
code_blocks = re.findall(pattern, text, re.DOTALL)
|
||||||
|
|
||||||
|
return code_blocks
|
||||||
@@ -24,8 +24,7 @@ def realtime_response(config: dict, realtime_action: dict) -> None:
|
|||||||
assert isinstance(bb, Backboard)
|
assert isinstance(bb, Backboard)
|
||||||
|
|
||||||
# 统一记历史
|
# 统一记历史
|
||||||
bb.add_history(realtime_action)
|
print(f"backboard action {realtime_action}")
|
||||||
|
|
||||||
rtype = realtime_action.get("type")
|
rtype = realtime_action.get("type")
|
||||||
|
|
||||||
if rtype == "workspaceFolders":
|
if rtype == "workspaceFolders":
|
||||||
@@ -35,7 +34,7 @@ def realtime_response(config: dict, realtime_action: dict) -> None:
|
|||||||
file_path = realtime_action.get("filePath")
|
file_path = realtime_action.get("filePath")
|
||||||
bb.active_file_path = file_path
|
bb.active_file_path = file_path
|
||||||
assert isinstance(file_path, str)
|
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()
|
bb.active_file_content = f.read()
|
||||||
|
|
||||||
elif rtype == "paste":
|
elif rtype == "paste":
|
||||||
@@ -43,16 +42,16 @@ def realtime_response(config: dict, realtime_action: dict) -> None:
|
|||||||
assert isinstance(file_path, str)
|
assert isinstance(file_path, str)
|
||||||
bb.pasted_file_path = file_path
|
bb.pasted_file_path = file_path
|
||||||
bb.pasted_content = realtime_action.get("content")
|
bb.pasted_content = realtime_action.get("content")
|
||||||
bb.active_file_path = file_path
|
with open(os.path.join(bb.root_path, bb.active_file_path), "r", encoding="utf-8") as f:
|
||||||
with open(os.path.join(bb.root_path, bb.active_file_content), "r", encoding="utf-8") as f:
|
|
||||||
bb.active_file_content = f.read()
|
bb.active_file_content = f.read()
|
||||||
|
|
||||||
elif rtype == "fileEdit":
|
elif rtype == "fileEdit":
|
||||||
file_path = realtime_action.get("filePath")
|
file_path = realtime_action.get("filePath")
|
||||||
assert isinstance(file_path, str)
|
assert isinstance(file_path, str)
|
||||||
bb.active_file_path = file_path
|
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()
|
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()
|
chatmanager.upload_bb()
|
||||||
@@ -15,10 +15,7 @@ from ..services.user_service import get_or_load_current_user
|
|||||||
class VSCodeNamespace(Namespace):
|
class VSCodeNamespace(Namespace):
|
||||||
def on_login(self,data):
|
def on_login(self,data):
|
||||||
ex = current_app.extensions
|
ex = current_app.extensions
|
||||||
uuid2username = ex["uuid2username"]
|
|
||||||
backboard_manager = ex["backboard_manager"]
|
|
||||||
print("VSCode client connected")
|
print("VSCode client connected")
|
||||||
print(data)
|
|
||||||
dataconfig = data['config']
|
dataconfig = data['config']
|
||||||
user_uuid = dataconfig.get('user_uuid')
|
user_uuid = dataconfig.get('user_uuid')
|
||||||
path = dataconfig.get('path')
|
path = dataconfig.get('path')
|
||||||
@@ -26,11 +23,18 @@ class VSCodeNamespace(Namespace):
|
|||||||
lesson_id = dataconfig.get('chapter_id')
|
lesson_id = dataconfig.get('chapter_id')
|
||||||
print(f"User {user_uuid} connected with path: {path}")
|
print(f"User {user_uuid} connected with path: {path}")
|
||||||
join_room(user_uuid, namespace='/vscode')
|
join_room(user_uuid, namespace='/vscode')
|
||||||
if backboard_manager.get_backboard(user_uuid) == None:
|
chatmanager = ex['user_uuid2chatmanager'][user_uuid]
|
||||||
backboard_manager.add_backboard(user_uuid,uuid2username[user_uuid], course_id, lesson_id, path)
|
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):
|
def on_message(self, data):
|
||||||
# print(f"Received from VSCode client: {data}")
|
# print(f"Received from VSCode client: {data}")
|
||||||
dataconfig = data['config']
|
dataconfig = data['config']
|
||||||
@@ -139,6 +143,9 @@ class AgentNamespace(Namespace):
|
|||||||
user_uuid2ase_client[uuid].send_text(d['route'], data['data'])
|
user_uuid2ase_client[uuid].send_text(d['route'], data['data'])
|
||||||
|
|
||||||
def on_connect_to_ase(client_entry: HSAEngineClient, namespace_entry, init_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}")
|
print(f"on_connect_to_ase {client_entry}, {namespace_entry}, {init_data}")
|
||||||
namespace_entry, ase_client = namespace_entry
|
namespace_entry, ase_client = namespace_entry
|
||||||
namespace_entry.emit('open-iframe',"", room=init_data['room'], namespace='/agent')
|
namespace_entry.emit('open-iframe',"", room=init_data['room'], namespace='/agent')
|
||||||
|
|||||||
@@ -30,7 +30,8 @@ body {
|
|||||||
border-radius: 15px;
|
border-radius: 15px;
|
||||||
box-shadow: 0 10px 30px rgba(0, 0, 0, 0.1);
|
box-shadow: 0 10px 30px rgba(0, 0, 0, 0.1);
|
||||||
width: 100%;
|
width: 100%;
|
||||||
max-width: 500px;
|
min-width: 400px;
|
||||||
|
max-width: 600px;
|
||||||
text-align: center;
|
text-align: center;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
69
Html/apps/static/css/register_field_tip.css
Normal file
69
Html/apps/static/css/register_field_tip.css
Normal 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;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -8,7 +8,11 @@ document.addEventListener('DOMContentLoaded', function() {
|
|||||||
const email = document.getElementById('email').value;
|
const email = document.getElementById('email').value;
|
||||||
const password = document.getElementById('password').value;
|
const password = document.getElementById('password').value;
|
||||||
const confirmPassword = document.getElementById('confirm-password').value;
|
const confirmPassword = document.getElementById('confirm-password').value;
|
||||||
|
const u = validateLinuxUsername(username);
|
||||||
|
if (!u.ok) {
|
||||||
|
alert(u.error);
|
||||||
|
return;
|
||||||
|
}
|
||||||
if (password !== confirmPassword) {
|
if (password !== confirmPassword) {
|
||||||
alert('密码和确认密码不一致');
|
alert('密码和确认密码不一致');
|
||||||
return;
|
return;
|
||||||
@@ -36,4 +40,36 @@ document.addEventListener('DOMContentLoaded', function() {
|
|||||||
alert('注册失败');
|
alert('注册失败');
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// 校验 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 };
|
||||||
|
}
|
||||||
27
Html/apps/static/js/register_field_tip.js
Normal file
27
Html/apps/static/js/register_field_tip.js
Normal 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();
|
||||||
|
});
|
||||||
|
})();
|
||||||
@@ -8,7 +8,11 @@ document.addEventListener('DOMContentLoaded', function() {
|
|||||||
const email = document.getElementById('teacher-email').value;
|
const email = document.getElementById('teacher-email').value;
|
||||||
const password = document.getElementById('teacher-password').value;
|
const password = document.getElementById('teacher-password').value;
|
||||||
const confirmPassword = document.getElementById('teacher-confirm-password').value;
|
const confirmPassword = document.getElementById('teacher-confirm-password').value;
|
||||||
|
const u = validateLinuxUsername(username);
|
||||||
|
if (!u.ok) {
|
||||||
|
alert(u.error);
|
||||||
|
return;
|
||||||
|
}
|
||||||
// 检查密码和确认密码是否一致
|
// 检查密码和确认密码是否一致
|
||||||
if (password !== confirmPassword) {
|
if (password !== confirmPassword) {
|
||||||
alert('密码和确认密码不一致');
|
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 };
|
||||||
|
}
|
||||||
@@ -5,6 +5,7 @@
|
|||||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||||
<title>学生注册</title>
|
<title>学生注册</title>
|
||||||
<link rel="stylesheet" href="/static/css/login.css">
|
<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>
|
<script src="/static/js/register.js"></script>
|
||||||
</head>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
@@ -15,6 +16,17 @@
|
|||||||
<div class="input-group">
|
<div class="input-group">
|
||||||
<label for="username">用户名</label>
|
<label for="username">用户名</label>
|
||||||
<input type="text" id="username" name="username" placeholder="请输入用户名" required>
|
<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>
|
||||||
<div class="input-group">
|
<div class="input-group">
|
||||||
<label for="email">邮箱</label>
|
<label for="email">邮箱</label>
|
||||||
@@ -32,5 +44,8 @@
|
|||||||
</form>
|
</form>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|
||||||
|
<script src="/static/js/register_field_tip.js"></script>
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
||||||
@@ -5,6 +5,7 @@
|
|||||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||||
<title>教师注册</title>
|
<title>教师注册</title>
|
||||||
<link rel="stylesheet" href="/static/css/login.css">
|
<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>
|
<script src="/static/js/register_teacher.js"></script>
|
||||||
</head>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
@@ -30,7 +31,20 @@
|
|||||||
</div>
|
</div>
|
||||||
<button type="submit" class="login-btn">注册</button>
|
<button type="submit" class="login-btn">注册</button>
|
||||||
</form>
|
</form>
|
||||||
</div>
|
</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>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<script src="/static/js/register_field_tip.js"></script>
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
||||||
|
|||||||
@@ -1,6 +1,21 @@
|
|||||||
let vs_socket = null;
|
let vs_socket = null;
|
||||||
let debounceTimeout;
|
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) {
|
function debounceSendToServer(data, delay) {
|
||||||
if (debounceTimeout) {
|
if (debounceTimeout) {
|
||||||
@@ -50,6 +65,7 @@ vs_socket.on('connect', () => {
|
|||||||
// vscode.window.showInformationMessage('Connected to server');
|
// vscode.window.showInformationMessage('Connected to server');
|
||||||
console.log('Connected to server');
|
console.log('Connected to server');
|
||||||
vs_socket.emit('login', { config: code_like_config });
|
vs_socket.emit('login', { config: code_like_config });
|
||||||
|
vs_socket.emit('load_chapter',{config: code_like_config})
|
||||||
});
|
});
|
||||||
vs_socket.on('disconnect', () => {
|
vs_socket.on('disconnect', () => {
|
||||||
// vscode.window.showInformationMessage('Disconnected from server');
|
// 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) {
|
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('无效的代码格式');
|
||||||
|
// 处理格式错误的情况
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|||||||
@@ -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) {
|
function saveFile(event) {
|
||||||
event.stopPropagation();
|
event.stopPropagation();
|
||||||
@@ -311,8 +323,7 @@ function saveFile(event) {
|
|||||||
if (selectedItem.path==undefined){
|
if (selectedItem.path==undefined){
|
||||||
selectedItem.path='.';
|
selectedItem.path='.';
|
||||||
}
|
}
|
||||||
fetch(`/vsc-like/save-file`, { method: 'POST', body: JSON.stringify({ path: selectedItem.path+'/'+selectedItem.name, content: content }) , headers: { 'Content-Type': 'application/json' } })
|
postSaveFile(selectedItem.path+'/'+selectedItem.name, content)
|
||||||
.then(() => loadFileTree());
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user