Spaces:
Sleeping
Sleeping
| /** | |
| * ChatThread — renders the scrollable list of chat messages with auto-scroll. | |
| * | |
| * - Renders each message as a <MessageBubble /> with appropriate props | |
| * - Renders an in-flight streaming bubble when streaming is active | |
| * - Auto-scrolls to bottom on new messages or streaming content updates | |
| * | |
| * Requirements: 4.3 | |
| */ | |
| import { useEffect, useRef } from "react"; | |
| import { MessageBubble } from "./MessageBubble"; | |
| import type { Message, QueryResult } from "../types"; | |
| export interface ChatThreadProps { | |
| messages: Message[]; | |
| results: Record<string, QueryResult>; | |
| streamingContent: string; | |
| streamingIndex: number | null; | |
| isStreaming: boolean; | |
| conversationId: string; | |
| isSaved: (index: number) => boolean; | |
| onSave: (index: number) => void; | |
| onExport: (index: number) => void; | |
| } | |
| /** | |
| * Renders the chat message thread with auto-scroll behaviour. | |
| * | |
| * Each message in the `messages` array is rendered as a <MessageBubble />. | |
| * When `isStreaming` is true and `streamingIndex` is not null, an additional | |
| * assistant bubble is rendered with the current `streamingContent` (flushed | |
| * by the rAF loop in useStream). | |
| * | |
| * Auto-scroll fires whenever messages change or streaming content updates, | |
| * keeping the latest content visible without manual scrolling. | |
| */ | |
| export function ChatThread({ | |
| messages, | |
| results, | |
| streamingContent, | |
| streamingIndex, | |
| isStreaming, | |
| conversationId, | |
| isSaved, | |
| onSave, | |
| onExport, | |
| }: ChatThreadProps) { | |
| const bottomRef = useRef<HTMLDivElement>(null); | |
| // Auto-scroll to bottom when messages change or streaming content updates | |
| useEffect(() => { | |
| bottomRef.current?.scrollIntoView({ behavior: "smooth" }); | |
| }, [messages, streamingContent]); | |
| return ( | |
| <div className="flex-1 overflow-y-auto px-4 py-6"> | |
| <div className="max-w-3xl mx-auto"> | |
| {messages.map((message, index) => ( | |
| <MessageBubble | |
| key={`${conversationId}-${index}`} | |
| message={message} | |
| index={index} | |
| conversationId={conversationId} | |
| result={results[String(index)]} | |
| isSaved={isSaved(index)} | |
| onSave={onSave} | |
| onExport={onExport} | |
| /> | |
| ))} | |
| {/* In-flight streaming bubble */} | |
| {streamingIndex !== null && isStreaming && ( | |
| <MessageBubble | |
| key={`${conversationId}-streaming`} | |
| message={{ role: "assistant", content: streamingContent }} | |
| index={streamingIndex} | |
| conversationId={conversationId} | |
| isStreaming={true} | |
| /> | |
| )} | |
| {/* Scroll anchor */} | |
| <div ref={bottomRef} /> | |
| </div> | |
| </div> | |
| ); | |
| } | |
| export default ChatThread; | |