Files
professor/components/chat/MessageBubble.tsx
Aodhan Collins f644937604 Initial commit — AI-powered coding tutor (Professor)
Next.js 16, React 19, Monaco editor, Anthropic SDK, multi-provider AI,
Wandbox Python execution, iframe HTML preview, SQLite auth + session persistence.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-04 21:48:34 +00:00

69 lines
2.7 KiB
TypeScript

'use client';
import ReactMarkdown from 'react-markdown';
import remarkGfm from 'remark-gfm';
import { Prism as SyntaxHighlighter } from 'react-syntax-highlighter';
import { vscDarkPlus } from 'react-syntax-highlighter/dist/esm/styles/prism';
import type { Message } from '@/types';
interface Props {
message: Message;
}
export default function MessageBubble({ message }: Props) {
const isUser = message.role === 'user';
if (isUser) {
return (
<div className="flex justify-end">
<div className="max-w-[85%] rounded-2xl rounded-tr-sm bg-blue-600 px-4 py-3 text-sm text-white">
{message.content}
</div>
</div>
);
}
return (
<div className="flex justify-start">
<div className="max-w-[92%] rounded-2xl rounded-tl-sm bg-zinc-800 px-4 py-3 text-sm text-zinc-200">
<ReactMarkdown
remarkPlugins={[remarkGfm]}
components={{
code({ className, children, ...rest }) {
const match = /language-(\w+)/.exec(className ?? '');
const isBlock = Boolean(match);
return isBlock ? (
<SyntaxHighlighter
style={vscDarkPlus as Record<string, React.CSSProperties>}
language={match![1]}
PreTag="div"
customStyle={{ margin: '0.5rem 0', borderRadius: '0.375rem', fontSize: '0.8rem' }}
>
{String(children).replace(/\n$/, '')}
</SyntaxHighlighter>
) : (
<code className="rounded bg-zinc-700 px-1 py-0.5 text-xs font-mono text-zinc-200" {...rest}>
{children}
</code>
);
},
p: ({ children }) => <p className="mb-2 last:mb-0 leading-relaxed">{children}</p>,
ul: ({ children }) => <ul className="mb-2 list-disc pl-5 space-y-1">{children}</ul>,
ol: ({ children }) => <ol className="mb-2 list-decimal pl-5 space-y-1">{children}</ol>,
li: ({ children }) => <li className="leading-relaxed">{children}</li>,
h1: ({ children }) => <h1 className="mb-2 text-base font-bold text-zinc-100">{children}</h1>,
h2: ({ children }) => <h2 className="mb-2 text-sm font-bold text-zinc-100">{children}</h2>,
h3: ({ children }) => <h3 className="mb-1 text-sm font-semibold text-zinc-200">{children}</h3>,
strong: ({ children }) => <strong className="font-semibold text-zinc-100">{children}</strong>,
blockquote: ({ children }) => (
<blockquote className="my-2 border-l-2 border-blue-500 pl-3 text-zinc-400 italic">{children}</blockquote>
),
}}
>
{message.content}
</ReactMarkdown>
</div>
</div>
);
}