(function () {
let active = false;
let initialized = false;
const API_URL = (() => {
if (window.API_URL) return window.API_URL;
try {
return `${window.location.origin}/api`;
} catch (error) {
return 'https://fb.srv.medeba-media.de/api';
}
})();
const LOGIN_PAGE = 'login.html';
const state = {
traces: [],
selectedTraceId: null,
loading: false
};
const tableBody = document.getElementById('aiDebugTableBody');
const statusEl = document.getElementById('aiDebugStatus');
const detailMeta = document.getElementById('aiDebugDetailMeta');
const detailJson = document.getElementById('aiDebugDetailJson');
const refreshBtn = document.getElementById('aiDebugRefreshBtn');
const statusFilter = document.getElementById('aiDebugStatusFilter');
const limitFilter = document.getElementById('aiDebugLimitFilter');
function handleUnauthorized(response) {
if (response && response.status === 401) {
if (typeof redirectToLogin === 'function') {
redirectToLogin();
} else {
window.location.href = LOGIN_PAGE;
}
return true;
}
return false;
}
async function apiFetchJSON(path, options = {}) {
const response = await fetch(`${API_URL}${path}`, {
credentials: 'include',
...options
});
if (handleUnauthorized(response)) {
throw new Error('Nicht angemeldet');
}
if (!response.ok) {
const payload = await response.json().catch(() => ({}));
throw new Error(payload.error || 'Unbekannter Fehler');
}
return response.json();
}
function setStatus(message, isError = false) {
if (!statusEl) return;
statusEl.textContent = message || '';
statusEl.classList.toggle('ai-debug-status--error', !!isError);
}
function formatDate(value) {
if (!value) return '—';
const date = new Date(value);
if (Number.isNaN(date.getTime())) return '—';
return date.toLocaleString('de-DE', {
day: '2-digit',
month: '2-digit',
year: 'numeric',
hour: '2-digit',
minute: '2-digit',
second: '2-digit'
});
}
function formatMs(value) {
const numeric = Number(value);
if (!Number.isFinite(numeric) || numeric < 0) {
return '—';
}
return `${Math.round(numeric)} ms`;
}
function compactId(value) {
if (!value || typeof value !== 'string') return '—';
if (value.length <= 16) return value;
return `${value.slice(0, 8)}…${value.slice(-6)}`;
}
function getBackendTotalMs(item) {
return item
&& item.backend_timings
&& typeof item.backend_timings === 'object'
? item.backend_timings.totalMs
: null;
}
function getAiRequestMs(item) {
return item
&& item.frontend_timings
&& typeof item.frontend_timings === 'object'
? item.frontend_timings.aiRequestMs
: null;
}
function renderTable() {
if (!tableBody) return;
tableBody.innerHTML = '';
if (!state.traces.length) {
const tr = document.createElement('tr');
tr.innerHTML = '
Keine Debug-Läufe gefunden. | ';
tableBody.appendChild(tr);
return;
}
state.traces.forEach((item) => {
const tr = document.createElement('tr');
tr.dataset.traceId = item.trace_id;
if (item.trace_id === state.selectedTraceId) {
tr.classList.add('is-selected');
}
tr.innerHTML = `
${formatDate(item.created_at)} |
${item.status || '—'} |
${formatMs(item.total_duration_ms)} |
${formatMs(getBackendTotalMs(item))} |
${formatMs(getAiRequestMs(item))} |
${compactId(item.flow_id)} / ${compactId(item.trace_id)} |
`;
tableBody.appendChild(tr);
});
}
function renderDetail(trace) {
if (!detailMeta || !detailJson) {
return;
}
if (!trace) {
detailMeta.textContent = 'Bitte einen Eintrag auswählen.';
detailJson.textContent = '';
return;
}
detailMeta.textContent = `${trace.status || '—'} · ${formatDate(trace.created_at)} · Trace ${trace.trace_id || '—'}`;
detailJson.textContent = JSON.stringify(trace, null, 2);
}
async function loadTraceDetail(traceId) {
if (!traceId) {
renderDetail(null);
return;
}
try {
const trace = await apiFetchJSON(`/ai/debug-traces/${encodeURIComponent(traceId)}`);
if (!active) return;
renderDetail(trace);
} catch (error) {
if (!active) return;
setStatus(`Details konnten nicht geladen werden: ${error.message}`, true);
}
}
async function loadTraces() {
if (!active || state.loading) {
return;
}
state.loading = true;
setStatus('Lade Debug-Läufe...');
try {
const params = new URLSearchParams();
const status = statusFilter ? statusFilter.value.trim() : '';
const limit = limitFilter ? parseInt(limitFilter.value, 10) : 50;
if (status) {
params.set('status', status);
}
if (Number.isFinite(limit) && limit > 0) {
params.set('limit', String(limit));
}
const data = await apiFetchJSON(`/ai/debug-traces?${params.toString()}`);
if (!active) return;
state.traces = Array.isArray(data.items) ? data.items : [];
if (!state.selectedTraceId || !state.traces.some((item) => item.trace_id === state.selectedTraceId)) {
state.selectedTraceId = state.traces.length ? state.traces[0].trace_id : null;
}
renderTable();
await loadTraceDetail(state.selectedTraceId);
setStatus(`${state.traces.length} Lauf/Läufe geladen.`);
} catch (error) {
if (!active) return;
state.traces = [];
renderTable();
renderDetail(null);
setStatus(`Debug-Läufe konnten nicht geladen werden: ${error.message}`, true);
} finally {
state.loading = false;
}
}
function setupEvents() {
if (refreshBtn) {
refreshBtn.addEventListener('click', () => loadTraces());
}
if (statusFilter) {
statusFilter.addEventListener('change', () => loadTraces());
}
if (limitFilter) {
limitFilter.addEventListener('change', () => loadTraces());
}
if (tableBody) {
tableBody.addEventListener('click', (event) => {
const row = event.target.closest('tr[data-trace-id]');
if (!row) return;
const traceId = row.dataset.traceId;
if (!traceId) return;
state.selectedTraceId = traceId;
renderTable();
loadTraceDetail(traceId);
});
}
}
function init() {
if (initialized) return;
setupEvents();
initialized = true;
}
function activate() {
init();
active = true;
loadTraces();
}
function deactivate() {
active = false;
}
window.AIDebugPage = {
activate,
deactivate
};
const section = document.querySelector('[data-view="ai-debug"]');
if (section && section.classList.contains('app-view--active')) {
activate();
}
})();