Spaces:
Running
Running
'use client'; | |
// import { ChatList } from '@/components/chat/ChatList'; | |
import Composer from '@/components/chat/Composer'; | |
import useVisionAgent from '@/lib/hooks/useVisionAgent'; | |
import { useScrollAnchor } from '@/lib/hooks/useScrollAnchor'; | |
import { useEffect } from 'react'; | |
import { ChatWithMessages } from '@/lib/types'; | |
import { ChatMessage } from './ChatMessage'; | |
import { Button } from '../ui/Button'; | |
import { cn } from '@/lib/utils'; | |
import { IconArrowDown } from '../ui/Icons'; | |
import { dbPostCreateMessage } from '@/lib/db/functions'; | |
import { Card } from '../ui/Card'; | |
import { useSetAtom } from 'jotai'; | |
import { selectedMessageId } from '@/state/chat'; | |
export interface ChatListProps { | |
chat: ChatWithMessages; | |
userId?: string | null; | |
} | |
export const SCROLL_BOTTOM = 120; | |
const ChatList: React.FC<ChatListProps> = ({ chat, userId }) => { | |
const { id, messages: dbMessages, userId: chatUserId } = chat; | |
const { append, isLoading, data } = useVisionAgent(chat); | |
// Only login and chat owner can compose | |
const canCompose = !chatUserId || userId === chatUserId; | |
const lastDbMessage = dbMessages[dbMessages.length - 1]; | |
const setMessageId = useSetAtom(selectedMessageId); | |
const { messagesRef, scrollRef, visibilityRef, isVisible, scrollToBottom } = | |
useScrollAnchor(SCROLL_BOTTOM); | |
// Scroll to bottom on init and highlight last message | |
useEffect(() => { | |
scrollToBottom(); | |
if (lastDbMessage.result) { | |
setMessageId(lastDbMessage.id); | |
} | |
// eslint-disable-next-line react-hooks/exhaustive-deps | |
}, []); | |
return ( | |
<Card | |
className="size-full max-w-5xl overflow-auto relative" | |
ref={scrollRef} | |
> | |
<div className="overflow-auto h-full p-4 z-10" ref={messagesRef}> | |
{dbMessages.map((message, index) => { | |
const isLastMessage = index === dbMessages.length - 1; | |
return ( | |
<ChatMessage | |
key={message.id} | |
message={message} | |
loading={isLastMessage && isLoading} | |
wipAssistantMessage={data} | |
/> | |
); | |
})} | |
<div | |
className="w-full" | |
style={{ height: SCROLL_BOTTOM }} | |
ref={visibilityRef} | |
/> | |
</div> | |
{canCompose && ( | |
<div className="absolute bottom-4 w-full"> | |
<Composer | |
// Use the last message mediaUrl as the initial mediaUrl | |
initMediaUrl={dbMessages[dbMessages.length - 1]?.mediaUrl} | |
disabled={isLoading} | |
onSubmit={async ({ input, mediaUrl: newMediaUrl }) => { | |
const messageInput = { | |
prompt: input, | |
mediaUrl: newMediaUrl, | |
}; | |
const resp = await dbPostCreateMessage(id, messageInput); | |
append(resp); | |
}} | |
/> | |
</div> | |
)} | |
{/* Scroll to bottom Icon */} | |
<Button | |
size="icon" | |
className={cn( | |
'absolute bottom-3 right-3 transition-opacity duration-300 size-6', | |
isVisible ? 'opacity-0' : 'opacity-100', | |
)} | |
onClick={() => scrollToBottom()} | |
> | |
<IconArrowDown className="size-3" /> | |
</Button> | |
</Card> | |
); | |
}; | |
export default ChatList; | |