'use client'; /* eslint-disable @next/next/no-img-element */ import React, { MutableRefObject, useEffect, useState } from 'react'; import { Message } from './ChatWindow'; import { cn } from '@/lib/utils'; import { BookCopy, Disc3, Volume2, StopCircle, Layers3, Plus, Brain, ChevronDown, } from 'lucide-react'; import Markdown from 'markdown-to-jsx'; import Copy from './MessageActions/Copy'; import Rewrite from './MessageActions/Rewrite'; import MessageSources from './MessageSources'; import SearchImages from './SearchImages'; import SearchVideos from './SearchVideos'; import { useSpeech } from 'react-text-to-speech'; const MessageBox = ({ message, messageIndex, history, loading, dividerRef, isLast, rewrite, sendMessage, }: { message: Message; messageIndex: number; history: Message[]; loading: boolean; dividerRef?: MutableRefObject; isLast: boolean; rewrite: (messageId: string) => void; sendMessage: (message: string) => void; }) => { const [parsedMessage, setParsedMessage] = useState(message.content); const [speechMessage, setSpeechMessage] = useState(message.content); const [thinking, setThinking] = useState(''); const [answer, setAnswer] = useState(''); const [isThinkingExpanded, setIsThinkingExpanded] = useState(false); useEffect(() => { const regex = /\[(\d+)\]/g; const thinkRegex = /(.*?)(?:<\/think>|$)(.*)/s; // Check for thinking content, including partial tags const match = message.content.match(thinkRegex); if (match) { const [_, thinkingContent, answerContent] = match; // Set thinking content even if hasn't appeared yet if (thinkingContent) { setThinking(thinkingContent.trim()); setIsThinkingExpanded(true); // Auto-expand when thinking starts } // Only set answer content if we have it (after ) if (answerContent) { setAnswer(answerContent.trim()); // Process the answer part for sources if needed if (message.role === 'assistant' && message?.sources && message.sources.length > 0) { setParsedMessage( answerContent.trim().replace( regex, (_, number) => `${number}`, ), ); } else { setParsedMessage(answerContent.trim()); } setSpeechMessage(answerContent.trim().replace(regex, '')); } } else { // No thinking content - process as before if (message.role === 'assistant' && message?.sources && message.sources.length > 0) { setParsedMessage( message.content.replace( regex, (_, number) => `${number}`, ), ); } else { setParsedMessage(message.content); } setSpeechMessage(message.content.replace(regex, '')); } }, [message.content, message.sources, message.role]); const { speechStatus, start, stop } = useSpeech({ text: speechMessage }); return (
{message.role === 'user' && (

{message.content}

)} {message.role === 'assistant' && (
{message.sources && message.sources.length > 0 && (

Sources

)}
{thinking && (
{isThinkingExpanded && (
{thinking.split('\n\n').map((paragraph, index) => { if (!paragraph.trim()) return null; const content = paragraph.replace(/^[•\-\d.]\s*/, ''); return (

{content}

); })}
)}
)}

Answer

{parsedMessage}
{loading && isLast ? null : (
{/* */}
)} {isLast && message.suggestions && message.suggestions.length > 0 && message.role === 'assistant' && !loading && ( <>

Related

{message.suggestions.map((suggestion, i) => (
{ sendMessage(suggestion); }} className="cursor-pointer flex flex-row justify-between font-medium space-x-2 items-center" >

{suggestion}

))}
)}
)}
); }; export default MessageBox;