import React from 'react'; import { RiArrowDownLine } from '@remixicon/react'; import { ChatInput } from './ChatInput'; import { useSessionStore } from '@/stores/useSessionStore'; import { Skeleton } from '@/components/ui/skeleton'; import { OpenChamberLogo } from '@/components/ui/OpenChamberLogo'; import MessageList from './MessageList'; import { ScrollShadow } from '@/components/ui/ScrollShadow'; import { useChatScrollManager } from '@/hooks/useChatScrollManager'; import { useDeviceInfo } from '@/lib/device'; import { Button } from '@/components/ui/button'; import { OverlayScrollbar } from '@/components/ui/OverlayScrollbar'; export const ChatContainer: React.FC = () => { const { currentSessionId, messages, permissions, streamingMessageIds, isLoading, loadMessages, loadMoreMessages, updateViewportAnchor, sessionMemoryState, isSyncing, messageStreamStates, trimToViewportWindow, sessionActivityPhase, } = useSessionStore(); const streamingMessageId = React.useMemo(() => { if (!currentSessionId) return null; return streamingMessageIds.get(currentSessionId) ?? null; }, [currentSessionId, streamingMessageIds]); const { isMobile } = useDeviceInfo(); const sessionMessages = React.useMemo(() => { return currentSessionId ? messages.get(currentSessionId) || [] : []; }, [currentSessionId, messages]); const sessionPermissions = React.useMemo(() => { return currentSessionId ? permissions.get(currentSessionId) || [] : []; }, [currentSessionId, permissions]); const { scrollRef, handleMessageContentChange, getAnimationHandlers, showScrollButton, scrollToBottom, spacerHeight, pendingAnchorId, hasActiveAnchor, } = useChatScrollManager({ currentSessionId, sessionMessages, streamingMessageId, sessionMemoryState, updateViewportAnchor, isSyncing, isMobile, messageStreamStates, sessionPermissions, trimToViewportWindow, sessionActivityPhase, }); const memoryState = React.useMemo(() => { if (!currentSessionId) { return null; } return sessionMemoryState.get(currentSessionId) ?? null; }, [currentSessionId, sessionMemoryState]); const hasMoreAbove = Boolean(memoryState?.hasMoreAbove); const [isLoadingOlder, setIsLoadingOlder] = React.useState(false); React.useEffect(() => { setIsLoadingOlder(false); }, [currentSessionId]); const lastScrolledSessionRef = React.useRef(null); React.useLayoutEffect(() => { if (!currentSessionId || currentSessionId === lastScrolledSessionRef.current) { return; } lastScrolledSessionRef.current = currentSessionId; const container = scrollRef.current; if (container) { container.scrollTop = container.scrollHeight - container.clientHeight; } }, [currentSessionId, scrollRef]); const handleLoadOlder = React.useCallback(async () => { if (!currentSessionId || isLoadingOlder) { return; } const container = scrollRef.current; const prevHeight = container?.scrollHeight ?? null; const prevTop = container?.scrollTop ?? null; setIsLoadingOlder(true); try { await loadMoreMessages(currentSessionId, 'up'); if (container && prevHeight !== null && prevTop !== null) { const heightDiff = container.scrollHeight - prevHeight; container.scrollTop = prevTop + heightDiff; } } finally { setIsLoadingOlder(false); } }, [currentSessionId, isLoadingOlder, loadMoreMessages, scrollRef]); React.useEffect(() => { if (!currentSessionId) { return; } const hasSessionMessages = messages.has(currentSessionId); const existingMessages = hasSessionMessages ? messages.get(currentSessionId) ?? [] : []; if (existingMessages.length > 0) { return; } const load = async () => { try { await loadMessages(currentSessionId); } finally { if (typeof window === 'undefined') { scrollToBottom(); } else { window.requestAnimationFrame(() => { scrollToBottom(); }); } } }; void load(); }, [currentSessionId, loadMessages, messages, scrollToBottom]); if (!currentSessionId) { return (
); } if (isLoading && sessionMessages.length === 0 && !streamingMessageId) { const hasMessagesEntry = messages.has(currentSessionId); if (!hasMessagesEntry) { return (
{[1, 2, 3].map((i) => (
))}
); } } if (sessionMessages.length === 0 && !streamingMessageId) { return (
); } return (
{} {spacerHeight > 0 && hasActiveAnchor && (
{showScrollButton && sessionMessages.length > 0 && (
)}
); };