feat(llm): improve UTF-8 handling in response processing to prevent infinite loops

feat(shell): enhance output capturing by using threads for stdout and stderr
feat(tui): update usage widget to display token counts and provider/model information
refactor(tui): simplify status bar rendering by removing unnecessary token calculations
This commit is contained in:
asepharyana
2026-07-20 16:16:26 +07:00
parent 6bef4a3f82
commit 7ea226509c
4 changed files with 68 additions and 45 deletions
+27 -4
View File
@@ -96,19 +96,40 @@ fn draw_usage_widget(frame: &mut Frame, area: Rect, state: &crate::state::AppSta
let lines: Vec<Line> = if let Some(ref rt) = state.session_runtime {
let now_ms = chrono::Utc::now().timestamp_millis();
let summary = compute_usage_summary(&rt.usage, rt.session_start, now_ms);
vec![
let max_tokens = crate::state::resolve_context_window(&state.app_config, &state.settings);
let current_tokens = state.cached_token_count;
let mut items = vec![
Line::from(Span::styled(
format!(" {:>6}: {} tok", "total", summary.total_tokens),
Style::default()
.fg(Theme::TEXT)
.add_modifier(Modifier::BOLD),
)),
Line::from(Span::styled(
];
if summary.self_learning_tokens > 0 {
items.push(Line::from(Span::styled(
format!(" {:>6}: {} tok", "main", summary.main_tokens),
Style::default().fg(Theme::TEXT_DIM),
)));
items.push(Line::from(Span::styled(
format!(" {:>6}: {} tok", "learn", summary.self_learning_tokens),
Style::default().fg(Theme::TEXT_DIM),
)));
}
items.extend(vec![
Line::from(Span::styled(
format!(" {:>6}: {}/{}", "ctx", current_tokens, max_tokens),
Style::default().fg(Theme::TEXT_DIM),
)),
Line::from(Span::styled(
format!(" {:>6}: {} tok", "learn", summary.self_learning_tokens),
format!(" {:>6}: {}", "prov", state.settings.provider),
Style::default().fg(Theme::TEXT_DIM),
)),
Line::from(Span::styled(
format!(" {:>6}: {}", "model", state.settings.model),
Style::default().fg(Theme::TEXT_DIM),
)),
Line::from(Span::styled(
@@ -122,7 +143,9 @@ fn draw_usage_widget(frame: &mut Frame, area: Rect, state: &crate::state::AppSta
),
Style::default().fg(Theme::TEXT_DIM),
)),
]
]);
items
} else {
vec![Line::from(Span::styled(
" No active session.",
+1 -26
View File
@@ -48,32 +48,7 @@ pub fn draw_status_bar(frame: &mut Frame, area: Rect, state: &crate::state::AppS
status_badge,
];
let max_tokens = crate::state::resolve_context_window(&state.app_config, &state.settings);
let right_str = if let Some(ref rt) = state.session_runtime {
// Use cached token count — recomputed lazily only when new messages
// arrive (token_count_dirty flag), not on every render frame.
// This eliminates the expensive tiktoken_rs call from the hot path.
let current_tokens = state.cached_token_count;
let mut parts = Vec::new();
if rt.usage.last_tokens_in > 0 || rt.usage.last_tokens_out > 0 {
parts.push(format!(
"{}{}",
rt.usage.last_tokens_in, rt.usage.last_tokens_out
));
}
parts.push(format!("{current_tokens}/{max_tokens}"));
parts.push(state.settings.provider.clone());
parts.push(state.settings.model.clone());
format!(" {} ", parts.join(" · "))
} else {
format!(
" 0/{max_tokens} · {} · {} ",
state.settings.provider, state.settings.model
)
};
let right_str = String::new();
let left_line = Line::from(left_spans);
let right_line = Line::from(Span::styled(