feat(journal): live AI status updates in entry cards via Postgres NOTIFY trigger + SSE ai_status events

This commit is contained in:
dev
2026-09-17 11:14:27 +03:00
parent cc9a9e6c2d
commit bbfde902a5
4 changed files with 102 additions and 4 deletions
+32 -4
View File
@@ -66,7 +66,7 @@ function aiBadge(e) {
const st = e.ai_status;
const label = AI_STATUS_LABELS[st] || `ИИ: ${st}`;
const icon = AI_STATUS_ICONS[st] || 'sparkles';
return `<span class="ai-badge ai-badge-ic ai-badge-${esc(st)}" title="${esc(label)}" role="img" aria-label="${esc(label)}"><i data-lucide="${icon}"></i></span>`;
return `<span class="ai-badge ai-badge-ic ai-badge-${esc(st)}" data-ai-badge="${e.id}" title="${esc(label)}" role="img" aria-label="${esc(label)}"><i data-lucide="${icon}"></i></span>`;
}
function findFile(id) {
@@ -236,6 +236,27 @@ function goPage(p) {
let liveSource = null;
let liveReconnectTimer = null;
let aiRefreshTimer = null;
let entriesRefreshTimer = null;
function applyAiUpdate(update) {
const e = currentEntries.find(x => x.id === update.id);
if (!e) return;
e.ai_status = update.ai_status;
e.ai_error = update.ai_error || null;
if (update.ai_status === 'done') {
if (aiRefreshTimer) return;
aiRefreshTimer = setTimeout(() => { aiRefreshTimer = null; loadEntries(); }, 300);
return;
}
document.querySelectorAll(`[data-ai-badge="${update.id}"]`).forEach(badgeEl => {
badgeEl.outerHTML = aiBadge(e);
});
renderIcons();
const editBadge = document.getElementById('editAiStatus');
if (editId === update.id && editBadge && editBadge.style.display !== 'none') {
renderEditAiStatus(e);
}
}
function connectLiveUpdates() {
if (liveSource) { liveSource.close(); liveSource = null; }
const connect = () => {
@@ -243,9 +264,16 @@ function connectLiveUpdates() {
if (!t) return;
liveSource = new EventSource(`${API}/api/events?token=${encodeURIComponent(t)}`);
liveSource.addEventListener('entries_changed', () => {
showToast('Поступила новая запись — список обновлён');
page = 1;
loadEntries();
if (entriesRefreshTimer) return;
entriesRefreshTimer = setTimeout(() => {
entriesRefreshTimer = null;
showToast('Поступила новая запись — список обновлён');
page = 1;
loadEntries();
}, 300);
});
liveSource.addEventListener('ai_status', ev => {
try { applyAiUpdate(JSON.parse(ev.data)); } catch (err) {}
});
liveSource.onerror = () => {
if (liveSource) { liveSource.close(); liveSource = null; }