diff --git a/frontend/src/App.tsx b/frontend/src/App.tsx index 78f0074..8ac8cd1 100644 --- a/frontend/src/App.tsx +++ b/frontend/src/App.tsx @@ -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) => { diff --git a/frontend/src/hooks/useAnalytics.ts b/frontend/src/hooks/useAnalytics.ts index ae14be3..da7dbb9 100644 --- a/frontend/src/hooks/useAnalytics.ts +++ b/frontend/src/hooks/useAnalytics.ts @@ -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(null); const [loading, setLoading] = useState(false); const [error, setError] = useState(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, diff --git a/frontend/src/hooks/useDashboardSocket.ts b/frontend/src/hooks/useDashboardSocket.ts index 5bfba84..865de3e 100644 --- a/frontend/src/hooks/useDashboardSocket.ts +++ b/frontend/src/hooks/useDashboardSocket.ts @@ -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) {