diff --git a/README.md b/README.md index 91512b7..3d66d50 100644 --- a/README.md +++ b/README.md @@ -263,6 +263,30 @@ All segments are configurable with: Supported segments: Directory, Git, Model, Usage, Time, Cost, OutputStyle +### Usage Segment Options + +The `usage` segment displays Claude API usage from the `/api/oauth/usage` endpoint and supports the following options: + +| Option | Type | Default | Description | +|---|---|---|---| +| `api_base_url` | string | `"https://api.anthropic.com"` | Base URL for the Anthropic API | +| `cache_duration` | integer | `300` | How long to cache API results (seconds) | +| `timeout` | integer | `2` | API request timeout (seconds) | +| `reset_period` | string | `"session"` | Which reset time to display: `"session"` (5-hour window) or `"weekly"` (7-day window) | +| `reset_format` | string | `"time"` | How to format the reset time: `"time"` (e.g. `2-22-13`) or `"duration"` (e.g. `4h 52m`) | + +Example configuration: + +```toml +[[segments]] +id = "usage" +enabled = true + +[segments.options] +reset_period = "session" +reset_format = "duration" +cache_duration = 180 +``` ## Requirements diff --git a/README.zh.md b/README.zh.md index 88656db..6446b74 100644 --- a/README.zh.md +++ b/README.zh.md @@ -255,6 +255,30 @@ CCometixLine 支持通过 TOML 文件和交互式 TUI 进行完整配置: 支持的段落:目录、Git、模型、使用量、时间、成本、输出样式 +### 使用量段落选项 + +`usage` 段落通过 `/api/oauth/usage` 接口显示 Claude API 使用情况,支持以下选项: + +| 选项 | 类型 | 默认值 | 说明 | +|---|---|---|---| +| `api_base_url` | 字符串 | `"https://api.anthropic.com"` | Anthropic API 的基础 URL | +| `cache_duration` | 整数 | `300` | API 结果缓存时长(秒) | +| `timeout` | 整数 | `2` | API 请求超时时间(秒) | +| `reset_period` | 字符串 | `"session"` | 显示哪个重置时间:`"session"`(5小时窗口)或 `"weekly"`(7天窗口) | +| `reset_format` | 字符串 | `"time"` | 重置时间的显示格式:`"time"`(例如 `2-22-13`)或 `"duration"`(例如 `4h 52m`) | + +配置示例: + +```toml +[[segments]] +id = "usage" +enabled = true + +[segments.options] +reset_period = "session" +reset_format = "duration" +cache_duration = 180 +``` ## 系统要求 diff --git a/src/core/segments/usage.rs b/src/core/segments/usage.rs index 135ad47..7103fc3 100644 --- a/src/core/segments/usage.rs +++ b/src/core/segments/usage.rs @@ -21,7 +21,8 @@ struct UsagePeriod { struct ApiUsageCache { five_hour_utilization: f64, seven_day_utilization: f64, - resets_at: Option, + five_hour_resets_at: Option, + seven_day_resets_at: Option, cached_at: String, } @@ -65,6 +66,34 @@ impl UsageSegment { "?".to_string() } + fn format_reset_duration(reset_time_str: Option<&str>) -> String { + if let Some(time_str) = reset_time_str { + if let Ok(dt) = DateTime::parse_from_rfc3339(time_str) { + let now = Utc::now(); + let reset_utc = dt.with_timezone(&Utc); + let remaining = reset_utc.signed_duration_since(now); + + if remaining.num_seconds() <= 0 { + return "now".to_string(); + } + + let total_minutes = remaining.num_minutes(); + let days = total_minutes / (24 * 60); + let hours = (total_minutes % (24 * 60)) / 60; + let minutes = total_minutes % 60; + + return if days > 0 { + format!("{}d {}h", days, hours) + } else if hours > 0 { + format!("{}h {}m", hours, minutes) + } else { + format!("{}m", minutes) + }; + } + } + "?".to_string() + } + fn get_cache_path() -> Option { let home = dirs::home_dir()?; Some( @@ -209,12 +238,25 @@ impl Segment for UsageSegment { .map(|cache| self.is_cache_valid(cache, cache_duration)) .unwrap_or(false); - let (five_hour_util, seven_day_util, resets_at) = if use_cached { + let reset_period = segment_config + .and_then(|sc| sc.options.get("reset_period")) + .and_then(|v| v.as_str()) + .unwrap_or("session") + .to_string(); + + let reset_format = segment_config + .and_then(|sc| sc.options.get("reset_format")) + .and_then(|v| v.as_str()) + .unwrap_or("time") + .to_string(); + + let (five_hour_util, seven_day_util, five_hour_resets_at, seven_day_resets_at) = if use_cached { let cache = cached_data.unwrap(); ( cache.five_hour_utilization, cache.seven_day_utilization, - cache.resets_at, + cache.five_hour_resets_at, + cache.seven_day_resets_at, ) } else { match self.fetch_api_usage(api_base_url, &token, timeout) { @@ -222,13 +264,15 @@ impl Segment for UsageSegment { let cache = ApiUsageCache { five_hour_utilization: response.five_hour.utilization, seven_day_utilization: response.seven_day.utilization, - resets_at: response.seven_day.resets_at.clone(), + five_hour_resets_at: response.five_hour.resets_at.clone(), + seven_day_resets_at: response.seven_day.resets_at.clone(), cached_at: Utc::now().to_rfc3339(), }; self.save_cache(&cache); ( response.five_hour.utilization, response.seven_day.utilization, + response.five_hour.resets_at, response.seven_day.resets_at, ) } @@ -237,7 +281,8 @@ impl Segment for UsageSegment { ( cache.five_hour_utilization, cache.seven_day_utilization, - cache.resets_at, + cache.five_hour_resets_at, + cache.seven_day_resets_at, ) } else { return None; @@ -246,10 +291,21 @@ impl Segment for UsageSegment { } }; + let resets_at = if reset_period == "weekly" { + seven_day_resets_at.as_deref() + } else { + five_hour_resets_at.as_deref() + }; + let dynamic_icon = Self::get_circle_icon(seven_day_util / 100.0); let five_hour_percent = five_hour_util.round() as u8; let primary = format!("{}%", five_hour_percent); - let secondary = format!("· {}", Self::format_reset_time(resets_at.as_deref())); + let reset_str = if reset_format == "duration" { + Self::format_reset_duration(resets_at) + } else { + Self::format_reset_time(resets_at) + }; + let secondary = format!("· {}", reset_str); let mut metadata = HashMap::new(); metadata.insert("dynamic_icon".to_string(), dynamic_icon);