Files
gott/wwwroot/js/pages/session-page.js
T
Meleki 0120bb8f73 Telegram: Bot=Session statt User-Level, Chat-Code-Refactoring
- Telegram-Config (Bot-Token, Webhook-Secret, ChatId) von User-Settings
  in sessions-Tabelle verschoben → jede Session hat ihren eigenen Bot
- Link-Code-System komplett entfernt (TelegramLinkRecord gelöscht)
- /start verknüpft automatisch, kein /link mehr nötig
- Alle Endpoints session-basiert: /api/sessions/{id}/telegram/*
- Webhook-URL pro Session: /api/telegram/webhook/{sessionId}

Refactoring: Doppelten Chat-Code eliminiert
- Neuer ChatService: PrepareContext, BuildHistoryFromDb, ResolveModelId
- Neues ChatContext-Model als gemeinsame Datenstruktur
- AnthropicService.ChatNonStreaming() für Telegram (statt eigenem Loop)
- TelegramService um ~180 Zeilen reduziert, kein eigener Claude-Loop mehr
- ChatEndpoint nutzt ChatService.PrepareContext() statt eigener Setup-Logik
- Model-Mapping nur noch an einer Stelle (3x → 1x)

Frontend
- Telegram-Section von Settings-Page in Session-Bearbeitungsseite verschoben
- Webhook-Secret readonly, wird einmalig generiert und persistent gespeichert
- Status wird automatisch geladen, Register/Delete je nach Webhook-Status
- Toast-Notification-System (showToast) in ui.js
- Link-Code-Modal und Telegram-Menüpunkt im Dashboard entfernt
- Telegram-Badge auf Session-Cards (konfiguriert/verbunden)

Bugfix: Bearer statt x-api-key für OAuth-Tokens im TelegramService
2026-03-03 21:46:59 +01:00

304 lines
10 KiB
JavaScript

// ── Session Edit Page ──
let _sessionImageData = null;
let _sessionPasteHandler = 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 imagePreview = document.getElementById('sessionImagePreview');
const imageInput = document.getElementById('sessionImageInput');
const imageBtn = document.getElementById('sessionImageBtn');
const imageClearBtn = document.getElementById('sessionImageClearBtn');
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');
_sessionImageData = null;
if (session) {
titleEl.textContent = session.name || 'Unbenannt';
nameEl.value = session.name || '';
descEl.value = session.description || '';
contextEl.value = session.context || '';
workspaceEl.value = session.workspace || '';
_sessionImageData = session.image || null;
deleteBtn.hidden = false;
if (session.image) {
imagePreview.innerHTML = `<img src="${session.image}" alt="">`;
}
// Custom Commands laden
loadCommandsIntoUI(commandsList, session.customCommands);
} else {
titleEl.textContent = 'Neue Session';
deleteBtn.hidden = true;
}
// Befehl hinzufügen
addCommandBtn.addEventListener('click', () => createCommandRow(commandsList));
// Telegram-Section nur für existierende Sessions
if (!isNew) {
initSessionTelegram(sessionId);
}
// Agents laden
const agents = await apiGetAgents();
agentSelect.innerHTML = '<option value="">-- Kein Agent --</option>';
for (const a of agents) {
const opt = document.createElement('option');
opt.value = a.agentId;
opt.textContent = `${a.name} (${a.model})`;
agentSelect.appendChild(opt);
}
if (session && session.agentId) agentSelect.value = session.agentId;
// Bild Upload
imageBtn.addEventListener('click', () => imageInput.click());
imageInput.addEventListener('change', () => {
const file = imageInput.files[0];
if (!file) return;
const reader = new FileReader();
reader.onload = () => {
_sessionImageData = reader.result;
imagePreview.innerHTML = `<img src="${reader.result}" alt="">`;
};
reader.readAsDataURL(file);
});
imageClearBtn.addEventListener('click', () => {
_sessionImageData = null;
imagePreview.innerHTML = '?';
imageInput.value = '';
});
// Bild Paste (Ctrl+V)
document.addEventListener('paste', _sessionPasteHandler = (e) => {
const items = e.clipboardData?.items;
if (!items) return;
for (const item of items) {
if (item.type.startsWith('image/')) {
e.preventDefault();
const file = item.getAsFile();
if (!file) return;
const reader = new FileReader();
reader.onload = () => {
_sessionImageData = reader.result;
imagePreview.innerHTML = `<img src="${reader.result}" alt="">`;
};
reader.readAsDataURL(file);
return;
}
}
});
// Workspace Browse
const workspaceBrowse = document.getElementById('sessionWorkspaceBrowse');
if (workspaceBrowse) {
workspaceBrowse.addEventListener('click', () => {
openFolderBrowser({
startPath: workspaceEl.value.trim() || '/',
onSelect(path) {
workspaceEl.value = path;
}
});
});
}
// Speichern
saveBtn.addEventListener('click', async () => {
const commands = getCommandsFromUI(commandsList);
const data = {
name: nameEl.value.trim() || 'Unbenannt',
description: descEl.value.trim(),
image: _sessionImageData,
agentId: agentSelect.value || null,
context: contextEl.value || null,
workspace: workspaceEl.value.trim() || null,
customCommands: commands
};
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 (_sessionPasteHandler) {
document.removeEventListener('paste', _sessionPasteHandler);
_sessionPasteHandler = null;
}
_sessionImageData = null;
}
});
function initSessionTelegram(sessionId) {
var tokenInput = document.getElementById('sessionTelegramToken');
var secretInput = document.getElementById('sessionTelegramSecret');
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.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();
await apiSessionTelegramSave(sessionId, token, secret);
}
function escapeHtml(str) {
return String(str)
.replace(/&/g, '&amp;')
.replace(/</g, '&lt;')
.replace(/>/g, '&gt;')
.replace(/"/g, '&quot;');
}