feat(cli): add delete subcommand to remove downloaded models

This commit is contained in:
XiaoYang
2026-02-08 13:56:57 +08:00
parent ba16c9edf5
commit b81f63fcbd
3 changed files with 161 additions and 0 deletions
+31
View File
@@ -223,6 +223,37 @@ aha download -m qwen3vl-2b --download-retries 5
aha download -m minicpm4-0.5b -s models
```
### delete - Delete downloaded model
Delete a downloaded model from the default location (`~/.aha/{model_id}`).
**Syntax:**
```bash
aha delete [OPTIONS] --model <MODEL>
```
**Options:**
| Option | Description | Default |
|--------|-------------|---------|
| `-m, --model <MODEL>` | Model type (required) | - |
**Examples:**
```bash
# Delete RMBG2.0 model from default location
aha delete -m rmbg2.0
# Delete Qwen3-VL-2B model
aha delete --model qwen3vl-2b
```
**Behavior:**
- Displays model information (ID, location, size) before deletion
- Requires confirmation (y/N) before proceeding
- Shows "Model not found" message if the model directory doesn't exist
- Shows "Model deleted successfully" message after completion
## Supported Models
| Model ID | Model Name | Description |
+31
View File
@@ -223,6 +223,37 @@ aha download -m qwen3vl-2b --download-retries 5
aha download -m minicpm4-0.5b -s models
```
### delete - 删除已下载的模型
删除默认位置(`~/.aha/{model_id}`)的已下载模型。
**语法:**
```bash
aha delete [OPTIONS] --model <MODEL>
```
**选项:**
| 选项 | 说明 | 默认值 |
|------|------|--------|
| `-m, --model <MODEL>` | 模型类型(必选) | - |
**示例:**
```bash
# 删除 RMBG2.0 模型
aha delete -m rmbg2.0
# 删除 Qwen3-VL-2B 模型
aha delete --model qwen3vl-2b
```
**行为说明:**
- 删除前会显示模型信息(ID、位置、大小)
- 需要用户确认(y/N)才会执行删除
- 如果模型目录不存在,显示"模型未找到"消息
- 删除完成后显示"删除成功"消息
## 支持的模型
| 模型标识 | 模型名称 | 说明 |
+99
View File
@@ -56,6 +56,8 @@ enum Commands {
Serv(ServArgs),
/// List all running aha services
Ps(ServListArgs),
/// Delete a downloaded model from the default location (~/.aha/{model_id})
Delete(DeleteArgs),
/// Download model only
Download(DownloadArgs),
/// Run model inference directly
@@ -158,6 +160,14 @@ struct RunArgs {
weight_path: Option<String>,
}
/// Arguments for the 'delete' subcommand (delete model from default location)
#[derive(Args, Debug)]
struct DeleteArgs {
/// Model type (required)
#[arg(short, long)]
model: WhichModel,
}
/// Get the default weight path for a given model
/// Returns ~/.aha/{model_id} e.g., ~/.aha/OpenBMB/VoxCPM1.5
fn get_default_weight_path(model: WhichModel) -> String {
@@ -408,6 +418,94 @@ fn run_run(args: RunArgs) -> anyhow::Result<()> {
Ok(())
}
/// Run the 'delete' subcommand: delete model from default location
fn run_delete(args: DeleteArgs) -> anyhow::Result<()> {
let DeleteArgs { model } = args;
let model_id = model.model_id();
let save_dir = get_default_save_dir().expect("Failed to get home directory");
let model_path = format!("{}/{}", save_dir, model_id);
let path = std::path::Path::new(&model_path);
if !path.exists() {
println!("Model not found: {} does not exist", model_path);
return Ok(());
}
// Show model info
println!("Model ID: {}", model_id);
println!("Location: {}", model_path);
// Calculate size if possible
if let Ok(metadata) = std::fs::metadata(path) {
if metadata.is_dir() {
if let Ok(total_size) = dir_size(path) {
println!("Size: {}", bytes_to_human(total_size));
}
}
}
// Confirm deletion
print!("Are you sure you want to delete this model? (y/N): ");
use std::io::Write;
std::io::stdout().flush()?;
let mut input = String::new();
std::io::stdin().read_line(&mut input)?;
let input = input.trim().to_lowercase();
if input != "y" && input != "yes" {
println!("Deletion cancelled.");
return Ok(());
}
// Delete the directory
std::fs::remove_dir_all(path)?;
println!("Model deleted successfully: {}", model_path);
Ok(())
}
/// Calculate total size of a directory recursively
fn dir_size(path: &std::path::Path) -> anyhow::Result<u64> {
let mut total = 0;
if path.is_dir() {
for entry in std::fs::read_dir(path)? {
let entry = entry?;
let entry_path = entry.path();
if entry_path.is_dir() {
total += dir_size(&entry_path)?;
} else {
total += entry.metadata()?.len();
}
}
} else {
total = std::fs::metadata(path)?.len();
}
Ok(total)
}
/// Convert bytes to human readable format
fn bytes_to_human(bytes: u64) -> String {
const KB: u64 = 1024;
const MB: u64 = KB * 1024;
const GB: u64 = MB * 1024;
const TB: u64 = GB * 1024;
if bytes >= TB {
format!("{:.2} TB", bytes as f64 / TB as f64)
} else if bytes >= GB {
format!("{:.2} GB", bytes as f64 / GB as f64)
} else if bytes >= MB {
format!("{:.2} MB", bytes as f64 / MB as f64)
} else if bytes >= KB {
format!("{:.2} KB", bytes as f64 / KB as f64)
} else {
format!("{} B", bytes)
}
}
#[tokio::main]
async fn main() -> anyhow::Result<()> {
let cli = Cli::parse();
@@ -416,6 +514,7 @@ async fn main() -> anyhow::Result<()> {
Some(Commands::Cli(args)) => run_cli(args).await,
Some(Commands::Serv(args)) => run_serv(args).await,
Some(Commands::Ps(args)) => run_ps(args),
Some(Commands::Delete(args)) => run_delete(args),
Some(Commands::Download(args)) => run_download(args).await,
Some(Commands::Run(args)) => run_run(args),
Some(Commands::List) => run_list(),