文档: 架构方案文档(意图识别论证+多主题愿景/论证+文档物理分类+边界清晰化)

squash合并:
- 意图识别层论证(8维度+10业界佐证)
- 多主题上下文管理愿景+并存论证+补充论证(多轮agentic)
- 架构设计文档物理分类(四子目录+INDEX+命名规范+引用同步+边界清晰化)
- 前端架构技术债清单归档
This commit is contained in:
2026-06-19 15:04:04 +08:00
parent f1a06732fd
commit 998a2f243d
73 changed files with 1083 additions and 80 deletions

View File

@@ -0,0 +1,146 @@
# AI Chat 流式 Markdown 渲染调研2026-06-15
> 范围:`AiChat.vue` 流式渲染优化。当前 AR-1 修复commit f58743e用「流式纯文本短路」防掉帧副作用是流式过程无格式。本调研找不掉帧且有格式的更好方案。
> 方法3 路并行调研(机制方案 / 现成库 / Vue3 落地)+ 主代理整合。
> 性质:调研 + 方案,**未实施**。
> 关联:[aichat审查报告-2026-06-14.md](aichat审查报告-2026-06-14.md) AR-1[近期改动代码审查-2026-06-15.md](../05-代码审查/近期改动代码审查-2026-06-15.md) §亮点「renderMd 流式纯文本短路」。
---
## §1 问题根因
现状(`src/components/AiChat.vue:200,347`
```vue
<div v-html="renderMd(text, isLastAi(msg) && store.state.streaming)"></div>
```
```js
function renderMd(text, isStreaming=false){
if (isStreaming || !mdReady) return escapeHtml(text) // 流式纯文本短路
return _purify.sanitize(_marked.parse(text))
}
```
掉帧两因素叠加:
- **(A) 解析成本**:每个 delta 全量 `marked.parse()` O(N)N 随回复增长,后期单帧解析几十 ms。
- **(B) 重渲染成本**`v-html` 整段替换触发子树重布局。
当前方案牺牲「流式格式」躲掉 (A)(B),但 delta 20-80ms 一个,流式时用户看到裸 markdown 源码(`#``-`、```` ``` ```` 符号),结束才出格式。
---
## §2 五大机制对比
| 机制 | 消除瓶颈 | 流式有格式 | 成本 | 适用 | 独立够吗 |
|---|---|---|---|---|---|
| 1. rAF 批量节流 | 中(砍频率) | 是 | 低 | 全部 | 否(长文本单次仍重) |
| 2. 块级 diff + memo | **高**(砍到 O(末块) | 是 | 低-中 | 全部 | **基本够(主流首选)** |
| 3. Web Worker 解析 | 高(主线程归零) | 是 | 中 | 超长文档/重 sanitize | 通常与 2 叠加 |
| 4a. 代码块延迟高亮 | 高(代码块零成本) | 代码块流式无色 | 低 | 有代码块 | 仅代码块,需叠加 |
| 4b. shiki-stream 增量高亮 | 高 | 代码块流式有色 | 中 | 有代码块 | 仅代码块 |
| 5. 虚拟滚动 | 中(砍 DOM 不砍 parse | 是 | 中 | 长会话消息列表 | 否 |
**机制 2块级 diff + memo是业界主流首选**——Vercel AI SDK 官方 cookbook 做法。原理:`marked.lexer()` 按双换行/代码围栏切块,每块独立 memoize只有「正在生长的末块」重 parse已完成块缓存跳过。解析成本从 O(全文) 降到 O(末块)。
**大厂可见行为**ChatGPT / Claude / Gemini 流式时均有格式,代码块流式时等宽 + 基本着色。Chrome 官方文档明确反对「整篇重 parse + innerHTML 替换」(正是当前实现),推荐 streaming-markdown 增量 append。
**rAF 实测数据**SitepointReact 18.3 生产构建 M2朴素逐 token setState 平均 commit 18ms80 token/s 达 52ms 可见卡顿rAF 批量后 3-5ms。
---
## §3 现成库评估
| 库 | Vue3 兼容 | 流式 | 安全 | 维护 | 结论 |
|---|---|---|---|---|---|
| **markstream-vue** | ✅ 原生组件 | ✅ 双模式(虚拟窗口 + 增量批处理) | ✅ 内置 safe HTML | ✅ 2.2k star / open issue 2 / 近乎日更 / 尤雨溪背书 / 1.0 稳定 | **首选** |
| streamdown (Vercel) | ❌ React 绑定 + 强绑 Tailwind/shadcn | ✅ | ✅ rehype-harden | ✅ | 仅参考issue #19 求 Vue 版未解决) |
| streaming-markdown | ⚠️ callback 驱动需自己接线 | ✅ token 级 | ❌ 需自配 | 功能不全 | 参考 |
| marked当前用 | ✅ | ❌ 无官方流式 API | ❌ 需自配 DOMPurify | ✅ 36.9k star | 不直用做流式层 |
| markdown-it | ✅ | ❌ 无原生流式 | - | ✅ | 同上 |
| antfu/shiki-stream | ✅ 组件 | ✅ 增量高亮 | - | ✅ 511 star | 代码块高亮专库 |
**核心结论**:有 Vue3 原生可直接用的流式 markdown 库 **markstream-vue**尤雨溪推荐命中全部诉求——流式有格式、不掉帧、TS-first、内置 sanitize、`final` prop 解未闭合 token 卡死。内部已含 marked + Shiki + sanitize 组合,等于替你造好轮子。
**降级路径**:若样式侵入太重,只用其解析内核 `stream-markdown-parser`(框架无关纯函数)+ 现有 DOMPurify 自控渲染。
---
## §4 落地方案(递进)
### 方案 ArAF 节流渲染(最小改动)
流式时 `requestAnimationFrame` 每帧最多 parse 一次(封顶 60fps`v-html` 绑响应式 `streamingHtml`。流式结束取消 rAF + 强制最终渲染落 mdCache。
- 性能:频率从「每 delta」降到「每帧」主线程有空隙让浏览器 paint。
- 观感:流式全程有格式;**瑕疵**:未闭合代码块(末尾 ```)会闪烁。
- 成本:低(~60 行)。
- 风险:超长回答(>20k 字)每帧 parse 全文仍偏重。
```js
const streamingHtml = ref('')
let rafId = null, lastStreamText = ''
function scheduleStreamParse(text){
if (rafId !== null) return
rafId = requestAnimationFrame(()=>{
rafId = null
if (text === lastStreamText) return
lastStreamText = text
streamingHtml.value = _purify.sanitize(_marked.parse(text))
})
}
watch(()=>store.state.streaming,(s,old)=>{
if(old && !s){ if(rafId) cancelAnimationFrame(rafId); rafId=null; lastStreamText=''; streamingHtml.value='' }
})
onBeforeUnmount(()=>{ if(rafId) cancelAnimationFrame(rafId) })
```
### 方案 B方案 A + 代码块流式降级(体验最佳,推荐起步)
在 A 基础上,流式期把 fenced code 块正则抠出降级为纯文本(`<pre class="md-code-streaming">`),其余正常 marked。流式结束一次完整 marked 替换。
- 消掉方案 A 的「未闭合代码块闪烁」瑕疵——AI 回答大量含代码块,高频可见。
- 成本:中(~100 行 + CSS
- **推荐起步方案**A→B 平滑叠加B 只替换 `doStreamParse` 一个函数,不返工)。
### 方案 CWeb Worker彻底解耦
marked+sanitize 移 Worker主线程零 parse。Worker 内 DOMPurify 需 jsdom shim或换 `sanitize-html` 免 DOM
- 成本:高(~150 行 + worker 文件 + jsdom
- 适用:极端长回答/多会话。方案 B 不够再上。
### 方案 D换库直上 markstream-vue
用 `<MarkdownRender :content :final />` 替换整个 renderMd 手写层。
- 成本:中(库接入 + 样式对接)。
- 收益:省自造轮子,拿到虚拟窗口 + 增量高亮 + 未闭合处理全套。
- 风险:样式侵入、新依赖、迁移工作量需评估。
---
## §5 推荐结论
**两条路,按风险偏好二选一**
| 路线 | 方案 | 收益 | 风险 | 工作量 |
|---|---|---|---|---|
| **保守(自研改造)** | 方案 BrAF 节流 + 代码块降级) | 流式全程有格式、不掉帧、消代码块闪烁、无新依赖 | 超长回答边界需观察 | 中(~100 行 + CSS |
| **激进(换库)** | 方案 Dmarkstream-vue | 同上 + 虚拟窗口 + 增量高亮 + 长期省维护 | 新依赖、样式侵入、迁移成本 | 中-高 |
**渐进建议**:先上方案 B 跑通(验证 rAF 节流 + 代码块降级在当前 delta 频率下不掉帧)→ 观察长回答边界 → 若需更强能力(虚拟窗口/增量高亮)再评估换 markstream-vue。
**当前 AR-1 临时方案(流式纯文本短路)由方案 D 替换后退役**——纯增量收益,流式全程有格式。
> **决策2026-06-15选方案 D**(换 markstream-vue。理由自研块级 memo 与 D 复杂度接近D 白送 shiki 代码高亮 + 虚拟窗口 + 未闭合 token 修复器,自研只在「坚决不引新依赖」时才划算。落地 todo:ARC-260615-08。
>
> **决策转向2026-06-15同日复盘方案 D 试装后弃用,改自研块级 memo方案 B 增强版)**。复盘markstream-vue@1.0.1 接入 + vue-tsc 通过无技术障碍,但「样式 100% 还原现有 .ai-md」成本高且脆——markstream DOM 异构于 marked 输出(代码块 `.code-block-container` chrome 结构 / 暗色 `--ms-*` 变量体系依赖 `.dark` class 而 DevFlow 用 `[data-theme]` / prose 作用域 `.markstream-vue`4 处对接且随 markstream 升级易漂移。用户优先级是「保留原样式」>「虚拟窗口/shiki 高级能力」,转自研块级 memomarked 输出标准 HTML → .ai-md 样式零对接,借鉴 §2 机制 2块级 memo业界主流实现 splitBlocks代码围栏整体一块/非代码双换行切)+ 前块缓存命中 O(末块) + 末块不缓存处理未闭合 token + rAF 节流。结论:**自研块级 memo = 方案 B 增强rAF + 真块级 memo非仅代码块降级零新依赖、零样式对接、D 级流式性能**。§5 原决策表「自研只在坚决不引新依赖时才划算」判断在「不要高级能力、要原样式」前提下反转——此时自研反而最优。落地ARC-260615-08 自研版已实施vue-tsc exit 0待 dev 运行时验证。
---
## 来源
- [Best practices to render streamed LLM responses — Chrome for Developers](https://developer.chrome.com/docs/ai/render-llm-responses)streaming-markdown 增量 append、Paint flashing 验证)
- [Markdown Chatbot with Memoization — Vercel AI SDK Cookbook](https://ai-sdk.dev/cookbook/next/markdown-chatbot-with-memoization)(块级 diff + memo 官方实现)
- [Streaming Backends & React: Controlling the Re-render Chaos — Sitepoint](https://www.sitepoint.com/streaming-backends-react-controlling-re-render-chaos/)rAF 实测数据)
- [antfu/shiki-stream — GitHub](https://github.com/antfu/shiki-stream)
- [markstream-vue — GitHub](https://github.com/Simon-He95/markstream-vue)
- [vercel/streamdown — GitHub](https://github.com/vercel/streamdown)
- [HuggingFace chat-ui: webworker for markdown parsing #1733](https://huggingface.co/spaces/jdelavande/chat-ui-energy/commit/7d6fc19984864d960f2875391858ea067b030dc6)
- [shikijs/shiki Discussion #891](https://github.com/shikijs/shiki/discussions/891)GrammarState 流式高亮底层)