feat(dashboard): integrate link suggestions and predictive links into UI

- FastAPI: parse_engram now includes link_suggestions and predictive_links from metadata
- FastAPI: add POST /api/links/accept to create links from suggestions
- Dashboard: new renderCardsWithSuggestions() displays suggestions in each card
- Dashboard: acceptLink() function handles click-to-link
- Dashboard: loadCards() calls renderCardsWithSuggestions()
- Systemd: remove DirectoryNotEmpty from ingest path unit (already present)

Refs: #25 #27
This commit is contained in:
2026-05-31 15:42:46 +02:00
parent 2024e2850d
commit 0ff6db73ea
4 changed files with 102 additions and 24 deletions

View File

@@ -170,7 +170,7 @@ async function loadCards() {
const data = await api(url);
state.items = data.items;
renderCards();
renderCardsWithSuggestions();
document.getElementById('pageNum').textContent = Math.floor(state.offset / state.limit) + 1;
document.getElementById('btnPrev').disabled = state.offset === 0;
document.getElementById('btnNext').disabled = data.items.length < state.limit;
@@ -1114,6 +1114,53 @@ setInterval(() => {
// ─── Init ───────────────────────────────────────────────────────────────────
loadStats();
loadCards();
function renderCardsWithSuggestions() {
const el = document.getElementById('cards');
el.innerHTML = state.items.map(item => {
const suggestions = (item.link_suggestions || []).concat(item.predictive_links || []);
const suggHtml = suggestions.length ? suggestions.map(s => `
<div class="suggestion">
<span class="sugg-id">${s.engram_id.substring(0,8)}</span>
<span class="sugg-preview">${escapeHtml(s.preview || s.content_preview || '')}</span>
<button class="btn-link" onclick="acceptLink('${item.id}', '${s.engram_id}', event)">🔗</button>
</div>
`).join('') : '<span class="muted">Keine Vorschläge</span>';
return `
<div class="card ${item.confirmed ? 'confirmed' : ''} ${item.rejections > 0 ? 'rejected' : ''}" data-id="${item.id}">
<div class="card-header">
<span class="conf-badge" style="background:hsl(${item.confidence*120},70%,40%)">${Math.round(item.confidence*100)}%</span>
${renderVerdictPill(item)}
<span class="tags">${item.tags.map(t => '<span class="tag">'+t+'</span>').join('')}</span>
<span class="date">${fmtDate(item.created)}</span>
</div>
<div class="card-body" onclick="showDetail('${item.id}')">
${escapeHtml(item.content.substring(0, 200))}${item.content.length>200?'...':''}
</div>
<div class="suggestions">
<strong>Vorschläge:</strong> ${suggHtml}
</div>
<div class="card-footer">
<input type="text" class="reason-input" placeholder="Grund (optional)" id="reason-${item.id}"/>
<div class="actions">
<button class="btn-ok" onclick="confirm('${item.id}', event)">✅</button>
<button class="btn-no" onclick="reject('${item.id}', event)">❌</button>
<button class="btn-archive" onclick="refresh('${item.id}', event)">🔄</button>
</div>
</div>
</div>
`).join('');
}
async function acceptLink(fromId, toId, ev) {
ev.stopPropagation();
await api('/api/links/accept', {
method: 'POST',
headers: {'Content-Type': 'application/x-www-form-urlencoded'},
body: new URLSearchParams({from_id: fromId, to_id: toId})
});
alert('Link erstellt');
await loadCards();
}
</script>
</body>
</html>