feat: add chat assistant with role-aware prompt and vision support
This commit is contained in:
@@ -0,0 +1,272 @@
|
||||
'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>
|
||||
);
|
||||
}
|
||||
Reference in New Issue
Block a user