流式文字节拍器与微渐显预设
大模型网络 Token 到达脉冲抖动、useStreamPacer 恒定节拍调度、Aurora 微渐显与全屏单光标交接契约。
流式文字节拍器与微渐显预设
在大模型生成界面中,Token 的到达具有高度不确定性。直接将网络包刷入 DOM 会造成严重的视觉痉挛感。
AI 大模型网络 Token 到达的非均匀性
由于服务端推理排队与网络传输抖动,流式输出经常表现为:
- 首字长达数百毫秒卡顿(TTFT 延迟)。
- 突然一个 TCP 包整块倾泻数十个字。
- 随后再次停顿数秒。
如果缺乏调度缓冲,屏幕上的文字会在“冻结”与“暴雨刷屏”之间剧烈跳跃,用户视线完全无法跟进阅读。
useStreamPacer 时钟调度状态机
TuffEx 的 TxStreamText 与 TxStreamElement 将底层网络流与渲染呈现解耦,通过内部时钟平滑输出。TxStreamText 内部这样调用节拍器(节选):
// 节拍参数取自 props,不传时用 STREAM_PACING_DEFAULTS
const pacer = useStreamPacer({
total: () => available.value, // 已到达的词元数
streaming: () => props.streaming, // 源是否仍在输出
wordMs: () => props.wordMs, // 默认 24:至少每 24ms 释放一个词元
maxLagMs: () => props.maxLagMs, // 默认 600:每个词元最迟在到达后 600ms 显示
drainMs: () => props.drainMs, // 默认 320:源结束后,剩余积压在 320ms 内出清
pauseMs: () => props.pauseMs, // 默认 400:源未结束、400ms 没有词元可放,状态记为 paused
})
- 正常阶段:至少每 24ms 释放一个词元。
- 持续追赶:流式期间,释放速率取「1 ÷ wordMs」与「积压 ÷ 最老未显示词元剩余的期限」中较大的一个,期限是它的到达时间加
maxLagMs(600ms)。整段积压在这个期限前均匀放完,突发读起来是一段加快的字流,而不是一次砸下一大块;源一直快过 24ms 一个词元时,显示延迟稳定在略低于 600ms。 - 收尾:源结束的那一刻,速率定为「1 ÷ wordMs」与「积压 ÷ drainMs」中较大的一个,剩余积压在 320ms 内放完。
- 暂停:源仍在输出、显示已追平且 400ms 没有新的释放时,状态变为
paused。光标的外观不随状态变化,任何实时状态下都是同一个环绕加呼吸的动画。 - 末词等待:
TxStreamText会留住最后一个还没写完的词,等到空白、收尾标点,或源安静 200ms 后再放出。
useStreamPacer 也从 stream-text 导出,可以单独使用,total 与 streaming 两项必填。
微渐显风格预设(Aurora 与 Blur)
新释放的词元在进入 DOM 时,可配置微妙的过渡效果:
aurora(默认,460ms):从 4px 模糊中淡入,颜色同时由蓝经紫、粉扫到正文墨色。hue(180ms):同样的淡入与色彩扫过,但没有模糊。blur(420ms):只从 4px 模糊中淡入,不带颜色。languid(900ms):从 8px 模糊中淡入,同时上浮 4px。none(0ms):纯文本即时呈现。
系统开启「减弱动态效果」时,TxStreamText 跳过节拍、不播放入场,文字一到达就以最终样式出现。
示例加载中...
全屏唯一光标交接契约
当复杂的 Markdown 响应由段落、列表、代码块等多个子组件构成时: 全屏严禁出现多个闪烁光标! 后序组件接管光标的瞬间,前序组件必须通过状态传递立即销毁或隐藏自身光标;流式完全结束后,尾部光标优雅淡出收拢。