feat: Reply/Zitat-Feature für Chat-Nachrichten
- Reply-Button (Hover) auf jeder User/Assistant-Nachricht - Reply-Preview-Leiste über dem Input-Feld (mit Timestamp, Rolle, Text-Preview) - Beim Senden: Zitat als Kontext-Block vorangestellt (> [Zitat: HH:MM, Role]:) - User-Nachrichten parsen Zitat-Blöcke als visuelle Quote-Elemente (blaue Borderlinie) - Timestamp-basierte Referenzierung (HH:MM oder DD.MM. HH:MM) - clearReply() bei Chat-Clear, Navigation und Destroy
This commit is contained in:
1 parent
7c0e5d180a
commit
57b4b5931b
6 files changed
+224
-6
No files matched your search
+62
-2
@@ -33,10 +33,64 @@ function truncate(s, max) {
|
||||
return s && s.length > max ? s.substring(0, max) + '...' : (s || '');
|
||||
}
|
||||
|
||||
// ── Reply Button ──
|
||||
|
||||
function _addReplyButton(div) {
|
||||
const btn = document.createElement('button');
|
||||
btn.className = 'msg-reply-btn icon-btn';
|
||||
btn.innerHTML = '<i class="fa fa-reply"></i>';
|
||||
btn.title = 'Antworten';
|
||||
btn.addEventListener('click', (e) => {
|
||||
e.stopPropagation();
|
||||
// Nur den eigentlichen Nachrichten-Text holen (ohne Reply-Button-Text)
|
||||
const clone = div.cloneNode(true);
|
||||
clone.querySelectorAll('.msg-reply-btn, .attachment-indicator, .reply-quote').forEach(el => el.remove());
|
||||
const text = clone.textContent.trim();
|
||||
setReply({
|
||||
role: div.dataset.role,
|
||||
timestamp: div.dataset.timestamp || '',
|
||||
text
|
||||
});
|
||||
});
|
||||
div.appendChild(btn);
|
||||
}
|
||||
|
||||
// ── Message Bubbles ──
|
||||
|
||||
function renderUserContent(container, text) {
|
||||
const raw = (text || '').trim();
|
||||
|
||||
// Reply-Zitat erkennen und als visuellen Block rendern
|
||||
// Format: > [Zitat: HH:MM, Role]:\n> text...\n\nrest
|
||||
const quoteMatch = raw.match(/^> \[Zitat: ([^\]]+)\]:\n((?:> .*\n?)*)\n*([\s\S]*)$/);
|
||||
if (quoteMatch) {
|
||||
const quoteMeta = quoteMatch[1]; // z.B. "14:32, Assistant"
|
||||
const quoteBody = quoteMatch[2].replace(/^> /gm, '').trim();
|
||||
const restText = quoteMatch[3].trim();
|
||||
|
||||
const quoteEl = document.createElement('div');
|
||||
quoteEl.className = 'reply-quote';
|
||||
const metaEl = document.createElement('div');
|
||||
metaEl.className = 'reply-quote-meta';
|
||||
metaEl.innerHTML = '<i class="fa fa-reply"></i> ' + esc(quoteMeta);
|
||||
quoteEl.appendChild(metaEl);
|
||||
const bodyEl = document.createElement('div');
|
||||
bodyEl.className = 'reply-quote-text';
|
||||
bodyEl.textContent = truncate(quoteBody, 200);
|
||||
quoteEl.appendChild(bodyEl);
|
||||
container.appendChild(quoteEl);
|
||||
|
||||
// Rest als normalen Text weiterverarbeiten
|
||||
if (restText) {
|
||||
_renderUserTextContent(container, restText);
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
_renderUserTextContent(container, raw);
|
||||
}
|
||||
|
||||
function _renderUserTextContent(container, raw) {
|
||||
// [Datei: path] oder [Datei: path:1-10] erkennen
|
||||
const pattern = /\[Datei: ([^\]]+)\]/g;
|
||||
let last = 0;
|
||||
@@ -75,19 +129,25 @@ function renderUserContent(container, text) {
|
||||
container.appendChild(frag);
|
||||
}
|
||||
|
||||
function addUserBubble(text, streamed = false) {
|
||||
function addUserBubble(text, streamed = false, timestamp = '') {
|
||||
const div = document.createElement('div');
|
||||
div.className = 'msg user';
|
||||
div.dataset.role = 'user';
|
||||
div.dataset.timestamp = timestamp || new Date().toISOString();
|
||||
if (streamed) div.classList.add('streamed');
|
||||
renderUserContent(div, text);
|
||||
_addReplyButton(div);
|
||||
chatEl().appendChild(div);
|
||||
scrollDown();
|
||||
}
|
||||
|
||||
function addAssistantBubble(text) {
|
||||
function addAssistantBubble(text, timestamp = '') {
|
||||
const div = document.createElement('div');
|
||||
div.className = 'msg assistant markdown';
|
||||
div.dataset.role = 'assistant';
|
||||
div.dataset.timestamp = timestamp || new Date().toISOString();
|
||||
div.innerHTML = renderMarkdown(text);
|
||||
_addReplyButton(div);
|
||||
chatEl().appendChild(div);
|
||||
scrollDown();
|
||||
return div;
|
||||
|
||||
Reference in new issue
Block a user