// ── Streaming Bubble ── let _streamDiv = null; let _streamText = ''; function startStreamBubble() { _streamDiv = document.createElement('div'); _streamDiv.className = 'msg assistant markdown streamed'; _streamDiv.innerHTML = '

'; _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 = ''; } // ── Thinking Bubble (Nvidia Reasoning) ── let _thinkingDiv = null; let _thinkingText = ''; function startThinkingBubble() { _thinkingDiv = document.createElement('div'); _thinkingDiv.className = 'tool-box thinking-box streamed'; var header = document.createElement('div'); header.className = 'tool-box-header clickable'; header.innerHTML = 'Denkprozess'; var content = document.createElement('div'); content.className = 'thinking-content markdown'; content.hidden = true; content.innerHTML = '

'; header.addEventListener('click', function() { content.hidden = !content.hidden; }); _thinkingDiv.appendChild(header); _thinkingDiv.appendChild(content); _thinkingText = ''; chatEl().appendChild(_thinkingDiv); scrollDown(); } function appendToThinking(text) { if (!_thinkingDiv) startThinkingBubble(); _thinkingText += text; var contentEl = _thinkingDiv.querySelector('.thinking-content'); contentEl.innerHTML = renderMarkdown(_thinkingText); var cursor = document.createElement('span'); cursor.className = 'cursor'; var lastBlock = contentEl.lastElementChild; if (lastBlock) lastBlock.appendChild(cursor); else contentEl.appendChild(cursor); scrollDown(); } function endThinkingBubble() { if (_thinkingDiv) { var cursor = _thinkingDiv.querySelector('.cursor'); if (cursor) cursor.remove(); var contentEl = _thinkingDiv.querySelector('.thinking-content'); if (!_thinkingText.trim()) _thinkingDiv.remove(); else { contentEl.innerHTML = renderMarkdown(_thinkingText); contentEl.hidden = true; } } _thinkingDiv = null; _thinkingText = ''; } // ── 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 = '' + esc(displayName) + ' '; _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 = ' Weitergeleitet an Session-Agent'; } else if (agentType === 'preparer' && type === 'query') { status.className += ' review-rejected'; status.innerHTML = ' Rückfrage an User'; } else if (agentType === 'finalizer' || type === 'done') { status.className += ' review-approved'; status.innerHTML = ' Abgeschlossen'; } else if (type === 'approved') { status.className += ' review-approved'; status.innerHTML = ' Approved'; } else { status.className += ' review-rejected'; status.innerHTML = ' Feedback – Worker korrigiert...'; } _reviewBubble.appendChild(status); _reviewBubble = null; _reviewTextEl = null; _reviewText = ''; _currentReviewToolBox = null; scrollDown(); }