import { useState, useRef, useEffect, useCallback } from 'react' import { ArrowDown, MessageSquare, PanelRightClose, Send } from 'lucide-react' import { Button } from '@/components/ui/button' import { Input } from '@/components/ui/input' import { Tooltip, TooltipContent, TooltipTrigger } from '@/components/ui/tooltip' import { ChatMessage } from './ChatMessage' import { useChatStore } from '@/stores/chatStore' import { useRoomStore } from '@/stores/roomStore' import { useChat } from '@/hooks/useChat' /** Threshold (px) to consider the user "at the bottom" of the scroll container */ const SCROLL_BOTTOM_THRESHOLD = 80 interface ChatPanelProps { onCollapse?: () => void } export function ChatPanel({ onCollapse }: ChatPanelProps) { const [input, setInput] = useState('') const [showNewMsgHint, setShowNewMsgHint] = useState(false) const messagesEndRef = useRef(null) const scrollContainerRef = useRef(null) const isAtBottomRef = useRef(true) const messages = useChatStore((s) => s.messages) const currentUser = useRoomStore((s) => s.currentUser) const { sendMessage } = useChat() // Track whether the user has scrolled to the bottom const handleScroll = useCallback(() => { const el = scrollContainerRef.current if (!el) return const atBottom = el.scrollHeight - el.scrollTop - el.clientHeight < SCROLL_BOTTOM_THRESHOLD isAtBottomRef.current = atBottom if (atBottom) setShowNewMsgHint(false) }, []) // Smart auto-scroll: only scroll to bottom if user was already at the bottom useEffect(() => { const frame = requestAnimationFrame(() => { if (isAtBottomRef.current) { messagesEndRef.current?.scrollIntoView({ behavior: 'smooth' }) } else { setShowNewMsgHint(true) } }) return () => cancelAnimationFrame(frame) }, [messages]) const scrollToBottom = useCallback(() => { messagesEndRef.current?.scrollIntoView({ behavior: 'smooth' }) setShowNewMsgHint(false) }, []) const handleSend = () => { if (!input.trim()) return sendMessage(input) setInput('') } return (
{/* Header */}
{onCollapse && ( 收起聊天 )} 聊天
{/* Messages */}
{messages.length === 0 ? (

还没有消息,开始聊天吧~

) : ( messages.map((msg) => ( )) )}
{/* New message hint */} {showNewMsgHint && ( )}
{/* Input */}
setInput(e.target.value)} onKeyDown={(e) => e.key === 'Enter' && handleSend()} className="flex-1" aria-label="输入聊天消息" /> 发送
) }