// ── 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 panelAgentsCard = document.getElementById('panelAgentsCard'); const panelAgentIdsEl = document.getElementById('panelAgentIds'); const moaLayersEl = document.getElementById('moaLayers'); const imagePreview = document.getElementById('sessionImagePreview'); const saveBtn = document.getElementById('sessionSaveBtn'); const cancelBtn = document.getElementById('sessionCancelBtn'); const deleteBtn = document.getElementById('sessionDeleteBtn'); // Rücksprung nach Speichern/Abbrechen: zurück zum Chat, wenn von dort geöffnet – sonst Dashboard const goBack = () => { if (params.returnTo === 'chat' && params.returnId) { navigateTo('chat', { id: params.returnId }); } else { navigateTo('dashboard'); } }; const commandsList = document.getElementById('customCommandsList'); const addCommandBtn = document.getElementById('addCommandBtn'); const mcpServersList = document.getElementById('mcpServersList'); const addMcpServerBtn = document.getElementById('addMcpServerBtn'); const ownerRow = document.getElementById('sessionOwnerRow'); const userSelectCard = document.getElementById('sessionUserSelectCard'); const userSelect = document.getElementById('sessionUserSelect'); 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); // MCP-Server loadMcpServersIntoUI(mcpServersList, session.mcpServers); } else { titleEl.textContent = 'Neue Session'; deleteBtn.hidden = true; } // EasyMDE für Kontext _sessionContextMde = createMDE(contextEl, { initialValue: (session && session.context) || '', placeholder: 'Zusätzlicher Kontext für den System-Prompt (optional)' }); // Befehl hinzufügen addCommandBtn.addEventListener('click', () => createCommandRow(commandsList)); // MCP-Server hinzufügen addMcpServerBtn.addEventListener('click', () => createMcpServerRow(mcpServersList)); // Telegram-Section nur für existierende Sessions if (!isNew) { initSessionTelegram(sessionId, session); } // Agents laden + Skills parallel const agents = await apiGetAvailableAgents(); const agentMap = {}; for (const a of agents) agentMap[a.agentId] = a; const agentSkillsMap = {}; for (const a of agents) { agentSkillsMap[a.agentId] = a.skills || []; } function renderAgentInCard(cardEl, agent, emptyIcon, emptyText) { const inner = agent ? buildAgentCard(agent, agentSkillsMap[agent.agentId] || []) : null; _renderCardPreview(cardEl, inner, emptyIcon, emptyText, 'agent-select-change'); } function updateReviewVisibility() { reviewIterationsRow.hidden = !reviewAgentSelect.value; } // Helper: Agent-Picker für eine Rolle einrichten (Initial-Rendering + Click-Handler) function setupAgentPicker(cardEl, selectEl, initialId, emptyIcon, emptyText, onChange) { if (initialId) { selectEl.value = initialId; renderAgentInCard(cardEl, agentMap[initialId] || null, emptyIcon, emptyText); } else { renderAgentInCard(cardEl, null, emptyIcon, emptyText); } cardEl.addEventListener('click', () => { openAgentPickerModal(agents, agentSkillsMap, selectEl.value, (selectedId) => { selectEl.value = selectedId || ''; renderAgentInCard(cardEl, selectedId ? agentMap[selectedId] : null, emptyIcon, emptyText); if (onChange) onChange(selectedId); }); }); } setupAgentPicker(agentSelectCard, agentSelect, session && session.agentId, 'fa-user-circle-o', 'Kein Agent ausgewählt'); setupAgentPicker(preparerAgentSelectCard, preparerAgentSelect, session && session.preparerAgentId, 'fa-filter', 'Kein Vorbereiter-Agent'); setupAgentPicker(reviewAgentSelectCard, reviewAgentSelect, session && session.reviewAgentId, 'fa-check-circle-o', 'Kein Review-Agent', updateReviewVisibility); if (session && session.reviewAgentId) maxReviewIterationsEl.value = session.maxReviewIterations || 3; updateReviewVisibility(); setupAgentPicker(finalizerAgentSelectCard, finalizerAgentSelect, session && session.finalizerAgentId, 'fa-flag-checkered', 'Kein Finalizer-Agent'); // Multi-Modell-Panel (Fusion): Modus + Mehrfach-Agenten-Picker function renderPanelInCard() { var ids = (panelAgentIdsEl.value || '').split(',').map(function(s){ return s.trim(); }).filter(Boolean); if (ids.length === 0) { panelAgentsCard.innerHTML = '
Keine Panel-Agenten
'; return; } var chips = ids.map(function(id) { var a = agentMap[id]; var label = a ? a.name : id.slice(0, 8); var model = a ? ((typeof prettyModelName === 'function') ? prettyModelName(a.model) : a.model) : ''; return '' + esc(label) + (model ? ' · ' + esc(model) : '') + ''; }).join(' '); panelAgentsCard.innerHTML = '
' + chips + '
' + ''; } // Panel-Modus (Fusion/MoA) wird on-demand im Chat gewählt (nicht mehr hier persistiert). // In den Settings legt man nur noch die Panel-Mitglieder + MoA-Runden fest (immer sichtbar). panelAgentIdsEl.value = (session && session.panelAgentIds) || ''; if (moaLayersEl) moaLayersEl.value = (session && session.moaLayers) || 2; renderPanelInCard(); panelAgentsCard.addEventListener('click', function() { var currentIds = (panelAgentIdsEl.value || '').split(',').map(function(s){ return s.trim(); }).filter(Boolean); openMultiAgentPickerModal(agents, agentSkillsMap, currentIds, function(newIds) { panelAgentIdsEl.value = newIds.join(','); renderPanelInCard(); }); }); // User-Zuweisung (jeder Besitzer, nur für bestehende Sessions) if (!isNew) { initSessionUserPicker(ownerRow, userSelectCard, userSelect, session && session.userId); } initMoreButton(more); // 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, panelAgentIds: panelAgentIdsEl.value || null, moaLayers: moaLayersEl ? (parseInt(moaLayersEl.value, 10) || 2) : 2, context: _sessionContextMde.value().trim() || null, workspace: workspaceEl.value.trim() || null, customCommands: commands, avatarScale: _sessionAvatarEditor.getScale(), avatarOffsetX: _sessionAvatarEditor.getOffsetX(), avatarOffsetY: _sessionAvatarEditor.getOffsetY(), mcpServers: getMcpServersFromUI(mcpServersList) }; // Session einem anderen Benutzer zuweisen. Wechselt der Besitzer tatsächlich, // erst rückfragen – die Session verschwindet danach aus der eigenen Übersicht. if (!isNew && userSelect.value && userSelect.value !== (session && session.userId)) { var targetLabel = userSelect.dataset.userLabel || 'einem anderen Benutzer'; if (!confirm('Diese Session an "' + targetLabel + '" zuweisen? Sie verschwindet danach aus deiner Übersicht.')) { return; } data.userId = userSelect.value; } if (isNew) { await apiCreateSession(data); } else { await apiUpdateSession(sessionId, data); // Telegram-Config mitspeichern try { var tgBotId = document.getElementById('telegramBotSelect').value || ''; var tgChatId = document.getElementById('sessionTelegramChatId').value.trim(); await apiSessionTelegramSave(sessionId, tgBotId, tgChatId); } catch(e) { showToast('Session gespeichert, aber Telegram-Fehler: ' + e.message, 'error'); return; } } goBack(); }); // Abbrechen cancelBtn.addEventListener('click', () => goBack()); // 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) { const items = agents.map(a => { a._pickerId = a.agentId; return a; }); _openPickerModal('Agent auswählen', items, currentId, onSelect, { noneLabel: 'Kein Agent', buildCard: (a) => buildAgentCard(a, agentSkillsMap[a.agentId] || []) }); } function openMultiAgentPickerModal(agents, agentSkillsMap, selectedIds, onConfirm) { const items = agents.map(a => { a._pickerId = a.agentId; return a; }); _openMultiPickerModal('Panel-Agenten auswählen', items, selectedIds, onConfirm, { buildCard: (a) => buildAgentCard(a, agentSkillsMap[a.agentId] || []) }); } function initSessionTelegram(sessionId, session) { var botSelectCard = document.getElementById('telegramBotSelectCard'); var botSelect = document.getElementById('telegramBotSelect'); var chatIdInput = document.getElementById('sessionTelegramChatId'); var unlinkBtn = document.getElementById('sessionTelegramUnlink'); // Unlink-Button unlinkBtn.addEventListener('click', function() { if (!chatIdInput.value) return; if (!confirm('Telegram-Verknüpfung wirklich lösen?')) return; chatIdInput.value = ''; showToast('Chat-ID entfernt. Speichern nicht vergessen.', 'info'); }); // Config laden apiSessionTelegramGet(sessionId).then(function(config) { if (config.chatId) chatIdInput.value = config.chatId; if (config.telegramBotId) botSelect.value = config.telegramBotId; // Bot-Picker initialisieren initTelegramBotPicker(botSelectCard, botSelect, config.telegramBotId); }).catch(function() { initTelegramBotPicker(botSelectCard, botSelect, null); }); } async function initTelegramBotPicker(cardEl, selectEl, currentBotId) { var bots = []; try { var result = await apiGetAvailableTelegramBots(); if (Array.isArray(result)) bots = result; } catch(e) { /* ignore */ } var botMap = {}; for (var i = 0; i < bots.length; i++) botMap[bots[i].botId] = bots[i]; function renderBotInCard(bot) { var inner = bot ? buildTelegramBotCard(bot) : null; _renderCardPreview(cardEl, inner, 'fa-paper-plane', 'Kein Bot zugewiesen', 'tg-bot-select-change'); } if (currentBotId && botMap[currentBotId]) { selectEl.value = currentBotId; renderBotInCard(botMap[currentBotId]); } else { renderBotInCard(null); } cardEl.addEventListener('click', function() { openTelegramBotPickerModal(bots, selectEl.value, function(selectedBotId) { selectEl.value = selectedBotId || ''; renderBotInCard(selectedBotId ? botMap[selectedBotId] : null); }); }); } // Session einem anderen Benutzer zuweisen (jeder Besitzer seiner eigenen Session). async function initSessionUserPicker(rowEl, cardEl, selectEl, currentOwnerId) { var users = []; try { var result = await apiGetUsers(); if (Array.isArray(result)) users = result; } catch (e) { // Fehler beim Laden → Zuweisung bleibt versteckt return; } var userMap = {}; for (var i = 0; i < users.length; i++) userMap[users[i].userId] = users[i]; function userLabel(user) { return user ? (user.displayName || user.username || '') : ''; } function renderUserInCard(user) { var inner = user ? buildUserCard(user) : null; _renderCardPreview(cardEl, inner, 'fa-user', 'Kein Benutzer', 'agent-select-change'); selectEl.dataset.userLabel = userLabel(user); } selectEl.value = currentOwnerId || ''; renderUserInCard(userMap[currentOwnerId] || null); rowEl.hidden = false; cardEl.addEventListener('click', function() { openUserPickerModal(users, selectEl.value, function(selectedUserId) { // "Keine Änderung" (null) ignorieren – eine Session muss immer einen Besitzer haben. if (!selectedUserId) return; selectEl.value = selectedUserId; renderUserInCard(userMap[selectedUserId] || null); }); }); } function openUserPickerModal(users, currentId, onSelect) { var items = users.map(function(u) { u._pickerId = u.userId; return u; }); _openPickerModal('Benutzer auswählen', items, currentId, onSelect, { noneLabel: 'Keine Änderung', noneIcon: 'fa-user', buildCard: function(u) { return buildUserCard(u); } }); }