Spaces:
Sleeping
Sleeping
| 'use client'; | |
| import ReactMarkdown from 'react-markdown'; | |
| import remarkGfm from 'remark-gfm'; | |
| import type { Components } from 'react-markdown'; | |
| interface MarkdownMessageProps { | |
| content: string; | |
| variant?: 'user' | 'assistant'; | |
| } | |
| export default function MarkdownMessage({ content, variant = 'assistant' }: MarkdownMessageProps) { | |
| const isUser = variant === 'user'; | |
| const components: Components = { | |
| // Headings | |
| h1: ({ children }) => ( | |
| <h1 className="text-lg font-bold mb-2 mt-3 first:mt-0 text-white">{children}</h1> | |
| ), | |
| h2: ({ children }) => ( | |
| <h2 className="text-base font-semibold mb-2 mt-3 first:mt-0 text-white">{children}</h2> | |
| ), | |
| h3: ({ children }) => ( | |
| <h3 className="text-sm font-semibold mb-1.5 mt-2 first:mt-0 text-white">{children}</h3> | |
| ), | |
| // Paragraphs | |
| p: ({ children }) => ( | |
| <p className="mb-2 last:mb-0 leading-relaxed">{children}</p> | |
| ), | |
| // Strong/Bold - for scripture references | |
| strong: ({ children }) => ( | |
| <strong className={`font-semibold ${isUser ? 'text-amber-200' : 'text-purple-300'}`}> | |
| {children} | |
| </strong> | |
| ), | |
| // Emphasis/Italic | |
| em: ({ children }) => ( | |
| <em className="italic text-neutral-300">{children}</em> | |
| ), | |
| // Links | |
| a: ({ href, children }) => ( | |
| <a | |
| href={href} | |
| target="_blank" | |
| rel="noopener noreferrer" | |
| className={`underline underline-offset-2 hover:no-underline transition-colors ${ | |
| isUser ? 'text-amber-400 hover:text-amber-300' : 'text-purple-400 hover:text-purple-300' | |
| }`} | |
| > | |
| {children} | |
| </a> | |
| ), | |
| // Lists | |
| ul: ({ children }) => ( | |
| <ul className="list-none space-y-1.5 my-2 ml-1">{children}</ul> | |
| ), | |
| ol: ({ children }) => ( | |
| <ol className="list-decimal list-inside space-y-1.5 my-2 ml-1">{children}</ol> | |
| ), | |
| li: ({ children }) => ( | |
| <li className="flex items-start gap-2"> | |
| <span className={`mt-2 w-1.5 h-1.5 rounded-full shrink-0 ${isUser ? 'bg-amber-400/60' : 'bg-purple-400/60'}`} /> | |
| <span className="flex-1">{children}</span> | |
| </li> | |
| ), | |
| // Blockquotes - for scripture | |
| blockquote: ({ children }) => ( | |
| <blockquote className={`my-3 pl-4 border-l-2 ${ | |
| isUser ? 'border-amber-500/40 bg-amber-500/5' : 'border-purple-500/40 bg-purple-500/5' | |
| } rounded-r-lg py-2 pr-3 italic`}> | |
| {children} | |
| </blockquote> | |
| ), | |
| // Code blocks | |
| code: ({ className, children }) => { | |
| const isInline = !className; | |
| if (isInline) { | |
| return ( | |
| <code className={`px-1.5 py-0.5 rounded text-xs font-mono ${ | |
| isUser ? 'bg-amber-500/20 text-amber-200' : 'bg-purple-500/20 text-purple-200' | |
| }`}> | |
| {children} | |
| </code> | |
| ); | |
| } | |
| return ( | |
| <code className="block p-3 my-2 rounded-lg bg-black/30 border border-white/10 text-xs font-mono overflow-x-auto text-neutral-200"> | |
| {children} | |
| </code> | |
| ); | |
| }, | |
| // Pre for code blocks | |
| pre: ({ children }) => ( | |
| <pre className="my-2 rounded-lg overflow-hidden">{children}</pre> | |
| ), | |
| // Horizontal rule | |
| hr: () => ( | |
| <hr className={`my-4 border-t ${isUser ? 'border-amber-500/20' : 'border-purple-500/20'}`} /> | |
| ), | |
| // Tables | |
| table: ({ children }) => ( | |
| <div className="my-3 overflow-x-auto"> | |
| <table className="w-full text-xs border-collapse">{children}</table> | |
| </div> | |
| ), | |
| thead: ({ children }) => ( | |
| <thead className={`${isUser ? 'bg-amber-500/10' : 'bg-purple-500/10'}`}>{children}</thead> | |
| ), | |
| tbody: ({ children }) => <tbody>{children}</tbody>, | |
| tr: ({ children }) => ( | |
| <tr className="border-b border-white/5">{children}</tr> | |
| ), | |
| th: ({ children }) => ( | |
| <th className="px-3 py-2 text-left font-semibold text-white">{children}</th> | |
| ), | |
| td: ({ children }) => ( | |
| <td className="px-3 py-2 text-neutral-300">{children}</td> | |
| ), | |
| }; | |
| return ( | |
| <div className={`prose prose-sm max-w-none ${isUser ? 'text-amber-50' : 'text-purple-50'}`}> | |
| <ReactMarkdown remarkPlugins={[remarkGfm]} components={components}> | |
| {content} | |
| </ReactMarkdown> | |
| </div> | |
| ); | |
| } | |