diff --git a/lyra/web/static/index.html b/lyra/web/static/index.html
index 8c73e65..fad6703 100644
--- a/lyra/web/static/index.html
+++ b/lyra/web/static/index.html
@@ -298,12 +298,47 @@
}
}
+ function renderMarkdown(text) {
+ var bt = String.fromCharCode(96);
+ var esc = function (s) { return s.replace(/&/g, "&").replace(//g, ">").replace(/"/g, """); };
+ var src = String(text == null ? "" : text).replace(/\r\n/g, "\n");
+ var blocks = [];
+ var fenceRe = new RegExp(bt + bt + bt + "[^\\n]*\\n?([\\s\\S]*?)" + bt + bt + bt, "g");
+ src = src.replace(fenceRe, function (_, code) { blocks.push(code.replace(/\n+$/, "")); return "@@CB" + (blocks.length - 1) + "@@"; });
+ var codeRe = new RegExp(bt + "([^" + bt + "]+)" + bt, "g");
+ var inline = function (s) {
+ return esc(s)
+ .replace(codeRe, "$1")
+ .replace(/\*\*([^*]+)\*\*/g, "$1")
+ .replace(/__([^_]+)__/g, "$1")
+ .replace(/\*([^*\n]+)\*/g, "$1")
+ .replace(/\[([^\]]+)\]\((https?:\/\/[^\s)]+)\)/g, '$1')
+ .replace(/(^|[\s(])(https?:\/\/[^\s<)]+)/g, '$1$2');
+ };
+ var lines = src.split("\n");
+ var out = [], para = [], list = null;
+ var flushPara = function () { if (para.length) { out.push("
" + para.map(inline).join("
") + "
"); para = []; } };
+ var flushList = function () { if (list) { out.push("<" + list.t + ">" + list.items.map(function (it) { return "" + inline(it) + ""; }).join("") + "" + list.t + ">"); list = null; } };
+ var flushAll = function () { flushPara(); flushList(); };
+ for (var i = 0; i < lines.length; i++) {
+ var line = lines[i].replace(/\s+$/, ""); var t = line.trim(); var m;
+ if ((m = t.match(/^@@CB(\d+)@@$/))) { flushAll(); out.push("" + esc(blocks[+m[1]]) + "
"); continue; }
+ if (!t) { flushAll(); continue; }
+ if ((m = line.match(/^(#{1,4})\s+(.*)$/))) { flushAll(); out.push("" + inline(m[2]) + ""); continue; }
+ if ((m = line.match(/^\s*\d+[.)]\s+(.*)$/))) { flushPara(); if (!list || list.t !== "ol") { flushList(); list = { t: "ol", items: [] }; } list.items.push(m[1]); continue; }
+ if ((m = line.match(/^\s*[-*+]\s+(.*)$/))) { flushPara(); if (!list || list.t !== "ul") { flushList(); list = { t: "ul", items: [] }; } list.items.push(m[1]); continue; }
+ flushList(); para.push(line);
+ }
+ flushAll();
+ return out.join("\n");
+ }
+
function addMessage(role, text, autoScroll = true) {
const messagesEl = document.getElementById("messages");
const msgDiv = document.createElement("div");
msgDiv.className = `msg ${role}`;
- msgDiv.textContent = text;
+ if (role === "assistant") { msgDiv.innerHTML = renderMarkdown(text); } else { msgDiv.textContent = text; }
messagesEl.appendChild(msgDiv);
// Auto-scroll to bottom if enabled
diff --git a/lyra/web/static/style.css b/lyra/web/static/style.css
index a93bf8a..fd4fe6f 100644
--- a/lyra/web/static/style.css
+++ b/lyra/web/static/style.css
@@ -907,59 +907,90 @@ select:hover {
display: none !important;
}
}
-
-/* ---- Live Log lines ---- */
-.log-line {
- display: flex;
- flex-wrap: wrap;
- align-items: baseline;
- gap: 8px;
- padding: 4px 8px;
- border-radius: 4px;
- font-size: 0.8rem;
- font-family: 'Courier New', monospace;
- border-left: 3px solid var(--text-fade);
- animation: thinkingSlideIn 0.25s ease-out;
- word-break: break-word;
-}
-.log-time { color: var(--text-fade); flex-shrink: 0; }
-.log-level {
- flex-shrink: 0;
- text-transform: uppercase;
- font-size: 0.7rem;
- font-weight: bold;
- letter-spacing: 0.05em;
-}
-.log-msg { color: var(--text); }
-.log-fields { color: var(--text-fade); width: 100%; padding-left: 4px; }
-
-.log-info { border-left-color: #00bfff; }
-.log-info .log-level { color: #7dd3fc; }
-.log-debug { border-left-color: #8a2be2; }
-.log-debug .log-level { color: #c79cff; }
-.log-error { border-left-color: #ff3333; background: rgba(255,51,51,0.08); }
-.log-error .log-level, .log-error .log-msg { color: #fca5a5; }
-.log-system { border-left-color: #00ff66; }
-.log-system .log-level { color: #00ff66; }
-
-.log-detail { width: 100%; margin-top: 4px; }
-.log-detail summary {
- cursor: pointer;
- color: var(--accent);
- font-size: 0.72rem;
- user-select: none;
-}
-.log-detail pre {
- margin: 6px 0 0;
- padding: 8px;
- max-height: 340px;
- overflow: auto;
- background: rgba(0,0,0,0.25);
- border-left: 2px solid var(--accent);
- border-radius: 4px;
- font-size: 0.72rem;
- line-height: 1.4;
- white-space: pre-wrap;
- word-break: break-word;
- color: var(--text);
-}
+
+/* ---- Live Log lines ---- */
+.log-line {
+ display: flex;
+ flex-wrap: wrap;
+ align-items: baseline;
+ gap: 8px;
+ padding: 4px 8px;
+ border-radius: 4px;
+ font-size: 0.8rem;
+ font-family: 'Courier New', monospace;
+ border-left: 3px solid var(--text-fade);
+ animation: thinkingSlideIn 0.25s ease-out;
+ word-break: break-word;
+}
+.log-time { color: var(--text-fade); flex-shrink: 0; }
+.log-level {
+ flex-shrink: 0;
+ text-transform: uppercase;
+ font-size: 0.7rem;
+ font-weight: bold;
+ letter-spacing: 0.05em;
+}
+.log-msg { color: var(--text); }
+.log-fields { color: var(--text-fade); width: 100%; padding-left: 4px; }
+
+.log-info { border-left-color: #00bfff; }
+.log-info .log-level { color: #7dd3fc; }
+.log-debug { border-left-color: #8a2be2; }
+.log-debug .log-level { color: #c79cff; }
+.log-error { border-left-color: #ff3333; background: rgba(255,51,51,0.08); }
+.log-error .log-level, .log-error .log-msg { color: #fca5a5; }
+.log-system { border-left-color: #00ff66; }
+.log-system .log-level { color: #00ff66; }
+
+.log-detail { width: 100%; margin-top: 4px; }
+.log-detail summary {
+ cursor: pointer;
+ color: var(--accent);
+ font-size: 0.72rem;
+ user-select: none;
+}
+.log-detail pre {
+ margin: 6px 0 0;
+ padding: 8px;
+ max-height: 340px;
+ overflow: auto;
+ background: rgba(0,0,0,0.25);
+ border-left: 2px solid var(--accent);
+ border-radius: 4px;
+ font-size: 0.72rem;
+ line-height: 1.4;
+ white-space: pre-wrap;
+ word-break: break-word;
+ color: var(--text);
+}
+
+/* Rendered markdown in Lyra's replies — readable proportional type + structure. */
+.msg.assistant {
+ font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif;
+ line-height: 1.55;
+ max-width: 88%;
+}
+.msg.assistant p { margin: 0 0 10px; }
+.msg.assistant p:last-child { margin-bottom: 0; }
+.msg.assistant h1, .msg.assistant h2, .msg.assistant h3, .msg.assistant h4 {
+ margin: 14px 0 6px; line-height: 1.3; color: var(--accent);
+}
+.msg.assistant h1 { font-size: 1.18rem; }
+.msg.assistant h2 { font-size: 1.1rem; }
+.msg.assistant h3 { font-size: 1.02rem; }
+.msg.assistant h4 { font-size: 0.96rem; }
+.msg.assistant ul, .msg.assistant ol { margin: 6px 0 10px; padding-left: 22px; }
+.msg.assistant li { margin: 3px 0; }
+.msg.assistant li > ul, .msg.assistant li > ol { margin: 3px 0; }
+.msg.assistant strong { font-weight: 600; color: var(--text); }
+.msg.assistant em { font-style: italic; }
+.msg.assistant a { color: var(--accent); text-decoration: underline; }
+.msg.assistant code {
+ font-family: "IBM Plex Mono", monospace; font-size: 0.88em;
+ background: rgba(255,255,255,0.08); padding: 1px 5px; border-radius: 4px;
+}
+.msg.assistant pre {
+ background: rgba(0,0,0,0.32); border: 1px solid rgba(255,102,0,0.3);
+ border-radius: 6px; padding: 10px 12px; margin: 8px 0; overflow-x: auto;
+}
+.msg.assistant pre code { background: none; padding: 0; font-size: 0.85em; }