// ── DOM Helpers ── const $ = (sel) => document.querySelector(sel); const chatEl = () => $('#chat'); const metaEl = () => $('#meta'); function esc(s) { const d = document.createElement('div'); d.textContent = s; return d.innerHTML; } function truncate(s, max) { return s && s.length > max ? s.substring(0, max) + '...' : (s || ''); } function scrollDown() { const chat = chatEl(); if (!chat) return; chat.scrollTop = chat.scrollHeight; // Nach Font-Swap kann sich die Höhe ändern → nochmal scrollen document.fonts.ready.then(() => { chat.scrollTop = chat.scrollHeight; }); } function prettyModelName(model) { if (!model) return '?'; model = model.split('claude-').join(''); if (model == 'sonnet') return 'Sonnet'; if (model == 'sonnet-4-5') return 'Sonnet 4.5'; if (model == 'sonnet-4-6') return 'Sonnet 4.6'; if (model == 'opus') return 'Opus'; if (model == 'opus-4-5') return 'Opus 4.5'; if (model == 'opus-4-6') return 'Opus 4.6'; if (model == 'haiku') return 'Haiku'; if (model == 'haiku-4-5') return 'Haiku 4.5'; return 'Unbekannt (' + model + ')'; } // ── Toast Notifications ── function showToast(message, type) { type = type || 'info'; var container = document.getElementById('toastContainer'); if (!container) { container = document.createElement('div'); container.id = 'toastContainer'; document.body.appendChild(container); } var toast = document.createElement('div'); toast.className = 'toast toast-' + type; var icon = type === 'success' ? 'fa-check-circle' : type === 'error' ? 'fa-times-circle' : 'fa-info-circle'; toast.innerHTML = ' ' + (message || ''); container.appendChild(toast); // Einblenden requestAnimationFrame(function() { toast.classList.add('toast-visible'); }); // Automatisch ausblenden var timeout = setTimeout(function() { removeToast(toast); }, 4000); toast.addEventListener('click', function() { clearTimeout(timeout); removeToast(toast); }); } function removeToast(toast) { toast.classList.remove('toast-visible'); 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', maxHeight: options.maxHeight || undefined, placeholder: options.placeholder || '', previewClass: ['editor-preview', 'markdown'] }); mde.codemirror.setOption('theme', 'default'); mde.codemirror.getWrapperElement().closest('.EasyMDEContainer').classList.add('fe-mde-wrap'); return mde; } // ── Card Preview (for Picker-Select-Cards) ── /** * Renders a card preview into a select-card element. * Used by agent picker (session-page) and session picker (cronjob-page). * @param {HTMLElement} cardEl - The container element * @param {HTMLElement|null} innerCard - Result of buildAgentCard/buildSessionCard, or null for empty state * @param {string} emptyIcon - FA icon class for empty state * @param {string} emptyText - Text for empty state * @param {string} changeClass - CSS class for the change indicator (e.g. 'agent-select-change', 'session-select-change') */ function _renderCardPreview(cardEl, innerCard, emptyIcon, emptyText, changeClass) { cardEl.innerHTML = ''; if (!innerCard) { cardEl.innerHTML = '