Skip to content

Repository files navigation

infinite-chat —— 用游戏引擎的思路做 LLM 对话渲染

verify

一个面向 LLM 对话的高性能渲染引擎:把大模型流式吐出的对话事件,渲染成一块流畅、 带丰富动效、可无限缩放平移的画布。用 Rust 写、编译成 WebAssembly、打包成 npm 包, React / Vue 可直接引入

🎬 在线官网(官网本身就是最大 demo):ohbonsai.github.io/infinite-chat —— 首页是全屏单 canvas 幕式播放器(引擎放映自己的宣传片:8 幕含字阵绽放高潮——整段对话飞起聚成 一朵花、花瓣飘落、指针可拨可点绽放,逐字皆 SDF 图元的编队;自动播 + 可接管 ←/→/进度点/滚轮/触摸; 无 GPU 走截图幻灯降级) · 完整会话重放 · Markdown 全类型 + playground · 图标画廊。部署与本地预演见 DEPLOY-pages;降级幻灯资产用 node scripts/gen-pages-assets.mjs 本地生成入库。

立场:LLM 时代的 chat,值得被重做

LLM 改变了一切。而人与 LLM 之间的主入口,就是 chat。 不是 IDE 插件、不是一堆表单和按钮——是一条对话。对话就是新的命令行、新的操作系统入口。

可现在的 chat 界面,坦白说基本都是垃圾:逼你一个任务开一个新对话、长会话越用越卡、富内容(代码 / 表格 / 公式 / 图)渲染得又丑又糙、流式吐字像打嗝、动效廉价或干脆没有。它们配不上背后那个正在改变世界的模型。

所以我们重做一个——一条永不结束、无比流畅、效果上限拉满的对话,配得上 LLM 时代的主入口。这不是又一个聊天框,是把 chat 当成第一公民界面来做。

核心信念:做好一个实时流式 + 高动效 + 弱网可丢消息的 AI 对话界面,本质上是在做一个 游戏引擎——所以我们就用游戏引擎的手法做。详见 spec/decision/0000-overview


核心场景:无限会话(infinite session)

痛点:现在的 AI 对话工具逼你"一个任务开一个新对话"——做完一件事就新建会话,历史散落各处;可真实使用里一个会话经常 100+ 轮还停不下来,而"为每个任务不停建新对话"的体验很糟。

我要的:像微信聊天那样,和某个"对象"(某个 agent / 项目)的所有历史永远在一条会话里,一直往下滚——超长会话 / 无限会话(infinite session)。这是本项目最强的应用场景。

为什么这逼出一个游戏引擎:一条 100+ 轮、上万行、还在流式增长的会话,用普通 DOM / react 渲染会越用越卡(DOM 节点爆炸、reflow、内存只涨不降)。所以才用游戏引擎手法——GPU 实例化 + 块冻结(settled 不重排)+ 视口裁剪(只画可见)+ 无边画布——让会话无限长也始终丝滑,fps/内存只与"可见的一屏"成正比,与历史总量无关。

一句话:infinite-chat 的存在就是为了承载 infinite session——其余的流式丝滑、SDF 文字、无边画布、极致规模(见 TODO2 C),本质都是为"一条永不结束的会话依然流畅好看"服务。


这是什么 / 不是什么

  • :一个对话渲染引擎(画布 + 文字 + 嵌入块 + 动效 + 流式/容错状态机),以 可嵌入组件形态交付。前端框架只管外围控件(输入框、按钮、弹窗),对话画布交给本库。
  • 不是:一个 markdown→HTML 组件(那是 react-markdown / Streamdown 的活)。普通体量、 纯 DOM 的聊天,用 DOM 方案更省;本引擎的价值在无边画布 + GPU 动效 + 规模下不卡 (见 0011 的适用边界)。

交付形态

Rust 核心(流式/markdown 大脑) ──编译──▶ WebAssembly ──打包──▶ npm 包 ──▶ React / Vue 直接 import
  • 浏览器 / wasm 是固定主战场。原生(Tauri 等)不作为当前目标约束。
  • 图形层只有 wgpu 一个抽象:instance 开 BROWSER_WEBGPU | GLWebGPU 优先、WebGL2 自动兜底(同一份代码,已启用,待专测);Canvas2D 不做,极端无 GPU 交 a11y 的 DOM 镜像兜底。注意 WebGL2 无 compute,逐字 compute 特效为 WebGPU 专属(0003 §50011 §3.4)。

设计原则

三条定调(产品形态)

  1. 可嵌入组件:wasm 库,React / Vue 直接引入;前端框架无关,只暴露画布 + 少量配置。 → 因此包体要轻、依赖要省(这是否决重型文字栈/框架的根本原因)。
  2. 2D SDF 世界 + 无边画布:文字是 SDF 图元(非 DOM、非位图),与矩形/图片 quad 共用相机、视口裁剪、实例化;任意缩放清晰,支持 GPU shader 特效。
  3. 场景 = LLM 对话,FSM 驱动事件:对话事件流驱动一切;状态机贯穿回合收尾、标签区域、 嵌入块生命周期。

你容易忘、但同等重要的原则(已写在各 ADR 里)

  1. content→layout→render 三层契约,且 layout/render 可替换(0001 §2.2)。 语义角色(StyledSpan)进、像素/坐标出;换解析器/排版/渲染后端只动各自内部,契约不动。 这条是本项目所有"能换方案而不伤筋动骨"的根本
  2. 效果是数据,不是分支(0002 §5.1); 插件 = 注册表项,不是代码分支,可热加载(0006 §7)。
  3. 流式正确性是一等公民:平滑器(蓄水池匀速吐字,做法同网游平滑远端玩家)、 块冻结(settled 块不重算,只动尾块)、remend(尾部主动补全防半截语法闪烁)、 GPU spawn_time 淡入(逐字动画零 CPU 参与)。这套"流式大脑"是 Rust 核心最不可替代的价值。
  4. FSM 驱动的不止"事件":回合收尾看门狗(soft/hard 超时,"忘了 idle"兜底,0005)、 标签区域(0006 §5)、嵌入块 Placeholder→Loading→Ready→Failed(0004 §7.3)。
  5. 容错 / 对账 / 可恢复:catch-up vs live 双模、resync_from_snapshot、EventSource 自动重连、 幂等快照、确定性重放(0003)。刷新不丢历史、弱网不丢不错
  6. 降级与无障碍:WebGPU→WebGL2→Canvas2D 兜底;canvas 对屏幕阅读器是黑盒, 作为可嵌入组件必须配一层 DOM 镜像(否则部分接入方不可用,常比性能更早成否决项)。
  7. 安全:模型输出、插件注入的标签一律当数据,绝不执行、不当真 HTML 解析; 未知标签默认原样显示绝不静默吞掉(0006 §4/§6)。
  8. 让浏览器干重活:浏览器解码图片、Canvas2D 做文字整形(→TinySDF 生成 SDF)、 SVG/mermaid 交浏览器光栅化;wasm 只持元数据。
  9. 自有引擎,不被框架锁定:借算法不借框架——用 TinySDF/ESDT(一个算法), 否决 AntV G / egui / cosmic-text / glyphon(0011 §2)。
  10. GPU-driven:实例化 + 视口裁剪 + 块冻结 → 规模下每帧成本平(对比 DOM 节点爆炸的根本动机)。
  11. 工程严谨可测 + 运行时可观测:测试期——确定性重放、proptest、naga shader 构建期校验、native 测试为铁律 (testing-and-benchmark);运行时——?debug 节流帧统计(fps/帧耗时/发射 glyph vs 总量/atlas 占用·淘汰), 因为性能退化多是运行时、数据相关、GPU/主线程的,测试期抓不到(见 TODO 可观测性)。
  12. 字体自带、放宽 BR5:打包自选字体(@font-face 供 Canvas2D 光栅),接受字形非系统字体 (即"追平 GitHub 结构/配色可,但字形一致与自带字体互斥",00090011)。

关键技术选型(各有 ADR)

关切 选择 出处
markdown 解析 pulldown-cmark(经 vendored jcode-render-core),不手写 nom、不上 comrak 0010
自定义语法(<thinking>/:::/@/角标) 标签层 segmenter + 注册表,不动解析器 0006 / 0010
文字渲染 SDF 图元(移植 TinySDF/ESDT),逐字 compute/vertex/fragment 三层 0011
数据结构 命令日志→派生缓存、CPU 树/GPU 扁平网格双索引、定长瓦片 page-pool 图集、GPU-SDF/CPU-盒双表示 0011 §3.3
嵌入块(图片/mermaid) 降格为异步纹理块,浏览器光栅化,wasm 只持元数据 0004 §7 / 0007
多实例同步 见 ADR 0008

仓库结构

crates/
├── core/      # 流式/markdown 大脑:content(解析→StyledSpan)、store、fsm、app、frame —— 引擎无关、可测
├── render/    # WebGPU 渲染:atlas / scene / shaders(将从位图升级到 SDF,0011 退役清单)
└── wasm/      # wasm 绑定:ChatCanvas、transport、layout_bridge、glyph_bridge
web/           # 浏览器侧 harness + JS 桥(pretext-bridge / glyph-raster,0011 起让位给 SDF)
vendor/        # jcode-render-core(后端中立 markdown 文档模型)
spec/          # 设计文档:decision(ADR 0000–0011)、plan、research、architecture

功能一览:功能体检报告(Plan 39)

想快速看这个引擎能做什么?生成一份单文件功能体检报告 —— plan28–38 这一大轮的全部 人可感知功能,逐项 e2e 捕获截图/视频 + 人话解释(九大项 / 44 小项),装订成一个 自包含 HTML,双击即看。

生成

node test/feature-report.mjs              # 全量:逐项捕获截图/视频 → 生成报告(约 8 分钟)
node test/feature-report.mjs --only cards # 增量:只重捕某大项或某小项(如 cards / card-diff)
node test/feature-report.mjs --report-only # 只用现有资产重出报告(几秒,不重新捕获)

产物:test/results/feature-report.html(约 13 MB 单文件,所有图片/视频 base64 内嵌)。

报告本体与捕获资产都 gitignored(重跑即得,不入库);manifest / 生成器 / 捕获 spec 入库。

打开

file:// 下部分浏览器禁内嵌视频,起个本地静态服务最省事:

cd test/results && python3 -m http.server 8899
# 浏览器打开 http://localhost:8899/feature-report.html(推荐 Chrome / Edge,webm 兼容最好)

报告左侧是粘性目录(九大项 + 小项锚点),顶部总览标注 commit 与五层门状态,每个小项一张 卡片(标题 + 人话「你会看到什么」+ 出处 plan/ADR + 截图或可播放视频)。

常驻资产

  • 报告即测试:每项捕获都带可见性断言,任一项失败即红、不出残缺报告;清单结构由 web/src/feature-manifest.test.ts 锁进五层门,保清单不腐。
  • 每个大 plan 收口后重跑一次,功能不腐、介绍常新。
  • 单一真值源 = test/feature-manifest.json(增删功能点、改解释都在这里)。

现状

  • Plan 2(F–J)已完成:快照/过滤、滚动/视口裁剪/块冻结、markdown 角色化、回合收尾、 弱网容错;46 个 native 测试。详见并经审核:spec/plan/plan2_progress
  • Plan 3 方向:画布化(SDF 文字图元 + 相机 + 空间索引)、input/选区/hit-test、 嵌入块、富 shader 特效、DOM 镜像无障碍。

文档地图

设计决策按编号读:0000 总览 · 0001 架构契约 · 0002 事件管线/FSM · 0003 容错降级 · 0004 markdown 管线 · 0005 回合收尾 · 0006 标签层/扩展 · 0007 富媒体嵌入 · 0008 多实例同步 · 0009 文字渲染引擎 · 0010 markdown 解析策略 · 0011 SDF 文字图元 · 0012 调试器 GUI:HTML vs egui · 0013 数学(LaTeX)渲染策略 · 0014 表格两趟布局 · 0015 字形源解析与回退(Bitmap/TinySDF/MSDF) · 0016 streaming 形变渲染机制(past→current 双关键帧) · 0017 markdown 流式落地(提交前沿 + 保守预测/和解) · 0018 SDF 装饰/面板图元(参数化 shader 框 + 共享 storage buffer)

About

极致的LLM Chat体验, SDF的世界

Resources

Stars

1 star

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages