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 = `
-
-
- `;
- 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) {