import { Query } from './conversationModels'; import { Fragment, useEffect, useRef, useState } from 'react'; import { useTranslation } from 'react-i18next'; import { useNavigate, useParams } from 'react-router-dom'; import conversationService from '../api/services/conversationService'; import ConversationBubble from './ConversationBubble'; import Send from '../assets/send.svg'; import Spinner from '../assets/spinner.svg'; import { selectClientAPIKey, setClientApiKey, updateQuery, addQuery, fetchSharedAnswer, selectStatus, } from './sharedConversationSlice'; import { setIdentifier, setFetchedData } from './sharedConversationSlice'; import { useDispatch } from 'react-redux'; import { AppDispatch } from '../store'; import { selectDate, selectTitle, selectQueries, } from './sharedConversationSlice'; import { useSelector } from 'react-redux'; import { Helmet } from 'react-helmet'; export const SharedConversation = () => { const navigate = useNavigate(); const { identifier } = useParams(); //identifier is a uuid, not conversationId const queries = useSelector(selectQueries); const title = useSelector(selectTitle); const date = useSelector(selectDate); const apiKey = useSelector(selectClientAPIKey); const status = useSelector(selectStatus); const inputRef = useRef(null); const sharedConversationRef = useRef(null); const { t } = useTranslation(); const dispatch = useDispatch(); const [lastQueryReturnedErr, setLastQueryReturnedErr] = useState(false); const [eventInterrupt, setEventInterrupt] = useState(false); const endMessageRef = useRef(null); const handleUserInterruption = () => { if (!eventInterrupt && status === 'loading') setEventInterrupt(true); }; useEffect(() => { !eventInterrupt && scrollIntoView(); }, [queries.length, queries[queries.length - 1]]); useEffect(() => { identifier && dispatch(setIdentifier(identifier)); const element = document.getElementById('inputbox') as HTMLInputElement; if (element) { element.focus(); } }, []); useEffect(() => { if (queries.length) { queries[queries.length - 1].error && setLastQueryReturnedErr(true); queries[queries.length - 1].response && setLastQueryReturnedErr(false); //considering a query that initially returned error can later include a response property on retry } }, [queries[queries.length - 1]]); const scrollIntoView = () => { if (!sharedConversationRef?.current || eventInterrupt) return; if (status === 'idle' || !queries[queries.length - 1].response) { sharedConversationRef.current.scrollTo({ behavior: 'smooth', top: sharedConversationRef.current.scrollHeight, }); } else { sharedConversationRef.current.scrollTop = sharedConversationRef.current.scrollHeight; } }; const fetchQueries = () => { identifier && conversationService .getSharedConversation(identifier || '') .then((res) => { if (res.status === 404 || res.status === 400) navigate('/pagenotfound'); return res.json(); }) .then((data) => { if (data.success) { dispatch( setFetchedData({ queries: data.queries, title: data.title, date: data.date, identifier, }), ); data.api_key && dispatch(setClientApiKey(data.api_key)); } }); }; const handlePaste = (e: React.ClipboardEvent) => { e.preventDefault(); const text = e.clipboardData.getData('text/plain'); inputRef.current && (inputRef.current.innerText = text); }; const prepResponseView = (query: Query, index: number) => { let responseView; if (query.response) { responseView = ( ); } else if (query.error) { responseView = ( ); } return responseView; }; const handleQuestionSubmission = () => { if (inputRef.current?.textContent && status !== 'loading') { if (lastQueryReturnedErr) { // update last failed query with new prompt dispatch( updateQuery({ index: queries.length - 1, query: { prompt: inputRef.current.textContent, }, }), ); handleQuestion({ question: queries[queries.length - 1].prompt, isRetry: true, }); } else { handleQuestion({ question: inputRef.current.textContent }); } inputRef.current.textContent = ''; } }; const handleQuestion = ({ question, isRetry = false, }: { question: string; isRetry?: boolean; }) => { question = question.trim(); if (question === '') return; setEventInterrupt(false); !isRetry && dispatch(addQuery({ prompt: question })); //dispatch only new queries dispatch(fetchSharedAnswer({ question })); }; useEffect(() => { fetchQueries(); }, []); return ( <> {`DocsGPT | ${title}`}

{title}

{t('sharedConv.subtitle')}{' '} DocsGPT

{date}

{queries?.map((query, index) => { return ( {prepResponseView(query, index)} ); })}
{apiKey ? (
{ if (e.key === 'Enter' && !e.shiftKey) { e.preventDefault(); handleQuestionSubmission(); } }} >
{status === 'loading' ? ( ) : (
)}
) : ( )} {t('sharedConv.meta')}
); };