Files
zesdex/apps/interfaces/tui/src/view/overlays/usage.rs
T
asepharyana 7b0b53671f style: format seluruh workspace dengan cargo fmt
Menyeragamkan format kode sesuai rustfmt (126 file). Sebelumnya
lefthook pre-commit 'cargo fmt --check' akan gagal pada commit apa pun.
2026-08-27 22:10:28 +07:00

115 lines
3.9 KiB
Rust

//! Overlay: usage statistics — detailed token usage, API call count, edit/
//! review/lesson activity counters, and session elapsed time.
use crate::view::sidebar::compute_usage_summary;
use crate::view::theme::Theme;
use ratatui::style::{Modifier, Style};
use ratatui::text::{Line, Span};
use ratatui::widgets::{Block, Paragraph};
use ratatui::Frame;
use tracing::debug;
/// Render the Usage overlay.
#[tracing::instrument(skip_all)]
pub fn render(
frame: &mut Frame,
area: ratatui::layout::Rect,
block: Block<'static>,
state: &crate::state::AppStateRest,
) {
let has_runtime = state.session_runtime.is_some();
debug!("Rendering Usage overlay, has runtime: {has_runtime}");
let block = block
.title(Span::styled(
" Usage ",
Style::default()
.fg(Theme::INFO)
.add_modifier(Modifier::BOLD),
))
.border_style(Style::default().fg(Theme::INFO));
let runtime = state.session_runtime.as_ref();
let now_ms = chrono::Utc::now().timestamp_millis();
let summary = runtime.map(|r| compute_usage_summary(&r.usage, r.session_start, now_ms));
let (edit_count, lesson_count, review_count, consec_empty) =
runtime.map_or((0, 0, 0, 0), |r| {
(
r.edit_count,
r.lessons.total,
r.review_count,
r.consecutive_empty_reviews,
)
});
let mut lines = vec![
Line::from(Span::styled(
" Token Usage",
Style::default()
.fg(Theme::INFO)
.add_modifier(Modifier::BOLD),
)),
Line::from(Span::raw("")),
];
if let Some(s) = &summary {
lines.push(Line::from(Span::styled(
format!(" Main agent: {} tokens", s.main_tokens),
Style::default().fg(Theme::TEXT),
)));
lines.push(Line::from(Span::styled(
format!(" Self-learning: {} tokens", s.self_learning_tokens),
Style::default().fg(Theme::TEXT_MUTED),
)));
lines.push(Line::from(Span::styled(
format!(" Total: {} tokens", s.total_tokens),
Style::default()
.fg(Theme::TEXT)
.add_modifier(Modifier::BOLD),
)));
lines.push(Line::from(Span::styled(
format!(" API calls: {}", s.api_calls),
Style::default().fg(Theme::TEXT),
)));
} else {
lines.push(Line::from(Span::styled(
" No active session.",
Style::default().fg(Theme::TEXT_DIM),
)));
}
lines.push(Line::from(Span::raw("")));
lines.push(Line::from(Span::styled(
" Activity",
Style::default()
.fg(Theme::INFO)
.add_modifier(Modifier::BOLD),
)));
lines.push(Line::from(Span::styled(
format!(" Edits: {edit_count}"),
Style::default().fg(Theme::TEXT),
)));
lines.push(Line::from(Span::styled(
format!(" Reviews: {review_count}"),
Style::default().fg(Theme::TEXT),
)));
lines.push(Line::from(Span::styled(
format!(" Lessons: {lesson_count}"),
Style::default().fg(Theme::TEXT_MUTED),
)));
lines.push(Line::from(Span::styled(
format!(" Empty reviews: {consec_empty}"),
Style::default().fg(if consec_empty > 3 {
Theme::WARNING
} else {
Theme::TEXT_DIM
}),
)));
if let Some(s) = &summary {
lines.push(Line::from(Span::raw("")));
lines.push(Line::from(Span::styled(
format!(
" Session: {}h {}m {}s",
s.elapsed_hours, s.elapsed_minutes, s.elapsed_seconds
),
Style::default().fg(Theme::TEXT_DIM),
)));
}
let paragraph = Paragraph::new(lines).block(block);
frame.render_widget(paragraph, area);
}