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:
1 parent
92c20402eb
commit
e45498ac50
5 files changed
+176
-56
No files matched your search
+13
-7
@@ -297,9 +297,10 @@ label.skill-item {
|
||||
margin-left: auto;
|
||||
}
|
||||
|
||||
/* ── Agent Select Card (in Session-Formular) ── */
|
||||
/* ── Select Cards (Agent / Session in Formularen) ── */
|
||||
|
||||
.agent-select-card {
|
||||
.agent-select-card,
|
||||
.session-select-card {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: var(--box-spacing);
|
||||
@@ -311,30 +312,35 @@ label.skill-item {
|
||||
transition: border-color var(--transition-duration);
|
||||
}
|
||||
|
||||
.agent-select-card:hover {
|
||||
.agent-select-card:hover,
|
||||
.session-select-card:hover {
|
||||
border-color: var(--primary);
|
||||
}
|
||||
|
||||
.agent-select-card .card-image {
|
||||
.agent-select-card .card-image,
|
||||
.session-select-card .card-image {
|
||||
flex: 0 0 4em;
|
||||
height: 4em;
|
||||
}
|
||||
|
||||
.agent-select-empty {
|
||||
.agent-select-empty,
|
||||
.session-select-empty {
|
||||
opacity: var(--semi-transparent);
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: var(--badge-spacing);
|
||||
}
|
||||
|
||||
.agent-select-change {
|
||||
.agent-select-change,
|
||||
.session-select-change {
|
||||
margin-left: auto;
|
||||
opacity: var(--semi-transparent);
|
||||
flex-shrink: 0;
|
||||
transition: opacity var(--transition-duration);
|
||||
}
|
||||
|
||||
.agent-select-card:hover .agent-select-change {
|
||||
.agent-select-card:hover .agent-select-change,
|
||||
.session-select-card:hover .session-select-change {
|
||||
opacity: 1;
|
||||
color: var(--primary);
|
||||
}
|
||||
|
||||
+10
-4
@@ -246,8 +246,11 @@
|
||||
<input type="text" id="cronjobName" placeholder="z.B. Täglicher Report">
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label for="cronjobSession">Session</label>
|
||||
<select id="cronjobSession"></select>
|
||||
<label>Session</label>
|
||||
<div id="cronjobSessionCard" class="session-select-card">
|
||||
<div class="session-select-empty"><i class="fa fa-comment-o"></i> Keine Session ausgewählt</div>
|
||||
</div>
|
||||
<input type="hidden" id="cronjobSession" value="">
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label for="cronjobCron">Cron-Expression</label>
|
||||
@@ -259,8 +262,11 @@
|
||||
<textarea id="cronjobPrompt" rows="6" placeholder="Die Nachricht, die an den Agent gesendet wird..."></textarea>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label for="cronjobNotifySession">Benachrichtigungs-Session <small>(optional)</small></label>
|
||||
<select id="cronjobNotifySession"></select>
|
||||
<label>Benachrichtigungs-Session <small>(optional)</small></label>
|
||||
<div id="cronjobNotifySessionCard" class="session-select-card">
|
||||
<div class="session-select-empty"><i class="fa fa-bell-o"></i> Keine (Antwort bleibt in Arbeits-Session)</div>
|
||||
</div>
|
||||
<input type="hidden" id="cronjobNotifySession" value="">
|
||||
<small class="form-hint">Wenn gesetzt, wird eine Zusammenfassung in diese Session geschrieben (z.B. eine mit Telegram).</small>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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)
|
||||
|
||||
Reference in new issue
Block a user