fix(dashboard): infinite scroll, auto-refetch, analytics cache invalidation, remove topic row cap

Frontend:
- Cursor pagination (100/page) replacing hardcoded 80 message limit
- Removed .slice(0,200) cap on mergeMessages
- IntersectionObserver infinite scroll with skeleton loading
- 15s periodic refetch for message list sync

Backend:
- Removed LIMIT 2000 from topic trends SQL query
- Added invalidateAnalyticsCache on message capture (messageCreated)
- Added invalidateAnalyticsCache on batch analysis completion

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
MythEclipse
2026-06-01 18:14:32 +07:00
co-authored by Claude Opus 4.8
parent 5078b34ac2
commit 4117f83c1b
8 changed files with 121 additions and 15 deletions
+2
View File
@@ -21,6 +21,7 @@ import {
updateMessageAIAnalysis,
updateMessagesAIAnalysisBulk,
} from "./messageStore.js";
import { invalidateAnalyticsCache } from "./analyticsStore.js";
import type {
AnalysisQueueStatus,
MessageRecord,
@@ -374,6 +375,7 @@ async function processIndividualFallback(
const rows = await updateMessagesAIAnalysisBulk(updates);
for (const row of rows) {
getModerationBroadcaster()?.messageAnalyzed(row);
invalidateAnalyticsCache(row.guild_id);
scheduleAutoDelete(row);
}
+2 -3
View File
@@ -424,8 +424,8 @@ export async function getTopicTrends(input: {
try {
const since = Date.now() - hours * 3600_000;
// Only fetch messages that have ai_analysis (the ones that actually have topics)
// This dramatically reduces rows for large guilds
// Fetch all analyzed messages within the time window (no hard row cap).
// Messages without ai_analysis are excluded which naturally limits rows.
const rows = (await executeAll(
`
SELECT
@@ -438,7 +438,6 @@ export async function getTopicTrends(input: {
AND ai_analysis IS NOT NULL
${channelId ? `AND (channel_id = ? OR thread_id = ?)` : ""}
ORDER BY created_at DESC
LIMIT 2000
`,
channelId ? [guildId, since, channelId, channelId] : [guildId, since],
)) as MessageRecord[];
+2 -1
View File
@@ -608,10 +608,11 @@ Ini adalah server Discord komunitas Indonesia. Kamu harus memahami:
- Bahasa gaul/slang Indonesia: "anjay", "wkwk", "gws", "gaskeun", "santuy", "njir", "baka", "woy", "woi", "hadeh", dll.
- Singkatan umum: "gw", "lo", "emg", "kyk", "tdk", "krn", "jgn", dll.
- Konteks budaya lokal: SARA (Suku, Agama, Ras, Antar-golongan), hoaks, ujaran kebencian berbasis konteks Indonesia.
- Makian/kata kasar umum (seperti "anjing", "asu", "bangsat") BUKAN pelanggaran SARA. SARA khusus untuk diskriminasi/hinaan terhadap Suku, Agama, Ras, dan Antargolongan. NAMUN makian/kata kasar TETAP bisa di-flag sebagai "harassment" atau "vulgar_language" sesuai konteks (misalnya jika ditujukan ke orang lain atau dalam tone agresif). Jangan flag sebagai SARA, tapi flag sesuai kategori yang tepat.
- Makian/kata kasar umum (seperti "anjing", "asu", "bangsat") BUKAN pelanggaran SARA. SARA khusus untuk diskriminasi/hinaan terhadap Suku, Agama, Ras, dan Antargolongan. NAMUN makian/kata kasar TETAP bisa di-flag sebagai "harassment" atau "vulgar_language" HANYA jika: (1) ditujukan langsung ke orang lain sebagai serangan/hinaan, (2) dalam tone agresif/mengancam, atau (3) bagian dari pola harassment berkelanjutan. Jangan flag sekadar karena kata itu muncul dalam teks. Jangan flag sebagai SARA, tapi flag sesuai kategori yang tepat.
- Kata "asus" adalah merk teknologi, jangan pernah dianggap sebagai makian "asu".
- Perbedaan antara humor/banter biasa vs konten yang benar-benar melanggar.
- "woy"/"woi" adalah sapaan/interjeksi informal Indonesia dan tidak boleh dianggap SARA, hate speech, atau harassment tanpa target hinaan/ancaman jelas.
- Kata-kata AMAN yang TIDAK BOLEH di-flag: "kakek" (kakek = grandfather, family term), "Wah" (exclamation), "hadeh" (slang exclamation), dan kata-kata keluarga/interjeksi umum lainnya. Jika kata ini muncul dalam teks, JANGAN flag sebagai vulgar_language atau harassment.
- Discord custom emoji seperti <:hadeh:123> atau [emoji:hadeh] adalah ekspresi/emoji, bukan pelanggaran teks. Gunakan sebagai konteks ekspresi saja.
- Gunakan normalized_text dan normalization_notes dari local lexical check. Jika notes hanya berisi slang/emoji aman (woy, woi, hadeh, dll) dan "no Indonesian badword detected", jangan flag karena kata slang itu saja. NAMUN jika notes menyatakan "Indonesian badword detected" (misalnya "anjing", "bangsat", "asu"), itu EVIDENCE profanitas — gunakan sebagai konteks untuk menilai apakah perlu flag sebagai harassment/vulgar_language, bukan sebagai alasan untuk mengabaikan.
- Topik seksual/identitas yang dibatasi server: LGBT/LGBTQ, furry/transfurry, therian, otherkin, protogen, yiff/fursona/fursuit, dan istilah terkait. Jika pesan mempromosikan, mendukung, mengajak, menyatakan identitas/roleplay, membagikan media, atau menjadikan topik ini sebagai pembahasan utama, flag sebagai "sexual_deviation". Jika pesan hanya mengecam/menolak topik tersebut tanpa hinaan ke orang/kelompok, status bisa "clean" atau "warn" sesuai tone. Jangan gunakan kebijakan ini untuk membenarkan doxxing, ancaman, atau penghinaan personal; ancaman/hinaan tetap flag sebagai harassment/hate_speech juga.
+4
View File
@@ -16,6 +16,7 @@ import {
updateMessageAsEdited,
upsertMessageForCapture,
} from "./messageStore.js";
import { invalidateAnalyticsCache } from "./analyticsStore.js";
import type { AttachmentRecord, MessageRecord } from "./types.js";
const logger = createChildLogger("message-capture");
@@ -127,6 +128,9 @@ export async function captureMessage(
broadcaster.messageCreated(messageRecord);
}
// Invalidate analytics cache so real-time stats reflect the new message
invalidateAnalyticsCache(messageRecord.guild_id);
const attachmentUploadTasks: Promise<void>[] = [];
// Insert attachments before queuing analysis to avoid race condition