// ── Session Edit Page ── let _sessionAvatarEditor = null; let _sessionContextMde = null; registerPage('session', { async init(params) { const sessionId = params.id; const isNew = !sessionId; let session = null; if (!isNew) { session = await apiGetSession(sessionId); if (!session) return navigateTo('dashboard'); } const titleEl = document.getElementById('sessionPageTitle'); const nameEl = document.getElementById('sessionName'); const descEl = document.getElementById('sessionDescription'); const contextEl = document.getElementById('sessionContext'); const workspaceEl = document.getElementById('sessionWorkspace'); const agentSelect = document.getElementById('agentSelect'); const agentSelectCard = document.getElementById('agentSelectCard'); const preparerAgentSelect = document.getElementById('preparerAgentSelect'); const preparerAgentSelectCard = document.getElementById('preparerAgentSelectCard'); const reviewAgentSelect = document.getElementById('reviewAgentSelect'); const reviewAgentSelectCard = document.getElementById('reviewAgentSelectCard'); const reviewIterationsRow = document.getElementById('reviewIterationsRow'); const maxReviewIterationsEl = document.getElementById('maxReviewIterations'); const finalizerAgentSelect = document.getElementById('finalizerAgentSelect'); const finalizerAgentSelectCard = document.getElementById('finalizerAgentSelectCard'); const imagePreview = document.getElementById('sessionImagePreview'); const saveBtn = document.getElementById('sessionSaveBtn'); const cancelBtn = document.getElementById('sessionCancelBtn'); const deleteBtn = document.getElementById('sessionDeleteBtn'); const commandsList = document.getElementById('customCommandsList'); const addCommandBtn = document.getElementById('addCommandBtn'); const more = document.querySelector('.more'); // Avatar Editor initialisieren _sessionAvatarEditor = AvatarEditor.init({ previewEl: imagePreview, initialImage: (session && session.image) || null, initialScale: (session && session.avatarScale) || 1, initialOffsetX: (session && session.avatarOffsetX) || 0, initialOffsetY: (session && session.avatarOffsetY) || 0 }); if (session) { titleEl.textContent = session.name || 'Unbenannt'; nameEl.value = session.name || ''; descEl.value = session.description || ''; workspaceEl.value = session.workspace || ''; deleteBtn.hidden = false; // Custom Commands laden loadCommandsIntoUI(commandsList, session.customCommands); } else { titleEl.textContent = 'Neue Session'; deleteBtn.hidden = true; } // EasyMDE für Kontext _sessionContextMde = new EasyMDE({ element: 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'] }); _sessionContextMde.codemirror.setOption('theme', 'default'); _sessionContextMde.codemirror.getWrapperElement().closest('.EasyMDEContainer').classList.add('fe-mde-wrap'); // Befehl hinzufügen addCommandBtn.addEventListener('click', () => createCommandRow(commandsList)); // Telegram-Section nur für existierende Sessions if (!isNew) { initSessionTelegram(sessionId); } // Agents laden + Skills parallel const agents = await apiGetAgents(); const agentMap = {}; for (const a of agents) agentMap[a.agentId] = a; const agentSkillsMap = {}; await Promise.all(agents.map(async a => { agentSkillsMap[a.agentId] = await apiGetAgentSkills(a.agentId); })); function renderAgentInCard(cardEl, agent, emptyIcon, emptyText) { cardEl.innerHTML = ''; if (!agent) { cardEl.innerHTML = '
' + emptyText + '
'; return; } const skills = agentSkillsMap[agent.agentId] || []; const inner = buildAgentCard(agent, skills); while (inner.firstChild) cardEl.appendChild(inner.firstChild); cardEl.insertAdjacentHTML('beforeend', ''); } function updateReviewVisibility() { reviewIterationsRow.hidden = !reviewAgentSelect.value; } // Initiale Anzeige – Agent if (session && session.agentId) { agentSelect.value = session.agentId; renderAgentInCard(agentSelectCard, agentMap[session.agentId] || null, 'fa-user-circle-o', 'Kein Agent ausgewählt'); } else { renderAgentInCard(agentSelectCard, null, 'fa-user-circle-o', 'Kein Agent ausgewählt'); } // Initiale Anzeige – Preparer-Agent if (session && session.preparerAgentId) { preparerAgentSelect.value = session.preparerAgentId; renderAgentInCard(preparerAgentSelectCard, agentMap[session.preparerAgentId] || null, 'fa-filter', 'Kein Vorbereiter-Agent'); } else { renderAgentInCard(preparerAgentSelectCard, null, 'fa-filter', 'Kein Vorbereiter-Agent'); } // Initiale Anzeige – Review-Agent if (session && session.reviewAgentId) { reviewAgentSelect.value = session.reviewAgentId; renderAgentInCard(reviewAgentSelectCard, agentMap[session.reviewAgentId] || null, 'fa-check-circle-o', 'Kein Review-Agent'); maxReviewIterationsEl.value = session.maxReviewIterations || 3; } else { renderAgentInCard(reviewAgentSelectCard, null, 'fa-check-circle-o', 'Kein Review-Agent'); } updateReviewVisibility(); // Initiale Anzeige – Finalizer-Agent if (session && session.finalizerAgentId) { finalizerAgentSelect.value = session.finalizerAgentId; renderAgentInCard(finalizerAgentSelectCard, agentMap[session.finalizerAgentId] || null, 'fa-flag-checkered', 'Kein Finalizer-Agent'); } else { renderAgentInCard(finalizerAgentSelectCard, null, 'fa-flag-checkered', 'Kein Finalizer-Agent'); } // Klick auf Agent-Card → Modal öffnen agentSelectCard.addEventListener('click', () => { openAgentPickerModal(agents, agentSkillsMap, agentSelect.value, (selectedId) => { agentSelect.value = selectedId || ''; renderAgentInCard(agentSelectCard, selectedId ? agentMap[selectedId] : null, 'fa-user-circle-o', 'Kein Agent ausgewählt'); }); }); // Klick auf Preparer-Agent-Card → Modal öffnen preparerAgentSelectCard.addEventListener('click', () => { openAgentPickerModal(agents, agentSkillsMap, preparerAgentSelect.value, (selectedId) => { preparerAgentSelect.value = selectedId || ''; renderAgentInCard(preparerAgentSelectCard, selectedId ? agentMap[selectedId] : null, 'fa-filter', 'Kein Vorbereiter-Agent'); }); }); // Klick auf Review-Agent-Card → Modal öffnen reviewAgentSelectCard.addEventListener('click', () => { openAgentPickerModal(agents, agentSkillsMap, reviewAgentSelect.value, (selectedId) => { reviewAgentSelect.value = selectedId || ''; renderAgentInCard(reviewAgentSelectCard, selectedId ? agentMap[selectedId] : null, 'fa-check-circle-o', 'Kein Review-Agent'); updateReviewVisibility(); }); }); // Klick auf Finalizer-Agent-Card → Modal öffnen finalizerAgentSelectCard.addEventListener('click', () => { openAgentPickerModal(agents, agentSkillsMap, finalizerAgentSelect.value, (selectedId) => { finalizerAgentSelect.value = selectedId || ''; renderAgentInCard(finalizerAgentSelectCard, selectedId ? agentMap[selectedId] : null, 'fa-flag-checkered', 'Kein Finalizer-Agent'); }); }); more.addEventListener('click', () => { const content = more.querySelector('[hidden]'); while (content.firstChild) more.parentNode.insertBefore(content.firstChild, more); more.remove(); }); // Workspace Browse const workspaceBrowse = document.getElementById('sessionWorkspaceBrowse'); if (workspaceBrowse) { workspaceBrowse.addEventListener('click', () => { const defaultPath = (currentUser && currentUser.defaultPath) || ''; const currentVal = workspaceEl.value.trim(); // Für den Folder-Browser den absoluten Startpfad zusammenbauen let startPath = '/'; if (defaultPath && currentVal) { startPath = defaultPath.replace(/\/+$/, '') + '/' + currentVal; } else if (defaultPath) { startPath = defaultPath; } else if (currentVal) { startPath = currentVal; } openFolderBrowser({ startPath, basePath: defaultPath || null, onSelect(absolutePath) { // Relativen Pfad zum DefaultPath berechnen if (defaultPath && absolutePath.startsWith(defaultPath)) { let rel = absolutePath.slice(defaultPath.length).replace(/^\/+/, ''); workspaceEl.value = rel || ''; } else { // Kein DefaultPath oder Pfad außerhalb → absoluten Pfad beibehalten (Legacy) workspaceEl.value = absolutePath; } } }); }); } // Speichern saveBtn.addEventListener('click', async () => { const commands = getCommandsFromUI(commandsList); const data = { name: nameEl.value.trim() || 'Unbenannt', description: descEl.value.trim(), image: _sessionAvatarEditor.getImage(), agentId: agentSelect.value || null, reviewAgentId: reviewAgentSelect.value || null, maxReviewIterations: parseInt(maxReviewIterationsEl.value) || 3, finalizerAgentId: finalizerAgentSelect.value || null, preparerAgentId: preparerAgentSelect.value || null, context: _sessionContextMde.value().trim() || null, workspace: workspaceEl.value.trim() || null, customCommands: commands, avatarScale: _sessionAvatarEditor.getScale(), avatarOffsetX: _sessionAvatarEditor.getOffsetX(), avatarOffsetY: _sessionAvatarEditor.getOffsetY() }; if (isNew) { await apiCreateSession(data); } else { await apiUpdateSession(sessionId, data); // Telegram-Config mitspeichern (falls Section sichtbar) var tgToken = document.getElementById('sessionTelegramToken'); var tgSecret = document.getElementById('sessionTelegramSecret'); if (tgToken) { try { await saveTelegramConfig(sessionId, tgToken, tgSecret); } catch(e) { showToast('Session gespeichert, aber Telegram-Fehler: ' + e.message, 'error'); return; } } } navigateTo('dashboard'); }); // Abbrechen cancelBtn.addEventListener('click', () => navigateTo('dashboard')); // Löschen deleteBtn.addEventListener('click', async () => { if (!confirm('Session wirklich löschen?')) return; await apiDeleteSession(sessionId); navigateTo('dashboard'); }); }, destroy() { if (_sessionContextMde) { _sessionContextMde.toTextArea(); _sessionContextMde = null; } if (_sessionAvatarEditor) { _sessionAvatarEditor.destroy(); _sessionAvatarEditor = null; } } }); 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 = `
Kein Agent
`; noneCard.addEventListener('click', () => { onSelect(null); overlay.remove(); }); 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(); }); } function initSessionTelegram(sessionId) { var tokenInput = document.getElementById('sessionTelegramToken'); var secretInput = document.getElementById('sessionTelegramSecret'); var allowedInput = document.getElementById('sessionTelegramAllowed'); var registerBtn = document.getElementById('sessionTelegramRegisterBtn'); var deleteBtn = document.getElementById('sessionTelegramDeleteBtn'); var statusBox = document.getElementById('sessionTelegramStatusBox'); // Vorhandene Config laden + Status abrufen apiSessionTelegramGet(sessionId).then(async function(config) { if (config.botToken) tokenInput.value = config.botToken; if (config.allowedChatIds) allowedInput.value = config.allowedChatIds; if (config.webhookSecret) { secretInput.value = config.webhookSecret; } else { // Secret einmalig generieren und sofort persistent speichern var newSecret = crypto.randomUUID().replace(/-/g, ''); secretInput.value = newSecret; await apiSessionTelegramSave(sessionId, config.botToken || '', newSecret); } // Status automatisch abrufen wenn Token konfiguriert ist if (config.botToken) { refreshTelegramStatus(sessionId, statusBox, registerBtn, deleteBtn); } else { showTelegramUnconfigured(statusBox, registerBtn, deleteBtn); } }).catch(function() { showTelegramUnconfigured(statusBox, registerBtn, deleteBtn); }); // Webhook registrieren registerBtn.addEventListener('click', async function() { var baseUrl = prompt('Base-URL der App:', window.location.origin); if (!baseUrl) return; registerBtn.disabled = true; registerBtn.textContent = 'Wird registriert...'; try { // Erst Token + Secret speichern (falls geändert) await saveTelegramConfig(sessionId, tokenInput, secretInput); var res = await apiSessionTelegramRegisterWebhook(sessionId, baseUrl.trim()); if (res.ok) { showToast('Webhook registriert!', 'success'); } else { showToast('Antwort: ' + JSON.stringify(res), 'error'); } } catch(e) { showToast('Fehler: ' + e.message, 'error'); } finally { registerBtn.disabled = false; registerBtn.textContent = 'Webhook registrieren'; refreshTelegramStatus(sessionId, statusBox, registerBtn, deleteBtn); } }); // Webhook entfernen deleteBtn.addEventListener('click', async function() { if (!confirm('Webhook wirklich entfernen?')) return; deleteBtn.disabled = true; deleteBtn.textContent = 'Wird entfernt...'; try { var res = await apiSessionTelegramDeleteWebhook(sessionId); if (res.ok) { showToast('Webhook entfernt', 'success'); } else { showToast('Antwort: ' + JSON.stringify(res), 'error'); } } catch(e) { showToast('Fehler: ' + e.message, 'error'); } finally { deleteBtn.disabled = false; deleteBtn.textContent = 'Webhook entfernen'; refreshTelegramStatus(sessionId, statusBox, registerBtn, deleteBtn); } }); } function showTelegramUnconfigured(statusBox, registerBtn, deleteBtn) { statusBox.innerHTML = '

⚠️ Kein Bot-Token konfiguriert. Speichere zuerst die Session mit einem Token.

'; statusBox.hidden = false; registerBtn.hidden = true; deleteBtn.hidden = true; } async function refreshTelegramStatus(sessionId, statusBox, registerBtn, deleteBtn) { statusBox.hidden = true; try { var info = await apiSessionTelegramWebhookInfo(sessionId); var result = info.result || {}; var url = result.url || ''; var pending = result.pending_update_count || 0; var lastError = result.last_error_message; var active = !!url; var html = ''; if (active) { html += '

✅ Webhook aktiv

'; html += '

URL: ' + esc(url) + '

'; html += '

Ausstehend: ' + pending + '

'; if (lastError) html += '

Letzter Fehler: ' + esc(lastError) + '

'; } else { html += '

⚠️ Webhook nicht registriert

'; } statusBox.innerHTML = html; statusBox.hidden = false; // Buttons ein/ausblenden je nach Status registerBtn.hidden = active; deleteBtn.hidden = !active; } catch(e) { statusBox.innerHTML = '

Status konnte nicht abgerufen werden: ' + esc(e.message) + '

'; statusBox.hidden = false; registerBtn.hidden = false; deleteBtn.hidden = true; } } async function saveTelegramConfig(sessionId, tokenInput, secretInput) { var token = tokenInput.value.trim(); var secret = secretInput.value.trim(); var allowedInput = document.getElementById('sessionTelegramAllowed'); var allowed = allowedInput ? allowedInput.value.trim() : ''; await apiSessionTelegramSave(sessionId, token, secret, allowed); }