Summary
Add frontend infrastructure to consume the backend SSE event stream (GET /frontend/events/stream) and display live updates in the SmartEM dashboard.
Depends on: DiamondLightSource/smartem-decisions#246 (backend SSE endpoint + agent log shipping)
Why the SSE endpoint needs a hand-written hook
The API client is auto-generated from the backend OpenAPI spec via Orval. Orval generates React Query hooks backed by Axios, which is request-response only -- it doesn't support the EventSource streaming protocol. The generated hook for the SSE endpoint would make a regular GET that either hangs or returns garbage. We need a custom useEventStream hook using the browser's native EventSource API instead.
Implementation steps
Step 1: Regenerate API client
npm run api:fetch:local # Fetch OpenAPI spec from local backend (must be running with new endpoints)
npm run api:generate # Regenerate hooks, types, MSW mocks
This picks up:
AgentLog model type
AgentLogBatchRequest / AgentLogBatchResponse types
- Updated
Micrograph model with updated_at field
- The SSE endpoint (as a regular hook -- we'll ignore this generated hook and use our custom one)
Step 2: Create event type definitions
Create packages/api/src/events.ts:
- TypeScript types mirroring the backend
FrontendEventType enum and event data models
AgentStatusEvent, AcquisitionProgressEvent, InstructionLifecycleEvent, ProcessingMetricEvent, AgentLogEvent
- Hand-written (not generated) because SSE event payloads aren't modelled as response schemas in OpenAPI
Step 3: Create useEventStream hook
Create packages/api/src/useEventStream.ts:
- Uses browser
EventSource API
- Connects to
/frontend/events/stream with query params (acquisition_uuid, agent_id, event_types)
- Parses SSE
event field to route to typed handlers
- Handles reconnection via
Last-Event-ID (automatic with EventSource)
- Provides connection state (connected / reconnecting / disconnected)
- Exposes callbacks per event type
- Cleans up on unmount
Key API surface:
function useEventStream(options: {
acquisitionUuid?: string
agentId?: string
eventTypes?: FrontendEventType[]
enabled?: boolean
onAgentStatus?: (data: AgentStatusEvent) => void
onAcquisitionProgress?: (data: AcquisitionProgressEvent) => void
onInstructionLifecycle?: (data: InstructionLifecycleEvent) => void
onProcessingMetric?: (data: ProcessingMetricEvent) => void
onAgentLog?: (data: AgentLogEvent) => void
}): {
connectionState: 'connecting' | 'open' | 'closed'
lastEventId: string | null
}
Step 4: React Query cache integration
SSE callbacks should invalidate relevant React Query caches so existing hooks automatically refetch:
onAcquisitionProgress -> invalidate acquisition detail queries
onProcessingMetric -> invalidate micrograph queries
- Or: directly update cache via
queryClient.setQueryData for lower latency
Wrapper hook (e.g. useSmartEMEventStream) combines useEventStream with useQueryClient for cache wiring.
Step 5: UI components
- Connection indicator: Small badge in header showing SSE connection state
- Agent status badges: Online/offline/stale per agent
- Live acquisition counters: Grid/gridsquare/foilhole/micrograph counts
- Log viewer panel: Scrolling log view with level/agent filtering
- Processing metrics: Motion correction / CTF stats updating in real time
Step 6: Export from packages/api
Export useEventStream, event types, and useSmartEMEventStream from packages/api/src/index.ts.
Key files
All paths relative to repo root.
| File |
Action |
packages/api/src/openapi.json |
Regenerate |
packages/api/src/generated/ |
Regenerate |
packages/api/src/events.ts |
Create -- SSE event type definitions |
packages/api/src/useEventStream.ts |
Create -- core SSE hook |
packages/api/src/useSmartEMEventStream.ts |
Create -- React Query integration wrapper |
packages/api/src/index.ts |
Modify -- export new hooks and types |
apps/smartem/src/components/... |
Create -- UI components per event type |
Event types to consume
| Event |
Frontend use |
agent.status |
Microscope online/offline indicators, connection health |
acquisition.progress |
Live entity counts on acquisition detail views |
instruction.lifecycle |
Instruction status timeline/feed |
processing.metric |
Live motion correction, CTF, particle picking stats |
agent.log |
Remote log viewer panel (filterable by level, agent, logger) |
heartbeat |
Connection health indicator |
Notes
- Backend filters events server-side via query params and DB queries -- ML/processing results from RabbitMQ only reach the SSE stream if they map to tracked entities (Micrograph processing fields). The frontend doesn't need additional filtering logic beyond choosing which
event_types to subscribe to.
- The log ingestion endpoint (
POST /agent/{id}/session/{id}/logs) is agent-to-backend only; the frontend consumes logs via the SSE stream, not by calling this endpoint directly.
Verification
- Regenerate client, verify new types appear in
generated/models/
- Connect to local backend with SSE: open browser console, verify events arrive
- UI components display live data when agent + EPUPlayer are running
- Connection indicator reflects actual SSE state (reconnects on network interruption)
- Log viewer shows agent logs in real time with level filtering
Related
Summary
Add frontend infrastructure to consume the backend SSE event stream (
GET /frontend/events/stream) and display live updates in the SmartEM dashboard.Depends on: DiamondLightSource/smartem-decisions#246 (backend SSE endpoint + agent log shipping)
Why the SSE endpoint needs a hand-written hook
The API client is auto-generated from the backend OpenAPI spec via Orval. Orval generates React Query hooks backed by Axios, which is request-response only -- it doesn't support the
EventSourcestreaming protocol. The generated hook for the SSE endpoint would make a regular GET that either hangs or returns garbage. We need a customuseEventStreamhook using the browser's nativeEventSourceAPI instead.Implementation steps
Step 1: Regenerate API client
This picks up:
AgentLogmodel typeAgentLogBatchRequest/AgentLogBatchResponsetypesMicrographmodel withupdated_atfieldStep 2: Create event type definitions
Create
packages/api/src/events.ts:FrontendEventTypeenum and event data modelsAgentStatusEvent,AcquisitionProgressEvent,InstructionLifecycleEvent,ProcessingMetricEvent,AgentLogEventStep 3: Create
useEventStreamhookCreate
packages/api/src/useEventStream.ts:EventSourceAPI/frontend/events/streamwith query params (acquisition_uuid,agent_id,event_types)eventfield to route to typed handlersLast-Event-ID(automatic withEventSource)Key API surface:
Step 4: React Query cache integration
SSE callbacks should invalidate relevant React Query caches so existing hooks automatically refetch:
onAcquisitionProgress-> invalidate acquisition detail queriesonProcessingMetric-> invalidate micrograph queriesqueryClient.setQueryDatafor lower latencyWrapper hook (e.g.
useSmartEMEventStream) combinesuseEventStreamwithuseQueryClientfor cache wiring.Step 5: UI components
Step 6: Export from
packages/apiExport
useEventStream, event types, anduseSmartEMEventStreamfrompackages/api/src/index.ts.Key files
All paths relative to repo root.
packages/api/src/openapi.jsonpackages/api/src/generated/packages/api/src/events.tspackages/api/src/useEventStream.tspackages/api/src/useSmartEMEventStream.tspackages/api/src/index.tsapps/smartem/src/components/...Event types to consume
agent.statusacquisition.progressinstruction.lifecycleprocessing.metricagent.logheartbeatNotes
event_typesto subscribe to.POST /agent/{id}/session/{id}/logs) is agent-to-backend only; the frontend consumes logs via the SSE stream, not by calling this endpoint directly.Verification
generated/models/Related