use anyhow::Result;
use indicatif::{ProgressBar, ProgressStyle};
use std::io::IsTerminal;
use std::sync::atomic::{AtomicBool, Ordering};
use std::sync::Arc;
use std::time::Duration;
fn format_elapsed_time(elapsed: Duration) -> String {
let total_secs = elapsed.as_secs();
if total_secs < 60 {
format!("{}s", total_secs)
} else if total_secs < 3600 {
let mins = total_secs / 60;
let secs = total_secs % 60;
if secs > 0 {
format!("{}m {}s", mins, secs)
} else {
format!("{}m", mins)
}
} else {
let hours = total_secs / 3600;
let mins = (total_secs % 3600) / 60;
let secs = total_secs % 60;
if mins > 0 && secs > 0 {
format!("{}h {}m {}s", hours, mins, secs)
} else if mins > 0 {
format!("{}h {}m", hours, mins)
} else if secs > 0 {
format!("{}h {}s", hours, secs)
} else {
format!("{}h", hours)
}
}
}
pub async fn show_loading_animation(
cancel_flag: Arc<AtomicBool>,
cost: f64,
current_context_tokens: u64,
max_session_tokens_threshold: usize,
) -> Result<()> {
let spinner = ProgressBar::new_spinner();
spinner.set_style(
ProgressStyle::default_spinner()
.template(" {spinner:.cyan} {msg:.cyan}")
.unwrap()
.tick_chars("⠋⠙⠹⠸⠼⠴⠦⠧"),
);
let start_time = std::time::Instant::now();
let base_message = if cost > 0.0 {
if max_session_tokens_threshold > 0 {
let percentage = (current_context_tokens as f64 / max_session_tokens_threshold as f64
* 100.0)
.min(100.0);
format!("[${:.2}|{:.1}%] Working …", cost, percentage)
} else {
format!("[${:.2}|∞] Working …", cost)
}
} else {
"Working …".to_string()
};
spinner.set_message(base_message.clone());
spinner.enable_steady_tick(Duration::from_millis(50));
while !cancel_flag.load(Ordering::SeqCst) {
tokio::time::sleep(Duration::from_millis(100)).await;
let elapsed = start_time.elapsed();
let elapsed_secs = elapsed.as_secs();
let message = if elapsed_secs > 0 {
use colored::Colorize;
let time_and_hint = format!("({} • Ctrl+C to interrupt)", format_elapsed_time(elapsed));
format!("{} {}", base_message, time_and_hint.dimmed())
} else {
use colored::Colorize;
format!("{} {}", base_message, "(Ctrl+C to interrupt)".dimmed())
};
spinner.set_message(message);
}
spinner.finish_and_clear();
Ok(())
}
pub async fn show_no_animation(cancel_flag: Arc<AtomicBool>, cost: f64) -> Result<()> {
if !std::io::stdin().is_terminal() {
println!(
" ── cost: ${:.5} ────────────────────────────────────────",
cost
);
}
while !cancel_flag.load(Ordering::SeqCst) {
tokio::time::sleep(Duration::from_millis(10)).await;
}
Ok(())
}
pub async fn show_smart_animation(
cancel_flag: Arc<AtomicBool>,
cost: f64,
current_context_tokens: u64,
max_session_tokens_threshold: usize,
) -> Result<()> {
if std::io::stdin().is_terminal() {
show_loading_animation(
cancel_flag,
cost,
current_context_tokens,
max_session_tokens_threshold,
)
.await
} else {
show_no_animation(cancel_flag, cost).await
}
}
pub fn show_generation_message_static(cost: f64) {
if !std::io::stdin().is_terminal() {
println!(
" ── cost: ${:.5} ────────────────────────────────────────",
cost
);
}
}