fix(shorts): живые обновления страницы и лимит на вызовы ИИ

- shorts.js подписан на SSE shorts_changed с дебаунсом и реконнектом
- dispatchEvent больше не отдаёт любой неизвестный тип как
  entries_changed: entries_changed/lessons_changed/shorts_changed
  теперь маршрутизируются по имени (иначе правка короткого
  сообщения будила журнал с ложным тостом о новой записи)
- добавлен aiLimiter (30/мин на cache.rateLimitStore('ai')) и
  навешен на оба вызова модели: /api/ai/correct и
  /api/shorts/ai-correct
This commit is contained in:
dev
2026-10-06 00:09:28 +03:00
parent d00de2cb1a
commit 9f815e5143
2 changed files with 41 additions and 2 deletions
+26
View File
@@ -25,9 +25,35 @@ async function init() {
document.getElementById('shortsSearch').addEventListener('keydown', e => {
if (e.key === 'Enter') { shortsPage = 1; loadShorts(); }
});
connectShortsLive();
await loadShorts();
}
let shortsLiveSource = null;
let shortsRefreshTimer = null;
function connectShortsLive() {
if (shortsLiveSource) { shortsLiveSource.close(); shortsLiveSource = null; }
const connect = () => {
const t = sessionStorage.getItem('authToken');
if (!t) return;
shortsLiveSource = new EventSource(`${API}/api/events?token=${encodeURIComponent(t)}`);
shortsLiveSource.addEventListener('shorts_changed', () => {
if (shortsRefreshTimer) return;
shortsRefreshTimer = setTimeout(() => {
shortsRefreshTimer = null;
loadShorts();
}, 300);
});
shortsLiveSource.onerror = () => {
if (shortsLiveSource) { shortsLiveSource.close(); shortsLiveSource = null; }
setTimeout(connect, 5000);
};
};
connect();
window.addEventListener('beforeunload', () => { if (shortsLiveSource) shortsLiveSource.close(); });
}
let shortsPage = 1;
const SHORTS_PAGE_SIZE = 50;
let shortsTotal = 0;