Add latex support with marked-katex-extension (#450)
Browse files* Add latex support with marked-katex-extension
* Add renderer
* Fix marked default option problem
* Fix linting error
* Fix lock error
- package-lock.json +45 -0
- package.json +1 -0
- src/lib/components/chat/ChatMessage.svelte +16 -4
- src/routes/conversation/[id]/+page.svelte +6 -0
package-lock.json
CHANGED
@@ -43,6 +43,7 @@
|
|
43 |
"eslint": "^8.28.0",
|
44 |
"eslint-config-prettier": "^8.5.0",
|
45 |
"eslint-plugin-svelte": "^2.27.3",
|
|
|
46 |
"prettier": "^2.8.0",
|
47 |
"prettier-plugin-svelte": "^2.8.1",
|
48 |
"prettier-plugin-tailwindcss": "^0.2.7",
|
@@ -1002,6 +1003,12 @@
|
|
1002 |
"integrity": "sha512-wOuvG1SN4Us4rez+tylwwwCV1psiNVOkJeM3AUWUNWg/jDQY2+HE/444y5gc+jBmRqASOm2Oeh5c1axHobwRKQ==",
|
1003 |
"dev": true
|
1004 |
},
|
|
|
|
|
|
|
|
|
|
|
|
|
1005 |
"node_modules/@types/long": {
|
1006 |
"version": "4.0.2",
|
1007 |
"resolved": "https://registry.npmjs.org/@types/long/-/long-4.0.2.tgz",
|
@@ -3328,6 +3335,31 @@
|
|
3328 |
"integrity": "sha512-gfFQZrcTc8CnKXp6Y4/CBT3fTc0OVuDofpre4aEeEpSBPV5X5v4+Vmx+8snU7RLPrNHPKSgLxGo9YuQzz20o+w==",
|
3329 |
"dev": true
|
3330 |
},
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
3331 |
"node_modules/kleur": {
|
3332 |
"version": "4.1.5",
|
3333 |
"resolved": "https://registry.npmjs.org/kleur/-/kleur-4.1.5.tgz",
|
@@ -3484,6 +3516,19 @@
|
|
3484 |
"node": ">= 12"
|
3485 |
}
|
3486 |
},
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
3487 |
"node_modules/md5-hex": {
|
3488 |
"version": "3.0.1",
|
3489 |
"resolved": "https://registry.npmjs.org/md5-hex/-/md5-hex-3.0.1.tgz",
|
|
|
43 |
"eslint": "^8.28.0",
|
44 |
"eslint-config-prettier": "^8.5.0",
|
45 |
"eslint-plugin-svelte": "^2.27.3",
|
46 |
+
"marked-katex-extension": "^3.0.6",
|
47 |
"prettier": "^2.8.0",
|
48 |
"prettier-plugin-svelte": "^2.8.1",
|
49 |
"prettier-plugin-tailwindcss": "^0.2.7",
|
|
|
1003 |
"integrity": "sha512-wOuvG1SN4Us4rez+tylwwwCV1psiNVOkJeM3AUWUNWg/jDQY2+HE/444y5gc+jBmRqASOm2Oeh5c1axHobwRKQ==",
|
1004 |
"dev": true
|
1005 |
},
|
1006 |
+
"node_modules/@types/katex": {
|
1007 |
+
"version": "0.16.3",
|
1008 |
+
"resolved": "https://registry.npmjs.org/@types/katex/-/katex-0.16.3.tgz",
|
1009 |
+
"integrity": "sha512-CeVMX9EhVUW8MWnei05eIRks4D5Wscw/W9Byz1s3PA+yJvcdvq9SaDjiUKvRvEgjpdTyJMjQA43ae4KTwsvOPg==",
|
1010 |
+
"dev": true
|
1011 |
+
},
|
1012 |
"node_modules/@types/long": {
|
1013 |
"version": "4.0.2",
|
1014 |
"resolved": "https://registry.npmjs.org/@types/long/-/long-4.0.2.tgz",
|
|
|
3335 |
"integrity": "sha512-gfFQZrcTc8CnKXp6Y4/CBT3fTc0OVuDofpre4aEeEpSBPV5X5v4+Vmx+8snU7RLPrNHPKSgLxGo9YuQzz20o+w==",
|
3336 |
"dev": true
|
3337 |
},
|
3338 |
+
"node_modules/katex": {
|
3339 |
+
"version": "0.16.8",
|
3340 |
+
"resolved": "https://registry.npmjs.org/katex/-/katex-0.16.8.tgz",
|
3341 |
+
"integrity": "sha512-ftuDnJbcbOckGY11OO+zg3OofESlbR5DRl2cmN8HeWeeFIV7wTXvAOx8kEjZjobhA+9wh2fbKeO6cdcA9Mnovg==",
|
3342 |
+
"dev": true,
|
3343 |
+
"funding": [
|
3344 |
+
"https://opencollective.com/katex",
|
3345 |
+
"https://github.com/sponsors/katex"
|
3346 |
+
],
|
3347 |
+
"dependencies": {
|
3348 |
+
"commander": "^8.3.0"
|
3349 |
+
},
|
3350 |
+
"bin": {
|
3351 |
+
"katex": "cli.js"
|
3352 |
+
}
|
3353 |
+
},
|
3354 |
+
"node_modules/katex/node_modules/commander": {
|
3355 |
+
"version": "8.3.0",
|
3356 |
+
"resolved": "https://registry.npmjs.org/commander/-/commander-8.3.0.tgz",
|
3357 |
+
"integrity": "sha512-OkTL9umf+He2DZkUq8f8J9of7yL6RJKI24dVITBmNfZBmri9zYZQrKkuXiKhyfPSu8tUhnVBB1iKXevvnlR4Ww==",
|
3358 |
+
"dev": true,
|
3359 |
+
"engines": {
|
3360 |
+
"node": ">= 12"
|
3361 |
+
}
|
3362 |
+
},
|
3363 |
"node_modules/kleur": {
|
3364 |
"version": "4.1.5",
|
3365 |
"resolved": "https://registry.npmjs.org/kleur/-/kleur-4.1.5.tgz",
|
|
|
3516 |
"node": ">= 12"
|
3517 |
}
|
3518 |
},
|
3519 |
+
"node_modules/marked-katex-extension": {
|
3520 |
+
"version": "3.0.6",
|
3521 |
+
"resolved": "https://registry.npmjs.org/marked-katex-extension/-/marked-katex-extension-3.0.6.tgz",
|
3522 |
+
"integrity": "sha512-X1XPjXVFcE0zo6oCcHuIOUrFCzUNMOPXqh05c18kNEB/htLSohrJTzOSWhDnNyVynoTiYrl8IhwZu6C0lTNFAQ==",
|
3523 |
+
"dev": true,
|
3524 |
+
"dependencies": {
|
3525 |
+
"@types/katex": "^0.16.2",
|
3526 |
+
"katex": "^0.16.8"
|
3527 |
+
},
|
3528 |
+
"peerDependencies": {
|
3529 |
+
"marked": ">=4 <10"
|
3530 |
+
}
|
3531 |
+
},
|
3532 |
"node_modules/md5-hex": {
|
3533 |
"version": "3.0.1",
|
3534 |
"resolved": "https://registry.npmjs.org/md5-hex/-/md5-hex-3.0.1.tgz",
|
package.json
CHANGED
@@ -27,6 +27,7 @@
|
|
27 |
"eslint": "^8.28.0",
|
28 |
"eslint-config-prettier": "^8.5.0",
|
29 |
"eslint-plugin-svelte": "^2.27.3",
|
|
|
30 |
"prettier": "^2.8.0",
|
31 |
"prettier-plugin-svelte": "^2.8.1",
|
32 |
"prettier-plugin-tailwindcss": "^0.2.7",
|
|
|
27 |
"eslint": "^8.28.0",
|
28 |
"eslint-config-prettier": "^8.5.0",
|
29 |
"eslint-plugin-svelte": "^2.27.3",
|
30 |
+
"marked-katex-extension": "^3.0.6",
|
31 |
"prettier": "^2.8.0",
|
32 |
"prettier-plugin-svelte": "^2.8.1",
|
33 |
"prettier-plugin-tailwindcss": "^0.2.7",
|
src/lib/components/chat/ChatMessage.svelte
CHANGED
@@ -1,5 +1,6 @@
|
|
1 |
<script lang="ts">
|
2 |
import { marked } from "marked";
|
|
|
3 |
import type { Message } from "$lib/types/Message";
|
4 |
import { afterUpdate, createEventDispatcher } from "svelte";
|
5 |
import { deepestChild } from "$lib/utils/deepestChild";
|
@@ -59,20 +60,31 @@
|
|
59 |
let pendingTimeout: ReturnType<typeof setTimeout>;
|
60 |
|
61 |
const renderer = new marked.Renderer();
|
62 |
-
|
63 |
// For code blocks with simple backticks
|
64 |
renderer.codespan = (code) => {
|
65 |
// Unsanitize double-sanitized code
|
66 |
return `<code>${code.replaceAll("&", "&")}</code>`;
|
67 |
};
|
68 |
|
|
|
|
|
|
|
|
|
|
|
69 |
const options: marked.MarkedOptions = {
|
70 |
-
...
|
71 |
gfm: true,
|
72 |
breaks: true,
|
73 |
renderer,
|
74 |
};
|
75 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
76 |
$: tokens = marked.lexer(sanitizeMd(message.content));
|
77 |
|
78 |
afterUpdate(() => {
|
@@ -140,7 +152,7 @@
|
|
140 |
<CodeBlock lang={token.lang} code={unsanitizeMd(token.text)} />
|
141 |
{:else}
|
142 |
<!-- eslint-disable-next-line svelte/no-at-html-tags -->
|
143 |
-
{@html marked(token.raw, options)}
|
144 |
{/if}
|
145 |
{/each}
|
146 |
</div>
|
@@ -150,7 +162,7 @@
|
|
150 |
<div class="text-gray-400">Sources:</div>
|
151 |
{#each webSearchSources as { link, title, hostname }}
|
152 |
<a
|
153 |
-
class="flex items-center gap-2 whitespace-nowrap rounded-lg border bg-white px-2 py-1.5 leading-none hover:border-gray-300
|
154 |
href={link}
|
155 |
target="_blank"
|
156 |
>
|
|
|
1 |
<script lang="ts">
|
2 |
import { marked } from "marked";
|
3 |
+
import markedKatex from "marked-katex-extension";
|
4 |
import type { Message } from "$lib/types/Message";
|
5 |
import { afterUpdate, createEventDispatcher } from "svelte";
|
6 |
import { deepestChild } from "$lib/utils/deepestChild";
|
|
|
60 |
let pendingTimeout: ReturnType<typeof setTimeout>;
|
61 |
|
62 |
const renderer = new marked.Renderer();
|
|
|
63 |
// For code blocks with simple backticks
|
64 |
renderer.codespan = (code) => {
|
65 |
// Unsanitize double-sanitized code
|
66 |
return `<code>${code.replaceAll("&", "&")}</code>`;
|
67 |
};
|
68 |
|
69 |
+
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
70 |
+
const { extensions, ...defaults } = marked.getDefaults() as marked.MarkedOptions & {
|
71 |
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
72 |
+
extensions: any;
|
73 |
+
};
|
74 |
const options: marked.MarkedOptions = {
|
75 |
+
...defaults,
|
76 |
gfm: true,
|
77 |
breaks: true,
|
78 |
renderer,
|
79 |
};
|
80 |
|
81 |
+
marked.use(
|
82 |
+
markedKatex({
|
83 |
+
throwOnError: false,
|
84 |
+
// output: "html",
|
85 |
+
})
|
86 |
+
);
|
87 |
+
|
88 |
$: tokens = marked.lexer(sanitizeMd(message.content));
|
89 |
|
90 |
afterUpdate(() => {
|
|
|
152 |
<CodeBlock lang={token.lang} code={unsanitizeMd(token.text)} />
|
153 |
{:else}
|
154 |
<!-- eslint-disable-next-line svelte/no-at-html-tags -->
|
155 |
+
{@html marked.parse(token.raw, options)}
|
156 |
{/if}
|
157 |
{/each}
|
158 |
</div>
|
|
|
162 |
<div class="text-gray-400">Sources:</div>
|
163 |
{#each webSearchSources as { link, title, hostname }}
|
164 |
<a
|
165 |
+
class="flex items-center gap-2 whitespace-nowrap rounded-lg border bg-white px-2 py-1.5 leading-none hover:border-gray-300 dark:border-gray-800 dark:bg-gray-900 dark:hover:border-gray-700"
|
166 |
href={link}
|
167 |
target="_blank"
|
168 |
>
|
src/routes/conversation/[id]/+page.svelte
CHANGED
@@ -270,6 +270,12 @@
|
|
270 |
|
271 |
<svelte:head>
|
272 |
<title>{title}</title>
|
|
|
|
|
|
|
|
|
|
|
|
|
273 |
</svelte:head>
|
274 |
|
275 |
<ChatWindow
|
|
|
270 |
|
271 |
<svelte:head>
|
272 |
<title>{title}</title>
|
273 |
+
<link
|
274 |
+
rel="stylesheet"
|
275 |
+
href="https://cdn.jsdelivr.net/npm/[email protected]/dist/katex.min.css"
|
276 |
+
integrity="sha384-GvrOXuhMATgEsSwCs4smul74iXGOixntILdUW9XmUC6+HX0sLNAK3q71HotJqlAn"
|
277 |
+
crossorigin="anonymous"
|
278 |
+
/>
|
279 |
</svelte:head>
|
280 |
|
281 |
<ChatWindow
|