Streaming

Diffusez des réponses d’IA qui se lisent avec fluidité. Les morceaux irréguliers du modèle sont révélés à un rythme régulier qui accélère pour rattraper son retard, avec un léger fondu sur les nouveaux mots et un curseur pendant l’écriture.

Les modèles envoient le texte par rafales : quelques mots, une pause, puis toute une phrase d’un coup. Afficher chaque morceau à son arrivée donne une réponse saccadée. Streaming sépare ce que vous avez reçu de ce que vous affichez, et le révèle à un rythme régulier de 90 caractères par seconde.

Quand les morceaux s’accumulent, le rythme augmente pour combler l’écart en environ 0,3 seconde, et une fois le flux terminé, le reste est vidé en environ 0,12 seconde, donc le texte ne traîne jamais loin derrière le modèle. Les emoji et autres caractères composés ne sont jamais coupés en pleine révélation, et si le texte est remplacé au lieu d’être complété, comme après une régénération, la révélation repart de ce que les deux versions ont en commun.

Le texte qui n’est pas en streaming s’affiche en entier dès la première image, donc l’historique et les rendus serveur ne s’animent jamais. StreamingText ajoute un léger fondu sur les mots les plus récents et un curseur pendant l’écriture. useSmoothText vous donne la cadence seule, à placer devant n’importe quel moteur de rendu, Markdown compris.

  1. Ajouter le registre Pro à components.json

    components.json
    {
      "registries": {
        "@hextaui-pro": {
          "url": "https://hextaui.com/r/pro/{name}.json",
          "headers": {
            "Authorization": "Bearer ${HEXTAUI_PRO_TOKEN}"
          }
        }
      }
    }
  2. Ajouter votre token

    Créez un token sur votre page de compte et placez-le dans .env.local sous le nom HEXTAUI_PRO_TOKEN.

  3. Ajouter le block

    pnpm dlx shadcn@latest add @hextaui-pro/streaming

Avec AI SDK

Affichez chaque part de texte d’un message useChat. L’état propre de la part indique à StreamingText quand le modèle écrit encore, donc les messages terminés et l’historique apparaissent instantanément.

"use client"

import * as React from "react"
import { useChat } from "@ai-sdk/react"

import { StreamingText } from "@/components/blocks/streaming/streaming-text"

export function Chat() {
  const { messages, sendMessage, status } = useChat()
  const [input, setInput] = React.useState("")

  return (
    <div className="flex flex-col gap-4">
      {messages.map((message) => (
        <div key={message.id} className="flex flex-col gap-2">
          {message.parts.map((part, index) =>
            part.type === "text" ? (
              <StreamingText
                key={index}
                text={part.text}
                streaming={part.state === "streaming"}
              />
            ) : null
          )}
        </div>
      ))}
      <form
        onSubmit={(event) => {
          event.preventDefault()
          sendMessage({ text: input })
          setInput("")
        }}
      >
        <input
          value={input}
          disabled={status !== "ready"}
          onChange={(event) => setInput(event.target.value)}
        />
      </form>
    </div>
  )
}

Avec Markdown

Utilisez le hook seul pour lisser le texte avant n’importe quel moteur de rendu. Streamdown gère le Markdown inachevé pendant le streaming ; react-markdown fonctionne de la même façon.

"use client"

import { Streamdown } from "streamdown"

import { useSmoothText } from "@/components/blocks/streaming/use-smooth-text"

export function MarkdownAnswer({
  text,
  streaming,
}: {
  text: string
  streaming: boolean
}) {
  const smooth = useSmoothText(text, { streaming })

  return (
    <Streamdown mode="streaming" isAnimating={streaming || smooth.animating}>
      {smooth.text}
    </Streamdown>
  )
}

Avec n’importe quel flux

Ajoutez chaque morceau à une chaîne et passez-la. Cela fonctionne de la même façon pour fetch, les deltas d’OpenAI ou d’Anthropic, ou les server-sent events.

"use client"

import * as React from "react"

import { StreamingText } from "@/components/blocks/streaming/streaming-text"

export function Answer({ prompt }: { prompt: string }) {
  const [text, setText] = React.useState("")
  const [streaming, setStreaming] = React.useState(false)

  const ask = async () => {
    setText("")
    setStreaming(true)
    const response = await fetch("/api/chat", {
      method: "POST",
      body: JSON.stringify({ prompt }),
    })
    const reader = response
      .body!.pipeThrough(new TextDecoderStream())
      .getReader()
    for (;;) {
      const { done, value } = await reader.read()
      if (done) break
      setText((current) => current + value)
    }
    setStreaming(false)
  }

  return (
    <div className="flex flex-col gap-3">
      <button onClick={ask}>Ask</button>
      <StreamingText text={text} streaming={streaming} />
    </div>
  )
}

Anatomie

Les parties à composer, de l’extérieur vers l’intérieur.

PartieDescription
StreamingTextUn élément de texte prêt à l’emploi qui cadence, estompe et affiche le curseur. Utilisez-le pour les réponses en texte brut.
useSmoothTextLa cadence seule. Renvoie le texte à afficher maintenant et indique s’il est encore en train de rattraper son retard.

StreamingText

Accepte aussi toutes les props de div sauf children.

PropTypePar défaut
textTout ce qui a été reçu jusqu’ici. Ajoutez-y au fur et à mesure de l’arrivée des morceaux.
string–
streamingIndique si du texte va encore arriver. Affiche le curseur et garde ouverte la fenêtre de rattrapage.
booleanfalse
smoothDésactivez la cadence pour afficher le texte exactement comme reçu.
booleantrue
charsPerSecondRythme de lecture de base avant tout rattrapage.
number90

useSmoothText(text, options)

Renvoie { text, animating }. animating reste true jusqu’à ce que le texte affiché atteigne le texte complet.

PropTypePar défaut
textLe texte complet reçu jusqu’ici.
string–
options.streamingIndique si la source envoie encore.
booleanfalse
options.enabledQuand c’est false, le hook renvoie le texte inchangé.
booleantrue
options.charsPerSecondRythme de lecture de base.
number90
  • Le conteneur de texte est une région live polie avec aria-busy pendant le streaming, afin que les lecteurs d’écran annoncent la réponse une fois stabilisée plutôt que chaque caractère.
  • Le curseur est décoratif et masqué aux technologies d’assistance.
  • Avec réduction des animations, les mots apparaissent sans fondu et le curseur cesse de pulser. La cadence reste, car elle concerne la vitesse de lecture, pas le mouvement.

Construit avec

Les composants HextaUI gratuits dont Streaming est constitué. Chacun s’installe séparément.

Code

3 fichiers, ajoutés à components/blocks/streaming.