import React, { useEffect, useRef, useState } from 'react'; import ReactMarkdown, { Components } from 'react-markdown'; import { Prism as SyntaxHighlighter } from 'react-syntax-highlighter'; import { nightOwl } from 'react-syntax-highlighter/dist/esm/styles/prism'; import remarkGfm from 'remark-gfm'; import remarkMath from 'remark-math'; import rehypeKatex from 'rehype-katex'; import rehypeRaw from 'rehype-raw'; import SendIcon from '@mui/icons-material/Send'; import 'katex/dist/katex.min.css'; import { CopyButton } from './components/CopyButton'; // Define the code component props interface interface CodeComponentProps { node?: any; inline?: boolean; className?: string; children?: React.ReactNode; } // Add types for math components interface MathComponentProps { value: string; } // Extend Components type to include math components interface MarkdownComponents extends Components { math?: (props: MathComponentProps) => JSX.Element; inlineMath?: (props: MathComponentProps) => JSX.Element; } export type ChatMessage = { id: string; isSelf: boolean; name: string; message: string; timestamp: number; }; export type ChatTileProps = { messages: ChatMessage[]; onSend: (message: string) => void; isConnected?: boolean; }; const MessageContent = ({ message }: { message: string }) => { if (message.match(/^https?:\/\/.*\.(jpg|jpeg|png|gif|webp)$/i)) { return ( Shared ); } const components: MarkdownComponents = { code({ inline, className, children }: CodeComponentProps) { const match = /language-(\w+)|^(\w+)/.exec(className || ''); const language = match ? match[1] || match[2] : ''; if (!inline && language) { return (
{language}
{String(children).replace(/\n$/, '')}
); } return ( {String(children)} ); }, // Style links a: ({ node, children, ...props }) => ( {children} ), // Style tables table: ({ node, ...props }) => (
), th: ({ node, ...props }) => (
), td: ({ node, ...props }) => ( ), // Add special styling for math blocks math: ({ value }) => (
{value}
), inlineMath: ({ value }) => ( {value} ), }; return ( {message} ); }; export function ChatTile({ messages, onSend, isConnected = false }: ChatTileProps) { const [message, setMessage] = useState(''); const messagesEndRef = useRef(null); // Sort messages by timestamp before rendering const sortedMessages = [...messages].sort((a, b) => a.timestamp - b.timestamp); const handleSubmit = (e: React.FormEvent) => { e.preventDefault(); if (message.trim()) { onSend(message.trim()); setMessage(''); } }; useEffect(() => { messagesEndRef.current?.scrollIntoView({ behavior: 'smooth' }); }, [messages]); return (
{/* Messages container - scrollable */}
{sortedMessages.map((msg) => (
{msg.name}
))}
{/* Input area - fixed height */}
setMessage(e.target.value)} className={`flex-grow bg-gray-800/50 border border-gray-700/50 rounded-lg px-3 py-1.5 text-sm text-gray-100 focus:outline-none focus:ring-2 focus:ring-cyan-500/50 ${!isConnected && 'opacity-50 cursor-not-allowed'}`} placeholder={isConnected ? "Type a message..." : "Connect to send messages..."} disabled={!isConnected} />
); }