Render chat bot answers as markdown in the UI.

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
keboss-m 2026-06-01 18:29:53 +03:00
parent e9f5b80e23
commit 1f2ef8f012
3 changed files with 72 additions and 2 deletions

View File

@ -445,6 +445,18 @@ class TranscriptionApp {
return div.innerHTML; return div.innerHTML;
} }
renderMarkdown(text) {
if (!text) return '';
if (typeof marked === 'undefined') {
return this.escapeHtml(text);
}
const html = marked.parse(text, { breaks: true, gfm: true });
if (typeof DOMPurify !== 'undefined') {
return DOMPurify.sanitize(html);
}
return html;
}
formatBytes(bytes) { formatBytes(bytes) {
if (bytes === 0) return '0 B'; if (bytes === 0) return '0 B';
const k = 1024; const k = 1024;
@ -526,8 +538,7 @@ class TranscriptionApp {
this.setChatThinking(false); this.setChatThinking(false);
const answer = data.answer || 'Нет ответа'; const answer = data.answer || 'Нет ответа';
const project = data.project ? `Проект: ${data.project}` : 'Все проекты'; const project = data.project ? `Проект: ${data.project}` : 'Все проекты';
const sources = ''; // можно добавить data.context, но он может быть слишком длинным const html = `<div class="bubble-body md-content">${this.renderMarkdown(answer)}</div>`;
const html = `<div>${this.escapeHtml(answer)}</div>`;
this.addChatBubble('bot', html, { isHtml: true, meta: project }); this.addChatBubble('bot', html, { isHtml: true, meta: project });
// Сохраняем в историю // Сохраняем в историю

View File

@ -8,6 +8,7 @@
<link rel="stylesheet" href="/static/styles.css"> <link rel="stylesheet" href="/static/styles.css">
<!-- Markdown renderer --> <!-- Markdown renderer -->
<script src="https://cdn.jsdelivr.net/npm/marked/marked.min.js"></script> <script src="https://cdn.jsdelivr.net/npm/marked/marked.min.js"></script>
<script src="https://cdn.jsdelivr.net/npm/dompurify@3.2.4/dist/purify.min.js"></script>
</head> </head>
<body> <body>
<div class="container"> <div class="container">

View File

@ -622,6 +622,64 @@ header h1 {
color: var(--text-secondary); color: var(--text-secondary);
} }
.chat-bubble.bot .md-content p {
margin: 0 0 0.6em;
}
.chat-bubble.bot .md-content p:last-child {
margin-bottom: 0;
}
.chat-bubble.bot .md-content ul,
.chat-bubble.bot .md-content ol {
margin: 0.4em 0;
padding-left: 1.4em;
}
.chat-bubble.bot .md-content li {
margin: 0.2em 0;
}
.chat-bubble.bot .md-content strong {
font-weight: 600;
}
.chat-bubble.bot .md-content h1,
.chat-bubble.bot .md-content h2,
.chat-bubble.bot .md-content h3,
.chat-bubble.bot .md-content h4 {
font-size: 1em;
font-weight: 600;
margin: 0.75em 0 0.35em;
}
.chat-bubble.bot .md-content h1:first-child,
.chat-bubble.bot .md-content h2:first-child,
.chat-bubble.bot .md-content h3:first-child,
.chat-bubble.bot .md-content h4:first-child {
margin-top: 0;
}
.chat-bubble.bot .md-content code {
background: var(--bg-hover);
padding: 0.1em 0.35em;
border-radius: 4px;
font-size: 0.9em;
}
.chat-bubble.bot .md-content pre {
background: var(--bg-hover);
padding: 10px 12px;
border-radius: var(--radius);
overflow-x: auto;
margin: 0.5em 0;
}
.chat-bubble.bot .md-content pre code {
background: none;
padding: 0;
}
.chat-thinking { .chat-thinking {
align-self: flex-start; align-self: flex-start;
color: var(--text-secondary); color: var(--text-secondary);