pub mod stt;
pub mod tts;
mod providers;
use std::collections::HashMap;
use std::sync::Arc;
use crate::config::{Config, SttProviderConfig, TtsProviderConfig};
pub use stt::SttProvider;
pub use tts::TtsProvider;
pub const PIPELINE_SAMPLE_RATE: u32 = 16_000;
#[derive(Debug, Clone)]
pub enum VoicePushItem {
Start { task: Option<String> },
AssistantText(String),
AudioChunk(Vec<i16>),
Done,
Error(String),
}
pub struct VoiceProviders {
stt: HashMap<String, Arc<dyn SttProvider>>,
tts: HashMap<String, Arc<dyn TtsProvider>>,
}
impl VoiceProviders {
pub fn from_config(config: &Config) -> anyhow::Result<Self> {
let mut stt: HashMap<String, Arc<dyn SttProvider>> = HashMap::new();
for (name, cfg) in &config.stt_providers {
let provider = build_stt(name, cfg)?;
stt.insert(name.clone(), provider);
}
let mut tts: HashMap<String, Arc<dyn TtsProvider>> = HashMap::new();
for (name, cfg) in &config.tts_providers {
let provider = build_tts(name, cfg)?;
tts.insert(name.clone(), provider);
}
Ok(Self { stt, tts })
}
pub fn stt(&self, name: &str) -> Option<Arc<dyn SttProvider>> {
self.stt.get(name).cloned()
}
pub fn tts(&self, name: &str) -> Option<Arc<dyn TtsProvider>> {
self.tts.get(name).cloned()
}
}
fn build_stt(name: &str, cfg: &SttProviderConfig) -> anyhow::Result<Arc<dyn SttProvider>> {
match cfg {
SttProviderConfig::Mock { transcript } => Ok(Arc::new(providers::MockStt::new(
name.to_string(),
transcript.clone(),
))),
SttProviderConfig::SherpaOnnx(cfg) => {
#[cfg(feature = "voice-sherpa")]
{
Ok(Arc::new(providers::SherpaOnnxStt::new(
name.to_string(),
cfg.clone(),
)?))
}
#[cfg(not(feature = "voice-sherpa"))]
{
let _ = cfg;
anyhow::bail!(
"stt_provider '{name}': type = \"sherpa_onnx\" requires the \
`voice-sherpa` cargo feature to be enabled at build time"
)
}
}
SttProviderConfig::OpenAiWhisperApi { .. } => {
anyhow::bail!(
"stt_provider '{name}': type = \"openai_whisper_api\" is not yet implemented"
)
}
}
}
fn build_tts(name: &str, cfg: &TtsProviderConfig) -> anyhow::Result<Arc<dyn TtsProvider>> {
match cfg {
TtsProviderConfig::Mock {
duration_ms,
frequency_hz,
} => Ok(Arc::new(providers::MockTts::new(
name.to_string(),
*duration_ms,
*frequency_hz,
))),
TtsProviderConfig::OpenAiTts {
api_key_env,
base_url,
model,
voice,
} => Ok(Arc::new(providers::OpenAiTts::new(
name.to_string(),
api_key_env.as_deref(),
base_url.as_deref(),
model.as_deref(),
voice.as_deref(),
)?)),
TtsProviderConfig::SherpaOnnx(cfg) => {
#[cfg(feature = "voice-sherpa")]
{
Ok(Arc::new(providers::SherpaOnnxTts::new(
name.to_string(),
cfg.clone(),
)?))
}
#[cfg(not(feature = "voice-sherpa"))]
{
let _ = cfg;
anyhow::bail!(
"tts_provider '{name}': type = \"sherpa_onnx\" requires the \
`voice-sherpa` cargo feature to be enabled at build time"
)
}
}
}
}