hmc-rag / frontend /src /components /ChatThread.tsx
webmuppet
chat: constrain thread to max-w-3xl centred column
2befd58
Raw
History Blame Contribute Delete
2.65 kB
/**
* 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;