WPS灵犀Claw如何实现Markdown文件的实时动态预览?

灵犀 Claw 作为 AI 对话客户端,每天需要将海量的 AI 流式输出实时渲染为格式化的 Markdown 文档。为了实现这一目标,灵犀引入了一套完整的渲染管线,这套渲染引擎实现了从 AI 流式输出到产品级预览,解决了 AI 场景特有的渲染难题。


一、整体架构

灵犀的 Markdown 渲染在前端进程(app.asar)内完成,基于 streamdown 库构建。streamdown 由 Vercel 开源,定位是"面向 AI 流式输出的 react-markdown 替代品"。灵犀在此基础上集成了代码高亮、图表渲染、数学公式和安全过滤等完整能力。

核心管线:

Markdown 文本流
    │
    ▼
streamdown (parseMarkdownIntoBlocks)  ← 逐块切分
    │
    ▼
remend (v1.2.2)                      ← 流式容错:补全未关闭的语法
    │
    ▼
unified 管线
    ├─ remark-parse (micromark)       ← Markdown → mdast
    ├─ remark-gfm                     ← GitHub Flavored Markdown 扩展
    ├─ remark-rehype                  ← mdast → hast
    ├─ rehype-raw                     ← 透传原始 HTML
    ├─ rehype-sanitize                ← HTML 安全过滤
    └─ rehype-harden                  ← 二次安全加固
    │
    ▼
React 组件树渲染
    ├─ 文本块(带动画)
    ├─ 代码块(shiki 高亮,明/暗主题)
    ├─ 表格(交互:复制、下载)
    ├─ Mermaid 图表(可选插件)
    └─ 数学公式(@streamdown/math,可选插件)

所有代码均打包在 renderer bundle(out/renderer/assets/index-z1lmW2JW.js)中,不依赖外部 CDN


二、streamdown:为 AI 场景而生的渲染引擎

2.1 核心设计理念

传统 Markdown 渲染库(如 react-markdown)假设输入是完整的文档。而灵犀的场景完全不同:AI 模型逐 token 输出,文本在每毫秒都可能增长或变化。这意味着渲染器必须面对:

  • 不完整的语法结构:代码围栏只开了头还没关、表格只写了一半、加粗标记只有开标签没有闭标签

  • 高频重渲染:每次收到新 token 都需要重新解析和渲染

  • 视觉连续性:渲染结果不能因中间状态而剧烈跳动

streamdown 通过两个机制解决这些问题:

逐块独立渲染parseMarkdownIntoBlocks 将 Markdown 文本按顶层块级元素(段落、代码块、表格、标题等)拆分为独立单元。每个块有自己完整的语法边界,可以独立解析、独立挂载到 DOM 上。当新 token 到达时,只需更新当前正在生长的块,已完成的块保持不变。

remend 流式容错。remend(v1.2.2)是 streamdown 的姊妹库,专门处理不完整的 Markdown 语法。从 app.asar 中提取的源码可以看到,remend 对以下场景做了容错处理:

  • 未关闭的代码围栏\``python\nprint("hello` 不会导致整个后续文本被吞入代码块,remend 会检测到围栏未关闭,自动补全闭合标记后再交给解析器

  • 未配对的加粗/斜体**important 在输出过程中不会产生格式泄漏,remend 会将其视为普通文本

  • 转义反引号:正确处理 \` 在代码块内外的语义差异,避免将转义的反引号误判为行内代码的边界

2.2 渲染模式配置

从 renderer bundle 中提取的默认配置:

mode: "streaming"
isAnimating: false
shikiTheme: ["github-light", "github-dark"]
controls: true
linkSafety: { enabled: true }
mermaid: void 0  // 默认关闭
  • mode: "streaming":启用流式渲染,每次 token 更新只处理变化的块

  • isAnimating: false:块入场动画默认关闭,可通过 StreamdownContext 按需开启

  • controls: true:代码块显示"复制"和"下载"按钮

2.3 入场动画

streamdown/styles.css定义了三种动画:

@keyframes sd-fadeIn { from { opacity: 0 } to { opacity: 1 } }
@keyframes sd-blurIn { from { opacity: 0; filter: blur(4px) } to { opacity: 1; filter: blur(0) } }
@keyframes sd-slideUp { from { opacity: 0; transform: translateY(4px) } to { opacity: 1; transform: translateY(0) } }
 
[data-sd-animate] {
  animation: var(--sd-animation, sd-fadeIn) var(--sd-duration, 150ms) var(--sd-easing, ease) both;
}

isAnimating: true 时,每个新渲染的块会带动画出现,动画持续 150ms,默认使用 fadeIn 效果。用户可通过 CSS 变量 --sd-animation--sd-duration--sd-easing 自定义。


三、代码高亮:shiki

3.1 双主题支持

灵犀使用 shiki 进行代码高亮,默认配置了明暗双主题:

shikiTheme: ["github-light", "github-dark"]

渲染后的代码块通过 CSS 变量实现主题切换:

  • --sdm-bg:代码块背景色

  • --shiki-dark-bg:暗色主题下的代码块背景

  • --shiki-dark:暗色主题的 class 标记

  • --sdm-c:文字颜色

这意味着可以在不重新解析代码的情况下,仅通过切换 CSS 变量即可在明暗主题间切换代码高亮。

3.2 语言支持

renderer bundle 中内置了一个包含 295 条映射规则的语言表。部分映射:

输入标识

shiki 语言

输入标识

shiki 语言

python / py

py

typescript / ts

ts

javascript / js

js

java

java

c

c

cpp

cpp

rust / rs

rs

go

go

sql

sql

bash / sh

sh

docker / dockerfile

dockerfile

yaml / yml

yaml

json

json

latex / tex

tex

wasm

wasm

mermaid / mmd

mmd

文言

wy

wolfram / wl

wl

3.3 动态加载策略

HighlightedCodeBlockBody 组件通过 React lazy() + Vite __vitePreload 动态加载:

HighlightedCodeBlockBody = react.lazy(() => __vitePreload(() => import("./highlighted-body-TPN3WLV5-L3cR62FZ.js")))

这意味着 shiki 引擎(含 Oniguruma WASM 和语法 grammar)只有在页面首次出现代码块时才会加载,避免了首屏性能开销。


四、表格交互能力

streamdown 内置了表格交互组件,支持以下操作:

  • 复制表格TableCopyDropdown 将 HTML 表格转为多种格式(CSV / TSV / Markdown)复制到剪贴板

  • 下载表格TableDownloadButton 将表格导出为 CSV、TSV 或 Markdown 文件

这些组件通过 extractTableDataFromElement 从 DOM 中提取结构化数据,实现了纯前端的表格导出能力,无需后端参与。

代码块同样支持复制CodeBlockCopyButton)和下载源文件CodeBlockDownloadButton)。


五、可选插件

streamdown 采用插件化架构,灵犀注册了以下插件:

插件

来源

默认状态

说明

remarkGfm

内置 remark-gfm v4

启用

GitHub Flavored Markdown(表格、删除线、任务列表等)

codeMeta

streamdown 内置

启用

代码块元信息(语言标识、标题、行高亮等)

@streamdown/cjk

可选包 v1.0.2

未在 deps 中

CJK 字符优化(中日韩文本的排版处理)

@streamdown/math

可选包 v1.0.2

未在 deps 中

数学公式渲染(KaTeX/LaTeX)

@streamdown/mermaid

可选包 v1.0.2

未在 deps 中

Mermaid 图表渲染

其中 @streamdown/cjk@streamdown/math 虽在 devDependencies 中列出,但未出现在 dependencies 中,说明灵犀当前版本未启用数学公式和 CJK 优化插件。Mermaid 通过 StreamdownContextmermaid prop 按需注入。


六、安全机制

Markdown 渲染的输入来自 AI 模型输出,安全性至关重要。灵犀部署了三重过滤

层级

职责

第一层

rehype-raw

允许在 Markdown 中嵌入原始 HTML 标签(如 <details><kbd> 等)

第二层

rehype-sanitize

基于 HTML Sanitizer 规范,白名单放行安全标签和属性

第三层

rehype-harden

二次加固,额外过滤潜在的 XSS 向量

此外,linkSafety: { enabled: true } 启用了链接安全机制,对用户可点击的 URL 进行安全检查。


七、建议将 Markdown 预览能力引入 WPS 客户端

WPS 目前不支持直接打开和预览 .md 文件。用户查看 Markdown 文件通常需要:

  1. 使用第三方编辑器(VS Code、Typora 等)

  1. 或在浏览器中使用 Markdown 预览插件

  1. 或将 Markdown 转换为其他格式后再打开

这造成了工作流中断——用户在 Markdown 文件和 WPS 办公套件之间频繁切换。

具体建议:

在 WPS 客户端中增加 .md 文件的关联打开能力。双击 .md 文件时,在 WPS 内嵌的 Chromium 视图中渲染 Markdown 预览。渲染引擎可复用灵犀的 streamdown + shiki 方案,实现零依赖的纯前端渲染。

在预览基础上增加所见即所得的 Markdown 编辑。streamdown 的逐块渲染架构天然适合编辑场景——每个块可独立编辑、独立保存,不会影响其他块的状态。可引入 CodeMirror 或 Monaco Editor 作为代码块的编辑器。

将 Markdown 文件纳入 WPS 文档生态:

  • 支持 Markdown 与 WPS 文档格式(docx)的双向转换

  • 支持 Markdown 中嵌入 WPS 公式(与现有公式编辑器互通)

  • 支持 Mermaid 图表的渲染和导出(与 WPS 的 SmartArt 生态打通)

  • .md 文件可直接保存到金山文档云空间,在 Web 端同步预览

北京
浏览 88
收藏
7
分享
7 +1
1
+1
全部评论 1
 
cui
严重支持。
· 河南省
1
回复