KI-Agenten
Streaming response
Zeigt eine Antwort, während sie geschrieben wird. Neue Wörter blenden ein, und ein Cursor markiert das Ende.
Vorschau
Verwendung
streaming-response-demo.tsx
import { useEffect, useState } from 'react'
import { StreamingResponse } from '@/components/streaming-response'
const REPLY = 'Your order ships tomorrow morning. It should reach you on Thursday.'
export default function StreamingResponseDemo() {
const [shown, setShown] = useState('')
// Stand-in for a streamed model reply: pass the text so far and whether more is coming.
useEffect(() => {
const words = REPLY.split(/(\s+)/)
let i = 0
const id = setInterval(() => {
i += 2
setShown(words.slice(0, i).join(''))
if (i >= words.length) clearInterval(id)
}, 70)
return () => clearInterval(id)
}, [])
return <StreamingResponse text={shown} streaming={shown.length < REPLY.length} />
}Quellcode
streaming-response.tsx
import { cn } from '@/lib/utils'
export type StreamingResponseProps = {
/** The text so far. Append to it as tokens arrive. */
text: string
/** True while more text is coming. Shows the caret. */
streaming?: boolean
className?: string
}
/**
* Shows a reply as it is written. New words fade in; the caret blinks at the end while streaming.
* Paragraphs are split on blank lines. Under reduced motion the words appear without fading.
*/
export function StreamingResponse({ text, streaming = false, className }: StreamingResponseProps) {
const paragraphs = text.split(/\n{2,}/)
return (
<div className={cn('max-w-[65ch] text-base leading-7 text-pretty', className)} aria-busy={streaming}>
{paragraphs.map((p, i) => {
const last = i === paragraphs.length - 1
return (
<p key={i} className="mb-4 last:mb-0">
{p.split(/(\s+)/).map((word, j) =>
/^\s+$/.test(word) ? (
word
) : (
// A stable key per position: a word only animates the first time it renders.
<span key={j} className="animate-[stream-in_240ms_cubic-bezier(0.23,1,0.32,1)_both] motion-reduce:animate-none">
{word}
</span>
),
)}
{last && streaming && (
<span
className="ml-0.5 inline-block h-[1.1em] w-[2px] translate-y-[0.2em] animate-[stream-caret_1s_steps(1)_infinite] bg-foreground motion-reduce:animate-none"
aria-hidden
/>
)}
</p>
)
})}
<style href="manniche-streaming" precedence="default">{`
@keyframes stream-in { from { opacity: 0 } }
@keyframes stream-caret { 50% { opacity: 0 } }
`}</style>
</div>
)
}Installieren
npx shadcn@latest add https://mikkelmanniche.dk/lab/r/streaming-response.jsonMit eingetragener Registry (components.json)
npx shadcn@latest add @manniche/streaming-response