Files
gott/wwwroot/js/pages/embedding-debug-page.js
T
MelekiandClaude Opus 4.6 3f2baa1639 feat: AutoContext-Suche grundlegend verbessern + Embedding-Debug-Tool
Hybrid-Suche (Vektor + FTS5-Keyword), Recency-Boost, Query-Erweiterung
(letzte 2-3 User-Messages), Chunk-Overlapping, FullText-Speicherung,
konfigurierbares Embedding-Modell/MaxSeqLen, deduplizierter AutoContext-
Code (Streaming + CLI shared), Admin-Diagnose-Seite mit Re-Embed-Funktion.
Threshold von 0.45 auf 0.35 gesenkt (Hybrid-Suche ist präziser).

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-04-01 08:50:41 +02:00

207 lines
11 KiB
JavaScript

// ── Embedding Debug Page ──
registerPage('embedding-debug', {
async init() {
if (!currentUser || !currentUser.isAdmin) {
navigateTo('dashboard');
return;
}
var queryInput = document.getElementById('embDebugQuery');
var agentSelect = document.getElementById('embDebugAgent');
var topNInput = document.getElementById('embDebugTopN');
var searchBtn = document.getElementById('embDebugSearchBtn');
var statsBtn = document.getElementById('embDebugStatsBtn');
var reEmbedBtn = document.getElementById('embDebugReEmbedBtn');
var summaryEl = document.getElementById('embDebugSummary');
var resultsEl = document.getElementById('embDebugResults');
// Agents laden
try {
var resp = await fetch('/api/agents/available');
var agents = await resp.json();
(agents || []).forEach(function(a) {
var opt = document.createElement('option');
opt.value = a.agentId;
opt.textContent = a.name;
agentSelect.appendChild(opt);
});
} catch (e) { /* ignore */ }
searchBtn.addEventListener('click', doSearch);
queryInput.addEventListener('keydown', function(e) {
if (e.key === 'Enter') doSearch();
});
statsBtn.addEventListener('click', doStats);
if (reEmbedBtn) {
reEmbedBtn.addEventListener('click', async function() {
if (!confirm('Alle Embeddings löschen und neu generieren?\n\nDas kann bei vielen Messages einige Minuten dauern.')) return;
reEmbedBtn.disabled = true;
reEmbedBtn.innerHTML = '<i class="fa fa-spinner fa-spin"></i> Läuft...';
try {
var resp = await fetch('/api/admin/embedding-debug/re-embed', { method: 'POST' });
var data = await resp.json();
summaryEl.innerHTML = '<div class="success-box">' + esc(data.status || data.error) + (data.model ? ' (Modell: ' + esc(data.model) + ')' : '') + '</div>';
} catch (e) {
summaryEl.innerHTML = '<div class="error-box">Fehler: ' + esc(e.message) + '</div>';
} finally {
reEmbedBtn.disabled = false;
reEmbedBtn.innerHTML = '<i class="fa fa-refresh"></i> Re-Embed All';
}
});
}
async function doSearch() {
var query = queryInput.value.trim();
if (!query) return;
searchBtn.disabled = true;
searchBtn.innerHTML = '<i class="fa fa-spinner fa-spin"></i> Suche...';
summaryEl.innerHTML = '';
resultsEl.innerHTML = '';
try {
var params = new URLSearchParams({
q: query,
agentId: agentSelect.value,
topN: topNInput.value || '20'
});
var resp = await fetch('/api/admin/embedding-debug?' + params);
var data = await resp.json();
if (data.error) {
summaryEl.innerHTML = '<div class="error-box">' + esc(data.error) + '</div>';
return;
}
renderSummary(data.summary);
renderResults(data.messages, data.memory);
} catch (e) {
summaryEl.innerHTML = '<div class="error-box">Fehler: ' + esc(e.message) + '</div>';
} finally {
searchBtn.disabled = false;
searchBtn.innerHTML = '<i class="fa fa-search"></i> Suchen';
}
}
async function doStats() {
statsBtn.disabled = true;
statsBtn.innerHTML = '<i class="fa fa-spinner fa-spin"></i>';
summaryEl.innerHTML = '';
resultsEl.innerHTML = '';
try {
var resp = await fetch('/api/admin/embedding-debug/stats');
var data = await resp.json();
renderStats(data);
} catch (e) {
summaryEl.innerHTML = '<div class="error-box">Fehler: ' + esc(e.message) + '</div>';
} finally {
statsBtn.disabled = false;
statsBtn.innerHTML = '<i class="fa fa-chart-bar"></i> Statistiken';
}
}
function renderSummary(s) {
summaryEl.innerHTML =
'<div class="emb-summary">' +
'<div class="emb-summary-grid">' +
'<div class="emb-stat"><span class="emb-stat-num">' + s.totalMessageResults + '</span><span class="emb-stat-label">Treffer gesamt</span></div>' +
'<div class="emb-stat good"><span class="emb-stat-num">' + s.aboveThreshold035 + '</span><span class="emb-stat-label">&ge; 0.35 (Threshold)</span></div>' +
'<div class="emb-stat warn"><span class="emb-stat-num">' + s.belowThreshold + '</span><span class="emb-stat-label">&lt; 0.35</span></div>' +
'<div class="emb-stat"><span class="emb-stat-num">' + s.wouldAppearInAutoContext + '</span><span class="emb-stat-label">→ AutoContext</span></div>' +
'<div class="emb-stat"><span class="emb-stat-num">' + s.filteredByChain + '</span><span class="emb-stat-label">Chain-gefiltert</span></div>' +
'<div class="emb-stat"><span class="emb-stat-num">' + s.filteredByLength + '</span><span class="emb-stat-label">Zu kurz (&lt;20)</span></div>' +
'</div>' +
(s.totalMemoryResults > 0 ?
'<div class="emb-summary-grid" style="margin-top:0.5rem">' +
'<div class="emb-stat"><span class="emb-stat-num">' + s.totalMemoryResults + '</span><span class="emb-stat-label">Memory-Treffer</span></div>' +
'<div class="emb-stat good"><span class="emb-stat-num">' + s.memoryAboveThreshold + '</span><span class="emb-stat-label">Memory &ge; 0.35</span></div>' +
'</div>' : '') +
'</div>';
}
function renderResults(messages, memory) {
var html = '';
if (messages && messages.length > 0) {
html += '<h3 style="margin:1rem 0 0.5rem">Message-Ergebnisse</h3>';
html += '<div class="emb-results">';
messages.forEach(function(r, i) {
var scoreClass = r.score >= 0.35 ? 'score-good' : (r.score >= 0.2 ? 'score-medium' : 'score-low');
var filterTags = '';
if (r.wouldBeFilteredByChain) filterTags += '<span class="emb-tag filter">Chain</span>';
if (r.wouldBeFilteredByLength) filterTags += '<span class="emb-tag filter">Kurz</span>';
if (r.wouldAppearInAutoContext) filterTags += '<span class="emb-tag pass">AutoContext</span>';
else if (r.wouldPassThreshold) filterTags += '<span class="emb-tag threshold">Threshold OK</span>';
var hasFullText = r.fullText && r.fullText.length > (r.textPreview || '').length;
html += '<div class="emb-result-item">' +
'<div class="emb-result-header">' +
'<span class="emb-rank">#' + (i + 1) + '</span>' +
'<span class="emb-score ' + scoreClass + '">' + r.score.toFixed(4) + '</span>' +
'<span class="emb-role">' + esc(r.role) + '</span>' +
'<span class="emb-date">' + esc(r.createdAt ? r.createdAt.substring(0, 16) : '') + '</span>' +
filterTags +
(hasFullText ? '<span class="emb-tag" style="cursor:pointer;background:var(--bg-secondary)" onclick="this.closest(\'.emb-result-item\').querySelector(\'.emb-fulltext\').style.display=this.closest(\'.emb-result-item\').querySelector(\'.emb-fulltext\').style.display===\'none\'?\'block\':\'none\'">Full</span>' : '') +
'</div>' +
'<div class="emb-result-text">' + esc(r.textPreview || '(kein Preview)') + '</div>' +
(hasFullText ? '<div class="emb-fulltext" style="display:none;margin-top:0.3rem;padding:0.5rem;background:var(--bg-secondary);border-radius:4px;font-size:0.8rem;white-space:pre-wrap;max-height:300px;overflow-y:auto">' + esc(r.fullText) + '</div>' : '') +
'<div class="emb-result-meta">MsgID: ' + r.messageId + ' | Session: ' + esc(r.sessionId || '').substring(0, 8) + ' | Chain: ' + esc(r.chainId || '').substring(0, 8) + '</div>' +
'</div>';
});
html += '</div>';
}
if (memory && memory.length > 0) {
html += '<h3 style="margin:1rem 0 0.5rem">Memory-Ergebnisse</h3>';
html += '<div class="emb-results">';
memory.forEach(function(r, i) {
var scoreClass = r.score >= 0.35 ? 'score-good' : (r.score >= 0.2 ? 'score-medium' : 'score-low');
var hasFullText = r.fullText && r.fullText.length > (r.textPreview || '').length;
html += '<div class="emb-result-item">' +
'<div class="emb-result-header">' +
'<span class="emb-rank">#' + (i + 1) + '</span>' +
'<span class="emb-score ' + scoreClass + '">' + r.score.toFixed(4) + '</span>' +
'<span class="emb-role">memory</span>' +
'<span class="emb-date">' + esc(r.filePath || '') + '</span>' +
(r.score >= 0.35 ? '<span class="emb-tag pass">AutoContext</span>' : '') +
(hasFullText ? '<span class="emb-tag" style="cursor:pointer;background:var(--bg-secondary)" onclick="this.closest(\'.emb-result-item\').querySelector(\'.emb-fulltext\').style.display=this.closest(\'.emb-result-item\').querySelector(\'.emb-fulltext\').style.display===\'none\'?\'block\':\'none\'">Full</span>' : '') +
'</div>' +
'<div class="emb-result-text">' + esc(r.textPreview || '(kein Preview)') + '</div>' +
(hasFullText ? '<div class="emb-fulltext" style="display:none;margin-top:0.3rem;padding:0.5rem;background:var(--bg-secondary);border-radius:4px;font-size:0.8rem;white-space:pre-wrap;max-height:300px;overflow-y:auto">' + esc(r.fullText) + '</div>' : '') +
'</div>';
});
html += '</div>';
}
if (!html) {
html = '<p style="color:var(--text-muted)">Keine Ergebnisse gefunden (auch nicht unter Threshold).</p>';
}
resultsEl.innerHTML = html;
}
function renderStats(data) {
var html = '<h3 style="margin-bottom:0.5rem">Embedding-Statistiken</h3>';
html += '<div class="emb-summary-grid">' +
'<div class="emb-stat"><span class="emb-stat-num">' + (data.totalMessages || 0).toLocaleString() + '</span><span class="emb-stat-label">Message-Embeddings</span></div>' +
'<div class="emb-stat"><span class="emb-stat-num">' + (data.totalMemory || 0).toLocaleString() + '</span><span class="emb-stat-label">Memory-Chunks</span></div>' +
'</div>';
if (data.perAgent && data.perAgent.length > 0) {
html += '<h3 style="margin:1rem 0 0.5rem">Pro Agent</h3>';
html += '<table class="db-table"><thead><tr><th>Agent</th><th>Messages</th><th>Memory</th></tr></thead><tbody>';
data.perAgent.forEach(function(a) {
html += '<tr><td>' + esc(a.agentName || a.agentId || '(kein Agent)') + '</td>' +
'<td class="db-table-num">' + (a.messageCount || 0).toLocaleString() + '</td>' +
'<td class="db-table-num">' + (a.memoryChunks || 0).toLocaleString() + '</td></tr>';
});
html += '</tbody></table>';
}
resultsEl.innerHTML = html;
}
}
});