feat: improve Markdown rendering with proper styling

- Add remark-gfm for GitHub Flavored Markdown support
- Create dedicated MarkdownContent component
- Improved message bubble padding (16px 20px) for better spacing
- Code blocks now in separate box with dark background (#1e1e1e)
- Code blocks show language label in blue accent header
- Fixed list indentation to align properly with message text
- Lists have consistent spacing (margin/padding)
- Better blockquote styling with background and border
- Improved table styling with borders and alternating row colors
- Support for all common Markdown elements (headings, links, bold, italic, hr)
- All 52 tests passing
This commit is contained in:
2026-02-26 02:03:30 +01:00
parent 183241536d
commit f0ce25991f
4 changed files with 386 additions and 83 deletions
+2 -1
View File
@@ -17,7 +17,8 @@
"@capacitor/core": "^5.0.0",
"react": "^18.2.0",
"react-dom": "^18.2.0",
"react-markdown": "^10.1.0"
"react-markdown": "^10.1.0",
"remark-gfm": "^4.0.1"
},
"devDependencies": {
"@testing-library/jest-dom": "^5.17.0",
+201
View File
@@ -26,6 +26,9 @@ importers:
react-markdown:
specifier: ^10.1.0
version: 10.1.0(@types/react@19.2.14)(react@18.3.1)
remark-gfm:
specifier: ^4.0.1
version: 4.0.1
devDependencies:
'@testing-library/jest-dom':
specifier: ^5.17.0
@@ -839,6 +842,10 @@ packages:
resolution: {integrity: sha512-UpzcLCXolUWcNu5HtVMHYdXJjArjsF9C0aNnquZYY4uW/Vu0miy5YoWvbV345HauVvcAUnpRuhMMcqTcGOY2+w==}
engines: {node: '>=8'}
escape-string-regexp@5.0.0:
resolution: {integrity: sha512-/veY75JbMK4j1yjvuUxuVsiS/hr/4iHs9FTT6cgTexxdE0Ly/glccBAkloH/DofkjRbZU3bnoj38mOmhkZ0lHw==}
engines: {node: '>=12'}
estree-util-is-identifier-name@3.0.0:
resolution: {integrity: sha512-hFtqIDZTIUZ9BXLb8y4pYGyk6+wekIivNVTcmvk8NoOh+VeRn5y6cEHzbURrWbfp1fIqdVipilzj+lfaadNZmg==}
@@ -1168,13 +1175,37 @@ packages:
magic-string@0.30.21:
resolution: {integrity: sha512-vd2F4YUyEXKGcLHoq+TEyCjxueSeHnFxyyjNp80yg0XV4vUhnDer/lvvlqM/arB5bXQN5K2/3oinyCRyx8T2CQ==}
markdown-table@3.0.4:
resolution: {integrity: sha512-wiYz4+JrLyb/DqW2hkFJxP7Vd7JuTDm77fvbM8VfEQdmSMqcImWeeRbHwZjBjIFki/VaMK2BhFi7oUUZeM5bqw==}
math-intrinsics@1.1.0:
resolution: {integrity: sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g==}
engines: {node: '>= 0.4'}
mdast-util-find-and-replace@3.0.2:
resolution: {integrity: sha512-Tmd1Vg/m3Xz43afeNxDIhWRtFZgM2VLyaf4vSTYwudTyeuTneoL3qtWMA5jeLyz/O1vDJmmV4QuScFCA2tBPwg==}
mdast-util-from-markdown@2.0.3:
resolution: {integrity: sha512-W4mAWTvSlKvf8L6J+VN9yLSqQ9AOAAvHuoDAmPkz4dHf553m5gVj2ejadHJhoJmcmxEnOv6Pa8XJhpxE93kb8Q==}
mdast-util-gfm-autolink-literal@2.0.1:
resolution: {integrity: sha512-5HVP2MKaP6L+G6YaxPNjuL0BPrq9orG3TsrZ9YXbA3vDw/ACI4MEsnoDpn6ZNm7GnZgtAcONJyPhOP8tNJQavQ==}
mdast-util-gfm-footnote@2.1.0:
resolution: {integrity: sha512-sqpDWlsHn7Ac9GNZQMeUzPQSMzR6Wv0WKRNvQRg0KqHh02fpTz69Qc1QSseNX29bhz1ROIyNyxExfawVKTm1GQ==}
mdast-util-gfm-strikethrough@2.0.0:
resolution: {integrity: sha512-mKKb915TF+OC5ptj5bJ7WFRPdYtuHv0yTRxK2tJvi+BDqbkiG7h7u/9SI89nRAYcmap2xHQL9D+QG/6wSrTtXg==}
mdast-util-gfm-table@2.0.0:
resolution: {integrity: sha512-78UEvebzz/rJIxLvE7ZtDd/vIQ0RHv+3Mh5DR96p7cS7HsBhYIICDBCu8csTNWNO6tBWfqXPWekRuj2FNOGOZg==}
mdast-util-gfm-task-list-item@2.0.0:
resolution: {integrity: sha512-IrtvNvjxC1o06taBAVJznEnkiHxLFTzgonUdy8hzFVeDun0uTjxxrRGVaNFqkU1wJR3RBPEfsxmU6jDWPofrTQ==}
mdast-util-gfm@3.1.0:
resolution: {integrity: sha512-0ulfdQOM3ysHhCJ1p06l0b0VKlhU0wuQs3thxZQagjcjPrlFRqY215uZGHHJan9GEAXd9MbfPjFJz+qMkVR6zQ==}
mdast-util-mdx-expression@2.0.1:
resolution: {integrity: sha512-J6f+9hUp+ldTZqKRSg7Vw5V6MqjATc+3E4gf3CFNcuZNWD8XdyI6zQ8GqH7f8169MM6P7hMBRDVGnn7oHB9kXQ==}
@@ -1199,6 +1230,27 @@ packages:
micromark-core-commonmark@2.0.3:
resolution: {integrity: sha512-RDBrHEMSxVFLg6xvnXmb1Ayr2WzLAWjeSATAoxwKYJV94TeNavgoIdA0a9ytzDSVzBy2YKFK+emCPOEibLeCrg==}
micromark-extension-gfm-autolink-literal@2.1.0:
resolution: {integrity: sha512-oOg7knzhicgQ3t4QCjCWgTmfNhvQbDDnJeVu9v81r7NltNCVmhPy1fJRX27pISafdjL+SVc4d3l48Gb6pbRypw==}
micromark-extension-gfm-footnote@2.1.0:
resolution: {integrity: sha512-/yPhxI1ntnDNsiHtzLKYnE3vf9JZ6cAisqVDauhp4CEHxlb4uoOTxOCJ+9s51bIB8U1N1FJ1RXOKTIlD5B/gqw==}
micromark-extension-gfm-strikethrough@2.1.0:
resolution: {integrity: sha512-ADVjpOOkjz1hhkZLlBiYA9cR2Anf8F4HqZUO6e5eDcPQd0Txw5fxLzzxnEkSkfnD0wziSGiv7sYhk/ktvbf1uw==}
micromark-extension-gfm-table@2.1.1:
resolution: {integrity: sha512-t2OU/dXXioARrC6yWfJ4hqB7rct14e8f7m0cbI5hUmDyyIlwv5vEtooptH8INkbLzOatzKuVbQmAYcbWoyz6Dg==}
micromark-extension-gfm-tagfilter@2.0.0:
resolution: {integrity: sha512-xHlTOmuCSotIA8TW1mDIM6X2O1SiX5P9IuDtqGonFhEK0qgRI4yeC6vMxEV2dgyr2TiD+2PQ10o+cOhdVAcwfg==}
micromark-extension-gfm-task-list-item@2.1.0:
resolution: {integrity: sha512-qIBZhqxqI6fjLDYFTBIa4eivDMnP+OZqsNwmQ3xNLE4Cxwc+zfQEfbs6tzAo2Hjq+bh6q5F+Z8/cksrLFYWQQw==}
micromark-extension-gfm@3.0.0:
resolution: {integrity: sha512-vsKArQsicm7t0z2GugkCKtZehqUm31oeGBV/KVSorWSy8ZlNAv7ytjFhvaryUiCUJYqs+NoE6AFhpQvBTM6Q4w==}
micromark-factory-destination@2.0.1:
resolution: {integrity: sha512-Xe6rDdJlkmbFRExpTOmRj9N3MaWmbAgdpSrBQvCFqhezUn4AHqJHbaEnfbVYYiexVSs//tqOdY/DxhjdCiJnIA==}
@@ -1468,12 +1520,18 @@ packages:
resolution: {integrity: sha512-dYqgNSZbDwkaJ2ceRd9ojCGjBq+mOm9LmtXnAnEGyHhN/5R7iDW2TRw3h+o/jCFxus3P2LfWIIiwowAjANm7IA==}
engines: {node: '>= 0.4'}
remark-gfm@4.0.1:
resolution: {integrity: sha512-1quofZ2RQ9EWdeN34S79+KExV1764+wCUGop5CPL1WGdD0ocPpu91lzPGbwWMECpEpd42kJGQwzRfyov9j4yNg==}
remark-parse@11.0.0:
resolution: {integrity: sha512-FCxlKLNGknS5ba/1lmpYijMUzX2esxW5xQqjWxw2eHFfS2MSdaHVINFmhjo+qN1WhZhNimq0dZATN9pH0IDrpA==}
remark-rehype@11.1.2:
resolution: {integrity: sha512-Dh7l57ianaEoIpzbp0PC9UKAdCSVklD8E5Rpw7ETfbTl3FqcOOgq5q2LVDhgGCkaBv7p24JXikPdvhhmHvKMsw==}
remark-stringify@11.0.0:
resolution: {integrity: sha512-1OSmLd3awB/t8qdoEOMazZkNsfVTeY4fTsgzcQFdXNq8ToTN4ZGwrMnlda4K6smTFKD+GRV6O48i6Z4iKgPPpw==}
requires-port@1.0.0:
resolution: {integrity: sha512-KigOCHcocU3XODJxsu8i/j8T9tzT4adHiecwORRQ0ZZFcp7ahwXuRU1m+yuO90C5ZUyGeGfocHDI14M3L3yDAQ==}
@@ -2754,6 +2812,8 @@ snapshots:
escape-string-regexp@2.0.0: {}
escape-string-regexp@5.0.0: {}
estree-util-is-identifier-name@3.0.0: {}
expect@30.2.0:
@@ -3130,8 +3190,17 @@ snapshots:
dependencies:
'@jridgewell/sourcemap-codec': 1.5.5
markdown-table@3.0.4: {}
math-intrinsics@1.1.0: {}
mdast-util-find-and-replace@3.0.2:
dependencies:
'@types/mdast': 4.0.4
escape-string-regexp: 5.0.0
unist-util-is: 6.0.1
unist-util-visit-parents: 6.0.2
mdast-util-from-markdown@2.0.3:
dependencies:
'@types/mdast': 4.0.4
@@ -3149,6 +3218,63 @@ snapshots:
transitivePeerDependencies:
- supports-color
mdast-util-gfm-autolink-literal@2.0.1:
dependencies:
'@types/mdast': 4.0.4
ccount: 2.0.1
devlop: 1.1.0
mdast-util-find-and-replace: 3.0.2
micromark-util-character: 2.1.1
mdast-util-gfm-footnote@2.1.0:
dependencies:
'@types/mdast': 4.0.4
devlop: 1.1.0
mdast-util-from-markdown: 2.0.3
mdast-util-to-markdown: 2.1.2
micromark-util-normalize-identifier: 2.0.1
transitivePeerDependencies:
- supports-color
mdast-util-gfm-strikethrough@2.0.0:
dependencies:
'@types/mdast': 4.0.4
mdast-util-from-markdown: 2.0.3
mdast-util-to-markdown: 2.1.2
transitivePeerDependencies:
- supports-color
mdast-util-gfm-table@2.0.0:
dependencies:
'@types/mdast': 4.0.4
devlop: 1.1.0
markdown-table: 3.0.4
mdast-util-from-markdown: 2.0.3
mdast-util-to-markdown: 2.1.2
transitivePeerDependencies:
- supports-color
mdast-util-gfm-task-list-item@2.0.0:
dependencies:
'@types/mdast': 4.0.4
devlop: 1.1.0
mdast-util-from-markdown: 2.0.3
mdast-util-to-markdown: 2.1.2
transitivePeerDependencies:
- supports-color
mdast-util-gfm@3.1.0:
dependencies:
mdast-util-from-markdown: 2.0.3
mdast-util-gfm-autolink-literal: 2.0.1
mdast-util-gfm-footnote: 2.1.0
mdast-util-gfm-strikethrough: 2.0.0
mdast-util-gfm-table: 2.0.0
mdast-util-gfm-task-list-item: 2.0.0
mdast-util-to-markdown: 2.1.2
transitivePeerDependencies:
- supports-color
mdast-util-mdx-expression@2.0.1:
dependencies:
'@types/estree-jsx': 1.0.5
@@ -3240,6 +3366,64 @@ snapshots:
micromark-util-symbol: 2.0.1
micromark-util-types: 2.0.2
micromark-extension-gfm-autolink-literal@2.1.0:
dependencies:
micromark-util-character: 2.1.1
micromark-util-sanitize-uri: 2.0.1
micromark-util-symbol: 2.0.1
micromark-util-types: 2.0.2
micromark-extension-gfm-footnote@2.1.0:
dependencies:
devlop: 1.1.0
micromark-core-commonmark: 2.0.3
micromark-factory-space: 2.0.1
micromark-util-character: 2.1.1
micromark-util-normalize-identifier: 2.0.1
micromark-util-sanitize-uri: 2.0.1
micromark-util-symbol: 2.0.1
micromark-util-types: 2.0.2
micromark-extension-gfm-strikethrough@2.1.0:
dependencies:
devlop: 1.1.0
micromark-util-chunked: 2.0.1
micromark-util-classify-character: 2.0.1
micromark-util-resolve-all: 2.0.1
micromark-util-symbol: 2.0.1
micromark-util-types: 2.0.2
micromark-extension-gfm-table@2.1.1:
dependencies:
devlop: 1.1.0
micromark-factory-space: 2.0.1
micromark-util-character: 2.1.1
micromark-util-symbol: 2.0.1
micromark-util-types: 2.0.2
micromark-extension-gfm-tagfilter@2.0.0:
dependencies:
micromark-util-types: 2.0.2
micromark-extension-gfm-task-list-item@2.1.0:
dependencies:
devlop: 1.1.0
micromark-factory-space: 2.0.1
micromark-util-character: 2.1.1
micromark-util-symbol: 2.0.1
micromark-util-types: 2.0.2
micromark-extension-gfm@3.0.0:
dependencies:
micromark-extension-gfm-autolink-literal: 2.1.0
micromark-extension-gfm-footnote: 2.1.0
micromark-extension-gfm-strikethrough: 2.1.0
micromark-extension-gfm-table: 2.1.1
micromark-extension-gfm-tagfilter: 2.0.0
micromark-extension-gfm-task-list-item: 2.1.0
micromark-util-combine-extensions: 2.0.1
micromark-util-types: 2.0.2
micromark-factory-destination@2.0.1:
dependencies:
micromark-util-character: 2.1.1
@@ -3589,6 +3773,17 @@ snapshots:
gopd: 1.2.0
set-function-name: 2.0.2
remark-gfm@4.0.1:
dependencies:
'@types/mdast': 4.0.4
mdast-util-gfm: 3.1.0
micromark-extension-gfm: 3.0.0
remark-parse: 11.0.0
remark-stringify: 11.0.0
unified: 11.0.5
transitivePeerDependencies:
- supports-color
remark-parse@11.0.0:
dependencies:
'@types/mdast': 4.0.4
@@ -3606,6 +3801,12 @@ snapshots:
unified: 11.0.5
vfile: 6.0.3
remark-stringify@11.0.0:
dependencies:
'@types/mdast': 4.0.4
mdast-util-to-markdown: 2.1.2
unified: 11.0.5
requires-port@1.0.0: {}
rimraf@4.4.1:
+7 -82
View File
@@ -1,7 +1,7 @@
import { useState, useRef, useEffect } from 'react'
import ReactMarkdown from 'react-markdown'
import { sendMessage, sendMessageStream } from '../utils/llmApi'
import type { Session, Config } from '../services/sessionService'
import { MarkdownContent } from './MarkdownContent'
interface ChatWindowProps {
session: Session | null
@@ -155,81 +155,6 @@ export function ChatWindow({ session, config, onAddMessage }: ChatWindowProps) {
return session.messages.length
}
// Markdown content renderer
const MarkdownContent = ({ content }: { content: string }) => (
<div style={{
fontSize: '14px',
lineHeight: 1.7,
'& pre': {
backgroundColor: 'var(--bg-primary)',
padding: '12px',
borderRadius: 'var(--radius-sm)',
overflowX: 'auto',
border: '1px solid var(--border)'
},
'& code': {
backgroundColor: 'var(--bg-primary)',
padding: '2px 4px',
borderRadius: '4px',
fontSize: '13px',
fontFamily: 'Monaco, Consolas, "Courier New", monospace'
},
'& p': {
marginBottom: '12px',
lineHeight: 1.7
},
'& p:last-child': {
marginBottom: 0
},
'& ul, & ol': {
marginBottom: '12px',
paddingLeft: '20px'
},
'& li': {
marginBottom: '4px'
},
'& h1, & h2, & h3, & h4, & h5, & h6': {
marginTop: '16px',
marginBottom: '8px',
fontWeight: 600,
lineHeight: 1.3
},
'& blockquote': {
borderLeft: '4px solid var(--accent)',
paddingLeft: '12px',
margin: '12px 0',
color: 'var(--text-secondary)',
fontStyle: 'italic'
},
'& a': {
color: 'var(--accent-light)',
textDecoration: 'underline'
},
'& strong': {
fontWeight: 600
},
'& em': {
fontStyle: 'italic'
},
'& table': {
borderCollapse: 'collapse',
width: '100%',
marginBottom: '12px'
},
'& th, & td': {
border: '1px solid var(--border)',
padding: '8px 12px',
textAlign: 'left'
},
'& th': {
backgroundColor: 'var(--bg-tertiary)',
fontWeight: 600
}
} as React.CSSProperties}>
<ReactMarkdown>{content}</ReactMarkdown>
</div>
)
return (
<div style={{
display: 'flex',
@@ -370,7 +295,7 @@ export function ChatWindow({ session, config, onAddMessage }: ChatWindowProps) {
<div
style={{
maxWidth: '80%',
padding: '14px 18px',
padding: '16px 20px',
borderRadius: msg.role === 'user'
? 'var(--radius-lg) var(--radius-lg) 4px var(--radius-lg)'
: 'var(--radius-lg) var(--radius-lg) var(--radius-lg) 4px',
@@ -385,12 +310,12 @@ export function ChatWindow({ session, config, onAddMessage }: ChatWindowProps) {
<div style={{
fontSize: '12px',
fontWeight: 600,
marginBottom: '6px',
marginBottom: '8px',
color: msg.role === 'user' ? 'rgba(255,255,255,0.8)' : 'var(--accent)'
}}>
{msg.role === 'user' ? 'You' : 'Assistant'}
</div>
<div style={{ fontSize: '14px', lineHeight: 1.6 }}>
<div style={{ fontSize: '14px', lineHeight: 1.7 }}>
<MarkdownContent content={msg.content} />
</div>
</div>
@@ -409,7 +334,7 @@ export function ChatWindow({ session, config, onAddMessage }: ChatWindowProps) {
<div
style={{
maxWidth: '80%',
padding: '14px 18px',
padding: '16px 20px',
borderRadius: 'var(--radius-lg) var(--radius-lg) var(--radius-lg) 4px',
backgroundColor: 'var(--bg-secondary)',
color: 'var(--text-primary)',
@@ -420,7 +345,7 @@ export function ChatWindow({ session, config, onAddMessage }: ChatWindowProps) {
<div style={{
fontSize: '12px',
fontWeight: 600,
marginBottom: '6px',
marginBottom: '8px',
color: 'var(--accent)',
display: 'flex',
alignItems: 'center',
@@ -435,7 +360,7 @@ export function ChatWindow({ session, config, onAddMessage }: ChatWindowProps) {
borderRadius: '50%'
}} />
</div>
<div style={{ fontSize: '14px', lineHeight: 1.6 }}>
<div style={{ fontSize: '14px', lineHeight: 1.7 }}>
<MarkdownContent content={streamingContent} />
</div>
</div>
+176
View File
@@ -0,0 +1,176 @@
import ReactMarkdown from 'react-markdown'
import remarkGfm from 'remark-gfm'
// Custom code block component with language detection and syntax styling
const CodeBlock = ({ children, className }: { children?: React.ReactNode; className?: string }) => {
const language = className?.replace('language-', '') || 'text'
const codeString = String(children).replace(/\n$/, '')
return (
<div style={{
margin: '16px 0',
borderRadius: 'var(--radius-md)',
overflow: 'hidden',
border: '1px solid var(--border)',
backgroundColor: '#1e1e1e'
}}>
{/* Language label */}
{language && language !== 'text' && (
<div style={{
backgroundColor: 'var(--accent)',
color: 'white',
padding: '4px 12px',
fontSize: '11px',
fontWeight: 600,
textTransform: 'uppercase',
letterSpacing: '0.5px',
display: 'flex',
alignItems: 'center',
justifyContent: 'space-between'
}}>
<span>{language}</span>
</div>
)}
<pre style={{
margin: 0,
padding: '16px',
overflowX: 'auto',
backgroundColor: '#1e1e1e'
}}>
<code className={className} style={{
fontFamily: 'Monaco, Consolas, "Courier New", monospace',
fontSize: '13px',
lineHeight: 1.6,
color: '#d4d4d4'
}}>
{codeString}
</code>
</pre>
</div>
)
}
// Inline code component
const InlineCode = ({ children }: { children?: React.ReactNode }) => (
<code style={{
backgroundColor: 'var(--bg-primary)',
padding: '2px 6px',
borderRadius: '4px',
fontSize: '13px',
fontFamily: 'Monaco, Consolas, "Courier New", monospace',
color: 'var(--accent-light)',
border: '1px solid var(--border)'
}}>
{children}
</code>
)
interface MarkdownContentProps {
content: string
}
/**
* Markdown content renderer with proper styling
*/
export function MarkdownContent({ content }: MarkdownContentProps) {
return (
<div style={{
fontSize: '14px',
lineHeight: 1.7
}}>
<ReactMarkdown
components={{
code({ className, children, ...props }) {
const match = /language-(\w+)/.exec(className || '')
// If it's a code block (has language class), use CodeBlock
if (match) {
return <CodeBlock className={className}>{children}</CodeBlock>
}
// If it's inline code (no language class), use inline code
return <InlineCode {...props}>{children}</InlineCode>
},
p({ children }) {
return <p style={{ marginBottom: '12px' }}>{children}</p>
},
ul({ children }) {
return <ul style={{ margin: '12px 0', paddingLeft: '24px' }}>{children}</ul>
},
ol({ children }) {
return <ol style={{ margin: '12px 0', paddingLeft: '24px' }}>{children}</ol>
},
li({ children }) {
return <li style={{ marginBottom: '8px', paddingLeft: '4px' }}>{children}</li>
},
blockquote({ children }) {
return (
<blockquote style={{
borderLeft: '4px solid var(--accent)',
paddingLeft: '16px',
margin: '16px 0',
color: 'var(--text-secondary)',
fontStyle: 'italic',
backgroundColor: 'var(--bg-tertiary)',
padding: '12px 16px',
borderRadius: '0 var(--radius-sm) var(--radius-sm) 0'
}}>
{children}
</blockquote>
)
},
table({ children }) {
return (
<div style={{ overflowX: 'auto', margin: '16px 0' }}>
<table style={{
borderCollapse: 'collapse',
width: '100%',
border: '1px solid var(--border)'
}}>
{children}
</table>
</div>
)
},
th({ children }) {
return (
<th style={{
backgroundColor: 'var(--bg-tertiary)',
fontWeight: 600,
border: '1px solid var(--border)',
padding: '10px 14px',
textAlign: 'left'
}}>
{children}
</th>
)
},
td({ children }) {
return (
<td style={{
backgroundColor: 'var(--bg-secondary)',
border: '1px solid var(--border)',
padding: '10px 14px'
}}>
{children}
</td>
)
},
h1({ children }) {
return <h1 style={{ fontSize: '24px', marginTop: '20px', marginBottom: '12px' }}>{children}</h1>
},
h2({ children }) {
return <h2 style={{ fontSize: '20px', marginTop: '18px', marginBottom: '12px' }}>{children}</h2>
},
h3({ children }) {
return <h3 style={{ fontSize: '18px', marginTop: '16px', marginBottom: '10px' }}>{children}</h3>
},
hr() {
return <hr style={{ border: 'none', borderTop: '1px solid var(--border)', margin: '24px 0' }} />
}
}}
remarkPlugins={[remarkGfm]}
>
{content}
</ReactMarkdown>
</div>
)
}