Components/StreamText

StreamText

Text that streams in word by word at a steady pace.

VerifiedSince 0.6.3

Installation

EXAMPLE.BASH
pnpm add @talex-touch/tuffex
EXAMPLE.TYPESCRIPT
import { TxStreamText } from '@talex-touch/tuffex/stream-text'
import '@talex-touch/tuffex/stream-text/style.css'
import '@talex-touch/tuffex/inline-citation/style.css' // citation chips
import '@talex-touch/tuffex/base.css' // once per app

Usage

Streaming

Pass everything received so far as content, and keep streaming on while the source is live.

Loading demo...

Reveal Presets

reveal sets how a word enters. With complete content, replay() plays it again and reserve keeps the layout still.

Loading demo...

Slots and State

content also takes a list of runs: text with marks and links, citations, and custom runs.

Loading demo...

Best Practices

  • Pass the whole text so far, not the latest delta.
  • Keep streaming true until the source has really finished; leave it off for complete answers (history, replays) and use replay() to play them.
  • Turn on reserve when complete text plays back in a layout that must not move, such as a card or a bubble.
  • Copy and share the full text you hold: the display trails the source by up to maxLagMs.
  • The component is not a live region; wrap the conversation in role="log" to announce replies.

API Reference

Props

NameTypeDefaultDescription
contentstring | StreamInline[]—Everything received so far. Required.
streamingbooleanfalseThe source is live; a possibly unfinished last word waits for what follows or 200ms of quiet.
wordMsnumber24Releases one word every wordMs.
maxLagMsnumber600No word shows later than this after it arrives.
drainMsnumber320Once streaming turns false, releases the rest within this.
pauseMsnumber400Reports paused after this long without a new word.
reveal'aurora' | 'hue' | 'blur' | 'languid' | 'none''aurora'How a word enters; see StreamRevealPreset.
caretbooleantrueShows the caret while live; switched off, it goes at once.
reservebooleanfalseHolds the final layout with an invisible copy during playback; ignored while streaming.
pacedbooleantruefalse shows each word as it arrives, still animated; TxStreamElement passes it to pace parts itself.
appearbooleanfalseContent present at mount enters too. Read at mount.
tagstring'span'Root element.
localestring'zh'Locale for Intl.Segmenter word segmentation.

Events

NamePayloadDescription
state-change(state: StreamState)Fires when the stream changes state.
done—Fires once per play-through, when the last word shows and the source has finished.
cite(source: AiSourceItem)Fires when a citation chip opens; the chip never navigates.

Slots

NameScopeDescription
caret{ state }Replaces the Tuff caret; rendered only while live.
citation{ source, label, index }Replaces the citation chip; index is the marker's number.
inline{ name, props }Renders { type: 'custom' } runs.

Exposed Methods

NameTypeDescription
stateStreamStateThe current stream state.
replay() => voidPlays the whole content again from the first word, at wordMs.
skip() => voidShows everything now, without entrances.

Types

EXAMPLE.TYPESCRIPT
type StreamState = 'idle' | 'streaming' | 'paused' | 'draining' | 'done'

type StreamRevealPreset =
  | 'aurora' // fades out of a 4px blur through the blue–violet–pink sweep (default)
  | 'hue' // the sweep alone
  | 'blur' // the blur alone
  | 'languid' // rises slowly out of an 8px blur
  | 'none' // shows each word as it is released

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 Variables

VariableDefaultDescription
--tx-stream-reveal-1 / -2 / -3blue / violet / pink, per themeThe sweep's color stops; all equal the ink in high-contrast themes.
--tx-stream-caret-start / --tx-stream-caret-end#199ffe / #810dc6The caret gradient, from the Tuff logo.
--tx-stream-reveal-durationthe preset'sWritten onto the root from reveal; hosts do not set it.

Overview

  • Words go out every wordMs; a burst speeds the release so each word shows within maxLagMs of arriving. Once the source stops, the rest drains within drainMs.
  • States: idle → streaming → paused (no new word for pauseMs) → draining (source finished, backlog still going out) → done; the caret retracts on done.
  • Only new text enters: content present at mount shows at once, and a rewrite re-releases from the first changed word.
  • Text splits by word with Intl.Segmenter, punctuation staying with its word; without it, Latin splits on spaces and CJK by character.
  • href renders only for http(s), mailto, tel, relative, query, and hash URLs; //host stays text. Links carry rel="noopener noreferrer".
  • Under reduced motion, nothing enters or is paced and the caret rests; server rendering outputs plain text; the root carries aria-busy="true" while live.
  • The caret and the reserve copy are hidden from assistive technology (the copy is also inert); the text is not a live region, so wrap the conversation in role="log" to announce replies.

Technologies

  • The pacer use-stream-pacer.ts and the stream-reveal-* mixins in style/mixins.scss are shared by the stream family.
  • Motion follows the streaming text of kobra.systems (hue) and Beautiful UI (blur); the default aurora combines both. The caret is drawn from the Tuff logo.
  • Source: packages/tuffex/packages/components/src/stream-text/.
查看源码
packages/tuffex/packages/components/src/stream-text/index.ts

Use cases

  • An AI answer arriving token by token, in a chat, a panel, or a notification.
  • Replaying a stored answer or a scripted demo at a readable pace, without moving the layout.
  • A short generated sentence with sources, where each citation lands on its claim.