use serde::{Deserialize, Serialize};
pub type LanguageCode = String;
#[derive(Debug, Clone, Default, Deserialize, Serialize)]
#[serde(rename_all = "camelCase")]
pub struct ListenConfig {
#[serde(default)]
pub language: Option<LanguageCode>,
#[serde(default, rename = "interimResults")]
pub interim_results: bool,
#[serde(default)]
pub continuous: bool,
#[serde(default, rename = "maxDuration")]
pub max_duration: u32,
#[serde(default, rename = "maxAlternatives")]
pub max_alternatives: Option<u32>,
#[serde(default, rename = "onDevice")]
pub on_device: bool,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
#[serde(rename_all = "lowercase")]
#[derive(Default)]
pub enum RecognitionState {
#[default]
Idle,
Listening,
Processing,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct RecognitionResult {
pub transcript: String,
pub is_final: bool,
#[serde(default)]
pub confidence: Option<f32>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub audio_data: Option<String>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct RecognitionStatus {
pub state: RecognitionState,
pub is_available: bool,
#[serde(default)]
pub language: Option<LanguageCode>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct SupportedLanguage {
pub code: LanguageCode,
pub name: String,
#[serde(default)]
pub installed: Option<bool>,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
#[serde(rename_all = "lowercase")]
pub enum PermissionStatus {
Granted,
Denied,
Unknown,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct PermissionResponse {
pub microphone: PermissionStatus,
pub speech_recognition: PermissionStatus,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct AvailabilityResponse {
pub available: bool,
#[serde(default)]
pub reason: Option<String>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct SupportedLanguagesResponse {
pub languages: Vec<SupportedLanguage>,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
#[serde(rename_all = "SCREAMING_SNAKE_CASE")]
#[derive(Default)]
pub enum SttErrorCode {
#[default]
None,
NotAvailable,
PermissionDenied,
SpeechPermissionDenied,
NetworkError,
AudioError,
Timeout,
NoSpeech,
LanguageNotSupported,
Cancelled,
AlreadyListening,
NotListening,
Busy,
ModelNotInstalled,
Unknown,
}
impl SttErrorCode {
pub fn description(&self) -> &'static str {
match self {
Self::None => "No error",
Self::NotAvailable => "Speech recognition is not available on this device",
Self::PermissionDenied => "Microphone permission was denied",
Self::SpeechPermissionDenied => "Speech recognition permission was denied",
Self::NetworkError => "Network error during recognition",
Self::AudioError => "Error accessing audio input",
Self::Timeout => "Recognition timed out",
Self::NoSpeech => "No speech was detected",
Self::LanguageNotSupported => "The requested language is not supported",
Self::Cancelled => "Recognition was cancelled",
Self::AlreadyListening => "Already listening for speech",
Self::NotListening => "Not currently listening",
Self::Busy => "Speech recognition service is busy",
Self::ModelNotInstalled => "No speech recognition model has been downloaded",
Self::Unknown => "An unknown error occurred",
}
}
pub fn code(&self) -> i32 {
match self {
Self::None => 0,
Self::NotAvailable => -1,
Self::PermissionDenied => -2,
Self::SpeechPermissionDenied => -3,
Self::NetworkError => -4,
Self::AudioError => -5,
Self::Timeout => -6,
Self::NoSpeech => -7,
Self::LanguageNotSupported => -8,
Self::Cancelled => -9,
Self::AlreadyListening => -10,
Self::NotListening => -11,
Self::Busy => -12,
Self::ModelNotInstalled => -13,
Self::Unknown => -99,
}
}
}
#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct SttError {
pub code: SttErrorCode,
pub message: String,
#[serde(default)]
pub details: Option<String>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct WhisperModelInfo {
pub id: String,
pub display_name: String,
pub size_mb: u32,
pub required_memory_mb: u32,
pub installed: bool,
pub active: bool,
pub recommended: bool,
pub tier: String,
#[serde(default)]
pub language: Option<String>,
pub fits_in_memory: bool,
pub advanced: bool,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct WhisperModelsResponse {
pub models: Vec<WhisperModelInfo>,
#[serde(default)]
pub active: Option<String>,
pub total_disk_bytes: u64,
pub system_memory_mb: u32,
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_listen_config_defaults() {
let config: ListenConfig = serde_json::from_str("{}").unwrap();
assert!(config.language.is_none());
assert!(!config.interim_results);
assert!(!config.continuous);
assert_eq!(config.max_duration, 0);
}
#[test]
fn test_listen_config_full() {
let json = r#"{
"language": "pt-BR",
"interimResults": true,
"continuous": true,
"maxDuration": 30
}"#;
let config: ListenConfig = serde_json::from_str(json).unwrap();
assert_eq!(config.language, Some("pt-BR".to_string()));
assert!(config.interim_results);
assert!(config.continuous);
assert_eq!(config.max_duration, 30);
}
#[test]
fn test_recognition_state_serialization() {
assert_eq!(
serde_json::to_string(&RecognitionState::Idle).unwrap(),
"\"idle\""
);
assert_eq!(
serde_json::to_string(&RecognitionState::Listening).unwrap(),
"\"listening\""
);
assert_eq!(
serde_json::to_string(&RecognitionState::Processing).unwrap(),
"\"processing\""
);
}
#[test]
fn test_recognition_result() {
let result = RecognitionResult {
transcript: "Hello world".to_string(),
is_final: true,
confidence: Some(0.95),
audio_data: None,
};
let json = serde_json::to_string(&result).unwrap();
assert!(json.contains("\"transcript\":\"Hello world\""));
assert!(json.contains("\"isFinal\":true"));
assert!(json.contains("\"confidence\":0.95"));
}
#[test]
fn test_permission_status_serialization() {
assert_eq!(
serde_json::to_string(&PermissionStatus::Granted).unwrap(),
"\"granted\""
);
assert_eq!(
serde_json::to_string(&PermissionStatus::Denied).unwrap(),
"\"denied\""
);
assert_eq!(
serde_json::to_string(&PermissionStatus::Unknown).unwrap(),
"\"unknown\""
);
}
}