This commit is contained in:
Cai
2025-09-28 21:33:00 +08:00
parent a24df620b3
commit dc0eeaa933
9 changed files with 532 additions and 187 deletions

177
Html/.log

File diff suppressed because one or more lines are too long

View File

@@ -92,6 +92,7 @@ class ASEngineClient:
@self.sio.on(route, namespace=self.namespace)
def on_route_message(data, _route=route):
if not self.connected: return
print(f"Received message on route '{_route}': {data}")
route_info = self.routes.get(_route)
if not route_info:

View File

@@ -90,6 +90,12 @@ class AgentNamespace(Namespace):
entry=self,
init_data={'room': user_uuid}
)
user_uuid2ase_client[user_uuid].register_route_with_entry(
route='dialog-hint',
callback=self.receive_ase_dialog_hint,
entry=self,
init_data={'room': user_uuid}
)
user_uuid2ase_client[user_uuid].register_route_with_entry(
route='judge',
callback=self.receive_ase_judge,
@@ -158,6 +164,222 @@ class AgentNamespace(Namespace):
client_entry.chatmanager.chat_historys.append({'role': 'assistant', 'content': data['data']})
namespace_entry.emit('message', data['data'], room=init_data['room'], namespace='/agent')
def receive_ase_dialog_hint():
pass
def receive_ase_judge(client_entry: HSAEngineClient, namespace_entry: Namespace, data, init_data):
print("receive_ase_judge", data)
namespace_entry.emit('request_function', data, room=init_data['room'], namespace='/agent') # data 是一个列表 /dict也支持
def receive_ase_next_chapter(client_entry: HSAEngineClient, namespace_entry: Namespace, data, init_data):
print("receive_ase_next_chapter", data)
namespace_entry.emit('request_function',data, room=init_data['room'], namespace='/agent') # data 是一个列表 /dict也支持
def receive_ase_chapter_score(client_entry: HSAEngineClient, user_and_manager, data, init_data):
print("load_next_chapter")
user, chatmanager = user_and_manager
if len(chatmanager.scores) >= chatmanager.chapter_chain_now:
chatmanager.scores[chatmanager.chapter_chain_now - 1] = data.get('data', None)
else: chatmanager.scores.append(data.get('data', None))
user.set_course_progress(chatmanager.chat_historys, chatmanager.scores, chatmanager.material_id, chatmanager.chapter_name, chatmanager.lesson_name)
chatmanager.load_next_chapter()
def on_initiative(self,data): # 主动call
print("User active function call")
ex = current_app.extensions
uuid = session.get('user_uuid')
manager = ex["user_uuid2chatmanager"][uuid]
backboard_manager = ex["backboard_manager"]
if data['name'] == 'sample_judge':
manager.sample_judge(uuid, backboard_manager.get_backboard(uuid))
if data['name'] == 'judge':
res = manager.judge(uuid, backboard_manager.get_backboard(uuid))
emit('message', res.message, room=uuid, namespace='/agent')
def on_disconnect(self,data):
ex = current_app.extensions
uuid = session.get('user_uuid')
user_uuid2chatmanager = ex["user_uuid2chatmanager"]
user_uuid2chatmanager[uuid].disconnect(uuid)
print("VSCode client disconnected")
print("Disconnect reason:"+str(data))
# myapp/sockets/namespaces.py
import json
from flask import current_app, request, session
from flask_socketio import Namespace, join_room, leave_room, emit
from ..function_tools import judge
from ..function_tools import next_chapter
from ..services.backboard_service import realtime_response
from ..services.markdown_service import load_full_markdown_file
from ..extension_ase.ase_client import HSAEngineClient
from ..services.user_service import get_or_load_current_user
class VSCodeNamespace(Namespace):
def on_login(self,data):
ex = current_app.extensions
print("VSCode client connected")
dataconfig = data['config']
user_uuid = dataconfig.get('user_uuid')
path = dataconfig.get('path')
course_id = dataconfig.get('course_id')
lesson_id = dataconfig.get('chapter_id')
print(f"User {user_uuid} connected with path: {path}")
join_room(user_uuid, namespace='/vscode')
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']
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))
class AgentNamespace(Namespace):
def on_login(self, data):
self.current_app = current_app
ex = current_app.extensions
uuid2username = ex["uuid2username"]
backboard_manager = ex["backboard_manager"]
data = json.loads(data)
user_uuid = data['user_uuid']
course_id = data['course_id']
lesson_name = data['lesson_name']
chapter_name = data['chapter_name']
user_id = uuid2username[user_uuid]
session['user_uuid'] = user_uuid
join_room(user_uuid, namespace='/agent') # 将该用户加入以user_id为名的room
print(f'User connected with session user_uuid: {user_uuid}')
user_uuid2chatmanager = ex["user_uuid2chatmanager"]
user_uuid2ase_client = ex["user_uuid2ase_client"]
# if (user_uuid2ase_client[user_uuid] is not None and user_uuid2ase_client[user_uuid].connected):
# user_uuid2ase_client[user_uuid].disconnect()
chatmanager = user_uuid2chatmanager[user_uuid]
chatmanager.function_manager.add_function(judge, {'course_id': course_id, 'root_path': f'/home/{user_id}/{course_id}/{chapter_name}/{lesson_name}'})
chatmanager.function_manager.add_function(next_chapter, {'user_uuid': user_uuid})
chatmanager.chat_historys = []
chatmanager.scores = []
fmd,fmdp,fsp = load_full_markdown_file(course_id, chapter_name, lesson_name)
user_uuid2ase_client[user_uuid] = HSAEngineClient(current_app.config["ASE_ENGINE_URL"], current_app.config["ASE_ENGINE_NAMESPACE"],id=user_id, chatmanager = chatmanager)
chatmanager.init(user_uuid, user_uuid2ase_client[user_uuid], fmd, fmdp, fsp, course_id, chapter_name, lesson_name, max_iter=5, app=current_app, socketio=self)
backboard_manager.add_backboard(user_uuid, user_id, course_id, lesson_name, root_path=f'/home/{user_id}/{course_id}/{chapter_name}/{lesson_name}')
chatmanager.bb = backboard_manager.get_backboard(user_uuid)
chatmanager.next_chapter()
self.chatmanager = chatmanager
self.ase_client = user_uuid2ase_client[user_uuid]
user_uuid2ase_client[user_uuid].register_on_connect_entry(
callback=self.on_connect_to_ase,
entry=(self, user_uuid2ase_client[user_uuid]),
init_data={'room':user_uuid}
)
user_uuid2ase_client[user_uuid].register_route_with_entry(
route='dialog',
callback=self.receive_ase_dialog,
entry=self,
init_data={'room': user_uuid}
)
user_uuid2ase_client[user_uuid].register_route_with_entry(
route='dialog-hint',
callback=self.receive_ase_dialog_hint,
entry=self,
init_data={'room': user_uuid}
)
user_uuid2ase_client[user_uuid].register_route_with_entry(
route='judge',
callback=self.receive_ase_judge,
entry=self,
init_data={'room': user_uuid}
)
user_uuid2ase_client[user_uuid].register_route_with_entry(
route='next_chapter',
callback=self.receive_ase_next_chapter,
entry=self,
init_data={'room': user_uuid}
)
user_uuid2ase_client[user_uuid].register_route_with_entry(
route='chapter_score',
callback=self.receive_ase_chapter_score,
entry=(get_or_load_current_user(),chatmanager),
init_data={'room': user_uuid}
)
user_uuid2ase_client[user_uuid].connect()
def on_language(self, language):
ex = current_app.extensions
print(f"Language changed to: {language}")
def on_message(self, data):
print(f"Message from client: {data}")
ex = current_app.extensions
chatmanager = ex["user_uuid2chatmanager"]
uuid = session.get('user_uuid')
user_uuid2ase_client = ex["user_uuid2ase_client"]
if (type(data)==str):
data = json.loads(data)
if data['type'] == 'text':
res = user_uuid2ase_client[uuid].send_text('dialog', data['data'])
chatmanager[uuid].chat_historys.append({'role': 'user', 'content': data['data']})
print(f"Send text to route 'dialog' success: {res}")
if data['type'] == 'function': # 在这里 前端会发送允许执行的function与参数
print("function_call", data['data'])
d = data['data']
res = chatmanager[uuid].function_call(d['data']['name'], d['data']['args'])
d['data'] = res.to_dict()
print("function_call_res", d['data'])
emit(d['route'], d, room=uuid, namespace='/agent')
chatmanager[uuid].chat_historys.append({'role': 'user', 'content': data['data']})
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')
ase_client.chatmanager.load_next_chapter()
ase_client.send_text("chapter-start", "")
namespace_entry.emit('message', "Multi-Agents服务已连接", room=init_data['room'], namespace='/agent')
def receive_ase_dialog(client_entry: HSAEngineClient, namespace_entry: Namespace, data, init_data):
print("receive_ase_dialog", data)
client_entry.chatmanager.chat_historys.append({'role': 'assistant', 'content': data['data']})
namespace_entry.emit('message', data['data'], room=init_data['room'], namespace='/agent')
def receive_ase_dialog_hint(client_entry: HSAEngineClient, namespace_entry: Namespace, data, init_data):
print("receive_ase_dialog_hint", data)
namespace_entry.emit('message-hint', data['data'], room=init_data['room'], namespace='/agent')
def receive_ase_judge(client_entry: HSAEngineClient, namespace_entry: Namespace, data, init_data):
print("receive_ase_judge", data)
namespace_entry.emit('request_function', data, room=init_data['room'], namespace='/agent') # data 是一个列表 /dict也支持

View File

@@ -0,0 +1,133 @@
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>Document</title>
<!-- 你的其他样式 -->
<script src="https://cdn.jsdelivr.net/npm/mathjax@3/es5/tex-mml-chtml.js"></script>
<script>
MathJax.config = {
tex: {
inlineMath: [['$', '$'], ['\(', '\)']]
},
svg: {
fontCache: 'global'
}
};
</script>
</head>
<body>
<h1>效率的重要性与实践验证</h1>
<h2>效率</h2>
<h3>理论热身:算法选择的智慧</h3>
<h4>任务:分析与决策</h4>
<p>项目组目前有两套备选的交通信号灯同步算法,它们将在不同性能的服务器上运行:</p>
<ul>
<li>
<p><strong>方案A</strong>部署在超级服务器上10^9 次运算/秒采用的是一种较为简单的算法处理n个路口需要 <eq>2n^2</eq> 次计算。</p>
</li>
<li>
<p><strong>方案B</strong>部署在普通服务器上10^7 次运算/秒但采用的是一种更优化的算法处理n个路口需要 <eq>50nlog_2n</eq> 次计算。</p>
</li>
</ul>
<p>你的项目经理右侧的Agent希望你通过分析来判断哪个方案更具前景。请与TA对话逐一回答以下问题。</p>
<h4>思考题</h4>
<p>与右侧的Agent对话回答以下问题</p>
<ol>
<li>
<p><strong>概念回顾</strong>:首先,请向你的“经理”解释,根据课程所学,一个合格的“算法”应具备哪些基本特征?</p>
</li>
<li>
<p><strong>小规模测试</strong>对于一个包含100个路口的小型城区n=100计算并说明方案A和方案B分别需要多长时间完成计算在这种情况下你会推荐哪个方案</p>
</li>
<li>
<p><strong>大规模应用</strong>现在我们需要为一座拥有100万个路口的大都市n=1,000,000进行规划。再次计算并说明两个方案的耗时。你的推荐会改变吗为什么</p>
</li>
<li>
<p><strong>总结陈词</strong>:综合以上分析,向你的“经理”总结:为什么一个更优的算法设计,其重要性远超硬件性能的提升? 这验证了课程中提到的哪个核心观点?</p>
</li>
</ol>
<h3>编程实践:验证算法的真实性能</h3>
<h4>任务:编码与分析</h4>
<p>理论分析让你认识到了算法效率的重要性。现在,你需要通过编程来亲身感受不同交通状况对同一算法性能的巨大影响。我们将以“插入排序”为例,来处理三种典型的交通流量数据,这分别对应算法分析中的<strong>最好</strong><strong>最坏</strong><strong>平均</strong>情况。</p>
<h5>题目:模拟交通流量排序</h5>
<p>实现插入排序算法,并验证其在处理“畅通无阻”(数据有序)、“交通大堵塞”(数据逆序)和“随机车流”(数据随机)三种模式时的效率差异。</p>
<h5>代码框架</h5>
<p>在代码编辑区,完成 <code>insertion_sort(arr)</code> 函数的实现后运行完整代码并与Agent讨论结果。
<strong>请创建任意文件,将下面代码写入到编辑器中</strong></p>
<pre><code class="language-python">import random
import time
def insertion_sort(arr):
&quot;&quot;&quot;
实现插入排序算法
参数arr: 待排序的交通数据数组(整数代表车辆通行次序)
返回: 排序后的数组
&quot;&quot;&quot;
# TODO: 请在此处实现你的插入排序逻辑
def generate_traffic_data(n):
&quot;&quot;&quot;
生成模拟交通数据
参数n: 数据规模(路口数量或监控点数量)
返回: 三种不同交通状况的数据
&quot;&quot;&quot;
random_data = [random.randint(0, 10**6) for _ in range(n)]
# 模拟“畅通无阻”:交通流按次序进入,数据基本有序 (Best Case)
best_case_data = sorted(random_data)
# 模拟“交通大堵塞”:疏散时情况完全反转,数据逆序 (Worst Case)
worst_case_data = sorted(random_data, reverse=True)
# 模拟“随机车流”:正常但无规律的交通状况 (Average Case)
average_case_data = random_data
return best_case_data, worst_case_data, average_case_data
def measure_performance(func, data):
&quot;&quot;&quot;
测量算法性能
参数func: 排序函数
参数data: 交通数据
返回: 执行时间(毫秒)
&quot;&quot;&quot;
start_time = time.perf_counter_ns()
func(data.copy()) # 使用副本避免影响其他测试
end_time = time.perf_counter_ns()
return (end_time - start_time) / 10**6 # 转换为毫秒
#测试不同规模的路口网络
network_sizes = [1000, 5000, 10000]
print(&quot;交通数据处理算法性能测试:&quot;)
for size in network_sizes:
best, worst, avg = generate_traffic_data(size)
time_best = measure_performance(insertion_sort, best)
time_worst = measure_performance(insertion_sort, worst)
time_avg = measure_performance(insertion_sort, avg)
print(f&quot;网络规模 n={size}:&quot;)
print(f&quot; - 畅通无阻 (Best Case): {time_best:.2f} ms&quot;)
print(f&quot; - 交通大堵塞 (Worst Case): {time_worst:.2f} ms&quot;)
print(f&quot; - 随机车流 (Average Case): {time_avg:.2f} ms&quot;)
</code></pre>
<h4>分析与讨论</h4>
<p>完成编程并得到输出后请与右侧Agent讨论以下问题以检验你的理解</p>
<ol>
<li>
<p><strong>结果分析</strong>当网络规模从1000增加到10000时“交通大堵塞”最坏情况的处理时间增长了大约多少倍这更符合O(n)线性还是O(n2)(二次)的增长模式?</p>
</li>
<li>
<p><strong>原因探究</strong>:为什么“畅通无阻”(最好情况)的处理速度如此之快?它的时间复杂度是什么?请结合你的代码逻辑来解释。</p>
</li>
<li>
<p><strong>实践应用</strong>:根据你的实验结果,你认为插入排序是否适合用于需要快速响应大规模交通拥堵的实时预警系统?为什么?</p>
</li>
<li>
<p><strong>融会贯通</strong>:结合第一关的理论分析和第二关的编程实验,你对“算法是解决问题的核心”这句话有了怎样更深的理解?</p>
</li>
</ol>
</body>
</html>

View File

@@ -0,0 +1,116 @@
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>Document</title>
<!-- 你的其他样式 -->
<script src="https://cdn.jsdelivr.net/npm/mathjax@3/es5/tex-mml-chtml.js"></script>
<script>
MathJax.config = {
tex: {
inlineMath: [['$', '$'], ['\(', '\)']]
},
svg: {
fontCache: 'global'
}
};
</script>
</head>
<body>
<h1>分治策略进阶与主方法</h1>
<blockquote>
<p>Auto-generated at 2025-09-22 15:44</p>
</blockquote>
<h2>分治策略进阶与主方法</h2>
<h3>第一关:理论学习:主方法 (Master Theorem)</h3>
<h4>任务:掌握主方法</h4>
<h5>场景介绍</h5>
<p>在分析归并排序时,我们通过递推关系<eq>T(n)=2T(n/2)+Θ(n)</eq>得出了 O(nlogn) 的结论。但每次都画递归树很繁琐。主方法为形如 T(n)=aT(n/b)+f(n) 的递推式提供了一个“公式化”的求解捷径。</p>
<h5>思考题</h5>
<p>请与右侧的Agent你的项目经理对话学习并应用主方法</p>
<ol>
<li>
<p><strong>回顾与识别</strong>:对于归并排序的递推式 <eq>T(n)=2T(n/2)+Θ(n)</eq>,请指出主方法公式中对应的 <code>a</code><code>b</code><code>f(n)</code> 分别是什么?</p>
</li>
<li>
<p><strong>核心比较</strong>:主方法的核心是比较 <code>f(n)</code><eq>nlog_ba</eq> 的大小。请计算出归并排序中<eq>nlog_ba</eq> 的值,并判断它与 <code>f(n)</code> 的关系符合主方法的哪种情况Case</p>
</li>
<li>
<p><strong>实战演练</strong>:现在,运用你学到的主方法,求解以下几种在算法分析中常见的递推式,并说明你分别应用了主方法的哪种情况。</p>
<ul>
<li>a. <eq>T(n)=2T(n/4)+\sqrt n</eq></li>
<li>b.<eq>T(n)=7T(n/2)+n^2</eq> (这是Strassen矩阵乘法算法的复杂度模型)</li>
<li>c. <eq>T(n)=7T(n/3)+n^2</eq></li>
</ul>
</li>
</ol>
<h3>第二关:编程实践:最大子数组问题</h3>
<h4>任务:编码与分析</h4>
<h5>场景介绍</h5>
<p>我们的任务是分析每日交通流量的<strong>变化数组</strong>(正数代表流量增加,负数代表减少),并找到哪一个<strong>连续时段</strong>的流量总增量最大。这在算法上被称为“最大子数组问题” 6666。例如对于流量变化数组<code>[13, -3, -25, 20, -3, -16, -23, 18, 20, -7, 12, -5, -22, 15, -4, 7]</code>总增量最大的连续时段是从第8天到第11天总增量为 <code>18 + 20 - 7 + 12 = 43</code>
虽然这个问题可以通过O(n2) 的暴力法求解,但我们将使用更高效的分治策略。</p>
<h5>题目:最大交通流量增量</h5>
<p>你需要实现一个分治算法来解决最大子数组问题。同时,为了对比,你也会实现一个暴力求解算法。</p>
<h5>代码框架</h5>
<p>在下方代码编辑区,完成 <code>find_maximum_subarray</code>(分治法)和 <code>find_max_crossing_subarray</code> 以及 <code>find_maximum_subarray_brute</code>(暴力法)三个函数。</p>
<pre><code class="language-python">import time
import random
def find_maximum_subarray_brute(arr):
&quot;&quot;&quot;
使用暴力法求解最大子数组问题
返回: (最大和, 开始索引, 结束索引)
&quot;&quot;&quot;
#--- 本周任务:请在下方实现分治算法 ---
def find_max_crossing_subarray(arr, low, mid, high):
&quot;&quot;&quot;
找到跨越中点的最大子数组
返回: (最大和, 开始索引, 结束索引)
&quot;&quot;&quot;
# TODO: 实现寻找跨越中点的最大子数组的逻辑
# 提示: 从mid向左和向右分别扫描找到各自的最大和
def find_maximum_subarray(arr, low, high):
&quot;&quot;&quot;
使用分治法求解最大子数组问题
返回: (最大和, 开始索引, 结束索引)
&quot;&quot;&quot;
# TODO: 实现分治递归逻辑
# 提示: 递归基是当数组只有一个元素时
#--- 测试与对比部分 ---
traffic_changes = [13, -3, -25, 20, -3, -16, -23, 18, 20, -7, 12, -5, -22, 15, -4, 7]
print(f&quot;交通流量变化数据: {traffic_changes}&quot;)
#使用分治法
max_sum_dc, start_dc, end_dc = find_maximum_subarray(traffic_changes, 0, len(traffic_changes) - 1)
print(f&quot;分治法结果: 最大增量 = {max_sum_dc}, 时段 = Day {start_dc} to Day {end_dc}&quot;)
#使用暴力法验证
max_sum_brute, start_brute, end_brute = find_maximum_subarray_brute(traffic_changes)
print(f&quot;暴力法结果: 最大增量 = {max_sum_brute}, 时段 = Day {start_brute} to Day {end_brute}&quot;)
#性能测试
large_data = [random.randint(-50, 50) for _ in range(2000)]
start_time = time.perf_counter()
find_maximum_subarray(large_data, 0, len(large_data) - 1)
dc_time = (time.perf_counter() - start_time) * 1000
print(f&quot;\n在 n=2000 的数据集上,分治法耗时: {dc_time:.2f} ms&quot;)
start_time = time.perf_counter()
find_maximum_subarray_brute(large_data)
brute_time = (time.perf_counter() - start_time) * 1000
print(f&quot;在 n=2000 的数据集上,暴力法耗时: {brute_time:.2f} ms&quot;)</code></pre>
</body>
</html>

View File

@@ -7,7 +7,9 @@
display: flex;
flex-direction: column; /* 使子元素垂直排列 */
}
#leftSidebar {
padding-left: 3px;
}
body {
font-family: Arial, sans-serif;
display: flex;

View File

@@ -26,6 +26,17 @@ document.addEventListener('DOMContentLoaded', function() {
// 滚动到最新消息
document.getElementById('chatbox').scrollTop = document.getElementById('chatbox').scrollHeight;
});
socket.on('message-hint', function(data){
console.log('get message-hint');
// 显示服务器的回复消息
const serverMessage = document.createElement('div');
serverMessage.innerHTML = `<div id="hint-title"><b>提示</b></div><div id="hint-message" class="chat-message server-message"> ${data}</div>`;
document.getElementById('chatbox').appendChild(serverMessage);
// 滚动到最新消息
document.getElementById('chatbox').scrollTop = document.getElementById('chatbox').scrollHeight;
});
socket.on('request_function', function(data){
try {
console.log("request_function", data);

View File

@@ -3,8 +3,10 @@ from flask import Blueprint, jsonify, current_app, send_from_directory
from ..services.markdown_service import (
convert_markdown_to_html, wrap_with_styles,
save_html, copy_images, load_markdown_file
)
from markdown_it import MarkdownIt
from mdit_py_plugins import texmath
bp = Blueprint("markdown", __name__)
@@ -12,15 +14,17 @@ bp = Blueprint("markdown", __name__)
def convert_md(course_id, chapter_name, lesson_name):
print(f"convert_md: {course_id}, {chapter_name}, {lesson_name}")
md_file = load_markdown_file(course_id, chapter_name, lesson_name)
# 转换 markdown -> html
html = convert_markdown_to_html(md_file)
# 配置Markdown解析器,启用数学公式支持
md = MarkdownIt()
md.use(texmath.texmath_plugin) # 添加数学公式插件
# 转换markdown为html包含公式标记
html = md.render(md_file)
# 包装样式时添加MathJax支持用于在浏览器中渲染公式
html_with_styles = wrap_with_styles(html)
# 保存 HTML 文件到 static
html_output_path = os.path.join(current_app.config["STATIC_DIR"], f"{chapter_name}-{lesson_name}.html")
# 保存HTML文件到static
html_output_path = os.path.join(current_app.config["STATIC_DIR"], f"{course_id}-{chapter_name}-{lesson_name}.html")
save_html(html_with_styles, html_output_path)
return jsonify({"html_url": f"/static/{chapter_name}-{lesson_name}.html"})
return jsonify({"html_url": f"/static/{course_id}-{chapter_name}-{lesson_name}.html"})
# 提供静态文件访问
@bp.route("/static/<path:filename>")
@@ -28,3 +32,34 @@ def serve_static(filename):
return send_from_directory(current_app.config["STATIC_DIR"], filename)
def wrap_with_styles(html_content):
# 在HTML中添加MathJax支持
mathjax_script = """
<script src="https://cdn.jsdelivr.net/npm/mathjax@3/es5/tex-mml-chtml.js"></script>
<script>
MathJax.config = {
tex: {
inlineMath: [['$', '$'], ['\\(', '\\)']]
},
svg: {
fontCache: 'global'
}
};
</script>
"""
# 包装HTML内容和样式
return f"""
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>Document</title>
<!-- 你的其他样式 -->
{mathjax_script}
</head>
<body>
{html_content}
</body>
</html>
"""

View File

@@ -9,3 +9,5 @@ flask_sqlalchemy
flask-cors
pydantic
psutil
markdown-it-py
mdit-py-plugins