({ message })
| 11 | } |
| 12 | |
| 13 | const MessageBubble: React.FC<MessageBubbleProps> = ({ message }) => { |
| 14 | const isUser = message.role === 'user'; |
| 15 | |
| 16 | return ( |
| 17 | <div className={clsx('flex flex-col', isUser ? 'items-end' : 'items-start', 'mb-4')}> |
| 18 | <div className={clsx('flex items-center gap-2 mb-1', isUser ? 'flex-row-reverse' : 'flex-row')}> |
| 19 | <span className="text-xs font-medium flex items-center space-x-1"> |
| 20 | {!isUser && ( |
| 21 | <img src={logo} alt="AI" className="h-4 w-4" /> |
| 22 | )} |
| 23 | <span>{isUser ? 'You' : 'ScrapeCraft AI'}</span> |
| 24 | </span> |
| 25 | <span className="text-xs text-muted"> |
| 26 | {format(new Date(message.timestamp), 'HH:mm')} |
| 27 | </span> |
| 28 | </div> |
| 29 | <div className={clsx( |
| 30 | 'max-w-[80%] rounded-lg p-4 animate-slide-up', |
| 31 | isUser ? 'bg-primary text-white' : 'bg-secondary' |
| 32 | )}> |
| 33 | |
| 34 | <div className="text-sm"> |
| 35 | {isUser ? ( |
| 36 | <p className="whitespace-pre-wrap">{message.content}</p> |
| 37 | ) : ( |
| 38 | <div className="prose prose-sm prose-invert max-w-none |
| 39 | prose-headings:text-foreground prose-headings:font-semibold |
| 40 | prose-p:text-foreground prose-p:leading-relaxed |
| 41 | prose-a:text-accent prose-a:no-underline hover:prose-a:underline |
| 42 | prose-strong:text-foreground prose-strong:font-semibold |
| 43 | prose-code:text-accent prose-code:bg-code-bg prose-code:px-1 prose-code:py-0.5 prose-code:rounded prose-code:before:content-[''] prose-code:after:content-[''] |
| 44 | prose-pre:bg-code-bg prose-pre:text-code-text prose-pre:p-4 prose-pre:rounded-md prose-pre:overflow-x-auto |
| 45 | prose-ol:text-foreground prose-ul:text-foreground |
| 46 | prose-li:text-foreground prose-li:marker:text-muted |
| 47 | prose-blockquote:text-muted prose-blockquote:border-l-4 prose-blockquote:border-border prose-blockquote:pl-4"> |
| 48 | <ReactMarkdown |
| 49 | remarkPlugins={[remarkGfm]} |
| 50 | components={{ |
| 51 | code({className, children, ...props}: any) { |
| 52 | const match = /language-(\w+)/.exec(className || ''); |
| 53 | const isInline = !match; |
| 54 | |
| 55 | if (!isInline && match) { |
| 56 | return ( |
| 57 | <pre className="bg-code-bg text-code-text p-4 rounded-md overflow-x-auto"> |
| 58 | <code className={className} {...props}> |
| 59 | {children} |
| 60 | </code> |
| 61 | </pre> |
| 62 | ); |
| 63 | } |
| 64 | |
| 65 | return ( |
| 66 | <code className="text-accent bg-code-bg px-1 py-0.5 rounded text-sm" {...props}> |
| 67 | {children} |
| 68 | </code> |
| 69 | ); |
| 70 | } |
nothing calls this directly
no outgoing calls
no test coverage detected