Skip to content

mosuzi/typewriter

Repository files navigation

typewriter

一个自适应打字机 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-room
pnpm build      # tsc -b && vite build
pnpm preview    # 本地预览生产构建

需要 Node 18+ 与 pnpm 9+。

🧪 推荐玩法

  1. 选「长段落 · 压力测试」预设 → 点发送,观察自适应节奏。
  2. chunk size 拉到 [80, 200]interval 拉到 [300, 600],模拟"大爆发
    • 慢空窗",能直观看到加速/减速过渡。
  3. 流到一半点「暂停」 → 文字定格;点「恢复」 → 继续追赶 queue。
  4. 切换到「Markdown · 含图片」预设,关掉「Markdown 图片」开关再发送一遍, ![alt](url) 会被切坏;打开开关再来一次,丝滑无瑕疵 —— 这是 skipRules 的可视化证明。

🧠 核心 API

useTypewriter

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

useSSESimulator

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
Loading

📦 技术栈

  • React 19 + TypeScript 5.9
  • Vite 7 + @vitejs/plugin-react-swc
  • react-router 7
  • SCSS ModulescamelCase 自动转换)
  • 零运行时 UI 框架依赖(手撸 SCSS,~7KB CSS / gzip 2KB)

📝 关于这套算法

详见 BLOG.md,里面拆解了:

  • 为什么 AI 流式回答需要打字机层
  • theoreticalSpeed 反馈环的三段式调速逻辑
  • skipRules 的"步长吞掉多匹配"嵌套 while 设计
  • enqueuestartsWith 判别替换/追加的小心思
  • [paused] effect 依赖与 clear() 之间一个真实踩过的坑

📄 License

MIT

About

A typewriter demo.

Resources

License

Stars

Watchers

Forks

Releases

No releases published

Packages

 
 
 

Contributors