black-yt commited on
Commit
6798401
·
1 Parent(s): 1fefce0

Render math in frontend Markdown

Browse files
frontend/static/app.css CHANGED
@@ -390,6 +390,17 @@ button {
390
  word-break: break-word;
391
  }
392
 
 
 
 
 
 
 
 
 
 
 
 
393
  .markdown-body > *:first-child {
394
  margin-top: 0;
395
  }
 
390
  word-break: break-word;
391
  }
392
 
393
+ .markdown-body .katex {
394
+ font-size: 1.02em;
395
+ }
396
+
397
+ .markdown-body .katex-display {
398
+ margin: 0.85rem 0;
399
+ overflow-x: auto;
400
+ overflow-y: hidden;
401
+ padding-bottom: 0.1rem;
402
+ }
403
+
404
  .markdown-body > *:first-child {
405
  margin-top: 0;
406
  }
frontend/static/app.js CHANGED
@@ -164,14 +164,53 @@
164
  .replaceAll("'", "'");
165
  }
166
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
167
  function renderMarkdown(text) {
168
  if (!window.marked || !window.DOMPurify) {
169
  console.warn("Markdown renderer unavailable; falling back to plain text.");
170
  return "<pre>" + escapeHtml(text) + "</pre>";
171
  }
172
  try {
173
- var rawHtml = window.marked.parse(String(text || ""), { gfm: true, breaks: false, async: false });
 
174
  var safeHtml = window.DOMPurify.sanitize(rawHtml, { USE_PROFILES: { html: true } });
 
175
  return '<div class="markdown-body">' + safeHtml + "</div>";
176
  } catch (e) {
177
  console.warn("Markdown rendering failed; falling back to plain text.", e);
@@ -338,6 +377,7 @@
338
  toggleEvent(node);
339
  });
340
  timeline.appendChild(node);
 
341
  setEventExpanded(node, true, false);
342
  scrollTimeline(shouldFollow);
343
  }
 
164
  .replaceAll("'", "&#039;");
165
  }
166
 
167
+ function protectMathSegments(text) {
168
+ var segments = [];
169
+ var protectedText = String(text || "").replace(/(\$\$[\s\S]+?\$\$|\\\[[\s\S]+?\\\]|\\\([\s\S]+?\\\))/g, function (match) {
170
+ var token = "@@RH_MATH_" + segments.length + "@@";
171
+ segments.push({ token: token, text: match });
172
+ return token;
173
+ });
174
+ return { text: protectedText, segments: segments };
175
+ }
176
+
177
+ function restoreMathSegments(html, segments) {
178
+ var restored = String(html || "");
179
+ (segments || []).forEach(function (segment) {
180
+ restored = restored.split(segment.token).join(escapeHtml(segment.text));
181
+ });
182
+ return restored;
183
+ }
184
+
185
+ function renderMathInMarkdown(container) {
186
+ if (!window.renderMathInElement) return;
187
+ container.querySelectorAll(".markdown-body").forEach(function (body) {
188
+ try {
189
+ window.renderMathInElement(body, {
190
+ delimiters: [
191
+ { left: "$$", right: "$$", display: true },
192
+ { left: "\\[", right: "\\]", display: true },
193
+ { left: "\\(", right: "\\)", display: false }
194
+ ],
195
+ ignoredTags: ["script", "noscript", "style", "textarea", "pre", "code"],
196
+ throwOnError: false
197
+ });
198
+ } catch (e) {
199
+ console.warn("Math rendering failed.", e);
200
+ }
201
+ });
202
+ }
203
+
204
  function renderMarkdown(text) {
205
  if (!window.marked || !window.DOMPurify) {
206
  console.warn("Markdown renderer unavailable; falling back to plain text.");
207
  return "<pre>" + escapeHtml(text) + "</pre>";
208
  }
209
  try {
210
+ var protectedMath = protectMathSegments(text);
211
+ var rawHtml = window.marked.parse(protectedMath.text, { gfm: true, breaks: false, async: false });
212
  var safeHtml = window.DOMPurify.sanitize(rawHtml, { USE_PROFILES: { html: true } });
213
+ safeHtml = restoreMathSegments(safeHtml, protectedMath.segments);
214
  return '<div class="markdown-body">' + safeHtml + "</div>";
215
  } catch (e) {
216
  console.warn("Markdown rendering failed; falling back to plain text.", e);
 
377
  toggleEvent(node);
378
  });
379
  timeline.appendChild(node);
380
+ renderMathInMarkdown(node);
381
  setEventExpanded(node, true, false);
382
  scrollTimeline(shouldFollow);
383
  }
frontend/static/index.html CHANGED
@@ -5,6 +5,7 @@
5
  <meta name="viewport" content="width=device-width, initial-scale=1" />
6
  <title>ResearchHarness Chat</title>
7
  <link rel="icon" type="image/svg+xml" href="/static/favicon.svg?v=rocket-1" />
 
8
  <link rel="stylesheet" href="/static/app.css" />
9
  </head>
10
  <body>
@@ -80,6 +81,8 @@
80
  </nav>
81
  <script src="https://cdn.jsdelivr.net/npm/dompurify@3.2.6/dist/purify.min.js"></script>
82
  <script src="https://cdn.jsdelivr.net/npm/marked@15.0.12/marked.min.js"></script>
 
 
83
  <script src="/static/app.js"></script>
84
  </body>
85
  </html>
 
5
  <meta name="viewport" content="width=device-width, initial-scale=1" />
6
  <title>ResearchHarness Chat</title>
7
  <link rel="icon" type="image/svg+xml" href="/static/favicon.svg?v=rocket-1" />
8
+ <link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/katex@0.16.11/dist/katex.min.css" />
9
  <link rel="stylesheet" href="/static/app.css" />
10
  </head>
11
  <body>
 
81
  </nav>
82
  <script src="https://cdn.jsdelivr.net/npm/dompurify@3.2.6/dist/purify.min.js"></script>
83
  <script src="https://cdn.jsdelivr.net/npm/marked@15.0.12/marked.min.js"></script>
84
+ <script src="https://cdn.jsdelivr.net/npm/katex@0.16.11/dist/katex.min.js"></script>
85
+ <script src="https://cdn.jsdelivr.net/npm/katex@0.16.11/dist/contrib/auto-render.min.js"></script>
86
  <script src="/static/app.js"></script>
87
  </body>
88
  </html>