active_call/offline/
mod.rs

1pub mod config;
2pub mod downloader;
3
4#[cfg(feature = "offline")]
5pub mod sensevoice;
6
7#[cfg(feature = "offline")]
8pub mod supertonic;
9
10pub use config::OfflineConfig;
11pub use downloader::{ModelDownloader, ModelType};
12
13#[cfg(feature = "offline")]
14pub use sensevoice::SensevoiceEncoder;
15
16#[cfg(feature = "offline")]
17pub use supertonic::SupertonicTts;
18
19use anyhow::{Result, anyhow};
20use once_cell::sync::OnceCell;
21use std::sync::Arc;
22use tokio::sync::RwLock;
23use tracing::{debug, info};
24
25#[cfg(feature = "offline")]
26pub struct OfflineModels {
27    config: OfflineConfig,
28    sensevoice: Arc<RwLock<Option<SensevoiceEncoder>>>,
29    supertonic: Arc<RwLock<Option<SupertonicTts>>>,
30}
31
32#[cfg(feature = "offline")]
33impl OfflineModels {
34    pub fn new(config: OfflineConfig) -> Self {
35        Self {
36            config,
37            sensevoice: Arc::new(RwLock::new(None)),
38            supertonic: Arc::new(RwLock::new(None)),
39        }
40    }
41
42    pub async fn init_sensevoice(&self) -> Result<()> {
43        let mut guard = self.sensevoice.write().await;
44        if guard.is_none() {
45            if !self.config.sensevoice_available() {
46                anyhow::bail!(
47                    "SenseVoice model files not found. Please run with --download-models sensevoice"
48                );
49            }
50
51            info!("Initializing SenseVoice encoder...");
52            let encoder = SensevoiceEncoder::new(
53                &self.config.sensevoice_model_path(),
54                &self.config.sensevoice_tokens_path(),
55                self.config.threads,
56            )?;
57            *guard = Some(encoder);
58            info!("✓ SenseVoice encoder initialized");
59        }
60        Ok(())
61    }
62
63    pub async fn get_sensevoice(&self) -> Result<Arc<RwLock<Option<SensevoiceEncoder>>>> {
64        self.init_sensevoice().await?;
65        Ok(self.sensevoice.clone())
66    }
67
68    pub async fn init_supertonic(&self) -> Result<()> {
69        let mut guard = self.supertonic.write().await;
70        if guard.is_none() {
71            if !self.config.supertonic_available() {
72                anyhow::bail!(
73                    "Supertonic model files not found. Please run with --download-models supertonic"
74                );
75            }
76
77            info!("Initializing Supertonic TTS...");
78            let tts = SupertonicTts::new(
79                &self.config.supertonic_onnx_dir(),
80                &self.config.supertonic_config_path(),
81                &self.config.supertonic_voice_styles_dir(),
82                self.config.threads,
83                false, // use_gpu
84            )?;
85            *guard = Some(tts);
86            info!("✓ Supertonic TTS initialized");
87        }
88        Ok(())
89    }
90
91    pub async fn get_supertonic(&self) -> Result<Arc<RwLock<Option<SupertonicTts>>>> {
92        self.init_supertonic().await?;
93        Ok(self.supertonic.clone())
94    }
95
96    pub fn config(&self) -> &OfflineConfig {
97        &self.config
98    }
99}
100
101#[cfg(feature = "offline")]
102static OFFLINE_MODELS: OnceCell<OfflineModels> = OnceCell::new();
103
104#[cfg(feature = "offline")]
105pub fn init_offline_models(config: OfflineConfig) -> Result<()> {
106    debug!(
107        "Initializing offline models with dir: {}",
108        config.models_dir.display()
109    );
110    OFFLINE_MODELS
111        .set(OfflineModels::new(config))
112        .map_err(|_| anyhow!("offline models already initialized"))
113}
114
115#[cfg(feature = "offline")]
116pub fn get_offline_models() -> Option<&'static OfflineModels> {
117    OFFLINE_MODELS.get()
118}
119
120#[cfg(test)]
121mod tests {
122    use super::*;
123
124    #[test]
125    fn test_config_paths() {
126        let config = OfflineConfig::default();
127        assert!(
128            config
129                .sensevoice_dir()
130                .to_string_lossy()
131                .contains("sensevoice")
132        );
133        assert!(
134            config
135                .supertonic_dir()
136                .to_string_lossy()
137                .contains("supertonic")
138        );
139    }
140}