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:
@@ -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>
|
||||
|
||||
Reference in New Issue
Block a user