feat: add chat assistant with role-aware prompt and vision support

This commit is contained in:
MrFrostDev
2026-05-23 13:58:27 +02:00
parent 8aa47f1ff7
commit e4f3ccfaaf
10 changed files with 2200 additions and 0 deletions
+272
View File
@@ -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>
);
}