265 lines
8.1 KiB
Rust
265 lines
8.1 KiB
Rust
use anyhow::{Context, Result};
|
|
use clap::Parser;
|
|
use std::path::PathBuf;
|
|
use tracing::{error, info};
|
|
use voice_cli::{
|
|
cli::{Cli, Commands, ModelAction, ServerAction, TtsAction},
|
|
config::ServiceType,
|
|
config_rs_integration::ConfigRsLoader,
|
|
server,
|
|
};
|
|
#[tokio::main]
|
|
async fn main() {
|
|
init_locale_from_env();
|
|
|
|
// Parse command line arguments
|
|
let cli = Cli::parse();
|
|
|
|
// Don't initialize logging here - let each service initialize its own logging
|
|
// based on configuration files
|
|
|
|
// Generate CLI overrides from command line arguments
|
|
let cli_overrides = match ConfigRsLoader::generate_cli_overrides_from_args(&cli) {
|
|
Ok(overrides) => overrides,
|
|
Err(e) => {
|
|
error!("Failed to generate CLI overrides: {}", e);
|
|
std::process::exit(1);
|
|
}
|
|
};
|
|
|
|
// Load configuration based on command type using config-rs with proper hierarchy
|
|
let config = match &cli.command {
|
|
// For init commands, we don't need to load existing config
|
|
Commands::Server {
|
|
action: ServerAction::Init { .. },
|
|
} => {
|
|
// Use default config for init commands
|
|
voice_cli::Config::default()
|
|
}
|
|
|
|
// For server commands, use server-specific config
|
|
Commands::Server { action } => {
|
|
let config_path = get_config_path_for_server_action(action, &cli.config);
|
|
match ConfigRsLoader::load(
|
|
config_path.as_ref(),
|
|
&cli_overrides,
|
|
Some(ServiceType::Server),
|
|
) {
|
|
Ok(config) => config,
|
|
Err(e) => {
|
|
error!("Failed to load server configuration: {}", e);
|
|
std::process::exit(1);
|
|
}
|
|
}
|
|
}
|
|
|
|
// For other commands, use default config loading
|
|
_ => {
|
|
let config_path = PathBuf::from(&cli.config);
|
|
match ConfigRsLoader::load(Some(&config_path), &cli_overrides, None) {
|
|
Ok(config) => config,
|
|
Err(e) => {
|
|
error!("Failed to load configuration: {}", e);
|
|
std::process::exit(1);
|
|
}
|
|
}
|
|
}
|
|
};
|
|
|
|
// Log configuration summary if verbose
|
|
if cli.verbose {
|
|
info!("Configuration loaded successfully");
|
|
}
|
|
|
|
// Route to appropriate handler
|
|
let result = match cli.command {
|
|
Commands::Server { action } => handle_server_command(action, &config).await,
|
|
Commands::Model { action } => handle_model_command(action, &config).await,
|
|
Commands::Tts { action } => handle_tts_command(action, &config).await,
|
|
};
|
|
|
|
// Handle result
|
|
match result {
|
|
Ok(_) => {
|
|
info!("Command completed successfully");
|
|
}
|
|
Err(e) => {
|
|
// Print error to stderr to ensure it's always visible
|
|
eprintln!("❌ Error: {}", e);
|
|
|
|
// Also print the error chain if available
|
|
let mut current_error = e.source();
|
|
while let Some(err) = current_error {
|
|
eprintln!(" Caused by: {}", err);
|
|
current_error = err.source();
|
|
}
|
|
|
|
// Also log the error
|
|
error!("Command failed: {}", e);
|
|
std::process::exit(1);
|
|
}
|
|
}
|
|
}
|
|
|
|
const AVAILABLE_LOCALES: &[&str] = &["en", "zh-CN", "zh-TW"];
|
|
const DEFAULT_LOCALE: &str = "en";
|
|
|
|
fn init_locale_from_env() {
|
|
for env_key in ["DEFAULT_LOCALE", "LANG"] {
|
|
let Ok(raw_locale) = std::env::var(env_key) else {
|
|
continue;
|
|
};
|
|
|
|
let normalized = if env_key == "LANG" {
|
|
parse_lang_env(&raw_locale)
|
|
} else {
|
|
normalize_locale(&raw_locale)
|
|
};
|
|
|
|
if AVAILABLE_LOCALES.contains(&normalized.as_str()) {
|
|
rust_i18n::set_locale(&normalized);
|
|
return;
|
|
}
|
|
}
|
|
|
|
rust_i18n::set_locale(DEFAULT_LOCALE);
|
|
}
|
|
|
|
fn parse_lang_env(lang: &str) -> String {
|
|
let lang = lang.split('.').next().unwrap_or(lang);
|
|
let lang = lang.split('@').next().unwrap_or(lang);
|
|
normalize_locale(lang)
|
|
}
|
|
|
|
fn normalize_locale(input: &str) -> String {
|
|
let input = input.trim();
|
|
let input = input.split('.').next().unwrap_or(input);
|
|
let input = input.split('@').next().unwrap_or(input);
|
|
|
|
match input.to_lowercase().as_str() {
|
|
"en" | "en_us" | "en-us" | "en_gb" | "en-gb" => "en".to_string(),
|
|
"zh-cn" | "zh_cn" | "zh-hans" | "zh" => "zh-CN".to_string(),
|
|
"zh-tw" | "zh_tw" | "zh-hant" => "zh-TW".to_string(),
|
|
_ => input.to_string(),
|
|
}
|
|
}
|
|
|
|
/// Handle server-related commands
|
|
async fn handle_server_command(action: ServerAction, config: &voice_cli::Config) -> Result<()> {
|
|
match action {
|
|
ServerAction::Init {
|
|
config: config_path,
|
|
force,
|
|
} => {
|
|
info!("Initializing server configuration");
|
|
server::handle_server_init(config_path, force)
|
|
.await
|
|
.context("Failed to initialize server configuration")
|
|
}
|
|
ServerAction::Run { config: _ } => {
|
|
info!("Running server in foreground mode");
|
|
server::handle_server_run(config)
|
|
.await
|
|
.context("Failed to run server")
|
|
}
|
|
}
|
|
}
|
|
|
|
/// Handle model-related commands
|
|
async fn handle_model_command(action: ModelAction, config: &voice_cli::Config) -> Result<()> {
|
|
use voice_cli::cli::model;
|
|
|
|
match action {
|
|
ModelAction::Download { model_name } => {
|
|
info!("Downloading model: {}", model_name);
|
|
model::handle_model_download(config, &model_name)
|
|
.await
|
|
.context("Failed to download model")
|
|
}
|
|
ModelAction::List => {
|
|
info!("Listing models");
|
|
model::handle_model_list(config)
|
|
.await
|
|
.context("Failed to list models")
|
|
}
|
|
ModelAction::Validate => {
|
|
info!("Validating models");
|
|
model::handle_model_validate(config)
|
|
.await
|
|
.context("Failed to validate models")
|
|
}
|
|
ModelAction::Remove { model_name } => {
|
|
info!("Removing model: {}", model_name);
|
|
model::handle_model_remove(config, &model_name)
|
|
.await
|
|
.context("Failed to remove model")
|
|
}
|
|
ModelAction::Diagnose { model_name } => {
|
|
info!("Diagnosing model: {}", model_name);
|
|
model::handle_model_diagnose(config, &model_name)
|
|
.await
|
|
.context("Failed to diagnose model")
|
|
}
|
|
}
|
|
}
|
|
|
|
/// Handle TTS-related commands
|
|
async fn handle_tts_command(action: TtsAction, config: &voice_cli::Config) -> Result<()> {
|
|
use voice_cli::cli::tts;
|
|
|
|
match action {
|
|
TtsAction::Init { force } => {
|
|
info!("Initializing TTS environment");
|
|
tts::handle_tts_init(force)
|
|
.await
|
|
.context("Failed to initialize TTS environment")
|
|
}
|
|
TtsAction::Test {
|
|
text,
|
|
output,
|
|
model,
|
|
speed,
|
|
pitch,
|
|
volume,
|
|
format,
|
|
} => {
|
|
info!("Testing TTS functionality");
|
|
tts::handle_tts_test(config, text, output, model, speed, pitch, volume, format)
|
|
.await
|
|
.context("Failed to test TTS functionality")
|
|
}
|
|
}
|
|
}
|
|
|
|
/// Extract config path from server action
|
|
fn get_config_path_for_server_action(
|
|
action: &ServerAction,
|
|
_default_config: &str,
|
|
) -> Option<PathBuf> {
|
|
match action {
|
|
ServerAction::Run { config } => config.clone(),
|
|
_ => None,
|
|
}
|
|
}
|
|
|
|
#[cfg(test)]
|
|
mod tests {
|
|
use super::*;
|
|
use tempfile::TempDir;
|
|
|
|
#[test]
|
|
fn test_cli_parsing() {
|
|
use clap::Parser;
|
|
|
|
// Test server run command
|
|
let args = vec!["voice-cli", "server", "run"];
|
|
let cli = Cli::try_parse_from(args);
|
|
assert!(cli.is_ok());
|
|
|
|
// Test model download command
|
|
let args = vec!["voice-cli", "model", "download", "base"];
|
|
let cli = Cli::try_parse_from(args);
|
|
assert!(cli.is_ok());
|
|
}
|
|
}
|