diff --git a/css/agent-chat.css b/css/agent-chat.css index 17ca372..97a10f0 100644 --- a/css/agent-chat.css +++ b/css/agent-chat.css @@ -143,3 +143,58 @@ font-size: 0.72rem; } } + +/* ========================================= + PHASE 2 — Markdown rendering in chat + ========================================= */ +.chat-msg-body strong { color: var(--accent); font-weight: 700; } +.chat-msg-body em { color: var(--text-dim); font-style: italic; } +.chat-msg-body code { + background: rgba(20, 241, 149, 0.1); + padding: 0 0.3rem; + border-radius: 2px; + color: var(--status-green, #14f195); + font-family: 'JetBrains Mono', 'Courier New', monospace; + font-size: 0.85em; + border: 1px solid rgba(20, 241, 149, 0.2); +} +.chat-msg-body pre { + background: rgba(0, 0, 0, 0.45); + padding: 0.6rem 0.75rem; + border-radius: 4px; + overflow-x: auto; + border-left: 2px solid var(--accent); + margin: 0.5rem 0; + max-width: 100%; +} +.chat-msg-body pre code { + background: transparent; + border: none; + padding: 0; + color: var(--text, #e8e8e8); + font-size: 0.8em; + white-space: pre; +} +.chat-msg-body a { + color: var(--accent); + text-decoration: underline; + text-underline-offset: 2px; + transition: color 0.15s ease; +} +.chat-msg-body a:hover { color: var(--status-green, #14f195); } +.chat-msg-body h2, +.chat-msg-body h3, +.chat-msg-body h4 { + color: var(--accent); + margin: 0.5rem 0 0.25rem; + font-weight: 600; + letter-spacing: 0.5px; +} +.chat-msg-body h2 { font-size: 1.1em; } +.chat-msg-body h3 { font-size: 1.0em; } +.chat-msg-body h4 { font-size: 0.95em; } +.chat-msg-body ul, .chat-msg-body ol { + margin: 0.3rem 0 0.3rem 1.25rem; + padding: 0; +} +.chat-msg-body li { margin: 0.15rem 0; list-style: disc; } diff --git a/js/chat.js b/js/chat.js index a7b0a09..5924ba8 100644 --- a/js/chat.js +++ b/js/chat.js @@ -28,6 +28,36 @@ }); } + // ─── Minimal safe Markdown → HTML (assistant messages only) ── + function md(text) { + if (!text) return ''; + let html = String(text).replace(/&/g, '&').replace(//g, '>'); + // Fenced code blocks ```...``` + html = html.replace(/```([\s\S]*?)```/g, function (_m, code) { + return '
' + code + '';
+ });
+ // Inline code
+ html = html.replace(/`([^`\n]+)`/g, '$1');
+ // Bold **...**
+ html = html.replace(/\*\*([^*\n]+)\*\*/g, '$1');
+ // Italic *...* (avoid double-asterisk leftovers)
+ html = html.replace(/(^|[^*])\*([^*\n]+)\*(?!\*)/g, '$1$2');
+ // Links [text](url)
+ html = html.replace(/\[([^\]]+)\]\(([^)\s]+)\)/g, '$1');
+ // Headings
+ html = html.replace(/^### (.+)$/gm, ')
+ html = html.replace(/([\s\S]*?<\/pre>)|\n/g, function (m, pre) {
+ return pre ? pre : '
';
+ });
+ return html;
+ }
+
// ─── Render a message bubble ──────────────────────
function addMessage(role, text) {
const welcome = chatMessages.querySelector('.chat-welcome');
@@ -42,7 +72,7 @@
const body = document.createElement('div');
body.className = 'chat-msg-body';
- body.textContent = text;
+ if (role === 'assistant') { body.innerHTML = md(text); } else { body.textContent = text; }
msg.appendChild(label);
msg.appendChild(body);
@@ -101,22 +131,24 @@
// ─── Typewriter effect ────────────────────────────
function typewriterEffect(element, text, speed) {
speed = speed || 10;
- // If reply is too long, bypass animation
+ element.innerHTML = '';
+ // Bypass animation for long replies — render full markdown immediately
if (!text || text.length > 600) {
- element.textContent = text || '';
+ element.innerHTML = md(text || '');
chatMessages.scrollTop = chatMessages.scrollHeight;
return Promise.resolve();
}
let i = 0;
- element.textContent = '';
return new Promise(function (resolve) {
function tick() {
if (i < text.length) {
- element.textContent += text.charAt(i);
i++;
+ element.innerHTML = md(text.slice(0, i));
chatMessages.scrollTop = chatMessages.scrollHeight;
setTimeout(tick, speed);
} else {
+ // Dispatch event so voice-mode / others can hook
+ try { document.dispatchEvent(new CustomEvent('jae-agent-reply', { detail: { text: text } })); } catch (e) {}
resolve();
}
}
@@ -138,7 +170,7 @@
label.textContent = role === 'user' ? 'YOU' : 'JAE-AI';
const body = document.createElement('div');
body.className = 'chat-msg-body';
- body.textContent = m.content || '';
+ if (role === 'assistant') { body.innerHTML = md(m.content || ''); } else { body.textContent = m.content || ''; }
const el = document.createElement('div');
el.className = `chat-msg chat-msg-${role}`;
el.appendChild(label);
@@ -296,7 +328,7 @@
const body = addMessage('assistant', '');
const msgEl = body && body.parentElement;
if (msgEl) msgEl.classList.add('chat-msg-cli');
- body.textContent = res.output;
+ body.innerHTML = md(res.output);
chatMessages.scrollTop = chatMessages.scrollHeight;
}
}