blanchon commited on
Commit
15bf16f
1 Parent(s): 77df078

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 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("&amp;", "&")}</code>`;
67
  };
68
 
 
 
 
 
 
69
  const options: marked.MarkedOptions = {
70
- ...marked.getDefaults(),
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 dark:border-gray-800 dark:bg-gray-900 dark:hover:border-gray-700"
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("&amp;", "&")}</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