import { useRecoilValue } from 'recoil'; import { useParams } from 'react-router-dom'; import { useState, useRef, useMemo } from 'react'; import { EModelEndpoint } from 'librechat-data-provider'; import { useGetEndpointsQuery } from 'librechat-data-provider/react-query'; import type { MouseEvent, FocusEvent, KeyboardEvent } from 'react'; import { useConversations, useNavigateToConvo } from '~/hooks'; import { useUpdateConversationMutation } from '~/data-provider'; import { MinimalIcon } from '~/components/Endpoints'; import { NotificationSeverity } from '~/common'; import { useToastContext } from '~/Providers'; import DeleteButton from './DeleteButton'; import { getEndpointField } from '~/utils'; import RenameButton from './RenameButton'; import store from '~/store'; type KeyEvent = KeyboardEvent; export default function Conversation({ conversation, retainView, toggleNav, isLatestConvo }) { const params = useParams(); const currentConvoId = useMemo(() => params.conversationId, [params.conversationId]); const updateConvoMutation = useUpdateConversationMutation(currentConvoId ?? ''); const activeConvos = useRecoilValue(store.allConversationsSelector); const { data: endpointsConfig } = useGetEndpointsQuery(); const { refreshConversations } = useConversations(); const { navigateToConvo } = useNavigateToConvo(); const { showToast } = useToastContext(); const { conversationId, title } = conversation; const inputRef = useRef(null); const [titleInput, setTitleInput] = useState(title); const [renaming, setRenaming] = useState(false); const clickHandler = async (event: React.MouseEvent) => { if (event.button === 0 && event.ctrlKey) { toggleNav(); return; } event.preventDefault(); if (currentConvoId === conversationId) { return; } toggleNav(); // set document title document.title = title; // set conversation to the new conversation if (conversation?.endpoint === EModelEndpoint.gptPlugins) { let lastSelectedTools = []; try { lastSelectedTools = JSON.parse(localStorage.getItem('lastSelectedTools') ?? '') ?? []; } catch (e) { // console.error(e); } navigateToConvo({ ...conversation, tools: lastSelectedTools }); } else { navigateToConvo(conversation); } }; const renameHandler = (e: MouseEvent) => { e.preventDefault(); setTitleInput(title); setRenaming(true); setTimeout(() => { if (!inputRef.current) { return; } inputRef.current.focus(); }, 25); }; const onRename = (e: MouseEvent | FocusEvent | KeyEvent) => { e.preventDefault(); setRenaming(false); if (titleInput === title) { return; } updateConvoMutation.mutate( { conversationId, title: titleInput }, { onSuccess: () => refreshConversations(), onError: () => { setTitleInput(title); showToast({ message: 'Failed to rename conversation', severity: NotificationSeverity.ERROR, showIcon: true, }); }, }, ); }; const icon = MinimalIcon({ size: 20, iconURL: getEndpointField(endpointsConfig, conversation.endpoint, 'iconURL'), endpoint: conversation.endpoint, endpointType: conversation.endpointType, model: conversation.model, error: false, className: 'mr-0', isCreatedByUser: false, chatGptLabel: undefined, modelLabel: undefined, jailbreak: undefined, }); const handleKeyDown = (e: KeyEvent) => { if (e.key === 'Enter') { onRename(e); } }; const activeConvo = currentConvoId === conversationId || (isLatestConvo && currentConvoId === 'new' && activeConvos[0] && activeConvos[0] !== 'new'); const aProps = { className: 'group relative rounded-lg active:opacity-50 flex cursor-pointer items-center mt-2 gap-2 break-all rounded-lg bg-gray-200 dark:bg-gray-700 py-2 px-2', }; if (!activeConvo) { aProps.className = 'group relative grow overflow-hidden whitespace-nowrap rounded-lg active:opacity-50 flex cursor-pointer items-center mt-2 gap-2 break-all rounded-lg hover:bg-gray-200 dark:hover:bg-gray-800 py-2 px-2'; } return ( {icon}
{renaming === true ? ( setTitleInput(e.target.value)} onBlur={onRename} onKeyDown={handleKeyDown} /> ) : ( title )}
{activeConvo ? (
) : (
)} {activeConvo ? (
) : (
)} ); }