import { ReactNode, useCallback, useEffect, useRef } from 'react' import { Message } from '@/api/lightrag' import useTheme from '@/hooks/useTheme' import Button from '@/components/ui/Button' import { cn } from '@/lib/utils' import ReactMarkdown from 'react-markdown' import remarkGfm from 'remark-gfm' import rehypeReact from 'rehype-react' import remarkMath from 'remark-math' import mermaid from 'mermaid' import type { Element } from 'hast' import { Prism as SyntaxHighlighter } from 'react-syntax-highlighter' import { oneLight, oneDark } from 'react-syntax-highlighter/dist/cjs/styles/prism' import { LoaderIcon, CopyIcon } from 'lucide-react' import { useTranslation } from 'react-i18next' export type MessageWithError = Message & { isError?: boolean } export const ChatMessage = ({ message }: { message: MessageWithError }) => { const { t } = useTranslation() // Remove extra spaces around bold text message.content = message.content.replace(/\* {3}/g, '').replace(/ {4}\*\*/g, '**') const handleCopyMarkdown = useCallback(async () => { if (message.content) { try { await navigator.clipboard.writeText(message.content) } catch (err) { console.error(t('chat.copyError'), err) } } }, [message, t]) // Added t to dependency array return (
{message.content === '' &&{message.content} {message.role === 'assistant' && message.content && message.content.length > 0 && ( // Added check for message.content existence )}
Waiting for complete diagram...
'; return; // Don't attempt to render potentially incomplete content } const processedContent = rawContent .split('\n') .map(line => { const trimmedLine = line.trim(); // Keep subgraph processing if (trimmedLine.startsWith('subgraph')) { const parts = trimmedLine.split(' '); if (parts.length > 1) { const title = parts.slice(1).join(' ').replace(/["']/g, ''); return `subgraph "${title}"`; } } return trimmedLine; }) .filter(line => !line.trim().startsWith('linkStyle')) // Keep filtering linkStyle .join('\n'); const mermaidId = `mermaid-${Date.now()}`; mermaid.render(mermaidId, processedContent) .then(({ svg, bindFunctions }) => { // Check ref again inside async callback // Ensure the container is still the one we intended to update if (mermaidRef.current === container) { container.innerHTML = svg; if (bindFunctions) { try { // Add try-catch around bindFunctions as it can also throw bindFunctions(container); } catch (bindError) { console.error('Mermaid bindFunctions error:', bindError); // Optionally display a message in the container container.innerHTML += 'Diagram interactions might be limited.
'; } } } else { console.log('Mermaid container changed before rendering completed.'); } }) .catch(error => { console.error('Mermaid rendering promise error (debounced):', error); console.error('Failed content (debounced):', processedContent); if (mermaidRef.current === container) { const errorMessage = error instanceof Error ? error.message : String(error); // Make error display more robust const errorPre = document.createElement('pre'); errorPre.className = 'text-red-500 text-xs whitespace-pre-wrap break-words'; errorPre.textContent = `Mermaid diagram error: ${errorMessage}\n\nContent:\n${processedContent}`; container.innerHTML = ''; // Clear previous content container.appendChild(errorPre); } }); } catch (error) { console.error('Mermaid synchronous error (debounced):', error); console.error('Failed content (debounced):', String(children)); if (mermaidRef.current === container) { const errorMessage = error instanceof Error ? error.message : String(error); const errorPre = document.createElement('pre'); errorPre.className = 'text-red-500 text-xs whitespace-pre-wrap break-words'; errorPre.textContent = `Mermaid diagram setup error: ${errorMessage}`; container.innerHTML = ''; // Clear previous content container.appendChild(errorPre); } } }, 300); // 300ms debounce delay } // Cleanup function to clear the timer return () => { if (debounceTimerRef.current) { clearTimeout(debounceTimerRef.current); } }; }, [language, children, theme]); // Dependencies // Render based on language type if (language === 'mermaid') { // Container for Mermaid diagram return ; } // Handle non-Mermaid code blocks return !inline ? (
{children}
);
};