active_call/offline/
config.rs

1use anyhow::Result;
2use std::path::PathBuf;
3
4#[derive(Clone, Debug)]
5pub struct OfflineConfig {
6    pub models_dir: PathBuf,
7    pub threads: usize,
8}
9
10impl Default for OfflineConfig {
11    fn default() -> Self {
12        Self {
13            models_dir: Self::default_models_dir(),
14            threads: num_cpus::get().min(4),
15        }
16    }
17}
18
19impl OfflineConfig {
20    pub fn new(models_dir: PathBuf, threads: usize) -> Self {
21        Self {
22            models_dir,
23            threads,
24        }
25    }
26
27    pub fn default_models_dir() -> PathBuf {
28        std::env::var("OFFLINE_MODELS_DIR")
29            .ok()
30            .map(PathBuf::from)
31            .unwrap_or_else(|| PathBuf::from("./models"))
32    }
33
34    pub fn sensevoice_dir(&self) -> PathBuf {
35        self.models_dir.join("sensevoice")
36    }
37
38    pub fn sensevoice_model_path(&self) -> PathBuf {
39        self.sensevoice_dir().join("model.int8.onnx")
40    }
41
42    pub fn sensevoice_tokens_path(&self) -> PathBuf {
43        self.sensevoice_dir().join("tokens.txt")
44    }
45
46    pub fn supertonic_dir(&self) -> PathBuf {
47        self.models_dir.join("supertonic")
48    }
49
50    pub fn supertonic_onnx_dir(&self) -> PathBuf {
51        self.supertonic_dir().join("onnx")
52    }
53
54    pub fn supertonic_config_path(&self) -> PathBuf {
55        self.supertonic_onnx_dir().join("tts.json")
56    }
57
58    pub fn supertonic_voice_styles_dir(&self) -> PathBuf {
59        self.supertonic_dir().join("voice_styles")
60    }
61
62    pub fn validate(&self) -> Result<()> {
63        if !self.models_dir.exists() {
64            anyhow::bail!(
65                "Models directory does not exist: {}. Please run with --download-models flag.",
66                self.models_dir.display()
67            );
68        }
69        Ok(())
70    }
71
72    pub fn sensevoice_available(&self) -> bool {
73        self.sensevoice_model_path().exists() && self.sensevoice_tokens_path().exists()
74    }
75
76    pub fn supertonic_available(&self) -> bool {
77        let onnx_dir = self.supertonic_onnx_dir();
78        onnx_dir.join("duration_predictor.onnx").exists()
79            && onnx_dir.join("text_encoder.onnx").exists()
80            && onnx_dir.join("vector_estimator.onnx").exists()
81            && onnx_dir.join("vocoder.onnx").exists()
82            && self.supertonic_config_path().exists()
83    }
84}
85
86#[cfg(test)]
87mod tests {
88    use super::*;
89
90    #[test]
91    fn test_default_config() {
92        let config = OfflineConfig::default();
93        assert!(config.threads > 0);
94        assert!(config.threads <= 4);
95    }
96
97    #[test]
98    fn test_paths() {
99        let config = OfflineConfig::new(PathBuf::from("/test/models"), 2);
100        assert_eq!(
101            config.sensevoice_model_path(),
102            PathBuf::from("/test/models/sensevoice/model.int8.onnx")
103        );
104        assert_eq!(
105            config.supertonic_onnx_dir(),
106            PathBuf::from("/test/models/supertonic/onnx")
107        );
108    }
109}