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:
+2
-1
@@ -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",
|
||||
|
||||
Generated
+201
@@ -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:
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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>
|
||||
)
|
||||
}
|
||||
Reference in New Issue
Block a user