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
148 lines
4.5 KiB
JavaScript
148 lines
4.5 KiB
JavaScript
// ── 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 = '<i class="fa ' + icon + '"></i> ' + (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',
|
|
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) {
|
|
metaEl().textContent = text;
|
|
}
|
|
|
|
function setMetaHtml(html) {
|
|
metaEl().innerHTML = html;
|
|
}
|
|
|
|
// ── Older Messages Button ──
|
|
|
|
function showOlderMessagesButton(remainingCount) {
|
|
removeOlderMessagesButton();
|
|
const btn = document.createElement('button');
|
|
btn.className = 'archive-load-btn older-messages-btn';
|
|
btn.innerHTML = '<i class="fa fa-arrow-up"></i> ' + remainingCount + ' ältere Nachrichten laden';
|
|
btn.addEventListener('click', () => loadOlderMessages());
|
|
chatEl().prepend(btn);
|
|
}
|
|
|
|
function removeOlderMessagesButton() {
|
|
const btn = chatEl().querySelector('.older-messages-btn');
|
|
if (btn) btn.remove();
|
|
}
|
|
|
|
// ── Archive Button ──
|
|
|
|
function showArchiveLoadButton() {
|
|
const existing = chatEl().querySelector('.archive-load-btn:not(.older-messages-btn)');
|
|
if (existing) existing.remove();
|
|
|
|
const btn = document.createElement('button');
|
|
btn.className = 'archive-load-btn';
|
|
btn.innerHTML = '<i class="fa fa-archive"></i> Archivierte Nachrichten laden';
|
|
btn.addEventListener('click', loadLatestArchive);
|
|
|
|
// Immer ganz oben im Chat (aber nach dem older-messages-btn falls vorhanden)
|
|
const olderBtn = chatEl().querySelector('.older-messages-btn');
|
|
if (olderBtn) {
|
|
olderBtn.after(btn);
|
|
} else {
|
|
chatEl().prepend(btn);
|
|
}
|
|
}
|
|
|
|
function removeArchiveLoadButton() {
|
|
const btns = chatEl().querySelectorAll('.archive-load-btn:not(.older-messages-btn)');
|
|
btns.forEach(b => b.remove());
|
|
}
|
|
|
|
// ── Streamed Elements ──
|
|
|
|
function removeStreamedElements() {
|
|
chatEl().querySelectorAll('.streamed').forEach(el => el.remove());
|
|
}
|