import { Controller, Get, Post, Body, Query, Sse, Logger } from '@nestjs/common'; import { Observable, filter, map } from 'rxjs'; import { SupervisorService } from './supervisor.service'; import { LogStreamService } from '../logging/log-stream.service'; @Controller('api/supervisor') export class SupervisorController { private readonly logger = new Logger(SupervisorController.name); constructor(private readonly supervisor: SupervisorService) {} @Get('active-calls') getActiveCalls() { return this.supervisor.getActiveCalls(); } @Sse('active-calls/stream') streamActiveCalls(): Observable { this.logger.log('[SSE] Active calls stream opened'); return this.supervisor.activeCallSubject.pipe( map(event => ({ data: JSON.stringify(event), } as MessageEvent)), ); } @Get('team-performance') async getTeamPerformance(@Query('date') date?: string) { const targetDate = date ?? new Date().toISOString().split('T')[0]; this.logger.log(`Team performance: date=${targetDate}`); return this.supervisor.getTeamPerformance(targetDate); } @Post('call-event') handleCallEvent(@Body() body: any) { const event = body.data ?? body; this.logger.log(`[CALL-EVENT] ${JSON.stringify(event)}`); this.supervisor.handleCallEvent(event); return { received: true }; } @Post('agent-event') handleAgentEvent(@Body() body: any) { const event = body.data ?? body; this.logger.log(`[AGENT-EVENT] ${JSON.stringify(event)}`); this.supervisor.handleAgentEvent(event); return { received: true }; } @Get('agent-state') getAgentState(@Query('agentId') agentId: string) { const state = this.supervisor.getAgentState(agentId); return state ?? { state: 'offline', timestamp: null }; } @Sse('agent-state/stream') streamAgentState(@Query('agentId') agentId: string): Observable { this.logger.log(`[SSE] Agent state stream opened for ${agentId}`); return this.supervisor.agentStateSubject.pipe( filter(event => event.agentId === agentId), map(event => ({ data: JSON.stringify({ state: event.state, timestamp: event.timestamp }), } as MessageEvent)), ); } // Worklist SSE — broadcast to all connected agents. When a missed // call is created by the webhook, this fires immediately so agents // don't wait for the 30s worklist poll. The payload includes the // caller's phone + name for a toast notification. @Sse('worklist/stream') streamWorklistUpdates(): Observable { this.logger.log('[SSE] Worklist stream opened'); return this.supervisor.worklistSubject.pipe( map(event => ({ data: JSON.stringify(event), } as MessageEvent)), ); } @Get('logs/recent') getRecentLogs(@Query('limit') limit?: string) { return LogStreamService.instance.getRecentLogs(limit ? parseInt(limit, 10) : 200); } @Sse('logs/stream') streamLogs(): Observable { this.logger.log('[SSE] Log stream opened'); return LogStreamService.instance.logSubject.pipe( map(entry => ({ data: JSON.stringify(entry), } as MessageEvent)), ); } }