refactor(frontend): dedupliziere JS-Code und fixe Bugs
- fix: _oldestLoadedMessageId → _oldestLoadedSortKey (chat.js, chat-page.js) - move: formatTimestamp nach ui-helpers.js (war _formatTimestamp in msg-actions.js) - move: _isReviewMessage nach ui-helpers.js (war in history.js) - add: _getSubAgentMeta + _createSubAgentHeader in ui-helpers.js - refactor: stream-ui.js + history.js nutzen _createSubAgentHeader - refactor: flushText() Hilfsfunktion in _renderReviewGroup (history.js) - refactor: shared-page.js nutzt _mergeConsecutiveAssistantTexts statt eigener Kopie - refactor: _initCardMenu + _onCardMenuAction Helper in dashboard.js -53 Zeilen netto
This commit is contained in:
1 parent
57560cbbea
commit
33934b2c4a
9 files changed
+114
-167
No files matched your search
+2
-2
@@ -162,7 +162,7 @@ async function clearChat() {
|
|||||||
chatEl().innerHTML = '';
|
chatEl().innerHTML = '';
|
||||||
lastKnownMessageId = 0;
|
lastKnownMessageId = 0;
|
||||||
loadedArchiveCount = 0;
|
loadedArchiveCount = 0;
|
||||||
_oldestLoadedMessageId = 0;
|
_oldestLoadedSortKey = 0;
|
||||||
_hasMoreOlderMessages = false;
|
_hasMoreOlderMessages = false;
|
||||||
clearReply();
|
clearReply();
|
||||||
|
|
||||||
@@ -272,7 +272,7 @@ async function sendMessage() {
|
|||||||
let msgText = text;
|
let msgText = text;
|
||||||
if (pendingReply) {
|
if (pendingReply) {
|
||||||
const roleLabel = pendingReply.role === 'user' ? 'User' : 'Assistant';
|
const roleLabel = pendingReply.role === 'user' ? 'User' : 'Assistant';
|
||||||
const timeLabel = _formatTimestamp(pendingReply.timestamp) || 'Jetzt';
|
const timeLabel = formatTimestamp(pendingReply.timestamp) || 'Jetzt';
|
||||||
const quoteText = pendingReply.text.split('\n').map(l => '> ' + l).join('\n');
|
const quoteText = pendingReply.text.split('\n').map(l => '> ' + l).join('\n');
|
||||||
const quoteHeader = `> [Zitat: ${timeLabel}, ${roleLabel}]:`;
|
const quoteHeader = `> [Zitat: ${timeLabel}, ${roleLabel}]:`;
|
||||||
msgText = quoteHeader + '\n' + quoteText + '\n\n' + (text || '');
|
msgText = quoteHeader + '\n' + quoteText + '\n\n' + (text || '');
|
||||||
|
|||||||
+17
-40
@@ -293,27 +293,28 @@ function _renderReviewGroup(tagGroup, archived) {
|
|||||||
if (tag === 'preparer') bubble.classList.add('preparer-bubble');
|
if (tag === 'preparer') bubble.classList.add('preparer-bubble');
|
||||||
|
|
||||||
// Extract agent name from first text message: "[Review: AgentName] text..."
|
// Extract agent name from first text message: "[Review: AgentName] text..."
|
||||||
var agentName = tag === 'preparer' ? 'Vorbereiter-Agent' : tag === 'finalizer' ? 'Finalizer-Agent' : 'Review-Agent';
|
var agentName = null;
|
||||||
var firstText = tagGroup.find(function(m) { return m.type === 'text' && m.role === 'assistant'; });
|
var firstText = tagGroup.find(function(m) { return m.type === 'text' && m.role === 'assistant'; });
|
||||||
if (firstText) {
|
if (firstText) {
|
||||||
var nameMatch = firstText.content.match(/^\[(Review|Preparer|Finalizer): ([^\]]+)\]/);
|
var nameMatch = firstText.content.match(/^\[(Review|Preparer|Finalizer): ([^\]]+)\]/);
|
||||||
if (nameMatch) agentName = nameMatch[2];
|
if (nameMatch) agentName = nameMatch[2];
|
||||||
}
|
}
|
||||||
|
|
||||||
// Header
|
bubble.appendChild(_createSubAgentHeader(tag, agentName));
|
||||||
var header = document.createElement('div');
|
|
||||||
header.className = 'review-header';
|
|
||||||
if (tag === 'preparer') {
|
|
||||||
header.innerHTML = '<i class="fa fa-filter"></i> <strong>' + esc(agentName) + '</strong>';
|
|
||||||
} else if (tag === 'finalizer') {
|
|
||||||
header.innerHTML = '<i class="fa fa-flag-checkered"></i> <strong>' + esc(agentName) + '</strong>';
|
|
||||||
} else {
|
|
||||||
header.innerHTML = '<i class="fa fa-check-circle-o"></i> <strong>' + esc(agentName) + '</strong>';
|
|
||||||
}
|
|
||||||
bubble.appendChild(header);
|
|
||||||
|
|
||||||
// Content: collect text (strip prefix) and tool-use indicators
|
// Content: collect text (strip prefix) and tool-use indicators
|
||||||
var textParts = [];
|
var textParts = [];
|
||||||
|
|
||||||
|
function flushText() {
|
||||||
|
if (textParts.length === 0) return;
|
||||||
|
var div = document.createElement('div');
|
||||||
|
div.className = 'review-content markdown';
|
||||||
|
div.innerHTML = renderMarkdown(textParts.join('\n\n'));
|
||||||
|
parseFluentEmojis(div);
|
||||||
|
bubble.appendChild(div);
|
||||||
|
textParts = [];
|
||||||
|
}
|
||||||
|
|
||||||
for (var i = 0; i < tagGroup.length; i++) {
|
for (var i = 0; i < tagGroup.length; i++) {
|
||||||
var m = tagGroup[i];
|
var m = tagGroup[i];
|
||||||
if (m.type === 'text' && m.role === 'assistant') {
|
if (m.type === 'text' && m.role === 'assistant') {
|
||||||
@@ -323,15 +324,7 @@ function _renderReviewGroup(tagGroup, archived) {
|
|||||||
if (text) textParts.push(text);
|
if (text) textParts.push(text);
|
||||||
} else if (m.type === 'tool_use') {
|
} else if (m.type === 'tool_use') {
|
||||||
if (!isToolVisible(m.toolName)) continue;
|
if (!isToolVisible(m.toolName)) continue;
|
||||||
// Flush accumulated text before tool box
|
flushText();
|
||||||
if (textParts.length > 0) {
|
|
||||||
var contentDiv = document.createElement('div');
|
|
||||||
contentDiv.className = 'review-content markdown';
|
|
||||||
contentDiv.innerHTML = renderMarkdown(textParts.join('\n\n'));
|
|
||||||
parseFluentEmojis(contentDiv);
|
|
||||||
bubble.appendChild(contentDiv);
|
|
||||||
textParts = [];
|
|
||||||
}
|
|
||||||
// Full tool-box (same as normal tool boxes)
|
// Full tool-box (same as normal tool boxes)
|
||||||
var toolDiv = document.createElement('div');
|
var toolDiv = document.createElement('div');
|
||||||
toolDiv.className = 'tool-box review-tool-box' + (archived ? ' archived' : '');
|
toolDiv.className = 'tool-box review-tool-box' + (archived ? ' archived' : '');
|
||||||
@@ -349,14 +342,7 @@ function _renderReviewGroup(tagGroup, archived) {
|
|||||||
bubble.appendChild(toolDiv);
|
bubble.appendChild(toolDiv);
|
||||||
} else if (m.type === 'web_search_result') {
|
} else if (m.type === 'web_search_result') {
|
||||||
if (!isToolVisible('web_search')) continue;
|
if (!isToolVisible('web_search')) continue;
|
||||||
if (textParts.length > 0) {
|
flushText();
|
||||||
var contentDiv = document.createElement('div');
|
|
||||||
contentDiv.className = 'review-content markdown';
|
|
||||||
contentDiv.innerHTML = renderMarkdown(textParts.join('\n\n'));
|
|
||||||
parseFluentEmojis(contentDiv);
|
|
||||||
bubble.appendChild(contentDiv);
|
|
||||||
textParts = [];
|
|
||||||
}
|
|
||||||
var wsSources = [];
|
var wsSources = [];
|
||||||
try { wsSources = JSON.parse(m.content || '[]'); } catch(e) {}
|
try { wsSources = JSON.parse(m.content || '[]'); } catch(e) {}
|
||||||
if (wsSources.length > 0) {
|
if (wsSources.length > 0) {
|
||||||
@@ -369,14 +355,7 @@ function _renderReviewGroup(tagGroup, archived) {
|
|||||||
// tool_result is consumed by tool_use look-ahead above
|
// tool_result is consumed by tool_use look-ahead above
|
||||||
}
|
}
|
||||||
|
|
||||||
// Flush remaining text
|
flushText();
|
||||||
if (textParts.length > 0) {
|
|
||||||
var contentDiv = document.createElement('div');
|
|
||||||
contentDiv.className = 'review-content markdown';
|
|
||||||
contentDiv.innerHTML = renderMarkdown(textParts.join('\n\n'));
|
|
||||||
parseFluentEmojis(contentDiv);
|
|
||||||
bubble.appendChild(contentDiv);
|
|
||||||
}
|
|
||||||
|
|
||||||
return bubble;
|
return bubble;
|
||||||
}
|
}
|
||||||
@@ -389,9 +368,7 @@ function _addOpenClawBadge(div) {
|
|||||||
div.insertBefore(badge, div.firstChild);
|
div.insertBefore(badge, div.firstChild);
|
||||||
}
|
}
|
||||||
|
|
||||||
function _isReviewMessage(content) {
|
// _isReviewMessage → moved to ui-helpers.js
|
||||||
return content && (content.startsWith('[Review:') || content.startsWith('[Review-Feedback') || content.startsWith('[Finalizer:') || content.startsWith('[Preparer:'));
|
|
||||||
}
|
|
||||||
|
|
||||||
function _createChainMetaDiv(chain, extraClass) {
|
function _createChainMetaDiv(chain, extraClass) {
|
||||||
const badges = buildMetaBadges(chain);
|
const badges = buildMetaBadges(chain);
|
||||||
|
|||||||
@@ -1,21 +1,9 @@
|
|||||||
// ── Timestamp ──
|
// ── Timestamp ──
|
||||||
|
// formatTimestamp() → defined in ui-helpers.js
|
||||||
function _formatTimestamp(isoString) {
|
|
||||||
if (!isoString) return '';
|
|
||||||
var d = new Date(isoString);
|
|
||||||
if (isNaN(d.getTime())) return '';
|
|
||||||
var dd = String(d.getDate()).padStart(2, '0');
|
|
||||||
var mm = String(d.getMonth() + 1).padStart(2, '0');
|
|
||||||
var yyyy = d.getFullYear();
|
|
||||||
var hh = String(d.getHours()).padStart(2, '0');
|
|
||||||
var min = String(d.getMinutes()).padStart(2, '0');
|
|
||||||
var ss = String(d.getSeconds()).padStart(2, '0');
|
|
||||||
return dd + '.' + mm + '.' + yyyy + ' ' + hh + ':' + min + ':' + ss;
|
|
||||||
}
|
|
||||||
|
|
||||||
function _addTimestamp(div, timestamp) {
|
function _addTimestamp(div, timestamp) {
|
||||||
if (!timestamp) return;
|
if (!timestamp) return;
|
||||||
var formatted = _formatTimestamp(timestamp);
|
var formatted = formatTimestamp(timestamp);
|
||||||
if (!formatted) return;
|
if (!formatted) return;
|
||||||
var span = document.createElement('span');
|
var span = document.createElement('span');
|
||||||
span.className = 'msg-timestamp';
|
span.className = 'msg-timestamp';
|
||||||
|
|||||||
@@ -352,7 +352,7 @@ registerPage('chat', {
|
|||||||
// State zurücksetzen und History laden
|
// State zurücksetzen und History laden
|
||||||
lastKnownMessageId = 0;
|
lastKnownMessageId = 0;
|
||||||
loadedArchiveCount = 0;
|
loadedArchiveCount = 0;
|
||||||
_oldestLoadedMessageId = 0;
|
_oldestLoadedSortKey = 0;
|
||||||
_hasMoreOlderMessages = false;
|
_hasMoreOlderMessages = false;
|
||||||
isStreaming = false;
|
isStreaming = false;
|
||||||
|
|
||||||
|
|||||||
@@ -104,28 +104,15 @@ registerPage('dashboard', {
|
|||||||
card.addEventListener('click', () => navigateTo('chat', { id: s.sessionId }));
|
card.addEventListener('click', () => navigateTo('chat', { id: s.sessionId }));
|
||||||
}
|
}
|
||||||
|
|
||||||
const menuTrigger = card.querySelector('.card-menu-trigger');
|
const { dropdown: sessionMenu } = _initCardMenu(card);
|
||||||
const menuDropdown = card.querySelector('.card-menu-dropdown');
|
|
||||||
menuTrigger.addEventListener('click', (e) => {
|
|
||||||
e.stopPropagation();
|
|
||||||
_openCardMenu(menuTrigger, menuDropdown);
|
|
||||||
});
|
|
||||||
chatBtn.addEventListener('click', (e) => {
|
chatBtn.addEventListener('click', (e) => {
|
||||||
e.stopPropagation();
|
e.stopPropagation();
|
||||||
if (chatBtn.disabled) return;
|
if (chatBtn.disabled) return;
|
||||||
menuDropdown.hidden = true;
|
sessionMenu.hidden = true;
|
||||||
navigateTo('chat', { id: s.sessionId });
|
navigateTo('chat', { id: s.sessionId });
|
||||||
});
|
});
|
||||||
menuDropdown.querySelector('[data-action="edit"]').addEventListener('click', (e) => {
|
_onCardMenuAction(sessionMenu, 'edit', () => navigateTo('session', { id: s.sessionId }));
|
||||||
e.stopPropagation();
|
_onCardMenuAction(sessionMenu, 'openclaw-import', () => showOpenClawImportModal(s.sessionId));
|
||||||
menuDropdown.hidden = true;
|
|
||||||
navigateTo('session', { id: s.sessionId });
|
|
||||||
});
|
|
||||||
menuDropdown.querySelector('[data-action="openclaw-import"]').addEventListener('click', (e) => {
|
|
||||||
e.stopPropagation();
|
|
||||||
menuDropdown.hidden = true;
|
|
||||||
showOpenClawImportModal(s.sessionId);
|
|
||||||
});
|
|
||||||
grid.appendChild(card);
|
grid.appendChild(card);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -152,22 +139,9 @@ registerPage('dashboard', {
|
|||||||
</div>
|
</div>
|
||||||
</div>`;
|
</div>`;
|
||||||
titleRow.insertAdjacentHTML('beforeend', menuHtml);
|
titleRow.insertAdjacentHTML('beforeend', menuHtml);
|
||||||
const menuTrigger = card.querySelector('.card-menu-trigger');
|
const { dropdown: agentMenu } = _initCardMenu(card);
|
||||||
const menuDropdown = card.querySelector('.card-menu-dropdown');
|
_onCardMenuAction(agentMenu, 'files', () => openFileExplorer({ agentId: a.agentId }));
|
||||||
menuTrigger.addEventListener('click', (e) => {
|
_onCardMenuAction(agentMenu, 'edit', () => navigateTo('agent', { id: a.agentId }));
|
||||||
e.stopPropagation();
|
|
||||||
_openCardMenu(menuTrigger, menuDropdown);
|
|
||||||
});
|
|
||||||
menuDropdown.querySelector('[data-action="files"]').addEventListener('click', (e) => {
|
|
||||||
e.stopPropagation();
|
|
||||||
menuDropdown.hidden = true;
|
|
||||||
openFileExplorer({ agentId: a.agentId });
|
|
||||||
});
|
|
||||||
menuDropdown.querySelector('[data-action="edit"]').addEventListener('click', (e) => {
|
|
||||||
e.stopPropagation();
|
|
||||||
menuDropdown.hidden = true;
|
|
||||||
navigateTo('agent', { id: a.agentId });
|
|
||||||
});
|
|
||||||
agentGrid.appendChild(card);
|
agentGrid.appendChild(card);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -213,30 +187,14 @@ registerPage('dashboard', {
|
|||||||
</div>
|
</div>
|
||||||
`;
|
`;
|
||||||
|
|
||||||
const menuTrigger = card.querySelector('.card-menu-trigger');
|
const { dropdown: cjMenu } = _initCardMenu(card);
|
||||||
const menuDropdown = card.querySelector('.card-menu-dropdown');
|
_onCardMenuAction(cjMenu, 'edit', () => navigateTo('cronjob', { id: cj.cronjobId }));
|
||||||
menuTrigger.addEventListener('click', (e) => {
|
_onCardMenuAction(cjMenu, 'toggle', async () => {
|
||||||
e.stopPropagation();
|
|
||||||
_openCardMenu(menuTrigger, menuDropdown);
|
|
||||||
});
|
|
||||||
|
|
||||||
menuDropdown.querySelector('[data-action="edit"]').addEventListener('click', (e) => {
|
|
||||||
e.stopPropagation();
|
|
||||||
menuDropdown.hidden = true;
|
|
||||||
navigateTo('cronjob', { id: cj.cronjobId });
|
|
||||||
});
|
|
||||||
|
|
||||||
menuDropdown.querySelector('[data-action="toggle"]').addEventListener('click', async (e) => {
|
|
||||||
e.stopPropagation();
|
|
||||||
menuDropdown.hidden = true;
|
|
||||||
await apiToggleCronjob(cj.cronjobId);
|
await apiToggleCronjob(cj.cronjobId);
|
||||||
showToast(cj.isEnabled ? 'Cronjob pausiert' : 'Cronjob aktiviert');
|
showToast(cj.isEnabled ? 'Cronjob pausiert' : 'Cronjob aktiviert');
|
||||||
navigateTo('dashboard', {}, { replace: true });
|
navigateTo('dashboard', {}, { replace: true });
|
||||||
});
|
});
|
||||||
|
_onCardMenuAction(cjMenu, 'run', async () => {
|
||||||
menuDropdown.querySelector('[data-action="run"]').addEventListener('click', async (e) => {
|
|
||||||
e.stopPropagation();
|
|
||||||
menuDropdown.hidden = true;
|
|
||||||
showToast('Cronjob wird ausgeführt...');
|
showToast('Cronjob wird ausgeführt...');
|
||||||
const result = await apiRunCronjob(cj.cronjobId);
|
const result = await apiRunCronjob(cj.cronjobId);
|
||||||
if (result.status === 'success') {
|
if (result.status === 'success') {
|
||||||
@@ -305,27 +263,13 @@ registerPage('dashboard', {
|
|||||||
</div>
|
</div>
|
||||||
`;
|
`;
|
||||||
|
|
||||||
const menuTrigger = card.querySelector('.card-menu-trigger');
|
const { dropdown: shareMenu } = _initCardMenu(card);
|
||||||
const menuDropdown = card.querySelector('.card-menu-dropdown');
|
_onCardMenuAction(shareMenu, 'copy', async () => {
|
||||||
menuTrigger.addEventListener('click', (e) => {
|
|
||||||
e.stopPropagation();
|
|
||||||
_openCardMenu(menuTrigger, menuDropdown);
|
|
||||||
});
|
|
||||||
|
|
||||||
menuDropdown.querySelector('[data-action="copy"]').addEventListener('click', async (e) => {
|
|
||||||
e.stopPropagation();
|
|
||||||
menuDropdown.hidden = true;
|
|
||||||
await navigator.clipboard.writeText(shareUrl);
|
await navigator.clipboard.writeText(shareUrl);
|
||||||
showToast('Link kopiert!');
|
showToast('Link kopiert!');
|
||||||
});
|
});
|
||||||
menuDropdown.querySelector('[data-action="open"]').addEventListener('click', (e) => {
|
_onCardMenuAction(shareMenu, 'open', () => window.open(shareUrl, '_blank'));
|
||||||
e.stopPropagation();
|
_onCardMenuAction(shareMenu, 'delete', async () => {
|
||||||
menuDropdown.hidden = true;
|
|
||||||
window.open(shareUrl, '_blank');
|
|
||||||
});
|
|
||||||
menuDropdown.querySelector('[data-action="delete"]').addEventListener('click', async (e) => {
|
|
||||||
e.stopPropagation();
|
|
||||||
menuDropdown.hidden = true;
|
|
||||||
await apiDeleteShare(s.shareToken);
|
await apiDeleteShare(s.shareToken);
|
||||||
card.remove();
|
card.remove();
|
||||||
if (sharedGrid.children.length === 0) sharedSection.hidden = true;
|
if (sharedGrid.children.length === 0) sharedSection.hidden = true;
|
||||||
@@ -364,4 +308,30 @@ function _openCardMenu(trigger, dropdown) {
|
|||||||
if (finalRect.left < 0) dropdown.style.left = '8px';
|
if (finalRect.left < 0) dropdown.style.left = '8px';
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Initializes a card menu: wires up the trigger button to open/close the dropdown.
|
||||||
|
* Returns { trigger, dropdown } for further action wiring.
|
||||||
|
*/
|
||||||
|
function _initCardMenu(card) {
|
||||||
|
const trigger = card.querySelector('.card-menu-trigger');
|
||||||
|
const dropdown = card.querySelector('.card-menu-dropdown');
|
||||||
|
trigger.addEventListener('click', (e) => {
|
||||||
|
e.stopPropagation();
|
||||||
|
_openCardMenu(trigger, dropdown);
|
||||||
|
});
|
||||||
|
return { trigger, dropdown };
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Wires a card menu action button (by data-action) to a handler.
|
||||||
|
*/
|
||||||
|
function _onCardMenuAction(dropdown, action, handler) {
|
||||||
|
const btn = dropdown.querySelector(`[data-action="${action}"]`);
|
||||||
|
if (btn) btn.addEventListener('click', (e) => {
|
||||||
|
e.stopPropagation();
|
||||||
|
dropdown.hidden = true;
|
||||||
|
handler(e, btn);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
@@ -22,7 +22,7 @@ registerPage('shared', {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Aufeinanderfolgende Assistant-Text-Messages mergen (wie im Chat)
|
// Aufeinanderfolgende Assistant-Text-Messages mergen (wie im Chat)
|
||||||
messages = _mergeConsecutiveTexts(messages);
|
messages = _mergeConsecutiveAssistantTexts(messages);
|
||||||
|
|
||||||
for (const msg of messages) {
|
for (const msg of messages) {
|
||||||
const c = msg.content || '';
|
const c = msg.content || '';
|
||||||
@@ -73,30 +73,4 @@ registerPage('shared', {
|
|||||||
destroy: () => {}
|
destroy: () => {}
|
||||||
});
|
});
|
||||||
|
|
||||||
function _mergeConsecutiveTexts(msgs) {
|
// _mergeConsecutiveTexts → replaced by _mergeConsecutiveAssistantTexts from history.js
|
||||||
const result = [];
|
|
||||||
for (const msg of msgs) {
|
|
||||||
const c = msg.content || '';
|
|
||||||
if (msg.type === 'web_search_result') {
|
|
||||||
result.push(Object.assign({}, msg));
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
if (msg.role === 'assistant' && !_isReviewMessage(c) && msg.type !== 'web_search_result') {
|
|
||||||
let prev = null;
|
|
||||||
for (let i = result.length - 1; i >= 0; i--) {
|
|
||||||
if (result[i].type === 'web_search_result') continue;
|
|
||||||
prev = result[i];
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
if (prev && prev.role === 'assistant'
|
|
||||||
&& !_isReviewMessage(prev.content)
|
|
||||||
&& prev.iteration === msg.iteration) {
|
|
||||||
prev.content = (prev.content || '') + '\n\n' + c;
|
|
||||||
if (msg.createdAt) prev.createdAt = msg.createdAt;
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
result.push(Object.assign({}, msg));
|
|
||||||
}
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
+1
-1
@@ -24,7 +24,7 @@ function renderReplyPreview() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const roleLabel = pendingReply.role === 'user' ? 'User' : 'Assistant';
|
const roleLabel = pendingReply.role === 'user' ? 'User' : 'Assistant';
|
||||||
const timeLabel = _formatTimestamp(pendingReply.timestamp) || 'Jetzt';
|
const timeLabel = formatTimestamp(pendingReply.timestamp) || 'Jetzt';
|
||||||
const preview = truncate(pendingReply.text, 150);
|
const preview = truncate(pendingReply.text, 150);
|
||||||
|
|
||||||
container.innerHTML = `<div class="reply-preview-item">
|
container.innerHTML = `<div class="reply-preview-item">
|
||||||
|
|||||||
+1
-10
@@ -62,16 +62,7 @@ function addReviewBubble(type, data, agentType) {
|
|||||||
_reviewBubble.className = 'msg review-bubble streamed';
|
_reviewBubble.className = 'msg review-bubble streamed';
|
||||||
if (agentType === 'finalizer') _reviewBubble.classList.add('finalizer-bubble');
|
if (agentType === 'finalizer') _reviewBubble.classList.add('finalizer-bubble');
|
||||||
if (agentType === 'preparer') _reviewBubble.classList.add('preparer-bubble');
|
if (agentType === 'preparer') _reviewBubble.classList.add('preparer-bubble');
|
||||||
const header = document.createElement('div');
|
_reviewBubble.appendChild(_createSubAgentHeader(agentType || 'review', data.agentName, data));
|
||||||
header.className = 'review-header';
|
|
||||||
if (agentType === 'preparer') {
|
|
||||||
header.innerHTML = '<i class="fa fa-filter"></i> <strong>' + esc(data.agentName || 'Vorbereiter-Agent') + '</strong>';
|
|
||||||
} else if (agentType === 'finalizer') {
|
|
||||||
header.innerHTML = '<i class="fa fa-flag-checkered"></i> <strong>' + esc(data.agentName || 'Finalizer-Agent') + '</strong>';
|
|
||||||
} else {
|
|
||||||
header.innerHTML = '<i class="fa fa-check-circle-o"></i> <strong>' + esc(data.agentName || 'Review-Agent') + '</strong> <span class="review-round">Runde ' + data.round + '/' + data.maxRounds + '</span>';
|
|
||||||
}
|
|
||||||
_reviewBubble.appendChild(header);
|
|
||||||
_reviewTextEl = document.createElement('div');
|
_reviewTextEl = document.createElement('div');
|
||||||
_reviewTextEl.className = 'review-content markdown';
|
_reviewTextEl.className = 'review-content markdown';
|
||||||
_reviewBubble.appendChild(_reviewTextEl);
|
_reviewBubble.appendChild(_reviewTextEl);
|
||||||
|
|||||||
@@ -154,6 +154,19 @@ function formatDate(iso) {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function formatTimestamp(isoString) {
|
||||||
|
if (!isoString) return '';
|
||||||
|
var d = new Date(isoString);
|
||||||
|
if (isNaN(d.getTime())) return '';
|
||||||
|
var dd = String(d.getDate()).padStart(2, '0');
|
||||||
|
var mm = String(d.getMonth() + 1).padStart(2, '0');
|
||||||
|
var yyyy = d.getFullYear();
|
||||||
|
var hh = String(d.getHours()).padStart(2, '0');
|
||||||
|
var min = String(d.getMinutes()).padStart(2, '0');
|
||||||
|
var ss = String(d.getSeconds()).padStart(2, '0');
|
||||||
|
return dd + '.' + mm + '.' + yyyy + ' ' + hh + ':' + min + ':' + ss;
|
||||||
|
}
|
||||||
|
|
||||||
// ── Meta Bar ──
|
// ── Meta Bar ──
|
||||||
|
|
||||||
function setMeta(text) {
|
function setMeta(text) {
|
||||||
@@ -205,6 +218,40 @@ function removeArchiveLoadButton() {
|
|||||||
btns.forEach(b => b.remove());
|
btns.forEach(b => b.remove());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ── Review/Sub-Agent Helpers ──
|
||||||
|
|
||||||
|
function _isReviewMessage(content) {
|
||||||
|
return content && (content.startsWith('[Review:') || content.startsWith('[Review-Feedback') || content.startsWith('[Finalizer:') || content.startsWith('[Preparer:'));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns { icon, defaultName } for a sub-agent type.
|
||||||
|
* @param {string} agentType - 'preparer', 'finalizer', or 'review'
|
||||||
|
*/
|
||||||
|
function _getSubAgentMeta(agentType) {
|
||||||
|
if (agentType === 'preparer') return { icon: 'fa-filter', defaultName: 'Vorbereiter-Agent' };
|
||||||
|
if (agentType === 'finalizer') return { icon: 'fa-flag-checkered', defaultName: 'Finalizer-Agent' };
|
||||||
|
return { icon: 'fa-check-circle-o', defaultName: 'Review-Agent' };
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates a review/sub-agent header element.
|
||||||
|
* @param {string} agentType - 'preparer', 'finalizer', or 'review'
|
||||||
|
* @param {string} agentName - Display name of the agent
|
||||||
|
* @param {object} [extra] - Optional: { round, maxRounds } for review
|
||||||
|
*/
|
||||||
|
function _createSubAgentHeader(agentType, agentName, extra) {
|
||||||
|
var meta = _getSubAgentMeta(agentType);
|
||||||
|
var header = document.createElement('div');
|
||||||
|
header.className = 'review-header';
|
||||||
|
var html = '<i class="fa ' + meta.icon + '"></i> <strong>' + esc(agentName || meta.defaultName) + '</strong>';
|
||||||
|
if (agentType === 'review' && extra && extra.round) {
|
||||||
|
html += ' <span class="review-round">Runde ' + extra.round + '/' + extra.maxRounds + '</span>';
|
||||||
|
}
|
||||||
|
header.innerHTML = html;
|
||||||
|
return header;
|
||||||
|
}
|
||||||
|
|
||||||
// ── Streamed Elements ──
|
// ── Streamed Elements ──
|
||||||
|
|
||||||
function removeStreamedElements() {
|
function removeStreamedElements() {
|
||||||
|
|||||||
Reference in new issue
Block a user