fix(chat): render markdown in JAE-AI responses (bold/italic/code/links/headings)
This commit is contained in:
parent
bc3fdf28b8
commit
b94bbe2f3e
2 changed files with 94 additions and 7 deletions
|
|
@ -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; }
|
||||
|
|
|
|||
46
js/chat.js
46
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, '<').replace(/>/g, '>');
|
||||
// Fenced code blocks ```...```
|
||||
html = html.replace(/```([\s\S]*?)```/g, function (_m, code) {
|
||||
return '<pre><code>' + code + '</code></pre>';
|
||||
});
|
||||
// Inline code
|
||||
html = html.replace(/`([^`\n]+)`/g, '<code>$1</code>');
|
||||
// Bold **...**
|
||||
html = html.replace(/\*\*([^*\n]+)\*\*/g, '<strong>$1</strong>');
|
||||
// Italic *...* (avoid double-asterisk leftovers)
|
||||
html = html.replace(/(^|[^*])\*([^*\n]+)\*(?!\*)/g, '$1<em>$2</em>');
|
||||
// Links [text](url)
|
||||
html = html.replace(/\[([^\]]+)\]\(([^)\s]+)\)/g, '<a href="$2" target="_blank" rel="noopener">$1</a>');
|
||||
// Headings
|
||||
html = html.replace(/^### (.+)$/gm, '<h4>$1</h4>');
|
||||
html = html.replace(/^## (.+)$/gm, '<h3>$1</h3>');
|
||||
html = html.replace(/^# (.+)$/gm, '<h2>$1</h2>');
|
||||
// Bullet list lines: "- item" / "* item" → <li>
|
||||
html = html.replace(/^(?:-|\*) (.+)$/gm, '<li>$1</li>');
|
||||
html = html.replace(/(<li>[\s\S]*?<\/li>)(?!\s*<li>)/g, '<ul>$1</ul>');
|
||||
// Line breaks (avoid inside <pre>)
|
||||
html = html.replace(/(<pre>[\s\S]*?<\/pre>)|\n/g, function (m, pre) {
|
||||
return pre ? pre : '<br>';
|
||||
});
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue