StreamText 流式文字
逐词匀速显影的流式文字
安装
pnpm add @talex-touch/tuffex
import { TxStreamText } from '@talex-touch/tuffex/stream-text'
import '@talex-touch/tuffex/stream-text/style.css'
import '@talex-touch/tuffex/inline-citation/style.css' // 引用 chip
import '@talex-touch/tuffex/base.css' // 全局引入一次
用法
流式输出
把目前收到的全部文字传给 content,源头输出期间保持 streaming 为真。
示例加载中...
显影预设
reveal 决定词的进场方式。内容完整时用 replay() 重播,reserve 让版面保持不动。
示例加载中...
插槽与状态
content 也接受片段数组:带样式与链接的文字、引用和自定义片段。
示例加载中...
最佳实践
- 传目前为止的完整文字,不要只传增量。
- 源头真正结束前一直保持
streaming为真;已完整的回答(历史、回放)不开,重播用replay()。 - 完整文字在不能挪动的布局(卡片、气泡)里回放时,打开
reserve。 - 复制、分享用你持有的完整文字:屏幕显示最多落后源头
maxLagMs。 - 组件不是 live region;需要播报回复时,把对话包在
role="log"里。
API 参考
属性
| 名称 | 类型 | 默认值 | 说明 |
|---|---|---|---|
content | string | StreamInline[] | — | 目前收到的全部内容,必填。 |
streaming | boolean | false | 源头仍在输出;可能未写完的末词等后文或静默 200ms 后放出。 |
wordMs | number | 24 | 每 wordMs 放出一个词。 |
maxLagMs | number | 600 | 任何词都在到达后这段时间内显示。 |
drainMs | number | 320 | streaming 变为 false 后,在这段时间内放完剩余部分。 |
pauseMs | number | 400 | 输出中超过这段时间没有新词即报告 paused。 |
reveal | 'aurora' | 'hue' | 'blur' | 'languid' | 'none' | 'aurora' | 词的进场方式,见 StreamRevealPreset。 |
caret | boolean | true | 流式进行时显示光标;关闭时立即移除。 |
reserve | boolean | false | 完整内容回放时用隐形副本占住最终版面;streaming 时忽略。 |
paced | boolean | true | 为 false 时词一到就显示(仍有进场);TxStreamElement 统一控制节奏时传 false。 |
appear | boolean | false | 挂载时已有的内容也播放进场;只在挂载时读取。 |
tag | string | 'span' | 根元素。 |
locale | string | 'zh' | Intl.Segmenter 分词所用的语言。 |
事件
| 名称 | 参数 | 说明 |
|---|---|---|
state-change | (state: StreamState) | 流式状态变化时触发。 |
done | — | 每次播放触发一次:末词已显示且源头已结束。 |
cite | (source: AiSourceItem) | 引用 chip 被打开时触发;chip 自身不跳转。 |
插槽
| 名称 | 作用域 | 说明 |
|---|---|---|
caret | { state } | 替换 Tuff 光标,只在流式进行时渲染。 |
citation | { source, label, index } | 替换引用 chip;index 为标记中的数字。 |
inline | { name, props } | 渲染 { type: 'custom' } 片段。 |
暴露方法
| 名称 | 类型 | 说明 |
|---|---|---|
state | StreamState | 当前流式状态。 |
replay | () => void | 从第一个词起按 wordMs 重播全部内容。 |
skip | () => void | 立即显示全部内容,不播放进场。 |
类型
type StreamState = 'idle' | 'streaming' | 'paused' | 'draining' | 'done'
type StreamRevealPreset =
| 'aurora' // 从 4px 模糊析出,同时扫过蓝紫粉色带(默认)
| 'hue' // 只扫色带
| 'blur' // 只有模糊
| 'languid' // 从 8px 模糊缓慢上浮
| 'none' // 放出即显示
type StreamInline =
| { type: 'text', text: string, marks?: ('strong' | 'em' | 'del' | 'code')[], href?: string }
| { type: 'citation', source: AiSourceItem, label?: string, index?: number }
| { type: 'custom', name: string, props?: Record<string, unknown> }
CSS 变量
| 变量 | 默认值 | 说明 |
|---|---|---|
--tx-stream-reveal-1 / -2 / -3 | 蓝 / 紫 / 粉,随主题变化 | 色带的三个颜色节点;高对比度主题下均为正文色。 |
--tx-stream-caret-start / --tx-stream-caret-end | #199ffe / #810dc6 | 光标渐变,取自 Tuff logo。 |
--tx-stream-reveal-duration | 取决于预设 | 由组件按 reveal 写入根节点,无需设置。 |
概述
- 词按
wordMs匀速放出;突发时提速,保证每个词在到达后maxLagMs内显示;源头结束后,剩余部分在drainMs内放完。 - 状态依次为
idle→streaming→paused(pauseMs内无新词)→draining(源头已结束,积压仍在放出)→done;光标在done时收起。 - 只有新内容进场:挂载时已有的内容直接显示;前文被改写时,从第一个改动的词起重新放出。
- 用
Intl.Segmenter按词切分,标点随所属的词;不支持时拉丁文按空格、中日韩文字按字切分。 href只对http(s)、mailto、tel、相对路径、查询串与 hash 链接生效,//开头的地址保持为文字;链接带rel="noopener noreferrer"。- 减弱动效时不进场、不控节奏,光标静止;服务端渲染为纯文本;流式进行时根节点带
aria-busy="true"。 - 光标与
reserve副本对辅助技术隐藏(副本同时inert);文字本身不是 live region,需要播报时把对话包在role="log"里。
技术实现
- 时钟
use-stream-pacer.ts与style/mixins.scss中的stream-reveal-*mixin 由流式家族共用。 - 动效参考 kobra.systems(
hue)与 Beautiful UI(blur)的 streaming text,默认的aurora合并两者;光标取自 Tuff logo。 - 源码:
packages/tuffex/packages/components/src/stream-text/。
查看源码
packages/tuffex/packages/components/src/stream-text/index.ts
使用场景
- 逐 token 到达的 AI 回答:对话、侧边面板、通知。
- 以可读的节奏回放存档的回答或脚本演示,版面不动。
- 带出处的简短生成句子,引用落在对应论断处。
相关组件
- StreamMarkdown:流式渲染完整的 Markdown 文档,使用同一套显影预设与光标。
- CodeStream:流式输出代码。
- InlineCitation:默认的引用 chip。
- Sources:在回答末尾列出来源。