From 79e8480f3c4c56c4e8bbc688ef18e812c6905e7a Mon Sep 17 00:00:00 2001 From: hmo Date: Thu, 16 Jul 2026 22:25:17 +0800 Subject: [PATCH] =?UTF-8?q?fix:=20remove=20stale=20Loading=20span=20in=20I?= =?UTF-8?q?nfrastructure=20tab;=20add=20per-service=20=3F/=C2=A7=20for=20P?= =?UTF-8?q?latform=20(WeChat=20Bridge,=20API=20Proxy,=20article=5Fprocesso?= =?UTF-8?q?r)=20and=20Ejabberd=20with=20dedicated=20spec=20files?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- gateway/scripts/specs/api_proxy.json | 50 ++++++++++++ gateway/scripts/specs/article_processor.json | 54 +++++++++++++ gateway/scripts/specs/ejabberd.json | 47 ++++++++++++ gateway/scripts/specs/infra.json | 81 ++++++++++---------- gateway/scripts/specs/wechat_bridge.json | 48 ++++++++++++ gateway/scripts/templates/dashboard.html | 7 +- 6 files changed, 243 insertions(+), 44 deletions(-) create mode 100644 gateway/scripts/specs/api_proxy.json create mode 100644 gateway/scripts/specs/article_processor.json create mode 100644 gateway/scripts/specs/ejabberd.json create mode 100644 gateway/scripts/specs/wechat_bridge.json diff --git a/gateway/scripts/specs/api_proxy.json b/gateway/scripts/specs/api_proxy.json new file mode 100644 index 0000000..8266423 --- /dev/null +++ b/gateway/scripts/specs/api_proxy.json @@ -0,0 +1,50 @@ +{ + "module": "api_proxy", + "version": "1.0", + "purpose": "API Proxy 运行在 Windows (192.168.1.16:8787),为 VolcEngine(火山引擎)等外部 API 提供代理转发、重试和降级能力。", + "ui_location": "Infrastructure tab → Platform → API Proxy", + + "human_help": { + "title": "API Proxy(API 代理 :8787)", + "description": [ + "API Proxy 是一个轻量级反向代理,专门用于转发 VolcEngine(火山引擎)DeepSeek API 的请求。", + "主要功能:", + "• 请求代理——将内部请求转发到 VolcEngine 端点", + "• 自动重试——遇到网络抖动时自动重试", + "• 失败降级——主 API 不可用时自动切换到备用端点", + "简单说:它就是 AI API 的'中间人',保证请求能发出去、失败能重来。" + ], + "usage": [ + "监控状态:在 Infrastructure → Platform 下查看状态指示灯", + "如果状态为 stopped:AI 请求会直接失败", + "需要先确认 WeChat Bridge 正常运行" + ], + "troubleshooting": [ + "如果 API Proxy 停止:检查 Windows 上 api_proxy.py 进程是否在运行", + "排障命令:检查端口 8787 是否监听", + "重试策略:超时 5s,最多重试 2 次,失败后尝试备用端点" + ], + "related": "WeChat Bridge(依赖 API Proxy 访问 AI)、Ejabberd(底层通信)" + }, + + "ai_spec": { + "apis": [ + {"method": "POST", "path": "/v1/chat/completions", "returns": "OpenAI 兼容的 chat completion 响应"}, + {"method": "GET", "path": "/health", "returns": "{ok, status}"} + ], + "dependencies": [ + "依赖 VolcEngine(火山引擎)API 可用", + "依赖 Windows 网络连通性" + ], + "constraints": [ + "仅用于 AI 模型 API 调用,不代理其他流量", + "重试策略:5s 超时 + 2 次重试 + 备用端点自动切换", + "端口 8787 在 Windows 上监听" + ], + "related_files": [ + "gateway/scripts/api_proxy.py — 实际运行脚本", + "gateway/scripts/templates/dashboard.html — fI() 状态展示", + "gateway/scripts/specs/api_proxy.json — 本 spec 文件" + ] + } +} diff --git a/gateway/scripts/specs/article_processor.json b/gateway/scripts/specs/article_processor.json new file mode 100644 index 0000000..d9c6cbd --- /dev/null +++ b/gateway/scripts/specs/article_processor.json @@ -0,0 +1,54 @@ +{ + "module": "article_processor", + "version": "1.0", + "purpose": "文章抓取服务运行在 Windows (192.168.1.16:5810),负责抓取微信公众号全文链接并转换为纯文本,支持 OCR 图片识别。", + "ui_location": "Infrastructure tab → Platform → 文章抓取服务 (5810)", + + "human_help": { + "title": "文章抓取服务 (:5810)", + "description": [ + "这是整个系统的'阅读器'。当莫荷或小小莫需要阅读一个微信公众号文章链接时,由这个服务负责:", + "① 用 Chrome CDP 打开链接", + "② 等页面加载完成", + "③ 提取全文内容转为 Markdown", + "④ 如果包含图片,调用 GLM-OCR 识别图片文字", + "⑤ 返回结构化内容给请求方", + "依赖微信的 CDP session:如果微信未登录,抓取会失败('等待登录超时')。" + ], + "usage": [ + "监控状态:Infrastructure → Platform 下查看状态和最近抓取信息", + "如果 health_data 中有 error:表示最近一次抓取失败,查看 error 内容", + "点击服务旁的 § 查看 AI 接口详情" + ], + "troubleshooting": [ + "如果状态 stopped:检查 Windows 上 article_processor.py 进程", + "如果抓取失败返回 '等待登录超时':微信 CDP session 已过期,需要重新登录", + "如果图片 OCR 失败:检查 GLM-OCR 服务是否可用", + "日志位置:gateway/logs/article_processor.log" + ], + "related": "WeChat Bridge(依赖文章抓取服务处理微信中的链接)" + }, + + "ai_spec": { + "apis": [ + {"method": "GET", "path": "/health", "returns": "{ok, service, port, ocr_model, status}", "desc": "服务状态 + OCR 模型信息"}, + {"method": "GET", "path": "/logs?lines=N", "returns": "{ok, lines[]}", "desc": "最近 N 行日志"}, + {"method": "POST", "path": "/fetch", "body": "{\"url\":\"...\"}", "returns": "{ok, title, content, images[]}"} + ], + "dependencies": [ + "依赖 Chrome CDP session(微信登录态)", + "依赖 GLM-OCR 服务识别图片文字", + "依赖网络连通性访问微信公众号" + ], + "constraints": [ + "CDP session 过期后需要重新登录微信才能恢复", + "OCR 使用 GLM-OCR-8bit 模型", + "Linux 上访问 Windows 的此服务时需要 EasyTier VPN 连通" + ], + "related_files": [ + "gateway/scripts/article_processor.py — 实际运行脚本", + "gateway/scripts/templates/dashboard.html — fI() 状态展示(health_data)", + "gateway/scripts/specs/article_processor.json — 本 spec 文件" + ] + } +} diff --git a/gateway/scripts/specs/ejabberd.json b/gateway/scripts/specs/ejabberd.json new file mode 100644 index 0000000..4db6e5c --- /dev/null +++ b/gateway/scripts/specs/ejabberd.json @@ -0,0 +1,47 @@ +{ + "module": "ejabberd", + "version": "1.0", + "purpose": "Ejabberd XMPP 服务器运行在 Linux (246),是整个 Agent 通信系统的底层消息总线。所有的 Agent(xxm、mohe、zhiwei、xiaoguo)都通过它建立群聊(MUC)和私聊。", + "ui_location": "Infrastructure tab → Ejabberd", + + "human_help": { + "title": "Ejabberd(XMPP 服务器)", + "description": [ + "Ejabberd 是一个开源的 XMPP(Extensible Messaging and Presence Protocol)服务器。在这个系统中,它是所有 Agent 之间通信的底层基础设施。", + "每个 Agent 有一个 JID(Jabber ID,类似邮箱地址),例如 xxm@yoin.fun。Agent 们在 coregroup@conference.yoin.fun 群聊中收发消息。", + "Ejabberd 如果挂掉,所有 Agent 之间的消息都无法传递。dashboard 本身、WeChat Bridge、Hermes API 等都不依赖 Ejabberd(它们走 HTTP)。" + ], + "usage": [ + "监控状态:Infrastructure → Ejabberd 显示 ALIVE 或 DOWN", + "同时能看到当前在线的 JID 列表(online_jids)", + "如果显示 DOWN:必须立即登录 Linux 恢复" + ], + "troubleshooting": [ + "Ejabberd 挂掉时:ssh 到 246,运行 systemctl status ejabberd 查看状态", + "重启:sudo systemctl restart ejabberd", + "如果 Agent 连不上:检查 ejabberd 配置是否有对应的 JID 注册", + "端口 3021:客户端连接(C2S),开启 STARTTLS" + ], + "related": "F 健康 tab 查看 XMPP bot 连接状态、G 规范查看 Agent 通信协议" + }, + + "ai_spec": { + "apis": [ + {"method": "GET", "path": "/api/ejabberd", "returns": "{alive, xmpp_bot_connected, online_jids[], bot_jid}", "desc": "Ejabberd 存活状态 + 在线 JID 列表"} + ], + "dependencies": [ + "Ejabberd 本身不依赖其他服务", + "Agent 们依赖 Ejabberd 收发消息" + ], + "constraints": [ + "Dashboard 通过 HTTP API 检测 Ejabberd 健康(非 XMPP 协议直连)", + "online_jids 从 ejabberd API 获取(mod_admin_extra 需要开启)", + "xmpp_bot_connected 标识是否有至少一个 xmpp_agent_core 实例在线" + ], + "related_files": [ + "gateway/scripts/dashboard.py — /api/ejabberd 端点", + "gateway/scripts/templates/dashboard.html — fI() 状态展示", + "gateway/scripts/specs/ejabberd.json — 本 spec 文件" + ] + } +} diff --git a/gateway/scripts/specs/infra.json b/gateway/scripts/specs/infra.json index 082fa93..5bdf511 100644 --- a/gateway/scripts/specs/infra.json +++ b/gateway/scripts/specs/infra.json @@ -1,70 +1,67 @@ { "module": "infra", "version": "1.0", - "purpose": "Dashboard 'Infrastructure' tab 展示基础设施状态:Platform 进程、Ejabberd XMPP 服务、EasyTier VPN、RDP 远程桌面隧道、OpenCode Go 用量监控。", - "ui_location": "主菜单 → Infrastructure tab", + "purpose": "Platform 服务总览:展示 Windows 上运行的三个核心服务(WeChat Bridge、API Proxy、文章抓取服务)的运行状态,每个服务可通过其旁边的 ?/§ 查看详细文档。", + "ui_location": "Infrastructure tab → Platform", "human_help": { - "title": "Infrastructure 总览", + "title": "Platform 服务概览", "description": [ - "本页面展示系统基础设施的运行状态,分为多个区域:", - "• Platform — Windows/Linux 平台基础进程状态", - "• Ejabberd — XMPP 服务器心跳", - "• EasyTier VPN — P2P VPN 内网开关(连接两台机器)", - "• RDP Remote Desktop — SSH 反向隧道远程桌面", - "• OpenCode Go Usage — 四个订阅账号的 API 用量监控" + "Platform 区域展示在 Windows (192.168.1.16) 上运行的三个核心基础设施服务:", + "• WeChat Bridge (:5801) — 微信消息桥接,连接微信 ↔ Hermes AI", + "• API Proxy (:8787) — API 代理,转发 VolcEngine DeepSeek 请求", + "• 文章抓取服务 (:5810) — 微信公众号全文抓取 + OCR", + "", + "每个服务右侧有单独的 ?(使用说明)和 §(AI Spec)按钮。", + "点击可查看该服务的详细文档、API 端点和排障指南。", + "", + "其他基础设施部分(EasyTier / RDP / Usage / Ejabberd)各自独立,在本 tab 下方。" ], "usage": [ - "1. 检查 Platform 和 Ejabberd 的基础运行状态", - "2. EasyTier: 先 Turn On 建立 VPN 连接,确认 Connected 后再操作 RDP", - "3. RDP: Enable 启动 SSH 反向隧道,Disable 关闭", - "4. Usage: 查看各账号用量,点击 Refresh Now 手动采集最新数据" + "状态指示:绿色圆点 = 运行中,红色圆点 = 已停止", + "点击服务旁的 ? 查看人类可读的说明文档", + "点击服务旁的 § 查看 AI 接口文档(API、依赖、约束)", + "如果服务 stopped:检查 Windows 上对应的进程是否在运行" ], "troubleshooting": [ - "EasyTier 和 RDP 有依赖关系 — RDP 需 EasyTier 先 Connected", - "Usage 数据自动每 30min 采集一次,也可手动 Refresh", - "如果某个区域一直 loading:检查对应后端 API 是否正常" + "如果所有服务都 stopped:Windows 可能关机了,或 wechat_agent 没启动", + "如果单个服务 stopped:检查该服务的具体进程", + "Dashboard 每 5-10s 自动刷新 Platform 状态" ], - "related": "F 健康 tab 查看服务级健康状态" + "related": "Ejabberd(底层通信总线)、F 健康 tab(系统整体健康)" }, "ai_spec": { "apis": [ - {"method": "GET", "path": "/api/platform", "returns": "[{name,status}] — 平台进程状态"}, - {"method": "GET", "path": "/api/ejabberd", "returns": "{alive, xmpp_bot_connected} — XMPP 服务器心跳"}, - {"method": "GET", "path": "/api/easytier", "returns": "{status:{windows,246}, virtual_ips:{windows,246}}"}, - {"method": "POST", "path": "/api/easytier/toggle", "body": "{action:start|stop}"}, - {"method": "GET", "path": "/api/rdp", "returns": "{rdp_enabled, tunnel_running, public_endpoint}"}, - {"method": "POST", "path": "/api/rdp/toggle", "body": "{action:start|stop}"}, - {"method": "GET", "path": "/api/usage", "returns": "{ok, accounts[{name,usage,limit}], last_refresh}"}, - {"method": "POST", "path": "/api/usage/refresh", "returns": "{ok}"} + {"method": "GET", "path": "/api/platform", "returns": "[{id,name,type,desc,status,health_data}] — 三个 Platform 服务的状态列表"} ], "dependencies": [ - "EasyTier/RDP 依赖 xmpp_bot 的 HTTP bridge (Windows:5807)", - "Usage 依赖 CDP proxy 在 Chrome tab 内执行 fetch", - "Platform 状态通过 subprocess 本地检测" + "Platform 服务运行在 Windows (192.168.1.16),Dashboard (Linux 246) 通过网络检测", + "WeChat Bridge 使用 health_url = http://192.168.1.16:5801/health", + "API Proxy 使用 port check (8787)", + "文章抓取服务使用 health_url = http://192.168.1.16:5810/health" ], "constraints": [ - "fI() 使用 create-once/update-state pattern — DOM 元素只创建一次,后续只更新 class/text", - "Platform 和 Ejabberd 部分在 stable wrapper (
) 中全量重渲染", - "EasyTier/RDP/Usage section 各自独立创建,互不影响", - "Usage 数据采集后 10-15s 自动刷新" - ], - "must_not": [ - "不要把 fI() 改成全量重渲染 — 会破坏 create-once pattern 导致 EasyTier/RDP 元素闪烁", - "不要删除 infra-pe wrapper — Platform 和 Ejabberd 内容全量重渲染,其他 section 增量更新" + "Dashboard 从 246 通过 HTTP/端口扫描检测 Windows 服务", + "如果 246 无法连通 16 上的端口,所有服务显示 stopped", + "Platform 和 Ejabberd 内容在 infra-pe wrapper 中全量重渲染" ], "tests": [ - {"id": "IN01", "name": "Platform 状态正常返回", "endpoint": "GET /api/platform"}, - {"id": "IN02", "name": "Ejabberd 心跳正常", "endpoint": "GET /api/ejabberd"}, - {"id": "IN03", "name": "EasyTier toggle 操作正常", "endpoint": "POST /api/easytier/toggle"}, - {"id": "IN04", "name": "RDP toggle 操作正常", "endpoint": "POST /api/rdp/toggle"}, - {"id": "IN05", "name": "Usage 数据正常采集和展示", "endpoint": "GET /api/usage"} + {"id": "IN01", "name": "Platform 状态正常返回", "endpoint": "GET /api/platform"} + ], + "related_modules": [ + {"module": "wechat_bridge", "relation": "Platform 核心服务 — 微信消息桥接"}, + {"module": "api_proxy", "relation": "Platform 核心服务 — API 代理转发"}, + {"module": "article_processor", "relation": "Platform 核心服务 — 文章抓取 + OCR"}, + {"module": "ejabberd", "relation": "XMPP 通信总线(独立于 Platform)"} ], "related_files": [ "gateway/scripts/templates/dashboard.html — fI() 渲染函数", - "gateway/scripts/dashboard.py — 所有 /api/infra* 端点", + "gateway/scripts/dashboard.py — /api/platform 端点", "gateway/scripts/specs/infra.json — 本 spec 文件", + "gateway/scripts/specs/wechat_bridge.json — WeChat Bridge 详细 spec", + "gateway/scripts/specs/api_proxy.json — API Proxy 详细 spec", + "gateway/scripts/specs/article_processor.json — 文章抓取服务详细 spec", "gateway/scripts/specs/easytier.json — EasyTier 详细 spec", "gateway/scripts/specs/rdp.json — RDP 详细 spec", "gateway/scripts/specs/usage_monitor.json — Usage monitor 详细 spec" diff --git a/gateway/scripts/specs/wechat_bridge.json b/gateway/scripts/specs/wechat_bridge.json new file mode 100644 index 0000000..cea89dc --- /dev/null +++ b/gateway/scripts/specs/wechat_bridge.json @@ -0,0 +1,48 @@ +{ + "module": "wechat_bridge", + "version": "1.0", + "purpose": "WeChat Bridge 服务运行在 Windows (192.168.1.16:5801),负责将微信消息桥接到 Hermes Gateway (246:8642),实现老爸在微信上与莫荷的双向通信。", + "ui_location": "Infrastructure tab → Platform → WeChat Bridge", + + "human_help": { + "title": "WeChat Bridge(微信桥接 :5801)", + "description": [ + "这是整个系统的核心通道。部署在 Windows 上,接收微信消息后通过 HTTP POST 转发到 Linux (246) 上的 Hermes API,处理完成后再通过 wxhelper 将回复发回微信。", + "简单说:老爸在微信上发消息 → WeChat Bridge 接收 → 传给 Hermes AI 处理 → 回复发回微信。", + "它是一切的起点——没了它,微信通道就断了。" + ], + "usage": [ + "监控状态:在 Infrastructure → Platform 下查看状态指示灯(绿=运行,红=停止)", + "如果状态为 stopped:需要检查 Windows 上的 wechat_agent.py 进程", + "启动脚本:start-bot-server.bat(自动启动 opencode serve + wechat_agent)" + ], + "troubleshooting": [ + "如果 WeChat Bridge 停止:先检查 Windows 上 wxhelper 是否注入成功", + "如果 Hermes 不回复:检查 Linux 上 Hermes Gateway (:8642) 是否存活", + "如果消息延迟:消息走 HTTP 桥,网络延迟通常 < 2s", + "如果启动了但状态还是 stopped:页面每 5-10s 自动刷新,等一会" + ], + "related": "Ejabberd(XMPP 底层通信)、F 健康 tab(系统整体健康)" + }, + + "ai_spec": { + "apis": [ + {"method": "GET", "path": "/health", "returns": "{ok, status} — 服务健康检查"} + ], + "dependencies": [ + "依赖 wxhelper DLL 注入微信 3.9.5.81", + "依赖 Hermes API (:8642) 存活才能处理消息", + "依赖 EasyTier VPN 打通网络(Windows ↔ Linux)" + ], + "constraints": [ + "只支持 3.9.5.81 x64 微信版本", + "注入 wxhelper 后如果微信重启需重新注入", + "Python 请用 3.10(Miniconda3 3.13 的 encodings 模块损坏)" + ], + "related_files": [ + "gateway/scripts/wechat_agent.py — 实际运行脚本", + "gateway/scripts/templates/dashboard.html — fI() 状态展示", + "gateway/scripts/specs/wechat_bridge.json — 本 spec 文件" + ] + } +} diff --git a/gateway/scripts/templates/dashboard.html b/gateway/scripts/templates/dashboard.html index 8678ab3..64a72e3 100644 --- a/gateway/scripts/templates/dashboard.html +++ b/gateway/scripts/templates/dashboard.html @@ -39,6 +39,8 @@ h1{font-size:32px;font-weight:600;color:var(--accent);margin-bottom:4px}.subtitl .st{display:inline-flex;align-items:center;gap:4px;padding:4px 12px;border-radius:12px;font-size:19px;background:#0d1117;border:1px solid var(--border)} .st .d{width:6px;height:6px;border-radius:50%}.st .d.running{background:var(--green)}.st .d.stopped{background:var(--dim)} .st .p{color:var(--accent);font-family:monospace;font-size:18px;margin-left:4px} +.i{display:flex;align-items:center;gap:6px;margin:4px 0} +.sn{flex:1;min-width:0} .lp{display:none;border-top:1px solid var(--border);padding:12px 16px;background:#0a0e14} .lp.open{display:block}.lh{font-size:18px;color:var(--dim);margin-bottom:8px;display:flex;justify-content:space-between} .lc{font:11px/1.6 "Cascadia Code",Consolas,monospace;color:var(--dim);max-height:300px;overflow-y:auto;background:#06080c;border:1px solid var(--border);border-radius:6px;padding:10px;white-space:pre-wrap} @@ -271,17 +273,18 @@ if(es)es.onchange=fK;if(ea)ea.onchange=fK;}catch(e){}} async function fI(){ try{ var ci=document.getElementById('ct-infra');if(!ci)return; + var ls=ci.querySelector('span.dim');if(ls)ls.remove(); /* --- Stable wrapper for Platform+Ejabberd (rebuilt each cycle, no flicker since no state to preserve) --- */ var pe=document.getElementById('infra-pe'); if(!pe){pe=document.createElement('div');pe.id='infra-pe';ci.appendChild(pe);} var h=''; try{var r=await fetch('/api/platform'),d=await r.json(); h+='

Platform?§

'; - for(var i=0;i'+esc(d[i].name)+'
';} + for(var i=0;i'+esc(s.name)+'?§
';} h+='
'; }catch(e){} try{var r2=await fetch('/api/ejabberd'),e=await r2.json();var ea=e.alive||e.xmpp_bot_connected; - h+='

Ejabberd

'+(ea?'ALIVE':'DOWN')+'
'; + h+='

Ejabberd?§

'+(ea?'ALIVE':'DOWN')+'
'; }catch(e2){} pe.innerHTML=h;