chore: merge Rust backend into monorepo
git-subtree-dir: server git-subtree-mainline:fb6a84d75fgit-subtree-split:8a88706ff9
This commit is contained in:
167
server/src/config.rs
Normal file
167
server/src/config.rs
Normal file
@@ -0,0 +1,167 @@
|
||||
use std::{env, net::IpAddr, path::PathBuf};
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub enum SpeechProviderConfig {
|
||||
Local { api_url: String },
|
||||
Tencent(TencentSpeechConfig),
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct TencentSpeechConfig {
|
||||
pub app_id: u64,
|
||||
pub secret_id: String,
|
||||
pub secret_key: String,
|
||||
pub engine_type: String,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct SpeechConfig {
|
||||
pub provider: SpeechProviderConfig,
|
||||
pub request_timeout_seconds: u64,
|
||||
pub worker_concurrency: usize,
|
||||
pub job_lease_seconds: i64,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct SummaryConfig {
|
||||
pub api_url: String,
|
||||
pub api_key: Option<String>,
|
||||
pub model: String,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct Config {
|
||||
pub host: IpAddr,
|
||||
pub port: u16,
|
||||
pub database_url: Option<String>,
|
||||
pub database_max_connections: u32,
|
||||
pub cors_allowed_origin: Option<String>,
|
||||
pub audio_storage_dir: PathBuf,
|
||||
pub speech: Option<SpeechConfig>,
|
||||
pub summary: Option<SummaryConfig>,
|
||||
}
|
||||
|
||||
impl Config {
|
||||
pub fn from_env() -> Result<Self, String> {
|
||||
let host = env::var("HOST")
|
||||
.unwrap_or_else(|_| "127.0.0.1".to_owned())
|
||||
.parse()
|
||||
.map_err(|_| "HOST must be a valid IP address".to_owned())?;
|
||||
let port = env::var("PORT")
|
||||
.unwrap_or_else(|_| "8080".to_owned())
|
||||
.parse()
|
||||
.map_err(|_| "PORT must be a valid u16 value".to_owned())?;
|
||||
let database_max_connections = env::var("DATABASE_MAX_CONNECTIONS")
|
||||
.unwrap_or_else(|_| "5".to_owned())
|
||||
.parse()
|
||||
.map_err(|_| "DATABASE_MAX_CONNECTIONS must be a valid u32 value".to_owned())?;
|
||||
|
||||
let speech = speech_config()?;
|
||||
let summary = summary_config()?;
|
||||
|
||||
Ok(Self {
|
||||
host,
|
||||
port,
|
||||
database_url: env::var("DATABASE_URL")
|
||||
.ok()
|
||||
.filter(|value| !value.trim().is_empty()),
|
||||
database_max_connections,
|
||||
cors_allowed_origin: env::var("CORS_ALLOWED_ORIGIN")
|
||||
.ok()
|
||||
.filter(|value| !value.trim().is_empty()),
|
||||
audio_storage_dir: env::var("AUDIO_STORAGE_DIR")
|
||||
.map(PathBuf::from)
|
||||
.unwrap_or_else(|_| PathBuf::from("./data/audio")),
|
||||
speech,
|
||||
summary,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
fn speech_config() -> Result<Option<SpeechConfig>, String> {
|
||||
let provider = optional_env("ASR_PROVIDER").unwrap_or_else(|| "local".to_owned());
|
||||
if matches!(provider.as_str(), "disabled" | "none") {
|
||||
return Ok(None);
|
||||
}
|
||||
|
||||
let provider = match provider.as_str() {
|
||||
"local" | "funasr" => SpeechProviderConfig::Local {
|
||||
api_url: optional_env("LOCAL_ASR_URL")
|
||||
.unwrap_or_else(|| "http://127.0.0.1:10095".to_owned())
|
||||
.trim_end_matches('/')
|
||||
.to_owned(),
|
||||
},
|
||||
"tencent" => {
|
||||
let app_id = required_env("TENCENT_CLOUD_ASR_APP_ID")?
|
||||
.parse()
|
||||
.map_err(|_| "TENCENT_CLOUD_ASR_APP_ID must be an integer".to_owned())?;
|
||||
SpeechProviderConfig::Tencent(TencentSpeechConfig {
|
||||
app_id,
|
||||
secret_id: required_env("TENCENT_CLOUD_ASR_SECRET_ID")?,
|
||||
secret_key: required_env("TENCENT_CLOUD_ASR_SECRET_KEY")?,
|
||||
engine_type: optional_env("TENCENT_CLOUD_ASR_ENGINE_TYPE")
|
||||
.unwrap_or_else(|| "16k_zh".to_owned()),
|
||||
})
|
||||
}
|
||||
_ => {
|
||||
return Err(
|
||||
"ASR_PROVIDER must be local, funasr, tencent, disabled, or none".to_owned(),
|
||||
);
|
||||
}
|
||||
};
|
||||
|
||||
let request_timeout_seconds = parse_env("ASR_REQUEST_TIMEOUT_SECONDS", 1_800_u64)?;
|
||||
let worker_concurrency = parse_env("ASR_WORKER_CONCURRENCY", 1_usize)?;
|
||||
let job_lease_seconds = parse_env("ASR_JOB_LEASE_SECONDS", 3_600_i64)?;
|
||||
if request_timeout_seconds == 0 {
|
||||
return Err("ASR_REQUEST_TIMEOUT_SECONDS must be greater than 0".to_owned());
|
||||
}
|
||||
if worker_concurrency == 0 {
|
||||
return Err("ASR_WORKER_CONCURRENCY must be greater than 0".to_owned());
|
||||
}
|
||||
if job_lease_seconds <= 0 {
|
||||
return Err("ASR_JOB_LEASE_SECONDS must be greater than 0".to_owned());
|
||||
}
|
||||
|
||||
Ok(Some(SpeechConfig {
|
||||
provider,
|
||||
request_timeout_seconds,
|
||||
worker_concurrency,
|
||||
job_lease_seconds,
|
||||
}))
|
||||
}
|
||||
|
||||
fn summary_config() -> Result<Option<SummaryConfig>, String> {
|
||||
let Some(api_url) = optional_env("FEEDBACK_SUMMARY_API_URL") else {
|
||||
return Ok(None);
|
||||
};
|
||||
let model = optional_env("FEEDBACK_SUMMARY_MODEL").ok_or_else(|| {
|
||||
"FEEDBACK_SUMMARY_MODEL is required when summary API is configured".to_owned()
|
||||
})?;
|
||||
Ok(Some(SummaryConfig {
|
||||
api_url: api_url.trim_end_matches('/').to_owned(),
|
||||
api_key: optional_env("FEEDBACK_SUMMARY_API_KEY"),
|
||||
model,
|
||||
}))
|
||||
}
|
||||
|
||||
fn optional_env(name: &str) -> Option<String> {
|
||||
env::var(name)
|
||||
.ok()
|
||||
.map(|value| value.trim().to_owned())
|
||||
.filter(|value| !value.is_empty())
|
||||
}
|
||||
|
||||
fn required_env(name: &str) -> Result<String, String> {
|
||||
optional_env(name).ok_or_else(|| format!("{name} is required for the selected ASR provider"))
|
||||
}
|
||||
|
||||
fn parse_env<T>(name: &str, default: T) -> Result<T, String>
|
||||
where
|
||||
T: std::str::FromStr,
|
||||
{
|
||||
optional_env(name).map_or_else(
|
||||
|| Ok(default),
|
||||
|value| value.parse().map_err(|_| format!("{name} is invalid")),
|
||||
)
|
||||
}
|
||||
Reference in New Issue
Block a user