增加实时在线人数显示功能
This commit is contained in:
-171
@@ -1,171 +0,0 @@
|
||||
# 贡献指南
|
||||
|
||||
感谢您对本项目的关注!我们欢迎任何形式的贡献。
|
||||
|
||||
## 🤝 如何贡献
|
||||
|
||||
### 报告 Bug
|
||||
|
||||
如果您发现了 Bug,请:
|
||||
|
||||
1. 在 [Issues](https://github.com/your-repo/issues) 中搜索是否已有相同问题
|
||||
2. 如果没有,创建新 Issue,包含:
|
||||
- 清晰的标题
|
||||
- Bug 复现步骤
|
||||
- 预期行为 vs 实际行为
|
||||
- 系统环境(操作系统、Python 版本等)
|
||||
- 错误截图或日志(如果有)
|
||||
|
||||
### 提出新功能
|
||||
|
||||
如果您有新功能建议:
|
||||
|
||||
1. 创建 Feature Request Issue
|
||||
2. 描述功能的用途和价值
|
||||
3. 提供可能的实现思路(可选)
|
||||
|
||||
### 提交代码
|
||||
|
||||
#### 准备工作
|
||||
|
||||
1. Fork 本仓库
|
||||
2. 克隆到本地:
|
||||
```bash
|
||||
git clone https://github.com/your-username/AutoControl.git
|
||||
cd AutoControl
|
||||
```
|
||||
3. 创建新分支:
|
||||
```bash
|
||||
git checkout -b feature/your-feature-name
|
||||
```
|
||||
|
||||
#### 开发规范
|
||||
|
||||
**代码风格**:
|
||||
- 遵循 PEP 8 Python 代码规范
|
||||
- 使用有意义的变量名和函数名
|
||||
- 添加必要的注释和文档字符串
|
||||
|
||||
**提交信息**:
|
||||
```
|
||||
<type>(<scope>): <subject>
|
||||
|
||||
<body>
|
||||
|
||||
<footer>
|
||||
```
|
||||
|
||||
类型(type):
|
||||
- `feat`: 新功能
|
||||
- `fix`: Bug 修复
|
||||
- `docs`: 文档更新
|
||||
- `style`: 代码格式调整
|
||||
- `refactor`: 重构
|
||||
- `test`: 测试相关
|
||||
- `chore`: 构建/工具链相关
|
||||
|
||||
示例:
|
||||
```
|
||||
feat(analysis): 添加状态空间分析模块
|
||||
|
||||
- 实现状态空间表示转换
|
||||
- 添加可控性和可观测性判断
|
||||
- 集成到主界面标签页
|
||||
|
||||
Closes #123
|
||||
```
|
||||
|
||||
#### 测试
|
||||
|
||||
在提交前确保:
|
||||
- 代码能正常运行
|
||||
- 没有破坏现有功能
|
||||
- 新功能经过测试
|
||||
|
||||
#### 提交 Pull Request
|
||||
|
||||
1. 推送到您的 Fork:
|
||||
```bash
|
||||
git push origin feature/your-feature-name
|
||||
```
|
||||
2. 在 GitHub 上创建 Pull Request
|
||||
3. 填写 PR 描述:
|
||||
- 做了什么改动
|
||||
- 为什么需要这个改动
|
||||
- 如何测试
|
||||
4. 等待代码审查
|
||||
|
||||
## 📋 开发任务
|
||||
|
||||
### 当前优先级
|
||||
|
||||
#### 高优先级
|
||||
- [ ] 修复已知 Bug
|
||||
- [ ] 改进性能(大系统分析优化)
|
||||
- [ ] 移动端适配
|
||||
|
||||
#### 中优先级
|
||||
- [ ] 状态空间分析模块
|
||||
- [ ] PID 控制器调优工具
|
||||
- [ ] 系统对比功能
|
||||
|
||||
#### 低优先级
|
||||
- [ ] 导出分析报告
|
||||
- [ ] 多语言支持
|
||||
- [ ] 主题切换(暗色模式)
|
||||
|
||||
### 适合新手的任务
|
||||
|
||||
标记为 `good first issue` 的 Issue 适合新手参与:
|
||||
- 文档改进
|
||||
- UI 微调
|
||||
- 小功能添加
|
||||
|
||||
## 🎨 UI/UX 贡献
|
||||
|
||||
我们欢迎设计相关的贡献:
|
||||
- 界面美化
|
||||
- 交互优化
|
||||
- 图标设计
|
||||
- 配色方案
|
||||
|
||||
## 📚 文档贡献
|
||||
|
||||
文档同样重要:
|
||||
- 修正错别字
|
||||
- 改进说明
|
||||
- 添加示例
|
||||
- 翻译文档
|
||||
|
||||
## 💬 社区
|
||||
|
||||
- GitHub Issues: 技术讨论
|
||||
- [讨论区](https://github.com/your-repo/discussions): 一般交流
|
||||
|
||||
## ⚖️ 行为准则
|
||||
|
||||
### 我们的承诺
|
||||
|
||||
为建设开放友好的环境,我们贡献者和维护者承诺:
|
||||
|
||||
- 使用友好和包容的语言
|
||||
- 尊重不同的观点和经验
|
||||
- 优雅地接受建设性批评
|
||||
- 关注对社区最有利的事情
|
||||
|
||||
### 不可接受的行为
|
||||
|
||||
- 使用性暗示语言或图像
|
||||
- 挑衅、侮辱性评论,人身攻击
|
||||
- 公开或私下骚扰
|
||||
- 未经许可发布他人隐私信息
|
||||
|
||||
## 📄 许可证
|
||||
|
||||
贡献代码即表示您同意将您的贡献以 MIT 许可证授权。
|
||||
|
||||
---
|
||||
|
||||
**感谢您的贡献!** 🎉
|
||||
|
||||
如有任何问题,请随时联系维护者。
|
||||
@@ -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__":
|
||||
|
||||
@@ -0,0 +1,31 @@
|
||||
version: '3.8'
|
||||
|
||||
services:
|
||||
autocontrol:
|
||||
build: .
|
||||
container_name: autocontrol-app
|
||||
ports:
|
||||
- "7860:7860"
|
||||
restart: unless-stopped
|
||||
environment:
|
||||
- TZ=Asia/Shanghai
|
||||
- PYTHONUNBUFFERED=1
|
||||
# 可选:资源限制
|
||||
deploy:
|
||||
resources:
|
||||
limits:
|
||||
cpus: '2.0'
|
||||
memory: 2G
|
||||
reservations:
|
||||
cpus: '0.5'
|
||||
memory: 512M
|
||||
# 可选:日志持久化
|
||||
volumes:
|
||||
- ./logs:/app/logs
|
||||
# 健康检查
|
||||
healthcheck:
|
||||
test: ["CMD", "curl", "-f", "http://localhost:7860"]
|
||||
interval: 30s
|
||||
timeout: 10s
|
||||
retries: 3
|
||||
start_period: 40s
|
||||
Reference in New Issue
Block a user