use crate::commands::ModelsCommand;
use crate::context::CliContext;
use crate::paths;
use crate::services::ModelService;
use anyhow::{Context, Result};
use console::style;
use dialoguer::Confirm;
use indicatif::{ProgressBar, ProgressStyle};
use std::path::PathBuf;
pub async fn handle_models(_ctx: &mut CliContext, cmd: &ModelsCommand) -> Result<()> {
println!();
println!("{}", style("🤖 AI Model Management").bold());
println!("{}", style("━".repeat(50)).dim());
println!();
let models_dir = if let Ok(project) = _ctx.project() {
project.root().join(paths::project::MODELS_DIR)
} else {
PathBuf::from(paths::project::MODELS_DIR)
};
let service = ModelService::with_models_dir(models_dir).context("Failed to initialize model service")?;
match cmd {
ModelsCommand::List { verbose, recommended } => {
handle_list(&service, *verbose, *recommended)?;
}
ModelsCommand::Pull {
name,
all,
repo,
file,
custom_name,
} => {
handle_pull(&service, name.as_deref(), *all, repo, file, custom_name).await?;
}
ModelsCommand::Installed { verbose } => {
handle_installed(&service, *verbose).await?;
}
ModelsCommand::Remove { name, yes } => {
handle_remove(&service, name, *yes).await?;
}
ModelsCommand::Info { name } => {
handle_info(&service, name).await?;
}
}
Ok(())
}
fn handle_list(service: &ModelService, verbose: bool, _recommended: bool) -> Result<()> {
let catalog = service.list_catalog()?;
if catalog.is_empty() {
println!("No models found in catalog.");
return Ok(());
}
println!("{} Models available in catalog:", style("📦").bold());
println!();
for model in &catalog {
if verbose {
println!(" {} {}", style("•").cyan(), style(&model.name).bold());
println!(" Description: {}", model.description);
println!(" Task: {}", model.task);
println!(" Repository: {}", model.repo);
println!(" File: {}", model.filename);
if let Some(preset) = &model.preprocessing_preset {
println!(" Preset: {}", preset);
}
println!();
} else {
println!(
" {} {} - {}",
style("•").cyan(),
style(&model.name).bold(),
model.description
);
}
}
if !verbose {
println!();
println!("{}", style("Use --verbose for detailed information").dim());
}
println!();
println!("To download a model: {}", style("mecha10 models pull <name>").cyan());
println!();
Ok(())
}
async fn handle_pull(
service: &ModelService,
name: Option<&str>,
all: bool,
repo: &Option<String>,
file: &Option<String>,
custom_name: &Option<String>,
) -> Result<()> {
let pb = ProgressBar::new_spinner();
pb.set_style(
ProgressStyle::default_spinner()
.template("{spinner:.green} {msg}")
.unwrap(),
);
pb.enable_steady_tick(std::time::Duration::from_millis(100));
if all {
println!("Downloading all models from catalog...");
println!();
let catalog = service.list_catalog()?;
let mut paths = Vec::new();
for entry in catalog {
pb.set_message(format!("Downloading {}", entry.name));
match service.pull(&entry.name, Some(&pb)).await {
Ok(path) => paths.push(path),
Err(e) => {
eprintln!("⚠️ Failed to download {}: {}", entry.name, e);
}
}
}
pb.finish_and_clear();
println!("{} Downloaded {} models:", style("✓").green().bold(), paths.len());
for path in &paths {
println!(" • {}", path.display());
}
println!();
} else if let (Some(repo), Some(file), Some(custom_name)) = (repo, file, custom_name) {
pb.set_message(format!("Downloading {} from {}", custom_name, repo));
let path = service.pull_from_repo(repo, file, custom_name, Some(&pb)).await?;
pb.finish_and_clear();
println!(
"{} Downloaded custom model '{}'",
style("✓").green().bold(),
custom_name
);
println!(" Location: {}", path.display());
println!();
} else if let Some(name) = name {
pb.set_message(format!("Downloading {}", name));
let path = service.pull(name, Some(&pb)).await?;
pb.finish_and_clear();
println!("{} Downloaded '{}'", style("✓").green().bold(), name);
println!(" Location: {}", path.display());
println!();
} else {
pb.finish_and_clear();
return Err(anyhow::anyhow!(
"Please specify a model name, --all, or --repo with --file and --custom-name"
));
}
Ok(())
}
async fn handle_installed(service: &ModelService, verbose: bool) -> Result<()> {
let installed = service.list_installed().await?;
if installed.is_empty() {
println!("No models installed.");
println!();
println!("To download models: {}", style("mecha10 models pull <name>").cyan());
println!();
return Ok(());
}
let count_str = if installed.len() == 1 {
"1 model".to_string()
} else {
format!("{} models", installed.len())
};
println!("{} {} installed:", style("📦").bold(), count_str);
println!();
for model in &installed {
if verbose {
println!(" {} {}", style("•").cyan(), style(&model.name).bold());
println!(" Path: {}", model.path.display());
println!(" Size: {} bytes", model.size);
if let Some(entry) = &model.catalog_entry {
println!(" From catalog: {}", entry.description);
println!(" Task: {}", entry.task);
} else {
println!(" {} Custom model", style("ℹ").dim());
}
println!();
} else {
let size_mb = model.size as f64 / 1_048_576.0;
let catalog_info = if let Some(entry) = &model.catalog_entry {
format!(" ({})", entry.task)
} else {
" (custom)".to_string()
};
println!(
" {} {} - {:.2} MB{}",
style("•").cyan(),
style(&model.name).bold(),
size_mb,
style(catalog_info).dim()
);
}
}
if !verbose {
println!();
println!("{}", style("Use --verbose for detailed information").dim());
}
println!();
Ok(())
}
async fn handle_remove(service: &ModelService, name: &str, skip_confirm: bool) -> Result<()> {
let installed = service.list_installed().await?;
let model = installed
.iter()
.find(|m| m.name == name)
.ok_or_else(|| anyhow::anyhow!("Model '{}' is not installed", name))?;
if !skip_confirm {
let confirm = Confirm::new()
.with_prompt(format!("Remove model '{}'? ({} bytes)", name, model.size))
.default(false)
.interact()?;
if !confirm {
println!("Cancelled.");
return Ok(());
}
}
service.remove(name).await?;
println!("{} Removed model '{}'", style("✓").green().bold(), name);
println!();
Ok(())
}
async fn handle_info(service: &ModelService, name: &str) -> Result<()> {
let info = service.info(name).await?;
println!("{} Model: {}", style("ℹ").blue().bold(), style(name).bold());
println!();
if let Some(entry) = &info.catalog_entry {
println!("{}", style("Catalog Information:").underlined());
println!(" Description: {}", entry.description);
println!(" Task: {}", entry.task);
println!(" Repository: {}", entry.repo);
println!(" File: {}", entry.filename);
if let Some(preset) = &entry.preprocessing_preset {
println!(" Preset: {}", preset);
}
println!();
} else {
println!("{}", style("Not found in catalog (custom model)").dim());
println!();
}
if let Some(installed) = &info.installed_info {
println!("{}", style("Installation:").underlined());
println!(" Status: {}", style("Installed").green());
println!(" Path: {}", installed.path.display());
println!(
" Size: {} bytes ({:.2} MB)",
installed.size,
installed.size as f64 / 1_048_576.0
);
println!();
} else {
println!("{}", style("Installation:").underlined());
println!(" Status: {}", style("Not installed").yellow());
println!();
println!(
" To install: {}",
style(format!("mecha10 models pull {}", name)).cyan()
);
println!();
}
Ok(())
}