增加实时在线人数显示功能
This commit is contained in:
@@ -65,6 +65,48 @@ API_TYPE = "deepseek" # API 类型: "deepseek" 或 "gemini"
|
||||
# ==================================================
|
||||
import json
|
||||
import os
|
||||
import time
|
||||
from datetime import datetime
|
||||
from threading import Lock
|
||||
|
||||
# ==================== 在线人数统计 ====================
|
||||
# 全局变量:追踪活跃用户
|
||||
active_users = {} # 存储用户最后活跃时间 {session_id: timestamp}
|
||||
users_lock = Lock() # 线程锁,保证并发安全
|
||||
TIMEOUT_SECONDS = 300 # 5分钟无活动视为离线
|
||||
|
||||
def get_active_users_count():
|
||||
"""获取当前活跃用户数量"""
|
||||
current_time = time.time()
|
||||
with users_lock:
|
||||
# 移除超时用户
|
||||
expired_users = [uid for uid, last_time in active_users.items()
|
||||
if current_time - last_time > TIMEOUT_SECONDS]
|
||||
for uid in expired_users:
|
||||
del active_users[uid]
|
||||
return len(active_users)
|
||||
|
||||
def update_user_activity(session_id):
|
||||
"""更新用户活跃时间"""
|
||||
with users_lock:
|
||||
active_users[session_id] = time.time()
|
||||
|
||||
def get_online_status_html():
|
||||
"""生成在线人数显示的HTML"""
|
||||
count = get_active_users_count()
|
||||
return f"""
|
||||
<div style='display: inline-flex; align-items: center; gap: 8px;
|
||||
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
|
||||
padding: 8px 16px; border-radius: 20px;
|
||||
box-shadow: 0 4px 12px rgba(102, 126, 234, 0.3);'>
|
||||
<span style='font-size: 1.2em;'>🌐</span>
|
||||
<span style='color: white; font-weight: 600; font-size: 1em;'>在线人数:</span>
|
||||
<span style='color: #fff; font-weight: 700; font-size: 1.2em;
|
||||
background: rgba(255,255,255,0.2); padding: 2px 12px;
|
||||
border-radius: 12px; min-width: 30px; text-align: center;'>{count}</span>
|
||||
</div>
|
||||
"""
|
||||
# ==================================================
|
||||
|
||||
# --- [FIXED] 辅助函数:将系数数组转换为LaTeX数学公式字符串 ---
|
||||
def coeffs_to_latex(coeffs, var='s'):
|
||||
@@ -116,30 +158,32 @@ def coeffs_to_latex(coeffs, var='s'):
|
||||
|
||||
|
||||
# --- 功能函数1:显示传递函数 ---
|
||||
def display_transfer_function(num_str, den_str):
|
||||
def display_transfer_function(num_str, den_str, session_id):
|
||||
update_user_activity(session_id)
|
||||
try:
|
||||
num_str_cleaned = re.sub(r'[^0-9,\-.]', '', num_str)
|
||||
den_str_cleaned = re.sub(r'[^0-9,\-.]', '', den_str)
|
||||
num_coeffs = np.array([float(n) for n in num_str_cleaned.split(',') if n])
|
||||
den_coeffs = np.array([float(d) for d in den_str_cleaned.split(',') if d])
|
||||
if num_coeffs.size == 0 or den_coeffs.size == 0:
|
||||
return "分子或分母不能为空"
|
||||
return "分子或分母不能为空", get_online_status_html()
|
||||
num_latex = coeffs_to_latex(num_coeffs)
|
||||
den_latex = coeffs_to_latex(den_coeffs)
|
||||
tf_latex = f"$$ G(s) = \\frac{{{num_latex}}}{{{den_latex}}} $$"
|
||||
return tf_latex
|
||||
return tf_latex, get_online_status_html()
|
||||
except Exception as e:
|
||||
return f"输入格式错误: {e}"
|
||||
return f"输入格式错误: {e}", get_online_status_html()
|
||||
|
||||
# --- 功能函数2:执行时域分析和绘图 ---
|
||||
def time_domain_analysis(num_str, den_str):
|
||||
def time_domain_analysis(num_str, den_str, session_id):
|
||||
update_user_activity(session_id)
|
||||
try:
|
||||
num_str_cleaned = re.sub(r'[^0-9,\-.]', '', num_str)
|
||||
den_str_cleaned = re.sub(r'[^0-9,\-.]', '', den_str)
|
||||
num = np.array([float(n) for n in num_str_cleaned.split(',') if n])
|
||||
den = np.array([float(d) for d in den_str_cleaned.split(',') if d])
|
||||
if num.size == 0 or den.size == 0:
|
||||
return None, "错误:分子或分母系数不能为空。"
|
||||
return None, "错误:分子或分母系数不能为空。", get_online_status_html()
|
||||
system = ct.TransferFunction(num, den)
|
||||
t = np.linspace(0, 15, 1000)
|
||||
T_step, yout_step = ct.step_response(system, T=t)
|
||||
@@ -170,12 +214,13 @@ def time_domain_analysis(num_str, den_str):
|
||||
|
||||
plt.tight_layout()
|
||||
|
||||
return fig, metrics_text
|
||||
return fig, metrics_text, get_online_status_html()
|
||||
except Exception as e:
|
||||
return None, f"错误: {e}\n请检查您的输入。"
|
||||
return None, f"错误: {e}\n请检查您的输入。", get_online_status_html()
|
||||
|
||||
# --- 功能函数3:执行频域分析和绘图 ---
|
||||
def frequency_domain_analysis(num_str, den_str, k):
|
||||
def frequency_domain_analysis(num_str, den_str, k, session_id):
|
||||
update_user_activity(session_id)
|
||||
try:
|
||||
num_str_cleaned = re.sub(r'[^0-9,\-.]', '', num_str)
|
||||
den_str_cleaned = re.sub(r'[^0-9,\-.]', '', den_str)
|
||||
@@ -224,7 +269,8 @@ def frequency_domain_analysis(num_str, den_str, k):
|
||||
return None, f"错误: {e}", "", ""
|
||||
|
||||
# --- 功能函数4:执行根轨迹分析 ---
|
||||
def root_locus_analysis(num_str, den_str, log_k):
|
||||
def root_locus_analysis(num_str, den_str, log_k, session_id):
|
||||
update_user_activity(session_id)
|
||||
try:
|
||||
k = 10**log_k
|
||||
num_str_cleaned = re.sub(r'[^0-9,\-.]', '', num_str)
|
||||
@@ -265,11 +311,12 @@ def root_locus_analysis(num_str, den_str, log_k):
|
||||
|
||||
# --- [新增] 功能函数5: AI 智能问答 (支持 DeepSeek 和 Gemini) ---
|
||||
# [已修复] 兼容新版 Gradio 的 Chatbot 格式
|
||||
async def chat_with_ai(message, history):
|
||||
async def chat_with_ai(message, history, session_id):
|
||||
"""
|
||||
与 AI 模型进行流式对话。支持 DeepSeek 和 Gemini API。
|
||||
使用新版 Gradio 的 'messages' 格式。
|
||||
"""
|
||||
update_user_activity(session_id)
|
||||
# 系统指令
|
||||
system_prompt = "你是一位精通自动控制原理的专家教授。请用清晰、准确、专业的中文来回答有关自动控制课程内容的问题。在适当的时候,可以使用公式和示例来辅助解释。"
|
||||
|
||||
@@ -948,10 +995,19 @@ button[variant="secondary"]:hover {
|
||||
"""
|
||||
|
||||
with gr.Blocks(title="自动控制理论学习网站 - AI+数智平台", css=custom_css) as demo:
|
||||
# 用户会话ID(隐藏组件)
|
||||
session_id = gr.State(value=lambda: str(time.time()) + "_" + str(hash(time.time())))
|
||||
|
||||
# 主标题 - 带动画效果
|
||||
gr.HTML("<h1 class='main-title'> 自动控制理论AI+数智平台</h1>")
|
||||
gr.HTML("<p class='subtitle'>✨ 交互式控制系统分析与设计工具 | 时域·频域·根轨迹·AI问答 ✨</p>")
|
||||
|
||||
# 在线人数显示
|
||||
online_counter = gr.HTML(value=get_online_status_html(), elem_id="online-counter")
|
||||
|
||||
# 定时器:每10秒触发一次更新(隐藏组件)
|
||||
timer = gr.Timer(value=10, active=True)
|
||||
|
||||
# 项目信息横幅 - 优化对比度和可读性
|
||||
gr.HTML("""
|
||||
<div class='project-info-banner'>
|
||||
@@ -1966,7 +2022,6 @@ with gr.Blocks(title="自动控制理论学习网站 - AI+数智平台", css=cus
|
||||
chatbot = gr.Chatbot(
|
||||
label="🎓 自控原理AI助教",
|
||||
type="messages",
|
||||
bubble_full_width=False,
|
||||
avatar_images=(
|
||||
"https://img.icons8.com/fluency/96/user-male-circle.png",
|
||||
"https://img.icons8.com/fluency/96/chatbot.png"
|
||||
@@ -2032,54 +2087,103 @@ with gr.Blocks(title="自动控制理论学习网站 - AI+数智平台", css=cus
|
||||
|
||||
|
||||
# --- 事件绑定部分 ---
|
||||
confirm_button.click(fn=display_transfer_function, inputs=[num_input, den_input], outputs=tf_display)
|
||||
analyze_button.click(fn=time_domain_analysis, inputs=[num_input, den_input], outputs=[output_plot, output_metrics])
|
||||
# 包装函数:更新在线人数
|
||||
def update_online_counter_wrapper(func):
|
||||
def wrapper(*args, **kwargs):
|
||||
result = func(*args, **kwargs)
|
||||
# 如果返回的是元组,添加在线人数HTML
|
||||
if isinstance(result, tuple):
|
||||
return result + (get_online_status_html(),)
|
||||
return result, get_online_status_html()
|
||||
return wrapper
|
||||
|
||||
def update_frequency_analysis(num, den, log_k):
|
||||
confirm_button.click(
|
||||
fn=display_transfer_function,
|
||||
inputs=[num_input, den_input, session_id],
|
||||
outputs=[tf_display, online_counter]
|
||||
)
|
||||
|
||||
analyze_button.click(
|
||||
fn=time_domain_analysis,
|
||||
inputs=[num_input, den_input, session_id],
|
||||
outputs=[output_plot, output_metrics, online_counter]
|
||||
)
|
||||
|
||||
def update_frequency_analysis(num, den, log_k, sid):
|
||||
k = 10**log_k
|
||||
fig, metrics, tf_latex, stability = frequency_domain_analysis(num, den, k)
|
||||
return fig, metrics, tf_latex, stability, k
|
||||
fig, metrics, tf_latex, stability = frequency_domain_analysis(num, den, k, sid)
|
||||
return fig, metrics, tf_latex, stability, k, get_online_status_html()
|
||||
|
||||
log_k_slider_freq.release(fn=update_frequency_analysis, inputs=[num_input, den_input, log_k_slider_freq], outputs=[freq_plot_output, freq_metrics_display, freq_tf_display, freq_stability_display, k_number_display_freq])
|
||||
num_input.change(fn=update_frequency_analysis, inputs=[num_input, den_input, log_k_slider_freq], outputs=[freq_plot_output, freq_metrics_display, freq_tf_display, freq_stability_display, k_number_display_freq])
|
||||
den_input.change(fn=update_frequency_analysis, inputs=[num_input, den_input, log_k_slider_freq], outputs=[freq_plot_output, freq_metrics_display, freq_tf_display, freq_stability_display, k_number_display_freq])
|
||||
log_k_slider_freq.release(
|
||||
fn=update_frequency_analysis,
|
||||
inputs=[num_input, den_input, log_k_slider_freq, session_id],
|
||||
outputs=[freq_plot_output, freq_metrics_display, freq_tf_display, freq_stability_display, k_number_display_freq, online_counter]
|
||||
)
|
||||
|
||||
def update_rl_view(log_k, num, den):
|
||||
fig, poles, k_val = root_locus_analysis(num, den, log_k)
|
||||
return fig, poles, k_val
|
||||
num_input.change(
|
||||
fn=update_frequency_analysis,
|
||||
inputs=[num_input, den_input, log_k_slider_freq, session_id],
|
||||
outputs=[freq_plot_output, freq_metrics_display, freq_tf_display, freq_stability_display, k_number_display_freq, online_counter]
|
||||
)
|
||||
|
||||
log_k_slider_rl.release(fn=update_rl_view, inputs=[log_k_slider_rl, num_input, den_input], outputs=[rl_plot_output, rl_poles_display, k_number_display])
|
||||
num_input.change(fn=update_rl_view, inputs=[log_k_slider_rl, num_input, den_input], outputs=[rl_plot_output, rl_poles_display, k_number_display])
|
||||
den_input.change(fn=update_rl_view, inputs=[log_k_slider_rl, num_input, den_input], outputs=[rl_plot_output, rl_poles_display, k_number_display])
|
||||
den_input.change(
|
||||
fn=update_frequency_analysis,
|
||||
inputs=[num_input, den_input, log_k_slider_freq, session_id],
|
||||
outputs=[freq_plot_output, freq_metrics_display, freq_tf_display, freq_stability_display, k_number_display_freq, online_counter]
|
||||
)
|
||||
|
||||
def update_rl_view(log_k, num, den, sid):
|
||||
fig, poles, k_val = root_locus_analysis(num, den, log_k, sid)
|
||||
return fig, poles, k_val, get_online_status_html()
|
||||
|
||||
log_k_slider_rl.release(
|
||||
fn=update_rl_view,
|
||||
inputs=[log_k_slider_rl, num_input, den_input, session_id],
|
||||
outputs=[rl_plot_output, rl_poles_display, k_number_display, online_counter]
|
||||
)
|
||||
|
||||
num_input.change(
|
||||
fn=update_rl_view,
|
||||
inputs=[log_k_slider_rl, num_input, den_input, session_id],
|
||||
outputs=[rl_plot_output, rl_poles_display, k_number_display, online_counter]
|
||||
)
|
||||
|
||||
den_input.change(
|
||||
fn=update_rl_view,
|
||||
inputs=[log_k_slider_rl, num_input, den_input, session_id],
|
||||
outputs=[rl_plot_output, rl_poles_display, k_number_display, online_counter]
|
||||
)
|
||||
|
||||
# 聊天机器人事件处理 - 支持 DeepSeek 和 Gemini
|
||||
# 按钮点击事件
|
||||
send_button.click(
|
||||
fn=chat_with_ai,
|
||||
inputs=[chat_input, chatbot],
|
||||
inputs=[chat_input, chatbot, session_id],
|
||||
outputs=chatbot,
|
||||
).then(
|
||||
lambda: "", # 清空输入框
|
||||
outputs=chat_input
|
||||
lambda: ("", get_online_status_html()), # 清空输入框并更新在线人数
|
||||
outputs=[chat_input, online_counter]
|
||||
)
|
||||
|
||||
# 输入框回车事件
|
||||
chat_input.submit(
|
||||
fn=chat_with_ai,
|
||||
inputs=[chat_input, chatbot],
|
||||
inputs=[chat_input, chatbot, session_id],
|
||||
outputs=chatbot,
|
||||
).then(
|
||||
lambda: "", # 清空输入框
|
||||
outputs=chat_input
|
||||
lambda: ("", get_online_status_html()), # 清空输入框并更新在线人数
|
||||
outputs=[chat_input, online_counter]
|
||||
)
|
||||
|
||||
# 清空聊天记录
|
||||
clear_button.click(
|
||||
lambda: [],
|
||||
outputs=chatbot
|
||||
lambda sid: ([], get_online_status_html()),
|
||||
inputs=[session_id],
|
||||
outputs=[chatbot, online_counter]
|
||||
)
|
||||
|
||||
def on_tab_select(evt: gr.SelectData, num, den, log_k_freq, log_k_rl):
|
||||
def on_tab_select(evt: gr.SelectData, num, den, log_k_freq, log_k_rl, sid):
|
||||
update_user_activity(sid)
|
||||
outputs = {
|
||||
freq_plot_output: gr.update(),
|
||||
freq_metrics_display: gr.update(),
|
||||
@@ -2088,21 +2192,36 @@ with gr.Blocks(title="自动控制理论学习网站 - AI+数智平台", css=cus
|
||||
k_number_display_freq: gr.update(),
|
||||
rl_plot_output: gr.update(),
|
||||
rl_poles_display: gr.update(),
|
||||
k_number_display: gr.update()
|
||||
k_number_display: gr.update(),
|
||||
online_counter: get_online_status_html()
|
||||
}
|
||||
if evt.index == 1:
|
||||
k_freq = 10**log_k_freq
|
||||
fig, metrics, tf_latex, stability = frequency_domain_analysis(num, den, k_freq)
|
||||
fig, metrics, tf_latex, stability = frequency_domain_analysis(num, den, k_freq, sid)
|
||||
outputs[freq_plot_output], outputs[freq_metrics_display], outputs[freq_tf_display], outputs[freq_stability_display], outputs[k_number_display_freq] = fig, metrics, tf_latex, stability, k_freq
|
||||
elif evt.index == 2:
|
||||
fig, poles, k_val = root_locus_analysis(num, den, log_k_rl)
|
||||
fig, poles, k_val = root_locus_analysis(num, den, log_k_rl, sid)
|
||||
outputs[rl_plot_output], outputs[rl_poles_display], outputs[k_number_display] = fig, poles, k_val
|
||||
return outputs
|
||||
|
||||
tabs.select(
|
||||
on_tab_select,
|
||||
inputs=[num_input, den_input, log_k_slider_freq, log_k_slider_rl],
|
||||
outputs=[freq_plot_output, freq_metrics_display, freq_tf_display, freq_stability_display, k_number_display_freq, rl_plot_output, rl_poles_display, k_number_display]
|
||||
inputs=[num_input, den_input, log_k_slider_freq, log_k_slider_rl, session_id],
|
||||
outputs=[freq_plot_output, freq_metrics_display, freq_tf_display, freq_stability_display, k_number_display_freq, rl_plot_output, rl_poles_display, k_number_display, online_counter]
|
||||
)
|
||||
|
||||
# 页面加载时初始化在线人数
|
||||
demo.load(
|
||||
fn=lambda sid: (get_online_status_html(),),
|
||||
inputs=[session_id],
|
||||
outputs=[online_counter]
|
||||
)
|
||||
|
||||
# 定时器触发:每10秒更新一次在线人数
|
||||
timer.tick(
|
||||
fn=lambda sid: get_online_status_html(),
|
||||
inputs=[session_id],
|
||||
outputs=[online_counter]
|
||||
)
|
||||
|
||||
if __name__ == "__main__":
|
||||
|
||||
Reference in New Issue
Block a user