feat(tui/limits): refresh usage header and timing copy

This commit is contained in:
James Peter
2025-09-22 12:46:04 +10:00
parent 7b28e5a37b
commit ddc23f6a9f
7 changed files with 80 additions and 29 deletions
+72 -16
View File
@@ -10,24 +10,68 @@ pub fn format_elapsed(start_time: Instant) -> String {
/// Convert a [`std::time::Duration`] into a human-readable, compact string.
///
/// Formatting rules:
/// * < 1 s -> "{milli}ms"
/// * < 60 s -> "{sec:.2}s" (two decimal places)
/// * >= 60 s -> "{min}m {sec:02}s"
/// * < 1 s -> "{milli}ms"
/// * < 60 s -> "{sec:.2}s" (two decimal places)
/// * < 60 m -> "{min}m {sec:02}s"
/// * < 24 h -> "{hour}h {minute:02}m" (rounded to the nearest minute)
/// * >= 24 h -> "{day}d {hour:02}h" (rounded to the nearest hour)
pub fn format_duration(duration: Duration) -> String {
let millis = duration.as_millis() as i64;
format_elapsed_millis(millis)
let millis = duration.as_millis();
if millis < 1_000 {
return format!("{millis}ms");
}
let secs = duration.as_secs();
if secs < 60 {
return format!("{:.2}s", duration.as_secs_f64());
}
if secs < 3_600 {
return format_minutes_seconds(duration);
}
if secs < 86_400 {
return format_hours_minutes(duration);
}
format_days_hours(duration)
}
fn format_elapsed_millis(millis: i64) -> String {
if millis < 1000 {
format!("{millis}ms")
} else if millis < 60_000 {
format!("{:.2}s", millis as f64 / 1000.0)
} else {
let minutes = millis / 60_000;
let seconds = (millis % 60_000) / 1000;
format!("{minutes}m {seconds:02}s")
fn format_minutes_seconds(duration: Duration) -> String {
let total_seconds = duration.as_secs();
let minutes = total_seconds / 60;
let seconds = total_seconds % 60;
format!("{minutes}m {seconds:02}s")
}
fn format_hours_minutes(duration: Duration) -> String {
let total_hours_f = duration.as_secs_f64() / 3_600.0;
let mut hours = total_hours_f.floor() as u64;
let mut minutes = ((total_hours_f - hours as f64) * 60.0).round() as u64;
if minutes == 60 {
minutes = 0;
hours += 1;
}
if hours >= 24 {
return format_days_hours(duration);
}
format!("{hours}h {minutes:02}m")
}
fn format_days_hours(duration: Duration) -> String {
let total_hours_f = duration.as_secs_f64() / 3_600.0;
let mut days = (total_hours_f / 24.0).floor() as u64;
let mut hours = (total_hours_f - days as f64 * 24.0).round() as u64;
if hours == 24 {
hours = 0;
days += 1;
}
format!("{days}d {hours:02}h")
}
#[cfg(test)]
@@ -67,12 +111,24 @@ mod tests {
assert_eq!(format_duration(dur_exact), "1m 00s");
let dur_long = Duration::from_millis(3_601_000);
assert_eq!(format_duration(dur_long), "60m 01s");
assert_eq!(format_duration(dur_long), "1h 00m");
}
#[test]
fn test_format_duration_one_hour_has_space() {
let dur_hour = Duration::from_millis(3_600_000);
assert_eq!(format_duration(dur_hour), "60m 00s");
assert_eq!(format_duration(dur_hour), "1h 00m");
}
#[test]
fn test_format_duration_hours_rounds_minutes() {
let dur = Duration::from_secs(4 * 3_600 + 58 * 60 + 40);
assert_eq!(format_duration(dur), "4h 59m");
}
#[test]
fn test_format_duration_days_rounds_hours() {
let dur = Duration::from_secs(2 * 86_400 + 11 * 3_600 + 45 * 60);
assert_eq!(format_duration(dur), "2d 12h");
}
}
@@ -4,7 +4,6 @@ expression: visual
---
[magenta]/limits[/]
[bold]Rate limit usage snapshot[/]
[dim] Tip: run `/limits` right after Codex replies for freshest numbers.[/]
[bold]Usage Limits[/]
Real usage data is not available yet.
[dim] Send a message to Codex, then run /limits again.[/]
@@ -4,8 +4,7 @@ expression: visual
---
[magenta]/limits[/]
[bold]Rate limit usage snapshot[/]
[dim] Tip: run `/limits` right after Codex replies for freshest numbers.[/]
[bold]Usage Limits[/]
• Hourly limit[dim] (≈5 hours window)[/]: [dark-gray+bold]30.0% used[/]
• Weekly limit[dim] (≈1 week window)[/]: [dark-gray+bold]60.0% used[/]
[green] Within current limits[/]
@@ -4,8 +4,7 @@ expression: visual
---
[magenta]/limits[/]
[bold]Rate limit usage snapshot[/]
[dim] Tip: run `/limits` right after Codex replies for freshest numbers.[/]
[bold]Usage Limits[/]
• Hourly limit[dim] (≈5 hours window)[/]: [dark-gray+bold]0.0% used[/]
• Weekly limit[dim] (≈1 week window)[/]: [dark-gray+bold]20.0% used[/]
[green] Within current limits[/]
@@ -4,8 +4,7 @@ expression: visual
---
[magenta]/limits[/]
[bold]Rate limit usage snapshot[/]
[dim] Tip: run `/limits` right after Codex replies for freshest numbers.[/]
[bold]Usage Limits[/]
• Hourly limit[dim] (≈5 hours window)[/]: [dark-gray+bold]20.0% used[/]
• Weekly limit[dim] (≈1 week window)[/]: [dark-gray+bold]20.0% used[/]
[green] Within current limits[/]
@@ -4,8 +4,7 @@ expression: visual
---
[magenta]/limits[/]
[bold]Rate limit usage snapshot[/]
[dim] Tip: run `/limits` right after Codex replies for freshest numbers.[/]
[bold]Usage Limits[/]
• Hourly limit[dim] (≈5 hours window)[/]: [dark-gray+bold]98.0% used[/]
• Weekly limit[dim] (≈1 week window)[/]: [dark-gray+bold]0.0% used[/]
[green] Within current limits[/]
+3 -3
View File
@@ -181,7 +181,7 @@ fn build_summary_lines(
let mut lines: Vec<Line<'static>> = vec![
"/limits".magenta().into(),
"".into(),
vec![" Tip: run `/limits` right after Codex replies for freshest numbers.".dim()].into(),
vec!["Usage Limits".bold()].into(),
build_usage_line(
" • Hourly limit",
&metrics.hourly_window_label,
@@ -336,12 +336,12 @@ fn build_reset_line(
let last = last_reset?;
let now = Utc::now();
let since = now.signed_duration_since(last).to_std().ok()?;
let mut text = format!(" • {label} window reset ~{} ago", format_duration(since));
let mut text = format!(" • {label} window started ~{} ago", format_duration(since));
let next = last + chrono::Duration::minutes(window_minutes as i64);
if let Ok(until) = next.signed_duration_since(now).to_std() {
if until.as_secs() > 0 {
text.push_str(&format!(" (next ≈ {})", format_duration(until)));
text.push_str(&format!(" (resets in ≈ {})", format_duration(until)));
}
}