1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
|
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<TurnId> {
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,
}
}
}
|