chore: establish YCode replication baseline and TODO

This commit is contained in:
xiaohan.yuan committed 2026-10-17 02:43:49 +08:00
commit 6dc59f89ba
1118 files changed
+196002

No files matched your search

+205
View File
@@ -0,0 +1,205 @@
# Ygent 富内容渲染设计(HTML 制品 / 流程图 / 公式)
> 状态:设计提案,待确认
> 背景:`docs/reference/workbuddy/chat-tool-fold.md` 已摸清 WorkBuddy 的实现与安全边界
> 本文只讨论「对话区要支持哪些内容类型、怎么安全地渲染、分几步做」
---
## 1. 现状盘点
| 内容类型 | WorkBuddy | Ygent 现状 | 差距 |
|---|---|---|---|
| 段落 / 列表 / 引用 | ✓ | ✓(remark-gfm) | — |
| 表格 | ✓ | ✓ | — |
| 代码块高亮 | ✓ Prism | ✓ highlight.js | 可接受 |
| 代码 diff(改文件时) | ✓ `tool-diff` | ✗ | 见 §7 |
| 图片 | ✓ | ✓(react-markdown 默认) | — |
| **流程图 / 图表** | ✓ mermaid | **✗** | 需新增 |
| **数学公式** | ✓ KaTeX | **✗** | 需新增 |
| **HTML 制品** | ✓ sandbox iframe | **✗** | 需新增 |
| 内联 SVG(消毒) | ✓ DOMPurify | ✗ | 低优先 |
Ygent 当前渲染管线:`remarkGfm` + `rehypeHighlight`,**无 `rehype-raw`**(即不渲染 Markdown 里的裸 HTML)——这一点与 WorkBuddy 一致,是安全基线,**不要为了省事打开它**。
---
## 2. 两条进入路径(关键决策)
富内容有两种进入对话的方式,**建议两条都做,但优先级不同**:
### 路径 A:Markdown 内联(模型天然会用)
模型输出 ```mermaid 代码块或 `$$…$$` 公式,由渲染管线识别。
- 优点:**零工具改动**。模型不需要被教会调用新工具,它在别处已经习惯了这套写法。
- 证据:WorkBuddy 的 mermaid 就是这条路径——存在 i18n 键 `markdown.mermaid.rendering`(渲染中占位文案),说明是代码块触发而非工具产出。
- 适用:mermaid、KaTeX
### 路径 B:工具产出制品
专用工具(如 `show_html`)返回一段 HTML,以「制品块」形式呈现,带独立查看器 UI。
- 优点:有明确的类型边界,可以配查看器(切代码 / 复制 / 下载 / 导出图片)与独立的安全边界。
- 证据:WorkBuddy 的 `show_widget` 工具 + `SYn = {show_widget, visualize:show_widget, visualizer:show_widget}`,走 `<iframe sandbox="allow-scripts">`。
- 适用:HTML 制品(交互式页面、可视化)
---
## 3. 各类型的渲染方案与安全边界
### 3.1 mermaid 流程图(路径 A)
```
```mermaid
flowchart LR
A[用户] --> B[内核] --> C[结果]
```
```
渲染:` ```mermaid ` 代码块 → 动态加载 mermaid → `mermaid.render()` → 得到 SVG → **消毒** → 注入。
**消毒是必须的**,直接照抄 WorkBuddy 已验证的配置:
```js
DOMPurify.sanitize(svg, {
USE_PROFILES: { svg: true, svgFilters: true },
FORBID_TAGS: ["script", "foreignObject"],
FORBID_ATTR: ["srcdoc", "formaction"],
});
```
要点:
- **只开 SVG profile**(不含 HTML profile)——消毒结果只能表达图形,带不了 `<a>`/`<form>`
- **禁 `foreignObject`**:它能把 HTML 塞进 SVG 里绕过 SVG profile
- 渲染中显示占位(对应 `markdown.mermaid.rendering` 那种文案),失败时**回退显示原始代码**而不是空白
### 3.2 数学公式(路径 A)
`$inline$` 与 `$$block$$` → remark-math + rehype-katex → KaTeX 产出 HTML。
KaTeX 的输入是公式文本、输出是受控 HTML,**不需要额外消毒**(与 WorkBuddy 一致)。
### 3.3 HTML 制品(路径 B)
```jsx
<iframe sandbox="allow-scripts" srcDoc={html} title="交互制品" />
```
**不带 `allow-same-origin` 是整个方案的核心**:制品里的 JS 能跑(图表要动),但因为源是 opaque origin ——
读不到 `localStorage`、拿不到父窗口 DOM、发不出带 cookie 的请求。
已确认的前提:Ygent 的 `tauri.conf.json` 里 `"csp": null`,**不会拦 iframe srcdoc**。
若将来收紧 CSP,需为 iframe 放行(或在制品 HTML 内自带 `<meta http-equiv="Content-Security-Policy">`)。
查看器能力(参考 WorkBuddy 的 `widget.*` 文案):
显示 UI ⇄ 查看代码、复制代码、下载到本地、导出为图片、加载中态、错误面板。
---
## 4. 数据模型
**不需要新增 `MsgKind`。** 制品就是工具产出,复用现有 `tool` kind:
```ts
export type MsgKind = "user" | "agent" | "tool" | "perm"; // 不变
export interface ChatMessage {
// …现有字段
/** 制品:HTML 内容或落盘路径(见 §4.1) */
artifact?: { kind: "html"; path: string; title?: string };
/** 制品必须 keepVisible —— 折叠时不能被收走(否则制品整个消失) */
keepVisible?: boolean;
}
```
`toolState` 已在本轮补上,制品卡直接复用「运行中 / 完成」两态。
### 4.1 制品内容存哪(待决策)
| 方案 | 优点 | 缺点 |
|---|---|---|
| 存 `messages.result`(TEXT) | 零改动 | 大 HTML 撑大 SQLite;`list_messages` 变重 |
| **落项目工作目录 + DB 只存路径(建议)** | 符合「目录即项目」哲学;可被工具读回;DB 轻 | 需要文件生命周期管理 |
建议第二种:`.agent/artifacts/<message-id>.html`,DB 的 `result` 只存相对路径。
删除会话时一并清理(或按「永不删除项目目录」的铁律只解除登记)。
---
## 5. 与「回合分组 / 折叠」的关系(容易踩坑)
这是 WorkBuddy 用 `isShowWidget` + `keepVisible` + `presentationGroupId` 解决的问题,Ygent 必须一起考虑:
1. **制品不能被折叠掉。** 回合结束时会把连续工具块收成一行摘要——制品必须 `keepVisible`,
否则用户点了折叠,图表就没了。
2. **制品要和它的解释文字绑在一起。** WorkBuddy 的做法是制品块向前找最近的正文块,
共享 `presentationGroupId`。Ygent 目前用「回合」粒度分组(见 `ChatView.tsx` 的 `groupTurns`),
如果制品总是出现在回合末尾,回合粒度暂时够用;一旦出现「文字→制品→文字」的结构,
就需要下沉到块粒度。
3. **制品高度不定**,不能像工具卡那样单行省略。需要 iframe 通过 `postMessage` 上报高度自适应,
或给一个可拖拽/固定高度 + 内部滚动。
---
## 6. 体积代价(必须先决策)
| 依赖 | 体积 | 现状 |
|---|---|---|
| mermaid | **~2.8 MB**(WorkBuddy 的 `vendor-mermaid` chunk) | Ygent 前端 bundle 现在共 563 KB |
| katex | ~300 KB(含字体) | — |
| dompurify | ~45 KB | — |
**结论:一律动态 `import()`,按需加载,首次出现该内容类型时才拉。**
否则只为了偶尔一张流程图,就把首屏 bundle 撑到 3 MB+,冷启动明显变慢——这跟前面查到的「OMP 内核启动 1.31s」是同一类问题。
---
## 7. 顺带:代码 diff 视图(与富内容同级)
工具改文件时显示行级 diff,WorkBuddy 的 `tool-diff` 是现成参考:
- 输入 `oldCode` / `newCode` / `fileName`
- 按扩展名推断语言(`ts/tsx/js/py/rs/go/java/kt/swift/c/cpp/cs/php/css/html/vue/sh/yml/sql/scala`)
- Prism 懒加载该语言 + LRU 缓存,**语言不支持时回退 HTML 转义纯文本**(不要渲染空白)
- 行渲染 `+/−` 号
Ygent 的 `edit` / `write` 卡目前只显示「项目/新增文档.md +15」徽标。
要做 diff 的话,**必须用转义兜底**——文件内容绝不能当 HTML 注入。
---
## 8. 建议的分阶段落地
| 阶段 | 内容 | 成本 | 收益 |
|---|---|---|---|
| **P1** | mermaid 代码块(动态 import + DOMPurify SVG 消毒 + 失败回退原始代码) | 低 | 直观,模型零适配 |
| **P2** | KaTeX 公式(remark-math + rehype-katex,动态 import) | 低 | 技术对话可读性明显提升 |
| **P3** | 代码 diff 视图(`edit`/`write` 卡) | 中 | 直接改善现有工具卡 |
| **P4** | HTML 制品(工具 + iframe 查看器 + 存储策略) | 高 | 能力上限最高,但需要先定 §4.1 和 §5 |
P1 + P2 可以在一个改动里做完(都是 Markdown 管线扩展 + 动态加载),建议作为第一步。
---
## 9. 待决策清单
1. **制品 HTML 存 DB 还是落项目目录?**(建议落目录,见 §4.1)
2. **是否允许制品访问外网(CDN 脚本/字体)?** 现在 `csp: null` 允许;要不要收紧到白名单?
3. **制品高度**:`postMessage` 自适应,还是固定高度 + 内部滚动?
4. **mermaid 之外是否还要别的图表库**(如 ECharts)?WorkBuddy 的 `show_widget` 走的是「生成 HTML」而不是绑定图表库,这条路更通用但要制品能力先就位。
5. **是否给制品加「导出为图片」**?WorkBuddy 有(`widget.downloadImage`),但需要额外依赖做 HTML→图片。
---
## 附:与 WorkBuddy 的差异(有意为之)
| 项 | WorkBuddy | Ygent 建议 |
|---|---|---|
| 高亮库 | Prism | 保持 highlight.js(已用,换库无收益) |
| Markdown 裸 HTML | 未开启 | **同样不开启** |
| 制品渲染 | iframe sandbox | 同 |
| 内联 SVG 消毒 | DOMPurify SVG profile | 同(做 mermaid 时一并引入) |
| 虚拟列表 | 有 | **不做**(Ygent 无此瓶颈) |
| widget 分组 | 有(`presentationGroupId`) | **暂不做**,先用回合粒度;出现「文字→制品→文字」再下沉 |