Files
ycode/docs/reference/ygent-rich-content.md
T

9.0 KiB
Raw Blame History

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)

<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:

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) 暂不做,先用回合粒度;出现「文字→制品→文字」再下沉