refactor(js): dritte Refactoring-Iteration – weitere Duplikate eliminiert

Zusammengeführt/dedupliziert:
- renderMessages() nutzt jetzt _renderMessagesIntoFragment() statt eigenen switch/case
- addChoiceBubble() + addChoiceBubbleStatic() → _createChoiceBubble() Helper
- addErrorBubble() + addWarningBubble() → _addSystemBubble() Helper
- Chain-Meta-Erstellung (3x) → _createChainMetaDiv() Helper
- Agent-Picker-Setup (4x) → setupAgentPicker() Helper in session-page
- more-Click-Handler (2x) → initMoreButton() Helper in ui-helpers
- showLoginLinkModal() nutzt jetzt _createModal()
- showMessage() in server-settings → showToast()

Verwaiste Funktionen entfernt:
- addAssistantBubble(), addReviewBubbleStatic(), addChoiceBubbleStatic()
- addAttachmentIndicator(), addToolBoxStatic(), appendToolResult()
- serverSettingsMessage HTML-Element

Netto: -261 Zeilen
This commit is contained in:
Meleki committed 2026-03-07 21:58:36 +01:00
1 parent 1a2c407538
commit e4ccaf9dee
11 files changed
+96 -357

No files matched your search

+1 -1
View File
@@ -475,7 +475,7 @@
<div class="form-actions">
<button id="serverSettingsSaveBtn"><i class="fa fa-save"></i> Speichern</button>
</div>
<div id="serverSettingsMessage" class="profile-message" hidden></div>
</div>
</div>
</div>
+1 -5
View File
@@ -51,11 +51,7 @@ async function loadLatestArchive() {
for (const entry of data) {
_renderMessagesIntoFragment(fragment, _mergeConsecutiveAssistantTexts(entry.messages), archiveImageIds, { archived: true });
if (entry.chain && isChainMetaVisible(entry.chain)) {
const badges = buildMetaBadges(entry.chain);
const metaDiv = document.createElement('div');
metaDiv.className = 'chain-meta archived';
metaDiv.innerHTML = badges.map(b => '<span class="chain-meta-badge">' + esc(b) + '</span>').join('');
fragment.appendChild(metaDiv);
fragment.appendChild(_createChainMetaDiv(entry.chain, 'archived'));
}
}
+29 -84
View File
@@ -157,130 +157,75 @@ function addUserBubble(text, streamed = false, timestamp = '', messageId = null,
scrollDown();
}
function addAssistantBubble(text, timestamp = '', messageId = null, isMarked = false, deletedOn = null) {
const div = _createMessageBubble({ role: 'assistant', timestamp, messageId, isMarked, deletedOn });
div.innerHTML = renderMarkdown(text);
parseFluentEmojis(div);
wrapKreaGalleries(div);
_addBubbleActions(div, { timestamp, messageId, deletedOn, isMarked });
function _addSystemBubble(text, icon, color, streamed) {
const div = document.createElement('div');
div.className = 'msg system-info';
if (streamed) div.classList.add('streamed');
div.style.color = color;
div.innerHTML = '<i class="fa ' + icon + '"></i> ' + esc((text || '').trim());
chatEl().appendChild(div);
scrollDown();
return div;
}
function addErrorBubble(text, streamed = false) {
const div = document.createElement('div');
div.className = 'msg system-info';
if (streamed) div.classList.add('streamed');
div.style.color = 'var(--dangerous)';
div.innerHTML = '<i class="fa fa-times-circle"></i> ' + esc((text || '').trim());
chatEl().appendChild(div);
scrollDown();
return _addSystemBubble(text, 'fa-times-circle', 'var(--dangerous)', streamed);
}
function addWarningBubble(text, streamed = false) {
const div = document.createElement('div');
div.className = 'msg system-info';
if (streamed) div.classList.add('streamed');
div.style.color = 'var(--warning)';
div.innerHTML = '<i class="fa fa-exclamation-triangle"></i> ' + esc((text || '').trim());
chatEl().appendChild(div);
scrollDown();
return _addSystemBubble(text, 'fa-exclamation-triangle', 'var(--warning)', streamed);
}
// ── Choice Bubbles (ask_user_choice) ──
// Wird live aus dem SSE-Stream aufgerufen
function addChoiceBubble(toolCallId, question, choices, streamed = true) {
// Guard: choices muss ein Array sein
if (!Array.isArray(choices)) choices = [String(choices)];
/**
* Creates a choice bubble element (for both live stream and history rendering).
* @param {object} opts - { toolCallId, question, choices, selectedChoice, timestamp, messageId, streamed }
*/
function _createChoiceBubble(opts) {
if (!Array.isArray(opts.choices)) opts.choices = [String(opts.choices)];
const answered = !!opts.selectedChoice;
const div = document.createElement('div');
div.className = 'msg choice-bubble';
if (streamed) div.classList.add('streamed');
div.dataset.toolCallId = toolCallId;
div.className = 'msg choice-bubble' + (answered ? ' choice-answered' : '');
if (opts.streamed) div.classList.add('streamed');
div.dataset.toolCallId = opts.toolCallId || '';
if (opts.messageId) div.dataset.messageId = opts.messageId;
const questionDiv = document.createElement('div');
questionDiv.className = 'choice-question';
questionDiv.innerHTML = renderMarkdown(question);
questionDiv.innerHTML = renderMarkdown(opts.question);
parseFluentEmojis(questionDiv);
div.appendChild(questionDiv);
const buttonsDiv = document.createElement('div');
buttonsDiv.className = 'choice-buttons';
for (const choice of choices) {
for (const choice of opts.choices) {
const btn = document.createElement('button');
btn.className = 'choice-btn';
btn.textContent = choice;
btn.addEventListener('click', () => handleChoiceClick(toolCallId, choice, div));
buttonsDiv.appendChild(btn);
}
div.appendChild(buttonsDiv);
chatEl().appendChild(div);
scrollDown();
return div;
}
// Wird für History-Rendering aufgerufen (bereits beantwortete Choices)
function addChoiceBubbleStatic(toolCallId, question, choices, selectedChoice, timestamp, messageId) {
// Guard: choices muss ein Array sein
if (!Array.isArray(choices)) choices = [String(choices)];
const div = document.createElement('div');
div.className = 'msg choice-bubble choice-answered';
div.dataset.toolCallId = toolCallId;
if (messageId) div.dataset.messageId = messageId;
const questionDiv = document.createElement('div');
questionDiv.className = 'choice-question';
questionDiv.innerHTML = renderMarkdown(question);
parseFluentEmojis(questionDiv);
div.appendChild(questionDiv);
const buttonsDiv = document.createElement('div');
buttonsDiv.className = 'choice-buttons';
for (const choice of choices) {
const btn = document.createElement('button');
btn.className = 'choice-btn';
btn.disabled = true;
btn.textContent = choice;
if (choice === selectedChoice) {
btn.classList.add('choice-selected');
if (answered) {
btn.disabled = true;
btn.classList.add(choice === opts.selectedChoice ? 'choice-selected' : 'choice-unselected');
} else {
btn.classList.add('choice-unselected');
btn.addEventListener('click', () => handleChoiceClick(opts.toolCallId, choice, div));
}
buttonsDiv.appendChild(btn);
}
div.appendChild(buttonsDiv);
if (timestamp) _addTimestamp(div, timestamp);
chatEl().appendChild(div);
scrollDown();
if (opts.timestamp) _addTimestamp(div, opts.timestamp);
return div;
}
// ── Review Bubbles (Static / History) ──
function addReviewBubbleStatic(text, timestamp, messageId, isMarked, deletedOn) {
const div = _createMessageBubble({ role: 'assistant', extraClass: 'review-bubble', timestamp, messageId, isMarked, deletedOn });
div.innerHTML = renderMarkdown(text);
parseFluentEmojis(div);
_addBubbleActions(div, { timestamp, messageId, deletedOn, isMarked });
// Wird live aus dem SSE-Stream aufgerufen
function addChoiceBubble(toolCallId, question, choices, streamed = true) {
const div = _createChoiceBubble({ toolCallId, question, choices, streamed });
chatEl().appendChild(div);
scrollDown();
return div;
}
// ── Attachment Indicator ──
function addAttachmentIndicator(messageId, count, namesJson) {
const chat = chatEl();
const lastMsg = chat.lastElementChild;
if (!lastMsg) return;
lastMsg.appendChild(createAttachmentIndicator(messageId, count, namesJson));
}
async function loadAndShowAttachments(messageId, indicator) {
if (indicator.dataset.loaded) return;
indicator.dataset.loaded = '1';
+1 -5
View File
@@ -19,11 +19,7 @@ function addChainMetaBubble(d, streamed) {
// Chain noch nicht finalisiert (läuft noch) → nicht anzeigen
if (!d.inputTokens && !d.outputTokens && !d.durationMs) return;
if (!isChainMetaVisible(d)) return;
const badges = buildMetaBadges(d);
const div = document.createElement('div');
div.className = 'chain-meta';
if (streamed) div.classList.add('streamed');
div.innerHTML = badges.map(b => '<span class="chain-meta-badge">' + esc(b) + '</span>').join('');
const div = _createChainMetaDiv(d, streamed ? 'streamed' : null);
chatEl().appendChild(div);
scrollDown();
}
+26 -102
View File
@@ -106,11 +106,7 @@ async function loadOlderMessages() {
for (const entry of entries) {
_renderMessagesIntoFragment(fragment, _mergeConsecutiveAssistantTexts(entry.messages), imageMessageIds);
if (entry.chain && isChainMetaVisible(entry.chain)) {
const badges = buildMetaBadges(entry.chain);
const metaDiv = document.createElement('div');
metaDiv.className = 'chain-meta';
metaDiv.innerHTML = badges.map(b => '<span class="chain-meta-badge">' + esc(b) + '</span>').join('');
fragment.appendChild(metaDiv);
fragment.appendChild(_createChainMetaDiv(entry.chain));
}
}
@@ -274,6 +270,14 @@ function _isReviewMessage(content) {
return content && (content.startsWith('[Review:') || content.startsWith('[Review-Feedback') || content.startsWith('[Finalizer:') || content.startsWith('[Preparer:'));
}
function _createChainMetaDiv(chain, extraClass) {
const badges = buildMetaBadges(chain);
const div = document.createElement('div');
div.className = 'chain-meta' + (extraClass ? ' ' + extraClass : '');
div.innerHTML = badges.map(b => '<span class="chain-meta-badge">' + esc(b) + '</span>').join('');
return div;
}
function _renderChoiceBubbleInFragment(fragment, msgs, msgIdx) {
var msg = msgs[msgIdx];
try {
@@ -288,36 +292,14 @@ function _renderChoiceBubbleInFragment(fragment, msgs, msgIdx) {
break;
}
}
var choiceDiv = document.createElement('div');
choiceDiv.className = 'msg choice-bubble' + (selectedChoice ? ' choice-answered' : '');
choiceDiv.dataset.toolCallId = msg.toolCallId || '';
if (msg.id) choiceDiv.dataset.messageId = msg.id;
var qDiv = document.createElement('div');
qDiv.className = 'choice-question';
qDiv.innerHTML = renderMarkdown(choiceQuestion);
parseFluentEmojis(qDiv);
choiceDiv.appendChild(qDiv);
var btnsDiv = document.createElement('div');
btnsDiv.className = 'choice-buttons';
for (var co of choiceOptions) {
var cbtn = document.createElement('button');
cbtn.className = 'choice-btn';
cbtn.textContent = co;
if (selectedChoice) {
cbtn.disabled = true;
cbtn.classList.add(co === selectedChoice ? 'choice-selected' : 'choice-unselected');
} else {
(function(toolCallId, choice, bubble) {
cbtn.addEventListener('click', function() { handleChoiceClick(toolCallId, choice, bubble); });
})(msg.toolCallId, co, choiceDiv);
}
btnsDiv.appendChild(cbtn);
}
choiceDiv.appendChild(btnsDiv);
if (msg.createdAt) _addTimestamp(choiceDiv, msg.createdAt);
var choiceDiv = _createChoiceBubble({
toolCallId: msg.toolCallId,
question: choiceQuestion,
choices: choiceOptions,
selectedChoice: selectedChoice,
timestamp: msg.createdAt,
messageId: msg.id
});
fragment.appendChild(choiceDiv);
} catch(e) {
console.error('Choice-Bubble Rendering fehlgeschlagen:', e);
@@ -328,75 +310,17 @@ function _renderChoiceBubbleInFragment(fragment, msgs, msgIdx) {
function renderMessages(msgs, addToConversation) {
msgs = _mergeConsecutiveAssistantTexts(msgs);
for (var _rmIdx = 0; _rmIdx < msgs.length; _rmIdx++) {
const msg = msgs[_rmIdx];
const fragment = document.createDocumentFragment();
const imageMessageIds = [];
_renderMessagesIntoFragment(fragment, msgs, imageMessageIds);
chatEl().appendChild(fragment);
// Track lastKnownMessageId
for (const msg of msgs) {
if (msg.id > lastKnownMessageId) lastKnownMessageId = msg.id;
switch (msg.type) {
case 'text':
if (_isReviewMessage(msg.content)) {
addReviewBubbleStatic(msg.content, msg.createdAt, msg.id, msg.isMarked, msg.deletedOn);
} else if (msg.role === 'user') {
addUserBubble(msg.content, false, msg.createdAt, msg.id, msg.isMarked, msg.deletedOn);
if (msg.attachmentCount > 0) {
_autoLoadImageAttachments(msg.id);
addAttachmentIndicator(msg.id, msg.attachmentCount, msg.attachmentNames);
}
} else if (msg.role === 'assistant') {
addAssistantBubble(msg.content, msg.createdAt, msg.id, msg.isMarked, msg.deletedOn);
}
break;
case 'tool_use':
if (msg.toolName === 'ask_user_choice') {
try {
var choiceInput2 = typeof msg.content === 'string' ? JSON.parse(msg.content) : msg.content;
var choiceQ2 = choiceInput2.question || '';
var choiceOpts2 = Array.isArray(choiceInput2.choices) ? choiceInput2.choices : [String(choiceInput2.choices || '')];
// Voraus-Suche nach zugehörigem tool_result
var selectedChoice2 = null;
for (var ci2 = _rmIdx + 1; ci2 < msgs.length; ci2++) {
if (msgs[ci2].type === 'tool_result' && msgs[ci2].toolCallId === msg.toolCallId) {
selectedChoice2 = msgs[ci2].content;
msgs[ci2]._choiceHandled = true;
break;
}
}
if (selectedChoice2) {
addChoiceBubbleStatic(msg.toolCallId, choiceQ2, choiceOpts2, selectedChoice2, msg.createdAt, msg.id);
} else {
addChoiceBubble(msg.toolCallId, choiceQ2, choiceOpts2, false);
}
} catch(e) {
console.error('Choice-Bubble Rendering fehlgeschlagen:', e);
}
break;
}
if (isToolVisible(msg.toolName)) {
addToolBoxStatic(msg.toolName, msg.content, null, false);
}
break;
case 'web_search_result': {
if (isToolVisible('web_search')) {
var wsSrc = [];
try { wsSrc = JSON.parse(msg.content || '[]'); } catch(e) {}
if (wsSrc.length > 0) addToolBoxStatic('web_search', null, null, false, wsSrc);
}
break;
}
case 'tool_result':
if (msg._choiceHandled) break;
// tool_result wird dem vorherigen tool_use-Box angehängt wenn möglich
appendToolResult(msg.content, msg.isError);
break;
case 'error':
addErrorBubble(msg.content);
break;
}
}
// Auto-load image attachments
for (const mid of imageMessageIds) _autoLoadImageAttachments(mid);
scrollDown();
}
// ── Polling ──
+3 -31
View File
@@ -136,22 +136,12 @@ registerPage('admin', {
}
function showLoginLinkModal(user, link) {
// Entferne vorherigen Modal falls vorhanden
var existing = document.querySelector('.login-link-overlay');
if (existing) existing.remove();
var overlay = document.createElement('div');
overlay.className = 'login-link-overlay';
var modal = document.createElement('div');
modal.className = 'login-link-modal';
var title = document.createElement('h3');
title.textContent = 'Login-Link für ' + (user.displayName || user.username);
const { body } = _createModal('Login-Link für ' + (user.displayName || user.username));
var desc = document.createElement('p');
desc.className = 'login-link-desc';
desc.textContent = 'Dieser Link loggt automatisch als dieser Benutzer ein. Teile ihn nur mit vertrauenswürdigen Personen.';
body.appendChild(desc);
var inputRow = document.createElement('div');
inputRow.className = 'login-link-input-row';
@@ -176,25 +166,7 @@ registerPage('admin', {
inputRow.appendChild(input);
inputRow.appendChild(copyBtn);
var closeBtn = document.createElement('button');
closeBtn.className = 'btn';
closeBtn.textContent = 'Schließen';
closeBtn.addEventListener('click', function() {
overlay.remove();
});
modal.appendChild(title);
modal.appendChild(desc);
modal.appendChild(inputRow);
modal.appendChild(closeBtn);
overlay.appendChild(modal);
overlay.addEventListener('click', function(e) {
if (e.target === overlay) overlay.remove();
});
document.body.appendChild(overlay);
body.appendChild(inputRow);
// Input selektieren für einfaches Kopieren
input.select();
+1 -6
View File
@@ -91,12 +91,7 @@ registerPage('agent', {
// Befehl hinzufügen
addCommandBtn.addEventListener('click', () => createCommandRow(commandsList));
more.addEventListener('click', () => {
const content = more.querySelector('[hidden]');
while (content.firstChild)
more.parentNode.insertBefore(content.firstChild, more);
more.remove();
});
initMoreButton(more);
// Speichern
saveBtn.addEventListener('click', async () => {
+2 -11
View File
@@ -9,16 +9,8 @@ registerPage('server-settings', {
var container = document.getElementById('serverSettingsFields');
var saveBtn = document.getElementById('serverSettingsSaveBtn');
var messageEl = document.getElementById('serverSettingsMessage');
var inputs = {};
function showMessage(text, isError) {
messageEl.textContent = text;
messageEl.className = 'profile-message ' + (isError ? 'profile-message-error' : 'profile-message-success');
messageEl.hidden = false;
if (!isError) setTimeout(function() { messageEl.hidden = true; }, 4000);
}
// Settings laden und Formular aufbauen
try {
var result = await apiGetServerSettings();
@@ -109,7 +101,6 @@ registerPage('server-settings', {
// Speichern
saveBtn.addEventListener('click', async function() {
saveBtn.disabled = true;
messageEl.hidden = true;
try {
var data = {};
@@ -128,9 +119,9 @@ registerPage('server-settings', {
}
var result = await apiSaveServerSettings(data);
showMessage(result.hint || 'Einstellungen gespeichert', false);
showToast(result.hint || 'Einstellungen gespeichert');
} catch (err) {
showMessage(err.message, true);
showToast(err.message, 'error');
} finally {
saveBtn.disabled = false;
}
+21 -69
View File
@@ -100,79 +100,31 @@ registerPage('session', {
reviewIterationsRow.hidden = !reviewAgentSelect.value;
}
// Initiale Anzeige – Agent
if (session && session.agentId) {
agentSelect.value = session.agentId;
renderAgentInCard(agentSelectCard, agentMap[session.agentId] || null, 'fa-user-circle-o', 'Kein Agent ausgewählt');
} else {
renderAgentInCard(agentSelectCard, null, 'fa-user-circle-o', 'Kein Agent ausgewählt');
// Helper: Agent-Picker für eine Rolle einrichten (Initial-Rendering + Click-Handler)
function setupAgentPicker(cardEl, selectEl, initialId, emptyIcon, emptyText, onChange) {
if (initialId) {
selectEl.value = initialId;
renderAgentInCard(cardEl, agentMap[initialId] || null, emptyIcon, emptyText);
} else {
renderAgentInCard(cardEl, null, emptyIcon, emptyText);
}
cardEl.addEventListener('click', () => {
openAgentPickerModal(agents, agentSkillsMap, selectEl.value, (selectedId) => {
selectEl.value = selectedId || '';
renderAgentInCard(cardEl, selectedId ? agentMap[selectedId] : null, emptyIcon, emptyText);
if (onChange) onChange(selectedId);
});
});
}
// Initiale Anzeige – Preparer-Agent
if (session && session.preparerAgentId) {
preparerAgentSelect.value = session.preparerAgentId;
renderAgentInCard(preparerAgentSelectCard, agentMap[session.preparerAgentId] || null, 'fa-filter', 'Kein Vorbereiter-Agent');
} else {
renderAgentInCard(preparerAgentSelectCard, null, 'fa-filter', 'Kein Vorbereiter-Agent');
}
// Initiale Anzeige – Review-Agent
if (session && session.reviewAgentId) {
reviewAgentSelect.value = session.reviewAgentId;
renderAgentInCard(reviewAgentSelectCard, agentMap[session.reviewAgentId] || null, 'fa-check-circle-o', 'Kein Review-Agent');
maxReviewIterationsEl.value = session.maxReviewIterations || 3;
} else {
renderAgentInCard(reviewAgentSelectCard, null, 'fa-check-circle-o', 'Kein Review-Agent');
}
setupAgentPicker(agentSelectCard, agentSelect, session && session.agentId, 'fa-user-circle-o', 'Kein Agent ausgewählt');
setupAgentPicker(preparerAgentSelectCard, preparerAgentSelect, session && session.preparerAgentId, 'fa-filter', 'Kein Vorbereiter-Agent');
setupAgentPicker(reviewAgentSelectCard, reviewAgentSelect, session && session.reviewAgentId, 'fa-check-circle-o', 'Kein Review-Agent', updateReviewVisibility);
if (session && session.reviewAgentId) maxReviewIterationsEl.value = session.maxReviewIterations || 3;
updateReviewVisibility();
setupAgentPicker(finalizerAgentSelectCard, finalizerAgentSelect, session && session.finalizerAgentId, 'fa-flag-checkered', 'Kein Finalizer-Agent');
// Initiale Anzeige – Finalizer-Agent
if (session && session.finalizerAgentId) {
finalizerAgentSelect.value = session.finalizerAgentId;
renderAgentInCard(finalizerAgentSelectCard, agentMap[session.finalizerAgentId] || null, 'fa-flag-checkered', 'Kein Finalizer-Agent');
} else {
renderAgentInCard(finalizerAgentSelectCard, null, 'fa-flag-checkered', 'Kein Finalizer-Agent');
}
// Klick auf Agent-Card → Modal öffnen
agentSelectCard.addEventListener('click', () => {
openAgentPickerModal(agents, agentSkillsMap, agentSelect.value, (selectedId) => {
agentSelect.value = selectedId || '';
renderAgentInCard(agentSelectCard, selectedId ? agentMap[selectedId] : null, 'fa-user-circle-o', 'Kein Agent ausgewählt');
});
});
// Klick auf Preparer-Agent-Card → Modal öffnen
preparerAgentSelectCard.addEventListener('click', () => {
openAgentPickerModal(agents, agentSkillsMap, preparerAgentSelect.value, (selectedId) => {
preparerAgentSelect.value = selectedId || '';
renderAgentInCard(preparerAgentSelectCard, selectedId ? agentMap[selectedId] : null, 'fa-filter', 'Kein Vorbereiter-Agent');
});
});
// Klick auf Review-Agent-Card → Modal öffnen
reviewAgentSelectCard.addEventListener('click', () => {
openAgentPickerModal(agents, agentSkillsMap, reviewAgentSelect.value, (selectedId) => {
reviewAgentSelect.value = selectedId || '';
renderAgentInCard(reviewAgentSelectCard, selectedId ? agentMap[selectedId] : null, 'fa-check-circle-o', 'Kein Review-Agent');
updateReviewVisibility();
});
});
// Klick auf Finalizer-Agent-Card → Modal öffnen
finalizerAgentSelectCard.addEventListener('click', () => {
openAgentPickerModal(agents, agentSkillsMap, finalizerAgentSelect.value, (selectedId) => {
finalizerAgentSelect.value = selectedId || '';
renderAgentInCard(finalizerAgentSelectCard, selectedId ? agentMap[selectedId] : null, 'fa-flag-checkered', 'Kein Finalizer-Agent');
});
});
more.addEventListener('click', () => {
const content = more.querySelector('[hidden]');
while (content.firstChild)
more.parentNode.insertBefore(content.firstChild, more);
more.remove();
});
initMoreButton(more);
// Workspace Browse
const workspaceBrowse = document.getElementById('sessionWorkspaceBrowse');
-43
View File
@@ -256,47 +256,4 @@ function finishToolBox(div, data) {
scrollDown();
}
function addToolBoxStatic(name, input, result, isError, sources) {
const div = document.createElement('div');
div.className = 'tool-box';
div.dataset.toolName = name;
renderToolBoxContent(div, name, input, result, isError, sources);
chatEl().appendChild(div);
scrollDown();
}
function appendToolResult(result, isError) {
// Letzten tool-box im Chat finden und Result in die bereits vorhandene .tool-result-Div schreiben
const boxes = chatEl().querySelectorAll('.tool-box');
const lastBox = boxes[boxes.length - 1];
if (!lastBox) return;
// Wenn Diff vorhanden (edit_file/write_file) → kein Result anhängen
if (lastBox.querySelector('.tool-diff')) return;
const toolName = lastBox.dataset.toolName || '';
const isTodoWrite = toolName === 'todo_write';
if (isTodoWrite) {
// Vorhandene todo-list ersetzen oder neu erstellen
const existing = lastBox.querySelector('.todo-list');
if (existing) existing.remove();
if (result && !isError) {
const todoList = buildTodoList(result, isError);
lastBox.appendChild(todoList);
}
return;
}
const existing = lastBox.querySelector('.tool-result');
if (existing) {
existing.textContent = result || '';
if (isError) existing.classList.add('tool-error');
} else {
const resultDiv = document.createElement('div');
resultDiv.className = 'tool-result' + (isError ? ' tool-error' : '');
resultDiv.textContent = result || '';
resultDiv.hidden = true;
lastBox.appendChild(resultDiv);
}
}
+11
View File
@@ -89,6 +89,17 @@ function createMDE(element, options) {
return mde;
}
// ── More/Expand Button ──
function initMoreButton(moreEl) {
if (!moreEl) return;
moreEl.addEventListener('click', () => {
const content = moreEl.querySelector('[hidden]');
while (content.firstChild) moreEl.parentNode.insertBefore(content.firstChild, moreEl);
moreEl.remove();
});
}
// ── Meta Bar ──
function setMeta(text) {