subx_cli/cli/
ui.rs

1// src/cli/ui.rs
2use crate::cli::table::{MatchDisplayRow, create_match_table};
3use crate::core::matcher::MatchOperation;
4use colored::*;
5use indicatif::{ProgressBar, ProgressDrawTarget, ProgressStyle};
6
7/// 列印成功訊息
8pub fn print_success(message: &str) {
9    println!("{} {}", "✓".green().bold(), message);
10}
11
12/// 列印錯誤訊息
13pub fn print_error(message: &str) {
14    eprintln!("{} {}", "✗".red().bold(), message);
15}
16
17/// 列印警告訊息
18pub fn print_warning(message: &str) {
19    println!("{} {}", "⚠".yellow().bold(), message);
20}
21
22/// 建立進度條
23pub fn create_progress_bar(total: u64) -> ProgressBar {
24    let pb = ProgressBar::new(total);
25    // 根據配置決定是否顯示進度條
26    if let Ok(cfg) = crate::config::load_config() {
27        if !cfg.general.enable_progress_bar {
28            pb.set_draw_target(ProgressDrawTarget::hidden());
29        }
30    }
31    pb.set_style(
32        ProgressStyle::default_bar()
33            .template(
34                "{spinner:.green} [{elapsed_precise}] [{bar:40.cyan/blue}] {pos}/{len} ({eta})",
35            )
36            .unwrap(),
37    );
38    pb
39}
40
41/// 顯示 AI API 呼叫使用統計資訊
42pub fn display_ai_usage(usage: &crate::services::ai::AiUsageStats) {
43    println!("🤖 AI API 呼叫詳情:");
44    println!("   模型: {}", usage.model);
45    println!("   Prompt tokens: {}", usage.prompt_tokens);
46    println!("   Completion tokens: {}", usage.completion_tokens);
47    println!("   Total tokens: {}", usage.total_tokens);
48    println!();
49}
50
51/// 顯示檔案對映結果,支援 dry-run 預覽模式
52pub fn display_match_results(results: &[MatchOperation], is_dry_run: bool) {
53    if results.is_empty() {
54        println!("{}", "沒有找到匹配的檔案對映".yellow());
55        return;
56    }
57
58    println!("\n{}", "📋 檔案對映結果".bold().blue());
59    if is_dry_run {
60        println!("{}", "🔍 預覽模式 (不會實際修改檔案)".yellow());
61    }
62    println!();
63
64    let rows: Vec<MatchDisplayRow> = results
65        .iter()
66        .enumerate()
67        .map(|(i, op)| {
68            let idx = i + 1;
69            let video = op.video_file.path.to_string_lossy();
70            let subtitle = op.subtitle_file.path.to_string_lossy();
71            let new_name_str = &op.new_subtitle_name;
72            MatchDisplayRow {
73                status: if is_dry_run {
74                    "🔍 預覽".yellow().to_string()
75                } else {
76                    "✅ 完成".green().to_string()
77                },
78                video_file: format!("影片檔案 {}\n{}", idx, video),
79                subtitle_file: format!("字幕檔案 {}\n{}", idx, subtitle),
80                new_name: format!("新檔名 {}\n{}", idx, new_name_str),
81            }
82        })
83        .collect();
84
85    println!("{}", create_match_table(rows));
86
87    println!(
88        "\n{}",
89        format!("總共處理了 {} 個檔案對映", results.len()).bold()
90    );
91}
92
93#[cfg(test)]
94mod tests {
95    use super::*;
96
97    #[test]
98    fn test_match_table_display() {
99        let rows = vec![MatchDisplayRow {
100            status: "✅ 完成".to_string(),
101            video_file: "movie1.mp4".to_string(),
102            subtitle_file: "subtitle1.srt".to_string(),
103            new_name: "movie1.srt".to_string(),
104        }];
105
106        let table = create_match_table(rows);
107        assert!(table.contains("movie1.mp4"));
108    }
109}