asemxin commited on
Commit
9553bb1
·
0 Parent(s):

init: MoltBotMKX - McKinsey Supply Chain Expert

Browse files
Files changed (7) hide show
  1. .gitignore +18 -0
  2. Dockerfile +29 -0
  3. LICENSE +21 -0
  4. README.md +35 -0
  5. SOUL.md +29 -0
  6. entrypoint.sh +248 -0
  7. status_page.py +285 -0
.gitignore ADDED
@@ -0,0 +1,18 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # 敏感配置文件
2
+ config.env
3
+
4
+ # OpenClaw 运行时
5
+ .openclaw/
6
+
7
+ # Node
8
+ node_modules/
9
+
10
+ # OS
11
+ .DS_Store
12
+ Thumbs.db
13
+
14
+ # IDE
15
+ .vscode/
16
+ .idea/
17
+ *.swp
18
+ *.swo
Dockerfile ADDED
@@ -0,0 +1,29 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ FROM node:24-slim
2
+
3
+ # 系统依赖
4
+ RUN apt-get update && apt-get install -y \
5
+ python3 python3-pip curl git procps lsof \
6
+ && rm -rf /var/lib/apt/lists/*
7
+
8
+ # Python 依赖
9
+ RUN pip3 install flask psutil --break-system-packages
10
+
11
+ # 安装 OpenClaw
12
+ RUN npm install -g openclaw@latest
13
+
14
+ # 创建目录
15
+ RUN mkdir -p /root/.openclaw/workspace /root/.openclaw/extensions /root/.openclaw/credentials
16
+
17
+ # 安装飞书插件
18
+ RUN openclaw plugins install feishu-openclaw 2>/dev/null || true
19
+ RUN cd /root/.openclaw/extensions/feishu-openclaw && npm install @sinclair/typebox 2>/dev/null || true
20
+
21
+ # 复制文件
22
+ COPY SOUL.md /root/.openclaw/workspace/SOUL.md
23
+ COPY status_page.py /app/status_page.py
24
+ COPY entrypoint.sh /app/entrypoint.sh
25
+ RUN chmod +x /app/entrypoint.sh
26
+
27
+ EXPOSE 7860
28
+
29
+ CMD ["/app/entrypoint.sh"]
LICENSE ADDED
@@ -0,0 +1,21 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ MIT License
2
+
3
+ Copyright (c) 2026
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
README.md ADDED
@@ -0,0 +1,35 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ ---
2
+ title: MoltBotMKX
3
+ emoji: 📊
4
+ colorFrom: blue
5
+ colorTo: green
6
+ sdk: docker
7
+ pinned: false
8
+ ---
9
+
10
+ # 📊 MoltBotMKX - 麦肯锡供应链大师
11
+
12
+ 基于 [OpenClaw](https://openclaw.ai) 的飞书 AI 助手,专注供应链战略咨询。
13
+
14
+ ## ✨ 功能
15
+
16
+ - 🔗 **飞书集成** — WebSocket 长连接,无需公网 IP
17
+ - 🧠 **麦肯锡供应链大师人设** — 20年咨询经验的供应链专家
18
+ - 📊 **状态监控** — 内置 Web 监控页面
19
+ - 🐳 **Docker 部署** — 一键部署到 HF Spaces
20
+
21
+ ## 🚀 部署
22
+
23
+ 在 HF Space Settings → Secrets 中添加:
24
+
25
+ | Secret | 说明 |
26
+ |---|---|
27
+ | `FEISHU_APP_ID` | 飞书 App ID |
28
+ | `FEISHU_APP_SECRET` | 飞书 App Secret |
29
+ | `API_BASE_URL` | AI 模型 API 地址 |
30
+ | `API_KEY` | API 密钥 |
31
+ | `MODEL_NAME` | 模型名称(如 `gemini-3-flash`) |
32
+
33
+ ## 📄 License
34
+
35
+ MIT
SOUL.md ADDED
@@ -0,0 +1,29 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # 麦肯锡供应链大师
2
+
3
+ 你是一位拥有20年麦肯锡咨询经验的供应链战略专家,曾服务全球500强企业,专注于供应链数字化转型、精益运营和端到端价值链优化。
4
+
5
+ ## 说话风格
6
+ - 结构化思维,MECE原则贯穿始终
7
+ - 先给结论再展开,金字塔原理表达
8
+ - 善用框架拆解复杂问题(波特五力、SCOR模型、牛鞭效应等)
9
+ - 数据驱动,言必有据,常引用行业基准数据
10
+ - 语气自信专业但不傲慢,像资深合伙人跟客户对话
11
+ - 偶尔用一句犀利洞察点醒对方
12
+
13
+ ## 常用表达
14
+ - "这个问题本质上是..."
15
+ - "从我服务过的案例来看..."
16
+ - "我们可以从三个维度来拆解..."
17
+ - "这里有一个关键的trade-off..."
18
+ - "底层逻辑是..."
19
+ - "如果用80/20法则来看..."
20
+
21
+ ## 回答原则
22
+ - 任何问题先用框架定位,再给出可落地的行动方案
23
+ - 供应链问题给出端到端视角,不只看局部优化
24
+ - 擅长将理论转化为可执行的1-2-3步骤
25
+ - 回答时融入真实行业案例(汽车、快消、制造、零售等)
26
+ - 关注ROI和商业价值,不空谈概念
27
+ - 遇到模糊问题会反问厘清scope,像咨询师做需求澄清
28
+ - 对数字化工具(SAP、Oracle、WMS、TMS、APS)信手拈来
29
+ - 敢于挑战传统思维,提出反直觉的洞察
entrypoint.sh ADDED
@@ -0,0 +1,248 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ #!/bin/bash
2
+ set -e
3
+
4
+ echo "🤖 MoltBot AI - Starting..."
5
+
6
+ # ============================================
7
+ # 从环境变量生成 OpenClaw 配置
8
+ # ============================================
9
+ FEISHU_APP_ID="${FEISHU_APP_ID:-}"
10
+ FEISHU_APP_SECRET="${FEISHU_APP_SECRET:-}"
11
+ API_BASE_URL="${API_BASE_URL:-https://asem12345-cliproxyapi.hf.space/v1}"
12
+ API_KEY="${API_KEY:-}"
13
+ MODEL_NAME="${MODEL_NAME:-gemini-3-flash}"
14
+
15
+ if [ -z "$FEISHU_APP_ID" ] || [ -z "$FEISHU_APP_SECRET" ]; then
16
+ echo "❌ 错误: 请设置 FEISHU_APP_ID 和 FEISHU_APP_SECRET 环境变量"
17
+ echo " 在 HF Space Settings → Secrets 中添加"
18
+ exit 1
19
+ fi
20
+
21
+ echo "📝 生成 OpenClaw 配置..."
22
+
23
+ # 生成 provider ID:从 URL 提取域名部分,加 custom- 前缀
24
+ # 例如 https://asem12345-cliproxyapi.hf.space/v1 → custom-asem12345-cliproxyapi-hf-space
25
+ PROVIDER_ID="custom-$(echo "$API_BASE_URL" | sed 's|https\?://||' | sed 's|/.*||' | sed 's|[^a-zA-Z0-9]|-|g' | sed 's|-*$||')"
26
+
27
+ OPENCLAW_DIR="$HOME/.openclaw"
28
+
29
+ # 创建必要目录
30
+ mkdir -p "$OPENCLAW_DIR/agents/main/sessions"
31
+ mkdir -p "$OPENCLAW_DIR/workspace"
32
+ chmod 700 "$OPENCLAW_DIR" 2>/dev/null || true
33
+
34
+ # 先写一个最小配置让 doctor 能跑
35
+ cat > "$OPENCLAW_DIR/openclaw.json" << JSONEOF
36
+ {
37
+ "gateway": {
38
+ "port": 18789,
39
+ "bind": "loopback",
40
+ "mode": "local"
41
+ },
42
+ "channels": {
43
+ "feishu": {
44
+ "enabled": true,
45
+ "appId": "${FEISHU_APP_ID}",
46
+ "appSecret": "${FEISHU_APP_SECRET}"
47
+ }
48
+ }
49
+ }
50
+ JSONEOF
51
+
52
+ echo "✅ 最小配置已生成"
53
+ echo " 飞书 App ID: ${FEISHU_APP_ID}"
54
+
55
+ # ============================================
56
+ # 运行 doctor --fix(自动安装飞书插件等)
57
+ # ============================================
58
+ echo "🔧 运行 doctor --fix..."
59
+ openclaw doctor --fix || true
60
+
61
+ # ============================================
62
+ # doctor 完成后,写入完整配置(包含自定义模型)
63
+ # doctor 有时会覆盖我们的配置,所以放在 doctor 之后
64
+ # ============================================
65
+ echo "📝 写入完整配置..."
66
+
67
+ # 用 python 合并配置(保留 doctor 添加的字段如 meta, wizard, plugins 等)
68
+ python3 << PYEOF
69
+ import json, os
70
+
71
+ config_path = os.path.expanduser("~/.openclaw/openclaw.json")
72
+
73
+ # 读取 doctor 生成的配置
74
+ try:
75
+ with open(config_path) as f:
76
+ config = json.load(f)
77
+ except:
78
+ config = {}
79
+
80
+ # 设置 gateway
81
+ config.setdefault("gateway", {})
82
+ config["gateway"]["port"] = 18789
83
+ config["gateway"]["bind"] = "loopback"
84
+ config["gateway"]["mode"] = "local"
85
+
86
+ # 设置自定义 provider
87
+ config.setdefault("models", {})
88
+ config["models"]["mode"] = "merge"
89
+ config["models"].setdefault("providers", {})
90
+ config["models"]["providers"]["${PROVIDER_ID}"] = {
91
+ "baseUrl": "${API_BASE_URL}",
92
+ "apiKey": "${API_KEY}",
93
+ "api": "openai-completions",
94
+ "models": [
95
+ {
96
+ "id": "${MODEL_NAME}",
97
+ "name": "${MODEL_NAME} (Custom Provider)",
98
+ "reasoning": False,
99
+ "input": ["text"],
100
+ "cost": {"input": 0, "output": 0, "cacheRead": 0, "cacheWrite": 0},
101
+ "contextWindow": 131072,
102
+ "maxTokens": 8192
103
+ }
104
+ ]
105
+ }
106
+
107
+ # 设置 agent defaults
108
+ config.setdefault("agents", {}).setdefault("defaults", {})
109
+ config["agents"]["defaults"]["model"] = {
110
+ "primary": "${PROVIDER_ID}/${MODEL_NAME}"
111
+ }
112
+ config["agents"]["defaults"].setdefault("models", {})
113
+ config["agents"]["defaults"]["models"]["${PROVIDER_ID}/${MODEL_NAME}"] = {}
114
+ config["agents"]["defaults"].setdefault("workspace", os.path.expanduser("~/.openclaw/workspace"))
115
+ config["agents"]["defaults"].setdefault("compaction", {"mode": "safeguard"})
116
+ config["agents"]["defaults"].setdefault("maxConcurrent", 4)
117
+
118
+ # 设置飞书 channel
119
+ config.setdefault("channels", {})
120
+ config["channels"]["feishu"] = {
121
+ "enabled": True,
122
+ "appId": "${FEISHU_APP_ID}",
123
+ "appSecret": "${FEISHU_APP_SECRET}"
124
+ }
125
+
126
+ with open(config_path, "w") as f:
127
+ json.dump(config, f, indent=2)
128
+
129
+ print(f"✅ 完整配置已写入 {config_path}")
130
+ print(f" 模型: ${PROVIDER_ID}/${MODEL_NAME}")
131
+
132
+ # 同时写入 agent 级别的 models.json(防止 fallback 到 anthropic)
133
+ agent_dir = os.path.expanduser("~/.openclaw/agents/main/agent")
134
+ os.makedirs(agent_dir, exist_ok=True)
135
+
136
+ agent_models = {
137
+ "providers": {
138
+ "github-copilot": {
139
+ "baseUrl": "https://api.individual.githubcopilot.com",
140
+ "models": []
141
+ },
142
+ "${PROVIDER_ID}": {
143
+ "baseUrl": "${API_BASE_URL}",
144
+ "apiKey": "${API_KEY}",
145
+ "api": "openai-completions",
146
+ "models": [
147
+ {
148
+ "id": "${MODEL_NAME}",
149
+ "name": "${MODEL_NAME} (Custom Provider)",
150
+ "reasoning": False,
151
+ "input": ["text"],
152
+ "cost": {"input": 0, "output": 0, "cacheRead": 0, "cacheWrite": 0},
153
+ "contextWindow": 131072,
154
+ "maxTokens": 8192
155
+ }
156
+ ]
157
+ }
158
+ }
159
+ }
160
+
161
+ models_path = os.path.join(agent_dir, "models.json")
162
+ with open(models_path, "w") as f:
163
+ json.dump(agent_models, f, indent=2)
164
+
165
+ # 确保 auth-profiles.json 存在(OpenClaw 查找的是这个文件)
166
+ auth_path = os.path.join(agent_dir, "auth-profiles.json")
167
+ with open(auth_path, "w") as f:
168
+ json.dump({}, f)
169
+
170
+ # 也写一份 auth.json 以防万一
171
+ auth2_path = os.path.join(agent_dir, "auth.json")
172
+ if not os.path.exists(auth2_path):
173
+ with open(auth2_path, "w") as f:
174
+ json.dump({}, f)
175
+
176
+ print(f"✅ Agent 配置已写入 {agent_dir}")
177
+
178
+ # 递归扫描所有 json 文件,替换 anthropic 引用
179
+ import glob
180
+ replaced = []
181
+ provider_id = "${PROVIDER_ID}"
182
+ model_id = "${MODEL_NAME}"
183
+ full_model = f"{provider_id}/{model_id}"
184
+
185
+ for fpath in glob.glob(os.path.expanduser("~/.openclaw/**/*.json"), recursive=True):
186
+ try:
187
+ with open(fpath) as f:
188
+ content = f.read()
189
+ if "anthropic" in content:
190
+ original = content
191
+ # 替换 model references
192
+ content = content.replace('"anthropic/claude-sonnet-4-20250514"', f'"{full_model}"')
193
+ content = content.replace('"anthropic/claude-3-5-sonnet"', f'"{full_model}"')
194
+ content = content.replace('"anthropic/claude-3-5-haiku"', f'"{full_model}"')
195
+ content = content.replace('"anthropic/claude-3-haiku"', f'"{full_model}"')
196
+ # 通用 anthropic provider 引用
197
+ content = content.replace('"anthropic"', f'"{provider_id}"')
198
+ if content != original:
199
+ with open(fpath, "w") as f:
200
+ f.write(content)
201
+ replaced.append(fpath)
202
+ except:
203
+ pass
204
+
205
+ if replaced:
206
+ print(f"⚠️ 替换了 {len(replaced)} 个文件中的 anthropic 引用:")
207
+ for r in replaced:
208
+ print(f" - {r}")
209
+ else:
210
+ print("✅ 未发现 anthropic 引用")
211
+
212
+ # 打印调试信息
213
+ print("\n🔍 调试 - openclaw.json:")
214
+ try:
215
+ with open(config_path) as f:
216
+ print(f.read()[:2000])
217
+ except:
218
+ print(" 无法读取")
219
+
220
+ print("\n🔍 调试 - agent 目录内容:")
221
+ for fpath in glob.glob(os.path.join(agent_dir, "*")):
222
+ print(f" {fpath}")
223
+ try:
224
+ with open(fpath) as f:
225
+ c = f.read()[:500]
226
+ print(f" {c}")
227
+ except:
228
+ pass
229
+
230
+ PYEOF
231
+
232
+ # ============================================
233
+ # 启动 OpenClaw Gateway(后台)
234
+ # ============================================
235
+ echo "🚀 启动 OpenClaw Gateway..."
236
+ openclaw gateway --force &
237
+ GATEWAY_PID=$!
238
+ echo " Gateway PID: $GATEWAY_PID"
239
+
240
+ # 等待网关启动
241
+ sleep 5
242
+
243
+ # ============================================
244
+ # 启动状态监控网页(前台,端口 7860)
245
+ # ============================================
246
+ echo "📊 启动状态监控网页 (端口 7860)..."
247
+ exec python3 /app/status_page.py
248
+
status_page.py ADDED
@@ -0,0 +1,285 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """MoltBot 状态监控网页"""
2
+
3
+ import json
4
+ import os
5
+ import subprocess
6
+ import time
7
+ from datetime import datetime, timedelta
8
+
9
+ import psutil
10
+ from flask import Flask, Response
11
+
12
+ app = Flask(__name__)
13
+ START_TIME = time.time()
14
+
15
+
16
+ def get_gateway_status():
17
+ """检查 OpenClaw Gateway 是否在运行"""
18
+ for proc in psutil.process_iter(["pid", "name", "cmdline"]):
19
+ try:
20
+ cmdline = " ".join(proc.info.get("cmdline") or [])
21
+ if "openclaw" in cmdline and "gateway" in cmdline:
22
+ return {"running": True, "pid": proc.info["pid"]}
23
+ except (psutil.NoSuchProcess, psutil.AccessDenied):
24
+ continue
25
+ return {"running": False, "pid": None}
26
+
27
+
28
+ def get_feishu_status():
29
+ """检查飞书连接状态(通过日志)"""
30
+ log_file = f"/tmp/openclaw/openclaw-{datetime.now().strftime('%Y-%m-%d')}.log"
31
+ if not os.path.exists(log_file):
32
+ return "unknown"
33
+ try:
34
+ result = subprocess.run(
35
+ ["tail", "-100", log_file],
36
+ capture_output=True, text=True, timeout=5
37
+ )
38
+ lines = result.stdout
39
+ if "[feishu]" in lines:
40
+ if "ws client ready" in lines or "Received from" in lines:
41
+ return "connected"
42
+ elif "auto-restart" in lines:
43
+ return "reconnecting"
44
+ return "initializing"
45
+ except Exception:
46
+ return "unknown"
47
+
48
+
49
+ def get_message_count():
50
+ """统计今日消息数"""
51
+ log_file = f"/tmp/openclaw/openclaw-{datetime.now().strftime('%Y-%m-%d')}.log"
52
+ if not os.path.exists(log_file):
53
+ return 0
54
+ try:
55
+ result = subprocess.run(
56
+ ["grep", "-c", "Received from", log_file],
57
+ capture_output=True, text=True, timeout=5
58
+ )
59
+ return int(result.stdout.strip() or 0)
60
+ except Exception:
61
+ return 0
62
+
63
+
64
+ def get_system_info():
65
+ """获取系统资源信息"""
66
+ uptime = timedelta(seconds=int(time.time() - START_TIME))
67
+ return {
68
+ "cpu_percent": psutil.cpu_percent(interval=0.5),
69
+ "memory": psutil.virtual_memory()._asdict(),
70
+ "uptime": str(uptime),
71
+ }
72
+
73
+
74
+ def format_bytes(b):
75
+ """格式化字节"""
76
+ for unit in ["B", "KB", "MB", "GB"]:
77
+ if b < 1024:
78
+ return f"{b:.1f} {unit}"
79
+ b /= 1024
80
+ return f"{b:.1f} TB"
81
+
82
+
83
+ STATUS_COLORS = {
84
+ "connected": "#00e676",
85
+ "reconnecting": "#ffc107",
86
+ "initializing": "#2196f3",
87
+ "unknown": "#9e9e9e",
88
+ }
89
+
90
+ STATUS_TEXT = {
91
+ "connected": "✅ 已连接",
92
+ "reconnecting": "🔄 重连中",
93
+ "initializing": "⏳ 初始化中",
94
+ "unknown": "❓ 未知",
95
+ }
96
+
97
+
98
+ @app.route("/")
99
+ def index():
100
+ gw = get_gateway_status()
101
+ feishu = get_feishu_status()
102
+ msgs = get_message_count()
103
+ sys_info = get_system_info()
104
+ mem = sys_info["memory"]
105
+
106
+ feishu_color = STATUS_COLORS.get(feishu, "#9e9e9e")
107
+ feishu_text = STATUS_TEXT.get(feishu, "❓ 未知")
108
+ gw_color = "#00e676" if gw["running"] else "#f44336"
109
+ gw_text = f"✅ 运行中 (PID {gw['pid']})" if gw["running"] else "❌ 已停止"
110
+
111
+ html = f"""<!DOCTYPE html>
112
+ <html lang="zh-CN">
113
+ <head>
114
+ <meta charset="UTF-8">
115
+ <meta name="viewport" content="width=device-width, initial-scale=1.0">
116
+ <meta http-equiv="refresh" content="30">
117
+ <title>MoltBot 状态监控</title>
118
+ <style>
119
+ * {{ margin: 0; padding: 0; box-sizing: border-box; }}
120
+ body {{
121
+ font-family: 'Segoe UI', -apple-system, BlinkMacSystemFont, sans-serif;
122
+ background: linear-gradient(135deg, #0f0c29, #302b63, #24243e);
123
+ min-height: 100vh;
124
+ color: #e0e0e0;
125
+ padding: 20px;
126
+ }}
127
+ .container {{ max-width: 800px; margin: 0 auto; }}
128
+ .header {{
129
+ text-align: center;
130
+ padding: 40px 20px 30px;
131
+ }}
132
+ .header h1 {{
133
+ font-size: 2.2em;
134
+ background: linear-gradient(135deg, #667eea, #764ba2);
135
+ -webkit-background-clip: text;
136
+ -webkit-text-fill-color: transparent;
137
+ margin-bottom: 8px;
138
+ }}
139
+ .header p {{ color: #9e9e9e; font-size: 0.95em; }}
140
+ .cards {{ display: grid; grid-template-columns: 1fr 1fr; gap: 16px; margin-top: 24px; }}
141
+ .card {{
142
+ background: rgba(255,255,255,0.06);
143
+ backdrop-filter: blur(12px);
144
+ border: 1px solid rgba(255,255,255,0.08);
145
+ border-radius: 16px;
146
+ padding: 24px;
147
+ transition: transform 0.2s, box-shadow 0.2s;
148
+ }}
149
+ .card:hover {{
150
+ transform: translateY(-2px);
151
+ box-shadow: 0 8px 32px rgba(102,126,234,0.15);
152
+ }}
153
+ .card-title {{
154
+ font-size: 0.85em;
155
+ color: #9e9e9e;
156
+ text-transform: uppercase;
157
+ letter-spacing: 1px;
158
+ margin-bottom: 12px;
159
+ }}
160
+ .card-value {{
161
+ font-size: 1.4em;
162
+ font-weight: 600;
163
+ }}
164
+ .status-dot {{
165
+ display: inline-block;
166
+ width: 10px; height: 10px;
167
+ border-radius: 50%;
168
+ margin-right: 8px;
169
+ animation: pulse 2s infinite;
170
+ }}
171
+ @keyframes pulse {{
172
+ 0%, 100% {{ opacity: 1; }}
173
+ 50% {{ opacity: 0.5; }}
174
+ }}
175
+ .card-full {{ grid-column: 1 / -1; }}
176
+ .stats-grid {{ display: grid; grid-template-columns: repeat(3, 1fr); gap: 12px; margin-top: 12px; }}
177
+ .stat {{ text-align: center; }}
178
+ .stat-value {{ font-size: 1.6em; font-weight: 700; color: #667eea; }}
179
+ .stat-label {{ font-size: 0.8em; color: #9e9e9e; margin-top: 4px; }}
180
+ .progress-bar {{
181
+ width: 100%;
182
+ height: 8px;
183
+ background: rgba(255,255,255,0.1);
184
+ border-radius: 4px;
185
+ margin-top: 8px;
186
+ overflow: hidden;
187
+ }}
188
+ .progress-fill {{
189
+ height: 100%;
190
+ border-radius: 4px;
191
+ transition: width 0.5s;
192
+ }}
193
+ .footer {{
194
+ text-align: center;
195
+ padding: 30px;
196
+ color: #616161;
197
+ font-size: 0.85em;
198
+ }}
199
+ @media (max-width: 600px) {{
200
+ .cards {{ grid-template-columns: 1fr; }}
201
+ .stats-grid {{ grid-template-columns: 1fr; }}
202
+ }}
203
+ </style>
204
+ </head>
205
+ <body>
206
+ <div class="container">
207
+ <div class="header">
208
+ <h1>🤖 MoltBot AI</h1>
209
+ <p>飞书智能助手 · 状态监控</p>
210
+ </div>
211
+
212
+ <div class="cards">
213
+ <div class="card">
214
+ <div class="card-title">OpenClaw 网关</div>
215
+ <div class="card-value">
216
+ <span class="status-dot" style="background:{gw_color}"></span>
217
+ {gw_text}
218
+ </div>
219
+ </div>
220
+
221
+ <div class="card">
222
+ <div class="card-title">飞书连接</div>
223
+ <div class="card-value">
224
+ <span class="status-dot" style="background:{feishu_color}"></span>
225
+ {feishu_text}
226
+ </div>
227
+ </div>
228
+
229
+ <div class="card card-full">
230
+ <div class="card-title">系统概览</div>
231
+ <div class="stats-grid">
232
+ <div class="stat">
233
+ <div class="stat-value">{sys_info['uptime']}</div>
234
+ <div class="stat-label">运行时长</div>
235
+ </div>
236
+ <div class="stat">
237
+ <div class="stat-value">{msgs}</div>
238
+ <div class="stat-label">今日消息</div>
239
+ </div>
240
+ <div class="stat">
241
+ <div class="stat-value">{os.environ.get('MODEL_NAME', 'N/A')}</div>
242
+ <div class="stat-label">当前模型</div>
243
+ </div>
244
+ </div>
245
+ </div>
246
+
247
+ <div class="card">
248
+ <div class="card-title">CPU 使用率</div>
249
+ <div class="card-value">{sys_info['cpu_percent']}%</div>
250
+ <div class="progress-bar">
251
+ <div class="progress-fill" style="width:{sys_info['cpu_percent']}%;background:linear-gradient(90deg,#667eea,#764ba2)"></div>
252
+ </div>
253
+ </div>
254
+
255
+ <div class="card">
256
+ <div class="card-title">内存使用</div>
257
+ <div class="card-value">{format_bytes(mem['used'])} / {format_bytes(mem['total'])}</div>
258
+ <div class="progress-bar">
259
+ <div class="progress-fill" style="width:{mem['percent']}%;background:linear-gradient(90deg,#00e676,#00c853)"></div>
260
+ </div>
261
+ </div>
262
+ </div>
263
+
264
+ <div class="footer">
265
+ 最后更新: {datetime.now().strftime('%Y-%m-%d %H:%M:%S')} · 自动刷新 30s
266
+ </div>
267
+ </div>
268
+ </body>
269
+ </html>"""
270
+ return Response(html, content_type="text/html; charset=utf-8")
271
+
272
+
273
+ @app.route("/health")
274
+ def health():
275
+ gw = get_gateway_status()
276
+ return json.dumps({
277
+ "status": "ok" if gw["running"] else "error",
278
+ "gateway": gw,
279
+ "feishu": get_feishu_status(),
280
+ "uptime": time.time() - START_TIME,
281
+ }), 200 if gw["running"] else 503
282
+
283
+
284
+ if __name__ == "__main__":
285
+ app.run(host="0.0.0.0", port=7860)