📊 agentprof 可视化指南 · 目录 Wiki 13 / 14
Wiki

Web dashboard 架构

agentprof serve(M2.3)拉起一个 localhost-only 的 HTTP 看板 —— 5 个视图(sessions / session detail / aggregate / mcp-waste list / mcp-waste detail),5 秒轮询自动刷新,零 JS 框架。整套方案 reuse M2.2 已经在用的 axum + askama 栈,workspace top-level 零新增依赖。ADR-0024 把它当作 7 个独立决策(D-1..D-7)逐条钉死,本课带着真实代码把这 7 个决策摸一遍。

浏览器 5s 轮询 GET /api/<view>.html axum handler innerHTML swap
📦 类比 — 像 cargo doc --open
  • cargo doc:把当前 crate 的文档渲染成静态 HTML、起一个 localhost server、浏览器一拉就看。
  • agentprof serve:把当前 store DB 的 session 数据渲染成 HTML、起一个 localhost server、浏览器一拉就看。
  • 共同点:单进程、不需要 docker / nginx / 数据库 / 任何 ops;杀掉进程数据就没人能访问;不留 cookie / 不挂登录。

这种「ephemeral local dashboard」模式比「企业级 BI 看板」轻 100x —— 个人用户和小团队的 95% 需求都被覆盖,剩下 5% 的场景留给 grafana + OTLP collector + prometheus。agentprof 不想做 grafana 的活,做「本地 ROI 速查」就够了。

ADR-0024 全 7 决策对比(D-1..D-7)

决策编号选什么为什么 / 关键 trade-off
D-1 后端栈axum 0.7 + tokio + askama 0.16复用 M2.2 OTLP HTTP receiver 同款;workspace 已 depend,top-level 零新增依赖
D-2 前端框架无(vanilla JS poller, ~80 LOC)不引 React/Vue/Svelte = 不引 npm/webpack/vite 整个 build chain;本地看板规模 ≤ 千行 HTML,原生 DOM 完全够
D-3 数据传输HTML 切片(chunk-endpoint pattern)JS 拉 /api/<view>.htmlinnerHTML 替换;不需要 JSON → client-side 模板 → 渲染。少一层 = 少一次 bug
D-4 缓存策略无(每请求一次 SQLite read)5 秒轮询 × 单用户 ≈ 0.2 QPS,SQLite 完全顶得住;缓存层带 invalidation bug 多于性能收益
D-5 数据源store mode(不 fallback adapter scan)serve 要长期跑、要全量 trend;adapter scan 每次 walk filesystem 太重,必须有 SQLite store 撑
D-6 网络绑定默认 127.0.0.1:4329,loopback only,无 auth本地工具,认证靠 OS user;显式绑非 loopback 时 warn 用户(与 ADR-0022 同款 capacity-cap 风格防御)
D-7 发版策略v0.3.3(patch bump,不是 0.4.0)feature gated 在 serve feature 下;不破坏 v0.3.x 用户的 cli 兼容性;零 breaking change

⚠️ Recon 校正:chunk endpoint 真实路径形如 /api/sessions.html / /api/session/:id.html.html 后缀是 D-3 的 signal —— 这是HTML不是 JSON);handler 名是 *_chunk(5 个 chunk)+ *_page(5 个 page shell)共 10 个 + 1 个 healthz。Detail 页 reuse format::html::render_body_only —— 浏览器看到的 Cache 段、Tool rank 表和 analyze --export html cli 完全一致。

👇 三张卡片:① 7 决策 (D-1..D-7) 逐条摘要 · ② chunk-endpoint pattern 真实 router 表 · ③ 5 视图源码 walk-through。

1 ADR-0024 7 决策 (D-1..D-7) 摘要 点击展开
📋 D-1 Reuse axum + askama —— 「不新建栈」原则
M2.2 已经把 axum 0.7 / tokio / askama 0.16 都 add 到 workspace.dependencies;M2.3 不允许新增 top-level dep(要走 cargo deny allowlist + ADR)。决策结果:复用同一个 axum router builder pattern、同一个 askama 0.16 模板规约 —— 一致性 > 「找个更新潮的 web framework」。serve feature 跟 otlp feature 共享传输层 mod,binary size 增长几十 KB 内。
📋 D-2 Vanilla JS —— 「不引 build chain」原则
引 React 等于引 node + npm + webpack/vite + 一堆 tsconfig;CI 容器要先装 node 才能 build agentprof —— 这违背 agentprof「一个 cargo install 完事」的 USP。Vanilla JS poller 80 LOC,写在 static_assets.rs 里、build 时编进 binary(include_str!)—— 用户连 npm 都不知道在哪。取舍:失去 SPA 路由 / 状态管理 / vd-DOM 加速 —— 但本地 5 视图根本用不上。
📋 D-3 HTML chunk endpoint —— 「不引 client template」原则
主流做法:API 吐 JSON,前端用模板引擎渲染 DOM。chunk-endpoint pattern:服务端用 askama 渲染好 HTML 片段(不含 <html> / <head>),前端拉到后 el.innerHTML = chunk 直接换。少一层 = 少写一份模板 + 少一份「服务端 JSON schema 和前端期望对不上」的故障源。用户感知:每 5 秒页面区域 flash 一下 —— 加 CSS transition 就柔和了。
📋 D-4 / D-5 / D-6 安全 + 性能
D-4 无缓存层:5s × 单用户 ≈ 0.2 QPS,加缓存 = 加 invalidation bug;SQLite 跑 SELECT * FROM sessions ORDER BY started_at LIMIT 100 < 10ms 内即返。D-5 必须 store mode:serve 跑数小时 / 天,adapter scan 每次 walk filesystem ≈ 几秒,根本不行;启动时检查 storage_mode==Store,否则 cli 报错退码 1。D-6 loopback-only + 无 auth:本地工具,谁能 connect 127.0.0.1 谁就是本机用户 —— OS 已经做了认证。绑 0.0.0.0 / 公网 IP 时显式 warn,提醒用户加反代或 ssh tunnel。
📋 D-7 v0.3.3 patch release
serve 是 additive:feature gated(--features serve),不动现有 cli/cmd/* 任何子命令,不改 schema,不改 wire format。SemVer 规则下,additive feature = patch bump。v0.4.0 留给真正的 breaking change(比如 ADR-0019 决策反转之类)。这条决策也借鉴 ADR-0022 的 v0.3.2 hardening 经验 —— 硬化和 additive feature 都走 patch。
2 chunk-endpoint pattern 真实 router 表 点击展开
🛣️ 真实 router 表(crates/agentprof-cli/src/cmd/serve/router.rs:29
pub fn build_router(state: AppState) -> Router {
    Router::new()
        // page shells(完整 HTML,浏览器首次进入)
        .route("/",                        get(|| async { Redirect::permanent("/sessions") }))
        .route("/sessions",                get(handlers::sessions_page))
        .route("/session/:id",             get(handlers::session_page))
        .route("/aggregate",               get(handlers::aggregate_page))
        .route("/mcp-waste",               get(handlers::mcp_waste_list_page))
        .route("/mcp-waste/:tool",         get(handlers::mcp_waste_detail_page))
        // chunk endpoints(HTML 片段,JS 5 秒轮询)
        .route("/api/sessions.html",       get(handlers::sessions_chunk))
        .route("/api/session/:id.html",    get(handlers::session_chunk))
        .route("/api/aggregate.html",      get(handlers::aggregate_chunk))
        .route("/api/mcp-waste.html",      get(handlers::mcp_waste_list_chunk))
        .route("/api/mcp-waste/:tool.html",get(handlers::mcp_waste_detail_chunk))
        // 健康检查 + 静态资产
        .route("/healthz",                 get(handlers::healthz))
        .route("/static/:name",            get(handlers::static_asset))
        .with_state(state)
}
🔄 chunk-endpoint pattern 时序
  1. 浏览器 GET /sessionssessions_page 返完整 HTML(含 askama base 模板 + nav + 一个空 <div id="chunk">)。
  2. 页面 JS(include 在 base template 里,~80 LOC)启动 setInterval(refresh, 5000)
  3. 每 5 秒 fetch('/api/sessions.html')sessions_chunk 返 HTML 片段(不含 <html>)。
  4. JS document.getElementById('chunk').innerHTML = htmlText
  5. 浏览器原生 parse + render;表格、链接、CSS hover 全部按浏览器规则工作。

等价心智模型:把每个视图当成「会自动 reload 的静态页」—— 用户连刷新都不用按。如果未来要换 framework(HTMX / Turbo / Hotwire),切换的也只是「fetch + innerHTML」这一小段 JS,handler 一行都不动。

🤝 为什么 page shell + chunk 是两个 handler?
page需要把 nav / footer / CSS / JS poller bootstrap script 全装进去(一次性);chunk只需要 data 区域(per refresh)。如果只用 page handler,每 5 秒浏览器要重新 parse CSS / 重启 JS —— 卡顿。两个 handler 各做一件事,page 的 askama 模板 {% include %} chunk 模板,同一份 HTML 渲染逻辑被两个端点 reuse —— 改一个地方两处生效。
3 5 视图源码 walk-through 点击展开
👁️ 视图 1:sessions list (sessions_chunk)
SQLSELECT id, agent, dominant_model, started_at, duration_ms, total_input_tokens, total_output_tokens FROM sessions ORDER BY started_at DESC LIMIT 100askama 模板:渲染表格,每行 <a href="/session/{id}"> 跳详情。用户体验:dev 一边跑 agent 一边开浏览器,每 5 秒新 session 自动浮上来 —— 不用手动 list --since 1h
👁️ 视图 2:session detail (session_chunk) —— 关键 reuse 点
// crates/agentprof-cli/src/cmd/format/html.rs:175
pub fn render_body_only(
    report: &AnalysisReport,
    cache_metrics: Option<&CacheMetrics>,
    /* ... */
) -> askama::Result<String>;

analyze --export html 和 web detail共用这个 fn —— 浏览器里看到的 Turn Summary / Tool Rank / Cache 段,和 agentprof analyze --export html > out.html 输出完全一致。这是 ADR-0024 显式提到的「与 ADR-0023 cache metrics 一致」的实现机制意义:CLI 和 web 用户看到的「真相」是同一份;不会出现「CLI 显示 80% hit rate,web 显示 78%」这种诡异 bug。

👁️ 视图 3:aggregate (aggregate_chunk)
Reuse agentprof-core::aggregate(M2.1.1 加 episodes_json 后开放)。SQL 拉 sessions + episodes_json,分组维度(model / tool / day)由 URL query 决定(?by=model)。展示:表 + 简单 SVG bar chart(也是手写 SVG,不引 chart.js)。
👁️ 视图 4 + 5:mcp-waste list / detail
List 视图聚合跨 session的 MCP waste —— 调 aggregate_waste(per_session)(见 wiki 4),按 tool 名展示「累计 wasted tokens」+「loaded session count」+「actually-called session count」。Detail 视图点进单 tool,列出哪些 session 加载了它但从没调用 —— 用户能直接定位「哪个 server 该从 mcp.json 删了」。这是 agentprof 的核心 ROI 价值在 web 里的直接呈现。
🚀 为什么不加 WebSocket / SSE?
两者都比 5 秒 polling「更优雅」,但代价:① 后端要维护连接状态;② 防火墙 / 反代里有时被掐;③ 测试 / 调试比 HTTP 复杂。轮询的简单性压倒了「更新延迟从 0-5s 降到 0-1s」的边际收益 —— 本地看板的用户不在乎 5 秒 lag。如果未来真有需求,加 SSE 不破坏 chunk-endpoint pattern(chunk endpoint 升级为 stream)—— 后向兼容。

下一步

本课讲清了 agentprof serve 的 7 决策、chunk-endpoint pattern 真实路由、5 视图各自的数据源。下一课「贡献指南」是 Wiki 章节的收官 —— 怎么给 agentprof 提 PR、9 阶段 pipeline 怎么走、Conventional Commits 怎么写、加 ADR / 通过 CI 的实操清单。

📂 相关源码: agentprof-cli/cmd/serve/router.rs  build_router

📂 相关源码: agentprof-cli/cmd/serve/handlers.rs  sessions_chunk

📂 相关源码: agentprof-cli/cmd/format/html.rs  render_body_only