Spaces:
Running
Running
Saurabh Kumar Bajpai commited on
Commit ·
7bef330
1
Parent(s): 9f58b4d
feat: render chat markdown tables and code
Browse files
frontend/e2e/auth-and-chat.spec.ts
CHANGED
|
@@ -81,6 +81,17 @@ test("creates an account from the signup form", async ({ page }) => {
|
|
| 81 |
|
| 82 |
test("uploads a document and chats with it", async ({ page }) => {
|
| 83 |
const documents: typeof uploadedDocument[] = [];
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 84 |
|
| 85 |
await page.addInitScript(() => {
|
| 86 |
localStorage.setItem("token", "access-token");
|
|
@@ -103,10 +114,9 @@ test("uploads a document and chats with it", async ({ page }) => {
|
|
| 103 |
status: 200,
|
| 104 |
headers: { "content-type": "text/event-stream" },
|
| 105 |
body: [
|
| 106 |
-
|
| 107 |
-
|
| 108 |
-
|
| 109 |
-
'data: {"type":"done"}\n\n',
|
| 110 |
].join(""),
|
| 111 |
});
|
| 112 |
});
|
|
@@ -127,4 +137,7 @@ test("uploads a document and chats with it", async ({ page }) => {
|
|
| 127 |
|
| 128 |
await expect(page.getByText("Summarize this document")).toBeVisible();
|
| 129 |
await expect(page.getByText("A short summary.")).toBeVisible();
|
|
|
|
|
|
|
|
|
|
| 130 |
});
|
|
|
|
| 81 |
|
| 82 |
test("uploads a document and chats with it", async ({ page }) => {
|
| 83 |
const documents: typeof uploadedDocument[] = [];
|
| 84 |
+
const markdownAnswer = [
|
| 85 |
+
"A short summary.",
|
| 86 |
+
"",
|
| 87 |
+
"| Field | Value |",
|
| 88 |
+
"| --- | --- |",
|
| 89 |
+
"| Pages | 1 |",
|
| 90 |
+
"",
|
| 91 |
+
"```ts",
|
| 92 |
+
"const answer = 42;",
|
| 93 |
+
"```",
|
| 94 |
+
].join("\n");
|
| 95 |
|
| 96 |
await page.addInitScript(() => {
|
| 97 |
localStorage.setItem("token", "access-token");
|
|
|
|
| 114 |
status: 200,
|
| 115 |
headers: { "content-type": "text/event-stream" },
|
| 116 |
body: [
|
| 117 |
+
`data: ${JSON.stringify({ type: "token", data: markdownAnswer })}\n\n`,
|
| 118 |
+
`data: ${JSON.stringify({ type: "sources", data: [] })}\n\n`,
|
| 119 |
+
`data: ${JSON.stringify({ type: "done" })}\n\n`,
|
|
|
|
| 120 |
].join(""),
|
| 121 |
});
|
| 122 |
});
|
|
|
|
| 137 |
|
| 138 |
await expect(page.getByText("Summarize this document")).toBeVisible();
|
| 139 |
await expect(page.getByText("A short summary.")).toBeVisible();
|
| 140 |
+
await expect(page.getByRole("columnheader", { name: "Field" })).toBeVisible();
|
| 141 |
+
await expect(page.getByRole("cell", { name: "Pages" })).toBeVisible();
|
| 142 |
+
await expect(page.getByText("const answer = 42;")).toBeVisible();
|
| 143 |
});
|
frontend/package-lock.json
CHANGED
|
@@ -19,6 +19,7 @@
|
|
| 19 |
"react-dropzone": "^15.0.0",
|
| 20 |
"react-markdown": "^10.1.0",
|
| 21 |
"react-pdf": "^10.4.1",
|
|
|
|
| 22 |
"remark-gfm": "^4.0.1",
|
| 23 |
"shadcn": "^4.3.1",
|
| 24 |
"tailwind-merge": "^3.5.0",
|
|
@@ -6021,6 +6022,19 @@
|
|
| 6021 |
"node": ">= 0.4"
|
| 6022 |
}
|
| 6023 |
},
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 6024 |
"node_modules/hast-util-to-jsx-runtime": {
|
| 6025 |
"version": "2.3.6",
|
| 6026 |
"resolved": "https://registry.npmjs.org/hast-util-to-jsx-runtime/-/hast-util-to-jsx-runtime-2.3.6.tgz",
|
|
@@ -6048,6 +6062,22 @@
|
|
| 6048 |
"url": "https://opencollective.com/unified"
|
| 6049 |
}
|
| 6050 |
},
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 6051 |
"node_modules/hast-util-whitespace": {
|
| 6052 |
"version": "3.0.0",
|
| 6053 |
"resolved": "https://registry.npmjs.org/hast-util-whitespace/-/hast-util-whitespace-3.0.0.tgz",
|
|
@@ -6088,6 +6118,15 @@
|
|
| 6088 |
"hermes-estree": "0.25.1"
|
| 6089 |
}
|
| 6090 |
},
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 6091 |
"node_modules/hono": {
|
| 6092 |
"version": "4.12.14",
|
| 6093 |
"resolved": "https://registry.npmjs.org/hono/-/hono-4.12.14.tgz",
|
|
@@ -7418,6 +7457,21 @@
|
|
| 7418 |
"loose-envify": "cli.js"
|
| 7419 |
}
|
| 7420 |
},
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 7421 |
"node_modules/lru-cache": {
|
| 7422 |
"version": "5.1.1",
|
| 7423 |
"resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-5.1.1.tgz",
|
|
@@ -9629,6 +9683,23 @@
|
|
| 9629 |
"url": "https://github.com/sponsors/ljharb"
|
| 9630 |
}
|
| 9631 |
},
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 9632 |
"node_modules/remark-gfm": {
|
| 9633 |
"version": "4.0.1",
|
| 9634 |
"resolved": "https://registry.npmjs.org/remark-gfm/-/remark-gfm-4.0.1.tgz",
|
|
@@ -11107,6 +11178,20 @@
|
|
| 11107 |
"url": "https://opencollective.com/unified"
|
| 11108 |
}
|
| 11109 |
},
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 11110 |
"node_modules/unist-util-is": {
|
| 11111 |
"version": "6.0.1",
|
| 11112 |
"resolved": "https://registry.npmjs.org/unist-util-is/-/unist-util-is-6.0.1.tgz",
|
|
|
|
| 19 |
"react-dropzone": "^15.0.0",
|
| 20 |
"react-markdown": "^10.1.0",
|
| 21 |
"react-pdf": "^10.4.1",
|
| 22 |
+
"rehype-highlight": "^7.0.2",
|
| 23 |
"remark-gfm": "^4.0.1",
|
| 24 |
"shadcn": "^4.3.1",
|
| 25 |
"tailwind-merge": "^3.5.0",
|
|
|
|
| 6022 |
"node": ">= 0.4"
|
| 6023 |
}
|
| 6024 |
},
|
| 6025 |
+
"node_modules/hast-util-is-element": {
|
| 6026 |
+
"version": "3.0.0",
|
| 6027 |
+
"resolved": "https://registry.npmjs.org/hast-util-is-element/-/hast-util-is-element-3.0.0.tgz",
|
| 6028 |
+
"integrity": "sha512-Val9mnv2IWpLbNPqc/pUem+a7Ipj2aHacCwgNfTiK0vJKl0LF+4Ba4+v1oPHFpf3bLYmreq0/l3Gud9S5OH42g==",
|
| 6029 |
+
"license": "MIT",
|
| 6030 |
+
"dependencies": {
|
| 6031 |
+
"@types/hast": "^3.0.0"
|
| 6032 |
+
},
|
| 6033 |
+
"funding": {
|
| 6034 |
+
"type": "opencollective",
|
| 6035 |
+
"url": "https://opencollective.com/unified"
|
| 6036 |
+
}
|
| 6037 |
+
},
|
| 6038 |
"node_modules/hast-util-to-jsx-runtime": {
|
| 6039 |
"version": "2.3.6",
|
| 6040 |
"resolved": "https://registry.npmjs.org/hast-util-to-jsx-runtime/-/hast-util-to-jsx-runtime-2.3.6.tgz",
|
|
|
|
| 6062 |
"url": "https://opencollective.com/unified"
|
| 6063 |
}
|
| 6064 |
},
|
| 6065 |
+
"node_modules/hast-util-to-text": {
|
| 6066 |
+
"version": "4.0.2",
|
| 6067 |
+
"resolved": "https://registry.npmjs.org/hast-util-to-text/-/hast-util-to-text-4.0.2.tgz",
|
| 6068 |
+
"integrity": "sha512-KK6y/BN8lbaq654j7JgBydev7wuNMcID54lkRav1P0CaE1e47P72AWWPiGKXTJU271ooYzcvTAn/Zt0REnvc7A==",
|
| 6069 |
+
"license": "MIT",
|
| 6070 |
+
"dependencies": {
|
| 6071 |
+
"@types/hast": "^3.0.0",
|
| 6072 |
+
"@types/unist": "^3.0.0",
|
| 6073 |
+
"hast-util-is-element": "^3.0.0",
|
| 6074 |
+
"unist-util-find-after": "^5.0.0"
|
| 6075 |
+
},
|
| 6076 |
+
"funding": {
|
| 6077 |
+
"type": "opencollective",
|
| 6078 |
+
"url": "https://opencollective.com/unified"
|
| 6079 |
+
}
|
| 6080 |
+
},
|
| 6081 |
"node_modules/hast-util-whitespace": {
|
| 6082 |
"version": "3.0.0",
|
| 6083 |
"resolved": "https://registry.npmjs.org/hast-util-whitespace/-/hast-util-whitespace-3.0.0.tgz",
|
|
|
|
| 6118 |
"hermes-estree": "0.25.1"
|
| 6119 |
}
|
| 6120 |
},
|
| 6121 |
+
"node_modules/highlight.js": {
|
| 6122 |
+
"version": "11.11.1",
|
| 6123 |
+
"resolved": "https://registry.npmjs.org/highlight.js/-/highlight.js-11.11.1.tgz",
|
| 6124 |
+
"integrity": "sha512-Xwwo44whKBVCYoliBQwaPvtd/2tYFkRQtXDWj1nackaV2JPXx3L0+Jvd8/qCJ2p+ML0/XVkJ2q+Mr+UVdpJK5w==",
|
| 6125 |
+
"license": "BSD-3-Clause",
|
| 6126 |
+
"engines": {
|
| 6127 |
+
"node": ">=12.0.0"
|
| 6128 |
+
}
|
| 6129 |
+
},
|
| 6130 |
"node_modules/hono": {
|
| 6131 |
"version": "4.12.14",
|
| 6132 |
"resolved": "https://registry.npmjs.org/hono/-/hono-4.12.14.tgz",
|
|
|
|
| 7457 |
"loose-envify": "cli.js"
|
| 7458 |
}
|
| 7459 |
},
|
| 7460 |
+
"node_modules/lowlight": {
|
| 7461 |
+
"version": "3.3.0",
|
| 7462 |
+
"resolved": "https://registry.npmjs.org/lowlight/-/lowlight-3.3.0.tgz",
|
| 7463 |
+
"integrity": "sha512-0JNhgFoPvP6U6lE/UdVsSq99tn6DhjjpAj5MxG49ewd2mOBVtwWYIT8ClyABhq198aXXODMU6Ox8DrGy/CpTZQ==",
|
| 7464 |
+
"license": "MIT",
|
| 7465 |
+
"dependencies": {
|
| 7466 |
+
"@types/hast": "^3.0.0",
|
| 7467 |
+
"devlop": "^1.0.0",
|
| 7468 |
+
"highlight.js": "~11.11.0"
|
| 7469 |
+
},
|
| 7470 |
+
"funding": {
|
| 7471 |
+
"type": "github",
|
| 7472 |
+
"url": "https://github.com/sponsors/wooorm"
|
| 7473 |
+
}
|
| 7474 |
+
},
|
| 7475 |
"node_modules/lru-cache": {
|
| 7476 |
"version": "5.1.1",
|
| 7477 |
"resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-5.1.1.tgz",
|
|
|
|
| 9683 |
"url": "https://github.com/sponsors/ljharb"
|
| 9684 |
}
|
| 9685 |
},
|
| 9686 |
+
"node_modules/rehype-highlight": {
|
| 9687 |
+
"version": "7.0.2",
|
| 9688 |
+
"resolved": "https://registry.npmjs.org/rehype-highlight/-/rehype-highlight-7.0.2.tgz",
|
| 9689 |
+
"integrity": "sha512-k158pK7wdC2qL3M5NcZROZ2tR/l7zOzjxXd5VGdcfIyoijjQqpHd3JKtYSBDpDZ38UI2WJWuFAtkMDxmx5kstA==",
|
| 9690 |
+
"license": "MIT",
|
| 9691 |
+
"dependencies": {
|
| 9692 |
+
"@types/hast": "^3.0.0",
|
| 9693 |
+
"hast-util-to-text": "^4.0.0",
|
| 9694 |
+
"lowlight": "^3.0.0",
|
| 9695 |
+
"unist-util-visit": "^5.0.0",
|
| 9696 |
+
"vfile": "^6.0.0"
|
| 9697 |
+
},
|
| 9698 |
+
"funding": {
|
| 9699 |
+
"type": "opencollective",
|
| 9700 |
+
"url": "https://opencollective.com/unified"
|
| 9701 |
+
}
|
| 9702 |
+
},
|
| 9703 |
"node_modules/remark-gfm": {
|
| 9704 |
"version": "4.0.1",
|
| 9705 |
"resolved": "https://registry.npmjs.org/remark-gfm/-/remark-gfm-4.0.1.tgz",
|
|
|
|
| 11178 |
"url": "https://opencollective.com/unified"
|
| 11179 |
}
|
| 11180 |
},
|
| 11181 |
+
"node_modules/unist-util-find-after": {
|
| 11182 |
+
"version": "5.0.0",
|
| 11183 |
+
"resolved": "https://registry.npmjs.org/unist-util-find-after/-/unist-util-find-after-5.0.0.tgz",
|
| 11184 |
+
"integrity": "sha512-amQa0Ep2m6hE2g72AugUItjbuM8X8cGQnFoHk0pGfrFeT9GZhzN5SW8nRsiGKK7Aif4CrACPENkA6P/Lw6fHGQ==",
|
| 11185 |
+
"license": "MIT",
|
| 11186 |
+
"dependencies": {
|
| 11187 |
+
"@types/unist": "^3.0.0",
|
| 11188 |
+
"unist-util-is": "^6.0.0"
|
| 11189 |
+
},
|
| 11190 |
+
"funding": {
|
| 11191 |
+
"type": "opencollective",
|
| 11192 |
+
"url": "https://opencollective.com/unified"
|
| 11193 |
+
}
|
| 11194 |
+
},
|
| 11195 |
"node_modules/unist-util-is": {
|
| 11196 |
"version": "6.0.1",
|
| 11197 |
"resolved": "https://registry.npmjs.org/unist-util-is/-/unist-util-is-6.0.1.tgz",
|
frontend/package.json
CHANGED
|
@@ -22,6 +22,7 @@
|
|
| 22 |
"react-dropzone": "^15.0.0",
|
| 23 |
"react-markdown": "^10.1.0",
|
| 24 |
"react-pdf": "^10.4.1",
|
|
|
|
| 25 |
"remark-gfm": "^4.0.1",
|
| 26 |
"shadcn": "^4.3.1",
|
| 27 |
"tailwind-merge": "^3.5.0",
|
|
|
|
| 22 |
"react-dropzone": "^15.0.0",
|
| 23 |
"react-markdown": "^10.1.0",
|
| 24 |
"react-pdf": "^10.4.1",
|
| 25 |
+
"rehype-highlight": "^7.0.2",
|
| 26 |
"remark-gfm": "^4.0.1",
|
| 27 |
"shadcn": "^4.3.1",
|
| 28 |
"tailwind-merge": "^3.5.0",
|
frontend/src/app/globals.css
CHANGED
|
@@ -182,6 +182,16 @@
|
|
| 182 |
.prose-chat p { margin-bottom: 0.75em; line-height: 1.7; }
|
| 183 |
.prose-chat ul, .prose-chat ol { padding-left: 1.5em; margin-bottom: 0.75em; }
|
| 184 |
.prose-chat li { margin-bottom: 0.25em; }
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 185 |
.prose-chat code {
|
| 186 |
background: oklch(1 0 0 / 8%);
|
| 187 |
padding: 0.15em 0.4em;
|
|
@@ -198,6 +208,35 @@
|
|
| 198 |
.prose-chat pre code {
|
| 199 |
background: none;
|
| 200 |
padding: 0;
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 201 |
}
|
| 202 |
.prose-chat blockquote {
|
| 203 |
border-left: 3px solid oklch(0.65 0.2 265);
|
|
@@ -211,5 +250,8 @@
|
|
| 211 |
|
| 212 |
.light .prose-chat code { background: oklch(0 0 0 / 6%); }
|
| 213 |
.light .prose-chat pre { background: oklch(0.96 0 0); }
|
|
|
|
|
|
|
|
|
|
| 214 |
.light .prose-chat strong { color: oklch(0.2 0 0); }
|
| 215 |
-
.light .prose-chat blockquote { color: oklch(0.4 0 0); }
|
|
|
|
| 182 |
.prose-chat p { margin-bottom: 0.75em; line-height: 1.7; }
|
| 183 |
.prose-chat ul, .prose-chat ol { padding-left: 1.5em; margin-bottom: 0.75em; }
|
| 184 |
.prose-chat li { margin-bottom: 0.25em; }
|
| 185 |
+
.prose-chat table {
|
| 186 |
+
width: 100%;
|
| 187 |
+
}
|
| 188 |
+
.prose-chat th,
|
| 189 |
+
.prose-chat td {
|
| 190 |
+
white-space: nowrap;
|
| 191 |
+
}
|
| 192 |
+
.prose-chat tbody tr:last-child td {
|
| 193 |
+
border-bottom: 0;
|
| 194 |
+
}
|
| 195 |
.prose-chat code {
|
| 196 |
background: oklch(1 0 0 / 8%);
|
| 197 |
padding: 0.15em 0.4em;
|
|
|
|
| 208 |
.prose-chat pre code {
|
| 209 |
background: none;
|
| 210 |
padding: 0;
|
| 211 |
+
color: inherit;
|
| 212 |
+
font-size: 0.92em;
|
| 213 |
+
}
|
| 214 |
+
.prose-chat pre code[data-language]::before {
|
| 215 |
+
content: attr(data-language);
|
| 216 |
+
display: block;
|
| 217 |
+
margin-bottom: 0.75rem;
|
| 218 |
+
color: oklch(0.72 0 0);
|
| 219 |
+
font-family: var(--font-geist-sans), system-ui, sans-serif;
|
| 220 |
+
font-size: 0.72rem;
|
| 221 |
+
font-weight: 600;
|
| 222 |
+
letter-spacing: 0;
|
| 223 |
+
text-transform: uppercase;
|
| 224 |
+
}
|
| 225 |
+
.prose-chat .hljs-keyword,
|
| 226 |
+
.prose-chat .hljs-selector-tag,
|
| 227 |
+
.prose-chat .hljs-title.function_ {
|
| 228 |
+
color: oklch(0.78 0.16 305);
|
| 229 |
+
}
|
| 230 |
+
.prose-chat .hljs-string,
|
| 231 |
+
.prose-chat .hljs-attr {
|
| 232 |
+
color: oklch(0.82 0.15 150);
|
| 233 |
+
}
|
| 234 |
+
.prose-chat .hljs-number,
|
| 235 |
+
.prose-chat .hljs-literal {
|
| 236 |
+
color: oklch(0.82 0.13 80);
|
| 237 |
+
}
|
| 238 |
+
.prose-chat .hljs-comment {
|
| 239 |
+
color: oklch(0.62 0 0);
|
| 240 |
}
|
| 241 |
.prose-chat blockquote {
|
| 242 |
border-left: 3px solid oklch(0.65 0.2 265);
|
|
|
|
| 250 |
|
| 251 |
.light .prose-chat code { background: oklch(0 0 0 / 6%); }
|
| 252 |
.light .prose-chat pre { background: oklch(0.96 0 0); }
|
| 253 |
+
.light .prose-chat pre code { color: oklch(0.2 0 0); }
|
| 254 |
+
.light .prose-chat pre code[data-language]::before { color: oklch(0.45 0 0); }
|
| 255 |
+
.light .prose-chat .hljs-comment { color: oklch(0.5 0 0); }
|
| 256 |
.light .prose-chat strong { color: oklch(0.2 0 0); }
|
| 257 |
+
.light .prose-chat blockquote { color: oklch(0.4 0 0); }
|
frontend/src/components/chat/MessageBubble.tsx
CHANGED
|
@@ -1,7 +1,8 @@
|
|
| 1 |
"use client";
|
| 2 |
|
| 3 |
import { useState, useRef } from "react";
|
| 4 |
-
import ReactMarkdown from "react-markdown";
|
|
|
|
| 5 |
import remarkGfm from "remark-gfm";
|
| 6 |
import type { ChatMsg } from "@/store/chat-store";
|
| 7 |
import { Brain, User, Copy, Check } from "lucide-react";
|
|
@@ -11,6 +12,43 @@ interface Props {
|
|
| 11 |
message: ChatMsg;
|
| 12 |
}
|
| 13 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 14 |
export default function MessageBubble({ message }: Props) {
|
| 15 |
const isUser = message.role === "user";
|
| 16 |
const [copied, setCopied] = useState(false);
|
|
@@ -71,7 +109,11 @@ export default function MessageBubble({ message }: Props) {
|
|
| 71 |
)}
|
| 72 |
<div className={`prose-chat text-sm ${message.content ? "pr-7" : ""}`}>
|
| 73 |
{message.content ? (
|
| 74 |
-
<ReactMarkdown
|
|
|
|
|
|
|
|
|
|
|
|
|
| 75 |
{message.content}
|
| 76 |
</ReactMarkdown>
|
| 77 |
) : message.isStreaming ? (
|
|
|
|
| 1 |
"use client";
|
| 2 |
|
| 3 |
import { useState, useRef } from "react";
|
| 4 |
+
import ReactMarkdown, { type Components } from "react-markdown";
|
| 5 |
+
import rehypeHighlight from "rehype-highlight";
|
| 6 |
import remarkGfm from "remark-gfm";
|
| 7 |
import type { ChatMsg } from "@/store/chat-store";
|
| 8 |
import { Brain, User, Copy, Check } from "lucide-react";
|
|
|
|
| 12 |
message: ChatMsg;
|
| 13 |
}
|
| 14 |
|
| 15 |
+
const markdownComponents: Components = {
|
| 16 |
+
table: ({ children }) => (
|
| 17 |
+
<div className="my-3 overflow-x-auto rounded-lg border border-border/70">
|
| 18 |
+
<table className="min-w-full border-collapse text-left text-sm">
|
| 19 |
+
{children}
|
| 20 |
+
</table>
|
| 21 |
+
</div>
|
| 22 |
+
),
|
| 23 |
+
thead: ({ children }) => (
|
| 24 |
+
<thead className="bg-muted/60 text-foreground">{children}</thead>
|
| 25 |
+
),
|
| 26 |
+
th: ({ children }) => (
|
| 27 |
+
<th className="border-b border-border/70 px-3 py-2 font-semibold">
|
| 28 |
+
{children}
|
| 29 |
+
</th>
|
| 30 |
+
),
|
| 31 |
+
td: ({ children }) => (
|
| 32 |
+
<td className="border-b border-border/50 px-3 py-2 align-top">
|
| 33 |
+
{children}
|
| 34 |
+
</td>
|
| 35 |
+
),
|
| 36 |
+
pre: ({ children }) => (
|
| 37 |
+
<pre className="not-prose my-3 overflow-x-auto rounded-lg border border-border/70 bg-zinc-950 p-3 text-sm text-zinc-100">
|
| 38 |
+
{children}
|
| 39 |
+
</pre>
|
| 40 |
+
),
|
| 41 |
+
code: ({ className, children, ...props }) => {
|
| 42 |
+
const language = /language-(\w+)/.exec(className ?? "")?.[1];
|
| 43 |
+
|
| 44 |
+
return (
|
| 45 |
+
<code className={className} data-language={language} {...props}>
|
| 46 |
+
{children}
|
| 47 |
+
</code>
|
| 48 |
+
);
|
| 49 |
+
},
|
| 50 |
+
};
|
| 51 |
+
|
| 52 |
export default function MessageBubble({ message }: Props) {
|
| 53 |
const isUser = message.role === "user";
|
| 54 |
const [copied, setCopied] = useState(false);
|
|
|
|
| 109 |
)}
|
| 110 |
<div className={`prose-chat text-sm ${message.content ? "pr-7" : ""}`}>
|
| 111 |
{message.content ? (
|
| 112 |
+
<ReactMarkdown
|
| 113 |
+
remarkPlugins={[remarkGfm]}
|
| 114 |
+
rehypePlugins={[rehypeHighlight]}
|
| 115 |
+
components={markdownComponents}
|
| 116 |
+
>
|
| 117 |
{message.content}
|
| 118 |
</ReactMarkdown>
|
| 119 |
) : message.isStreaming ? (
|