feat: форматирование — переносы строк, жирный, списки, цветные блоки
This commit is contained in:
+31
-4
@@ -63,13 +63,40 @@
|
|||||||
document.querySelectorAll('.hitem').forEach(el => {
|
document.querySelectorAll('.hitem').forEach(el => {
|
||||||
el.onclick = () => {
|
el.onclick = () => {
|
||||||
const h = history[+el.dataset.idx];
|
const h = history[+el.dataset.idx];
|
||||||
chat.innerHTML += '<div class="user">📋 ' + esc(h.q) + '</div>';
|
chat.innerHTML += '<div class="user">📋 ' + fmt(h.q) + '</div>';
|
||||||
chat.innerHTML += '<div class="bot">🤖 ' + esc(h.a) + '</div>';
|
chat.innerHTML += '<div class="bot">🤖 ' + fmt(h.a) + '</div>';
|
||||||
chat.scrollTop = chat.scrollHeight;
|
chat.scrollTop = chat.scrollHeight;
|
||||||
};
|
};
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
function esc(s) { return String(s).replace(/</g,'<').replace(/>/g,'>'); }
|
function esc(s) { return String(s).replace(/</g,'<').replace(/>/g,'>'); }
|
||||||
|
function fmt(s) {
|
||||||
|
s = esc(s);
|
||||||
|
// Заменяем **жирный**
|
||||||
|
s = s.replace(/\*\*(.+?)\*\*/g, '<b>$1</b>');
|
||||||
|
// Заменяем `код`
|
||||||
|
s = s.replace(/`(.+?)`/g, '<code>$1</code>');
|
||||||
|
// Заменяем *курсив*
|
||||||
|
s = s.replace(/\*(.+?)\*/g, '<i>$1</i>');
|
||||||
|
// Горизонтальная линия ---
|
||||||
|
s = s.replace(/^---+/gm, '<hr>');
|
||||||
|
// Заголовки ###
|
||||||
|
s = s.replace(/^### (.+)$/gm, '<h3>$1</h3>');
|
||||||
|
s = s.replace(/^## (.+)$/gm, '<h2>$1</h2>');
|
||||||
|
s = s.replace(/^# (.+)$/gm, '<h1>$1</h1>');
|
||||||
|
// Маркированный список
|
||||||
|
s = s.replace(/^[•\-*] (.+)$/gm, '<li>$1</li>');
|
||||||
|
s = s.replace(/(<li>[\s\S]*?<\/li>)/g, '<ul>$1</ul>');
|
||||||
|
// Нумерованный список
|
||||||
|
s = s.replace(/^\d+\.\s+(.+)$/gm, '<li>$1</li>');
|
||||||
|
// Абзацы (двойной перенос строки)
|
||||||
|
s = s.replace(/\n\n+/g, '</p><p>');
|
||||||
|
// Одинарный перенос → <br>
|
||||||
|
s = s.replace(/\n/g, '<br>');
|
||||||
|
// Убираем пустые <ul>
|
||||||
|
s = s.replace(/<ul>\s*<\/ul>/g, '');
|
||||||
|
return '<p>' + s + '</p>';
|
||||||
|
}
|
||||||
|
|
||||||
renderHistory();
|
renderHistory();
|
||||||
|
|
||||||
@@ -152,7 +179,7 @@
|
|||||||
clearTimeout(to);
|
clearTimeout(to);
|
||||||
const tData = await tRes.json();
|
const tData = await tRes.json();
|
||||||
const text = tData.text || '(не распознано)';
|
const text = tData.text || '(не распознано)';
|
||||||
chat.innerHTML += '<div class="user">🗣 ' + esc(text) + '</div>';
|
chat.innerHTML += '<div class="user">🗣 ' + fmt(text) + '</div>';
|
||||||
chat.scrollTop = chat.scrollHeight;
|
chat.scrollTop = chat.scrollHeight;
|
||||||
|
|
||||||
startThinkTimer();
|
startThinkTimer();
|
||||||
@@ -166,7 +193,7 @@
|
|||||||
const cData = await cRes.json();
|
const cData = await cRes.json();
|
||||||
if (cData.sid) sessionSid = cData.sid;
|
if (cData.sid) sessionSid = cData.sid;
|
||||||
const resp = cData.response || cData.error || 'Ошибка';
|
const resp = cData.response || cData.error || 'Ошибка';
|
||||||
chat.innerHTML += '<div class="bot">🤖 ' + esc(resp) + '</div>';
|
chat.innerHTML += '<div class="bot">🤖 ' + fmt(resp) + '</div>';
|
||||||
chat.scrollTop = chat.scrollHeight;
|
chat.scrollTop = chat.scrollHeight;
|
||||||
setStatus('✅ Готово — ' + ((Date.now() - thinkStart) / 1000).toFixed(1) + 'с');
|
setStatus('✅ Готово — ' + ((Date.now() - thinkStart) / 1000).toFixed(1) + 'с');
|
||||||
|
|
||||||
|
|||||||
+11
-3
@@ -20,9 +20,17 @@ h1 { color: #7c6ff7; margin: 0 0 8px; font-size: 20px; }
|
|||||||
|
|
||||||
#status { text-align: center; color: #888; font-size: 14px; min-height: 20px; margin: 6px 0; }
|
#status { text-align: center; color: #888; font-size: 14px; min-height: 20px; margin: 6px 0; }
|
||||||
|
|
||||||
#chat { border: 1px solid #2a2a3e; border-radius: 8px; padding: 12px; min-height: 200px; max-height: 340px; overflow-y: auto; background: #13132a; }
|
#chat { border: 1px solid #2a2a3e; border-radius: 8px; padding: 14px; min-height: 200px; max-height: 340px; overflow-y: auto; background: #13132a; line-height: 1.6; }
|
||||||
.user { color: #00cec9; margin: 6px 0; word-break: break-word; font-size: 14px; }
|
.user { background: #0d2d2d; border-left: 3px solid #00cec9; padding: 8px 12px; margin: 8px 0; border-radius: 0 6px 6px 0; color: #00cec9; font-size: 14px; }
|
||||||
.bot { color: #a29bfe; margin: 6px 0 12px; word-break: break-word; font-size: 14px; }
|
.user p { margin: 0; }
|
||||||
|
.bot { background: #1a1a3e; border-left: 3px solid #a29bfe; padding: 10px 14px; margin: 8px 0 16px; border-radius: 0 6px 6px 0; color: #d0d0f0; font-size: 14px; }
|
||||||
|
.bot p { margin: 4px 0; }
|
||||||
|
.bot h1, .bot h2, .bot h3 { color: #7c6ff7; margin: 8px 0 4px; font-size: 15px; }
|
||||||
|
.bot b { color: #fff; }
|
||||||
|
.bot code { background: #2a2a4e; padding: 1px 5px; border-radius: 3px; font-size: 13px; color: #f8c291; }
|
||||||
|
.bot ul, .bot ol { margin: 4px 0; padding-left: 20px; }
|
||||||
|
.bot li { margin: 2px 0; }
|
||||||
|
.bot hr { border: none; border-top: 1px solid #2a2a3e; margin: 10px 0; }
|
||||||
|
|
||||||
#sidebar { width: 280px; background: #111122; border-left: 1px solid #2a2a3e; padding: 14px; overflow-y: auto; }
|
#sidebar { width: 280px; background: #111122; border-left: 1px solid #2a2a3e; padding: 14px; overflow-y: auto; }
|
||||||
#history-title { color: #7c6ff7; font-weight: bold; margin-bottom: 10px; font-size: 15px; }
|
#history-title { color: #7c6ff7; font-weight: bold; margin-bottom: 10px; font-size: 15px; }
|
||||||
|
|||||||
Reference in New Issue
Block a user