Files
qiming/qiming-mcp-proxy/voice-cli/src/main.rs
2026-06-01 13:03:20 +08:00

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());
}
}