Skip to main content

clawft_plugin/voice/
events.rs

1//! WebSocket event types for voice status reporting.
2//!
3//! Defines the `voice:status` event that is broadcast to WebSocket
4//! clients when the voice channel status changes.
5
6use serde::{Deserialize, Serialize};
7
8use super::channel::VoiceStatus;
9
10/// WebSocket event for voice status changes.
11///
12/// Sent to connected WebSocket clients whenever the voice pipeline
13/// transitions between states (idle, listening, transcribing,
14/// processing, speaking).
15#[derive(Debug, Clone, Serialize, Deserialize)]
16pub struct VoiceWsEvent {
17    /// Event type identifier. Always `"voice:status"`.
18    pub event: String,
19    /// Current voice pipeline status.
20    pub status: VoiceStatus,
21    /// Unix timestamp in milliseconds when the event occurred.
22    pub timestamp: u64,
23}
24
25impl VoiceWsEvent {
26    /// Create a new voice status event with the current timestamp.
27    pub fn new(status: VoiceStatus) -> Self {
28        let timestamp = std::time::SystemTime::now()
29            .duration_since(std::time::UNIX_EPOCH)
30            .unwrap_or_default()
31            .as_millis() as u64;
32        Self {
33            event: "voice:status".into(),
34            status,
35            timestamp,
36        }
37    }
38}
39
40#[cfg(test)]
41mod tests {
42    use super::*;
43
44    #[test]
45    fn voice_ws_event_new() {
46        let event = VoiceWsEvent::new(VoiceStatus::Listening);
47        assert_eq!(event.event, "voice:status");
48        assert_eq!(event.status, VoiceStatus::Listening);
49        assert!(event.timestamp > 0);
50    }
51
52    #[test]
53    fn voice_ws_event_serde_roundtrip() {
54        let event = VoiceWsEvent {
55            event: "voice:status".into(),
56            status: VoiceStatus::Speaking,
57            timestamp: 1700000000000,
58        };
59        let json = serde_json::to_string(&event).unwrap();
60        let restored: VoiceWsEvent = serde_json::from_str(&json).unwrap();
61        assert_eq!(restored.event, "voice:status");
62        assert_eq!(restored.status, VoiceStatus::Speaking);
63        assert_eq!(restored.timestamp, 1700000000000);
64    }
65
66    #[test]
67    fn voice_ws_event_json_structure() {
68        let event = VoiceWsEvent {
69            event: "voice:status".into(),
70            status: VoiceStatus::Idle,
71            timestamp: 1700000000000,
72        };
73        let json: serde_json::Value = serde_json::to_value(&event).unwrap();
74        assert_eq!(json["event"], "voice:status");
75        assert_eq!(json["status"], "idle");
76        assert_eq!(json["timestamp"], 1700000000000_u64);
77    }
78
79    #[test]
80    fn voice_ws_event_all_statuses() {
81        let statuses = vec![
82            VoiceStatus::Idle,
83            VoiceStatus::Listening,
84            VoiceStatus::Transcribing,
85            VoiceStatus::Processing,
86            VoiceStatus::Speaking,
87        ];
88        for status in statuses {
89            let event = VoiceWsEvent::new(status);
90            let json = serde_json::to_string(&event).unwrap();
91            let restored: VoiceWsEvent = serde_json::from_str(&json).unwrap();
92            assert_eq!(restored.status, status);
93        }
94    }
95}