1use serde::{Deserialize, Serialize};
2
3pub type LanguageCode = String;
5
6#[derive(Debug, Clone, Default, Deserialize, Serialize)]
8#[serde(rename_all = "camelCase")]
9pub struct ListenConfig {
10 #[serde(default)]
13 pub language: Option<LanguageCode>,
14
15 #[serde(default, rename = "interimResults")]
17 pub interim_results: bool,
18
19 #[serde(default)]
22 pub continuous: bool,
23
24 #[serde(default, rename = "maxDuration")]
26 pub max_duration: u32,
27
28 #[serde(default, rename = "maxAlternatives")]
30 pub max_alternatives: Option<u32>,
31
32 #[serde(default, rename = "onDevice")]
35 pub on_device: bool,
36}
37
38#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
40#[serde(rename_all = "lowercase")]
41#[derive(Default)]
42pub enum RecognitionState {
43 #[default]
45 Idle,
46 Listening,
48 Processing,
50}
51
52#[derive(Debug, Clone, Serialize, Deserialize)]
54#[serde(rename_all = "camelCase")]
55pub struct RecognitionResult {
56 pub transcript: String,
58
59 pub is_final: bool,
61
62 #[serde(default)]
64 pub confidence: Option<f32>,
65
66 #[serde(default, skip_serializing_if = "Option::is_none")]
69 pub audio_data: Option<String>,
70}
71
72#[derive(Debug, Clone, Serialize, Deserialize)]
74#[serde(rename_all = "camelCase")]
75pub struct RecognitionStatus {
76 pub state: RecognitionState,
78
79 pub is_available: bool,
81
82 #[serde(default)]
84 pub language: Option<LanguageCode>,
85}
86
87#[derive(Debug, Clone, Serialize, Deserialize)]
89#[serde(rename_all = "camelCase")]
90pub struct SupportedLanguage {
91 pub code: LanguageCode,
93
94 pub name: String,
96
97 #[serde(default)]
99 pub installed: Option<bool>,
100}
101
102#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
104#[serde(rename_all = "lowercase")]
105pub enum PermissionStatus {
106 Granted,
108 Denied,
110 Unknown,
112}
113
114#[derive(Debug, Clone, Serialize, Deserialize)]
116#[serde(rename_all = "camelCase")]
117pub struct PermissionResponse {
118 pub microphone: PermissionStatus,
120
121 pub speech_recognition: PermissionStatus,
123}
124
125#[derive(Debug, Clone, Serialize, Deserialize)]
127#[serde(rename_all = "camelCase")]
128pub struct AvailabilityResponse {
129 pub available: bool,
131
132 #[serde(default)]
134 pub reason: Option<String>,
135}
136
137#[derive(Debug, Clone, Serialize, Deserialize)]
139#[serde(rename_all = "camelCase")]
140pub struct SupportedLanguagesResponse {
141 pub languages: Vec<SupportedLanguage>,
143}
144
145#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
147#[serde(rename_all = "SCREAMING_SNAKE_CASE")]
148#[derive(Default)]
149pub enum SttErrorCode {
150 #[default]
152 None,
153 NotAvailable,
155 PermissionDenied,
157 SpeechPermissionDenied,
159 NetworkError,
161 AudioError,
163 Timeout,
165 NoSpeech,
167 LanguageNotSupported,
169 Cancelled,
171 AlreadyListening,
173 NotListening,
175 Busy,
177 ModelNotInstalled,
179 Unknown,
181}
182
183impl SttErrorCode {
184 pub fn description(&self) -> &'static str {
186 match self {
187 Self::None => "No error",
188 Self::NotAvailable => "Speech recognition is not available on this device",
189 Self::PermissionDenied => "Microphone permission was denied",
190 Self::SpeechPermissionDenied => "Speech recognition permission was denied",
191 Self::NetworkError => "Network error during recognition",
192 Self::AudioError => "Error accessing audio input",
193 Self::Timeout => "Recognition timed out",
194 Self::NoSpeech => "No speech was detected",
195 Self::LanguageNotSupported => "The requested language is not supported",
196 Self::Cancelled => "Recognition was cancelled",
197 Self::AlreadyListening => "Already listening for speech",
198 Self::NotListening => "Not currently listening",
199 Self::Busy => "Speech recognition service is busy",
200 Self::ModelNotInstalled => "No speech recognition model has been downloaded",
201 Self::Unknown => "An unknown error occurred",
202 }
203 }
204
205 pub fn code(&self) -> i32 {
207 match self {
208 Self::None => 0,
209 Self::NotAvailable => -1,
210 Self::PermissionDenied => -2,
211 Self::SpeechPermissionDenied => -3,
212 Self::NetworkError => -4,
213 Self::AudioError => -5,
214 Self::Timeout => -6,
215 Self::NoSpeech => -7,
216 Self::LanguageNotSupported => -8,
217 Self::Cancelled => -9,
218 Self::AlreadyListening => -10,
219 Self::NotListening => -11,
220 Self::Busy => -12,
221 Self::ModelNotInstalled => -13,
222 Self::Unknown => -99,
223 }
224 }
225}
226
227#[derive(Debug, Clone, Serialize, Deserialize)]
229#[serde(rename_all = "camelCase")]
230pub struct SttError {
231 pub code: SttErrorCode,
233 pub message: String,
235 #[serde(default)]
237 pub details: Option<String>,
238}
239
240#[derive(Debug, Clone, Serialize, Deserialize)]
241#[serde(rename_all = "camelCase")]
242pub struct WhisperModelInfo {
243 pub id: String,
247 pub display_name: String,
249 pub size_mb: u32,
252 pub required_memory_mb: u32,
257 pub installed: bool,
259 pub active: bool,
261 pub recommended: bool,
264 pub tier: String,
268 #[serde(default)]
272 pub language: Option<String>,
273 pub fits_in_memory: bool,
277 pub advanced: bool,
281}
282
283#[derive(Debug, Clone, Serialize, Deserialize)]
284#[serde(rename_all = "camelCase")]
285pub struct WhisperModelsResponse {
286 pub models: Vec<WhisperModelInfo>,
288 #[serde(default)]
290 pub active: Option<String>,
291 pub total_disk_bytes: u64,
294 pub system_memory_mb: u32,
298}
299
300#[cfg(test)]
301mod tests {
302 use super::*;
303
304 #[test]
305 fn test_listen_config_defaults() {
306 let config: ListenConfig = serde_json::from_str("{}").unwrap();
307 assert!(config.language.is_none());
308 assert!(!config.interim_results);
309 assert!(!config.continuous);
310 assert_eq!(config.max_duration, 0);
311 }
312
313 #[test]
314 fn test_listen_config_full() {
315 let json = r#"{
316 "language": "pt-BR",
317 "interimResults": true,
318 "continuous": true,
319 "maxDuration": 30
320 }"#;
321 let config: ListenConfig = serde_json::from_str(json).unwrap();
322 assert_eq!(config.language, Some("pt-BR".to_string()));
323 assert!(config.interim_results);
324 assert!(config.continuous);
325 assert_eq!(config.max_duration, 30);
326 }
327
328 #[test]
329 fn test_recognition_state_serialization() {
330 assert_eq!(
331 serde_json::to_string(&RecognitionState::Idle).unwrap(),
332 "\"idle\""
333 );
334 assert_eq!(
335 serde_json::to_string(&RecognitionState::Listening).unwrap(),
336 "\"listening\""
337 );
338 assert_eq!(
339 serde_json::to_string(&RecognitionState::Processing).unwrap(),
340 "\"processing\""
341 );
342 }
343
344 #[test]
345 fn test_recognition_result() {
346 let result = RecognitionResult {
347 transcript: "Hello world".to_string(),
348 is_final: true,
349 confidence: Some(0.95),
350 audio_data: None,
351 };
352 let json = serde_json::to_string(&result).unwrap();
353 assert!(json.contains("\"transcript\":\"Hello world\""));
354 assert!(json.contains("\"isFinal\":true"));
355 assert!(json.contains("\"confidence\":0.95"));
356 }
357
358 #[test]
359 fn test_permission_status_serialization() {
360 assert_eq!(
361 serde_json::to_string(&PermissionStatus::Granted).unwrap(),
362 "\"granted\""
363 );
364 assert_eq!(
365 serde_json::to_string(&PermissionStatus::Denied).unwrap(),
366 "\"denied\""
367 );
368 assert_eq!(
369 serde_json::to_string(&PermissionStatus::Unknown).unwrap(),
370 "\"unknown\""
371 );
372 }
373}