// ── 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; // Wenn ein :::ASK_USER_CHOICE::: Block begonnen aber noch nicht abgeschlossen wurde, // nur den Text VOR dem Marker rendern (Marker-Block verstecken bis er komplett ist) var displayText = _streamText; var choiceStart = displayText.indexOf(':::ASK_USER_CHOICE:::'); if (choiceStart >= 0 && displayText.indexOf(':::END_ASK_USER_CHOICE:::') < 0) { displayText = displayText.substring(0, choiceStart); } _streamDiv.innerHTML = renderMarkdown(displayText); // 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 = '' + 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(); }