diff --git a/wwwroot/index.html b/wwwroot/index.html
index 4c1a51f..28c4049 100644
--- a/wwwroot/index.html
+++ b/wwwroot/index.html
@@ -475,7 +475,7 @@
-
+
diff --git a/wwwroot/js/archive.js b/wwwroot/js/archive.js
index 37d0fc5..e0f2c70 100644
--- a/wwwroot/js/archive.js
+++ b/wwwroot/js/archive.js
@@ -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 => '' + esc(b) + '').join('');
- fragment.appendChild(metaDiv);
+ fragment.appendChild(_createChainMetaDiv(entry.chain, 'archived'));
}
}
diff --git a/wwwroot/js/bubbles.js b/wwwroot/js/bubbles.js
index 850bd37..bca44e1 100644
--- a/wwwroot/js/bubbles.js
+++ b/wwwroot/js/bubbles.js
@@ -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 = ' ' + 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 = ' ' + 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 = ' ' + 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';
diff --git a/wwwroot/js/chat.js b/wwwroot/js/chat.js
index c3b23c4..5c7b375 100644
--- a/wwwroot/js/chat.js
+++ b/wwwroot/js/chat.js
@@ -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 => '' + esc(b) + '').join('');
+ const div = _createChainMetaDiv(d, streamed ? 'streamed' : null);
chatEl().appendChild(div);
scrollDown();
}
diff --git a/wwwroot/js/history.js b/wwwroot/js/history.js
index 44cb051..af60947 100644
--- a/wwwroot/js/history.js
+++ b/wwwroot/js/history.js
@@ -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 => '' + esc(b) + '').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 => '' + esc(b) + '').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 ──
diff --git a/wwwroot/js/pages/admin-page.js b/wwwroot/js/pages/admin-page.js
index cf0b0fa..cdc11b3 100644
--- a/wwwroot/js/pages/admin-page.js
+++ b/wwwroot/js/pages/admin-page.js
@@ -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();
diff --git a/wwwroot/js/pages/agent-page.js b/wwwroot/js/pages/agent-page.js
index 468e2a9..f90c9b4 100644
--- a/wwwroot/js/pages/agent-page.js
+++ b/wwwroot/js/pages/agent-page.js
@@ -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 () => {
diff --git a/wwwroot/js/pages/server-settings-page.js b/wwwroot/js/pages/server-settings-page.js
index 079a442..39d27cf 100644
--- a/wwwroot/js/pages/server-settings-page.js
+++ b/wwwroot/js/pages/server-settings-page.js
@@ -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;
}
diff --git a/wwwroot/js/pages/session-page.js b/wwwroot/js/pages/session-page.js
index ca7bb6d..ae263a6 100644
--- a/wwwroot/js/pages/session-page.js
+++ b/wwwroot/js/pages/session-page.js
@@ -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');
diff --git a/wwwroot/js/tool-ui.js b/wwwroot/js/tool-ui.js
index 4d9cb53..da42dbf 100644
--- a/wwwroot/js/tool-ui.js
+++ b/wwwroot/js/tool-ui.js
@@ -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);
- }
-}
diff --git a/wwwroot/js/ui-helpers.js b/wwwroot/js/ui-helpers.js
index ee16cc8..e2eeb76 100644
--- a/wwwroot/js/ui-helpers.js
+++ b/wwwroot/js/ui-helpers.js
@@ -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) {