use colored::{ColoredString, Colorize};
pub fn duration(secs: f32) -> ColoredString {
let text = if secs < 0.001 {
format!("{:.0}\u{00B5}s", secs * 1_000_000.0) } else if secs < 1.0 {
format!("{:.0}ms", secs * 1000.0)
} else if secs < 60.0 {
format!("{:.1}s", secs)
} else {
let m = (secs / 60.0).floor() as u32;
let s = secs % 60.0;
format!("{}m{:.1}s", m, s)
};
if secs < 1.0 {
text.green()
} else if secs < 5.0 {
text.yellow()
} else {
text.red()
}
}
pub fn tokens(n: u64) -> String {
if n < 1000 {
n.to_string()
} else if n < 10_000 {
format!("{:.1}k", n as f64 / 1000.0)
} else if n < 1_000_000 {
format!("{}k", n / 1000)
} else {
format!("{:.1}M", n as f64 / 1_000_000.0)
}
}
pub fn pad_colored(cs: &ColoredString, width: usize) -> String {
let visible_len = stripped_len(&cs.to_string());
let pad = width.saturating_sub(visible_len);
format!("{}{}", cs, " ".repeat(pad))
}
pub fn stripped_len(s: &str) -> usize {
let mut len = 0;
let mut chars = s.chars().peekable();
while let Some(ch) = chars.next() {
if ch == '\x1b' {
while let Some(&next) = chars.peek() {
chars.next();
if next.is_ascii_alphabetic() || ('@'..='~').contains(&next) {
break;
}
}
} else {
len += 1;
}
}
len
}
pub fn sparkline(value: u64, max: u64) -> ColoredString {
const CHARS: &[char] = &[
'\u{2581}', '\u{2582}', '\u{2583}', '\u{2584}', '\u{2585}', '\u{2586}', '\u{2587}',
'\u{2588}',
];
let ratio = if max == 0 {
0.0
} else {
value as f64 / max as f64
};
let idx = (ratio * 7.0).round().min(7.0) as usize;
let bar: String = (0..8)
.map(|i| if i <= idx { CHARS[idx] } else { '\u{2591}' })
.collect();
bar.blue()
}
pub fn budget_bar(pct: f32, width: usize) -> String {
let filled = ((pct / 100.0) * width as f32).round() as usize;
let empty = width.saturating_sub(filled);
let bar = format!("{}{}", "\u{2593}".repeat(filled), "\u{2591}".repeat(empty));
let colored_bar = if pct < 70.0 {
bar.green()
} else if pct < 90.0 {
bar.yellow()
} else {
bar.red()
};
let pct_str = format!("{}%", pct.round() as u32);
let colored_pct = if pct < 70.0 {
pct_str.green()
} else if pct < 90.0 {
pct_str.yellow()
} else {
pct_str.red()
};
format!("{} {}", colored_bar, colored_pct)
}
pub fn cost(usd: f64) -> ColoredString {
if usd < 0.001 {
format!("${:.4}", usd).dimmed()
} else if usd < 0.01 {
format!("${:.3}", usd).green()
} else if usd < 0.10 {
format!("${:.2}", usd).yellow()
} else {
format!("${:.2}", usd).red().bold()
}
}
pub fn ttft(ms: u64) -> ColoredString {
let text = format!("{}ms", ms);
if ms < 200 {
text.green()
} else if ms < 500 {
text.yellow()
} else {
text.red()
}
}
pub fn floor_char_boundary(s: &str, i: usize) -> usize {
if i >= s.len() {
return s.len();
}
let mut pos = i;
while pos > 0 && !s.is_char_boundary(pos) {
pos -= 1;
}
pos
}
pub fn json_preview(json: &str, max_chars: usize) -> String {
let truncated = if json.len() > max_chars {
let end = floor_char_boundary(json, max_chars);
format!("{}\u{2026}", &json[..end]) } else {
json.to_string()
};
let mut result = String::with_capacity(truncated.len() * 2);
let mut in_key = false;
let mut in_string = false;
let mut after_colon = false;
for ch in truncated.chars() {
match ch {
'"' if !in_string && !after_colon => {
in_key = true;
in_string = true;
result.push_str("\x1b[34m\""); }
'"' if !in_string && after_colon => {
in_string = true;
result.push_str("\x1b[32m\""); }
'"' if in_string => {
result.push('"');
result.push_str("\x1b[0m");
in_string = false;
if in_key {
in_key = false;
}
after_colon = false;
}
':' if !in_string => {
after_colon = true;
result.push_str("\x1b[0m:");
}
',' | '{' | '}' | '[' | ']' if !in_string => {
after_colon = false;
result.push_str(&format!("\x1b[0m{}", ch));
}
c if !in_string && (c.is_ascii_digit() || c == '.' || c == '-') => {
result.push_str(&format!("\x1b[33m{}\x1b[0m", c)); }
_ => result.push(ch),
}
}
result.push_str("\x1b[0m");
result
}
pub fn markdown_preview(md: &str, max_lines: usize) -> Vec<String> {
md.lines()
.take(max_lines)
.map(|line| {
if line.starts_with('#') {
format!("{}", line.bold().white())
} else {
line.to_string()
}
})
.collect()
}