// ── 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 = ''; } // ── Auto-Context Bubble ── function addAutoContextBubble(contextText) { if (!contextText) return; var box = document.createElement('div'); box.className = 'tool-box auto-context-box streamed'; var header = document.createElement('div'); header.className = 'tool-box-header clickable'; header.innerHTML = 'Auto-Context'; var content = document.createElement('div'); content.className = 'auto-context-content markdown'; content.hidden = true; // recalled_context aus XML-Tags extrahieren var inner = contextText.replace(/<\/?recalled_context>/g, '').trim(); content.innerHTML = renderMarkdown(inner); header.addEventListener('click', function() { content.hidden = !content.hidden; }); box.appendChild(header); box.appendChild(content); chatEl().appendChild(box); scrollDown(); } // ── 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(); } // ── Panel Bubbles (Fusion: mehrere Member parallel, non-streaming) ── // Member liefern ihren Text gesammelt bei panel_member_done → pro Member eine eigene // Bubble (keyed by index), kein Konflikt mit dem Single-_reviewBubble-State. let _panelBubbles = {}; function startPanel(data) { endStreamBubble(); _panelBubbles = {}; } function addPanelMemberBubble(data) { var bubble = document.createElement('div'); bubble.className = 'msg review-bubble panel-bubble streamed'; var model = (typeof prettyModelName === 'function') ? prettyModelName(data.model) : data.model; bubble.appendChild(_createSubAgentHeader('panel', data.name, { model: model })); var content = document.createElement('div'); content.className = 'review-content markdown'; content.innerHTML = ' denkt nach…'; bubble.appendChild(content); bubble._panelContent = content; _panelBubbles[data.index] = bubble; chatEl().appendChild(bubble); scrollDown(); } function finalizePanelMember(data) { var bubble = _panelBubbles[data.index]; if (!bubble) return; var text = (data.text && data.text.trim()) ? data.text : '_(keine Antwort)_'; bubble._panelContent.innerHTML = renderMarkdown(text); delete _panelBubbles[data.index]; scrollDown(); } function endPanel(data) { _panelBubbles = {}; }