From 1a2c407538db949b5cb88504082d87ee2cd8f9c0 Mon Sep 17 00:00:00 2001 From: Meleki Date: Sat, 7 Mar 2026 21:50:23 +0100 Subject: [PATCH] =?UTF-8?q?refactor(frontend):=20Duplikate=20in=20JS=20eli?= =?UTF-8?q?minieren=20=E2=80=93=20Runde=202?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Neue Helper-Funktionen: - _openCardMenu(trigger, dropdown): Card-Menü-Positionierung (4x in dashboard.js dedupliziert) - _createModal(title, extraClasses): Modal-Boilerplate (3 Modals in modals.js dedupliziert) - _openPickerModal(title, items, currentId, onSelect, opts): Picker-Modal (Agent + Session zusammengeführt) - createMDE(element, opts): EasyMDE-Initialisierung (3x in agent/session/profile-page dedupliziert) Weitere Bereinigungen: - _formatReplyTimestamp() entfernt, durch _formatTimestamp() ersetzt (reply.js + chat.js) - showFormMessage/showPageMessage in admin-page.js durch showToast() ersetzt - Inline-Nachrichten (profileMessage, profilePasswordMessage, adminMessage, adminFormMessage) durch Toast-Notifications ersetzt, zugehörige HTML-Elemente entfernt - profile-page.js: messageDiv/passwordMessageDiv-Logik durch showToast() ersetzt 11 Dateien, -210 Zeilen netto --- wwwroot/index.html | 7 +- wwwroot/js/chat.js | 2 +- wwwroot/js/modals.js | 207 +++++++++++++++---------------- wwwroot/js/pages/admin-page.js | 37 ++---- wwwroot/js/pages/agent-page.js | 12 +- wwwroot/js/pages/cronjob-page.js | 69 ++--------- wwwroot/js/pages/dashboard.js | 74 +++-------- wwwroot/js/pages/profile-page.js | 43 ++----- wwwroot/js/pages/session-page.js | 73 ++--------- wwwroot/js/reply.js | 19 +-- wwwroot/js/ui-helpers.js | 21 ++++ 11 files changed, 177 insertions(+), 387 deletions(-) diff --git a/wwwroot/index.html b/wwwroot/index.html index 3c72e1c..4c1a51f 100644 --- a/wwwroot/index.html +++ b/wwwroot/index.html @@ -442,7 +442,7 @@
- +

Passwort ändern

@@ -453,7 +453,7 @@
- +
@@ -494,7 +494,7 @@
- + @@ -520,7 +520,6 @@
- diff --git a/wwwroot/js/chat.js b/wwwroot/js/chat.js index 0e43d22..c3b23c4 100644 --- a/wwwroot/js/chat.js +++ b/wwwroot/js/chat.js @@ -297,7 +297,7 @@ async function sendMessage() { let msgText = text; if (pendingReply) { const roleLabel = pendingReply.role === 'user' ? 'User' : 'Assistant'; - const timeLabel = _formatReplyTimestamp(pendingReply.timestamp); + const timeLabel = _formatTimestamp(pendingReply.timestamp) || 'Jetzt'; const quoteText = pendingReply.text.split('\n').map(l => '> ' + l).join('\n'); const quoteHeader = `> [Zitat: ${timeLabel}, ${roleLabel}]:`; msgText = quoteHeader + '\n' + quoteText + '\n\n' + (text || ''); diff --git a/wwwroot/js/modals.js b/wwwroot/js/modals.js index 569f83a..88c1138 100644 --- a/wwwroot/js/modals.js +++ b/wwwroot/js/modals.js @@ -1,3 +1,43 @@ +// ── Modal Helper ── + +function _createModal(titleText, extraClasses) { + const overlay = document.createElement('div'); + overlay.className = 'sp-overlay' + (extraClasses && extraClasses.overlay ? ' ' + extraClasses.overlay : ''); + + const modal = document.createElement('div'); + modal.className = 'sp-modal' + (extraClasses && extraClasses.modal ? ' ' + extraClasses.modal : ''); + + const header = document.createElement('div'); + header.className = 'sp-header'; + const title = document.createElement('span'); + title.textContent = titleText; + const closeBtn = document.createElement('button'); + closeBtn.innerHTML = ''; + closeBtn.className = 'sp-close icon-btn'; + header.appendChild(title); + header.appendChild(closeBtn); + + const body = document.createElement('div'); + body.className = 'sp-body' + (extraClasses && extraClasses.body ? ' ' + extraClasses.body : ''); + + modal.appendChild(header); + modal.appendChild(body); + overlay.appendChild(modal); + document.body.appendChild(overlay); + + const close = () => overlay.remove(); + closeBtn.addEventListener('click', close); + overlay.addEventListener('click', (e) => { + if (e.target === overlay) close(); + }); + const onKey = (e) => { + if (e.key === 'Escape') { close(); document.removeEventListener('keydown', onKey); } + }; + document.addEventListener('keydown', onKey); + + return { overlay, modal, body, close }; +} + // ── System-Prompt Modal ── function _renderSystemPromptBlocks(container, blocks) { @@ -24,47 +64,9 @@ function _renderSystemPromptBlocks(container, blocks) { } async function showSystemPromptModal(sessionId) { - // Overlay - const overlay = document.createElement('div'); - overlay.className = 'sp-overlay'; - - // Modal - const modal = document.createElement('div'); - modal.className = 'sp-modal'; - - // Header - const header = document.createElement('div'); - header.className = 'sp-header'; - const title = document.createElement('span'); - title.textContent = 'System-Prompt'; - const closeBtn = document.createElement('button'); - closeBtn.innerHTML = ''; - closeBtn.className = 'sp-close icon-btn'; - header.appendChild(title); - header.appendChild(closeBtn); - - // Body - const body = document.createElement('div'); - body.className = 'sp-body markdown'; + const { body } = _createModal('System-Prompt', { body: 'markdown' }); body.innerHTML = '
Laden...
'; - modal.appendChild(header); - modal.appendChild(body); - overlay.appendChild(modal); - document.body.appendChild(overlay); - - // Schließen - const close = () => overlay.remove(); - closeBtn.addEventListener('click', close); - overlay.addEventListener('click', (e) => { - if (e.target === overlay) close(); - }); - const onKey = (e) => { - if (e.key === 'Escape') { close(); document.removeEventListener('keydown', onKey); } - }; - document.addEventListener('keydown', onKey); - - // Laden try { const blocks = await apiGetSystemPromptBlocks(sessionId, typeof _planModeActive !== 'undefined' && _planModeActive); body.innerHTML = ''; @@ -77,30 +79,11 @@ async function showSystemPromptModal(sessionId) { // ── Command Output Modal ── function showCommandOutputModal(name, command, result) { - const overlay = document.createElement('div'); - overlay.className = 'sp-overlay'; - - const modal = document.createElement('div'); - modal.className = 'sp-modal cmd-output-modal'; - - // Header - const header = document.createElement('div'); - header.className = 'sp-header'; - const title = document.createElement('span'); - title.textContent = name; - const closeBtn = document.createElement('button'); - closeBtn.innerHTML = ''; - closeBtn.className = 'sp-close icon-btn'; - header.appendChild(title); - header.appendChild(closeBtn); - - // Body - const body = document.createElement('div'); - body.className = 'sp-body'; + const { body } = _createModal(name, { modal: 'cmd-output-modal' }); const exitCode = result.exitCode != null ? result.exitCode : '?'; const isError = exitCode !== 0; - const statusText = result.status === 'timeout' ? 'Timeout' : (isError ? `Exit ${exitCode}` : `Exit ${exitCode}`); + const statusText = result.status === 'timeout' ? 'Timeout' : `Exit ${exitCode}`; const meta = document.createElement('div'); meta.className = 'cmd-output-meta'; @@ -111,72 +94,82 @@ function showCommandOutputModal(name, command, result) { output.className = 'cmd-output-content'; output.textContent = result.output || '(kein Output)'; body.appendChild(output); - - modal.appendChild(header); - modal.appendChild(body); - overlay.appendChild(modal); - document.body.appendChild(overlay); - - // Schließen - const close = () => overlay.remove(); - closeBtn.addEventListener('click', close); - overlay.addEventListener('click', (e) => { - if (e.target === overlay) close(); - }); - const onKey = (e) => { - if (e.key === 'Escape') { close(); document.removeEventListener('keydown', onKey); } - }; - document.addEventListener('keydown', onKey); } // ── Krea Image Preview Modal ── -function showKreaImageModal(title, fullUrl) { - var overlay = document.createElement('div'); - overlay.className = 'sp-overlay krea-overlay'; - - var modal = document.createElement('div'); - modal.className = 'sp-modal krea-modal'; - - // Header - var header = document.createElement('div'); - header.className = 'sp-header'; - var titleEl = document.createElement('span'); - titleEl.textContent = title; - var closeBtn = document.createElement('button'); - closeBtn.innerHTML = ''; - closeBtn.className = 'sp-close icon-btn'; - header.appendChild(titleEl); - header.appendChild(closeBtn); - - // Body - var body = document.createElement('div'); - body.className = 'sp-body krea-modal-body'; +function showKreaImageModal(titleText, fullUrl) { + const { body } = _createModal(titleText, { overlay: 'krea-overlay', modal: 'krea-modal', body: 'krea-modal-body' }); var img = document.createElement('img'); img.src = fullUrl; img.className = 'krea-modal-image'; - img.alt = title; + img.alt = titleText; img.addEventListener('error', function() { body.innerHTML = '
Bild konnte nicht geladen werden
'; }); body.appendChild(img); +} + +// ── Picker Modal Helper ── + +function _openPickerModal(title, items, currentId, onSelect, options) { + options = options || {}; + const noneLabel = options.noneLabel || 'Keine Auswahl'; + const noneIcon = options.noneIcon || 'fa-ban'; + + const overlay = document.createElement('div'); + overlay.className = 'agent-picker-overlay'; + + const modal = document.createElement('div'); + modal.className = 'agent-picker-modal'; + + const header = document.createElement('div'); + header.className = 'agent-picker-header'; + header.innerHTML = `${esc(title)}`; modal.appendChild(header); + + const body = document.createElement('div'); + body.className = 'agent-picker-body'; + + // "Keine" Option + const noneCard = document.createElement('div'); + noneCard.className = 'card agent-picker-item' + (!currentId ? ' selected' : ''); + noneCard.innerHTML = ` +
+
+
+ ${esc(noneLabel)} +
+
+ `; + noneCard.addEventListener('click', () => { + onSelect(null); + overlay.remove(); + }); + body.appendChild(noneCard); + + // Items + for (const item of items) { + const cardEl = options.buildCard(item); + cardEl.classList.add('agent-picker-item'); + if (item._pickerId === currentId) cardEl.classList.add('selected'); + cardEl.addEventListener('click', () => { + onSelect(item._pickerId); + overlay.remove(); + }); + body.appendChild(cardEl); + } + modal.appendChild(body); overlay.appendChild(modal); document.body.appendChild(overlay); - // Schließen - var close = function() { overlay.remove(); }; - closeBtn.addEventListener('click', close); - overlay.addEventListener('click', function(e) { - if (e.target === overlay) close(); + header.querySelector('.agent-picker-close').addEventListener('click', () => overlay.remove()); + overlay.addEventListener('click', (e) => { + if (e.target === overlay) overlay.remove(); }); - var onKey = function(e) { - if (e.key === 'Escape') { close(); document.removeEventListener('keydown', onKey); } - }; - document.addEventListener('keydown', onKey); } // Event-Delegation für Krea-Thumbnails im Chat diff --git a/wwwroot/js/pages/admin-page.js b/wwwroot/js/pages/admin-page.js index 8ab0fae..cf0b0fa 100644 --- a/wwwroot/js/pages/admin-page.js +++ b/wwwroot/js/pages/admin-page.js @@ -22,26 +22,6 @@ registerPage('admin', { var defaultPathBrowse = document.getElementById('adminDefaultPathBrowse'); var isAdminCheckbox = document.getElementById('adminIsAdmin'); var saveBtn = document.getElementById('adminFormSaveBtn'); - var formMessage = document.getElementById('adminFormMessage'); - var pageMessage = document.getElementById('adminMessage'); - - // ── Hilfsfunktionen ── - - function showFormMessage(text, isError) { - formMessage.textContent = text; - formMessage.className = 'profile-message ' + (isError ? 'profile-message-error' : 'profile-message-success'); - formMessage.hidden = false; - if (!isError) { - setTimeout(function() { formMessage.hidden = true; }, 3000); - } - } - - function showPageMessage(text, isError) { - pageMessage.textContent = text; - pageMessage.className = 'profile-message ' + (isError ? 'profile-message-error' : 'profile-message-success'); - pageMessage.hidden = false; - setTimeout(function() { pageMessage.hidden = true; }, 4000); - } function renderUsers() { list.innerHTML = ''; @@ -111,7 +91,7 @@ registerPage('admin', { users = await apiAdminGetUsers(); renderUsers(); } catch (err) { - showPageMessage(err.message, true); + showToast(err.message, 'error'); } } @@ -128,7 +108,6 @@ registerPage('admin', { defaultPathInput.value = user.defaultPath || ''; isAdminCheckbox.checked = user.isAdmin; saveBtn.textContent = 'Speichern'; - formMessage.hidden = true; userForm.hidden = false; usernameInput.focus(); } @@ -142,7 +121,7 @@ registerPage('admin', { await apiAdminDeleteUser(user.userId); await loadUsers(); } catch (err) { - showPageMessage(err.message, true); + showToast(err.message, 'error'); } } @@ -152,7 +131,7 @@ registerPage('admin', { var link = window.location.origin + '/api/auth/autologin?token=' + result.token; showLoginLinkModal(user, link); } catch (err) { - showPageMessage(err.message, true); + showToast(err.message, 'error'); } } @@ -249,14 +228,12 @@ registerPage('admin', { defaultPathInput.value = ''; isAdminCheckbox.checked = false; saveBtn.textContent = 'Erstellen'; - formMessage.hidden = true; userForm.hidden = false; usernameInput.focus(); }); // Speichern saveBtn.addEventListener('click', async function() { - formMessage.hidden = true; saveBtn.disabled = true; try { @@ -276,14 +253,14 @@ registerPage('admin', { await apiAdminResetPassword(editingUserId, pw); } - showFormMessage('Benutzer aktualisiert', false); + showToast('Benutzer aktualisiert'); } else { // Create var username = usernameInput.value.trim(); var password = passwordInput.value; if (!username || !password) { - showFormMessage('Benutzername und Passwort erforderlich', true); + showToast('Benutzername und Passwort erforderlich', 'error'); return; } @@ -295,13 +272,13 @@ registerPage('admin', { isAdmin: isAdminCheckbox.checked }); - showFormMessage('Benutzer erstellt', false); + showToast('Benutzer erstellt'); } userForm.hidden = true; await loadUsers(); } catch (err) { - showFormMessage(err.message, true); + showToast(err.message, 'error'); } finally { saveBtn.disabled = false; } diff --git a/wwwroot/js/pages/agent-page.js b/wwwroot/js/pages/agent-page.js index 1d8a343..468e2a9 100644 --- a/wwwroot/js/pages/agent-page.js +++ b/wwwroot/js/pages/agent-page.js @@ -47,18 +47,10 @@ registerPage('agent', { // EasyMDE für Task-Instruction const taskEl = document.getElementById('agentTaskInstruction'); - _agentTaskMde = new EasyMDE({ - element: taskEl, + _agentTaskMde = createMDE(taskEl, { toolbar: ['bold', 'italic', 'heading', '|', 'quote', 'unordered-list', 'ordered-list', '|', 'link', 'code', '|', 'preview', 'guide'], - status: false, - autofocus: false, - spellChecker: false, - autoDownloadFontAwesome: false, - placeholder: 'Spezifische Anweisungen für diesen Agent (Markdown, optional)', - minHeight: '120px' + placeholder: 'Spezifische Anweisungen für diesen Agent (Markdown, optional)' }); - _agentTaskMde.codemirror.setOption('theme', 'default'); - _agentTaskMde.codemirror.getWrapperElement().closest('.EasyMDEContainer').classList.add('fe-mde-wrap'); if (agent) { title.textContent = agent.name; diff --git a/wwwroot/js/pages/cronjob-page.js b/wwwroot/js/pages/cronjob-page.js index 3191005..ec3e76b 100644 --- a/wwwroot/js/pages/cronjob-page.js +++ b/wwwroot/js/pages/cronjob-page.js @@ -156,68 +156,15 @@ registerPage('cronjob', { function openSessionPickerModal(sessions, agentMap, currentId, onSelect, options) { options = options || {}; const filterWithAgent = options.filterWithAgent || false; - const title = options.title || 'Session auswählen'; - const noneLabel = options.noneLabel || 'Keine Session'; - const noneIcon = options.noneIcon || 'fa-ban'; - - // Overlay - const overlay = document.createElement('div'); - overlay.className = 'agent-picker-overlay'; - - const modal = document.createElement('div'); - modal.className = 'agent-picker-modal'; - - // Header - const header = document.createElement('div'); - header.className = 'agent-picker-header'; - header.innerHTML = `${esc(title)}`; - modal.appendChild(header); - - // Body - const body = document.createElement('div'); - body.className = 'agent-picker-body'; - - // "Keine Session" Option - const noneCard = document.createElement('div'); - noneCard.className = 'card agent-picker-item' + (!currentId ? ' selected' : ''); - noneCard.innerHTML = ` -
-
-
- ${esc(noneLabel)} -
-
- `; - noneCard.addEventListener('click', () => { - onSelect(null); - overlay.remove(); + const filtered = filterWithAgent ? sessions.filter(s => s.agentId) : sessions; + const items = filtered.map(s => { + s._pickerId = s.sessionId; + return s; }); - body.appendChild(noneCard); - - // Session Cards via Helper - for (const s of sessions) { - // Filter: nur Sessions mit Agent, wenn gewünscht - if (filterWithAgent && !s.agentId) continue; - - const agent = s.agentId ? agentMap[s.agentId] : null; - const item = buildSessionCard(s, agent); - item.classList.add('agent-picker-item'); - if (s.sessionId === currentId) item.classList.add('selected'); - item.addEventListener('click', () => { - onSelect(s.sessionId); - overlay.remove(); - }); - body.appendChild(item); - } - - modal.appendChild(body); - overlay.appendChild(modal); - document.body.appendChild(overlay); - - // Schließen - header.querySelector('.agent-picker-close').addEventListener('click', () => overlay.remove()); - overlay.addEventListener('click', (e) => { - if (e.target === overlay) overlay.remove(); + _openPickerModal(options.title || 'Session auswählen', items, currentId, onSelect, { + noneLabel: options.noneLabel || 'Keine Session', + noneIcon: options.noneIcon || 'fa-ban', + buildCard: (s) => buildSessionCard(s, s.agentId ? agentMap[s.agentId] : null) }); } diff --git a/wwwroot/js/pages/dashboard.js b/wwwroot/js/pages/dashboard.js index 5c4a753..9ce3210 100644 --- a/wwwroot/js/pages/dashboard.js +++ b/wwwroot/js/pages/dashboard.js @@ -107,23 +107,7 @@ registerPage('dashboard', { const menuDropdown = card.querySelector('.card-menu-dropdown'); menuTrigger.addEventListener('click', (e) => { e.stopPropagation(); - closeAllCardMenus(); - // Position fixed dropdown below trigger, aligned to the left - const rect = menuTrigger.getBoundingClientRect(); - menuDropdown.hidden = false; - const dropRect = menuDropdown.getBoundingClientRect(); - // Position: below trigger, right edge aligned with trigger's right edge - menuDropdown.style.top = `${rect.bottom + 4}px`; - menuDropdown.style.left = `${rect.right - dropRect.width}px`; - // Adjust if off-screen bottom - if (rect.bottom + 4 + dropRect.height > window.innerHeight) { - menuDropdown.style.top = `${rect.top - dropRect.height - 4}px`; - } - // Safety: if off-screen left - const finalRect = menuDropdown.getBoundingClientRect(); - if (finalRect.left < 0) { - menuDropdown.style.left = '8px'; - } + _openCardMenu(menuTrigger, menuDropdown); }); chatBtn.addEventListener('click', (e) => { e.stopPropagation(); @@ -166,23 +150,7 @@ registerPage('dashboard', { const menuDropdown = card.querySelector('.card-menu-dropdown'); menuTrigger.addEventListener('click', (e) => { e.stopPropagation(); - closeAllCardMenus(); - // Position fixed dropdown below trigger, aligned to the left - const rect = menuTrigger.getBoundingClientRect(); - menuDropdown.hidden = false; - const dropRect = menuDropdown.getBoundingClientRect(); - // Position: below trigger, right edge aligned with trigger's right edge - menuDropdown.style.top = `${rect.bottom + 4}px`; - menuDropdown.style.left = `${rect.right - dropRect.width}px`; - // Adjust if off-screen bottom - if (rect.bottom + 4 + dropRect.height > window.innerHeight) { - menuDropdown.style.top = `${rect.top - dropRect.height - 4}px`; - } - // Safety: if off-screen left - const finalRect = menuDropdown.getBoundingClientRect(); - if (finalRect.left < 0) { - menuDropdown.style.left = '8px'; - } + _openCardMenu(menuTrigger, menuDropdown); }); menuDropdown.querySelector('[data-action="files"]').addEventListener('click', (e) => { e.stopPropagation(); @@ -243,17 +211,7 @@ registerPage('dashboard', { const menuDropdown = card.querySelector('.card-menu-dropdown'); menuTrigger.addEventListener('click', (e) => { e.stopPropagation(); - closeAllCardMenus(); - const rect = menuTrigger.getBoundingClientRect(); - menuDropdown.hidden = false; - const dropRect = menuDropdown.getBoundingClientRect(); - menuDropdown.style.top = `${rect.bottom + 4}px`; - menuDropdown.style.left = `${rect.right - dropRect.width}px`; - if (rect.bottom + 4 + dropRect.height > window.innerHeight) { - menuDropdown.style.top = `${rect.top - dropRect.height - 4}px`; - } - const finalRect = menuDropdown.getBoundingClientRect(); - if (finalRect.left < 0) menuDropdown.style.left = '8px'; + _openCardMenu(menuTrigger, menuDropdown); }); menuDropdown.querySelector('[data-action="edit"]').addEventListener('click', (e) => { @@ -327,17 +285,7 @@ registerPage('dashboard', { const menuDropdown = card.querySelector('.card-menu-dropdown'); menuTrigger.addEventListener('click', (e) => { e.stopPropagation(); - closeAllCardMenus(); - const rect = menuTrigger.getBoundingClientRect(); - menuDropdown.hidden = false; - const dropRect = menuDropdown.getBoundingClientRect(); - menuDropdown.style.top = `${rect.bottom + 4}px`; - menuDropdown.style.left = `${rect.right - dropRect.width}px`; - if (rect.bottom + 4 + dropRect.height > window.innerHeight) { - menuDropdown.style.top = `${rect.top - dropRect.height - 4}px`; - } - const finalRect = menuDropdown.getBoundingClientRect(); - if (finalRect.left < 0) menuDropdown.style.left = '8px'; + _openCardMenu(menuTrigger, menuDropdown); }); menuDropdown.querySelector('[data-action="copy"]').addEventListener('click', async (e) => { @@ -378,6 +326,20 @@ function closeAllCardMenus() { document.querySelectorAll('.card-menu-dropdown').forEach(d => d.hidden = true); } +function _openCardMenu(trigger, dropdown) { + closeAllCardMenus(); + const rect = trigger.getBoundingClientRect(); + dropdown.hidden = false; + const dropRect = dropdown.getBoundingClientRect(); + dropdown.style.top = `${rect.bottom + 4}px`; + dropdown.style.left = `${rect.right - dropRect.width}px`; + if (rect.bottom + 4 + dropRect.height > window.innerHeight) { + dropdown.style.top = `${rect.top - dropRect.height - 4}px`; + } + const finalRect = dropdown.getBoundingClientRect(); + if (finalRect.left < 0) dropdown.style.left = '8px'; +} + function formatDate(iso) { if (!iso) return ''; return new Date(iso).toLocaleString('de-AT', { diff --git a/wwwroot/js/pages/profile-page.js b/wwwroot/js/pages/profile-page.js index 5bf2912..5d2689b 100644 --- a/wwwroot/js/pages/profile-page.js +++ b/wwwroot/js/pages/profile-page.js @@ -12,28 +12,18 @@ registerPage('profile', { var defaultPathInput = document.getElementById('profileDefaultPath'); var personaInput = document.getElementById('profilePersona'); var saveBtn = document.getElementById('profileSaveBtn'); - var messageDiv = document.getElementById('profileMessage'); // EasyMDE für Persona - _profileMde = new EasyMDE({ - element: personaInput, + _profileMde = createMDE(personaInput, { initialValue: profile.persona || '', - toolbar: ['bold', 'italic', 'heading', '|', 'unordered-list', 'ordered-list', '|', 'link', 'code', '|', 'preview'], - status: false, - spellChecker: false, - autoDownloadFontAwesome: false, minHeight: '150px', - placeholder: 'Beschreibe dich selbst – Name, Rolle, Präferenzen, Kommunikationsstil...', - previewClass: ['editor-preview', 'markdown'] + placeholder: 'Beschreibe dich selbst – Name, Rolle, Präferenzen, Kommunikationsstil...' }); - _profileMde.codemirror.setOption('theme', 'default'); - _profileMde.codemirror.getWrapperElement().closest('.EasyMDEContainer').classList.add('fe-mde-wrap'); var currentPwInput = document.getElementById('profileCurrentPassword'); var newPwInput = document.getElementById('profileNewPassword'); var confirmPwInput = document.getElementById('profileConfirmPassword'); var passwordBtn = document.getElementById('profilePasswordBtn'); - var passwordMessageDiv = document.getElementById('profilePasswordMessage'); var logoutBtn = document.getElementById('profileLogoutBtn'); @@ -46,7 +36,6 @@ registerPage('profile', { saveBtn.addEventListener('click', async function() { saveBtn.disabled = true; saveBtn.textContent = 'Speichern...'; - messageDiv.hidden = true; try { var updated = await apiUpdateProfile({ @@ -59,14 +48,9 @@ registerPage('profile', { currentUser.displayName = updated.displayName; } - messageDiv.textContent = 'Profil gespeichert'; - messageDiv.className = 'profile-message profile-message-success'; - messageDiv.hidden = false; - setTimeout(function() { messageDiv.hidden = true; }, 3000); + showToast('Profil gespeichert'); } catch (err) { - messageDiv.textContent = err.message; - messageDiv.className = 'profile-message profile-message-error'; - messageDiv.hidden = false; + showToast(err.message, 'error'); } finally { saveBtn.disabled = false; saveBtn.textContent = 'Speichern'; @@ -75,23 +59,17 @@ registerPage('profile', { // Passwort ändern passwordBtn.addEventListener('click', async function() { - passwordMessageDiv.hidden = true; - var currentPw = currentPwInput.value; var newPw = newPwInput.value; var confirmPw = confirmPwInput.value; if (!currentPw || !newPw) { - passwordMessageDiv.textContent = 'Alle Felder ausfüllen'; - passwordMessageDiv.className = 'profile-message profile-message-error'; - passwordMessageDiv.hidden = false; + showToast('Alle Felder ausfüllen', 'error'); return; } if (newPw !== confirmPw) { - passwordMessageDiv.textContent = 'Neue Passwörter stimmen nicht überein'; - passwordMessageDiv.className = 'profile-message profile-message-error'; - passwordMessageDiv.hidden = false; + showToast('Neue Passwörter stimmen nicht überein', 'error'); return; } @@ -105,14 +83,9 @@ registerPage('profile', { newPwInput.value = ''; confirmPwInput.value = ''; - passwordMessageDiv.textContent = 'Passwort geändert'; - passwordMessageDiv.className = 'profile-message profile-message-success'; - passwordMessageDiv.hidden = false; - setTimeout(function() { passwordMessageDiv.hidden = true; }, 3000); + showToast('Passwort geändert'); } catch (err) { - passwordMessageDiv.textContent = err.message; - passwordMessageDiv.className = 'profile-message profile-message-error'; - passwordMessageDiv.hidden = false; + showToast(err.message, 'error'); } finally { passwordBtn.disabled = false; passwordBtn.textContent = 'Passwort ändern'; diff --git a/wwwroot/js/pages/session-page.js b/wwwroot/js/pages/session-page.js index 86ad8af..ca7bb6d 100644 --- a/wwwroot/js/pages/session-page.js +++ b/wwwroot/js/pages/session-page.js @@ -61,19 +61,10 @@ registerPage('session', { } // EasyMDE für Kontext - _sessionContextMde = new EasyMDE({ - element: contextEl, + _sessionContextMde = createMDE(contextEl, { initialValue: (session && session.context) || '', - toolbar: ['bold', 'italic', 'heading', '|', 'unordered-list', 'ordered-list', '|', 'link', 'code', '|', 'preview'], - status: false, - spellChecker: false, - autoDownloadFontAwesome: false, - minHeight: '120px', - placeholder: 'Zusätzlicher Kontext für den System-Prompt (optional)', - previewClass: ['editor-preview', 'markdown'] + placeholder: 'Zusätzlicher Kontext für den System-Prompt (optional)' }); - _sessionContextMde.codemirror.setOption('theme', 'default'); - _sessionContextMde.codemirror.getWrapperElement().closest('.EasyMDEContainer').classList.add('fe-mde-wrap'); // Befehl hinzufügen addCommandBtn.addEventListener('click', () => createCommandRow(commandsList)); @@ -277,61 +268,13 @@ registerPage('session', { }); function openAgentPickerModal(agents, agentSkillsMap, currentId, onSelect) { - // Overlay - const overlay = document.createElement('div'); - overlay.className = 'agent-picker-overlay'; - - const modal = document.createElement('div'); - modal.className = 'agent-picker-modal'; - - // Header - const header = document.createElement('div'); - header.className = 'agent-picker-header'; - header.innerHTML = 'Agent auswählen'; - modal.appendChild(header); - - // Body - const body = document.createElement('div'); - body.className = 'agent-picker-body'; - - // "Kein Agent" Option - const noneCard = document.createElement('div'); - noneCard.className = 'card agent-picker-item' + (!currentId ? ' selected' : ''); - noneCard.innerHTML = ` -
-
-
- Kein Agent -
-
- `; - noneCard.addEventListener('click', () => { - onSelect(null); - overlay.remove(); + const items = agents.map(a => { + a._pickerId = a.agentId; + return a; }); - body.appendChild(noneCard); - - // Agent Cards via Helper - for (const a of agents) { - const skills = agentSkillsMap[a.agentId] || []; - const item = buildAgentCard(a, skills); - item.classList.add('agent-picker-item'); - if (a.agentId === currentId) item.classList.add('selected'); - item.addEventListener('click', () => { - onSelect(a.agentId); - overlay.remove(); - }); - body.appendChild(item); - } - - modal.appendChild(body); - overlay.appendChild(modal); - document.body.appendChild(overlay); - - // Schließen - header.querySelector('.agent-picker-close').addEventListener('click', () => overlay.remove()); - overlay.addEventListener('click', (e) => { - if (e.target === overlay) overlay.remove(); + _openPickerModal('Agent auswählen', items, currentId, onSelect, { + noneLabel: 'Kein Agent', + buildCard: (a) => buildAgentCard(a, agentSkillsMap[a.agentId] || []) }); } diff --git a/wwwroot/js/reply.js b/wwwroot/js/reply.js index b6d44dc..a3b7e9d 100644 --- a/wwwroot/js/reply.js +++ b/wwwroot/js/reply.js @@ -2,23 +2,6 @@ let pendingReply = null; // { index, role, text } - Zitierte Nachricht -function _formatReplyTimestamp(ts) { - if (!ts) return 'Jetzt'; - try { - const d = new Date(ts); - const now = new Date(); - const hh = d.getHours().toString().padStart(2, '0'); - const mm = d.getMinutes().toString().padStart(2, '0'); - // Gleiches Datum → nur Uhrzeit, sonst Datum + Uhrzeit - if (d.toDateString() === now.toDateString()) { - return hh + ':' + mm; - } - const dd = d.getDate().toString().padStart(2, '0'); - const mo = (d.getMonth() + 1).toString().padStart(2, '0'); - return dd + '.' + mo + '. ' + hh + ':' + mm; - } catch { return 'Jetzt'; } -} - function setReply(reply) { pendingReply = reply; renderReplyPreview(); @@ -41,7 +24,7 @@ function renderReplyPreview() { } const roleLabel = pendingReply.role === 'user' ? 'User' : 'Assistant'; - const timeLabel = _formatReplyTimestamp(pendingReply.timestamp); + const timeLabel = _formatTimestamp(pendingReply.timestamp) || 'Jetzt'; const preview = truncate(pendingReply.text, 150); container.innerHTML = `
diff --git a/wwwroot/js/ui-helpers.js b/wwwroot/js/ui-helpers.js index ae8c6b4..ee16cc8 100644 --- a/wwwroot/js/ui-helpers.js +++ b/wwwroot/js/ui-helpers.js @@ -68,6 +68,27 @@ function removeToast(toast) { toast.addEventListener('transitionend', function() { toast.remove(); }); } +// ── EasyMDE Helper ── + +function createMDE(element, options) { + options = options || {}; + const mde = new EasyMDE({ + element: element, + initialValue: options.initialValue || '', + toolbar: options.toolbar || ['bold', 'italic', 'heading', '|', 'unordered-list', 'ordered-list', '|', 'link', 'code', '|', 'preview'], + status: false, + spellChecker: false, + autoDownloadFontAwesome: false, + autofocus: options.autofocus || false, + minHeight: options.minHeight || '120px', + placeholder: options.placeholder || '', + previewClass: ['editor-preview', 'markdown'] + }); + mde.codemirror.setOption('theme', 'default'); + mde.codemirror.getWrapperElement().closest('.EasyMDEContainer').classList.add('fe-mde-wrap'); + return mde; +} + // ── Meta Bar ── function setMeta(text) {