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:
1 parent
1a2c407538
commit
e4ccaf9dee
11 files changed
+96
-357
No files matched your search
+1
-1
@@ -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>
|
||||
|
||||
@@ -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
@@ -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
@@ -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
@@ -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 ──
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -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 () => {
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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');
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -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) {
|
||||
|
||||
Reference in new issue
Block a user