forked from sagnik/Velocity-OS
Initial commit: Velocity-OS migration
This commit is contained in:
128
webos/src/shared/hooks/useSentinelWebSocket.ts
Normal file
128
webos/src/shared/hooks/useSentinelWebSocket.ts
Normal file
@@ -0,0 +1,128 @@
|
||||
import { useEffect, useRef, useCallback } from 'react';
|
||||
import { useSentinelStore } from '@/store/sentinelStore';
|
||||
import { useAuthStore } from '@/store/authStore';
|
||||
|
||||
/**
|
||||
* useSentinelWebSocket
|
||||
* Connects to the Sentinel WebSocket endpoint on core-api.
|
||||
* Translates raw CCTV events into store actions.
|
||||
* The broker-facing UI NEVER sees: WebSocket status, connection state,
|
||||
* raw event types, MediaPipe processing info, or frame IDs.
|
||||
*
|
||||
* Events handled:
|
||||
* visitor_detected → setPendingAlert (triggers SentinelAlertBanner)
|
||||
* session_start → setShowroomActive(true)
|
||||
* qd_update → update session QD score
|
||||
* session_end → setShowroomActive(false)
|
||||
* ai_observation → update session AI observation text
|
||||
*/
|
||||
|
||||
interface RawSentinelEvent {
|
||||
type: 'visitor_detected' | 'session_start' | 'qd_update' | 'session_end' | 'ai_observation';
|
||||
session_id?: string;
|
||||
qd_score?: number;
|
||||
qd_trend?: number;
|
||||
zone?: string;
|
||||
matched_person_id?: string;
|
||||
matched_name?: string;
|
||||
face_confidence?: number;
|
||||
ai_observation?: string;
|
||||
peak_qd?: number;
|
||||
}
|
||||
|
||||
interface LiveSession {
|
||||
sessionId: string;
|
||||
qdScore: number;
|
||||
qdTrend: number;
|
||||
currentZone?: string;
|
||||
aiObservation?: string;
|
||||
peakQd?: number;
|
||||
}
|
||||
|
||||
export function useSentinelWebSocket() {
|
||||
const wsRef = useRef<WebSocket | null>(null);
|
||||
const sessionRef = useRef<LiveSession | null>(null);
|
||||
const { token } = useAuthStore.getState();
|
||||
const {
|
||||
setPendingAlert,
|
||||
setShowroomActive,
|
||||
setSessionDuration,
|
||||
setHasInsights,
|
||||
} = useSentinelStore();
|
||||
|
||||
const connect = useCallback(() => {
|
||||
// wss:// on velocity.local, proxied by Traefik → core-api
|
||||
const wsUrl = `wss://${window.location.host}/ws/sentinel?token=${token}`;
|
||||
const ws = new WebSocket(wsUrl);
|
||||
wsRef.current = ws;
|
||||
|
||||
ws.onmessage = (evt) => {
|
||||
let event: RawSentinelEvent;
|
||||
try { event = JSON.parse(evt.data); }
|
||||
catch { return; }
|
||||
|
||||
switch (event.type) {
|
||||
case 'visitor_detected':
|
||||
setPendingAlert({
|
||||
id: String(Date.now()),
|
||||
matchedName: event.matched_name,
|
||||
matchedPersonId:event.matched_person_id,
|
||||
confidence: event.face_confidence,
|
||||
zone: event.zone ?? 'Entrance',
|
||||
timestamp: new Date(),
|
||||
});
|
||||
break;
|
||||
|
||||
case 'session_start':
|
||||
sessionRef.current = {
|
||||
sessionId: event.session_id!,
|
||||
qdScore: 0,
|
||||
qdTrend: 0,
|
||||
};
|
||||
setShowroomActive(true, event.session_id);
|
||||
break;
|
||||
|
||||
case 'qd_update':
|
||||
if (sessionRef.current) {
|
||||
sessionRef.current = {
|
||||
...sessionRef.current,
|
||||
qdScore: event.qd_score ?? 0,
|
||||
qdTrend: event.qd_trend ?? 0,
|
||||
currentZone: event.zone,
|
||||
peakQd: Math.max(sessionRef.current.peakQd ?? 0, event.qd_score ?? 0),
|
||||
};
|
||||
}
|
||||
break;
|
||||
|
||||
case 'ai_observation':
|
||||
if (sessionRef.current) {
|
||||
sessionRef.current = {
|
||||
...sessionRef.current,
|
||||
aiObservation: event.ai_observation,
|
||||
};
|
||||
setHasInsights(true);
|
||||
}
|
||||
break;
|
||||
|
||||
case 'session_end':
|
||||
setShowroomActive(false);
|
||||
break;
|
||||
}
|
||||
};
|
||||
|
||||
ws.onerror = () => { /* silent — broker never sees connection errors */ };
|
||||
ws.onclose = (e) => {
|
||||
if (e.code !== 1000) {
|
||||
// Reconnect after 3s on unexpected disconnect
|
||||
setTimeout(connect, 3000);
|
||||
}
|
||||
};
|
||||
}, [token, setPendingAlert, setShowroomActive, setHasInsights]);
|
||||
|
||||
useEffect(() => {
|
||||
connect();
|
||||
return () => { wsRef.current?.close(1000, 'component unmount'); };
|
||||
}, [connect]);
|
||||
|
||||
return { session: sessionRef.current };
|
||||
}
|
||||
Reference in New Issue
Block a user