DesignX
AI Chat@paxeer/ai-chat/streaming-textimplemented

Streaming text

Token stream renderer with Skeleton while empty.

Install

$pnpm exec designx add @paxeer/ai-chat/streaming-text

Preview

Loading preview...

Source code

components/designx/ai-chat/streaming-text.tsxtsx
"use client";

import { useEffect, useState } from "react";
import { Badge, Skeleton } from "@paxeer/core";

export function StreamingText({
  text = "Two working bids sit under 2479. Drafting 1.50 ETH at 2480.40 keeps you inside the current clip.",
  streaming = true,
}: {
  text?: string;
  streaming?: boolean;
}) {
  const [shown, setShown] = useState(streaming ? "" : text);

  useEffect(() => {
    if (!streaming) {
      setShown(text);
      return;
    }
    setShown("");
    let index = 0;
    const id = window.setInterval(() => {
      index += 2;
      setShown(text.slice(0, index));
      if (index >= text.length) window.clearInterval(id);
    }, 24);
    return () => window.clearInterval(id);
  }, [streaming, text]);

  const done = shown.length >= text.length;

  return (
    <div data-slot="streaming-text" className="flex flex-col gap-2">
      <Badge variant={done ? "secondary" : "default"}>{done ? "complete" : "streaming"}</Badge>
      {shown.length === 0 ? (
        <div className="flex flex-col gap-2">
          <Skeleton className="h-4 w-5/6" />
          <Skeleton className="h-4 w-2/3" />
        </div>
      ) : (
        <p className="text-[length:var(--md-sys-typescale-body-medium-size)] text-on-surface">
          {shown}
          {!done ? (
            <span aria-hidden className="ml-0.5 animate-pulse text-primary">
              ▍
            </span>
          ) : null}
        </p>
      )}
    </div>
  );
}

Dependencies