feat(dashboard): richer engram detail overlay
This commit is contained in:
@@ -1007,9 +1007,11 @@ function escapeHtml(t) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// ─── Actions ────────────────────────────────────────────────────────────────
|
// ─── Actions ────────────────────────────────────────────────────────────────
|
||||||
async function confirm(id, ev) {
|
async function confirm(id, ev, ctx = 'card') {
|
||||||
ev.stopPropagation();
|
ev.stopPropagation();
|
||||||
const reason = document.getElementById('reason-'+id).value;
|
const reasonElId = (ctx === 'modal') ? ('reason-modal-' + id) : ('reason-' + id);
|
||||||
|
const reasonEl = document.getElementById(reasonElId);
|
||||||
|
const reason = reasonEl ? reasonEl.value : '';
|
||||||
await api(`/api/engrams/${id}/confirm`, {
|
await api(`/api/engrams/${id}/confirm`, {
|
||||||
method: 'POST',
|
method: 'POST',
|
||||||
headers: {'Content-Type': 'application/x-www-form-urlencoded'},
|
headers: {'Content-Type': 'application/x-www-form-urlencoded'},
|
||||||
@@ -1020,9 +1022,11 @@ async function confirm(id, ev) {
|
|||||||
if (state.view === 'status') loadStatus();
|
if (state.view === 'status') loadStatus();
|
||||||
}
|
}
|
||||||
|
|
||||||
async function reject(id, ev) {
|
async function reject(id, ev, ctx = 'card') {
|
||||||
ev.stopPropagation();
|
ev.stopPropagation();
|
||||||
const reason = document.getElementById('reason-'+id).value;
|
const reasonElId = (ctx === 'modal') ? ('reason-modal-' + id) : ('reason-' + id);
|
||||||
|
const reasonEl = document.getElementById(reasonElId);
|
||||||
|
const reason = reasonEl ? reasonEl.value : '';
|
||||||
await api(`/api/engrams/${id}/reject`, {
|
await api(`/api/engrams/${id}/reject`, {
|
||||||
method: 'POST',
|
method: 'POST',
|
||||||
headers: {'Content-Type': 'application/x-www-form-urlencoded'},
|
headers: {'Content-Type': 'application/x-www-form-urlencoded'},
|
||||||
@@ -1058,18 +1062,62 @@ async function createEngram() {
|
|||||||
async function showDetail(id) {
|
async function showDetail(id) {
|
||||||
const item = await api(`/api/engrams/${id}`);
|
const item = await api(`/api/engrams/${id}`);
|
||||||
const body = document.getElementById('modalBody');
|
const body = document.getElementById('modalBody');
|
||||||
|
const links = (item.links || []);
|
||||||
|
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>';
|
||||||
body.innerHTML = `
|
body.innerHTML = `
|
||||||
<h3>Engramm ${item.id.substring(0,8)}</h3>
|
<h3>Engramm <span class="pill">${item.id.substring(0,8)}</span></h3>
|
||||||
<p><b>Confidence:</b> ${Math.round(item.confidence*100)}%</p>
|
<div class="kv-row"><div class="kv-key">verdict</div><div class="kv-val">${renderVerdictPill(item)} <span class="muted small">${Math.round(item.confidence*100)}%</span></div></div>
|
||||||
<p><b>Confirmed:</b> ${item.confirmed ? '✅' : '❌'}</p>
|
<div class="kv-row"><div class="kv-key">source</div><div class="kv-val">${escapeHtml(item.source || '-')}</div></div>
|
||||||
<p><b>Tags:</b> ${item.tags.map(t => '<span class="tag">'+t+'</span>').join(' ')}</p>
|
<div class="kv-row"><div class="kv-key">created</div><div class="kv-val">${fmtDate(item.created)}</div></div>
|
||||||
<p><b>Content:</b></p>
|
<div class="kv-row"><div class="kv-key">modified</div><div class="kv-val">${fmtDate(item.modified)}</div></div>
|
||||||
<div class="detail-content">${escapeHtml(item.content)}</div>
|
<div class="kv-row"><div class="kv-key">access</div><div class="kv-val">${item.access_count ?? 0} • grounding ${item.grounding ?? 0}</div></div>
|
||||||
<p><b>History:</b></p>
|
<div class="kv-row"><div class="kv-key">tags</div><div class="kv-val">${(item.tags || []).map(t => '<span class="tag">'+escapeHtml(t)+'</span>').join(' ') || '-'}</div></div>
|
||||||
|
|
||||||
|
<div style="margin-top:10px"><b>Content</b></div>
|
||||||
|
<div class="detail-content">${escapeHtml(item.content || '')}</div>
|
||||||
|
|
||||||
|
<div class="panel" style="margin:10px 0 0; padding:10px 12px;">
|
||||||
|
<div class="panel-title">Vorschläge</div>
|
||||||
|
<div class="suggestions">${suggHtml}</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="panel" style="margin:10px 0 0; padding:10px 12px;">
|
||||||
|
<div class="panel-title">Links</div>
|
||||||
|
<div>
|
||||||
|
${links.length ? links.map(l => `<span class="pill" style="cursor:pointer" onclick="showDetail('${l}')">${l.substring(0,8)}</span>`).join(' ') : '<span class="muted">none</span>'}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
${Array.isArray(item.evidence) && item.evidence.length ? `
|
||||||
|
<div class="panel" style="margin:10px 0 0; padding:10px 12px;">
|
||||||
|
<div class="panel-title">Evidence</div>
|
||||||
<ul class="history">
|
<ul class="history">
|
||||||
${(item.review_history || []).map(h => `<li>${fmtDate(h.at)} — ${h.action} (${h.note})</li>`).join('')}
|
${item.evidence.map(e => `<li>${escapeHtml(typeof e === 'string' ? e : JSON.stringify(e))}</li>`).join('')}
|
||||||
</ul>
|
</ul>
|
||||||
<p><b>Links:</b> ${item.links?.join(', ') || 'none'}</p>
|
</div>` : ''}
|
||||||
|
|
||||||
|
<div class="panel" style="margin:10px 0 0; padding:10px 12px;">
|
||||||
|
<div class="panel-title">History</div>
|
||||||
|
<ul class="history">
|
||||||
|
${(item.review_history || []).map(h => `<li>${fmtDate(h.at)} — ${escapeHtml(h.action)} ${h.note ? ('(' + escapeHtml(h.note) + ')') : ''}</li>`).join('') || '<li class=\"muted\">-</li>'}
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="card-footer" style="margin-top:10px">
|
||||||
|
<input type="text" class="reason-input" placeholder="Grund (optional)" id="reason-modal-${item.id}"/>
|
||||||
|
<div class="actions">
|
||||||
|
<button class="btn-ok" onclick="confirm('${item.id}', event, 'modal')">✅</button>
|
||||||
|
<button class="btn-no" onclick="reject('${item.id}', event, 'modal')">❌</button>
|
||||||
|
<button class="btn-archive" onclick="refresh('${item.id}', event)">🔄</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
`;
|
`;
|
||||||
document.getElementById('detailModal').classList.add('open');
|
document.getElementById('detailModal').classList.add('open');
|
||||||
}
|
}
|
||||||
@@ -1120,6 +1168,12 @@ setInterval(() => {
|
|||||||
// ─── Init ───────────────────────────────────────────────────────────────────
|
// ─── Init ───────────────────────────────────────────────────────────────────
|
||||||
loadStats();
|
loadStats();
|
||||||
loadCards();
|
loadCards();
|
||||||
|
document.getElementById('detailModal').addEventListener('click', (e) => {
|
||||||
|
if (e.target && e.target.id === 'detailModal') closeModal();
|
||||||
|
});
|
||||||
|
document.addEventListener('keydown', (e) => {
|
||||||
|
if (e.key === 'Escape') closeModal();
|
||||||
|
});
|
||||||
function renderCardsWithSuggestions() {
|
function renderCardsWithSuggestions() {
|
||||||
const el = document.getElementById('cards');
|
const el = document.getElementById('cards');
|
||||||
el.innerHTML = state.items.map(item => {
|
el.innerHTML = state.items.map(item => {
|
||||||
|
|||||||
Reference in New Issue
Block a user