fix(tui): perbaiki isi overlay Todo dan Usage jadi tampilan detail nyata

Overlay Todo sebelumnya menampilkan jumlah pesan yang tidak relevan,
bukan isi task list. Overlay Usage sekarang pakai compute_usage_summary
yang sama dengan widget sidebar (DRY, angka konsisten).

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
asepharyana
2026-07-14 23:41:53 +07:00
co-authored by Claude Sonnet 5
parent 7fd55fa86d
commit aaea300699
+67 -91
View File
@@ -360,25 +360,16 @@ fn render_overlay(
// ── Todo ────────────────────────────────────────────────────── // ── Todo ──────────────────────────────────────────────────────
crate::app::state::types::Overlay::Todo => { crate::app::state::types::Overlay::Todo => {
let block = block let block = block
.title(Span::styled(" 📋 Tasks ", Style::default().fg(Theme::ACCENT_PURPLE).add_modifier(Modifier::BOLD))) .title(Span::styled(" Tasks ", Style::default().fg(Theme::ACCENT_PURPLE).add_modifier(Modifier::BOLD)))
.border_style(Style::default().fg(Theme::ACCENT_PURPLE)); .border_style(Style::default().fg(Theme::ACCENT_PURPLE));
let msg_count = state.transcript_cache.messages.len(); let content = if state.misc.todo_content.is_empty() {
let lines = vec![ " No tasks yet."
Line::from(Span::styled( } else {
" Session Activity", &state.misc.todo_content
Style::default().fg(Theme::TEXT).add_modifier(Modifier::BOLD), };
)), let paragraph = Paragraph::new(content)
Line::from(Span::raw("")), .block(block)
Line::from(Span::styled( .wrap(Wrap { trim: false });
format!(" Messages: {msg_count}"),
Style::default().fg(Theme::INFO),
)),
Line::from(Span::styled(
format!(" Overlay: {:?}", state.misc.overlay),
Style::default().fg(Theme::TEXT_DIM),
)),
];
let paragraph = Paragraph::new(lines).block(block);
frame.render_widget(paragraph, overlay_area); frame.render_widget(paragraph, overlay_area);
} }
@@ -615,90 +606,75 @@ fn render_overlay(
// ── Usage ──────────────────────────────────────────────────── // ── Usage ────────────────────────────────────────────────────
crate::app::state::types::Overlay::Usage => { crate::app::state::types::Overlay::Usage => {
let block = block let block = block
.title(Span::styled(" 📊 Usage ", Style::default().fg(Theme::INFO).add_modifier(Modifier::BOLD))) .title(Span::styled(" Usage ", Style::default().fg(Theme::INFO).add_modifier(Modifier::BOLD)))
.border_style(Style::default().fg(Theme::INFO)); .border_style(Style::default().fg(Theme::INFO));
let runtime = state.session_runtime.as_ref(); let runtime = state.session_runtime.as_ref();
let (tokens_in, tokens_out, api_calls, review_tokens, session_start) = runtime let now_ms = chrono::Utc::now().timestamp_millis();
.map_or((0, 0, 0, 0, 0), |r| { let summary = runtime.map(|r| sidebar::compute_usage_summary(&r.usage, r.session_start, now_ms));
(
r.usage.tokens_in,
r.usage.tokens_out,
r.usage.api_calls,
r.usage.review_tokens,
r.session_start,
)
});
let (edit_count, lesson_count, review_count, consec_empty) = runtime let (edit_count, lesson_count, review_count, consec_empty) = runtime
.map_or((0, 0, 0, 0), |r| { .map_or((0, 0, 0, 0), |r| {
( (r.edit_count, r.lesson_count, r.review_count, r.consecutive_empty_reviews)
r.edit_count,
r.lesson_count,
r.review_count,
r.consecutive_empty_reviews,
)
}); });
let elapsed_ms = chrono::Utc::now().timestamp_millis().saturating_sub(session_start); let mut lines = vec![
let hours = elapsed_ms / 3_600_000;
let minutes = (elapsed_ms % 3_600_000) / 60_000;
let seconds = (elapsed_ms % 60000) / 1000;
let total_tokens = tokens_in.saturating_add(tokens_out);
let self_learning_total = review_tokens;
let main_tokens = total_tokens.saturating_sub(self_learning_total);
let lines = vec![
Line::from(Span::styled( Line::from(Span::styled(
" Token Usage", " Token Usage",
Style::default().fg(Theme::INFO).add_modifier(Modifier::BOLD), Style::default().fg(Theme::INFO).add_modifier(Modifier::BOLD),
)), )),
Line::from(Span::raw("")), Line::from(Span::raw("")),
Line::from(Span::styled(
format!(" Main agent: {main_tokens} tokens"),
Style::default().fg(Theme::TEXT),
)),
Line::from(Span::styled(
format!(" Self-learning: {self_learning_total} tokens"),
Style::default().fg(Theme::TEXT_MUTED),
)),
Line::from(Span::styled(
format!(" Total: {total_tokens} tokens"),
Style::default().fg(Theme::TEXT).add_modifier(Modifier::BOLD),
)),
Line::from(Span::styled(
format!(" API calls: {api_calls}"),
Style::default().fg(Theme::TEXT),
)),
Line::from(Span::raw("")),
Line::from(Span::styled(
" Activity",
Style::default().fg(Theme::INFO).add_modifier(Modifier::BOLD),
)),
Line::from(Span::styled(
format!(" Edits: {edit_count}"),
Style::default().fg(Theme::TEXT),
)),
Line::from(Span::styled(
format!(" Reviews: {review_count}"),
Style::default().fg(Theme::TEXT),
)),
Line::from(Span::styled(
format!(" Lessons: {lesson_count}"),
Style::default().fg(Theme::TEXT_MUTED),
)),
Line::from(Span::styled(
format!(" Empty reviews: {}",
if consec_empty > 3 {
format!("{consec_empty}")
} else {
consec_empty.to_string()
},
),
Style::default().fg(if consec_empty > 3 { Theme::WARNING } else { Theme::TEXT_DIM }),
)),
Line::from(Span::raw("")),
Line::from(Span::styled(
format!(" Session: {hours}h {minutes}m {seconds}s"),
Style::default().fg(Theme::TEXT_DIM),
)),
]; ];
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: {}",
if consec_empty > 3 { format!("{consec_empty}") } else { consec_empty.to_string() },
),
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); let paragraph = Paragraph::new(lines).block(block);
frame.render_widget(paragraph, overlay_area); frame.render_widget(paragraph, overlay_area);
} }