- recalled_context-Header verstärkt (klare Nutzungs-Anweisung statt passivem Hinweis) - SSE-Event 'auto_context' ans Frontend senden für Live-Anzeige - Auto-Context als Message (type=auto_context) in DB speichern - Aufklappbare Auto-Context-Box im Chat-Stream und History-Rendering Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
208 lines
6.9 KiB
JavaScript
208 lines
6.9 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 = '';
|
||
}
|
||
|
||
// ── 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 = '<span class="tool-name">Denkprozess</span>';
|
||
var content = document.createElement('div');
|
||
content.className = 'thinking-content markdown';
|
||
content.hidden = true;
|
||
content.innerHTML = '<p><span class="cursor"></span></p>';
|
||
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 = '<span class="tool-name">Auto-Context</span>';
|
||
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 = '<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();
|
||
}
|