import { memo, useCallback, useEffect, useMemo, useRef, useState } from 'react' import { ThreadMessage } from '@janhq/core' import { useVirtualizer } from '@tanstack/react-virtual' import { useAtomValue } from 'jotai' import { loadModelErrorAtom } from '@/hooks/useActiveModel' import ChatItem from '../ChatItem' import LoadModelError from '../LoadModelError' import EmptyThread from './EmptyThread' import { getCurrentChatMessagesAtom } from '@/helpers/atoms/ChatMessage.atom' import { activeThreadAtom } from '@/helpers/atoms/Thread.atom' const ChatConfigurator = memo(() => { const messages = useAtomValue(getCurrentChatMessagesAtom) const currentThread = useAtomValue(activeThreadAtom) const [current, setCurrent] = useState([]) const loadModelError = useAtomValue(loadModelErrorAtom) const isMessagesIdentificial = ( arr1: ThreadMessage[], arr2: ThreadMessage[] ): boolean => { if (arr1.length !== arr2.length) return false return arr1.every((item, index) => item.id === arr2[index].id) } useEffect(() => { if ( !isMessagesIdentificial(messages, current) || messages.some((e) => e.thread_id !== currentThread?.id) ) { setCurrent(messages) } }, [messages, current, loadModelError, currentThread]) if (!messages.length) return return (
) }) const ChatBody = memo( ({ messages, loadModelError, }: { messages: ThreadMessage[] loadModelError?: string }) => { // The scrollable element for your list const parentRef = useRef(null) const prevScrollTop = useRef(0) const isUserManuallyScrollingUp = useRef(false) const count = useMemo( () => (messages?.length ?? 0) + (loadModelError ? 1 : 0), [messages, loadModelError] ) // The virtualizer const virtualizer = useVirtualizer({ count, getScrollElement: () => parentRef.current, estimateSize: () => 35, overscan: 5, }) useEffect(() => { if (isUserManuallyScrollingUp.current === true || !parentRef.current) return if (count > 0 && messages && virtualizer) { virtualizer.scrollToIndex(count - 1) } }, [ count, virtualizer, messages, loadModelError, isUserManuallyScrollingUp, ]) const items = virtualizer.getVirtualItems() virtualizer.shouldAdjustScrollPositionOnItemSizeChange = ( item, _, instance ) => { if (isUserManuallyScrollingUp.current === true) return false return ( // item.start < (instance.scrollOffset ?? 0) && instance.scrollDirection !== 'backward' ) } const handleScroll = useCallback((event: React.UIEvent) => { const currentScrollTop = event.currentTarget.scrollTop if (prevScrollTop.current > currentScrollTop) { isUserManuallyScrollingUp.current = true } else { const currentScrollTop = event.currentTarget.scrollTop const scrollHeight = event.currentTarget.scrollHeight const clientHeight = event.currentTarget.clientHeight if (currentScrollTop + clientHeight >= scrollHeight) { isUserManuallyScrollingUp.current = false } } if (isUserManuallyScrollingUp.current === true) { event.preventDefault() event.stopPropagation() } prevScrollTop.current = currentScrollTop }, []) return (
{items.map((virtualRow) => (
{loadModelError && virtualRow.index === count - 1 ? ( ) : ( )}
))}
) } ) export default memo(ChatConfigurator)