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 ![alt](img:///path/to/image.png)
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]|&#39;|&quot;)`;
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) {