11 Commits

15 changed files with 889 additions and 695 deletions

File diff suppressed because one or more lines are too long

View File

@@ -1,41 +1,53 @@
function show_books(courses_data, user_selected_courses){ function show_books(courses_data, user_selected_courses) {
console.log(courses_data) console.log(courses_data)
console.log(user_selected_courses) console.log(user_selected_courses)
} }
function show_course_details(course_id){ function show_course_details(course_id) {
window.location.href = '/course/' + course_id window.location.href = '/course/' + course_id
} }
function on_click_select_course(event){ function on_click_select_course(event) {
event.stopPropagation(); // 阻止事件冒泡 event.stopPropagation(); // 阻止事件冒泡
const courseId = event.currentTarget.getAttribute('data-course-id'); const button = event.currentTarget;
if (event.currentTarget.classList.contains("selected-button")) {alert('课程已选择'); return;} const courseId = button.getAttribute('data-course-id');
if (!courseId) { alert('缺少课程ID'); return; }
if (button.classList.contains("selected-button")) { alert('课程已选择'); return; }
if (button.disabled) { return; }
button.disabled = true;
fetch(`/select_course`, { fetch(`/select_course`, {
method: 'POST', method: 'POST',
headers: { headers: {
'Content-Type': 'application/json', 'Content-Type': 'application/json',
'X-CSRFToken': '{{ csrf_token() }}' // 如果使用 CSRF 保护
}, },
body: JSON.stringify({ course_id: courseId }) body: JSON.stringify({ course_id: courseId })
}) })
.then(response => response.json()) .then(async (response) => {
.then(data => { let data = null;
if (data.success) { try {
// 更新按钮样式或显示消息 data = await response.json();
this.classList.remove('select-button'); } catch (_) {
this.classList.add('selected-button'); }
this.textContent = '已选择'; if (!response.ok) {
throw new Error(data?.message || `请求失败(${response.status})`);
}
return data;
})
.then((data) => {
if (!data?.success) {
throw new Error(data?.message || '选择课程失败');
}
button.classList.remove('select-button');
button.classList.add('selected-button');
button.textContent = '已选课';
alert('选择课程成功'); alert('选择课程成功');
} else { })
alert('选择课程失败'); .catch(error => {
} console.error('Error:', error);
}) alert(error?.message || '选择课程失败');
.catch(error => { button.disabled = false;
console.error('Error:', error); });
alert('选择课程失败');
});
} }
document.addEventListener('DOMContentLoaded', function() { document.addEventListener('DOMContentLoaded', function () {
}); });

File diff suppressed because it is too large Load Diff

View File

@@ -4,6 +4,7 @@
<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>华实学伴 - 专业的在线学习平台</title> <title>华实学伴 - 专业的在线学习平台</title>
<script src="/static/cdnback/jquery.min.js"></script>
<link href="/static/cdnback/bootstrap.min.css" rel="stylesheet"> <link href="/static/cdnback/bootstrap.min.css" rel="stylesheet">
<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">

View File

@@ -1,8 +1,9 @@
<!DOCTYPE html> <!DOCTYPE html>
<html lang="zh"> <html lang="zh">
<head> <head>
<meta charset="UTF-8"> <meta charset="UTF-8">
<title>课程选择主页</title> <title>课程选择主页</title>
<script src="/static/cdnback/jquery.min.js"></script> <script src="/static/cdnback/jquery.min.js"></script>
<link href="/static/cdnback/bootstrap.min.css" rel="stylesheet"> <link href="/static/cdnback/bootstrap.min.css" rel="stylesheet">
@@ -12,6 +13,7 @@
<script src="/static/cdnback/js/bootstrap.bundle.min.js"></script> <script src="/static/cdnback/js/bootstrap.bundle.min.js"></script>
<link rel="stylesheet" href="/static/css/index.css"> <link rel="stylesheet" href="/static/css/index.css">
</head> </head>
<body> <body>
{% include 'navbar.html' %} {% include 'navbar.html' %}
@@ -38,7 +40,7 @@
<h2>课程列表</h2> <h2>课程列表</h2>
<div class="course-cards" id="course-cards"> <div class="course-cards" id="course-cards">
{% for course in courses_data %} {% for course in courses_data %}
<!-- <div class="course-card" onclick="show_course_details('{{ course._id }}')"> <!-- <div class="course-card" onclick="show_course_details('{{ course._id }}')">
<img src="{{ course.image_url }}" alt="{{ course.course_name }}"> <img src="{{ course.image_url }}" alt="{{ course.course_name }}">
<h3>{{ course.name }}</h3> <h3>{{ course.name }}</h3>
<p>{{ course.description }}</p> <p>{{ course.description }}</p>
@@ -49,10 +51,9 @@
{% endif %} {% endif %}
</div> --> </div> -->
<div class="col-md-6 col-lg-4"> <div class="col-md-6 col-lg-4">
<div class="course-card card h-100" onclick="openCourseProgress('{{course._id}}')"> <div class="course-card card h-100" onclick="openCourseProgress('{{course._id}}')">
<img src="{{course.image_url}}" <img src="{{course.image_url}}" class="course-img" alt="课程封面">
class="course-img" alt="课程封面">
<div class="card-body d-flex flex-column"> <div class="card-body d-flex flex-column">
<span class="course-category">算法设计</span> <span class="course-category">算法设计</span>
<h5 class="course-title">{{course.name}}</h5> <h5 class="course-title">{{course.name}}</h5>
@@ -62,11 +63,15 @@
<span class="course-rating"><i class="fas fa-star"></i> 4.7</span> <span class="course-rating"><i class="fas fa-star"></i> 4.7</span>
</div> </div>
<div class="mt-auto d-grid gap-2 d-md-flex"> <div class="mt-auto d-grid gap-2 d-md-flex">
<button class="btn btn-view flex-fill" onclick="btn_view(event,'{{course._id}}')"><i class="far fa-eye me-1"></i> 课程</button> <button class="btn btn-view flex-fill" onclick="btn_view(event,'{{course._id}}')"><i
class="far fa-eye me-1"></i> 课程</button>
{% if course._id in selected_courses %} {% if course._id in selected_courses %}
<button class="btn btn-process flex-fill selected-button" data-course-id="{{course._id}}">已选课</button> <button class="btn btn-process flex-fill selected-button"
data-course-id="{{course._id}}">已选课</button>
{% else %} {% else %}
<button class="btn btn-process flex-fill select-button" onclick="on_click_select_course(event)" data-course-id="{{course._id}}">选择课程</button> <button class="btn btn-process flex-fill select-button"
onclick="on_click_select_course(event)"
data-course-id="{{course._id}}">选择课程</button>
{% endif %} {% endif %}
</div> </div>
@@ -74,13 +79,13 @@
</div> </div>
</div> </div>
{% endfor %} {% endfor %}
</div> </div>
</div> </div>
</main> </main>
{% include 'footer-brief.html' %} {% include 'footer-brief.html' %}
</body> </body>
<script> <script>
window.appData = { window.appData = {
@@ -88,5 +93,6 @@
} }
</script> </script>
<script src="/static/js/index.js"></script> <script src="/static/js/index.js"></script>
</html> <script src="/static/js/dashboard.js"></script>
</html>

View File

@@ -5,7 +5,7 @@ api_key = sk-aMpnWklN2IbsK44d1kNpy6YOP9bk1pdPJjFeEmbb0a5ytEFf
model=gpt-4.1-nano model=gpt-4.1-nano
[VSCODE_WEB] [VSCODE_WEB]
url = https://hsamooc.cn url = https://hsamooc.com
[CODE_LIKE] [CODE_LIKE]
url = /vsc-like url = /vsc-like
#http://asengine.net:8282 #http://asengine.net:8282
@@ -27,6 +27,6 @@ bucket = hsamooc-cdn-1374354408
region = ap-guangzhou region = ap-guangzhou
[ASE_ENGINE] [ASE_ENGINE]
url = https://test.asengine.net url = https://asengine.net
namespace = /socket.io/afe58a9e4c03e10fae6fae5985e8adde23a49635 namespace = /socket.io/afe58a9e4c03e10fae6fae5985e8adde23a49635
url_token = afe58a9e4c03e10fae6fae5985e8adde23a49635 url_token = afe58a9e4c03e10fae6fae5985e8adde23a49635

View File

@@ -0,0 +1 @@
{"username": "_10235101560"}

View File

@@ -0,0 +1 @@
{"username": "shanks"}

View File

@@ -0,0 +1 @@
{"username": "ts88"}

View File

@@ -0,0 +1 @@
{"username": "xuans"}

View File

@@ -0,0 +1 @@
{"username": "xuans_"}

View File

@@ -1,5 +1,32 @@
import eventlet import eventlet
eventlet.monkey_patch() # 进行猴子补丁操作 import logging
# 配置 eventlet 日志,抑制 IOClosed 等错误日志
logging.getLogger('eventlet.hubs').setLevel(logging.CRITICAL)
logging.getLogger('eventlet.greenio').setLevel(logging.CRITICAL)
# 进行猴子补丁操作
eventlet.monkey_patch()
# 配置 eventlet hub 以更优雅地处理 IOClosed 错误
try:
from eventlet.hubs import hub
# 保存原始的 handle_error 方法
original_handle_error = hub.Hub.handle_error
def custom_handle_error(self, context, type, value, tb):
# 忽略 IOClosed 错误
if type.__name__ == 'IOClosed' and 'Operation on closed file' in str(value):
return
# 其他错误调用原始方法
original_handle_error(self, context, type, value, tb)
# 替换原始方法
hub.Hub.handle_error = custom_handle_error
except Exception as e:
# 如果修改失败,忽略错误
pass
from flask import Flask from flask import Flask
from .views.routes import main_bp from .views.routes import main_bp
from .views.file import file_bp from .views.file import file_bp

View File

@@ -29,8 +29,12 @@ terminal_bp = Blueprint('terminal', __name__, url_prefix='/vsc-like')
def set_winsize(fd, row, col, xpix=0, ypix=0): def set_winsize(fd, row, col, xpix=0, ypix=0):
winsize = struct.pack("HHHH", row, col, xpix, ypix) try:
fcntl.ioctl(fd, termios.TIOCSWINSZ, winsize) winsize = struct.pack("HHHH", row, col, xpix, ypix)
fcntl.ioctl(fd, termios.TIOCSWINSZ, winsize)
except (OSError, IOError):
# File descriptor closed or invalid, do nothing
pass
def read_and_forward_pty_output(fd=None, pid=None, room_id=None,namespace=None): def read_and_forward_pty_output(fd=None, pid=None, room_id=None,namespace=None):
@@ -67,21 +71,35 @@ def read_and_forward_pty_output(fd=None, pid=None, room_id=None,namespace=None):
return return
if fd: if fd:
timeout_sec = 0 timeout_sec = 0
(data_ready, _, _) = select.select([fd], [], [], timeout_sec) try:
if data_ready: # Check if file descriptor is still valid by trying to select on it
# output = os.read(fd, max_read_bytes).decode('ascii') (data_ready, _, _) = select.select([fd], [], [], timeout_sec)
timeout=0.1 if data_ready:
try: timeout=0.1
output = os.read(fd, max_read_bytes).decode() try:
except Exception as err: output = os.read(fd, max_read_bytes).decode()
output = """ except (OSError, IOError, EOFError) as err:
***AQUI WEB TERM ERR*** # File descriptor closed or other IO error, exit gracefully
{} return
*********************** except UnicodeDecodeError as err:
""".format(err) output = """
# the key for different visitor to get different terminal (instead of mixing up) ***AQUI WEB TERM ERR***
# is to let the background task push pty response to each one's own (default) ROOM! Unicode decode error: {}
namespace.emit("pty_output", {"output": output}, room=room_id) ***********************
""".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!
try:
namespace.emit("pty_output", {"output": output}, room=room_id)
except Exception:
# If emit fails, the client is probably disconnected, exit
return
except (OSError, IOError) as err:
# File descriptor closed or invalid, exit gracefully
return
except Exception as e:
# Catch any other unexpected exceptions to prevent server crash
pass
finally: finally:
# Clean up file descriptor if it's open # Clean up file descriptor if it's open
if fd: if fd:
@@ -158,7 +176,13 @@ class VSCLikeNameSpace(Namespace):
if fd: if fd:
# print("writing to ptd: %s" % data["input"]) # print("writing to ptd: %s" % data["input"])
# os.write(fd, data["input"].encode('ascii')) # os.write(fd, data["input"].encode('ascii'))
os.write(fd, data["input"].encode()) try:
os.write(fd, data["input"].encode())
except (OSError, IOError):
# File descriptor closed or invalid, clean up
disconnect()
session['terminal_config'] = TERM_INIT_CONFIG
return
def on_resize(self, data): def on_resize(self, data):
@@ -174,10 +198,29 @@ class VSCLikeNameSpace(Namespace):
return return
fd = session.get('terminal_config').get('fd') fd = session.get('terminal_config').get('fd')
if fd: if fd:
set_winsize(fd, data["rows"], data["cols"]) # 检查文件描述符是否有效
try:
# 尝试一个简单的操作来检查文件描述符是否有效
os.fstat(fd)
set_winsize(fd, data["rows"], data["cols"])
except (OSError, IOError):
# 文件描述符无效,清理资源
disconnect()
session['terminal_config'] = TERM_INIT_CONFIG
return
def on_disconnect(self): def on_disconnect(self):
child_pid = session.get('terminal_config', {}).get('child_pid') terminal_config = session.get('terminal_config', {})
child_pid = terminal_config.get('child_pid')
fd = terminal_config.get('fd')
# 先关闭文件描述符,避免其他操作尝试使用无效的文件描述符
if fd:
try:
os.close(fd)
except Exception:
pass
if child_pid: if child_pid:
try: try:
child_process = psutil.Process(child_pid) child_process = psutil.Process(child_pid)
@@ -196,21 +239,14 @@ class VSCLikeNameSpace(Namespace):
pass pass
except psutil.TimeoutExpired: except psutil.TimeoutExpired:
# If process didn't terminate in time, kill it forcefully # If process didn't terminate in time, kill it forcefully
child_process.kill()
try: try:
child_process.kill()
child_process.wait(timeout=1) child_process.wait(timeout=1)
except Exception: except Exception:
pass pass
except Exception as err: except Exception as err:
current_app.logger.error(f'Error terminating process: {err}') current_app.logger.error(f'Error terminating process: {err}')
finally:
# Close the file descriptor if it's open # Reset session config
fd = session.get('terminal_config', {}).get('fd') session['terminal_config'] = TERM_INIT_CONFIG
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')

View File

@@ -1,12 +1,32 @@
from app import create_app from app import create_app
from app.extensions import socketio from app.extensions import socketio
import logging
# 配置日志减少eventlet的调试日志
logging.getLogger('eventlet').setLevel(logging.ERROR)
logging.getLogger('socketio').setLevel(logging.ERROR)
logging.getLogger('werkzeug').setLevel(logging.ERROR)
app = create_app() app = create_app()
if __name__ == '__main__': if __name__ == '__main__':
socketio.run( try:
app, socketio.run(
host="0.0.0.0", app,
port=5200, host="0.0.0.0",
debug=False, # 开发期打开 port=5200,
allow_unsafe_werkzeug=True, # 避免 dev server 的安全限制提示 debug=False, # 开发期打开
allow_unsafe_werkzeug=True, # 避免 dev server 的安全限制提示
log_output=False # 禁用详细输出
) )
except KeyboardInterrupt:
print("Server stopped by user")
except Exception as e:
# 捕获所有异常,包括 eventlet 内部的 IOClosed 错误
if "IOClosed" in str(type(e).__name__) or "Operation on closed file" in str(e):
# 优雅处理 eventlet 关闭文件的错误
print("Eventlet IOClosed error handled gracefully")
else:
# 其他异常仍然打印
import traceback
traceback.print_exc()

69
et --hard HEAD@{1} Normal file
View File

@@ -0,0 +1,69 @@
68493d6a (HEAD -> main, origin/main) HEAD@{0}: pull origin main: Fast-forward
0852e121 HEAD@{1}: reset: moving to HEAD
0852e121 HEAD@{2}: commit: cdn fix more
7dc4714a HEAD@{3}: commit: index style
9dc81b14 (origin/feature/paste_detecte_new, feature/paste_detecte_new) HEAD@{4}: merge feature/paste_detecte_new: Fast-forward
7862647b HEAD@{5}: checkout: moving from feature/paste_detecte_new to main
9dc81b14 (origin/feature/paste_detecte_new, feature/paste_detecte_new) HEAD@{6}: commit (merge): Merge branch 'main' into feature/paste_detecte_new
0cf1896b HEAD@{7}: checkout: moving from main to feature/paste_detecte_new
7862647b HEAD@{8}: commit: style fix
6b9c8e9e HEAD@{9}: commit: cdn change to local and use markdown and style change
33844786 HEAD@{10}: commit: use markdown to edit
ce88ae64 HEAD@{11}: commit: use queue to allow send vscode_ws when it is none
6c38e906 HEAD@{12}: pull: Fast-forward
9759c93a HEAD@{13}: commit: fix funciton response
ecf19f49 HEAD@{14}: commit: fix function double promlem
f5d50772 HEAD@{15}: commit: function call can be expire
4a390adb HEAD@{16}: commit: fix style bug
960d1f1d HEAD@{17}: commit: style fix
c73ea447 HEAD@{18}: commit: fix index
33bf569f HEAD@{19}: commit: fix nextchapter bug
b921bda4 HEAD@{20}: commit: auto get domain
9921848d HEAD@{21}: commit: remove prefix of http url
4d81aef8 HEAD@{22}: commit (merge): Merge branch 'feature/paste_deteced'
8fe5ee16 HEAD@{23}: checkout: moving from feature/paste_deteced to main
9ce212e9 (origin/feature/paste_deteced, feature/paste_deteced) HEAD@{24}: checkout: moving from main to feature/paste_deteced
8fe5ee16 HEAD@{25}: commit (merge): 准备合并main
908d5915 HEAD@{26}: checkout: moving from syt to main
57d8ac32 (origin/syt, syt) HEAD@{27}: checkout: moving from main to syt
908d5915 HEAD@{28}: commit: ready
02b12791 HEAD@{29}: checkout: moving from syt to main
57d8ac32 (origin/syt, syt) HEAD@{30}: checkout: moving from main to syt
02b12791 HEAD@{31}: pull: Fast-forward
7d861e34 (origin/zrz) HEAD@{32}: checkout: moving from syt to main
57d8ac32 (origin/syt, syt) HEAD@{33}: commit: first
0196f37e HEAD@{34}: pull: Merge made by the 'ort' strategy.
57eb1197 HEAD@{35}: commit: ok
382f2006 HEAD@{36}: checkout: moving from main to syt
7d861e34 (origin/zrz) HEAD@{37}: commit (merge): Merge branch 'main' of https://gitee.com/CakeCN/code-agent
5f8f6c94 HEAD@{38}: commit: many ok
e24aa12e HEAD@{39}: commit: ready to process
6766ca85 HEAD@{40}: commit (merge): Merge branch 'feature/voice_send'
3c9c7553 HEAD@{41}: checkout: moving from feature/voice_send to main
58a95e9f (origin/feature/voice_send, feature/voice_send) HEAD@{42}: commit: ready merge to main
79125ffc HEAD@{43}: commit: change .cn to .com
2f9bc7df HEAD@{44}: checkout: moving from main to feature/voice_send
3c9c7553 HEAD@{45}: reset: moving to HEAD
3c9c7553 HEAD@{46}: checkout: moving from feature/voice_send to main
2f9bc7df HEAD@{47}: pull: Fast-forward
504c5cae (0v) HEAD@{48}: checkout: moving from main to feature/voice_send
3c9c7553 HEAD@{49}: commit: cn/com allow all
7122c3be HEAD@{50}: commit (merge): fix url
e07c41fb HEAD@{51}: commit: fix code-server reboot problem(close debug)
821e20c9 HEAD@{52}: commit: clear double iframe more safe
053584a7 HEAD@{53}: checkout: moving from feature/voice_send to main
504c5cae (0v) HEAD@{54}: checkout: moving from main to feature/voice_send
053584a7 HEAD@{55}: commit: clear iframe double
10481b0b HEAD@{56}: commit: fix permisson bug
dc0eeaa9 HEAD@{57}: commit: add hint
a24df620 HEAD@{58}: commit: fix some bug
3ad36c29 HEAD@{59}: commit: 25/09/24 10:00~11:30 停机维护
07cf2363 HEAD@{60}: commit: fix many bug
6aac4200 HEAD@{61}: commit: try hello message
271672c9 HEAD@{62}: commit: check to code-server-like
252c426c HEAD@{63}: pull: Merge made by the 'ort' strategy.
588fc3b6 HEAD@{64}: commit: ready to code-like
b1f832a2 (origin/code-like-autosave, code-like-autosave) HEAD@{65}: merge code-like-autosave: Fast-forward
24f191d5 HEAD@{66}: checkout: moving from code-like-autosave to main
b1f832a2 (origin/code-like-autosave, code-like-autosave) HEAD@{67}: checkout: moving from main to code-like-autosave
24f191d5 HEAD@{68}: clone: from https://gitee.com/CakeCN/code-agent.git