# 7-70 Page AI Pi Lab 原生 OpenHub-like UI 设计 v1 状态:done Owner:07-ai / mnote-web / page-ai-runtime 日期:2026-07-04 ## 1. 结论 Pi Lab 下一阶段不做第二个 iframe,也不把 `@earendil-works/pi-web-ui` 或 LibreChat / Open WebUI 这类完整应用直接嵌入 MNote。正确方向是: ```text MNote Page AI 独立 Pi Lab 浮动入口 + 原生抽屉 UI -> 体验参考当前 OpenHub AI 面板,但入口和容器不依赖 OpenHub -> 运行协议参考 @assistant-ui/react-pi 的 PiClient / HTTP SSE thread contract -> 消息、composer、tool、artifact 形态参考 Pi 官方 web-ui 与 ai-elements -> 权限、知识库、receipt、watcher refresh 继续由 MNote 托管 ``` OpenHub 继续作为默认 Page AI 主线。Pi Lab 使用独立浮动按钮与独立抽屉容器,默认在 MNote 页面中显示;目标是把 7-69 的 debug spike UI 升级成可验证的 MNote-native Page AI 体验,而不是替代或复用 OpenHub 的入口。若后续确认不需要 Pi Lab,直接退役这一独立入口和 runtime 即可,不需要先用 feature flag 隔离。 ## 2. 原则 - **独立入口默认显示**:Pi Lab 新增自己的悬浮按钮和抽屉容器,默认显示,不复用当前 OpenHub 的按钮、iframe 页面或 provider tab 容器;以后退役 OpenHub 或 Pi Lab 任一侧,都不影响另一侧。 - **原生承载**:Pi Lab UI 渲染在 MNote 自有 DOM 中,不新增独立产品页作为主验收面,不 iframe 第二个完整 AI 应用。 - **优先参考和复用**:能复用当前 OpenHub 已证明的交互、Pi 官方 UI 的事件语义、assistant-ui 的 runtime contract、ai-elements 的组件形态,就不要自造一套孤立协议。 - **MNote 拥有真相**:session binding、allowed roots、LightRAG facade、tool receipt、file patch、citation/open-reference、watcher refresh 仍归 MNote;第三方 UI 不能接管这些事实源。 - **Debug 后置**:Page AI 首屏必须是对话、输入、上下文和变更结果;runtime health、scope JSON、receipt 原文只进折叠诊断区或开发态右栏。 - **不破坏 OpenHub**:OpenHub 仍是默认入口和默认 Page AI 主线;Pi Lab 作为独立入口与 OpenHub 并列但互不依赖,不出现普通用户可见的 fallback/迁移暗示。 ## 3. 参考资源取证 ### 3.1 当前 OpenHub UI 本地 OpenHub 是当前最接近目标体验的产品基线: - `/mnt/Data1T/Mnote_data/openhub/OpenHub/smart-query-frontend/src/pages/SmartQueryPage.jsx` - `/mnt/Data1T/Mnote_data/openhub/OpenHub/smart-query-frontend/src/components/ChatInput.jsx` - `/mnt/Data1T/Mnote_data/openhub/OpenHub/smart-query-frontend/src/components/AssistantMessage.jsx` - `/mnt/Data1T/Mnote_data/openhub/OpenHub/smart-query-frontend/src/components/ToolCall.jsx` - `/mnt/Data1T/Mnote_data/openhub/OpenHub/smart-query-frontend/src/components/HistoryDrawer.jsx` - `/mnt/Data1T/Mnote_data/openhub/OpenHub/smart-query-frontend/src/components/DiffViewer.jsx` - `/mnt/Data1T/Mnote_data/openhub/OpenHub/smart-query-frontend/src/components/KnowledgeManager.jsx` 可参考能力: - 顶部/底部紧凑控制区:agent mode、model select、当前页面/当前文件夹按钮。 - 主消息流:user / assistant bubble、streaming、retry / undo、idle state。 - assistant message:reasoning 折叠、tool summary、tool cards、citation chips。 - session/history:历史抽屉、恢复会话、归档。 - changed files / diff:隐藏索引桥接 MNote,用户侧展示 changed files / diff 入口。 - knowledge:知识库入口和 citation 回跳模式。 不直接复用方式: - 不把 OpenHub React/AntD 组件直接搬进 `mnote-web` 主运行时。 - 不复制 OpenHub session/message 全文存储作为 Pi Lab 真相。 - 不复用 OpenHub iframe shell 实现 Pi Lab,因为用户目标是 MNote-native。 ### 3.2 Pi 官方 web-ui `@earendil-works/pi-web-ui@0.75.3` 已核验为 MIT 包,描述为 “Reusable web UI components for AI chat interfaces powered by @earendil-works/pi-ai”。包内包含: - `ChatPanel` - `AgentInterface` - `MessageList` / `Messages` - `MessageEditor` - `StreamingMessageContainer` - `ThinkingBlock` - tool renderers - artifacts runtime / sandbox - `ModelSelector` - IndexedDB storage stores 可参考能力: - Pi 消息流与工具流的视觉分层。 - thinking / tool / artifact 的独立组件边界。 - composer 与 model/thinking selector 的组合方式。 - artifacts-style 侧栏。 不直接整包接入原因: - 默认假设 browser 侧持有 `Agent`、provider keys、IndexedDB session/settings。 - 默认可暴露 Pi 自带 tools / attachments / artifacts 事实源。 - 与 MNote 后端托管 Pi RPC/SSE、AiAccessScope、MNote-owned tools 的边界冲突。 迁移条件: - 先提供 remote-session adapter,使 `ChatPanel` / `AgentInterface` 不直接管理 provider key、IndexedDB 和 `Agent.state.tools`。 - 所有 tool call、file read/write、knowledge query、receipt 仍必须走 MNote facade。 ### 3.3 assistant-ui / react-pi `@assistant-ui/react-pi@0.0.5` 已核验为 MIT,说明其 browser entry 是 RPC-isomorphic `PiClient`,通过 HTTP/SSE 驱动 Pi-backed threads。其 contract 包含: ```text GET /threads POST /threads GET /threads/:id PATCH /threads/:id POST /threads/:id/messages POST /threads/:id/cancel GET /models POST /threads/:id/model POST /threads/:id/thinking GET /threads/:id/events ``` 可参考能力: - thread / message / composer / queue / cancel 的协议边界。 - snapshot-first SSE reconnect 语义。 - model / thinking controls。 - host UI approval requests。 - tool-associated approval / interrupt 的 UI 映射。 当前不直接引 React UI 的原因: - `mnote-web` 当前主路径是 Rust SSR + browser JS,不是 React app。 - 直接引入 React island、Tailwind/shadcn 体系会扩大构建和运行时边界。 - 当前优先目标是把 MNote-owned Pi adapter 做完整,而不是重开前端框架主线。 设计决策:**先对齐 contract,不先引 UI 框架**。后续如果 MNote 需要 React island,再以该 contract 为迁移桥。 ### 3.4 ai-elements `ai-elements@1.9.0` 已核验为 Apache-2.0,定位是基于 shadcn/ui 的 AI-native component registry。 可参考能力: - message / conversation / prompt input / reasoning / tool / source / response card 的组件形态。 - 更现代的 AI chat 视觉密度和状态表达。 当前不直接接入原因: - 依赖 React、Tailwind、shadcn registry 和 CSS variables。 - 对当前 MNote Rust SSR + browser JS 主路径引入成本偏高。 设计决策:作为视觉和组件拆分参考,不作为 7-70 第一阶段依赖。 ### 3.5 完整应用类参考 LibreChat、Open WebUI、AnythingLLM、Continue、Cline、Roo Code 只作为交互模式参考: - LibreChat / Open WebUI / AnythingLLM:参考历史会话、多模型、RAG、文件区、设置入口。 - Continue / Cline / Roo Code:参考 agent tool timeline、审批、diff、changed files、任务状态。 不作为直接依赖或嵌入目标。 ## 4. UI 目标结构 ### 4.1 独立入口与抽屉整体 ```text MNote page Pi Lab floating button(default visible) -> Pi Lab native drawer(independent from OpenHub iframe/page) topbar Pi Lab title session title runtime status close / minimize context strip current page chip selection chip allowed roots chip LightRAG chip model / thinking selector body message timeline optional right rail composer prompt input context buttons send / abort / queue state folded diagnostics ``` 桌面端允许右侧 rail;移动端不显示右 rail,改为底部 sheet 或折叠 section。 入口规则: - Pi Lab 使用独立悬浮按钮,例如右下角 `π`,默认渲染。 - Pi Lab 点击后打开独立抽屉,不借用 OpenHub 当前按钮、OpenHub iframe 页或 OpenHub provider tab。 - Pi Lab 抽屉可以视觉参考 OpenHub,但 DOM、状态、session、事件订阅、关闭/最小化都独立。 - OpenHub 的现有按钮和页面保持不变;Pi Lab 不在 OpenHub 面板内新增 tab 作为主入口。 - 若未来退役 OpenHub,Pi Lab 浮动入口仍可保留;若退役 Pi Lab,OpenHub 入口不受影响。 - `MNOTE_PAGE_AI_PI_LAB` 可保留为临时开发/强制关闭开关,但不是产品入口显示的前置条件。 ### 4.2 Topbar 目标是接近 OpenHub 的 AI 面板,而不是 debug 工具页。 必须包含: - Pi Lab 标题与实验标记;不放 OpenHub/Pi provider tabs 作为主导航。 - session label:`Pi Lab · 当前页面标题`,恢复会话后显示短 session id。 - runtime status:idle / starting / streaming / error。 - model label:默认 `omniroute/freefirst`,可后续扩展 selector。 - close/minimize。 不要在 topbar 放大段 `scope`、`disabled builtins`、raw env 等诊断文案。 ### 4.3 Context Strip 替代当前偏 debug 的工具列表,参考 OpenHub `ChatInput` 中当前页面/文件夹按钮: - 当前页:真实 `.md` 路径或页面标题,点击在 MNote 中定位。 - 选区:无选区时 disabled,有选区时显示字符数。 - allowed roots:显示 root 名称和数量,点击展开只读详情。 - LightRAG:显示 provider 状态;query citation 走 `mnote.reference.open`。 - 文件变更:当前 session 有 patch 后显示 changed files chip。 Context strip 是用户可理解的任务上下文,不展示 JSON。 ### 4.4 Message Timeline 消息模型要吸收 OpenHub `AssistantMessage` 与 Pi/assistant-ui 的 part 语义: ```text MNotePiUiMessage id role = user | assistant | system status = queued | streaming | done | error | aborted createdAt model parts[] MNotePiUiPart text reasoning toolCall toolResult citation diff fileChange approval error ``` 展示规则: - user message 右侧气泡。 - assistant message 左侧主体块,正文优先。 - reasoning 默认折叠,严禁把 provider `reasoning_content` 混入 final answer。 - tool timeline 默认折叠成 summary,运行中自动展开当前 tool。 - tool 输入/输出长 JSON 默认收起,显示摘要和 deny reason。 - citation 显示为来源 chip,点击走 MNote open-reference。 - diff / fileChange 显示为 changed file chips + diff summary,点击走 MNote document pane / diff view。 - error 明确显示 provider/runtime/tool deny,不伪装成功。 ### 4.5 Tool Timeline 参考 OpenHub `ToolCall.jsx`、Pi official tool renderers、Cline/Roo 的工具审批流。 第一阶段必须覆盖: - `mnote.current_page.read` - `mnote.selection.read` - `mnote.allowed_roots.describe` - `mnote.local_file.read` - `mnote.local_file.patch` - `mnote.knowledge_rag.query` - `mnote.reference.open` - `mnote.tool_receipt.write` 每个 tool card 至少显示: - tool name - state:pending / running / allowed / denied / error / done - compact params summary - deny reason - receipt id - file version before / after - diff summary - citation count 默认禁用 Pi 原始 `bash/read/write/edit` 的状态可以放在折叠详情里,不占据首屏。 ### 4.6 Composer 参考 OpenHub `ChatInput`、Pi `MessageEditor`、assistant-ui composer queue 语义。 第一阶段: - 多行输入。 - icon-only 当前页、选区、文件夹/allowed roots、LightRAG 按钮,带 tooltip 和 aria-label。 - send 按钮。 - streaming 时显示 abort;若后端支持 queue,再允许 follow-up queue。 - 发送前自动附带 MNote context envelope,不把上下文 URL 或 JSON 写入 textarea。 - 禁用态要有明确原因:未启动 runtime、missing model、missing auth、scope denied。 后续阶段: - model selector。 - thinking level selector。 - thread queue / steer。 - prompt template / skill picker。 ### 4.7 Right Rail / Drawer 桌面端右侧 rail 承接“有用但不该挤占主对话”的信息: - session/history:当前只显示当前 session 与最近 session,完整历史后续做抽屉。 - changed files:变更文件列表、patch count、打开按钮。 - citations:本轮 citation 列表。 - receipts:最近 tool receipts,默认摘要。 - runtime:Pi PID / provider session id / runtime mode,只在 dev mode 显示。 移动端右 rail 不常驻,改为按钮展开底部 sheet。 ### 4.8 Diagnostics 折叠 diagnostics 只面向开发: - raw SSE events tail。 - allowed roots raw snapshot。 - receipt raw JSON。 - runtime command / pid / session dir。 - disabled builtin tools。 默认折叠,不参与普通用户验收截图。 ## 5. 后端与协议设计 ### 5.1 保留现有 7-69 endpoint 当前 7-69 已有: - `/api/page-ai/pi/status` - `/api/page-ai/pi/bootstrap` - `/api/page-ai/pi/start` - `/api/page-ai/pi/send` - `/api/page-ai/pi/abort` - `/api/page-ai/pi/events` - `/api/page-ai/pi/tool-call` - `/page-ai/pi` 7-70 不要求立即破坏这些 endpoint。 但前端入口要调整为默认显示的独立浮动按钮与独立 drawer host;`/page-ai/pi` 可保留为 debug/internal 直达页,不作为产品主验收入口。 ### 5.2 新增 PiClient-aligned adapter 层 为了后续能接 `@assistant-ui/react-pi` 或复用其 reducer 语义,新增 provider-neutral adapter contract: ```text GET /api/page-ai/pi/threads POST /api/page-ai/pi/threads GET /api/page-ai/pi/threads/:id POST /api/page-ai/pi/threads/:id/messages POST /api/page-ai/pi/threads/:id/cancel GET /api/page-ai/pi/threads/:id/events GET /api/page-ai/pi/models POST /api/page-ai/pi/threads/:id/model POST /api/page-ai/pi/threads/:id/thinking ``` 兼容策略: - 第一阶段可在前端 adapter 中把旧 endpoint 映射为 thread contract。 - 后端可逐步增加新 endpoint;旧 smoke 不被破坏。 - SSE 必须 snapshot-first:重连先发当前 thread snapshot,再发 live events。 - browser disconnect 不等于 abort;只有 cancel/abort 明确停止 runtime。 ### 5.3 UI event normalization Pi RPC / mock / future SDK event 统一归一为: ```text thread_snapshot message_created message_delta message_completed reasoning_delta tool_call_started tool_call_updated tool_call_completed tool_call_denied citation_added file_patch_applied receipt_written runtime_status_changed thread_error ``` 所有 UI 只消费 normalized events,不直接依赖 Pi JSONL 原始字段。 ## 6. 数据与权限边界 - `AiAccessScope` / allowed roots 是所有 file tool 的唯一授权来源。 - Pi Lab UI 只显示 allowed roots 摘要,不允许用户从 UI 临时扩大 root。 - `mnote.local_file.read` / `patch` 必须返回 normalized path、root id、deny reason、file version。 - LightRAG 继续是唯一默认 knowledge provider;Pi 只能通过 `mnote.knowledge_rag.query` 查询。 - citation 点击必须走 `mnote.reference.open` / MNote open-reference,不直接让 Pi 打开文件。 - receipt 当前仍可落 provider-neutral adapter,但 UI v2 必须按 Turso/libSQL 迁移目标设计字段。 - 文件 patch 后必须通过 MNote watcher / document refresh 链路更新 tiptap;禁止新增轮询刷新。 ## 7. 实施阶段 ### Phase A:Native UI v2 骨架 修改范围: - `rust/crates/mnote-web/browser/sidebar-page-ai-pi-lab-runtime.js` - `scripts/task-pi-lab-static-smoke.js` - `scripts/task-pi-lab-browser-smoke.js` - 必要时补 `rust/crates/mnote-web/src/ssr/styles/components/page-ai.css` 目标: - 当前 Pi Lab 从 debug shell 升级为 OpenHub-like 抽屉。 - Pi Lab 使用独立悬浮按钮打开独立抽屉,不复用 OpenHub 按钮、OpenHub iframe 页面或 provider tab。 - 首屏只保留对话、context strip、composer。 - tool receipts / diagnostics 默认后置。 - 截图必须显示:在 MNote 当前页面中通过独立 `π` 浮动按钮打开 Pi Lab 原生抽屉,非 OpenHub iframe、非独立页、非 OpenHub provider tab。 ### Phase B:Event / message model 收口 修改范围: - `sidebar-page-ai-pi-lab-runtime.js` - `rust/crates/mnote-web/src/routes/page_ai_pi.rs` - smoke 脚本 目标: - 新增 normalized event reducer。 - reasoning、tool、citation、diff、receipt 不再混杂在一条 debug 文本里。 - SSE reconnect snapshot-first。 - abort/cancel 明确区分 browser disconnect。 ### Phase C:PiClient-aligned adapter 目标: - 增加或前端模拟 `/threads` contract。 - `send` / `cancel` / `events` 对齐 `@assistant-ui/react-pi` 的语义。 - 保持旧 endpoint 兼容,直到 smoke 全部迁移。 ### Phase D:OpenHub-like 功能补齐 目标: - session history 抽屉。 - changed files rail。 - citation list。 - diff preview。 - model / thinking selector。 - tool approval / host UI requests。 ### Phase E:第三方组件复用决策 进入条件: - Phase A-D 浏览器截图和 smoke 通过。 - 当前 MNote native UI 已形成稳定 message/event model。 决策: - 若 MNote 仍保持非 React 主路径:继续原生实现,只参考 Pi web-ui / ai-elements。 - 若引入 React island:优先评估 `assistant-ui + @assistant-ui/react-pi`,并复用 Phase C contract。 - 若官方 Pi web-ui 提供 remote-session adapter:评估局部复用 `AgentInterface` / `Messages` / `MessageEditor`,不得引入 browser provider key 和 IndexedDB 作为 MNote 真相。 ## 8. 验收标准 - OpenHub 默认入口不变。 - MNote 默认显示独立 Pi Lab 浮动按钮;点击后打开独立 Pi Lab 原生抽屉。 - Pi Lab 不复用 OpenHub 按钮、OpenHub 页面或 OpenHub iframe 容器。 - UI 首屏接近 OpenHub:消息流、上下文、输入框、模型/状态、tool/citation/diff 摘要齐全。 - 不出现第二个 iframe AI 应用。 - 可发送 prompt、接收 stream、abort。 - reasoning 折叠且不泄漏到 final answer。 - tool call 显示状态、deny reason、receipt id。 - allowed roots 越界读写在 UI 中显示 denied。 - LightRAG citation 可点击回跳。 - 当前 `.md` patch 后显示 changed file,并通过 watcher refresh 当前 tiptap 页。 - diagnostics 默认折叠。 - browser smoke 输出截图。 ## 9. 明确非目标 - 不替换 OpenHub。 - 不把 Pi Lab 做成普通用户默认入口。 - 不 iframe LibreChat / Open WebUI / AnythingLLM / Pi web-ui demo app。 - 不把 Pi Lab 做成 OpenHub 面板内部的 provider tab 或复用 OpenHub 页面容器。 - 不把 React / shadcn / Tailwind 作为 7-70 Phase A 必需依赖。 - 不开放 Pi 原始 bash、无约束 read/write/edit。 - 不新增第二套 RAG。 - 不把 OpenHub 或 Pi 的 session JSONL 作为 MNote control-plane 长期真相。 ## 10. 当前后续任务 - [ ] Phase A:重做 `sidebar-page-ai-pi-lab-runtime.js` 信息架构、独立浮动入口与视觉层级。 - [ ] Phase A:更新 browser smoke,截图验证独立 `π` 浮动按钮打开 OpenHub-like Pi Lab 抽屉。 - [ ] Phase B:抽出 normalized event reducer。 - [ ] Phase C:补 PiClient-aligned thread adapter。 - [ ] Phase D:补 changed files / diff / citation rail。 - [ ] Phase E:重新评估是否引入 `assistant-ui` 或 Pi official remote-session adapter。