SSE 实时推送与 Viewer UI
SSE 实时推送与 Viewer UI:Claude-Mem 的具体实现
学习目标
本章聚焦 Claude-Mem 如何通过 Server-Sent Events 实现实时记忆流推送,以及 React Viewer UI 的构建流程。
项目实践
SSE 广播器
SSEBroadcaster.ts(约 1.4KB)实现了轻量级 SSE 推送服务:
广播器的工作流程:
- Worker 产生新观察或状态变更时,调用
broadcast(event) - 广播器遍历所有已连接的 SSE 客户端
- 将事件格式化为 SSE 消息(
data: JSON.stringify(event))推送到每个客户端 - 推送失败的客户端自动断开连接
Viewer UI 构建
Viewer UI 是一个 React 应用,构建为单个 HTML 文件:
| 阶段 | 工具 | 输出 |
|---|---|---|
| 开发 | React 19 + TypeScript | src/ui/viewer/ |
| 构建 | esbuild | plugin/ui/viewer.html |
| 服务 | Worker 内置静态文件服务 | http://localhost:<port>/ |
构建产物嵌入到 plugin/ui/viewer.html,Worker Service 直接提供 HTTP 服务,无需额外的 Web 服务器。
Viewer 功能
Viewer UI 默认在 Worker 端口(如 http://localhost:37777)提供服务,支持:
- 实时记忆流:通过 SSE 接收并展示新记忆
- 时间线浏览:按时间查看观察和摘要
- 搜索:通过 Worker API 搜索历史记忆
- 版本切换:在 Settings 面板切换稳定版 / Beta 版
- 设置管理:查看和修改
settings.json
Beta 通道
Viewer UI 提供版本切换功能:
- 稳定版:当前发布的版本
- Beta 版:包含实验性功能(如 Endless Mode)
切换后,Worker 重新加载插件代码,应用新版本功能。
问题与规避
SSE 连接保持
问题:浏览器可能在网络切换或长时间空闲时断开 SSE 连接。
规避:浏览器端的 EventSource API 自动重连。Worker 侧无需特殊处理,新连接建立后即开始推送新事件。
构建产物同步
问题:Viewer UI 的 React 源码修改后需要重新构建并同步到 plugin/ui/viewer.html。
规避:npm run build-and-sync 命令自动执行构建和同步,将构建产物复制到 plugin/ 目录下的正确位置。
设计取舍
为什么用 SSE 而非 WebSocket?
| 维度 | SSE | WebSocket |
|---|---|---|
| 方向 | 单向(服务端→客户端) | 双向 |
| 协议 | HTTP 复用 | 独立协议 |
| 浏览器支持 | 内建 EventSource API | 需要 WebSocket API |
| 重连 | 自动 | 需要手动实现 |
| 复杂度 | 低 | 高 |
Claude-Mem 选择 SSE 因为 Viewer UI 只需要从服务端接收更新,不需要向服务端发送消息(搜索等操作通过 HTTP API 完成)。SSE 的自动重连和内建浏览器支持也降低了实现复杂度。
为什么构建为单文件 HTML?
Viewer UI 构建为单个 HTML 文件而非 SPA + API 分离:
- 优势:部署简单(只需一个文件),Worker Service 直接提供,无需额外的 Web 服务器
- 代价:首屏加载时需要下载完整 JS bundle
- 权衡:Viewer UI 是本地工具,带宽不是问题,简单性优先