feat: make analytics real-time via WebSocket-triggered refresh

- Analytics panel now refreshes immediately when messages are created, updated, deleted, or analyzed
- WebSocket events (message_created, message_updated, message_deleted, message_analyzed) trigger custom event 'analytics_refresh'
- Analytics hook listens to 'analytics_refresh' event for instant updates
- Auto-refresh interval lowered from 60s to 5s as fallback
- Two-tier refresh: real-time push + 5s polling safety net

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
MythEclipse
2026-05-30 16:58:25 +07:00
co-authored by Claude Opus 4.8
parent fed9f39bd9
commit 9a7b8f569d
3 changed files with 30 additions and 6 deletions
+20 -4
View File
@@ -65,13 +65,29 @@ export default function App() {
userTimelinesRef.current.set(userIdHash, nextStart + audioBuffer.duration);
}, [isListening]);
const triggerAnalyticsRefresh = useCallback(() => {
window.dispatchEvent(new CustomEvent("analytics_refresh"));
}, []);
const socket = useDashboardSocket({
onUIState: (state) => setUIState((prev) => ({ ...prev, ...state })),
onUserState: setActiveSpeakers,
onMessageCreated: (message) => messages.setMessages((prev) => mergeMessages(prev, [message])),
onMessageUpdated: (message) => messages.setMessages((prev) => prev.map((item) => (item.id === message.id ? { ...item, ...message } as MessageRecord : item))),
onMessageDeleted: (message) => messages.setMessages((prev) => prev.map((item) => (item.id === message.id ? { ...item, type: "deleted" } : item))),
onMessageAnalyzed: (message) => messages.setMessages((prev) => mergeMessages(prev, [message])),
onMessageCreated: (message) => {
messages.setMessages((prev) => mergeMessages(prev, [message]));
triggerAnalyticsRefresh();
},
onMessageUpdated: (message) => {
messages.setMessages((prev) => prev.map((item) => (item.id === message.id ? { ...item, ...message } as MessageRecord : item)));
triggerAnalyticsRefresh();
},
onMessageDeleted: (message) => {
messages.setMessages((prev) => prev.map((item) => (item.id === message.id ? { ...item, type: "deleted" } : item)));
triggerAnalyticsRefresh();
},
onMessageAnalyzed: (message) => {
messages.setMessages((prev) => mergeMessages(prev, [message]));
triggerAnalyticsRefresh();
},
onAttachmentUploaded: () => messages.fetchMessages(selectedTextChannel).catch(() => undefined),
onMediaState: media.setMediaState,
onVoiceRecordingUploaded: (recording) => {
+9 -2
View File
@@ -9,7 +9,7 @@ interface UseAnalyticsOptions {
refreshIntervalMs?: number;
}
export function useAnalytics({ guildId, channelId, hours = 24, autoRefresh = true, refreshIntervalMs = 60_000 }: UseAnalyticsOptions) {
export function useAnalytics({ guildId, channelId, hours = 24, autoRefresh = true, refreshIntervalMs = 5_000 }: UseAnalyticsOptions) {
const [overview, setOverview] = useState<AnalyticsOverview | null>(null);
const [loading, setLoading] = useState(false);
const [error, setError] = useState<string | null>(null);
@@ -33,7 +33,7 @@ export function useAnalytics({ guildId, channelId, hours = 24, autoRefresh = tru
load();
}, [load]);
// Auto-refresh
// Auto-refresh on interval
useEffect(() => {
if (!autoRefresh || !guildId) return;
intervalRef.current = setInterval(load, refreshIntervalMs);
@@ -42,6 +42,13 @@ export function useAnalytics({ guildId, channelId, hours = 24, autoRefresh = tru
};
}, [load, autoRefresh, refreshIntervalMs, guildId]);
// Real-time refresh via WebSocket-triggered custom event
useEffect(() => {
const handler = () => load();
window.addEventListener("analytics_refresh", handler);
return () => window.removeEventListener("analytics_refresh", handler);
}, [load]);
return {
overview,
loading,
+1
View File
@@ -17,6 +17,7 @@ export interface DashboardSocketHandlers {
onMediaState?: (state: MediaState) => void;
onVoiceRecordingUploaded?: (recording: any) => void;
onPcm?: (data: ArrayBuffer) => void;
onAnalyticsRefresh?: () => void;
}
export function useDashboardSocket(handlers: DashboardSocketHandlers) {