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:
co-authored by
Claude Opus 4.8
parent
fed9f39bd9
commit
9a7b8f569d
+20
-4
@@ -65,13 +65,29 @@ export default function App() {
|
|||||||
userTimelinesRef.current.set(userIdHash, nextStart + audioBuffer.duration);
|
userTimelinesRef.current.set(userIdHash, nextStart + audioBuffer.duration);
|
||||||
}, [isListening]);
|
}, [isListening]);
|
||||||
|
|
||||||
|
const triggerAnalyticsRefresh = useCallback(() => {
|
||||||
|
window.dispatchEvent(new CustomEvent("analytics_refresh"));
|
||||||
|
}, []);
|
||||||
|
|
||||||
const socket = useDashboardSocket({
|
const socket = useDashboardSocket({
|
||||||
onUIState: (state) => setUIState((prev) => ({ ...prev, ...state })),
|
onUIState: (state) => setUIState((prev) => ({ ...prev, ...state })),
|
||||||
onUserState: setActiveSpeakers,
|
onUserState: setActiveSpeakers,
|
||||||
onMessageCreated: (message) => messages.setMessages((prev) => mergeMessages(prev, [message])),
|
onMessageCreated: (message) => {
|
||||||
onMessageUpdated: (message) => messages.setMessages((prev) => prev.map((item) => (item.id === message.id ? { ...item, ...message } as MessageRecord : item))),
|
messages.setMessages((prev) => mergeMessages(prev, [message]));
|
||||||
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])),
|
},
|
||||||
|
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),
|
onAttachmentUploaded: () => messages.fetchMessages(selectedTextChannel).catch(() => undefined),
|
||||||
onMediaState: media.setMediaState,
|
onMediaState: media.setMediaState,
|
||||||
onVoiceRecordingUploaded: (recording) => {
|
onVoiceRecordingUploaded: (recording) => {
|
||||||
|
|||||||
@@ -9,7 +9,7 @@ interface UseAnalyticsOptions {
|
|||||||
refreshIntervalMs?: number;
|
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 [overview, setOverview] = useState<AnalyticsOverview | null>(null);
|
||||||
const [loading, setLoading] = useState(false);
|
const [loading, setLoading] = useState(false);
|
||||||
const [error, setError] = useState<string | null>(null);
|
const [error, setError] = useState<string | null>(null);
|
||||||
@@ -33,7 +33,7 @@ export function useAnalytics({ guildId, channelId, hours = 24, autoRefresh = tru
|
|||||||
load();
|
load();
|
||||||
}, [load]);
|
}, [load]);
|
||||||
|
|
||||||
// Auto-refresh
|
// Auto-refresh on interval
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (!autoRefresh || !guildId) return;
|
if (!autoRefresh || !guildId) return;
|
||||||
intervalRef.current = setInterval(load, refreshIntervalMs);
|
intervalRef.current = setInterval(load, refreshIntervalMs);
|
||||||
@@ -42,6 +42,13 @@ export function useAnalytics({ guildId, channelId, hours = 24, autoRefresh = tru
|
|||||||
};
|
};
|
||||||
}, [load, autoRefresh, refreshIntervalMs, guildId]);
|
}, [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 {
|
return {
|
||||||
overview,
|
overview,
|
||||||
loading,
|
loading,
|
||||||
|
|||||||
@@ -17,6 +17,7 @@ export interface DashboardSocketHandlers {
|
|||||||
onMediaState?: (state: MediaState) => void;
|
onMediaState?: (state: MediaState) => void;
|
||||||
onVoiceRecordingUploaded?: (recording: any) => void;
|
onVoiceRecordingUploaded?: (recording: any) => void;
|
||||||
onPcm?: (data: ArrayBuffer) => void;
|
onPcm?: (data: ArrayBuffer) => void;
|
||||||
|
onAnalyticsRefresh?: () => void;
|
||||||
}
|
}
|
||||||
|
|
||||||
export function useDashboardSocket(handlers: DashboardSocketHandlers) {
|
export function useDashboardSocket(handlers: DashboardSocketHandlers) {
|
||||||
|
|||||||
Reference in New Issue
Block a user