feat(cli): add delete subcommand to remove downloaded models
This commit is contained in:
+31
@@ -223,6 +223,37 @@ aha download -m qwen3vl-2b --download-retries 5
|
|||||||
aha download -m minicpm4-0.5b -s models
|
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
|
## Supported Models
|
||||||
|
|
||||||
| Model ID | Model Name | Description |
|
| Model ID | Model Name | Description |
|
||||||
|
|||||||
@@ -223,6 +223,37 @@ aha download -m qwen3vl-2b --download-retries 5
|
|||||||
aha download -m minicpm4-0.5b -s models
|
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
@@ -56,6 +56,8 @@ enum Commands {
|
|||||||
Serv(ServArgs),
|
Serv(ServArgs),
|
||||||
/// List all running aha services
|
/// List all running aha services
|
||||||
Ps(ServListArgs),
|
Ps(ServListArgs),
|
||||||
|
/// Delete a downloaded model from the default location (~/.aha/{model_id})
|
||||||
|
Delete(DeleteArgs),
|
||||||
/// Download model only
|
/// Download model only
|
||||||
Download(DownloadArgs),
|
Download(DownloadArgs),
|
||||||
/// Run model inference directly
|
/// Run model inference directly
|
||||||
@@ -158,6 +160,14 @@ struct RunArgs {
|
|||||||
weight_path: Option<String>,
|
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
|
/// Get the default weight path for a given model
|
||||||
/// Returns ~/.aha/{model_id} e.g., ~/.aha/OpenBMB/VoxCPM1.5
|
/// Returns ~/.aha/{model_id} e.g., ~/.aha/OpenBMB/VoxCPM1.5
|
||||||
fn get_default_weight_path(model: WhichModel) -> String {
|
fn get_default_weight_path(model: WhichModel) -> String {
|
||||||
@@ -408,6 +418,94 @@ fn run_run(args: RunArgs) -> anyhow::Result<()> {
|
|||||||
Ok(())
|
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]
|
#[tokio::main]
|
||||||
async fn main() -> anyhow::Result<()> {
|
async fn main() -> anyhow::Result<()> {
|
||||||
let cli = Cli::parse();
|
let cli = Cli::parse();
|
||||||
@@ -416,6 +514,7 @@ async fn main() -> anyhow::Result<()> {
|
|||||||
Some(Commands::Cli(args)) => run_cli(args).await,
|
Some(Commands::Cli(args)) => run_cli(args).await,
|
||||||
Some(Commands::Serv(args)) => run_serv(args).await,
|
Some(Commands::Serv(args)) => run_serv(args).await,
|
||||||
Some(Commands::Ps(args)) => run_ps(args),
|
Some(Commands::Ps(args)) => run_ps(args),
|
||||||
|
Some(Commands::Delete(args)) => run_delete(args),
|
||||||
Some(Commands::Download(args)) => run_download(args).await,
|
Some(Commands::Download(args)) => run_download(args).await,
|
||||||
Some(Commands::Run(args)) => run_run(args),
|
Some(Commands::Run(args)) => run_run(args),
|
||||||
Some(Commands::List) => run_list(),
|
Some(Commands::List) => run_list(),
|
||||||
|
|||||||
Reference in New Issue
Block a user