'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, } from 'lucide-react'; import Markdown, { MarkdownToJSX } 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'; import ThinkBox from './ThinkBox'; const ThinkTagProcessor = ({ children, thinkingEnded, }: { children: React.ReactNode; thinkingEnded: boolean; }) => { return ( ); }; const MessageBox = ({ message, messageIndex, history, loading, dividerRef, isLast, rewrite, sendMessage, statusText, }: { message: Message; messageIndex: number; history: Message[]; loading: boolean; dividerRef?: MutableRefObject; isLast: boolean; rewrite: (messageId: string) => void; sendMessage: (message: string) => void; statusText?: string; }) => { const [parsedMessage, setParsedMessage] = useState(message.content); const [speechMessage, setSpeechMessage] = useState(message.content); const [thinkingEnded, setThinkingEnded] = useState(false); useEffect(() => { const citationRegex = /\[([^\]]+)\]/g; const regex = /\[(\d+)\]/g; let processedMessage = message.content; if (message.role === 'assistant' && message.content.includes('')) { const openThinkTag = processedMessage.match(//g)?.length || 0; const closeThinkTag = processedMessage.match(/<\/think>/g)?.length || 0; if (openThinkTag > closeThinkTag) { processedMessage += ' '; // The extra is to prevent the the think component from looking bad } } if (message.role === 'assistant' && message.content.includes('')) { setThinkingEnded(true); } if ( message.role === 'assistant' && message?.sources && message.sources.length > 0 ) { setParsedMessage( processedMessage.replace( citationRegex, (_, capturedContent: string) => { const numbers = capturedContent .split(',') .map((numStr) => numStr.trim()); const linksHtml = numbers .map((numStr) => { const number = parseInt(numStr); if (isNaN(number) || number <= 0) { return `[${numStr}]`; } const source = message.sources?.[number - 1]; const url = source?.metadata?.url; if (url) { return `${numStr}`; } else { return ``; } }) .join(''); return linksHtml; }, ), ); setSpeechMessage(message.content.replace(regex, '')); return; } else if ( message.role === 'assistant' && message?.sources && message.sources.length === 0 ) { setParsedMessage(processedMessage.replace(regex, '')); setSpeechMessage(message.content.replace(regex, '')); return; } setSpeechMessage(message.content.replace(regex, '')); setParsedMessage(processedMessage); }, [message.content, message.sources, message.role]); const { speechStatus, start, stop } = useSpeech({ text: speechMessage }); const markdownOverrides: MarkdownToJSX.Options = { overrides: { think: { component: ThinkTagProcessor, props: { thinkingEnded: thinkingEnded, }, }, }, }; return ( {message.role === 'user' && ( {message.content} )} {message.role === 'assistant' && ( {message.sources && message.sources.length > 0 && ( Sources )} {loading && isLast && statusText ? statusText : 'Answer'} {parsedMessage} {loading && isLast ? null : ( {/* */} { if (speechStatus === 'started') { stop(); } else { start(); } }} className="p-2 text-black/70 dark:text-white/70 rounded-xl hover:bg-light-secondary dark:hover:bg-dark-secondary transition duration-200 hover:text-black dark:hover:text-white" > {speechStatus === 'started' ? ( ) : ( )} )} {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;
{suggestion}