MessageList.tsx (1971B)
1 import { useRef, useEffect } from "react"; 2 import type { Message as MessageType } from "../../store/chat"; 3 import { Message } from "../Message/Message"; 4 5 interface MessageListProps { 6 messages: MessageType[]; 7 onForkMessage?: (messageId: string) => void; 8 showForkButton?: boolean; 9 thinking?: boolean; 10 } 11 12 function TypingIndicator() { 13 return ( 14 <div className="flex items-center gap-2 px-4 py-3"> 15 <div className="flex gap-1"> 16 <span className="inline-block h-2 w-2 animate-bounce rounded-full bg-gray-500 [animation-delay:0ms]" /> 17 <span className="inline-block h-2 w-2 animate-bounce rounded-full bg-gray-500 [animation-delay:150ms]" /> 18 <span className="inline-block h-2 w-2 animate-bounce rounded-full bg-gray-500 [animation-delay:300ms]" /> 19 </div> 20 <span className="text-xs text-gray-500">Thinking...</span> 21 </div> 22 ); 23 } 24 25 export function MessageList({ 26 messages, 27 onForkMessage, 28 showForkButton = false, 29 thinking = false, 30 }: MessageListProps) { 31 const bottomRef = useRef<HTMLDivElement>(null); 32 33 useEffect(() => { 34 const container = bottomRef.current?.parentElement?.parentElement; 35 if (container && container.classList.contains('overflow-y-auto')) { 36 container.scrollTop = container.scrollHeight; 37 } 38 }, [messages, thinking]); 39 40 if (messages.length === 0 && !thinking) { 41 return ( 42 <div className="flex flex-1 items-center justify-center text-gray-500"> 43 Send a message to start 44 </div> 45 ); 46 } 47 48 return ( 49 <div className="flex-1 overflow-y-auto px-4 py-6"> 50 <div className="mx-auto max-w-3xl space-y-6"> 51 {messages.map((msg) => ( 52 <Message 53 key={msg.id} 54 message={msg} 55 showForkButton={showForkButton} 56 onFork={onForkMessage ? () => onForkMessage(msg.id) : undefined} 57 /> 58 ))} 59 {thinking && <TypingIndicator />} 60 <div ref={bottomRef} /> 61 </div> 62 </div> 63 ); 64 }