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",
|
"@capacitor/core": "^5.0.0",
|
||||||
"react": "^18.2.0",
|
"react": "^18.2.0",
|
||||||
"react-dom": "^18.2.0",
|
"react-dom": "^18.2.0",
|
||||||
"react-markdown": "^10.1.0"
|
"react-markdown": "^10.1.0",
|
||||||
|
"remark-gfm": "^4.0.1"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@testing-library/jest-dom": "^5.17.0",
|
"@testing-library/jest-dom": "^5.17.0",
|
||||||
|
|||||||
Generated
+201
@@ -26,6 +26,9 @@ importers:
|
|||||||
react-markdown:
|
react-markdown:
|
||||||
specifier: ^10.1.0
|
specifier: ^10.1.0
|
||||||
version: 10.1.0(@types/react@19.2.14)(react@18.3.1)
|
version: 10.1.0(@types/react@19.2.14)(react@18.3.1)
|
||||||
|
remark-gfm:
|
||||||
|
specifier: ^4.0.1
|
||||||
|
version: 4.0.1
|
||||||
devDependencies:
|
devDependencies:
|
||||||
'@testing-library/jest-dom':
|
'@testing-library/jest-dom':
|
||||||
specifier: ^5.17.0
|
specifier: ^5.17.0
|
||||||
@@ -839,6 +842,10 @@ packages:
|
|||||||
resolution: {integrity: sha512-UpzcLCXolUWcNu5HtVMHYdXJjArjsF9C0aNnquZYY4uW/Vu0miy5YoWvbV345HauVvcAUnpRuhMMcqTcGOY2+w==}
|
resolution: {integrity: sha512-UpzcLCXolUWcNu5HtVMHYdXJjArjsF9C0aNnquZYY4uW/Vu0miy5YoWvbV345HauVvcAUnpRuhMMcqTcGOY2+w==}
|
||||||
engines: {node: '>=8'}
|
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:
|
estree-util-is-identifier-name@3.0.0:
|
||||||
resolution: {integrity: sha512-hFtqIDZTIUZ9BXLb8y4pYGyk6+wekIivNVTcmvk8NoOh+VeRn5y6cEHzbURrWbfp1fIqdVipilzj+lfaadNZmg==}
|
resolution: {integrity: sha512-hFtqIDZTIUZ9BXLb8y4pYGyk6+wekIivNVTcmvk8NoOh+VeRn5y6cEHzbURrWbfp1fIqdVipilzj+lfaadNZmg==}
|
||||||
|
|
||||||
@@ -1168,13 +1175,37 @@ packages:
|
|||||||
magic-string@0.30.21:
|
magic-string@0.30.21:
|
||||||
resolution: {integrity: sha512-vd2F4YUyEXKGcLHoq+TEyCjxueSeHnFxyyjNp80yg0XV4vUhnDer/lvvlqM/arB5bXQN5K2/3oinyCRyx8T2CQ==}
|
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:
|
math-intrinsics@1.1.0:
|
||||||
resolution: {integrity: sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g==}
|
resolution: {integrity: sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g==}
|
||||||
engines: {node: '>= 0.4'}
|
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:
|
mdast-util-from-markdown@2.0.3:
|
||||||
resolution: {integrity: sha512-W4mAWTvSlKvf8L6J+VN9yLSqQ9AOAAvHuoDAmPkz4dHf553m5gVj2ejadHJhoJmcmxEnOv6Pa8XJhpxE93kb8Q==}
|
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:
|
mdast-util-mdx-expression@2.0.1:
|
||||||
resolution: {integrity: sha512-J6f+9hUp+ldTZqKRSg7Vw5V6MqjATc+3E4gf3CFNcuZNWD8XdyI6zQ8GqH7f8169MM6P7hMBRDVGnn7oHB9kXQ==}
|
resolution: {integrity: sha512-J6f+9hUp+ldTZqKRSg7Vw5V6MqjATc+3E4gf3CFNcuZNWD8XdyI6zQ8GqH7f8169MM6P7hMBRDVGnn7oHB9kXQ==}
|
||||||
|
|
||||||
@@ -1199,6 +1230,27 @@ packages:
|
|||||||
micromark-core-commonmark@2.0.3:
|
micromark-core-commonmark@2.0.3:
|
||||||
resolution: {integrity: sha512-RDBrHEMSxVFLg6xvnXmb1Ayr2WzLAWjeSATAoxwKYJV94TeNavgoIdA0a9ytzDSVzBy2YKFK+emCPOEibLeCrg==}
|
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:
|
micromark-factory-destination@2.0.1:
|
||||||
resolution: {integrity: sha512-Xe6rDdJlkmbFRExpTOmRj9N3MaWmbAgdpSrBQvCFqhezUn4AHqJHbaEnfbVYYiexVSs//tqOdY/DxhjdCiJnIA==}
|
resolution: {integrity: sha512-Xe6rDdJlkmbFRExpTOmRj9N3MaWmbAgdpSrBQvCFqhezUn4AHqJHbaEnfbVYYiexVSs//tqOdY/DxhjdCiJnIA==}
|
||||||
|
|
||||||
@@ -1468,12 +1520,18 @@ packages:
|
|||||||
resolution: {integrity: sha512-dYqgNSZbDwkaJ2ceRd9ojCGjBq+mOm9LmtXnAnEGyHhN/5R7iDW2TRw3h+o/jCFxus3P2LfWIIiwowAjANm7IA==}
|
resolution: {integrity: sha512-dYqgNSZbDwkaJ2ceRd9ojCGjBq+mOm9LmtXnAnEGyHhN/5R7iDW2TRw3h+o/jCFxus3P2LfWIIiwowAjANm7IA==}
|
||||||
engines: {node: '>= 0.4'}
|
engines: {node: '>= 0.4'}
|
||||||
|
|
||||||
|
remark-gfm@4.0.1:
|
||||||
|
resolution: {integrity: sha512-1quofZ2RQ9EWdeN34S79+KExV1764+wCUGop5CPL1WGdD0ocPpu91lzPGbwWMECpEpd42kJGQwzRfyov9j4yNg==}
|
||||||
|
|
||||||
remark-parse@11.0.0:
|
remark-parse@11.0.0:
|
||||||
resolution: {integrity: sha512-FCxlKLNGknS5ba/1lmpYijMUzX2esxW5xQqjWxw2eHFfS2MSdaHVINFmhjo+qN1WhZhNimq0dZATN9pH0IDrpA==}
|
resolution: {integrity: sha512-FCxlKLNGknS5ba/1lmpYijMUzX2esxW5xQqjWxw2eHFfS2MSdaHVINFmhjo+qN1WhZhNimq0dZATN9pH0IDrpA==}
|
||||||
|
|
||||||
remark-rehype@11.1.2:
|
remark-rehype@11.1.2:
|
||||||
resolution: {integrity: sha512-Dh7l57ianaEoIpzbp0PC9UKAdCSVklD8E5Rpw7ETfbTl3FqcOOgq5q2LVDhgGCkaBv7p24JXikPdvhhmHvKMsw==}
|
resolution: {integrity: sha512-Dh7l57ianaEoIpzbp0PC9UKAdCSVklD8E5Rpw7ETfbTl3FqcOOgq5q2LVDhgGCkaBv7p24JXikPdvhhmHvKMsw==}
|
||||||
|
|
||||||
|
remark-stringify@11.0.0:
|
||||||
|
resolution: {integrity: sha512-1OSmLd3awB/t8qdoEOMazZkNsfVTeY4fTsgzcQFdXNq8ToTN4ZGwrMnlda4K6smTFKD+GRV6O48i6Z4iKgPPpw==}
|
||||||
|
|
||||||
requires-port@1.0.0:
|
requires-port@1.0.0:
|
||||||
resolution: {integrity: sha512-KigOCHcocU3XODJxsu8i/j8T9tzT4adHiecwORRQ0ZZFcp7ahwXuRU1m+yuO90C5ZUyGeGfocHDI14M3L3yDAQ==}
|
resolution: {integrity: sha512-KigOCHcocU3XODJxsu8i/j8T9tzT4adHiecwORRQ0ZZFcp7ahwXuRU1m+yuO90C5ZUyGeGfocHDI14M3L3yDAQ==}
|
||||||
|
|
||||||
@@ -2754,6 +2812,8 @@ snapshots:
|
|||||||
|
|
||||||
escape-string-regexp@2.0.0: {}
|
escape-string-regexp@2.0.0: {}
|
||||||
|
|
||||||
|
escape-string-regexp@5.0.0: {}
|
||||||
|
|
||||||
estree-util-is-identifier-name@3.0.0: {}
|
estree-util-is-identifier-name@3.0.0: {}
|
||||||
|
|
||||||
expect@30.2.0:
|
expect@30.2.0:
|
||||||
@@ -3130,8 +3190,17 @@ snapshots:
|
|||||||
dependencies:
|
dependencies:
|
||||||
'@jridgewell/sourcemap-codec': 1.5.5
|
'@jridgewell/sourcemap-codec': 1.5.5
|
||||||
|
|
||||||
|
markdown-table@3.0.4: {}
|
||||||
|
|
||||||
math-intrinsics@1.1.0: {}
|
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:
|
mdast-util-from-markdown@2.0.3:
|
||||||
dependencies:
|
dependencies:
|
||||||
'@types/mdast': 4.0.4
|
'@types/mdast': 4.0.4
|
||||||
@@ -3149,6 +3218,63 @@ snapshots:
|
|||||||
transitivePeerDependencies:
|
transitivePeerDependencies:
|
||||||
- supports-color
|
- 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:
|
mdast-util-mdx-expression@2.0.1:
|
||||||
dependencies:
|
dependencies:
|
||||||
'@types/estree-jsx': 1.0.5
|
'@types/estree-jsx': 1.0.5
|
||||||
@@ -3240,6 +3366,64 @@ snapshots:
|
|||||||
micromark-util-symbol: 2.0.1
|
micromark-util-symbol: 2.0.1
|
||||||
micromark-util-types: 2.0.2
|
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:
|
micromark-factory-destination@2.0.1:
|
||||||
dependencies:
|
dependencies:
|
||||||
micromark-util-character: 2.1.1
|
micromark-util-character: 2.1.1
|
||||||
@@ -3589,6 +3773,17 @@ snapshots:
|
|||||||
gopd: 1.2.0
|
gopd: 1.2.0
|
||||||
set-function-name: 2.0.2
|
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:
|
remark-parse@11.0.0:
|
||||||
dependencies:
|
dependencies:
|
||||||
'@types/mdast': 4.0.4
|
'@types/mdast': 4.0.4
|
||||||
@@ -3606,6 +3801,12 @@ snapshots:
|
|||||||
unified: 11.0.5
|
unified: 11.0.5
|
||||||
vfile: 6.0.3
|
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: {}
|
requires-port@1.0.0: {}
|
||||||
|
|
||||||
rimraf@4.4.1:
|
rimraf@4.4.1:
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
import { useState, useRef, useEffect } from 'react'
|
import { useState, useRef, useEffect } from 'react'
|
||||||
import ReactMarkdown from 'react-markdown'
|
|
||||||
import { sendMessage, sendMessageStream } from '../utils/llmApi'
|
import { sendMessage, sendMessageStream } from '../utils/llmApi'
|
||||||
import type { Session, Config } from '../services/sessionService'
|
import type { Session, Config } from '../services/sessionService'
|
||||||
|
import { MarkdownContent } from './MarkdownContent'
|
||||||
|
|
||||||
interface ChatWindowProps {
|
interface ChatWindowProps {
|
||||||
session: Session | null
|
session: Session | null
|
||||||
@@ -155,81 +155,6 @@ export function ChatWindow({ session, config, onAddMessage }: ChatWindowProps) {
|
|||||||
return session.messages.length
|
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 (
|
return (
|
||||||
<div style={{
|
<div style={{
|
||||||
display: 'flex',
|
display: 'flex',
|
||||||
@@ -370,7 +295,7 @@ export function ChatWindow({ session, config, onAddMessage }: ChatWindowProps) {
|
|||||||
<div
|
<div
|
||||||
style={{
|
style={{
|
||||||
maxWidth: '80%',
|
maxWidth: '80%',
|
||||||
padding: '14px 18px',
|
padding: '16px 20px',
|
||||||
borderRadius: msg.role === 'user'
|
borderRadius: msg.role === 'user'
|
||||||
? 'var(--radius-lg) var(--radius-lg) 4px var(--radius-lg)'
|
? 'var(--radius-lg) var(--radius-lg) 4px var(--radius-lg)'
|
||||||
: 'var(--radius-lg) var(--radius-lg) var(--radius-lg) 4px',
|
: 'var(--radius-lg) var(--radius-lg) var(--radius-lg) 4px',
|
||||||
@@ -385,12 +310,12 @@ export function ChatWindow({ session, config, onAddMessage }: ChatWindowProps) {
|
|||||||
<div style={{
|
<div style={{
|
||||||
fontSize: '12px',
|
fontSize: '12px',
|
||||||
fontWeight: 600,
|
fontWeight: 600,
|
||||||
marginBottom: '6px',
|
marginBottom: '8px',
|
||||||
color: msg.role === 'user' ? 'rgba(255,255,255,0.8)' : 'var(--accent)'
|
color: msg.role === 'user' ? 'rgba(255,255,255,0.8)' : 'var(--accent)'
|
||||||
}}>
|
}}>
|
||||||
{msg.role === 'user' ? 'You' : 'Assistant'}
|
{msg.role === 'user' ? 'You' : 'Assistant'}
|
||||||
</div>
|
</div>
|
||||||
<div style={{ fontSize: '14px', lineHeight: 1.6 }}>
|
<div style={{ fontSize: '14px', lineHeight: 1.7 }}>
|
||||||
<MarkdownContent content={msg.content} />
|
<MarkdownContent content={msg.content} />
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -409,7 +334,7 @@ export function ChatWindow({ session, config, onAddMessage }: ChatWindowProps) {
|
|||||||
<div
|
<div
|
||||||
style={{
|
style={{
|
||||||
maxWidth: '80%',
|
maxWidth: '80%',
|
||||||
padding: '14px 18px',
|
padding: '16px 20px',
|
||||||
borderRadius: 'var(--radius-lg) var(--radius-lg) var(--radius-lg) 4px',
|
borderRadius: 'var(--radius-lg) var(--radius-lg) var(--radius-lg) 4px',
|
||||||
backgroundColor: 'var(--bg-secondary)',
|
backgroundColor: 'var(--bg-secondary)',
|
||||||
color: 'var(--text-primary)',
|
color: 'var(--text-primary)',
|
||||||
@@ -420,7 +345,7 @@ export function ChatWindow({ session, config, onAddMessage }: ChatWindowProps) {
|
|||||||
<div style={{
|
<div style={{
|
||||||
fontSize: '12px',
|
fontSize: '12px',
|
||||||
fontWeight: 600,
|
fontWeight: 600,
|
||||||
marginBottom: '6px',
|
marginBottom: '8px',
|
||||||
color: 'var(--accent)',
|
color: 'var(--accent)',
|
||||||
display: 'flex',
|
display: 'flex',
|
||||||
alignItems: 'center',
|
alignItems: 'center',
|
||||||
@@ -435,7 +360,7 @@ export function ChatWindow({ session, config, onAddMessage }: ChatWindowProps) {
|
|||||||
borderRadius: '50%'
|
borderRadius: '50%'
|
||||||
}} />
|
}} />
|
||||||
</div>
|
</div>
|
||||||
<div style={{ fontSize: '14px', lineHeight: 1.6 }}>
|
<div style={{ fontSize: '14px', lineHeight: 1.7 }}>
|
||||||
<MarkdownContent content={streamingContent} />
|
<MarkdownContent content={streamingContent} />
|
||||||
</div>
|
</div>
|
||||||
</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