Refactor: JavaScript-Code-Duplikate eliminiert (-227 Zeilen)

Neue Helper-Funktionen extrahiert:
- _runStream(): SSE-Stream-Loop aus sendMessage() + handleChoiceClick() (chat.js)
- _createMessageBubble() + _addBubbleActions(): Bubble-Erstellung aus 6+ Stellen (bubbles.js)
- createAttachmentIndicator(): Attachment-Anzeige aus bubbles/history/archive (bubbles.js)
- _renderMessagesIntoFragment(): Shared zwischen loadOlderMessages + loadLatestArchive (history.js)
- _isReviewMessage(): Review/Finalizer/Preparer-Check aus 8+ Stellen (history.js)
- _renderChoiceBubbleInFragment(): Choice-Bubble-Rendering extrahiert (history.js)
- _buildDiffLines(): Diff-Zeilen aus buildEditDiff + buildWritePreview (tool-ui.js)
- _buildAvatarTransformStyle(): Avatar-Transform aus buildSessionCard + buildAgentCard (cards.js)
This commit is contained in:
Meleki committed 2026-03-07 21:37:41 +01:00
1 parent b4c844b7c2
commit b56b83ad1e
7 files changed
+272 -499

No files matched your search

+54 -47
View File
@@ -100,41 +100,69 @@ function _renderUserTextContent(container, raw, inlineImages) {
parseFluentEmojis(wrap);
}
// ── Message Bubble Helpers ──
/**
* Creates a message bubble div with common setup (className, dataset, deletedOn, action buttons).
* @param {object} opts - { role, extraClass, timestamp, messageId, isMarked, deletedOn, streamed, archived }
*/
function _createMessageBubble(opts) {
const div = document.createElement('div');
const classes = ['msg', opts.extraClass || opts.role, 'markdown'];
if (opts.isMarked) classes.push('marked');
if (opts.archived) classes.push('archived');
if (opts.streamed) classes.push('streamed');
div.className = classes.join(' ');
if (opts.deletedOn) div.classList.add(isDeletedVisible() ? 'msg-deleted' : 'msg-deleted-hidden');
div.dataset.role = opts.role || '';
div.dataset.timestamp = opts.timestamp || new Date().toISOString();
if (opts.messageId) div.dataset.messageId = opts.messageId;
return div;
}
/**
* Adds standard action buttons (timestamp, reply, delete, mark, share) to a bubble.
*/
function _addBubbleActions(div, opts) {
_addTimestamp(div, opts.timestamp);
_addReplyButton(div);
_addDeleteButton(div, opts.messageId, opts.deletedOn);
_addMarkButton(div, opts.messageId, opts.isMarked);
_addShareButton(div, opts.messageId);
}
/**
* Creates an attachment indicator element for a message.
*/
function createAttachmentIndicator(messageId, count, namesJson) {
let names = [];
try { if (namesJson) names = JSON.parse(namesJson); } catch {}
const nameList = names.filter(n => n).join(', ');
const ind = document.createElement('div');
ind.className = 'attachment-indicator';
ind.innerHTML = `<i class="fa fa-paperclip"></i> ${count} Datei${count > 1 ? 'en' : ''}` + (nameList ? `: ${esc(nameList)}` : '');
ind.style.cursor = 'pointer';
ind.title = 'Klicken zum Anzeigen';
ind.addEventListener('click', () => loadAndShowAttachments(messageId, ind));
return ind;
}
// ── Message Bubbles ──
function addUserBubble(text, streamed = false, timestamp = '', messageId = null, isMarked = false, deletedOn = null, inlineImages = null) {
const div = document.createElement('div');
div.className = 'msg user markdown' + (isMarked ? ' marked' : '');
if (deletedOn) div.classList.add(isDeletedVisible() ? 'msg-deleted' : 'msg-deleted-hidden');
div.dataset.role = 'user';
div.dataset.timestamp = timestamp || new Date().toISOString();
if (messageId) div.dataset.messageId = messageId;
if (streamed) div.classList.add('streamed');
const div = _createMessageBubble({ role: 'user', timestamp, messageId, isMarked, deletedOn, streamed });
renderUserContent(div, text, inlineImages);
_addTimestamp(div, timestamp);
_addReplyButton(div);
_addDeleteButton(div, messageId, deletedOn);
_addMarkButton(div, messageId, isMarked);
_addShareButton(div, messageId);
_addBubbleActions(div, { timestamp, messageId, deletedOn, isMarked });
chatEl().appendChild(div);
scrollDown();
}
function addAssistantBubble(text, timestamp = '', messageId = null, isMarked = false, deletedOn = null) {
const div = document.createElement('div');
div.className = 'msg assistant markdown' + (isMarked ? ' marked' : '');
if (deletedOn) div.classList.add(isDeletedVisible() ? 'msg-deleted' : 'msg-deleted-hidden');
div.dataset.role = 'assistant';
div.dataset.timestamp = timestamp || new Date().toISOString();
if (messageId) div.dataset.messageId = messageId;
const div = _createMessageBubble({ role: 'assistant', timestamp, messageId, isMarked, deletedOn });
div.innerHTML = renderMarkdown(text);
parseFluentEmojis(div);
wrapKreaGalleries(div);
_addTimestamp(div, timestamp);
_addReplyButton(div);
_addDeleteButton(div, messageId, deletedOn);
_addMarkButton(div, messageId, isMarked);
_addShareButton(div, messageId);
_addBubbleActions(div, { timestamp, messageId, deletedOn, isMarked });
chatEl().appendChild(div);
scrollDown();
return div;
@@ -235,19 +263,10 @@ function addChoiceBubbleStatic(toolCallId, question, choices, selectedChoice, ti
// ── Review Bubbles (Static / History) ──
function addReviewBubbleStatic(text, timestamp, messageId, isMarked, deletedOn) {
const div = document.createElement('div');
div.className = 'msg review-bubble markdown' + (isMarked ? ' marked' : '');
if (deletedOn) div.classList.add(isDeletedVisible() ? 'msg-deleted' : 'msg-deleted-hidden');
div.dataset.role = 'assistant';
div.dataset.timestamp = timestamp || '';
if (messageId) div.dataset.messageId = messageId;
const div = _createMessageBubble({ role: 'assistant', extraClass: 'review-bubble', timestamp, messageId, isMarked, deletedOn });
div.innerHTML = renderMarkdown(text);
parseFluentEmojis(div);
_addTimestamp(div, timestamp);
_addReplyButton(div);
_addDeleteButton(div, messageId, deletedOn);
_addMarkButton(div, messageId, isMarked);
_addShareButton(div, messageId);
_addBubbleActions(div, { timestamp, messageId, deletedOn, isMarked });
chatEl().appendChild(div);
scrollDown();
return div;
@@ -259,19 +278,7 @@ function addAttachmentIndicator(messageId, count, namesJson) {
const chat = chatEl();
const lastMsg = chat.lastElementChild;
if (!lastMsg) return;
let names = [];
try { if (namesJson) names = JSON.parse(namesJson); } catch {}
const indicator = document.createElement('div');
indicator.className = 'attachment-indicator';
const nameList = names.filter(n => n).join(', ');
indicator.innerHTML = `<i class="fa fa-paperclip"></i> ${count} Datei${count > 1 ? 'en' : ''}` + (nameList ? `: ${esc(nameList)}` : '');
indicator.style.cursor = 'pointer';
indicator.title = 'Klicken zum Anzeigen';
indicator.addEventListener('click', () => loadAndShowAttachments(messageId, indicator));
lastMsg.appendChild(indicator);
lastMsg.appendChild(createAttachmentIndicator(messageId, count, namesJson));
}
async function loadAndShowAttachments(messageId, indicator) {