use std::time::{Duration, Instant}; /// Identifica un turno de conversación (una intervención del usuario y la /// respuesta que provoca). Todo lo que viaja por el bus lo lleva, para que un /// resultado que llega tarde no se confunda con el turno que ya está en curso /// —el caso típico cuando el usuario interrumpe al asistente. #[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, Default)] pub struct TurnId(pub u64); impl std::fmt::Display for TurnId { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { write!(f, "#{}", self.0) } } /// Motivo por el que se corta la reproducción en curso. #[derive(Debug, Clone, Copy, PartialEq, Eq)] pub enum InterruptReason { /// El usuario ha empezado a hablar encima del asistente (barge-in). UserSpoke, /// Petición explícita: tecla, señal o cierre. Requested, } /// Todo lo que las etapas se cuentan entre sí. Un único enum mantiene el bus /// observable: el renderizador y la telemetría ven exactamente los mismos /// eventos que el orquestador, sin canales paralelos que se desincronicen. #[derive(Debug, Clone)] pub enum Event { /// El VAD ha detectado el arranque de una intervención. SpeechStarted { turn: TurnId, at: Instant }, /// Transcripción provisional de la ventana deslizante: `committed` ya es /// estable, `volatile` todavía puede cambiar. Partial { turn: TurnId, committed: String, volatile: String, }, /// Transcripción definitiva de la intervención completa. Transcript { turn: TurnId, text: String, audio_secs: f32, decode: Duration, }, /// La intervención no contenía nada transcribible. Discarded { turn: TurnId }, /// Primer fragmento de texto que devuelve el modelo. ReplyStarted { turn: TurnId, ttft: Duration }, /// Un trozo de respuesta según llega del modelo. ReplyDelta { turn: TurnId, text: String }, /// Una frase completa lista para sintetizar. Sentence { turn: TurnId, index: usize, text: String, }, /// Respuesta completa del modelo para este turno. ReplyDone { turn: TurnId, text: String }, /// El modelo ha pedido ejecutar una herramienta. ToolRequested { turn: TurnId, name: String, arguments: String, }, /// Resultado de esa ejecución. ToolFinished { turn: TurnId, name: String, ok: bool, output: String, took: Duration, }, /// Primer audio audible del turno: la métrica que de verdad percibe quien habla. AudioStarted { turn: TurnId, latency: Duration }, /// Se ha terminado de reproducir todo lo del turno. AudioFinished { turn: TurnId }, /// Se ha cortado la reproducción. Interrupted { turn: TurnId, reason: InterruptReason, }, /// Aviso no fatal; el turno continúa. Warning { turn: TurnId, message: String }, /// Fallo que aborta el turno. Failed { turn: TurnId, message: String }, /// Cierre ordenado. Shutdown, } impl Event { pub fn turn(&self) -> Option { match self { Event::SpeechStarted { turn, .. } | Event::Partial { turn, .. } | Event::Transcript { turn, .. } | Event::Discarded { turn } | Event::ReplyStarted { turn, .. } | Event::ReplyDelta { turn, .. } | Event::Sentence { turn, .. } | Event::ReplyDone { turn, .. } | Event::ToolRequested { turn, .. } | Event::ToolFinished { turn, .. } | Event::AudioStarted { turn, .. } | Event::AudioFinished { turn } | Event::Interrupted { turn, .. } | Event::Warning { turn, .. } | Event::Failed { turn, .. } => Some(*turn), Event::Shutdown => None, } } }