Session-Cards: buildSessionCard() Helper + Picker-Modal für Cronjob-Formular

- buildSessionCard(session, agent) Helper in ui.js (analog zu buildAgentCard)
  Erzeugt .card mit Avatar, Name, Badges (Agent, Kontext, Telegram) und Meta (Nachrichten, Archiv, Workspace)
- Dashboard refactored: Session-Cards nutzen buildSessionCard() statt inline HTML
- Cronjob-Formular: <select> für Session und Benachrichtigungs-Session durch
  klickbare Session-Cards + Picker-Modal ersetzt
- openSessionPickerModal() mit filterWithAgent-Option (Session-Dropdown nur
  Sessions mit Agent, Notify-Dropdown alle Sessions)
- CSS: .session-select-card und .session-select-change Klassen analog zu agent-*
This commit is contained in:
Meleki committed 2026-03-06 14:05:32 +01:00
1 parent 92c20402eb
commit e45498ac50
5 files changed
+176 -56

No files matched your search

+108 -18
View File
@@ -6,38 +6,58 @@ registerPage('cronjob', {
const title = document.getElementById('cronjobPageTitle');
const nameInput = document.getElementById('cronjobName');
const sessionSelect = document.getElementById('cronjobSession');
const sessionCard = document.getElementById('cronjobSessionCard');
const cronInput = document.getElementById('cronjobCron');
const cronPreview = document.getElementById('cronjobCronPreview');
const promptInput = document.getElementById('cronjobPrompt');
const notifySelect = document.getElementById('cronjobNotifySession');
const notifyCard = document.getElementById('cronjobNotifySessionCard');
const enabledCheckbox = document.getElementById('cronjobEnabled');
const statusBox = document.getElementById('cronjobStatusBox');
const saveBtn = document.getElementById('cronjobSaveBtn');
const runBtn = document.getElementById('cronjobRunBtn');
const deleteBtn = document.getElementById('cronjobDeleteBtn');
// Sessions laden für Dropdown
// Sessions + Agents laden
const sessions = await apiGetSessions();
sessionSelect.innerHTML = '<option value="">– Session wählen –</option>';
notifySelect.innerHTML = '<option value="">– Keine (Antwort bleibt in Arbeits-Session) –</option>';
for (const s of sessions) {
const opt = document.createElement('option');
opt.value = s.sessionId;
opt.textContent = s.name || 'Unbenannt';
if (!s.agentId) {
opt.textContent += ' (kein Agent)';
opt.disabled = true;
}
sessionSelect.appendChild(opt);
const agents = await apiGetAgents();
const agentMap = {};
for (const a of agents) agentMap[a.agentId] = a;
const sessionMap = {};
for (const s of sessions) sessionMap[s.sessionId] = s;
// Notify-Dropdown: alle Sessions (auch ohne Agent), Telegram-Sessions markieren
const nOpt = document.createElement('option');
nOpt.value = s.sessionId;
nOpt.textContent = s.name || 'Unbenannt';
if (s.telegramChatId) nOpt.textContent += ' 📱';
notifySelect.appendChild(nOpt);
// Session-Card rendern
function renderSessionSelectCard(cardEl, session, emptyHtml) {
cardEl.innerHTML = '';
if (!session) {
cardEl.innerHTML = emptyHtml;
return;
}
const agent = session.agentId ? agentMap[session.agentId] : null;
const inner = buildSessionCard(session, agent);
while (inner.firstChild) cardEl.appendChild(inner.firstChild);
cardEl.insertAdjacentHTML('beforeend', '<span class="session-select-change"><i class="fa fa-pencil"></i></span>');
}
const sessionEmptyHtml = '<div class="session-select-empty"><i class="fa fa-comment-o"></i> Keine Session ausgewählt</div>';
const notifyEmptyHtml = '<div class="session-select-empty"><i class="fa fa-bell-o"></i> Keine (Antwort bleibt in Arbeits-Session)</div>';
// Session-Card click → Picker (nur Sessions mit Agent)
sessionCard.addEventListener('click', () => {
openSessionPickerModal(sessions, agentMap, sessionSelect.value, (selectedId) => {
sessionSelect.value = selectedId || '';
renderSessionSelectCard(sessionCard, selectedId ? sessionMap[selectedId] : null, sessionEmptyHtml);
}, { filterWithAgent: true, title: 'Session auswählen' });
});
// Notify-Session-Card click → Picker (alle Sessions)
notifyCard.addEventListener('click', () => {
openSessionPickerModal(sessions, agentMap, notifySelect.value, (selectedId) => {
notifySelect.value = selectedId || '';
renderSessionSelectCard(notifyCard, selectedId ? sessionMap[selectedId] : null, notifyEmptyHtml);
}, { filterWithAgent: false, title: 'Benachrichtigungs-Session auswählen', noneLabel: 'Keine Benachrichtigung', noneIcon: 'fa-bell-slash' });
});
// Cron-Expression Vorschau
cronInput.addEventListener('input', () => updateCronPreview(cronInput, cronPreview));
@@ -55,9 +75,11 @@ registerPage('cronjob', {
nameInput.value = job.name || '';
sessionSelect.value = job.sessionId || '';
renderSessionSelectCard(sessionCard, job.sessionId ? sessionMap[job.sessionId] : null, sessionEmptyHtml);
cronInput.value = job.cronExpression || '';
promptInput.value = job.prompt || '';
notifySelect.value = job.notifySessionId || '';
renderSessionSelectCard(notifyCard, job.notifySessionId ? sessionMap[job.notifySessionId] : null, notifyEmptyHtml);
enabledCheckbox.checked = job.isEnabled;
updateCronPreview(cronInput, cronPreview);
@@ -131,6 +153,74 @@ 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 = `<span>${escapeHtml(title)}</span><button class="icon-btn agent-picker-close"><i class="fa fa-times"></i></button>`;
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 = `
<div class="card-image" style="display:flex;align-items:center;justify-content:center"><i class="fa ${escapeHtml(noneIcon)}" style="font-size:2em;opacity:var(--semi-transparent)"></i></div>
<div class="card-body">
<div class="card-title-row">
<span class="card-title">${escapeHtml(noneLabel)}</span>
</div>
</div>
`;
noneCard.addEventListener('click', () => {
onSelect(null);
overlay.remove();
});
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();
});
}
function updateCronPreview(input, preview) {
const expr = input.value.trim();
if (!expr) {
+6 -27
View File
@@ -40,39 +40,18 @@ registerPage('dashboard', {
for (const s of sessions) {
const agent = s.agentId ? agentMap[s.agentId] : null;
const card = document.createElement('div');
card.className = 'card';
const msgLabel1 = s.messageCount ? `${s.messageCount} Nachrichten` : '';
const msgLabel2 = s.archiveMessageCount ? `${s.archiveMessageCount} Nachrichten archiviert` : '';
const workspaceLabel = s.workspace ? s.workspace : '';
const card = buildSessionCard(s, agent);
const sessionBadges = [];
if (agent) sessionBadges.push(`<span class="badge">${esc(agent.name)}</span>`);
if (s.context) sessionBadges.push(`<span class="badge badge-feature" title="${esc(s.context)}"><i class="fa fa-file-text-o"></i></span>`);
if (s.telegramConfigured) sessionBadges.push(`<span class="badge ${s.telegramLinked ? 'badge-feature' : 'badge-off'}" title="Telegram ${s.telegramLinked ? 'verbunden' : 'konfiguriert'}"><i class="fa fa-paper-plane"></i></span>`);
const scaleStyle = s.avatarScale && s.avatarScale !== 1 ? ` style="scale: ${s.avatarScale}"` : '';
card.innerHTML = `
<div class="card-image">${s.image ? `<img src="${s.image}" alt=""${scaleStyle}>` : ''}</div>
<div class="card-body">
<div class="card-title-row">
<span class="card-title">${esc(s.name || 'Unbenannt')}</span>
${sessionBadges.join('')}
<div class="card-menu">
// Menü zum title-row hinzufügen
const titleRow = card.querySelector('.card-title-row');
titleRow.insertAdjacentHTML('beforeend', `<div class="card-menu">
<button class="card-menu-trigger icon-btn"><i class="fa fa-ellipsis-v"></i></button>
<div class="card-menu-dropdown" hidden>
<button class="card-menu-item" data-action="edit"><i class="fa fa-pencil"></i> Bearbeiten</button>
<button class="card-menu-item" data-action="chat"><i class="fa fa-comment"></i> Chat öffnen</button>
</div>
</div>
</div>
<div class="card-meta">
${msgLabel1 ? `<span class="card-meta-item"><i class="fa fa-comment"></i> ${esc(msgLabel1)}</span>` : ''}
${msgLabel2 ? `<span class="card-meta-item"><i class="fa fa-archive"></i> ${esc(msgLabel2)}</span>` : ''}
${workspaceLabel ? `<span class="card-meta-item card-workspace" title="${esc(workspaceLabel)}"><i class="fa fa-folder"></i> ${esc(workspaceLabel)}</span>` : ''}
</div>
</div>
`;
</div>`);
// Chat deaktivieren wenn kein Agent gesetzt
const chatBtn = card.querySelector('[data-action="chat"]');
if (!agent) {
+39
View File
@@ -1,3 +1,42 @@
// ── Session Card Helper ──
// Erzeugt ein .card-Element für eine Session (wiederverwendbar für Dashboard + Picker).
// session: Session-Objekt, agent: zugehöriges Agent-Objekt (oder null)
// Gibt ein DOM-Element zurück (.card mit .card-image + .card-body inkl. Badges + Meta).
function buildSessionCard(session, agent) {
const card = document.createElement('div');
card.className = 'card';
const scaleStyle = session.avatarScale && session.avatarScale !== 1 ? ` style="scale: ${session.avatarScale}"` : '';
const imageHtml = session.image ? `<img src="${session.image}" alt=""${scaleStyle}>` : '';
// Badges
const badges = [];
if (agent) badges.push(`<span class="badge">${esc(agent.name)}</span>`);
if (session.context) badges.push(`<span class="badge badge-feature" title="${esc(session.context)}"><i class="fa fa-file-text-o"></i></span>`);
if (session.telegramConfigured) badges.push(`<span class="badge ${session.telegramLinked ? 'badge-feature' : 'badge-off'}" title="Telegram ${session.telegramLinked ? 'verbunden' : 'konfiguriert'}"><i class="fa fa-paper-plane"></i></span>`);
// Meta
const metaParts = [];
if (session.messageCount) metaParts.push(`<span class="card-meta-item"><i class="fa fa-comment"></i> ${esc(session.messageCount + ' Nachrichten')}</span>`);
if (session.archiveMessageCount) metaParts.push(`<span class="card-meta-item"><i class="fa fa-archive"></i> ${esc(session.archiveMessageCount + ' Nachrichten archiviert')}</span>`);
if (session.workspace) metaParts.push(`<span class="card-meta-item card-workspace" title="${esc(session.workspace)}"><i class="fa fa-folder"></i> ${esc(session.workspace)}</span>`);
card.innerHTML = `
<div class="card-image">${imageHtml}</div>
<div class="card-body">
<div class="card-title-row">
<span class="card-title">${esc(session.name || 'Unbenannt')}</span>
${badges.join('')}
</div>
${metaParts.length ? `<div class="card-meta">${metaParts.join('')}</div>` : ''}
</div>
`;
return card;
}
// ── Agent Card Helper ──
// Erzeugt ein .card-Element für einen Agent (wiederverwendbar für Dashboard + Picker).
// agent: Agent-Objekt, skills: Array von Skills (optional)