feat(webui): render markdown in response bubbles
linuztx committed
Jun 13, 2025 at 16:47 UTC
be067adf965603bd50e27e5efbcd2491af320491
1 file changed
+48
-7
webui/js/messages.js
+48
-7
@@ -1,5 +1,6 @@
1
// copy button
2
import { openImageModal } from "./image_modal.js";
3
+import { marked } from "https://cdn.jsdelivr.net/npm/marked/lib/marked.esm.js";
4
5
function createCopyButton() {
6
const button = document.createElement("button");
@@ -12,11 +13,11 @@ function createCopyButton() {
13
let textToCopy;
14
15
if (container.classList.contains("kvps-row")) {
15
- textToCopy = container.querySelector(".kvps-val").textContent;
16
+ textToCopy = container.querySelector(".kvps-val").innerText;
17
} else if (container.classList.contains("message-text")) {
17
- textToCopy = container.querySelector("span").textContent;
18
+ textToCopy = container.querySelector("span").innerText;
19
} else {
19
- textToCopy = container.querySelector("span").textContent;
20
+ textToCopy = container.querySelector("span").innerText;
21
}
22
23
try {
@@ -83,7 +84,8 @@ export function _drawMessage(
84
kvps = null,
85
messageClasses = [],
86
contentClasses = [],
86
- latex = false
87
+ latex = false,
88
+ markdown = false
89
) {
90
const messageDiv = document.createElement("div");
91
messageDiv.classList.add("message", ...messageClasses);
@@ -97,6 +99,30 @@ export function _drawMessage(
99
drawKvps(messageDiv, kvps, latex);
100
101
if (content && content.trim().length > 0) {
102
+ if (markdown) {
103
+ const contentDiv = document.createElement("div");
104
+ contentDiv.classList.add("msg-content", ...contentClasses);
105
+
106
+ const spanElement = document.createElement("span"); // Wrapper span
107
+ spanElement.innerHTML = marked.parse(content, { breaks: true });
108
+ contentDiv.appendChild(spanElement);
109
+
110
+ addCopyButtonToElement(contentDiv);
111
+ messageDiv.appendChild(contentDiv);
112
+
113
+ // KaTeX rendering for markdown
114
+ if (window.renderMathInElement && latex) {
115
+ renderMathInElement(contentDiv, {
116
+ delimiters: [
117
+ { left: '$$', right: '$$', display: true },
118
+ { left: '$', right: '$', display: false },
119
+ { left: '\\(', right: '\\)', display: false },
120
+ { left: '\\[', right: '\\]', display: true }
121
+ ],
122
+ throwOnError: false,
123
+ });
124
+ }
125
+ } else {
126
const preElement = document.createElement("pre");
127
preElement.classList.add("msg-content", ...contentClasses);
128
preElement.style.whiteSpace = "pre-wrap";
@@ -120,6 +146,7 @@ export function _drawMessage(
146
delimiters: [{ left: "$", right: "$", display: true }],
147
throwOnError: false,
148
});
149
+ }
150
}
151
}
152
@@ -150,6 +177,7 @@ export function drawMessageDefault(
177
kvps,
178
["message-ai", "message-default"],
179
["msg-json"],
180
+ false,
181
false
182
);
183
}
@@ -178,6 +206,7 @@ export function drawMessageAgent(
206
kvpsFlat,
207
["message-ai", "message-agent"],
208
["msg-json"],
209
+ false,
210
false
211
);
212
}
@@ -200,6 +229,7 @@ export function drawMessageResponse(
229
null,
230
["message-ai", "message-agent-response"],
231
[],
232
+ true,
233
true
234
);
235
}
@@ -216,13 +246,14 @@ export function drawMessageDelegation(
246
_drawMessage(
247
messageContainer,
248
heading,
219
- messageContent,
249
+ content,
250
temp,
251
true,
252
kvps,
253
["message-ai", "message-agent", "message-agent-delegation"],
254
[],
225
- true
255
+ true,
256
+ false
257
);
258
}
259
@@ -340,6 +371,7 @@ export function drawMessageTool(
371
kvps,
372
["message-ai", "message-tool"],
373
["msg-output"],
374
+ false,
375
false
376
);
377
}
@@ -362,6 +394,7 @@ export function drawMessageCodeExe(
394
null,
395
["message-ai", "message-code-exe"],
396
[],
397
+ false,
398
false
399
);
400
}
@@ -384,6 +417,7 @@ export function drawMessageBrowser(
417
kvps,
418
["message-ai", "message-browser"],
419
["msg-json"],
420
+ false,
421
false
422
);
423
}
@@ -407,6 +441,7 @@ export function drawMessageAgentPlain(
441
kvps,
442
[...classes],
443
[],
444
+ false,
445
false
446
);
447
messageContainer.classList.add("center-container");
@@ -451,6 +486,7 @@ export function drawMessageUtil(
486
kvps,
487
["message-util"],
488
["msg-json"],
489
+ false,
490
false
491
);
492
messageContainer.classList.add("center-container");
@@ -556,7 +592,12 @@ function drawKvps(container, kvps, latex) {
592
593
if (window.renderMathInElement && latex) {
594
renderMathInElement(span, {
559
- delimiters: [{ left: "$", right: "$", display: true }],
595
+ delimiters: [
596
+ { left: "$$", right: "$$", display: true },
597
+ { left: "$", right: "$", display: false },
598
+ { left: "\\(", right: "\\)", display: false },
599
+ { left: "\\[", right: "\\]", display: true }
600
+ ],
601
throwOnError: false,
602
});
603
}