Files
gott/wwwroot/js/pages/session-page.js
T
Meleki 92c20402eb refactor: Agent-Auswahl in Session-Form als Card + Picker-Modal
- buildAgentCard() Helper in ui.js erstellt (wiederverwendbar für Dashboard + Picker)
- Session-Form: select-Dropdown durch klickbare Agent-Card ersetzt
- Agent-Picker-Modal zeigt alle Agents mit identischen Cards wie am Dashboard
- Dashboard refactored: nutzt buildAgentCard() statt dupliziertem Inline-HTML
- Kein duplizierter Code mehr für Agent-Card-Rendering (Badges, Skills, Avatar)
2026-03-06 14:01:27 +01:00

400 lines
14 KiB
JavaScript

// ── 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 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
});
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 renderAgentSelectCard(agent) {
agentSelectCard.innerHTML = '';
if (!agent) {
agentSelectCard.innerHTML = '<div class="agent-select-empty"><i class="fa fa-user-circle-o"></i> Kein Agent ausgewählt</div>';
return;
}
const skills = agentSkillsMap[agent.agentId] || [];
const inner = buildAgentCard(agent, skills);
// Die inneren Elemente (card-image + card-body) in die Select-Card übernehmen
while (inner.firstChild) agentSelectCard.appendChild(inner.firstChild);
// Pencil-Icon hinzufügen
agentSelectCard.insertAdjacentHTML('beforeend', '<span class="agent-select-change"><i class="fa fa-pencil"></i></span>');
}
// Initiale Anzeige
if (session && session.agentId) {
agentSelect.value = session.agentId;
renderAgentSelectCard(agentMap[session.agentId] || null);
} else {
renderAgentSelectCard(null);
}
// Klick auf Card → Modal öffnen
agentSelectCard.addEventListener('click', () => {
openAgentPickerModal(agents, agentSkillsMap, agentSelect.value, (selectedId) => {
agentSelect.value = selectedId || '';
renderAgentSelectCard(selectedId ? agentMap[selectedId] : null);
});
});
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,
context: _sessionContextMde.value().trim() || null,
workspace: workspaceEl.value.trim() || null,
customCommands: commands,
avatarScale: _sessionAvatarEditor.getScale()
};
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 = '<span>Agent auswählen</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';
// "Kein Agent" 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 fa-ban" style="font-size:2em;opacity:var(--semi-transparent)"></i></div>
<div class="card-body">
<div class="card-title-row">
<span class="card-title">Kein Agent</span>
</div>
</div>
`;
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 = '<p>⚠️ Kein Bot-Token konfiguriert. Speichere zuerst die Session mit einem Token.</p>';
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 += '<p>✅ <strong>Webhook aktiv</strong></p>';
html += '<p><strong>URL:</strong> ' + escapeHtml(url) + '</p>';
html += '<p><strong>Ausstehend:</strong> ' + pending + '</p>';
if (lastError) html += '<p class="tg-error"><strong>Letzter Fehler:</strong> ' + escapeHtml(lastError) + '</p>';
} else {
html += '<p>⚠️ <strong>Webhook nicht registriert</strong></p>';
}
statusBox.innerHTML = html;
statusBox.hidden = false;
// Buttons ein/ausblenden je nach Status
registerBtn.hidden = active;
deleteBtn.hidden = !active;
} catch(e) {
statusBox.innerHTML = '<p class="tg-error">Status konnte nicht abgerufen werden: ' + escapeHtml(e.message) + '</p>';
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);
}
function escapeHtml(str) {
return String(str)
.replace(/&/g, '&amp;')
.replace(/</g, '&lt;')
.replace(/>/g, '&gt;')
.replace(/"/g, '&quot;');
}