2:I[7012,["4765","static/chunks/4765-f5afdf8061f456f3.js","9856","static/chunks/9856-3b185291364d9bef.js","6687","static/chunks/app/docs/%5B...slug%5D/page-e07536548216bee4.js"],"MarkdownRenderer"] 4:I[9856,["4765","static/chunks/4765-f5afdf8061f456f3.js","9856","static/chunks/9856-3b185291364d9bef.js","6687","static/chunks/app/docs/%5B...slug%5D/page-e07536548216bee4.js"],""] 5:I[4126,[],""] 7:I[9630,[],""] 8:I[4278,["9856","static/chunks/9856-3b185291364d9bef.js","8172","static/chunks/8172-b3a2d6fe4ae10d40.js","3185","static/chunks/app/layout-2814fa5d15b84fe4.js"],"HeadingProvider"] 9:I[1476,["9856","static/chunks/9856-3b185291364d9bef.js","8172","static/chunks/8172-b3a2d6fe4ae10d40.js","3185","static/chunks/app/layout-2814fa5d15b84fe4.js"],"Header"] a:I[3167,["9856","static/chunks/9856-3b185291364d9bef.js","8172","static/chunks/8172-b3a2d6fe4ae10d40.js","3185","static/chunks/app/layout-2814fa5d15b84fe4.js"],"Sidebar"] b:I[7409,["9856","static/chunks/9856-3b185291364d9bef.js","8172","static/chunks/8172-b3a2d6fe4ae10d40.js","3185","static/chunks/app/layout-2814fa5d15b84fe4.js"],"PageFrame"] 3:T4619, # Real-Time Events Guide This guide covers the real-time event system that powers live updates in the VoiceAssist admin panel. ## Overview The admin panel uses a real-time event system to provide live visibility into platform activity. Events are published from various backend services via Redis pub/sub and delivered to admin clients via WebSocket. ``` ┌──────────────┐ ┌──────────────┐ ┌──────────────┐ │ Backend │ │ Backend │ │ Backend │ │ Service A │ │ Service B │ │ Service C │ └──────┬───────┘ └──────┬───────┘ └──────┬───────┘ │ │ │ └─────────┬─────────┴─────────┬─────────┘ │ │ ▼ ▼ ┌───────────────────────────────────┐ │ Redis Pub/Sub │ │ Channel: admin:events │ └───────────────┬───────────────────┘ │ ▼ ┌───────────────────────────────────┐ │ API Gateway │ │ WebSocket: /api/admin/panel/ws │ └───────────────┬───────────────────┘ │ ┌───────────────┼───────────────┐ │ │ │ ▼ ▼ ▼ ┌─────────┐ ┌─────────┐ ┌─────────┐ │ Admin 1 │ │ Admin 2 │ │ Admin 3 │ └─────────┘ └─────────┘ └─────────┘ ``` ## Event Types ### Session Events | Event | Description | Triggered By | | ---------------------- | ------------------------------------- | ----------------- | | `session.connected` | User WebSocket connection established | WebSocket connect | | `session.disconnected` | User WebSocket connection closed | WebSocket close | **Payload:** ```json { "type": "session.connected", "timestamp": "2025-12-01T16:00:00Z", "user_id": "user-uuid", "user_email": "user@example.com", "session_id": "ws-session-uuid", "data": { "session_type": "web" } } ``` ### Conversation Events | Event | Description | Triggered By | | ---------------------- | ------------------------ | --------------------------------- | | `conversation.created` | New conversation started | First message in new conversation | | `conversation.updated` | Conversation modified | Title change, settings update | | `conversation.deleted` | Conversation removed | User or admin deletion | **Payload:** ```json { "type": "conversation.created", "timestamp": "2025-12-01T16:00:00Z", "user_id": "user-uuid", "resource_id": "conv-uuid", "resource_type": "conversation", "data": { "title": "New Conversation" } } ``` ### Message Events | Event | Description | Triggered By | | ----------------- | --------------------------------- | ------------------------- | | `message.created` | New message added to conversation | User input or AI response | **Payload:** ```json { "type": "message.created", "timestamp": "2025-12-01T16:00:00Z", "user_id": "user-uuid", "resource_id": "msg-uuid", "resource_type": "message", "data": { "conversation_id": "conv-uuid", "role": "user" } } ``` ### Clinical Context Events | Event | Description | Triggered By | | -------------------------- | ---------------------------- | --------------- | | `clinical_context.created` | New clinical context created | First PHI entry | | `clinical_context.updated` | Context modified | PHI update | **Payload:** ```json { "type": "clinical_context.updated", "timestamp": "2025-12-01T16:00:00Z", "user_id": "user-uuid", "resource_id": "ctx-uuid", "resource_type": "clinical_context", "data": { "fields_updated": ["medications", "allergies"] } } ``` ### PHI Events | Event | Description | Triggered By | | -------------- | -------------------------- | --------------------- | | `phi.accessed` | PHI data revealed by admin | Admin reveal action | | `phi.detected` | PHI detected in message | PHI detection service | **Payload (phi.accessed):** ```json { "type": "phi.accessed", "timestamp": "2025-12-01T16:00:00Z", "user_id": "admin-uuid", "user_email": "admin@example.com", "resource_id": "ctx-uuid", "resource_type": "clinical_context", "data": { "target_user_id": "user-uuid" } } ``` ### Voice Events | Event | Description | Triggered By | | ----------------------- | ----------------------- | ----------------------- | | `voice.session_started` | Voice session began | Voice mode activation | | `voice.session_ended` | Voice session completed | Voice mode deactivation | | `voice.session_error` | Voice session error | Processing failure | **Payload:** ```json { "type": "voice.session_started", "timestamp": "2025-12-01T16:00:00Z", "user_id": "user-uuid", "session_id": "voice-session-uuid", "resource_type": "voice_session", "data": { "session_type": "realtime", "voice": "alloy" } } ``` ### TT Pipeline Events | Event | Description | Triggered By | | -------------------- | ----------------------------- | ------------------------ | | `tt.state_changed` | TT pipeline state transition | State machine transition | | `tt.tool_called` | Tool execution in TT pipeline | Tool invocation | | `tt.context_created` | New TT context window | Context initialization | | `tt.context_expired` | TT context expired | TTL expiration | **Payload (tt.state_changed):** ```json { "type": "tt.state_changed", "timestamp": "2025-12-01T16:00:00Z", "user_id": "user-uuid", "session_id": "tt-session-uuid", "resource_type": "tt_session", "data": { "new_state": "thinking", "previous_state": "listening" } } ``` ### System Events | Event | Description | Triggered By | | ----------------------- | ------------------------- | ------------------- | | `system.alert` | System alert notification | Monitoring triggers | | `system.health_changed` | Health status change | Health check | **Payload:** ```json { "type": "system.alert", "timestamp": "2025-12-01T16:00:00Z", "data": { "alert_type": "high_latency", "message": "TT pipeline latency exceeds threshold", "severity": "warning", "details": { "current_latency_ms": 2500, "threshold_ms": 2000 } } } ``` ### User Events | Event | Description | Triggered By | | ----------------- | ------------------- | ---------------------- | | `user.logged_in` | User login | Authentication success | | `user.logged_out` | User logout | Session termination | | `user.created` | New user registered | Registration | ## Backend Integration ### Publishing Events Use the `AdminEventPublisher` service to publish events from backend services: ```python from app.services.admin_event_publisher import ( AdminEvent, AdminEventType, AdminEventPublisher, ) # Get the publisher singleton publisher = AdminEventPublisher.get_instance() # Create and publish an event event = AdminEvent( event_type=AdminEventType.VOICE_SESSION_STARTED, user_id="user-uuid", session_id="session-uuid", data={ "session_type": "realtime", "voice": "alloy" } ) await publisher.publish(event) ``` ### Convenience Functions For common events, use the provided convenience functions: ```python from app.services.admin_event_publisher import ( publish_session_connected, publish_conversation_created, publish_message_created, publish_voice_session_started, publish_voice_session_ended, publish_tt_state_changed, publish_phi_accessed, publish_system_alert, ) # Session events await publish_session_connected( user_id="user-uuid", user_email="user@example.com", session_id="ws-uuid", session_type="web" ) # Voice events await publish_voice_session_started( user_id="user-uuid", session_id="voice-uuid", session_type="realtime", voice="alloy" ) # TT pipeline events await publish_tt_state_changed( user_id="user-uuid", session_id="tt-uuid", new_state="thinking", previous_state="listening" ) # PHI audit events (published immediately) await publish_phi_accessed( admin_user_id="admin-uuid", admin_email="admin@example.com", context_id="ctx-uuid", target_user_id="user-uuid" ) # System alerts (published immediately) await publish_system_alert( alert_type="high_latency", message="TT pipeline latency exceeds threshold", severity="warning", details={"current_latency_ms": 2500} ) ``` ### Event Buffering Most events are buffered for efficiency: - **Buffer size**: 50 events - **Flush interval**: 1 second - **Immediate events**: PHI access, system alerts, voice session start, TT state changes ```python # Regular publish (buffered) await publisher.publish(event) # Immediate publish (bypasses buffer) await publisher.publish_immediate(event) ``` ### Starting the Publisher Initialize the publisher during application startup: ```python from fastapi import FastAPI from app.services.admin_event_publisher import AdminEventPublisher app = FastAPI() @app.on_event("startup") async def startup(): publisher = AdminEventPublisher.get_instance() await publisher.start() @app.on_event("shutdown") async def shutdown(): publisher = AdminEventPublisher.get_instance() await publisher.stop() ``` ## Frontend Integration ### Using the React Hook The `useRealtimeEvents` hook provides a complete interface for consuming real-time events: ```tsx import { useRealtimeEvents } from "@/hooks/useRealtimeEvents"; function EventMonitor() { const { status, events, metrics, lastEventTime, reconnectAttempts, connect, disconnect, clearEvents, subscribe } = useRealtimeEvents({ autoConnect: true, reconnectInterval: 5000, maxReconnectAttempts: 10, eventFilter: ["voice.session_started", "voice.session_ended"], onEvent: (event) => { console.log("Event received:", event); }, onMetrics: (metrics) => { console.log("Metrics update:", metrics); }, onConnectionChange: (status) => { console.log("Connection status:", status); }, }); return (
Status: {status}
Last event: {lastEventTime}
Reconnect attempts: {reconnectAttempts}
); } ``` ### Hook Options | Option | Type | Default | Description | | ---------------------- | -------- | --------- | -------------------- | | `autoConnect` | boolean | true | Connect on mount | | `reconnectInterval` | number | 5000 | Reconnect delay (ms) | | `maxReconnectAttempts` | number | 10 | Max reconnect tries | | `eventFilter` | string[] | undefined | Filter event types | | `onEvent` | function | undefined | Event callback | | `onMetrics` | function | undefined | Metrics callback | | `onConnectionChange` | function | undefined | Status callback | ### Hook Return Values | Value | Type | Description | | ------------------- | ---------------- | --------------------------------------------------------- | | `status` | ConnectionStatus | connecting, connected, reconnecting, disconnected, failed | | `events` | AdminEvent[] | Event buffer (max 100) | | `metrics` | MetricsUpdate | Latest metrics | | `lastEventTime` | string | Timestamp of last event | | `reconnectAttempts` | number | Current reconnect count | | `connect` | function | Manual connect | | `disconnect` | function | Manual disconnect | | `clearEvents` | function | Clear event buffer | | `subscribe` | function | Subscribe to event types | ### Event Listener Hook For listening to specific event types: ```tsx import { useAdminEventListener } from "@/hooks/useRealtimeEvents"; function VoiceSessionAlert() { const voiceEvents = useAdminEventListener(["voice.session_started", "voice.session_ended"], (event) => { if (event.type === "voice.session_started") { showNotification("Voice session started"); } }); return
Active voice sessions: {voiceEvents.filter((e) => e.type === "voice.session_started").length}
; } ``` ## WebSocket Protocol ### Connection ```javascript const ws = new WebSocket("wss://admin.asimo.io/api/admin/panel/ws"); ``` ### Message Types **Client → Server:** | Type | Description | | ----------- | ------------------------ | | `ping` | Keep-alive ping | | `subscribe` | Subscribe to event types | **Server → Client:** | Type | Description | | ---------------- | ---------------------- | | `connected` | Connection confirmed | | `pong` | Keep-alive response | | `heartbeat` | Server heartbeat | | `admin_event` | Admin event | | `metrics_update` | Metrics snapshot | | `subscribed` | Subscription confirmed | ### Subscribing to Events ```json // Client sends { "type": "subscribe", "payload": { "event_types": ["voice.session_started", "voice.session_ended"] } } // Server responds { "type": "subscribed", "payload": { "event_types": ["voice.session_started", "voice.session_ended"] } } ``` ### Keep-Alive The client should send pings every 30 seconds: ```json // Client sends { "type": "ping" } // Server responds { "type": "pong" } ``` ## Configuration ### Redis Configuration ```env REDIS_URL=redis://localhost:6379 ADMIN_EVENTS_CHANNEL=admin:events ``` ### Publisher Configuration ```python # In admin_event_publisher.py ADMIN_EVENTS_CHANNEL = "admin:events" class AdminEventPublisher: _buffer_size = 50 # Events before auto-flush _flush_interval = 1.0 # Seconds between flushes ``` ### WebSocket Configuration ```python # In admin_panel.py ADMIN_EVENTS_CHANNEL = "admin:events" HEARTBEAT_INTERVAL = 30 # seconds ``` ## Monitoring ### Redis Commands ```bash # Monitor all pub/sub messages redis-cli MONITOR # Subscribe to admin events channel redis-cli SUBSCRIBE admin:events # Check pub/sub stats redis-cli PUBSUB CHANNELS redis-cli PUBSUB NUMSUB admin:events ``` ### Metrics The WebSocket provides periodic metrics updates: ```json { "type": "metrics_update", "payload": { "active_websocket_sessions": 5, "database_pool": { "pool_size": 20, "checked_out": 3, "overflow": 0 }, "redis_pool": { "total_connections": 10, "available_connections": 8 }, "timestamp": "2025-12-01T16:00:00Z" } } ``` ## Troubleshooting ### Events Not Arriving 1. **Check Redis connection:** ```bash redis-cli ping ``` 2. **Verify publisher is started:** ```python # Check in application startup logger.info("AdminEventPublisher started") ``` 3. **Monitor Redis channel:** ```bash redis-cli SUBSCRIBE admin:events ``` 4. **Check WebSocket connection:** - Browser DevTools → Network → WS - Verify connection status is "connected" ### WebSocket Disconnects 1. **Check for CORS issues:** ```env ADMIN_PANEL_CORS_ORIGINS=https://admin.asimo.io ``` 2. **Verify authentication:** - JWT token must be valid - Admin role must be present 3. **Check reconnection:** - Hook automatically reconnects up to `maxReconnectAttempts` - Exponential backoff between attempts ### High Event Volume 1. **Adjust buffer settings:** ```python publisher._buffer_size = 100 publisher._flush_interval = 2.0 ``` 2. **Filter events on client:** ```tsx useRealtimeEvents({ eventFilter: ["voice.session_started"], // Only receive voice events }); ``` 3. **Implement event aggregation:** - Aggregate similar events before publishing - Use batching for high-frequency events 6:["slug","admin/REALTIME_EVENTS_GUIDE","c"] 0:["X7oMT3VrOffzp0qvbeOas",[[["",{"children":["docs",{"children":[["slug","admin/REALTIME_EVENTS_GUIDE","c"],{"children":["__PAGE__?{\"slug\":[\"admin\",\"REALTIME_EVENTS_GUIDE\"]}",{}]}]}]},"$undefined","$undefined",true],["",{"children":["docs",{"children":[["slug","admin/REALTIME_EVENTS_GUIDE","c"],{"children":["__PAGE__",{},[["$L1",["$","div",null,{"children":[["$","div",null,{"className":"mb-6 flex items-center justify-between gap-4","children":[["$","div",null,{"children":[["$","p",null,{"className":"text-sm text-gray-500 dark:text-gray-400","children":"Docs / Raw"}],["$","h1",null,{"className":"text-3xl font-bold text-gray-900 dark:text-white","children":"Real-Time Events Guide"}],["$","p",null,{"className":"text-sm text-gray-600 dark:text-gray-400","children":["Sourced from"," ",["$","code",null,{"className":"font-mono text-xs","children":["docs/","admin/REALTIME_EVENTS_GUIDE.md"]}]]}]]}],["$","a",null,{"href":"https://github.com/mohammednazmy/VoiceAssist/edit/main/docs/admin/REALTIME_EVENTS_GUIDE.md","target":"_blank","rel":"noreferrer","className":"inline-flex items-center gap-2 rounded-md border border-gray-200 dark:border-gray-700 px-3 py-1.5 text-sm text-gray-700 dark:text-gray-200 hover:border-primary-500 dark:hover:border-primary-400 hover:text-primary-700 dark:hover:text-primary-300","children":"Edit on GitHub"}]]}],["$","div",null,{"className":"rounded-lg border border-gray-200 dark:border-gray-800 bg-white dark:bg-gray-900 p-6","children":["$","$L2",null,{"content":"$3"}]}],["$","div",null,{"className":"mt-6 flex flex-wrap gap-2 text-sm","children":[["$","$L4",null,{"href":"/reference/all-docs","className":"inline-flex items-center gap-1 rounded-md bg-gray-100 px-3 py-1 text-gray-700 hover:bg-gray-200 dark:bg-gray-800 dark:text-gray-200 dark:hover:bg-gray-700","children":"← All documentation"}],["$","$L4",null,{"href":"/","className":"inline-flex items-center gap-1 rounded-md bg-gray-100 px-3 py-1 text-gray-700 hover:bg-gray-200 dark:bg-gray-800 dark:text-gray-200 dark:hover:bg-gray-700","children":"Home"}]]}]]}],null],null],null]},[null,["$","$L5",null,{"parallelRouterKey":"children","segmentPath":["children","docs","children","$6","children"],"error":"$undefined","errorStyles":"$undefined","errorScripts":"$undefined","template":["$","$L7",null,{}],"templateStyles":"$undefined","templateScripts":"$undefined","notFound":"$undefined","notFoundStyles":"$undefined"}]],null]},[null,["$","$L5",null,{"parallelRouterKey":"children","segmentPath":["children","docs","children"],"error":"$undefined","errorStyles":"$undefined","errorScripts":"$undefined","template":["$","$L7",null,{}],"templateStyles":"$undefined","templateScripts":"$undefined","notFound":"$undefined","notFoundStyles":"$undefined"}]],null]},[[[["$","link","0",{"rel":"stylesheet","href":"/_next/static/css/7f586cdbbaa33ff7.css","precedence":"next","crossOrigin":"$undefined"}]],["$","html",null,{"lang":"en","className":"h-full","children":["$","body",null,{"className":"__className_f367f3 h-full bg-white dark:bg-gray-900","children":[["$","a",null,{"href":"#main-content","className":"skip-to-content","children":"Skip to main content"}],["$","$L8",null,{"children":[["$","$L9",null,{}],["$","$La",null,{}],["$","main",null,{"id":"main-content","className":"lg:pl-64","role":"main","aria-label":"Documentation content","children":["$","$Lb",null,{"children":["$","$L5",null,{"parallelRouterKey":"children","segmentPath":["children"],"error":"$undefined","errorStyles":"$undefined","errorScripts":"$undefined","template":["$","$L7",null,{}],"templateStyles":"$undefined","templateScripts":"$undefined","notFound":[["$","title",null,{"children":"404: This page could not be found."}],["$","div",null,{"style":{"fontFamily":"system-ui,\"Segoe UI\",Roboto,Helvetica,Arial,sans-serif,\"Apple Color Emoji\",\"Segoe UI Emoji\"","height":"100vh","textAlign":"center","display":"flex","flexDirection":"column","alignItems":"center","justifyContent":"center"},"children":["$","div",null,{"children":[["$","style",null,{"dangerouslySetInnerHTML":{"__html":"body{color:#000;background:#fff;margin:0}.next-error-h1{border-right:1px solid rgba(0,0,0,.3)}@media (prefers-color-scheme:dark){body{color:#fff;background:#000}.next-error-h1{border-right:1px solid rgba(255,255,255,.3)}}"}}],["$","h1",null,{"className":"next-error-h1","style":{"display":"inline-block","margin":"0 20px 0 0","padding":"0 23px 0 0","fontSize":24,"fontWeight":500,"verticalAlign":"top","lineHeight":"49px"},"children":"404"}],["$","div",null,{"style":{"display":"inline-block"},"children":["$","h2",null,{"style":{"fontSize":14,"fontWeight":400,"lineHeight":"49px","margin":0},"children":"This page could not be found."}]}]]}]}]],"notFoundStyles":[]}]}]}]]}]]}]}]],null],null],["$Lc",null]]]] c:[["$","meta","0",{"name":"viewport","content":"width=device-width, initial-scale=1"}],["$","meta","1",{"charSet":"utf-8"}],["$","title","2",{"children":"Real-Time Events Guide | Docs | VoiceAssist Docs"}],["$","meta","3",{"name":"description","content":"Guide to the admin panel real-time event system using WebSocket and Redis pub/sub"}],["$","meta","4",{"name":"keywords","content":"VoiceAssist,documentation,medical AI,voice assistant,healthcare,HIPAA,API"}],["$","meta","5",{"name":"robots","content":"index, follow"}],["$","meta","6",{"name":"googlebot","content":"index, follow"}],["$","link","7",{"rel":"canonical","href":"https://assistdocs.asimo.io"}],["$","meta","8",{"property":"og:title","content":"VoiceAssist Documentation"}],["$","meta","9",{"property":"og:description","content":"Comprehensive documentation for VoiceAssist - Enterprise Medical AI Assistant"}],["$","meta","10",{"property":"og:url","content":"https://assistdocs.asimo.io"}],["$","meta","11",{"property":"og:site_name","content":"VoiceAssist Docs"}],["$","meta","12",{"property":"og:type","content":"website"}],["$","meta","13",{"name":"twitter:card","content":"summary"}],["$","meta","14",{"name":"twitter:title","content":"VoiceAssist Documentation"}],["$","meta","15",{"name":"twitter:description","content":"Comprehensive documentation for VoiceAssist - Enterprise Medical AI Assistant"}],["$","meta","16",{"name":"next-size-adjust"}]] 1:null