# PicoBot WebUI 全面重构设计

- 状态：设计已确认，待实现
- 日期：2026-07-23
- 范围：前端（`webui/`）全面重构 + 必要的后端接口新增/调整（`src/gateway/`）

## 1. 背景与目标

现有 WebUI（Svelte 5 + Bits UI，随二进制嵌入）已具备聊天、配置、记忆、任务、日志、主题切换等基础能力，但视觉与交互体验一般，且缺少运行状况观测、工具/Skill 浏览、实时日志等能力。本次重构目标：

1. 前端可直接与 PicoBot 沟通（聊天，已有，增强体验）
2. 可修改 PicoBot 各项配置（已有，增强）
3. 可观察 PicoBot 运行情况（**新增**：运行状况仪表盘）
4. 可查看工具列表、Skill 列表（**新增**）
5. 可查看实时日志（已有轮询，**升级为流式**）
6. 支持亮色/暗色的美观且易用的 UI（**全面重设计**）
7. 可查看并管理记忆、定时任务等信息（记忆**新增可编辑/可删除**）

## 2. 约束与不变量

- **单二进制发布**：前端构建产物仍打包进二进制，运行时从内存提供（`build.rs` → Cargo `OUT_DIR` → `include_str!`/`include_bytes!`）。最终用户无需 Node.js。
- **无外部 CDN**：生产页面不加载任何 CDN 资源。现有 CSP 为 `default-src 'self'; connect-src 'self' ws: wss:; img-src 'self' data:; style-src 'self'; script-src 'self'; base-uri 'none'; frame-ancestors 'none'`。字体等资产必须同源内嵌。
- **设备鉴权**：所有管理 API 与 `/ws` 受 `AuthManager` 保护；新增端点与 `/ws/logs` 同样走现有设备鉴权。
- **聊天复用现有链路**：浏览器聊天继续使用 `/ws` 与 `cli_chat` 渠道，复用 dialog scope、每会话串行 worker、历史持久化、出站 lane 与 turn 快照。WebUI 不直接调用 Provider 或 SessionManager。
- **密钥安全**：`/api/status` 等任何新响应不得包含密钥；日志脱敏在源头（不在日志中记录 secret）。
- **只读优先**：除配置（现有可写）与记忆写入（新增）外，其余新能力均为只读。

## 3. 总体方案

- **定位**：均衡控制台，但**聊天优先**——打开即聊天，其余功能区通过扁平导航平等直达；全局"活动脊"提供常驻运行感知。
- **技术栈**：继续使用 Svelte 5 + Bits UI + Vite，不引入新框架或状态管理库。
- **推进方式**：一份总体设计 + 分阶段实现（见 §9）。

## 4. 设计系统：Signal Deck

视觉方向为"仪表盘 / 工程仪器"：石墨蓝基底 + 琥珀（活动）/青绿（健康）双信号色，数据全部等宽字体，顶部一条永远在呼吸的"活动脊"作为签名元素。

### 4.1 色彩 Tokens

暗色（石墨蓝基底）：

| Token | Hex | 用途 |
|-------|-----|------|
| bg | `#0B1017` | 页面背景 |
| panel | `#0E1520` | 面板/卡片 |
| panel-2 | `#131C29` | 次级面板/悬停 |
| border | `#1D2733` | 边框 |
| border-strong | `#2C3A4C` | 强调边框/输入框 |
| text | `#E7ECF3` | 主文本 |
| text-soft | `#B8C4D4` | 次级文本 |
| muted | `#8FA3B8` | 辅助文本 |
| faint | `#5B6B7E` | 最弱文本/时间戳 |
| amber | `#FFB454` | 活动/进行中/警告 |
| teal | `#2DD4BF` | 健康/成功/只读 |
| danger | `#FF7B86` | 错误/危险/独占 |
| info | `#6AA6FF` | 信息/Timeline/思考 |
| code-bg | `#080C12` | 代码/日志底 |

亮色（冷纸白，信号色加深保证对比）：

| Token | Hex | Token | Hex |
|-------|-----|-------|-----|
| bg | `#EEF1F5` | text | `#1A2230` |
| panel | `#FFFFFF` | text-soft | `#3D4B5E` |
| panel-2 | `#F4F6F9` | muted | `#5B6B7E` |
| border | `#D8DEE8` | faint | `#8494A8` |
| border-strong | `#C2CCD9` | amber | `#C47400`（填充 `#E08600`） |
| teal | `#0D9488` | danger | `#D94354` |
| info | `#2F6FD0` | code-bg | `#F7F9FC` |

**关键规则**：亮色模式下"活动脊"仍为深色条（`#0E1520`），像物理仪器上的 LED 读数——两种主题下同一个记忆点，不做简单反色。

### 4.2 字体

- 展示 / UI：Space Grotesk（内嵌 woff2，仅拉丁），中文回落系统字体（PingFang SC / Microsoft YaHei / Noto Sans SC）。
- 数据 / 等宽：JetBrains Mono（内嵌 woff2），用于所有指标、日志、时间戳、small-caps 标签。
- 字号阶梯：9px（small-caps 标签，letter-spacing .12–.16em）/ 11px（caption、日志）/ 12.5–14px（正文）/ 16px（小标题）/ 19px（标题）/ 24–32px（指标数字）。
- 生产环境不加载 CDN；字体以内嵌二进制资产提供（见 §8.3）。

### 4.3 签名元素：活动脊（Activity Spine）

全局置于每个页面顶部的等宽状态条，两种状态：

- **有 Turn 在跑**：琥珀脉冲点 + `TURN 042 · STREAMING` + 实时 `▲ tok/s`、`ctx`、`queue`、`ws`，右侧 `gen #N · uptime · version`。
- **空闲**：青绿常亮点 + `IDLE` + 最近 turn 摘要。

Turn 实时状态来自聊天 WS 已有的 `turn_updated` 快照（本就实时推送，`WsOutbound::TurnUpdated`）；gen/uptime/version 等来自 `/api/status` 轮询。各字段来源：`▲ tok/s` 由前端对相邻 `turn_updated` 帧的 `usage.completion_tokens` 差值求导（快照本身不含速率字段）；`ctx` 取自 `usage.prompt_tokens`；`queue`/`ws` 取自 `/api/status`。

### 4.4 核心组件

按钮（primary=amber / secondary / ghost / danger）、状态徽标（正常/活动中/异常/离线）、指标块（大等宽数字 + sparkline + 分段容量条）、日志行（level 着色）、输入框、工具调用卡片（默认折叠，运行中=琥珀脉冲、完成=青绿）、表格行、标签页、Toast。图表统一手写 SVG sparkline / 分段仪表，不引入图表库。

## 5. 信息架构与应用外壳

- **导航**：左侧扁平导航——聊天（落地页）、概览、工具&Skills、日志、记忆、任务、配置；底部网关状态 + 主题切换。
- **应用外壳**：全局持有聊天 WS 连接（使活动脊在每个页面可用）、主题状态（`localStorage` 持久化 + `prefers-color-scheme` 默认）、设备鉴权状态（未配对显示 PairingPage）。
- **页面清单**：聊天 / 概览 / 工具&Skills / 日志 / 记忆 / 任务 / 配置，外加 PairingPage（鉴权）。

## 6. 页面设计

### 6.1 聊天页（落地页）

三栏布局：会话列表（搜索/新建/按日期分组/未读点）｜ 消息流 ｜ Todo 计划侧栏（默认收起，按需展开）。

- reasoning 与工具调用默认折叠为紧凑卡片（运行中=琥珀脉冲，完成=青绿）。
- 流式 turn 显示光标与 `▲ tok/s`，输入区出现"停止"按钮。
- 斜杠命令补全来自后端 `get_slash_commands`（不在前端硬编码命令表）。
- 附件走 HTTP 上传（`POST /api/chat/{client_id}/uploads`），WS 只传 `upload_id`；历史附件经 `GET .../attachments/{index}` 下载，安全 MIME 白名单内联预览。
- 正常完成合并 `turn_committed` 增量校准历史，不整段重载；断线/失败/取消用 `SessionHistory` 校准。

### 6.2 概览页（运行仪表盘）

- 主状态条：`RUNNING`、运行代、uptime、版本、WS 连接数、后台任务数、上次重载。
- 指标块（带 sparkline）：会话数、今日 Token（+费用）、工具调用（+运行中）、今日 Turns（+p95 延迟）。
- Provider 表：名称、模型、状态、延迟 sparkline、今日用量、费用。
- 消息总线：inbound/outbound/control 队列深度分段容量条、活跃 lane 数、调度器状态、MCP 连接。
- 渠道状态：feishu / cli_chat 等连接状态。
- 调度器：任务数、下次运行、7 天失败数。
- 实时活动流：最近 turn/memory/job 事件。
- 数据来自 `/api/status`，默认每 2s 轮询。

### 6.3 工具 & Skills 页

- 三个标签页：工具 / Skills / MCP。
- 工具卡片：名称、来源（builtin/mcp）、描述、调用次数、**能力徽标**、可展开参数 schema。
- **能力标识**（来自 `Tool` trait）：
  - `◇ 只读`（teal）= `read_only()`
  - `⇉ 可并发`（info）= `read_only() && !exclusive()`（即 `concurrency_safe()`）
  - `△ 有副作用`（amber）= `!read_only()`
  - `■ 独占`（danger）= `exclusive()`（如 bash）
- 支持搜索 + 按能力筛选（全部/只读/可并发/有副作用/独占）+ 图例。
- Skills 标签：名称、描述、always、来源目录。MCP 标签：服务器名 + 连接状态。

### 6.4 日志页（实时流式）

- 工具栏：level 过滤（全部/INF/WRN/ERR）、关键字搜索、暂停滚动、下载。
- 日志行：时间戳 + level 着色 + target + 消息，自动跟随尾部。
- 进入页面：`GET /api/logs`（保留）拉历史尾 → `/ws/logs` 接管实时；断线重连重新拉尾对齐。
- 顶部显示连接状态（实时推送中 · 行/分）。

### 6.5 记忆页（可编辑 / 可删除）

- **权限**：Knowledge 与 Timeline 均可编辑、可删除。
- **大量条目展示**：统计条（总量/Knowledge/Timeline/覆盖会话）→ 语义搜索优先 → 分类/会话/排序筛选 → 日期分组高密度行（列表/卡片视图可切换）→ 虚拟滚动 + 分页加载（"已显示 100 / 1,284 · 加载更多"）。
- 行内编辑：textarea + importance 调节 + 保存/取消（按 key upsert，`updated_at` 自动刷新）。
- **删除警告分级**：
  - Knowledge：普通确认（"删除后不可恢复，影响后续召回"）。
  - Timeline：**强警告**——"Timeline 是压缩后的历史上下文，删除后模型将永久失去该时段长期记忆且无法自动重建；原始消息仍保留在聊天历史，但不再进入模型上下文"，按钮文案"我了解，确认删除"。

### 6.6 任务页

- 两个标签页：定时任务 / 后台任务。
- 定时任务表：名称、cron 表达式、下次运行倒计时、上次运行、最近 10 次运行状态点（绿/琥珀/红）、启用状态；可展开运行记录（时间/耗时/摘要）。
- 后台子任务列表：名称、来源 session、运行中（脉冲）/完成、耗时。
- 只读浏览。

### 6.7 配置页（唯一可写页面之一）

- 标签页：config.json / USER.md / AGENTS.md。
- config.json：JSON 编辑器，密钥掩码（`********`，原样提交自动还原）、实时 JSON 校验 + default agent 有效性、未保存修改提示。
- 右侧配置大纲（gateway/providers/agent/channels/memory/scheduler），标注"重启生效""含密钥"。
- 重载状态卡：运行代、相位、上次重载结果。
- 操作：保存 / 保存并热重载 / 放弃修改。
- 复用现有 `GET/PUT /api/config`、`GET/PUT /api/profiles/{name}`、`POST /api/config/reload`、`GET /api/config/reload/status`。
- 提示 host/port/workspace 与存储路径为进程级不变量，修改后热重载被拒绝、需重启。

## 7. 后端接口设计

### 7.1 新增端点总览

| 方法 | 路径 | 用途 | 数据来源 |
|------|------|------|----------|
| GET | `/api/status` | 运行状况快照 | `Metrics` + 各服务只读查询 |
| GET | `/api/tools` | 工具列表（含能力字段） | `ToolRegistry` |
| GET | `/api/skills` | Skill 列表 | `SkillsLoader` |
| PUT | `/api/memories/{key}` | 更新记忆 content/importance | `Storage::upsert_memory` |
| DELETE | `/api/memories/{key}` | 删除记忆 | `Storage::delete_memory` |
| WS | `/ws/logs` | 实时日志流 | tracing 广播层 |

所有新端点走现有设备鉴权，注册在 `src/gateway/mod.rs` 的 protected router。

### 7.2 `GET /api/status`

返回单一 JSON 快照，概览页每 2s 轮询：

```json
{
  "generation": 7, "version": "1.3.0", "uptime_secs": 266400, "phase": "steady",
  "ws_connections": 2, "background_tasks": 3,
  "sessions": { "total": 14, "active_turns": 1 },
  "metrics": { "tokens_today": 1204882, "cost_today": 0.84,
               "tool_calls_today": 312, "turns_today": 87, "turn_latency_p95_ms": 4200 },
  "bus": { "inbound": {"depth":0,"cap":32}, "outbound": {"depth":1,"cap":64},
           "control": {"depth":0,"cap":64}, "active_lanes": 4 },
  "providers": [ {"name":"openai","model":"gpt-4o","status":"ok",
                  "latency_ms":820,"tokens":980000,"cost":0.61} ],
  "channels": [ {"name":"feishu","status":"connected","detail":"3 群"} ],
  "scheduler": { "enabled": true, "jobs": 5, "failed_7d": 0 },
  "mcp": [ {"name":"github","status":"connected"} ]
}
```

聚合来源分两类——**已有查询**与**需新增的内省接口**（后者是 P1 的真实后端工作量，不可当作现成只读查询）：

已有 / 低成本可得：
- `reload`：generation、相位（现有 `ReloadStatus`）
- `mcp::get_mcp_status()`：MCP 服务器连接状态（现有全局状态注册表）
- `Scheduler` / Storage：任务数、下次运行、7 天失败数（现有 Storage API）
- `ChannelManager`：各渠道连接状态
- `Metrics`：token/费用/工具调用/turn/延迟（见 §7.3，新增）

**需新增的内省接口**（当前代码无对应查询面）：
- `MessageBus`：三条队列的深度与容量。现状只有 publish/consume，且未保留配置容量（`src/bus/mod.rs`）。实现上让 bus 保留各队列 `mpsc::Sender`/容量，深度由 `max_capacity() - capacity()` 派生（tokio `mpsc::Sender` 提供这两个方法）。
- `OutboundDispatcher`：活跃 lane 数。现状只有 `new`/`run`（`src/bus/dispatcher.rs`），需新增计数查询。
- `TaskSupervisor`：运行中任务数。现状无查询面（`src/task_supervisor.rs`），需新增。
- `SessionManager`：会话总数与活动 Turn 数（确认现有方法是否足够，不足则补只读统计）。
- WebSocket 连接数（`ws_connections`）：当前无连接计数器，需在 `ws_handler` 用一个 `Arc<AtomicUsize>` 在连接建立/断开时增减。

这些内省方法必须轻量、非阻塞（不加锁等待慢操作），以支撑每 2s 轮询。

**不含任何密钥**（provider api_key 等一律不出现）。

### 7.3 指标采集（`Metrics`）

- 新增 `Metrics` 结构（原子计数为主）：tokens in/out、cost、per-tool 调用数、turns、per-provider 延迟与错误滚动窗口。
- 由 `AgentLoop` / Provider 在每次 turn / 工具调用时经 `Arc<Metrics>` 更新。
- 纯内存、不持久化、重启归零。"今日"统计为自进程启动起的滚动窗口（文档与 UI 注明，不暗示自然日）。
- provider 状态（ok/降级）由最近错误率派生。

### 7.4 `WS /ws/logs`

- 给 tracing 增加一个广播层：格式化日志记录后发送到 `tokio::sync::broadcast`（容量约 1024）；慢客户端丢旧（lag），不反压。无订阅者时发送为 no-op，近乎零开销。
- handler 连接后订阅，按查询参数 `level` / `search` 过滤，推送 `{ts, level, target, message}` 帧。
- 修改 `src/logging` 的订阅器初始化以挂载该广播层（保持文件轮转不变）。
- `GET /api/logs`（文件尾）保留，用于进入页面时拉取历史与重连对齐。

### 7.5 记忆写入端点

- `PUT /api/memories/{key}`：body `{content, importance?}`，按 key upsert（复用 `Storage::upsert_memory`），`updated_at` 自动刷新。
- `DELETE /api/memories/{key}`：复用 `Storage::delete_memory`。
- path 中的 key 需 URL 解码；实现时校验 key 存在性，返回 404 若不存在。
- 现有 `GET /api/memories`（list/search，含 category/session/limit/query）保留不变。

### 7.6 工具 / Skills 端点

- `GET /api/tools`：遍历 `ToolRegistry`，每项返回 `name, description, parameters_schema, source(builtin|mcp), read_only, exclusive, concurrency_safe, call_count`。`call_count` 取自 `Metrics` 的 per-tool 计数。
- `GET /api/skills`：数据源为 `SkillsLoader::get_loaded_skills()`（返回完整 `Skill { name, description, content, always, path }`，`src/skills/mod.rs`）；**不要**用 `list_skills()`，它只返回 `(name, description)` 二元组，缺少 `always`/`path`。返回 `name, description, always, source`，其中 `source` 由 `Skill.path` 所在目录派生（无独立来源字段）。默认不返回完整 `content`（可能较大）。

## 8. 前端架构

### 8.1 目录与数据层

- 保持 Svelte 5 runes；将 `src/lib/api.js` 扩展为按域划分的客户端模块（如 `api/status.js`、`api/tools.js`、`api/memories.js`），不引入状态管理库。
- 组件库 `src/lib/`：在现有 `Markdown.svelte`、`ToolCallCard.svelte`、`TurnView.svelte`、`Toast.svelte`、`StatusBadge.svelte` 基础上，新增 Signal Deck 组件（ActivitySpine、MetricTile、Sparkline、CapacityMeter、LogStream、BadgeSet 等）。
- 页面 `src/pages/`：重构 ChatPage、新增 OverviewPage、ToolsPage、重写 LogsPage、重构 MemoryPage、重构 TasksPage、重构 SettingsPage、保留 PairingPage。

### 8.2 主题

- 设计 tokens 以 CSS 自定义属性表达：`:root`（暗色）与 `:root[data-theme="light"]`（亮色），替换现有 `styles.css` 的变量集。
- 主题切换持久化到 `localStorage`，默认跟随 `prefers-color-scheme`。

### 8.3 字体内嵌（构建管线变更）

- 字体文件（latin 子集 woff2，取自 @fontsource）放入 `webui/public/fonts/`。Vite 默认 `publicDir` 会把 `public/` 内容**原样、固定名**复制到产物根（`OUT_DIR/webui/fonts/*.woff2`），无需改 `vite.config.js` 的 `assetFileNames`。
- `http.rs`：新增 `/fonts/{name}` 路由，用 `include_bytes!(concat!(env!("OUT_DIR"), "/webui/fonts/...woff2"))` 嵌入（静态 name→bytes 映射），返回 `Content-Type: font/woff2` 与长期缓存头；属公开静态资源层（与 app.js/styles.css 同级，不进设备鉴权）。
- CSP：现有 `default-src 'self'` 已允许同源字体（font-src 回落到 default-src），无需放宽。
- 二进制体积增量约 100–150KB（Space Grotesk + JetBrains Mono，可考虑子集化）。
- `build.rs` 的 `rerun-if-changed` 需追加 `webui/public`；依赖 stamp 逻辑不变。

### 8.4 全局 WS 与活动脊

- 聊天 WS 连接提升到应用外壳层（App.svelte），使活动脊在所有页面可用。
- 活动脊消费 WS 的 turn 快照得到实时 Turn 状态；其余字段轮询 `/api/status`。

## 9. 分阶段实现

- **P0 地基**：设计系统（tokens/组件库/双主题/字体内嵌）+ 应用外壳（扁平导航 + 全局活动脊 + 主题/鉴权）+ 聊天页重构。
- **P1 观测**：`Metrics` + `GET /api/status` + 概览页 + `GET /api/tools`/`/api/skills` + 工具&Skills 页。
- **P2 日志与数据**：tracing 广播层 + `/ws/logs` + 日志页 + 记忆写入端点 + 记忆页重构 + 任务页重构。
- **P3 配置**：配置编辑器重构 + 配置大纲 + profile + reload 状态可视化。

每阶段独立可验证；前端改动须过 `npm run check` + `npm run build` + `cargo build`（验证 OUT_DIR 嵌入），Rust 改动须过定向测试 + `cargo test --lib` + `cargo clippy --all-targets --all-features -- -D warnings`。

## 10. 风险与开放项

- **字体内嵌**：构建管线需同时支持文本（include_str!）与二进制（include_bytes!）资产；需在实现期验证 vite 固定名输出与 Cargo 嵌入路径。若字体子集化复杂，可退回系统字体栈（牺牲部分排版个性）。
- **`Metrics` 侵入性**：在 AgentLoop/Provider 埋点需避免持锁慢操作，遵循"不在持锁时做网络/模型/DB 慢操作"的不变量；计数用原子操作。
- **`/api/status` 聚合成本**：每 2s 轮询，聚合多个服务的只读查询；需确保各查询轻量、不加锁阻塞。必要时缓存短 TTL 快照。
- **tracing 广播层**：需保证无订阅者时零开销、有订阅者时不阻塞日志写入；广播满时丢旧而非阻塞。
- **记忆 key 路由**：key 可能含特殊字符，URL 编解码与 404 语义需在实现期明确。
- **Timeline 删除语义**：UI 已用强警告；后端不做额外保护（用户拥有自己的 Agent），但删除为幂等硬删除。

## 11. 验收标准

- 单二进制 `cargo build` 成功，WebUI 从内存提供，无外部 CDN 依赖。
- 亮/暗双主题完整覆盖所有页面与组件。
- 聊天页保留现有全部能力（dialog scope、历史持久化、turn 快照、斜杠补全、附件、Todo 侧栏）。
- 概览页实时反映运行状况；活动脊在所有页面可见且实时。
- 工具页正确展示 read_only/exclusive/concurrency_safe 能力标识。
- 日志页实时流式推送，支持 level/搜索过滤与暂停。
- 记忆页支持 Knowledge/Timeline 编辑与删除，删除警告分级，大量条目下虚拟滚动流畅。
- 配置页可编辑、密钥掩码、热重载状态可视。
- 所有新端点受设备鉴权保护，响应不含密钥。
- `npm run check`、`npm run build`、`cargo build`、`cargo test --lib`、`cargo clippy -- -D warnings` 全部通过。
