{ "$schema": "https://ui.shadcn.com/schema/registry-item.json", "author": "DesignX ", "name": "streaming-response", "type": "registry:component", "title": "Streaming Response", "description": "Token-by-token text reveal with inline citations and a blinking caret.", "dependencies": [ "lucide-react@^0.577.0", "motion@^14.0.0" ], "registryDependencies": [ "@dx/citations", "@dx/lib-ease", "@dx/utils" ], "files": [ { "path": "components/agents/streaming-response.tsx", "type": "registry:component", "target": "components/agents/streaming-response.tsx", "content": "import {\n Check,\n ChevronDown,\n Copy,\n RotateCcw,\n ThumbsDown,\n ThumbsUp,\n} from \"lucide-react\";\nimport { AnimatePresence, motion, useReducedMotion } from \"motion/react\";\nimport {\n type ReactNode,\n useCallback,\n useEffect,\n useId,\n useRef,\n useState,\n} from \"react\";\nimport {\n type CitationItem,\n CitationList,\n CitationStack,\n} from \"@/components/agents/citations\";\nimport { AgentDisclosure } from \"@/components/agents/agent-disclosure\";\nimport { EASE_OUT, SPRING_PRESS, SPRING_SWAP } from \"@/lib/ease\";\nimport { cn } from \"@/lib/utils\";\n\nexport type StreamingResponseStatus = \"streaming\" | \"complete\" | \"error\";\nexport type StreamingResponseFeedback = \"up\" | \"down\" | null;\n\nexport interface StreamingResponseProps {\n /** Rendered response content. Pass plain text or the output of a Markdown renderer. */\n children: ReactNode;\n status?: StreamingResponseStatus;\n /** Plain-text value copied by the built-in copy action. */\n copyText?: string;\n /** Overrides the built-in clipboard action. */\n onCopy?: () => void | Promise;\n onRetry?: () => void;\n /** Optional sources shown as a compact footer disclosure after streaming. */\n sources?: CitationItem[];\n sourcesOpen?: boolean;\n defaultSourcesOpen?: boolean;\n onSourcesOpenChange?: (open: boolean) => void;\n sourceIdPrefix?: string;\n feedback?: StreamingResponseFeedback;\n defaultFeedback?: StreamingResponseFeedback;\n onFeedbackChange?: (feedback: StreamingResponseFeedback) => void;\n /** Set false when a surrounding conversation log announces streamed text. */\n announce?: boolean;\n /** Hides the built-in completion actions without changing response status. */\n showActions?: boolean;\n className?: string;\n contentClassName?: string;\n actionsClassName?: string;\n}\n\nfunction ResponseAction({\n label,\n active = false,\n onClick,\n children,\n}: {\n label: string;\n active?: boolean;\n onClick: () => void;\n children: ReactNode;\n}) {\n const reduce = useReducedMotion() ?? false;\n\n return (\n \n {children}\n \n );\n}\n\nexport function StreamingResponse({\n children,\n status = \"streaming\",\n copyText,\n onCopy,\n onRetry,\n sources = [],\n sourcesOpen,\n defaultSourcesOpen = false,\n onSourcesOpenChange,\n sourceIdPrefix,\n feedback,\n defaultFeedback = null,\n onFeedbackChange,\n announce = true,\n showActions = true,\n className,\n contentClassName,\n actionsClassName,\n}: StreamingResponseProps) {\n const reduce = useReducedMotion() ?? false;\n const baseId = useId();\n const [copied, setCopied] = useState(false);\n const [internalFeedback, setInternalFeedback] =\n useState(defaultFeedback);\n const [internalSourcesOpen, setInternalSourcesOpen] =\n useState(defaultSourcesOpen);\n const copyTimer = useRef(undefined);\n const currentFeedback = feedback ?? internalFeedback;\n const currentSourcesOpen = sourcesOpen ?? internalSourcesOpen;\n const streaming = status === \"streaming\";\n const complete = status === \"complete\";\n const canCopy = Boolean(copyText || onCopy);\n const hasSources = sources.length > 0;\n const shouldShowActions =\n showActions && !streaming && (canCopy || onRetry || complete || hasSources);\n const sourcesContentId = `${baseId}-sources`;\n const resolvedSourcePrefix =\n sourceIdPrefix ?? `response-source-${baseId.replace(/:/g, \"\")}`;\n\n useEffect(\n () => () => {\n if (copyTimer.current) window.clearTimeout(copyTimer.current);\n },\n [],\n );\n\n const handleCopy = useCallback(async () => {\n if (onCopy) await onCopy();\n else if (copyText) await navigator.clipboard?.writeText(copyText);\n\n setCopied(true);\n if (copyTimer.current) window.clearTimeout(copyTimer.current);\n copyTimer.current = window.setTimeout(() => setCopied(false), 1600);\n }, [copyText, onCopy]);\n\n const setFeedback = (next: Exclude) => {\n const value = currentFeedback === next ? null : next;\n if (feedback === undefined) setInternalFeedback(value);\n onFeedbackChange?.(value);\n };\n\n const setSourcesOpen = useCallback(\n (next: boolean) => {\n if (sourcesOpen === undefined) setInternalSourcesOpen(next);\n onSourcesOpenChange?.(next);\n },\n [onSourcesOpenChange, sourcesOpen],\n );\n\n return (\n \n \n {children}\n \n\n \n {shouldShowActions ? (\n \n
\n {canCopy ? (\n \n {copied ? (\n \n ) : (\n \n )}\n \n ) : null}\n {onRetry ? (\n \n \n \n ) : null}\n {complete ? (\n <>\n setFeedback(\"up\")}\n >\n \n \n setFeedback(\"down\")}\n >\n \n \n \n ) : null}\n {hasSources ? (\n setSourcesOpen(!currentSourcesOpen)}\n className=\"group ml-1 inline-flex min-h-7 items-center gap-2 rounded-md px-1.5 text-xs text-muted-foreground outline-none transition-colors hover:text-foreground focus-visible:ring-2 focus-visible:ring-ring\"\n >\n \n \n {sources.length} {sources.length === 1 ? \"source\" : \"sources\"}\n \n \n \n \n \n ) : null}\n
\n\n {hasSources ? (\n \n \n \n ) : null}\n \n ) : null}\n
\n \n );\n}\n" }, { "path": "components/agents/agent-disclosure.tsx", "type": "registry:component", "target": "components/agents/agent-disclosure.tsx", "content": "import { motion, type HTMLMotionProps, useReducedMotion } from \"motion/react\";\nimport type { CSSProperties } from \"react\";\nimport { EASE_OUT } from \"@/lib/ease\";\nimport { cn } from \"@/lib/utils\";\n\nexport interface AgentDisclosureProps\n extends Omit, \"animate\" | \"initial\"> {\n open: boolean;\n openHeight?: CSSProperties[\"height\"];\n}\n\n/** Shared transform-only reveal for collapsible agent content. */\nexport function AgentDisclosure({\n open,\n openHeight = \"auto\",\n className,\n style,\n transition,\n ...props\n}: AgentDisclosureProps) {\n const reduce = useReducedMotion() ?? false;\n\n return (\n \n );\n}\n" } ], "categories": [ "agents" ], "meta": { "group": "agents", "docs": "https://dxuireact.com/docs/components/streaming-response" } }