feat(tui/history): round rate-limit windows with local reset time

This commit is contained in:
James Peter
2025-09-30 09:27:12 +10:00
parent 54718dc5a6
commit 0b13c26b68
3 changed files with 67 additions and 10 deletions
+1
View File
@@ -1348,6 +1348,7 @@ dependencies = [
"tempfile",
"textwrap 0.16.2",
"thiserror 1.0.69",
"time",
"tokio",
"tokio-util",
"tracing",
+1
View File
@@ -97,6 +97,7 @@ urlencoding = "2.1"
pulldown-cmark = "0.13"
portable-pty = "0.9.0"
thiserror = "1"
time = { workspace = true, features = ["formatting", "macros", "local-offset"] }
# vt100 0.16+ depends on unicode-width 0.2.1 which conflicts with ratatui 0.29.x.
# Stick to 0.15.x until ratatui upgrades.
vt100 = "0.15.0"
+65 -10
View File
@@ -3,6 +3,12 @@ use crate::history::state::{RateLimitLegendEntry, RateLimitsRecord, TextTone};
use codex_common::elapsed::format_duration;
use codex_core::protocol::RateLimitSnapshotEvent;
use ratatui::style::Color;
use time::{
format_description::FormatItem,
macros::format_description,
Duration as TimeDuration,
OffsetDateTime,
};
pub(crate) struct RateLimitsCell {
record: RateLimitsRecord,
@@ -78,9 +84,9 @@ fn snapshot_summary_lines(snapshot: &RateLimitSnapshotEvent) -> Vec<Line<'static
let mut lines = Vec::new();
let hourly_line = format!(
"└ Hourly usage: {:.0}% of {} min window",
snapshot.primary_used_percent,
snapshot.primary_window_minutes,
"└ Hourly usage: {percent:.0}% of {window}",
percent = snapshot.primary_used_percent,
window = format_window_minutes(snapshot.primary_window_minutes),
);
lines.push(Line::from(vec![Span::styled(
hourly_line,
@@ -88,17 +94,16 @@ fn snapshot_summary_lines(snapshot: &RateLimitSnapshotEvent) -> Vec<Line<'static
)]));
if let Some(seconds) = snapshot.primary_reset_after_seconds {
let reset = format_duration(std::time::Duration::from_secs(seconds));
lines.push(Line::from(vec![Span::styled(
format!(" • resets in {reset}"),
format_reset_line(seconds),
Style::default().fg(crate::colors::text_dim()),
)]));
}
let weekly_line = format!(
"└ Weekly usage: {:.0}% of {} min window",
snapshot.secondary_used_percent,
snapshot.secondary_window_minutes,
"└ Weekly usage: {percent:.0}% of {window}",
percent = snapshot.secondary_used_percent,
window = format_window_minutes(snapshot.secondary_window_minutes),
);
lines.push(Line::from(vec![Span::styled(
weekly_line,
@@ -106,9 +111,8 @@ fn snapshot_summary_lines(snapshot: &RateLimitSnapshotEvent) -> Vec<Line<'static
)]));
if let Some(seconds) = snapshot.secondary_reset_after_seconds {
let reset = format_duration(std::time::Duration::from_secs(seconds));
lines.push(Line::from(vec![Span::styled(
format!(" • resets in {reset}"),
format_reset_line(seconds),
Style::default().fg(crate::colors::text_dim()),
)]));
}
@@ -116,6 +120,57 @@ fn snapshot_summary_lines(snapshot: &RateLimitSnapshotEvent) -> Vec<Line<'static
lines
}
const TIME_OF_DAY_FORMAT: &[FormatItem<'static>] =
format_description!("[hour repr:12 padding:none]:[minute][period case:lower]");
const DAY_TIME_FORMAT: &[FormatItem<'static>] = format_description!(
"[weekday repr:long] [hour repr:12 padding:none]:[minute][period case:lower]"
);
fn format_window_minutes(minutes: u64) -> String {
if minutes < 60 {
return format!("{minutes} min window");
}
let hours = (minutes as f64 / 60.0).round().max(1.0);
if hours < 24.0 {
let hours = hours as u64;
let unit = if hours == 1 { "hour" } else { "hours" };
return format!("{hours} {unit} window");
}
let days = (minutes as f64 / 1_440.0).round().max(1.0) as u64;
if days % 7 == 0 {
let weeks = days / 7;
let unit = if weeks == 1 { "week" } else { "weeks" };
return format!("{weeks} {unit} window");
}
let unit = if days == 1 { "day" } else { "days" };
format!("{days} {unit} window")
}
fn format_reset_line(seconds: u64) -> String {
let reset_duration = std::time::Duration::from_secs(seconds);
let reset = format_duration(reset_duration);
let timestamp = format_reset_timestamp(seconds)
.map(|formatted| format!(" @ {formatted}"))
.unwrap_or_default();
format!(" • resets in {reset}{timestamp}")
}
fn format_reset_timestamp(seconds: u64) -> Option<String> {
let now = OffsetDateTime::now_local().unwrap_or_else(|_| OffsetDateTime::now_utc());
let seconds_i64 = i64::try_from(seconds).ok()?;
let reset_at = now + TimeDuration::seconds(seconds_i64);
let same_day = now.date() == reset_at.date();
let format = if same_day {
TIME_OF_DAY_FORMAT
} else {
DAY_TIME_FORMAT
};
reset_at.format(format).ok()
}
fn legend_lines(entry: &RateLimitLegendEntry) -> Vec<Line<'static>> {
let tone_style = Style::default().fg(color_for_tone(entry.tone));
let mut lines = Vec::new();