From 6369f7f09c566b3a4b036a5b0cc862cdc9d8a687 Mon Sep 17 00:00:00 2001 From: Meleki Date: Fri, 6 Mar 2026 17:31:30 +0100 Subject: [PATCH] Review-Nachrichten: konsistentes Styling in History, Chat und Shared-Links MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - renderMarkdown: [Review: Agent], [Review-Feedback ...], [APPROVED] Tags parsen → styled HTML-Header bzw. ausgeblendet (zentral für alle Views) - history.js: Review-Messages als .review-bubble rendern statt .assistant/.user Review-Messages vom Merging ausgenommen - shared-page.js: Review-Messages erkennen → .review-bubble Klasse Merge-Logik schützt Review-Messages vor Zusammenführung - addReviewBubbleStatic(): Neue Funktion für History-Rendering mit allen Buttons (Reply, Delete, Mark, Share) - chat.css: .review-inline-header, .review-inline-feedback Styles --- wwwroot/css/chat.css | 13 ++++++++++ wwwroot/js/history.js | 42 ++++++++++++++++++++++++++------- wwwroot/js/pages/shared-page.js | 19 +++++++++++---- wwwroot/js/ui.js | 34 +++++++++++++++++++++++++- 4 files changed, 94 insertions(+), 14 deletions(-) diff --git a/wwwroot/css/chat.css b/wwwroot/css/chat.css index 5a781ab..39bf6af 100644 --- a/wwwroot/css/chat.css +++ b/wwwroot/css/chat.css @@ -987,6 +987,19 @@ gap: var(--badge-spacing); } +.review-inline-header { + color: var(--secondary); + margin-right: .3em; +} + +.review-inline-feedback { + color: var(--warning); + margin-bottom: .4em; + display: flex; + align-items: center; + gap: var(--badge-spacing); +} + .review-header { display: flex; align-items: center; diff --git a/wwwroot/js/history.js b/wwwroot/js/history.js index a6e5f92..3ff1690 100644 --- a/wwwroot/js/history.js +++ b/wwwroot/js/history.js @@ -157,8 +157,10 @@ function _mergeConsecutiveAssistantTexts(msgs) { for (const msg of msgs) { if (msg.type === 'text' && msg.role === 'assistant') { const prev = result[result.length - 1]; + const isReview = msg.content && msg.content.startsWith('[Review:'); + const prevIsReview = prev && prev.content && prev.content.startsWith('[Review:'); if (prev && prev.type === 'text' && prev.role === 'assistant' - && prev.iteration === msg.iteration) { + && prev.iteration === msg.iteration && !isReview && !prevIsReview) { // Gleiche Iteration: nahtlos zusammenfügen (Web-Search-Fragmente etc.) prev.content = (prev.content || '') + ' ' + (msg.content || ''); // IDs/Timestamps: letztes behalten (für Mark-Button etc.) @@ -178,8 +180,9 @@ function _renderMessagesIntoFragment(fragment, msgs, imageMessageIds) { for (const msg of msgs) { const div = document.createElement('div'); switch (msg.type) { - case 'text': - if (msg.role === 'user') { + case 'text': { + const isReviewMsg = msg.content && (msg.content.startsWith('[Review:') || msg.content.startsWith('[Review-Feedback')); + if (msg.role === 'user' && !isReviewMsg) { div.className = 'msg user markdown' + (msg.isMarked ? ' marked' : ''); if (msg.deletedOn) div.classList.add(isDeletedVisible() ? 'msg-deleted' : 'msg-deleted-hidden'); div.dataset.role = 'user'; @@ -208,6 +211,20 @@ function _renderMessagesIntoFragment(fragment, msgs, imageMessageIds) { ind.addEventListener('click', () => loadAndShowAttachments(msg.id, ind)); div.appendChild(ind); } + } else if (isReviewMsg) { + div.className = 'msg review-bubble markdown' + (msg.isMarked ? ' marked' : ''); + if (msg.deletedOn) div.classList.add(isDeletedVisible() ? 'msg-deleted' : 'msg-deleted-hidden'); + div.dataset.role = msg.role; + div.dataset.timestamp = msg.createdAt || ''; + if (msg.id) div.dataset.messageId = msg.id; + div.innerHTML = renderMarkdown(msg.content); + parseFluentEmojis(div); + _addTimestamp(div, msg.createdAt); + _addReplyButton(div); + _addDeleteButton(div, msg.id, msg.deletedOn); + _addMarkButton(div, msg.id, msg.isMarked); + _addShareButton(div, msg.id); + fragment.appendChild(div); } else if (msg.role === 'assistant') { div.className = 'msg assistant markdown' + (msg.isMarked ? ' marked' : ''); if (msg.deletedOn) div.classList.add(isDeletedVisible() ? 'msg-deleted' : 'msg-deleted-hidden'); @@ -224,6 +241,7 @@ function _renderMessagesIntoFragment(fragment, msgs, imageMessageIds) { fragment.appendChild(div); } break; + } case 'tool_use': if (!isToolVisible(msg.toolName)) continue; div.className = 'tool-box'; @@ -262,13 +280,21 @@ function renderMessages(msgs, addToConversation) { switch (msg.type) { case 'text': if (msg.role === 'user') { - addUserBubble(msg.content, false, msg.createdAt, msg.id, msg.isMarked, msg.deletedOn); - if (msg.attachmentCount > 0) { - _autoLoadImageAttachments(msg.id); - addAttachmentIndicator(msg.id, msg.attachmentCount, msg.attachmentNames); + if (msg.content && msg.content.startsWith('[Review-Feedback')) { + addReviewBubbleStatic(msg.content, msg.createdAt, msg.id, msg.isMarked, msg.deletedOn); + } else { + addUserBubble(msg.content, false, msg.createdAt, msg.id, msg.isMarked, msg.deletedOn); + if (msg.attachmentCount > 0) { + _autoLoadImageAttachments(msg.id); + addAttachmentIndicator(msg.id, msg.attachmentCount, msg.attachmentNames); + } } } else if (msg.role === 'assistant') { - addAssistantBubble(msg.content, msg.createdAt, msg.id, msg.isMarked, msg.deletedOn); + if (msg.content && msg.content.startsWith('[Review:')) { + addReviewBubbleStatic(msg.content, msg.createdAt, msg.id, msg.isMarked, msg.deletedOn); + } else { + addAssistantBubble(msg.content, msg.createdAt, msg.id, msg.isMarked, msg.deletedOn); + } } break; diff --git a/wwwroot/js/pages/shared-page.js b/wwwroot/js/pages/shared-page.js index fdb40bd..3704dcb 100644 --- a/wwwroot/js/pages/shared-page.js +++ b/wwwroot/js/pages/shared-page.js @@ -25,12 +25,18 @@ registerPage('shared', { messages = _mergeConsecutiveTexts(messages); for (const msg of messages) { + const c = msg.content || ''; + const isReview = c.startsWith('[Review:') || c.startsWith('[Review-Feedback'); const div = document.createElement('div'); - div.className = 'msg ' + (msg.role === 'user' ? 'user' : 'assistant') + ' markdown'; - if (msg.role === 'user') { + if (isReview) { + div.className = 'msg review-bubble markdown'; + } else { + div.className = 'msg ' + (msg.role === 'user' ? 'user' : 'assistant') + ' markdown'; + } + if (!isReview && msg.role === 'user') { renderUserContent(div, msg.content); } else { - div.innerHTML = renderMarkdown(msg.content); + div.innerHTML = renderMarkdown(c); parseFluentEmojis(div); wrapKreaGalleries(div); } @@ -56,11 +62,14 @@ registerPage('shared', { function _mergeConsecutiveTexts(msgs) { const result = []; for (const msg of msgs) { - if (msg.role === 'assistant') { + const c = msg.content || ''; + const isReview = c.startsWith('[Review:') || c.startsWith('[Review-Feedback'); + if (msg.role === 'assistant' && !isReview) { const prev = result[result.length - 1]; if (prev && prev.role === 'assistant' + && !(prev.content || '').startsWith('[Review:') && prev.iteration === msg.iteration) { - prev.content = (prev.content || '') + ' ' + (msg.content || ''); + prev.content = (prev.content || '') + ' ' + c; if (msg.createdAt) prev.createdAt = msg.createdAt; continue; } diff --git a/wwwroot/js/ui.js b/wwwroot/js/ui.js index 95a7849..41f0fc2 100644 --- a/wwwroot/js/ui.js +++ b/wwwroot/js/ui.js @@ -167,7 +167,20 @@ function renderMarkdown(text) { return '%%CODE' + (codeBlocks.length - 1) + '%%'; }); - // 4) [Krea: dateiname.png] → Inline-Thumbnail mit Klick für Vollbild + // 4a) [Review: AgentName] prefix → Review-Header + html = html.replace(/\[Review:\s*([^\]]+)\]\s*/g, function(match, agentName) { + return ' ' + esc(agentName.trim()) + ' '; + }); + + // 4b) [Review-Feedback von AgentName (Runde X/Y)]: → Review-Feedback-Header + html = html.replace(/\[Review-Feedback von\s+([^\(]+)\(Runde\s+(\d+)\/(\d+)\)\]:\s*/g, function(match, agentName, round, maxRounds) { + return '
Feedback von ' + esc(agentName.trim()) + ' Runde ' + esc(round) + '/' + esc(maxRounds) + '
'; + }); + + // 4c) [APPROVED] ausblenden + html = html.replace(/\[APPROVED\]/g, ''); + + // 4d) [Krea: dateiname.png] → Inline-Thumbnail mit Klick für Vollbild html = html.replace(/\[Krea:\s*([^\]]+)\]/g, function(match, filename) { var fn = filename.trim(); var safeFn = esc(fn); @@ -640,6 +653,25 @@ function addWarningBubble(text, streamed = false) { // ── Review Bubbles ── +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; + div.innerHTML = renderMarkdown(text); + parseFluentEmojis(div); + _addTimestamp(div, timestamp); + _addReplyButton(div); + _addDeleteButton(div, messageId, deletedOn); + _addMarkButton(div, messageId, isMarked); + _addShareButton(div, messageId); + chatEl().appendChild(div); + scrollDown(); + return div; +} + let _reviewBubble = null; let _reviewTextEl = null; let _reviewText = '';