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
|