Spaces:
Build error
Build error
| import json | |
| import re | |
| import textwrap | |
| from datetime import datetime | |
| import gradio as gr | |
| def _now() -> str: | |
| return datetime.utcnow().strftime("%Y-%m-%d %H:%M UTC") | |
| SENSITIVE_KEYS = { | |
| "apikey", | |
| "token", | |
| "password", | |
| "secret", | |
| "authorization", | |
| "bearer", | |
| "access_token", | |
| "refresh_token", | |
| } | |
| def _looks_sensitive_key(k: str) -> bool: | |
| lk = k.lower() | |
| return any(s in lk for s in SENSITIVE_KEYS) | |
| def _mask_value(v): | |
| if v is None: | |
| return v | |
| if isinstance(v, (int, float, bool)): | |
| return v | |
| s = str(v).strip() | |
| if not s: | |
| return s | |
| if len(s) <= 8: | |
| return "***" | |
| return s[:2] + "***" + s[-2:] | |
| def redact(obj): | |
| if isinstance(obj, dict): | |
| out = {} | |
| for k, v in obj.items(): | |
| if _looks_sensitive_key(str(k)): | |
| out[k] = _mask_value(v) | |
| else: | |
| out[k] = redact(v) | |
| return out | |
| if isinstance(obj, list): | |
| return [redact(x) for x in obj] | |
| return obj | |
| def explain_config(raw: str): | |
| raw = (raw or "").strip() | |
| if not raw: | |
| return "请粘贴 openclaw.json 内容。", "", "" | |
| try: | |
| cfg = json.loads(raw) | |
| except Exception as e: | |
| return f"JSON 解析失败:{e}", "", "" | |
| red = redact(cfg) | |
| red_json = json.dumps(red, ensure_ascii=False, indent=2) | |
| tips = [f"体检时间:{_now()}"] | |
| def has(path): | |
| cur = cfg | |
| for p in path: | |
| if not isinstance(cur, dict) or p not in cur: | |
| return False | |
| cur = cur[p] | |
| return True | |
| if has(["gateway", "auth", "token"]) and has(["gateway", "auth", "password"]) and not has( | |
| ["gateway", "auth", "mode"] | |
| ): | |
| tips.append( | |
| "风险:同时配置了 gateway.auth.token 和 gateway.auth.password,但未显式设置 gateway.auth.mode;可能导致鉴权行为不符合预期。" | |
| ) | |
| tips.append("建议:明确写 gateway.auth.mode(按你的实际需求选 token/password/both)。") | |
| talk = cfg.get("talk", {}) if isinstance(cfg, dict) else {} | |
| if isinstance(talk, dict) and "silenceTimeoutMs" in talk: | |
| tips.append(f"Talk:silenceTimeoutMs={talk.get('silenceTimeoutMs')}(可控制静音多久后自动发送)。") | |
| provider_hits = [] | |
| def walk(o, prefix=""): | |
| if isinstance(o, dict): | |
| for k, v in o.items(): | |
| np = f"{prefix}.{k}" if prefix else str(k) | |
| if str(k).lower() in ("baseurl", "endpoint", "url") and isinstance(v, str): | |
| provider_hits.append((np, v)) | |
| walk(v, np) | |
| elif isinstance(o, list): | |
| for i, x in enumerate(o): | |
| walk(x, f"{prefix}[{i}]") | |
| walk(cfg) | |
| if provider_hits: | |
| tips.append("检测到可能的接口地址(原样显示;注意不要公开你的 key):") | |
| for p, v in provider_hits[:10]: | |
| tips.append(f"- {p} = {v}") | |
| if len(provider_hits) > 10: | |
| tips.append(f"- ... 共 {len(provider_hits)} 条(仅展示前 10)") | |
| cmds = textwrap.dedent( | |
| """ | |
| # 常用排障命令(复制到终端执行) | |
| openclaw status | |
| openclaw gateway status | |
| openclaw gateway probe | |
| openclaw logs --follow | |
| openclaw doctor | |
| openclaw channels status --probe | |
| """ | |
| ).strip() | |
| report = "\n".join(f"- {t}" for t in tips) | |
| return report, red_json, cmds | |
| def explain_logs(raw: str): | |
| raw = (raw or "").strip() | |
| if not raw: | |
| return "请粘贴日志片段。", "" | |
| lines = raw.splitlines() | |
| score = {"auth": 0, "rpc": 0, "channel": 0, "provider": 0, "other": 0} | |
| patterns = { | |
| "auth": [r"401", r"unauthorized", r"forbidden", r"auth", r"token"], | |
| "rpc": [r"rpc", r"timeout", r"ws://", r"websocket"], | |
| "channel": [r"telegram", r"signal", r"discord", r"channel"], | |
| "provider": [r"openai", r"zhipu", r"siliconflow", r"baseurl", r"api"], | |
| } | |
| for ln in lines[:2000]: | |
| low = ln.lower() | |
| matched = False | |
| for k, ps in patterns.items(): | |
| if any(re.search(p, low) for p in ps): | |
| score[k] += 1 | |
| matched = True | |
| if not matched: | |
| score["other"] += 1 | |
| kind = sorted(score.items(), key=lambda x: x[1], reverse=True)[0][0] | |
| advice = [f"体检时间:{_now()}", f"粗分类:{kind}(仅基于关键词,供快速定位)"] | |
| next_cmds = ["openclaw status", "openclaw gateway probe"] | |
| if kind == "auth": | |
| advice.append("关注点:鉴权配置/allowedOrigins/上游 token。") | |
| next_cmds += ["openclaw channels status --probe"] | |
| elif kind == "rpc": | |
| advice.append("关注点:gateway 重启后的短暂恢复窗口、网络、RPC 超时。") | |
| next_cmds += ["openclaw gateway status", "openclaw logs --follow"] | |
| elif kind == "channel": | |
| advice.append("关注点:通道连接/回调、provider 配置是否影响发送。") | |
| next_cmds += ["openclaw channels status --probe", "openclaw logs --follow"] | |
| elif kind == "provider": | |
| advice.append("关注点:baseUrl、API key、模型名、限流/401。") | |
| next_cmds += ["openclaw logs --follow"] | |
| else: | |
| advice.append("关注点:先用 openclaw doctor + logs 定位模块。") | |
| next_cmds += ["openclaw doctor", "openclaw logs --follow"] | |
| return "\n".join(f"- {x}" for x in advice), "\n".join(next_cmds) | |
| with gr.Blocks(title="OpenClaw 运维工具站(中文)") as demo: | |
| gr.Markdown( | |
| """ | |
| # OpenClaw 运维工具站(中文) | |
| - **配置体检**:粘贴 `openclaw.json` → 输出中文风险提示 + 脱敏后的配置 | |
| - **日志体检**:粘贴日志片段 → 快速归类 + 给出下一步排障命令 | |
| 说明:本工具会对疑似敏感字段做脱敏显示,但**请不要在公开场合粘贴真实密钥**。 | |
| """.strip() | |
| ) | |
| with gr.Tab("配置体检"): | |
| cfg_in = gr.Textbox( | |
| label="粘贴 openclaw.json(完整 JSON)", | |
| lines=16, | |
| placeholder="{\n \"gateway\": { ... }\n}\n", | |
| ) | |
| btn = gr.Button("开始体检") | |
| report = gr.Textbox(label="中文结论", lines=10) | |
| redacted = gr.Textbox(label="脱敏后的配置(可分享)", lines=16) | |
| cmds = gr.Textbox(label="下一步命令(复制执行)", lines=8) | |
| btn.click(explain_config, inputs=[cfg_in], outputs=[report, redacted, cmds]) | |
| with gr.Tab("日志体检"): | |
| log_in = gr.Textbox(label="粘贴日志片段", lines=16) | |
| btn2 = gr.Button("开始体检") | |
| report2 = gr.Textbox(label="中文结论", lines=10) | |
| cmds2 = gr.Textbox(label="下一步命令(复制执行)", lines=8) | |
| btn2.click(explain_logs, inputs=[log_in], outputs=[report2, cmds2]) | |
| gr.Markdown( | |
| """ | |
| ### 你可以怎么用它 | |
| 1) 先跑 `openclaw status`,把关键报错贴到“日志体检”。 | |
| 2) 想检查配置,就把 `openclaw.json` 全文贴到“配置体检”。 | |
| ### 后续计划 | |
| - 配置项中文解释更细 | |
| - 常见错误库(按版本/通道/Provider) | |
| - 一键生成备份/恢复脚本(脱敏) | |
| """.strip() | |
| ) | |
| demo.queue() | |
| def main(): | |
| demo.launch(server_name="0.0.0.0", server_port=7860) | |
| if __name__ == "__main__": | |
| main() | |