import { useEffect, useRef, type CSSProperties, type ReactNode } from 'react';
import { MessageBubble, type MessageBubbleProps } from './MessageBubble.js';
import { PromptComposer } from './PromptComposer.js';
import { useChat } from './useChat.js';
import type { Message, UseChatOptions } from './types.js';
export interface ChatStreamProps extends UseChatOptions {
/** Render slot above the messages list (e.g. a header or model picker). */
header?: ReactNode;
/** Render slot below the composer (e.g. a disclaimer). */
footer?: ReactNode;
/** Slot to render when there are no messages yet. */
emptyState?: ReactNode;
/** Per-message custom renderer. Defaults to . */
renderMessage?: (message: Message) => ReactNode;
/** Forwarded to every when default renderer is used. */
messageProps?: Omit;
/** Placeholder shown inside . */
placeholder?: string;
/** Tailwind escape hatch. */
className?: string;
/** Inline style escape hatch. */
style?: CSSProperties;
}
/**
* `` — opinionated composition of `useChat` +
* `` + ``. The 80% path for any chat UI
* in a ByteLyst product. Drop it in, point it at an endpoint, done.
*
* Behavior:
* - Auto-scrolls to bottom on new message (sticky-bottom pattern).
* - Shows Stop affordance while streaming.
* - Surfaces transport errors inline below the messages list.
* - Renders `emptyState` when there are zero messages.
*
* For more control, compose the primitives yourself — `useChat` returns
* everything you need.
*/
export function ChatStream({
header,
footer,
emptyState,
renderMessage,
messageProps,
placeholder,
className,
style,
...chatOptions
}: ChatStreamProps) {
const chat = useChat(chatOptions);
const scrollRef = useRef(null);
// Sticky-bottom scroll — only auto-scroll if the user was already at
// (or near) the bottom. Otherwise their reading position is preserved.
useEffect(() => {
const el = scrollRef.current;
if (!el) return;
const distanceFromBottom = el.scrollHeight - el.scrollTop - el.clientHeight;
if (distanceFromBottom < 80) {
el.scrollTop = el.scrollHeight;
}
}, [chat.messages]);
const messages = chat.messages;
const showEmpty = messages.length === 0 && !chat.isLoading;
return (