273 lines
7.6 KiB
TypeScript
273 lines
7.6 KiB
TypeScript
'use client';
|
|
|
|
import { Box, Link as MuiLink, Typography, Paper, useTheme } from '@mui/material';
|
|
import ReactMarkdown from 'react-markdown';
|
|
import remarkGfm from 'remark-gfm';
|
|
|
|
interface MarkdownMessageProps {
|
|
content: string;
|
|
inverse?: boolean;
|
|
}
|
|
|
|
export default function MarkdownMessage({ content, inverse }: MarkdownMessageProps) {
|
|
const theme = useTheme();
|
|
|
|
const codeBg = inverse
|
|
? 'rgba(255,255,255,0.18)'
|
|
: theme.palette.mode === 'light'
|
|
? '#F3F4F6'
|
|
: 'rgba(255,255,255,0.06)';
|
|
|
|
const codeColor = inverse ? 'white' : theme.palette.text.primary;
|
|
const borderColor = inverse ? 'rgba(255,255,255,0.25)' : theme.palette.divider;
|
|
const linkColor = inverse ? 'rgba(255,255,255,0.95)' : theme.palette.primary.main;
|
|
|
|
return (
|
|
<Box
|
|
sx={{
|
|
fontSize: '0.95rem',
|
|
lineHeight: 1.6,
|
|
color: 'inherit',
|
|
'& > *:first-of-type': { mt: 0 },
|
|
'& > *:last-of-type': { mb: 0 },
|
|
}}
|
|
>
|
|
<ReactMarkdown
|
|
remarkPlugins={[remarkGfm]}
|
|
components={{
|
|
h1: ({ children }) => (
|
|
<Typography
|
|
component="h1"
|
|
sx={{ fontSize: '1.25rem', fontWeight: 800, mt: 2, mb: 1, letterSpacing: '-0.01em' }}
|
|
>
|
|
{children}
|
|
</Typography>
|
|
),
|
|
h2: ({ children }) => (
|
|
<Typography
|
|
component="h2"
|
|
sx={{ fontSize: '1.1rem', fontWeight: 800, mt: 1.75, mb: 0.75, letterSpacing: '-0.005em' }}
|
|
>
|
|
{children}
|
|
</Typography>
|
|
),
|
|
h3: ({ children }) => (
|
|
<Typography
|
|
component="h3"
|
|
sx={{ fontSize: '1rem', fontWeight: 700, mt: 1.5, mb: 0.5 }}
|
|
>
|
|
{children}
|
|
</Typography>
|
|
),
|
|
h4: ({ children }) => (
|
|
<Typography
|
|
component="h4"
|
|
sx={{ fontSize: '0.95rem', fontWeight: 700, mt: 1.25, mb: 0.5 }}
|
|
>
|
|
{children}
|
|
</Typography>
|
|
),
|
|
p: ({ children }) => (
|
|
<Box component="p" sx={{ my: 0.75, '&:first-of-type': { mt: 0 } }}>
|
|
{children}
|
|
</Box>
|
|
),
|
|
strong: ({ children }) => (
|
|
<Box component="strong" sx={{ fontWeight: 700 }}>
|
|
{children}
|
|
</Box>
|
|
),
|
|
em: ({ children }) => (
|
|
<Box component="em" sx={{ fontStyle: 'italic' }}>
|
|
{children}
|
|
</Box>
|
|
),
|
|
ul: ({ children }) => (
|
|
<Box
|
|
component="ul"
|
|
sx={{
|
|
pl: 2.5,
|
|
my: 0.75,
|
|
'& li': { mb: 0.35 },
|
|
'& li::marker': {
|
|
color: inverse ? 'rgba(255,255,255,0.7)' : theme.palette.primary.main,
|
|
},
|
|
}}
|
|
>
|
|
{children}
|
|
</Box>
|
|
),
|
|
ol: ({ children }) => (
|
|
<Box
|
|
component="ol"
|
|
sx={{
|
|
pl: 2.5,
|
|
my: 0.75,
|
|
'& li': { mb: 0.35 },
|
|
'& li::marker': {
|
|
color: inverse ? 'rgba(255,255,255,0.7)' : theme.palette.primary.main,
|
|
fontWeight: 700,
|
|
},
|
|
}}
|
|
>
|
|
{children}
|
|
</Box>
|
|
),
|
|
li: ({ children }) => <Box component="li">{children}</Box>,
|
|
blockquote: ({ children }) => (
|
|
<Box
|
|
sx={{
|
|
borderLeft: '3px solid',
|
|
borderColor: inverse ? 'rgba(255,255,255,0.4)' : 'primary.main',
|
|
pl: 1.5,
|
|
py: 0.25,
|
|
my: 1,
|
|
color: inverse ? 'rgba(255,255,255,0.9)' : 'text.secondary',
|
|
fontStyle: 'italic',
|
|
}}
|
|
>
|
|
{children}
|
|
</Box>
|
|
),
|
|
a: ({ href, children }) => (
|
|
<MuiLink
|
|
href={href}
|
|
target="_blank"
|
|
rel="noopener noreferrer"
|
|
sx={{
|
|
color: linkColor,
|
|
textDecoration: 'underline',
|
|
textDecorationColor: 'currentColor',
|
|
textUnderlineOffset: 2,
|
|
fontWeight: 600,
|
|
'&:hover': { textDecoration: 'underline' },
|
|
}}
|
|
>
|
|
{children}
|
|
</MuiLink>
|
|
),
|
|
code: ({ inline, children }: any) => {
|
|
if (inline) {
|
|
return (
|
|
<Box
|
|
component="code"
|
|
sx={{
|
|
fontFamily:
|
|
'ui-monospace, "SF Mono", Menlo, Consolas, "Liberation Mono", monospace',
|
|
fontSize: '0.85em',
|
|
bgcolor: codeBg,
|
|
color: codeColor,
|
|
px: 0.6,
|
|
py: 0.15,
|
|
borderRadius: 1,
|
|
border: '1px solid',
|
|
borderColor,
|
|
}}
|
|
>
|
|
{children}
|
|
</Box>
|
|
);
|
|
}
|
|
return (
|
|
<Paper
|
|
variant="outlined"
|
|
component="pre"
|
|
sx={{
|
|
fontFamily:
|
|
'ui-monospace, "SF Mono", Menlo, Consolas, "Liberation Mono", monospace',
|
|
fontSize: '0.82rem',
|
|
lineHeight: 1.5,
|
|
my: 1,
|
|
p: 1.5,
|
|
borderRadius: 2,
|
|
overflowX: 'auto',
|
|
bgcolor: codeBg,
|
|
color: codeColor,
|
|
borderColor,
|
|
border: '1px solid',
|
|
}}
|
|
>
|
|
<code>{children}</code>
|
|
</Paper>
|
|
);
|
|
},
|
|
hr: () => (
|
|
<Box
|
|
component="hr"
|
|
sx={{
|
|
border: 'none',
|
|
borderTop: '1px solid',
|
|
borderColor,
|
|
my: 1.5,
|
|
}}
|
|
/>
|
|
),
|
|
table: ({ children }) => (
|
|
<Box
|
|
sx={{
|
|
overflowX: 'auto',
|
|
my: 1,
|
|
borderRadius: 2,
|
|
border: '1px solid',
|
|
borderColor,
|
|
}}
|
|
>
|
|
<Box
|
|
component="table"
|
|
sx={{
|
|
width: '100%',
|
|
borderCollapse: 'collapse',
|
|
fontSize: '0.85rem',
|
|
}}
|
|
>
|
|
{children}
|
|
</Box>
|
|
</Box>
|
|
),
|
|
thead: ({ children }) => (
|
|
<Box
|
|
component="thead"
|
|
sx={{
|
|
bgcolor: inverse ? 'rgba(255,255,255,0.1)' : 'action.hover',
|
|
fontWeight: 700,
|
|
}}
|
|
>
|
|
{children}
|
|
</Box>
|
|
),
|
|
th: ({ children }) => (
|
|
<Box
|
|
component="th"
|
|
sx={{
|
|
textAlign: 'left',
|
|
px: 1.25,
|
|
py: 0.75,
|
|
borderBottom: '1px solid',
|
|
borderColor,
|
|
fontWeight: 700,
|
|
}}
|
|
>
|
|
{children}
|
|
</Box>
|
|
),
|
|
td: ({ children }) => (
|
|
<Box
|
|
component="td"
|
|
sx={{
|
|
px: 1.25,
|
|
py: 0.65,
|
|
borderBottom: '1px solid',
|
|
borderColor,
|
|
}}
|
|
>
|
|
{children}
|
|
</Box>
|
|
),
|
|
}}
|
|
>
|
|
{content}
|
|
</ReactMarkdown>
|
|
</Box>
|
|
);
|
|
}
|