fix latex rendering

This commit is contained in:
Xuan Son Nguyen 2024-12-14 12:55:21 +01:00
parent 10aa898c83
commit bb4e17fc70
6 changed files with 80 additions and 4 deletions

Binary file not shown.

View file

@ -12,6 +12,7 @@
"autoprefixer": "^10.4.20", "autoprefixer": "^10.4.20",
"daisyui": "^4.12.14", "daisyui": "^4.12.14",
"highlight.js": "^11.10.0", "highlight.js": "^11.10.0",
"katex": "^0.16.15",
"markdown-it": "^14.1.0", "markdown-it": "^14.1.0",
"postcss": "^8.4.49", "postcss": "^8.4.49",
"tailwindcss": "^3.4.15", "tailwindcss": "^3.4.15",

View file

@ -18,6 +18,7 @@
"autoprefixer": "^10.4.20", "autoprefixer": "^10.4.20",
"daisyui": "^4.12.14", "daisyui": "^4.12.14",
"highlight.js": "^11.10.0", "highlight.js": "^11.10.0",
"katex": "^0.16.15",
"markdown-it": "^14.1.0", "markdown-it": "^14.1.0",
"postcss": "^8.4.49", "postcss": "^8.4.49",
"tailwindcss": "^3.4.15", "tailwindcss": "^3.4.15",

View file

@ -11,7 +11,7 @@
{ {
"id": 1734087548327, "id": 1734087548327,
"role": "assistant", "role": "assistant",
"content": "This is the formula:\n$\\frac{e^{x_i}}{\\sum_{j=1}^{n}e^{x_j}}$\n\nGiven an input vector \\(\\mathbf{x} = [x_1, x_2, \\ldots, x_n]\\)\n\n\\[\ny_i = \\frac{e^{x_i}}{\\sum_{j=1}^n e^{x_j}}\n\\]\n\nCode block latex:\n```latex\n\\frac{e^{x_i}}{\\sum_{j=1}^{n}e^{x_j}}\n```", "content": "This is the formula:\n$\\frac{e^{x_i}}{\\sum_{j=1}^{n}e^{x_j}}$\n\nGiven an input vector \\(\\mathbf{x} = [x_1, x_2, \\ldots, x_n]\\)\n\n\\[\ny_i = \\frac{e^{x_i}}{\\sum_{j=1}^n e^{x_j}}\n\\]\n\nCode block latex:\n```latex\n\\frac{e^{x_i}}{\\sum_{j=1}^{n}e^{x_j}}\n```\n\nTest dollar sign: $1234 $4567\n\nInvalid latex syntax: $E = mc^$ and $$E = mc^$$",
"timings": { "timings": {
"prompt_n": 1, "prompt_n": 1,
"prompt_ms": 28.923, "prompt_ms": 28.923,
@ -30,4 +30,4 @@
"content": "Code block js:\n```js\nconsole.log('hello world')\n```" "content": "Code block js:\n```js\nconsole.log('hello world')\n```"
} }
] ]
} }

View file

@ -0,0 +1,66 @@
import katex from 'katex';
// Adapted from https://github.com/SchneeHertz/markdown-it-katex-gpt
// MIT license
const defaultOptions = {
delimiters: [
{ left: '\\[', right: '\\]', display: true },
{ left: '\\(', right: '\\)', display: false },
],
};
export function renderLatexHTML(content, display = false) {
return katex.renderToString(content, {
throwOnError: false,
output: 'mathml',
displayMode: display,
});
}
function escapedBracketRule(options) {
return (state, silent) => {
const max = state.posMax;
const start = state.pos;
for (const { left, right, display } of options.delimiters) {
// Check if it starts with the left delimiter
if (!state.src.slice(start).startsWith(left)) continue;
// Skip the length of the left delimiter
let pos = start + left.length;
// Find the matching right delimiter
while (pos < max) {
if (state.src.slice(pos).startsWith(right)) {
break;
}
pos++;
}
// No matching right delimiter found, skip to the next match
if (pos >= max) continue;
// If not in silent mode, convert LaTeX formula to MathML
if (!silent) {
const content = state.src.slice(start + left.length, pos);
try {
const renderedContent = renderLatexHTML(content, display);
const token = state.push('html_inline', '', 0);
token.content = renderedContent;
} catch (e) {
console.error(e);
}
}
// Update position, skip the length of the right delimiter
state.pos = pos + right.length;
return true;
}
}
}
export default function (md, options = defaultOptions) {
md.inline.ruler.after('text', 'escaped_bracket', escapedBracketRule(options));
}

View file

@ -5,7 +5,8 @@ import TextLineStream from 'textlinestream';
// math formula rendering // math formula rendering
import 'katex/dist/katex.min.css'; import 'katex/dist/katex.min.css';
import markdownItKatex from '@vscode/markdown-it-katex'; import markdownItKatexGpt, { renderLatexHTML } from './katex-gpt';
import markdownItKatexNormal from '@vscode/markdown-it-katex';
// code highlighting // code highlighting
import hljs from './highlight-config'; import hljs from './highlight-config';
@ -90,6 +91,9 @@ const VueMarkdown = defineComponent(
const md = shallowRef(new MarkdownIt({ const md = shallowRef(new MarkdownIt({
breaks: true, breaks: true,
highlight: function (str, lang) { // Add highlight.js highlight: function (str, lang) { // Add highlight.js
if (lang === 'latex') {
return renderLatexHTML(str, true);
}
if (lang && hljs.getLanguage(lang)) { if (lang && hljs.getLanguage(lang)) {
try { try {
return '<pre><code class="hljs">' + return '<pre><code class="hljs">' +
@ -100,7 +104,8 @@ const VueMarkdown = defineComponent(
return '<pre><code class="hljs">' + md.value.utils.escapeHtml(str) + '</code></pre>'; return '<pre><code class="hljs">' + md.value.utils.escapeHtml(str) + '</code></pre>';
} }
})); }));
md.value.use(markdownItKatex, { // support latex with double dollar sign and square brackets
md.value.use(markdownItKatexGpt, {
delimiters: [ delimiters: [
{ left: '\\[', right: '\\]', display: true }, { left: '\\[', right: '\\]', display: true },
{ left: '\\(', right: '\\)', display: false }, { left: '\\(', right: '\\)', display: false },
@ -108,6 +113,9 @@ const VueMarkdown = defineComponent(
], ],
throwOnError: false, throwOnError: false,
}); });
// support latex with single dollar sign
md.value.use(markdownItKatexNormal, { throwOnError: false });
// add copy button to code blocks
const origFenchRenderer = md.value.renderer.rules.fence; const origFenchRenderer = md.value.renderer.rules.fence;
md.value.renderer.rules.fence = (tokens, idx, ...args) => { md.value.renderer.rules.fence = (tokens, idx, ...args) => {
const content = tokens[idx].content; const content = tokens[idx].content;