- 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
134 lines
4.4 KiB
JavaScript
134 lines
4.4 KiB
JavaScript
// ── Streaming Bubble ──
|
||
|
||
let _streamDiv = null;
|
||
let _streamText = '';
|
||
|
||
function startStreamBubble() {
|
||
_streamDiv = document.createElement('div');
|
||
_streamDiv.className = 'msg assistant markdown streamed';
|
||
_streamDiv.innerHTML = '<p><span class="cursor"></span></p>';
|
||
_streamText = '';
|
||
chatEl().appendChild(_streamDiv);
|
||
scrollDown();
|
||
}
|
||
|
||
function appendToStream(text) {
|
||
if (!_streamDiv) startStreamBubble();
|
||
_streamText += text;
|
||
_streamDiv.innerHTML = renderMarkdown(_streamText);
|
||
// Cursor ins letzte Inline/Text-Element einfügen, damit er nicht auf eine neue Zeile rutscht
|
||
const cursor = document.createElement('span');
|
||
cursor.className = 'cursor';
|
||
const lastBlock = _streamDiv.lastElementChild;
|
||
if (lastBlock) {
|
||
lastBlock.appendChild(cursor);
|
||
} else {
|
||
_streamDiv.appendChild(cursor);
|
||
}
|
||
scrollDown();
|
||
}
|
||
|
||
function pauseStreamBubble() {
|
||
// Cursor entfernen, aber Bubble + Text behalten (z.B. bei Web-Search-Pause)
|
||
if (_streamDiv) {
|
||
const cursor = _streamDiv.querySelector('.cursor');
|
||
if (cursor) cursor.remove();
|
||
}
|
||
}
|
||
|
||
function endStreamBubble() {
|
||
if (_streamDiv) {
|
||
const cursor = _streamDiv.querySelector('.cursor');
|
||
if (cursor) cursor.remove();
|
||
if (!_streamText.trim()) _streamDiv.remove();
|
||
else {
|
||
_streamDiv.innerHTML = renderMarkdown(_streamText);
|
||
parseFluentEmojis(_streamDiv);
|
||
wrapKreaGalleries(_streamDiv);
|
||
}
|
||
}
|
||
_streamDiv = null;
|
||
_streamText = '';
|
||
}
|
||
|
||
// ── Review Bubbles (Streaming) ──
|
||
|
||
let _reviewBubble = null;
|
||
let _reviewTextEl = null;
|
||
let _reviewText = '';
|
||
|
||
function addReviewBubble(type, data, agentType) {
|
||
_reviewBubble = document.createElement('div');
|
||
_reviewBubble.className = 'msg review-bubble streamed';
|
||
if (agentType === 'finalizer') _reviewBubble.classList.add('finalizer-bubble');
|
||
if (agentType === 'preparer') _reviewBubble.classList.add('preparer-bubble');
|
||
_reviewBubble.appendChild(_createSubAgentHeader(agentType || 'review', data.agentName, data));
|
||
_reviewTextEl = document.createElement('div');
|
||
_reviewTextEl.className = 'review-content markdown';
|
||
_reviewBubble.appendChild(_reviewTextEl);
|
||
_reviewText = '';
|
||
chatEl().appendChild(_reviewBubble);
|
||
scrollDown();
|
||
}
|
||
|
||
function appendToReviewText(text) {
|
||
if (!_reviewBubble) return;
|
||
_reviewText += text;
|
||
_reviewTextEl.innerHTML = renderMarkdown(_reviewText);
|
||
scrollDown();
|
||
}
|
||
|
||
let _currentReviewToolBox = null;
|
||
|
||
function addReviewToolUse(data) {
|
||
if (!_reviewBubble) return;
|
||
if (!isToolVisible(data.toolName)) {
|
||
_currentReviewToolBox = null;
|
||
return;
|
||
}
|
||
var toolDiv = document.createElement('div');
|
||
toolDiv.className = 'tool-box review-tool-box streamed';
|
||
toolDiv.dataset.toolName = data.toolName;
|
||
var displayName = TOOL_DISPLAY_NAMES[data.toolName] || data.toolName;
|
||
toolDiv.innerHTML = '<span class="tool-name">' + esc(displayName) + '</span> <span class="tool-spinner"><i class="fa fa-hourglass-half"></i></span>';
|
||
_reviewBubble.appendChild(toolDiv);
|
||
_currentReviewToolBox = toolDiv;
|
||
scrollDown();
|
||
}
|
||
|
||
function finishReviewToolBox(data) {
|
||
if (_currentReviewToolBox) {
|
||
renderToolBoxContent(_currentReviewToolBox, data.toolName, data.input, data.result, data.isError, data.sources);
|
||
_currentReviewToolBox = null;
|
||
scrollDown();
|
||
}
|
||
}
|
||
|
||
function finalizeReviewBubble(type, data, agentType) {
|
||
if (!_reviewBubble) return;
|
||
const status = document.createElement('div');
|
||
status.className = 'review-status';
|
||
if (agentType === 'preparer' && type === 'forwarded') {
|
||
status.className += ' review-approved';
|
||
status.innerHTML = '<i class="fa fa-arrow-right"></i> Weitergeleitet an Session-Agent';
|
||
} else if (agentType === 'preparer' && type === 'query') {
|
||
status.className += ' review-rejected';
|
||
status.innerHTML = '<i class="fa fa-question-circle"></i> Rückfrage an User';
|
||
} else if (agentType === 'finalizer' || type === 'done') {
|
||
status.className += ' review-approved';
|
||
status.innerHTML = '<i class="fa fa-flag-checkered"></i> Abgeschlossen';
|
||
} else if (type === 'approved') {
|
||
status.className += ' review-approved';
|
||
status.innerHTML = '<i class="fa fa-check"></i> Approved';
|
||
} else {
|
||
status.className += ' review-rejected';
|
||
status.innerHTML = '<i class="fa fa-repeat"></i> Feedback – Worker korrigiert...';
|
||
}
|
||
_reviewBubble.appendChild(status);
|
||
_reviewBubble = null;
|
||
_reviewTextEl = null;
|
||
_reviewText = '';
|
||
_currentReviewToolBox = null;
|
||
scrollDown();
|
||
}
|