import 'katex/dist/katex.min.css'; import { forwardRef, Fragment, useEffect, useRef, useState } from 'react'; import { useTranslation } from 'react-i18next'; import ReactMarkdown from 'react-markdown'; import { useSelector } from 'react-redux'; import { Prism as SyntaxHighlighter } from 'react-syntax-highlighter'; import { oneLight, vscDarkPlus, } from 'react-syntax-highlighter/dist/cjs/styles/prism'; import rehypeKatex from 'rehype-katex'; import remarkGfm from 'remark-gfm'; import remarkMath from 'remark-math'; import ChevronDown from '../assets/chevron-down.svg'; import Cloud from '../assets/cloud.svg'; import DocsGPT3 from '../assets/cute_docsgpt3.svg'; import Dislike from '../assets/dislike.svg?react'; import Document from '../assets/document.svg'; import DocumentationDark from '../assets/documentation-dark.svg'; import Edit from '../assets/edit.svg'; import Like from '../assets/like.svg?react'; import Link from '../assets/link.svg'; import Sources from '../assets/sources.svg'; import UserIcon from '../assets/user.svg'; import Accordion from '../components/Accordion'; import Avatar from '../components/Avatar'; import CopyButton from '../components/CopyButton'; import MermaidRenderer from '../components/MermaidRenderer'; import Sidebar from '../components/Sidebar'; import Spinner from '../components/Spinner'; import SpeakButton from '../components/TextToSpeechButton'; import { useDarkTheme, useOutsideAlerter } from '../hooks'; import { selectChunks, selectSelectedDocs, } from '../preferences/preferenceSlice'; import classes from './ConversationBubble.module.css'; import { FEEDBACK, MESSAGE_TYPE } from './conversationModels'; import { ToolCallsType } from './types'; const DisableSourceFE = import.meta.env.VITE_DISABLE_SOURCE_FE || false; const ConversationBubble = forwardRef< HTMLDivElement, { message?: string; type: MESSAGE_TYPE; className?: string; feedback?: FEEDBACK; handleFeedback?: (feedback: FEEDBACK) => void; thought?: string; sources?: { title: string; text: string; link: string }[]; toolCalls?: ToolCallsType[]; retryBtn?: React.ReactElement; questionNumber?: number; isStreaming?: boolean; handleUpdatedQuestionSubmission?: ( updatedquestion?: string, updated?: boolean, index?: number, ) => void; filesAttached?: { id: string; fileName: string }[]; } >(function ConversationBubble( { message, type, className, feedback, handleFeedback, thought, sources, toolCalls, retryBtn, questionNumber, isStreaming, handleUpdatedQuestionSubmission, filesAttached, }, ref, ) { const { t } = useTranslation(); const [isDarkTheme] = useDarkTheme(); // const bubbleRef = useRef(null); const chunks = useSelector(selectChunks); const selectedDocs = useSelector(selectSelectedDocs); const [isLikeHovered, setIsLikeHovered] = useState(false); const [isEditClicked, setIsEditClicked] = useState(false); const [isDislikeHovered, setIsDislikeHovered] = useState(false); const [isQuestionHovered, setIsQuestionHovered] = useState(false); const [editInputBox, setEditInputBox] = useState(''); const messageRef = useRef(null); const [shouldShowToggle, setShouldShowToggle] = useState(false); const [activeTooltip, setActiveTooltip] = useState(null); const [isSidebarOpen, setIsSidebarOpen] = useState(false); const editableQueryRef = useRef(null); const [isQuestionCollapsed, setIsQuestionCollapsed] = useState(true); useOutsideAlerter(editableQueryRef, () => setIsEditClicked(false), [], true); useEffect(() => { if (messageRef.current) { const height = messageRef.current.scrollHeight; setShouldShowToggle(height > 84); } }, [message]); const handleEditClick = () => { setIsEditClicked(false); handleUpdatedQuestionSubmission?.(editInputBox, true, questionNumber); }; let bubble; if (type === 'QUESTION') { bubble = (
setIsQuestionHovered(true)} onMouseLeave={() => setIsQuestionHovered(false)} className={className} >
{filesAttached && filesAttached.length > 0 && (
{filesAttached.map((file, index) => (
Attachment
{file.fileName}
))}
)}
} /> {!isEditClicked && ( <>
{message}
{shouldShowToggle && ( )}
)}
{isEditClicked && (