use std::sync::{Arc, Mutex};
use std::time::Duration;
use atomcode_core::coding_plan::types::UsageInfo;
use tokio::sync::mpsc;
use crate::render::HintSeverity;
pub const USAGE_COOLDOWN: Duration = Duration::from_secs(30);
pub fn spawn_check(slot: Arc<Mutex<Option<UsageInfo>>>, wake_tx: mpsc::Sender<()>) {
tokio::spawn(async move {
let fetched: Result<UsageInfo, ()> = tokio::task::spawn_blocking(|| {
let client = atomcode_core::coding_plan::client::Client::from_stored_auth()
.map_err(|_| ())?;
let resp = client.status_v2().map_err(|_| ())?;
resp.current_usage.ok_or(())
})
.await
.unwrap_or(Err(()));
let info = match fetched {
Ok(i) => i,
Err(_) => return,
};
if let Ok(mut s) = slot.lock() {
*s = Some(info);
}
let _ = wake_tx.try_send(());
});
}
pub fn build_usage_hint(
slot: &Arc<Mutex<Option<UsageInfo>>>,
current_provider: &str,
) -> Option<(String, HintSeverity)> {
if !crate::event_loop::monitor::is_codingplan_provider(current_provider) {
return None;
}
let info = slot.lock().ok()?.as_ref()?.clone();
build_usage_hint_from_info(&info)
}
fn build_usage_hint_from_info(info: &UsageInfo) -> Option<(String, HintSeverity)> {
let raw_percent = std::env::var("ATOMCODE_USAGE_DEBUG_PERCENT")
.ok()
.and_then(|v| v.parse::<f64>().ok())
.unwrap_or(info.usage_percent);
let threshold = std::env::var("ATOMCODE_USAGE_DEBUG_THRESHOLD")
.ok()
.and_then(|v| v.parse::<f64>().ok())
.unwrap_or(80.0);
if raw_percent < threshold {
return None;
}
let severity = if raw_percent >= 95.0 {
HintSeverity::Warning
} else {
HintSeverity::Info
};
let percent = raw_percent.round() as u32;
let window = info.window_hours;
let text = if info.reset_at_display.is_empty() {
format!("Token使用量 {}%,{}小时滚动窗口", percent, window)
} else {
format!(
"Token使用量 {}%,{}小时滚动窗口 重置于 {}",
percent, window, info.reset_at_display
)
};
Some((text, severity))
}
#[cfg(test)]
mod tests {
use super::*;
fn mk_info(percent: f64, window: i32, reset: &str) -> UsageInfo {
UsageInfo {
placeholder: false,
window_token_limit: 0,
window_tokens_used: 0,
usage_percent: percent,
window_hours: window,
reset_at: String::new(),
reset_at_display: reset.to_string(),
seconds_until_reset: 0,
reset_label: String::new(),
usage_status_desc: String::new(),
}
}
fn slot_with(info: Option<UsageInfo>) -> Arc<Mutex<Option<UsageInfo>>> {
Arc::new(Mutex::new(info))
}
#[test]
fn no_hint_when_not_codingplan_provider() {
let slot = slot_with(Some(mk_info(99.0, 5, "14:30")));
assert!(build_usage_hint(&slot, "OpenAI").is_none());
}
#[test]
fn no_hint_when_slot_empty() {
let slot: Arc<Mutex<Option<UsageInfo>>> = slot_with(None);
assert!(build_usage_hint(&slot, "AtomGit").is_none());
}
#[test]
fn no_hint_below_threshold_79_9() {
let info = mk_info(79.9, 5, "14:30");
assert!(build_usage_hint_from_info(&info).is_none());
}
#[test]
fn hint_at_exactly_80_percent_info_severity() {
let info = mk_info(80.0, 5, "14:30");
let (text, sev) = build_usage_hint_from_info(&info).expect("Some");
assert_eq!(sev, HintSeverity::Info);
assert!(text.contains("80%"));
assert!(text.contains("5小时"));
assert!(text.contains("14:30"));
}
#[test]
fn hint_at_94_percent_still_info() {
let info = mk_info(94.6, 5, "14:30");
let (_, sev) = build_usage_hint_from_info(&info).expect("Some");
assert_eq!(sev, HintSeverity::Info);
}
#[test]
fn hint_at_95_percent_warning_severity() {
let info = mk_info(95.0, 5, "14:30");
let (_, sev) = build_usage_hint_from_info(&info).expect("Some");
assert_eq!(sev, HintSeverity::Warning);
}
#[test]
fn hint_at_100_percent_warning() {
let info = mk_info(100.0, 5, "14:30");
let (text, sev) = build_usage_hint_from_info(&info).expect("Some");
assert_eq!(sev, HintSeverity::Warning);
assert!(text.contains("100%"));
}
#[test]
fn hint_format_matches_spec_template() {
let info = mk_info(87.4, 5, "20:32");
let (text, _) = build_usage_hint_from_info(&info).expect("Some");
assert_eq!(text, "Token使用量 87%,5小时滚动窗口 重置于 20:32");
}
#[test]
fn hint_omits_reset_when_display_empty() {
let info = mk_info(85.0, 5, "");
let (text, _) = build_usage_hint_from_info(&info).expect("Some");
assert_eq!(text, "Token使用量 85%,5小时滚动窗口");
}
#[test]
fn hint_uses_dynamic_window_hours() {
let info = mk_info(85.0, 1, "14:30");
let (text, _) = build_usage_hint_from_info(&info).expect("Some");
assert!(text.contains("1小时"), "got: {}", text);
}
}