import { memo, useCallback, useState, useEffect, useRef } from 'react';
import { useRecoilValue } from 'recoil';
import { useForm } from 'react-hook-form';
import { Spinner } from '@librechat/client';
import { useParams } from 'react-router-dom';
import { Constants, buildTree } from 'librechat-data-provider';
import type { TMessage } from 'librechat-data-provider';
import type { ChatFormValues } from '~/common';
import { ChatContext, AddedChatContext, useFileMapContext, ChatFormProvider } from '~/Providers';
import { useGetMessagesByConvoId, useGetConversationCosts } from '~/data-provider';
import { useChatHelpers, useAddedResponse, useSSE } from '~/hooks';
import ConversationStarters from './Input/ConversationStarters';
import MessagesView from './Messages/MessagesView';
import Presentation from './Presentation';
import ChatForm from './Input/ChatForm';
import CostBar from './CostBar';
import Landing from './Landing';
import Header from './Header';
import Footer from './Footer';
import { cn } from '~/utils';
import store from '~/store';
function LoadingSpinner() {
return (
);
}
function ChatView({ index = 0 }: { index?: number }) {
const { conversationId } = useParams();
const rootSubmission = useRecoilValue(store.submissionByIndex(index));
const addedSubmission = useRecoilValue(store.submissionByIndex(index + 1));
const centerFormOnLanding = useRecoilValue(store.centerFormOnLanding);
const fileMap = useFileMapContext();
const [showCostBar, setShowCostBar] = useState(true);
const lastScrollY = useRef(0);
const { data: messagesTree = null, isLoading } = useGetMessagesByConvoId(conversationId ?? '', {
select: useCallback(
(data: TMessage[]) => {
const dataTree = buildTree({ messages: data, fileMap });
return dataTree?.length === 0 ? null : (dataTree ?? null);
},
[fileMap],
),
enabled: !!fileMap,
});
const { data: conversationCosts } = useGetConversationCosts(
conversationId && conversationId !== Constants.NEW_CONVO ? conversationId : '',
{
enabled: !!conversationId && conversationId !== Constants.NEW_CONVO && conversationId !== '',
},
);
const chatHelpers = useChatHelpers(index, conversationId);
const addedChatHelpers = useAddedResponse({ rootIndex: index });
useSSE(rootSubmission, chatHelpers, false);
useSSE(addedSubmission, addedChatHelpers, true);
useEffect(() => {
const handleScroll = (event: Event) => {
const target = event.target as HTMLElement;
const currentScrollY = target.scrollTop;
const scrollHeight = target.scrollHeight;
const clientHeight = target.clientHeight;
const distanceFromBottom = scrollHeight - currentScrollY - clientHeight;
const isAtBottom = distanceFromBottom < 10;
setShowCostBar(isAtBottom);
lastScrollY.current = currentScrollY;
};
const findAndAttachScrollListener = () => {
const messagesContainer = document.querySelector('[class*="scrollbar-gutter-stable"]');
if (messagesContainer) {
messagesContainer.addEventListener('scroll', handleScroll, { passive: true });
return () => {
messagesContainer.removeEventListener('scroll', handleScroll);
};
}
setTimeout(findAndAttachScrollListener, 100);
};
const cleanup = findAndAttachScrollListener();
return cleanup;
}, [messagesTree]);
const methods = useForm({
defaultValues: { text: '' },
});
let content: JSX.Element | null | undefined;
const isLandingPage =
(!messagesTree || messagesTree.length === 0) &&
(conversationId === Constants.NEW_CONVO || !conversationId);
const isNavigating = (!messagesTree || messagesTree.length === 0) && conversationId != null;
if (isLoading && conversationId !== Constants.NEW_CONVO) {
content = ;
} else if ((isLoading || isNavigating) && !isLandingPage) {
content = ;
} else if (!isLandingPage) {
content = (
)
}
costs={conversationCosts}
/>
);
} else {
content = ;
}
return (
{!isLoading &&
}
<>
{content}
{isLandingPage ? : }
{isLandingPage &&
}
>
);
}
export default memo(ChatView);