katex fix
frdel committed
Jun 18, 2025 at 13:50 UTC
ea708e6e352f85b60ac310a5511fccd1833c3938
2 files changed
+58
-51
prompts/default/agent.system.tool.response.md
+1
-1
@@ -8,7 +8,7 @@ use emojis as icons improve readability
8
prefer using tables
9
output full file paths not only names they are clickable
10
images shown with 
11
-math with latex notation delimiters <$LATEX>...</$LATEX>
11
+all math with latex notation delimiters <latex>x = ...</latex>
12
usage:
13
~~~json
14
{
webui/js/messages.js
+57
-50
@@ -2,8 +2,8 @@
2
import { openImageModal } from "./image_modal.js";
3
import { marked } from "https://cdn.jsdelivr.net/npm/marked/lib/marked.esm.js";
4
5
-const katexDelimiterLeft = "<$LATEX>";
6
-const katexDelimiterRight = "</$LATEX>";
5
+const katexDelimiterLeft = "<latex>";
6
+const katexDelimiterRight = "</latex>";
7
8
function createCopyButton() {
9
const button = document.createElement("button");
@@ -100,7 +100,7 @@ export function _drawMessage(
100
messageDiv.appendChild(headingElement);
101
}
102
103
- drawKvps(messageDiv, kvps, latex);
103
+ drawKvps(messageDiv, kvps, false);
104
105
if (content && content.trim().length > 0) {
106
if (markdown) {
@@ -109,29 +109,41 @@ export function _drawMessage(
109
110
const spanElement = document.createElement("span"); // Wrapper span
111
let processedContent = content;
112
+
113
processedContent = convertImageTags(processedContent);
114
processedContent = convertImgFilePaths(processedContent);
115
processedContent = marked.parse(processedContent, { breaks: true });
116
processedContent = convertPathsToLinks(processedContent);
117
spanElement.innerHTML = processedContent;
117
- contentDiv.appendChild(spanElement);
118
-
119
- addCopyButtonToElement(contentDiv);
120
- messageDiv.appendChild(contentDiv);
118
119
// KaTeX rendering for markdown
120
if (window.renderMathInElement && latex) {
124
- renderMathInElement(contentDiv, {
125
- delimiters: [
126
- {
127
- left: katexDelimiterLeft,
128
- right: katexDelimiterRight,
129
- display: true,
130
- },
131
- ],
132
- throwOnError: false,
121
+ spanElement.querySelectorAll("latex").forEach((element) => {
122
+ katex.render(element.innerHTML, element, {
123
+ throwOnError: false,
124
+ });
125
});
126
+
127
+ // renderMathInElement(spanElement, {
128
+ // delimiters: [
129
+ // {
130
+ // left: katexDelimiterLeft,
131
+ // right: katexDelimiterRight,
132
+ // display: true,
133
+ // },
134
+ // {
135
+ // left: escapeHTML(katexDelimiterLeft),
136
+ // right: escapeHTML(katexDelimiterRight),
137
+ // display: true,
138
+ // },
139
+ // ],
140
+ // throwOnError: true,
141
+ // });
142
}
143
+
144
+ contentDiv.appendChild(spanElement);
145
+ addCopyButtonToElement(contentDiv);
146
+ messageDiv.appendChild(contentDiv);
147
} else {
148
const preElement = document.createElement("pre");
149
preElement.classList.add("msg-content", ...contentClasses);
@@ -149,20 +161,6 @@ export function _drawMessage(
161
preElement.appendChild(spanElement);
162
addCopyButtonToElement(preElement);
163
messageDiv.appendChild(preElement);
152
-
153
- // Render LaTeX math within the span
154
- if (window.renderMathInElement && latex) {
155
- renderMathInElement(spanElement, {
156
- delimiters: [
157
- {
158
- left: katexDelimiterLeft,
159
- right: katexDelimiterRight,
160
- display: true,
161
- },
162
- ],
163
- throwOnError: false,
164
- });
165
- }
164
}
165
}
166
@@ -609,7 +607,13 @@ function drawKvps(container, kvps, latex) {
607
608
if (window.renderMathInElement && latex) {
609
renderMathInElement(span, {
612
- delimiters: [{ left: katexDelimiterLeft, right: katexDelimiterRight, display: true }],
610
+ delimiters: [
611
+ {
612
+ left: katexDelimiterLeft,
613
+ right: katexDelimiterRight,
614
+ display: true,
615
+ },
616
+ ],
617
throwOnError: false,
618
});
619
}
@@ -677,7 +681,7 @@ function convertHTML(str) {
681
}
682
683
function convertImgFilePaths(str) {
680
- return str.replace("img://", "/image_get?path=");
684
+ return str.replace("img://", "/image_get?path=");
685
}
686
687
function escapeHTML(str) {
@@ -693,35 +697,38 @@ function escapeHTML(str) {
697
698
function convertPathsToLinks(str) {
699
function generateLinks(match) {
696
- const parts = match.split("/");
697
- if (!parts[0]) parts.shift(); // drop empty element left of first “/”
698
- let conc = "";
699
- let html = "";
700
- for (const part of parts) {
701
- conc += "/" + part;
702
- html += `/<a href="#" class="path-link" onclick="openFileLink('${conc}');">${part}</a>`;
703
- }
704
- return html;
700
+ const parts = match.split("/");
701
+ if (!parts[0]) parts.shift(); // drop empty element left of first “/”
702
+ let conc = "";
703
+ let html = "";
704
+ for (const part of parts) {
705
+ conc += "/" + part;
706
+ html += `/<a href="#" class="path-link" onclick="openFileLink('${conc}');">${part}</a>`;
707
+ }
708
+ return html;
709
}
710
711
const prefix = `(?:^|[> \`'"\\n]|'|")`;
712
const folder = `[a-zA-Z0-9_\\/.\\-]`;
713
const file = `[a-zA-Z0-9_\\-\\/]`;
714
const suffix = `(?<!\\.)`;
711
- const pathRegex = new RegExp(`(?<=${prefix})\\/${folder}*${file}${suffix}`, "g");
715
+ const pathRegex = new RegExp(
716
+ `(?<=${prefix})\\/${folder}*${file}${suffix}`,
717
+ "g"
718
+ );
719
720
// skip paths inside html tags, like <img src="/path/to/image">
721
const tagRegex = /(<(?:[^<>"']+|"[^"]*"|'[^']*')*>)/g;
722
723
return str
717
- .split(tagRegex) // keep tags & text separate
718
- .map(chunk => {
719
- // if it *starts* with '<', it’s a tag -> leave untouched
720
- if (chunk.startsWith("<")) return chunk;
721
- // otherwise run your link-generation
722
- return chunk.replace(pathRegex, generateLinks);
723
- })
724
- .join("");
724
+ .split(tagRegex) // keep tags & text separate
725
+ .map((chunk) => {
726
+ // if it *starts* with '<', it’s a tag -> leave untouched
727
+ if (chunk.startsWith("<")) return chunk;
728
+ // otherwise run your link-generation
729
+ return chunk.replace(pathRegex, generateLinks);
730
+ })
731
+ .join("");
732
}
733
734
// function convertPathsToLinksInHtml(htmlString) {