active_call/offline/
mod.rs1pub 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, )?;
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}