一个自适应打字机 React Hook 的可交互 playground,专门为 AI 流式回答场景设计。
把一段流式文本逐字"打"到屏幕上听起来像是 5 行代码就能搞定的事, 但当数据来源是真实 LLM SSE 流(忽快忽慢、块大小不规则、带 markdown 结构)时, 事情就复杂了。这个项目复刻并精简了一个生产环境跑了很久的打字机算法, 配上 SSE 模拟器和组件库式 demo 页面,让你可以实时调参看波形。
想看完整算法解析与踩坑笔记?直接看 BLOG.md。
- 自适应节奏 —— 根据流的累计平均速度动态调节步长,大块爆发时悄悄追赶, 慢空窗时匀速吐字,结束后优雅收尾。
- 结构感知截断 —— 用一组可配置的
skipRules正则,避免把英文单词、 markdown 图片![]()、列表标记等切坏。 - 替换/追加自动判别 ——
enqueue通过startsWith区分增量推进和重置, 调用方只管丢"完整累积串"。 - 配套 SSE 模拟器 ——
useSSESimulator按可调的块大小区间和间隔区间随机 切片,复刻真实流式数据的形态。 - 仿组件库 demo —— Ant Design / MUI 风格的 props 控制面板 + 实时聊天预览
- 实时统计(队列长度、滞后、有效 c·s⁻¹)。
cd typewriter
pnpm install
pnpm dev
# → http://localhost:5173/chat-roompnpm build # tsc -b && vite build
pnpm preview # 本地预览生产构建需要 Node 18+ 与 pnpm 9+。
- 选「长段落 · 压力测试」预设 → 点发送,观察自适应节奏。
- 把
chunk size拉到[80, 200]、interval拉到[300, 600],模拟"大爆发- 慢空窗",能直观看到加速/减速过渡。
- 流到一半点「暂停」 → 文字定格;点「恢复」 → 继续追赶 queue。
- 切换到「Markdown · 含图片」预设,关掉「Markdown 图片」开关再发送一遍,
会被切坏;打开开关再来一次,丝滑无瑕疵 —— 这是skipRules的可视化证明。
const { text, enqueue, pause, resume, clear, paused } = useTypewriter({
speed: 50, // tick 间隔(毫秒)
words: 2, // 每个 tick 至少前进的字符数
skipRules: [ // 禁止在这些匹配中间切断
/[A-Za-z]+\b/,
/!\[.*]\(.+\)/,
/\s+/,
],
});
useEffect(() => {
enqueue(streamingAccumulated); // 每次新 chunk 来了,把"完整累积串"丢进来
}, [streamingAccumulated]);
return <p>{text}</p>;const { start, stop, running } = useSSESimulator({
chunkSizeRange: [4, 24],
intervalRange: [80, 220],
onChunk: (accumulated) => enqueue(accumulated),
onDone: (full) => console.log("done"),
});
start("一段完整的目标文字…");src/
├── App.tsx # 首页(demo 索引)
├── routes/index.ts # react-router 路由表
├── hooks/
│ ├── useTypewriter.ts # 自适应打字机
│ └── useSSESimulator.ts # SSE 流式数据模拟器
└── pages/ChatRoom/
├── index.tsx # 聊天室 playground 主页
├── presets.ts # 4 条预设回复
└── components/
├── MessageBubble.tsx # 用户/AI 气泡
└── ControlPanel.tsx # 右侧 props 面板
flowchart LR
Sidebar[控制面板] --> Sim[useSSESimulator]
Sim -->|chunk| Acc[累积字符串]
Acc -->|enqueue| Hook[useTypewriter]
Hook -->|text| Bubble[Bot 消息气泡]
Bubble --> Preview[聊天预览卡片]
Sidebar -.pause/resume/clear.-> Hook
- React 19 + TypeScript 5.9
- Vite 7 +
@vitejs/plugin-react-swc - react-router 7
- SCSS Modules(
camelCase自动转换) - 零运行时 UI 框架依赖(手撸 SCSS,~7KB CSS / gzip 2KB)
详见 BLOG.md,里面拆解了:
- 为什么 AI 流式回答需要打字机层
theoreticalSpeed反馈环的三段式调速逻辑skipRules的"步长吞掉多匹配"嵌套 while 设计enqueue用startsWith判别替换/追加的小心思[paused]effect 依赖与clear()之间一个真实踩过的坑
MIT