From e4ccaf9dee6ab03b18695c6af8afb6458178739a Mon Sep 17 00:00:00 2001 From: Meleki Date: Sat, 7 Mar 2026 21:58:36 +0100 Subject: [PATCH] =?UTF-8?q?refactor(js):=20dritte=20Refactoring-Iteration?= =?UTF-8?q?=20=E2=80=93=20weitere=20Duplikate=20eliminiert?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 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 --- wwwroot/index.html | 2 +- wwwroot/js/archive.js | 6 +- wwwroot/js/bubbles.js | 113 +++++--------------- wwwroot/js/chat.js | 6 +- wwwroot/js/history.js | 128 +++++------------------ wwwroot/js/pages/admin-page.js | 34 +----- wwwroot/js/pages/agent-page.js | 7 +- wwwroot/js/pages/server-settings-page.js | 13 +-- wwwroot/js/pages/session-page.js | 90 ++++------------ wwwroot/js/tool-ui.js | 43 -------- wwwroot/js/ui-helpers.js | 11 ++ 11 files changed, 96 insertions(+), 357 deletions(-) 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) {