232 lines
12 KiB
Python
232 lines
12 KiB
Python
import gradio as gr
|
|||
|
|
from assets.knowledge_cards_html import (
|
||
|
|
TIME_DOMAIN_KNOWLEDGE,
|
||
|
|
FREQUENCY_DOMAIN_KNOWLEDGE,
|
||
|
|
ROOT_LOCUS_KNOWLEDGE
|
||
|
|
)
|
||
|
|
|
||
|
|
def create_header():
|
||
|
|
"""创建页面顶部的标题、横幅和在线计数器"""
|
||
|
|
gr.HTML("<h1 class='main-title'> 自动控制理论AI+数智平台</h1>")
|
||
|
|
gr.HTML("<p class='subtitle'>✨ 交互式控制系统分析与设计工具 | 时域·频域·根轨迹·AI问答 ✨</p>")
|
||
|
|
|
||
|
|
online_counter = gr.HTML(elem_id="online-counter")
|
||
|
|
|
||
|
|
gr.HTML("""
|
||
|
|
<div class='project-info-banner'>
|
||
|
|
<div style='display: flex; flex-wrap: wrap; justify-content: center; align-items: center; gap: 25px;'>
|
||
|
|
<div style='display: flex; align-items: center; gap: 10px; background: rgba(255,255,255,0.1); padding: 12px 18px; border-radius: 12px; backdrop-filter: blur(10px);'>
|
||
|
|
<span style='font-size: 1.8em; filter: drop-shadow(0 2px 4px rgba(0,0,0,0.2));'>📚</span>
|
||
|
|
<div>
|
||
|
|
<div style='font-size: 0.8em; color: rgba(255,255,255,0.85); font-weight: 500; letter-spacing: 0.5px;'>课程 Course</div>
|
||
|
|
<div style='font-weight: 700; font-size: 1.15em; color: white; margin-top: 2px;'>自动控制理论</div>
|
||
|
|
</div>
|
||
|
|
</div>
|
||
|
|
|
||
|
|
<div style='display: flex; align-items: center; gap: 10px; background: rgba(255,255,255,0.1); padding: 12px 18px; border-radius: 12px; backdrop-filter: blur(10px);'>
|
||
|
|
<span style='font-size: 1.8em; filter: drop-shadow(0 2px 4px rgba(0,0,0,0.2));'>👨🏫</span>
|
||
|
|
<div>
|
||
|
|
<div style='font-size: 0.8em; color: rgba(255,255,255,0.85); font-weight: 500; letter-spacing: 0.5px;'>负责人 Supervisor</div>
|
||
|
|
<div style='font-weight: 700; font-size: 1.15em; color: white; margin-top: 2px;'>魏鹏飞</div>
|
||
|
|
</div>
|
||
|
|
</div>
|
||
|
|
|
||
|
|
<div style='display: flex; align-items: center; gap: 10px; background: rgba(255,255,255,0.1); padding: 12px 18px; border-radius: 12px; backdrop-filter: blur(10px);'>
|
||
|
|
<span style='font-size: 1.8em; filter: drop-shadow(0 2px 4px rgba(0,0,0,0.2));'>📧</span>
|
||
|
|
<div>
|
||
|
|
<div style='font-size: 0.8em; color: rgba(255,255,255,0.85); font-weight: 500; letter-spacing: 0.5px;'>联系方式 Contact</div>
|
||
|
|
<a href='mailto:pengfeiwei@nwpu.edu.cn'
|
||
|
|
style='font-weight: 700; font-size: 1.15em; color: white; text-decoration: none;
|
||
|
|
border-bottom: 2px solid rgba(255,255,255,0.5); padding-bottom: 2px; margin-top: 2px; display: inline-block;
|
||
|
|
transition: all 0.3s ease;'
|
||
|
|
onmouseover='this.style.borderColor="white"; this.style.transform="translateY(-1px)";'
|
||
|
|
onmouseout='this.style.borderColor="rgba(255,255,255,0.5)"; this.style.transform="translateY(0)";'>
|
||
|
|
pengfeiwei@nwpu.edu.cn
|
||
|
|
</a>
|
||
|
|
</div>
|
||
|
|
</div>
|
||
|
|
</div>
|
||
|
|
|
||
|
|
<div style='text-align: center; margin-top: 20px; padding-top: 18px; border-top: 2px solid rgba(255,255,255,0.2);'>
|
||
|
|
<div style='display: inline-flex; align-items: center; gap: 8px; background: rgba(255,255,255,0.08); padding: 10px 20px; border-radius: 25px; backdrop-filter: blur(5px);'>
|
||
|
|
<span style='font-size: 1.3em; filter: drop-shadow(0 2px 4px rgba(0,0,0,0.2));'>🎓</span>
|
||
|
|
<span style='font-weight: 600; font-size: 1.05em; color: white;'>西北工业大学</span>
|
||
|
|
<span style='color: white; opacity: 0.9; font-size: 1.05em;'>Northwestern Polytechnical University</span>
|
||
|
|
</div>
|
||
|
|
<div style='margin-top: 10px;'>
|
||
|
|
<span style='color: white; opacity: 0.9; font-size: 0.95em; font-weight: 500;'>2025年校级本科生建设项目资助</span>
|
||
|
|
</div>
|
||
|
|
</div>
|
||
|
|
</div>
|
||
|
|
""")
|
||
|
|
return online_counter
|
||
|
|
|
||
|
|
def create_time_domain_tab():
|
||
|
|
"""创建时域分析选项卡的UI组件"""
|
||
|
|
ui_dict = {}
|
||
|
|
with gr.Row():
|
||
|
|
with gr.Column(scale=1):
|
||
|
|
with gr.Group():
|
||
|
|
gr.HTML("<div class='card-title'>🔧 系统模型</div>")
|
||
|
|
ui_dict["tf_display"] = gr.Markdown(label="当前传递函数", elem_classes="output-display")
|
||
|
|
with gr.Row():
|
||
|
|
ui_dict["confirm_button"] = gr.Button("✓ 显示传递函数", variant="secondary", scale=1)
|
||
|
|
ui_dict["analyze_button"] = gr.Button("🚀 开始分析", variant="primary", scale=1, elem_classes="primary-btn")
|
||
|
|
|
||
|
|
with gr.Group():
|
||
|
|
gr.HTML("<div class='card-title'>📈 动态性能指标</div>")
|
||
|
|
ui_dict["output_metrics"] = gr.Textbox(
|
||
|
|
label="Performance Metrics", lines=8, interactive=False, elem_classes="output-metrics"
|
||
|
|
)
|
||
|
|
with gr.Column(scale=2):
|
||
|
|
ui_dict["output_plot"] = gr.Plot(label="时域响应曲线", elem_classes="plot-container")
|
||
|
|
# 知识卡片
|
||
|
|
gr.HTML(f"""
|
||
|
|
<div id="time-domain-knowledge" style="max-height: 600px; overflow-y: auto; padding-right: 8px;">
|
||
|
|
{TIME_DOMAIN_KNOWLEDGE}
|
||
|
|
</div>
|
||
|
|
<script>
|
||
|
|
(function() {{
|
||
|
|
// 等待DOM加载完成
|
||
|
|
if (document.readyState === 'loading') {{
|
||
|
|
document.addEventListener('DOMContentLoaded', renderMath);
|
||
|
|
}} else {{
|
||
|
|
renderMath();
|
||
|
|
}}
|
||
|
|
|
||
|
|
function renderMath() {{
|
||
|
|
// 延迟执行以确保MathJax已加载
|
||
|
|
setTimeout(function() {{
|
||
|
|
if (typeof MathJax !== 'undefined' && MathJax.typesetPromise) {{
|
||
|
|
MathJax.typesetPromise([document.getElementById('time-domain-knowledge')])
|
||
|
|
.catch((err) => console.log('MathJax渲染错误:', err));
|
||
|
|
}} else {{
|
||
|
|
console.log('MathJax未加载,将在500ms后重试');
|
||
|
|
setTimeout(renderMath, 500);
|
||
|
|
}}
|
||
|
|
}}, 300);
|
||
|
|
}}
|
||
|
|
}})();
|
||
|
|
</script>
|
||
|
|
""")
|
||
|
|
return ui_dict
|
||
|
|
|
||
|
|
def create_frequency_domain_tab():
|
||
|
|
"""创建频域分析选项卡的UI组件"""
|
||
|
|
ui_dict = {}
|
||
|
|
with gr.Row():
|
||
|
|
with gr.Column(scale=1):
|
||
|
|
with gr.Group():
|
||
|
|
gr.HTML("<div class='card-title'>🎚️ 调整系统增益</div>")
|
||
|
|
ui_dict["log_k_slider"] = gr.Slider(minimum=-4, maximum=4, value=1, step=0.01, label="对数增益 log₁₀(K)", info="💡 拖动滑块查看实时变化")
|
||
|
|
ui_dict["k_number_display"] = gr.Number(value=10.0, label="当前增益 K", interactive=False, elem_classes="gain-display")
|
||
|
|
with gr.Group():
|
||
|
|
gr.HTML("<div class='card-title'>🔧 当前系统模型</div>")
|
||
|
|
ui_dict["tf_display"] = gr.Markdown(label="含增益K的开环传递函数", elem_classes="output-display")
|
||
|
|
with gr.Group():
|
||
|
|
gr.HTML("<div class='card-title'>📊 稳定裕度分析</div>")
|
||
|
|
ui_dict["metrics_display"] = gr.Textbox(label="Stability Margins", lines=4, interactive=False, elem_classes="output-metrics")
|
||
|
|
ui_dict["stability_display"] = gr.Markdown(elem_classes="stability-result")
|
||
|
|
with gr.Column(scale=2):
|
||
|
|
ui_dict["plot_output"] = gr.Plot(label="频域响应图", elem_classes="plot-container")
|
||
|
|
# 知识卡片
|
||
|
|
gr.HTML(f"""
|
||
|
|
<div id="freq-domain-knowledge" style="max-height: 600px; overflow-y: auto; padding-right: 8px;">
|
||
|
|
{FREQUENCY_DOMAIN_KNOWLEDGE}
|
||
|
|
</div>
|
||
|
|
<script>
|
||
|
|
(function() {{
|
||
|
|
if (document.readyState === 'loading') {{
|
||
|
|
document.addEventListener('DOMContentLoaded', renderMath);
|
||
|
|
}} else {{
|
||
|
|
renderMath();
|
||
|
|
}}
|
||
|
|
|
||
|
|
function renderMath() {{
|
||
|
|
setTimeout(function() {{
|
||
|
|
if (typeof MathJax !== 'undefined' && MathJax.typesetPromise) {{
|
||
|
|
MathJax.typesetPromise([document.getElementById('freq-domain-knowledge')])
|
||
|
|
.catch((err) => console.log('MathJax渲染错误:', err));
|
||
|
|
}} else {{
|
||
|
|
console.log('MathJax未加载,将在500ms后重试');
|
||
|
|
setTimeout(renderMath, 500);
|
||
|
|
}}
|
||
|
|
}}, 300);
|
||
|
|
}}
|
||
|
|
}})();
|
||
|
|
</script>
|
||
|
|
""")
|
||
|
|
return ui_dict
|
||
|
|
|
||
|
|
def create_root_locus_tab():
|
||
|
|
"""创建根轨迹分析选项卡的UI组件"""
|
||
|
|
ui_dict = {}
|
||
|
|
with gr.Row():
|
||
|
|
with gr.Column(scale=1):
|
||
|
|
with gr.Group():
|
||
|
|
gr.HTML("<div class='card-title'>🎚️ 调整系统增益</div>")
|
||
|
|
ui_dict["log_k_slider"] = gr.Slider(minimum=-4, maximum=4, value=1, step=0.01, label="对数增益 log₁₀(K)", info="💡 拖动滑块观察极点移动")
|
||
|
|
ui_dict["k_number_display"] = gr.Number(value=10.0, label="当前增益 K", interactive=False, elem_classes="gain-display")
|
||
|
|
with gr.Group():
|
||
|
|
gr.HTML("<div class='card-title'>📍 闭环极点位置</div>")
|
||
|
|
ui_dict["poles_display"] = gr.Textbox(label="Closed-Loop Pole Locations", lines=6, interactive=False, elem_classes="output-metrics")
|
||
|
|
with gr.Column(scale=2):
|
||
|
|
ui_dict["plot_output"] = gr.Plot(label="根轨迹图")
|
||
|
|
gr.HTML(f"""
|
||
|
|
<div id="root-locus-knowledge" style="max-height: 600px; overflow-y: auto; padding-right: 8px;">
|
||
|
|
{ROOT_LOCUS_KNOWLEDGE}
|
||
|
|
</div>
|
||
|
|
<script>
|
||
|
|
(function() {{
|
||
|
|
if (document.readyState === 'loading') {{
|
||
|
|
document.addEventListener('DOMContentLoaded', renderMath);
|
||
|
|
}} else {{
|
||
|
|
renderMath();
|
||
|
|
}}
|
||
|
|
|
||
|
|
function renderMath() {{
|
||
|
|
setTimeout(function() {{
|
||
|
|
if (typeof MathJax !== 'undefined' && MathJax.typesetPromise) {{
|
||
|
|
MathJax.typesetPromise([document.getElementById('root-locus-knowledge')])
|
||
|
|
.catch((err) => console.log('MathJax渲染错误:', err));
|
||
|
|
}} else {{
|
||
|
|
console.log('MathJax未加载,将在500ms后重试');
|
||
|
|
setTimeout(renderMath, 500);
|
||
|
|
}}
|
||
|
|
}}, 300);
|
||
|
|
}}
|
||
|
|
}})();
|
||
|
|
</script>
|
||
|
|
""")
|
||
|
|
return ui_dict
|
||
|
|
|
||
|
|
def create_chatbot_tab():
|
||
|
|
"""创建AI问答选项卡的UI组件"""
|
||
|
|
ui_dict = {}
|
||
|
|
ui_dict["chatbot"] = gr.Chatbot(
|
||
|
|
label="🎓 自控原理AI助教",
|
||
|
|
type="messages",
|
||
|
|
avatar_images=("https://img.icons8.com/fluency/96/user-male-circle.png", "https://img.icons8.com/fluency/96/chatbot.png"),
|
||
|
|
height=650,
|
||
|
|
latex_delimiters=[
|
||
|
|
{"left": "$$", "right": "$$", "display": True},
|
||
|
|
{"left": "$", "right": "$", "display": False},
|
||
|
|
{"left": "\\[", "right": "\\]", "display": True},
|
||
|
|
{"left": "\\(", "right": "\\)", "display": False}
|
||
|
|
],
|
||
|
|
elem_classes="modern-chatbot",
|
||
|
|
show_copy_button=True
|
||
|
|
)
|
||
|
|
with gr.Row():
|
||
|
|
ui_dict["chat_input"] = gr.Textbox(label="", placeholder="💬 输入您的问题...", scale=4, lines=2, max_lines=4)
|
||
|
|
with gr.Column(scale=1, min_width=120):
|
||
|
|
ui_dict["send_button"] = gr.Button("📤 发送", variant="primary", size="lg")
|
||
|
|
ui_dict["clear_button"] = gr.Button("🗑️ 清空", variant="secondary", size="lg")
|
||
|
|
|
||
|
|
gr.Examples(
|
||
|
|
examples=["什么是传递函数?", "如何判断系统稳定性?", "解释Bode图的物理意义", "PID控制器各参数的作用"],
|
||
|
|
inputs=ui_dict["chat_input"],
|
||
|
|
label="💡 试试这些问题:"
|
||
|
|
)
|
||
|
|
return ui_dict
|